# ๐Ÿ”’ Security ์ด ๋ฌธ์„œ๋Š” Meeting Minutes์˜ **์œ„ํ˜‘ ๋ชจ๋ธ, ์•Œ๋ ค์ง„ ํ•œ๊ณ„, ์•ˆ์ „ํ•œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•**์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค๋ฉด [์‹ ๊ณ  ๋ฐฉ๋ฒ•](#-์ทจ์•ฝ์ -์‹ ๊ณ )์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”. --- ## ๐Ÿ“‘ ๋ชฉ์ฐจ - [์œ„ํ˜‘ ๋ชจ๋ธ](#-์œ„ํ˜‘-๋ชจ๋ธ) - [์‹ ๋ขฐ ๋ชจ๋ธ](#-์‹ ๋ขฐ-๋ชจ๋ธ) - [๋ฐ์ดํ„ฐ ํ๋ฆ„๊ณผ ์™ธ๋ถ€ ์†ก์ถœ](#-๋ฐ์ดํ„ฐ-ํ๋ฆ„๊ณผ-์™ธ๋ถ€-์†ก์ถœ) - [๊ตฌํ˜„๋œ ๋ณด์•ˆ ์กฐ์น˜](#-๊ตฌํ˜„๋œ-๋ณด์•ˆ-์กฐ์น˜) - [์•Œ๋ ค์ง„ ํ•œ๊ณ„](#-์•Œ๋ ค์ง„-ํ•œ๊ณ„) - [๋ฐฐํฌ ๊ถŒ์žฅ ์‚ฌํ•ญ](#-๋ฐฐํฌ-๊ถŒ์žฅ-์‚ฌํ•ญ) - [์ทจ์•ฝ์  ์‹ ๊ณ ](#-์ทจ์•ฝ์ -์‹ ๊ณ ) --- ## ๐ŸŽฏ ์œ„ํ˜‘ ๋ชจ๋ธ ์ด ํ”„๋กœ์ ํŠธ๋Š” **๊ฐœ์ธ์šฉ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค**๋ฅผ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์œ„ํ˜‘ ๋ชจ๋ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: ### In-scope (๋ฐฉ์–ด ๋Œ€์ƒ) - ๊ฐ™์€ LAN/Wifi์— ์žˆ๋Š” ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๋…ธํŠธ๋ถ์— ์ ‘๊ทผํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค - ๊ณต์œ  PC์—์„œ ์ž ์‹œ ์ž๋ฆฌ๋ฅผ ๋น„์› ์„ ๋•Œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค - ๋งˆํฌ๋‹ค์šด์— ์‚ฝ์ž…๋œ ์•…์„ฑ HTML/์Šคํฌ๋ฆฝํŠธ๋กœ ์ธํ•œ XSS - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋น„์ •์ƒ ์ž…๋ ฅ (ํฐ ํ…์ŠคํŠธ, ์ž˜๋ชป๋œ MIME ๋“ฑ) ### Out-of-scope (๋ฐฉ์–ดํ•˜์ง€ ์•Š์Œ) - **๋‹ค์ค‘ ์‚ฌ์šฉ์ž / ์ธ์ฆ** โ€” ๋‹จ์ผ ์‚ฌ์šฉ์ž ๊ฐ€์ •์œผ๋กœ ์ธ์ฆ ์‹œ์Šคํ…œ ๋ฏธ๊ตฌํ˜„ - **ํ˜ธ์ŠคํŠธ OS / ๋””์Šคํฌ ์•”ํ˜ธํ™”** โ€” ์šด์˜์ฒด์ œ์™€ ๋””์Šคํฌ ์•”ํ˜ธํ™” ์„ค์ •์— ์œ„์ž„ - **ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ ๋ฐฐํฌ** โ€” localhost ์ „์šฉ์œผ๋กœ ์„ค๊ณ„๋จ - **๋ฌผ๋ฆฌ์  ์ ‘๊ทผ ํ†ต์ œ** โ€” ์‚ฌ์šฉ์ž PC์— ๋Œ€ํ•œ ๋ฌผ๋ฆฌ์  ๋ณด์•ˆ --- ## ๐Ÿ” ์‹ ๋ขฐ ๋ชจ๋ธ | ์‹ ๋ขฐ ์ˆ˜์ค€ | ํ•ญ๋ชฉ | |---|---| | **์‹ ๋ขฐ** | ํ˜ธ์ŠคํŠธ OS, Docker, ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐœ๊ธ‰ํ•œ Gemini API ํ‚ค, npm ํŒจํ‚ค์ง€ (`marked`, `DOMPurify`, Prisma ๋“ฑ) | | **๋ฐ˜์‹ ๋ขฐ** | ์‚ฌ์šฉ์ž ์ž…๋ ฅ transcript / markdown (DOMPurify๋กœ sanitize) | | **์‹ ๋ขฐ ์•ˆ ํ•จ** | ๊ฐ™์€ ๋„คํŠธ์›Œํฌ์˜ ๋‹ค๋ฅธ ์žฅ์น˜, ์™ธ๋ถ€ HTTP ์š”์ฒญ, Web Speech API ๊ฒฐ๊ณผ | --- ## ๐Ÿ“ก ๋ฐ์ดํ„ฐ ํ๋ฆ„๊ณผ ์™ธ๋ถ€ ์†ก์ถœ | ๋ฐ์ดํ„ฐ | ์ €์žฅ ์œ„์น˜ | ์™ธ๋ถ€ ์†ก์ถœ | |---|---|---| | ํšŒ์˜ transcript / ํšŒ์˜๋ก | PostgreSQL (`meetings.markdownMinutes`, `rawTranscript`) | Gemini AI ์š”์•ฝ ํ™œ์„ฑํ™” ์‹œ transcript๊ฐ€ Google๋กœ ์ „์†ก | | ์•ก์…˜ ์•„์ดํ…œ | PostgreSQL (`action_items`) | ์™ธ๋ถ€ ์†ก์ถœ ์—†์Œ | | Gemini API ํ‚ค | ๋ธŒ๋ผ์šฐ์ € LocalStorage ๋˜๋Š” `.env` | ์š”์ฒญ ์‹œ Google์—๋งŒ ์ „์†ก | | ์Œ์„ฑ ๋ฐ์ดํ„ฐ | ๋ฉ”๋ชจ๋ฆฌ (์‹ค์‹œ๊ฐ„), `/app/uploads` (ํŒŒ์ผ ์—…๋กœ๋“œ) | **Chrome Web Speech API โ†’ Google ์„œ๋ฒ„** โš ๏ธ | ### โš ๏ธ ์ฃผ์˜: Web Speech API์˜ ์Œ์„ฑ ์™ธ๋ถ€ ์ „์†ก Chrome์˜ `SpeechRecognition` API๋Š” **์Œ์„ฑ ๋ฐ์ดํ„ฐ๋ฅผ Google ์„œ๋ฒ„๋กœ ์ „์†กํ•˜์—ฌ ์ฒ˜๋ฆฌ**ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” Chrome ์ž์ฒด์˜ ๋™์ž‘์ด๋ฉฐ ์šฐ๋ฆฌ ์„œ๋ฒ„๋ฅผ ๊ฒฝ์œ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ์„ ์žฌ๊ณ ํ•˜์„ธ์š”: - ํšŒ์˜๊ฐ€ ํšŒ์‚ฌ ๊ธฐ๋ฐ€, ์ธ์‚ฌ ์ •๋ณด, ํ™˜์ž ์ •๋ณด ๋“ฑ์„ ํฌํ•จ - ์‚ฌ๋‚ด ์ปดํ”Œ๋ผ์ด์–ธ์Šค ์ •์ฑ…์ด ์™ธ๋ถ€ ํด๋ผ์šฐ๋“œ ์Œ์„ฑ ์ „์†ก์„ ๊ธˆ์ง€ - GDPR / HIPAA ๋“ฑ ๊ทœ์ œ ํ™˜๊ฒฝ ์ด ๊ฒฝ์šฐ **ํŒŒ์ผ ์—…๋กœ๋“œ ํƒญ**๋„ ๊ฐ™์€ ํ•œ๊ณ„๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ(ํ˜„์žฌ ์—…๋กœ๋“œ ํ›„ ์ „์‚ฌ๋Š” ๋ฏธ๊ตฌํ˜„, Phase 4์—์„œ ์ž์ฒด STT ๊ฒ€ํ†  ์˜ˆ์ •), ์ด ๋„๊ตฌ์˜ ์‚ฌ์šฉ์„ ๋ณด๋ฅ˜ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. --- ## โœ… ๊ตฌํ˜„๋œ ๋ณด์•ˆ ์กฐ์น˜ ### ์ธํ”„๋ผ - **Postgres ํฌํŠธ ๋ฏธ๋…ธ์ถœ** โ€” `db` ์ปจํ…Œ์ด๋„ˆ๋Š” `app`๋งŒ ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ๋กœ ์ ‘๊ทผ, ํ˜ธ์ŠคํŠธ ํฌํŠธ ์•ˆ ์—ด๋ฆผ - **App ํฌํŠธ localhost ๋ฐ”์ธ๋”ฉ** โ€” `127.0.0.1:3000` (LAN์˜ ๋‹ค๋ฅธ ์žฅ์น˜๋Š” ์ ‘๊ทผ ๋ถˆ๊ฐ€) - **DB ๋น„๋ฐ€๋ฒˆํ˜ธ fallback ์ œ๊ฑฐ** โ€” `.env`์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผ docker-compose ์‹œ์ž‘๋จ - **Prisma adapter ์‚ฌ์šฉ** โ€” Prisma 7 `@prisma/adapter-pg`๋กœ ์•ˆ์ „ํ•œ PostgreSQL ์—ฐ๊ฒฐ ### ์‘๋‹ต ํ—ค๋” `next.config.ts`์—์„œ ๋‹ค์Œ ํ—ค๋” ์ ์šฉ: - `X-Content-Type-Options: nosniff` โ€” MIME sniffing ์ฐจ๋‹จ - `X-Frame-Options: DENY` โ€” ํด๋ฆญ์žฌํ‚น ๋ฐฉ์ง€ - `Referrer-Policy: strict-origin-when-cross-origin` - `Permissions-Policy: camera=(), microphone=(self), geolocation=(), interest-cohort=()` ### ์ž…๋ ฅ ๊ฒ€์ฆ - **๋งˆํฌ๋‹ค์šด**: `marked` โ†’ `isomorphic-dompurify` ํ†ต๊ณผ - `USE_PROFILES: { html: true }` - `FORBID_TAGS: ['style']`, `FORBID_ATTR: ['style']` - `