Skip to Content
스킬보안 리뷰

보안 리뷰

다운로드 후 ~/.claude/skills/ 폴더에 복사하여 사용하세요

모든 코드가 보안 모범 사례를 따르고 잠재적 취약점을 식별하도록 보장합니다.

활성화 시점

  • 인증 또는 인가 구현
  • 사용자 입력 또는 파일 업로드 처리
  • 새 API 엔드포인트 생성
  • 시크릿 또는 자격 증명 작업
  • 결제 기능 구현
  • 민감한 데이터 저장 또는 전송

보안 체크리스트

1. 시크릿 관리

const apiKey = process.env.OPENAI_API_KEY const dbUrl = process.env.DATABASE_URL // 시크릿 존재 확인 if (!apiKey) { throw new Error('OPENAI_API_KEY가 설정되지 않았습니다') }

검증 단계:

  • 하드코딩된 API 키, 토큰, 비밀번호 없음
  • 모든 시크릿이 환경 변수에 있음
  • .env.local이 .gitignore에 있음
  • git 히스토리에 시크릿 없음

2. 입력 검증

import { z } from 'zod' // 검증 스키마 정의 const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(100), age: z.number().int().min(0).max(150) }) // 처리 전 검증 export async function createUser(input: unknown) { try { const validated = CreateUserSchema.parse(input) return await db.users.create(validated) } catch (error) { if (error instanceof z.ZodError) { return { success: false, errors: error.errors } } throw error } }

파일 업로드 검증

function validateFileUpload(file: File) { // 크기 검사 (최대 5MB) const maxSize = 5 * 1024 * 1024 if (file.size > maxSize) { throw new Error('파일이 너무 큽니다 (최대 5MB)') } // 타입 검사 const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] if (!allowedTypes.includes(file.type)) { throw new Error('잘못된 파일 타입') } return true }

3. SQL 인젝션 방지

절대 SQL을 연결하지 마세요!

// 파라미터화된 쿼리 const { data } = await supabase .from('users') .select('*') .eq('email', userEmail) // 또는 원시 SQL로 await db.query( 'SELECT * FROM users WHERE email = $1', [userEmail] )

4. 인증 & 인가

JWT 토큰 처리

// httpOnly 쿠키 res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)

인가 검사

export async function deleteUser(userId: string, requesterId: string) { // 항상 먼저 인가 검증 const requester = await db.users.findUnique({ where: { id: requesterId } }) if (requester.role !== 'admin') { return NextResponse.json( { error: '권한이 없습니다' }, { status: 403 } ) } // 삭제 진행 await db.users.delete({ where: { id: userId } }) }

Row Level Security (Supabase)

-- 모든 테이블에 RLS 활성화 ALTER TABLE users ENABLE ROW LEVEL SECURITY; -- 사용자는 자신의 데이터만 볼 수 있음 CREATE POLICY "Users view own data" ON users FOR SELECT USING (auth.uid() = id);

5. XSS 방지

HTML 새니타이즈

import DOMPurify from 'isomorphic-dompurify' // 항상 사용자 제공 HTML 새니타이즈 function renderUserContent(html: string) { const clean = DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'], ALLOWED_ATTR: [] }) return <div dangerouslySetInnerHTML={{ __html: clean }} /> }

Content Security Policy

// next.config.js const securityHeaders = [ { key: 'Content-Security-Policy', value: ` default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; `.replace(/\s{2,}/g, ' ').trim() } ]

6. CSRF 보호

import { csrf } from '@/lib/csrf' export async function POST(request: Request) { const token = request.headers.get('X-CSRF-Token') if (!csrf.verify(token)) { return NextResponse.json( { error: '잘못된 CSRF 토큰' }, { status: 403 } ) } // 요청 처리 }

7. 레이트 리미팅

import rateLimit from 'express-rate-limit' const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15분 max: 100, // 윈도우당 100 요청 message: '요청이 너무 많습니다' }) // 라우트에 적용 app.use('/api/', limiter)

8. 민감한 데이터 노출

// 민감한 데이터 마스킹 console.log('User login:', { email, userId }) console.log('Payment:', { last4: card.last4, userId }) // 일반적인 오류 메시지 catch (error) { console.error('Internal error:', error) return NextResponse.json( { error: '오류가 발생했습니다. 다시 시도해 주세요.' }, { status: 500 } ) }

9. 의존성 보안

# 취약점 확인 npm audit # 자동 수정 가능한 이슈 수정 npm audit fix # 의존성 업데이트 npm update # CI/CD에서 재현 가능한 빌드를 위해 사용 npm ci # npm install 대신

배포 전 보안 체크리스트

모든 프로덕션 배포 전:

  • 시크릿: 하드코딩된 시크릿 없음
  • 입력 검증: 모든 사용자 입력 검증됨
  • SQL 인젝션: 모든 쿼리 파라미터화됨
  • XSS: 사용자 컨텐츠 새니타이즈됨
  • CSRF: 보호 활성화됨
  • 인증: 적절한 토큰 처리
  • 인가: 역할 검사 적용됨
  • 레이트 리미팅: 모든 엔드포인트에 활성화됨
  • HTTPS: 프로덕션에서 강제됨
  • 보안 헤더: CSP, X-Frame-Options 설정됨
  • 오류 처리: 오류에 민감한 데이터 없음
  • 로깅: 로그에 민감한 데이터 없음
  • 의존성: 최신 상태, 취약점 없음

리소스

보안은 선택사항이 아닙니다. 하나의 취약점이 전체 플랫폼을 위험에 빠뜨릴 수 있습니다. 확신이 없으면 신중하게 선택하세요.

Last updated on