Optimization convention-security-hardening
보안 강화(Security Hardening) 컨벤션 참조 스킬. 입력 검증, SQL injection 방지, XSS 방지, 암호화, 권한 관리 등 프로덕션 배포 전 필수 보안 조치를 제공한다.
install
source · Clone the upstream repo
git clone https://github.com/sunLeee/optimization
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/sunLeee/optimization "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/reference/convention/convention-security-hardening" ~/.claude/skills/sunleee-optimization-convention-security-hardening && rm -rf "$T"
manifest:
.claude/skills/reference/convention/convention-security-hardening/SKILL.mdsource content
보안 강화(Security Hardening) 컨벤션
프로덕션 배포 전 필수 보안 조치.
목적
- OWASP Top 10 취약점 방지
- 입력 검증 및 정제
- 데이터 보호 (암호화)
- 접근 제어 (권한 관리)
- 감시 및 로깅
사용법
/convention-security-hardening [영역]
| 영역 | 설명 |
|---|---|
| 입력 검증 및 정제 |
| SQL Injection 방지 |
| XSS, CSRF 방지 |
| 인증 및 권한 관리 |
| 암호화 및 해싱 |
| 전체 (기본값) |
1. 입력 검증 (Input Validation)
1.1 기본 원칙
규칙: 모든 외부 입력을 신뢰하지 않는다 (Whitelist 방식)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """보안 강화 검증 모듈. Author: taeyang lee Created: 2026-01-21 16:00(KST, UTC+09:00) Modified: 2026-01-21 16:00(KST, UTC+09:00) Version: 1.0.0 """ import re from typing import Any import logging logger = logging.getLogger(__name__) def validate_email(email: str) -> bool: """이메일 형식을 검증한다. Args: email (str): 검증할 이메일 주소. Returns: bool: 유효하면 True, 아니면 False. Logics: 1. 이메일 길이 확인 (최대 254자). 2. 정규표현식으로 형식 검증. 3. 실제 메일 서버 존재 확인 (선택). Example: >>> validate_email('user@example.com') True >>> validate_email('invalid-email') False """ # 길이 확인 if not email or len(email) > 254: return False # 정규표현식 (RFC 5322 간소화) pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(pattern, email): return False logger.info(f"유효한 이메일: {email}") return True def sanitize_string( value: str, max_length: int = 256, allowed_chars: str = r'^[a-zA-Z0-9\s\-_.]*$', ) -> str: """문자열을 정제(sanitize)한다. Args: value (str): 정제할 문자열. max_length (int): 최대 길이. 기본값 256. allowed_chars (str): 허용 문자 정규표현식. Returns: str: 정제된 문자열. Raises: ValueError: 검증 실패 시 발생. Logics: 1. Whitelist 방식으로 허용 문자만 추출. 2. 길이 제한 적용. 3. 위험 문자 제거 (스크립트 태그 등). Example: >>> sanitize_string('<script>alert(1)</script>') '' # 스크립트 태그 제거 """ if not isinstance(value, str): raise ValueError("입력은 문자열이어야 함") # 위험 문자 제거 value = value.strip() # Whitelist 방식 필터링 if not re.match(allowed_chars, value): raise ValueError( f"허용되지 않는 문자 포함: {value}" ) # 길이 제한 if len(value) > max_length: raise ValueError( f"길이 초과: {len(value)} > {max_length}" ) logger.info(f"정제됨: {value}") return value
1.2 Pydantic을 이용한 검증
from pydantic import BaseModel, EmailStr, constr, Field class UserInput(BaseModel): """사용자 입력 검증 스키마. Pydantic으로 입력값 자동 검증. """ # 이메일: 유효한 형식 필수 email: EmailStr # 이름: 3~50자, 문자와 공백만 name: constr(min_length=3, max_length=50, regex=r'^[a-zA-Z\s]+$') # 나이: 18~120 age: int = Field(ge=18, le=120) # 전화번호: 형식 검증 phone: constr(regex=r'^\d{10,11}$') class Config: # 여분 필드 거부 extra = 'forbid' def process_user_input(data: dict) -> UserInput: """사용자 입력을 검증하고 처리한다. Args: data (dict): 입력 데이터. Returns: UserInput: 검증된 데이터. Raises: ValidationError: 검증 실패 시. Logics: 1. Pydantic 모델로 검증. 2. 실패하면 ValidationError 발생. 3. 검증 통과시 안전한 데이터 반환. Example: >>> process_user_input({ ... 'email': 'user@example.com', ... 'name': 'John Doe', ... 'age': 25, ... 'phone': '01012345678' ... }) # UserInput 객체 반환 """ try: validated = UserInput(**data) logger.info(f"입력 검증 성공: {validated}") return validated except Exception as e: logger.error(f"입력 검증 실패: {e}") raise
2. SQL Injection 방지
2.1 Parameterized Query (매개변수화 쿼리)
규칙: 절대 문자열 연결로 쿼리 작성 금지
import psycopg2 from psycopg2 import sql def find_user_safe( connection, email: str, ) -> dict: """SQL Injection을 방지하면서 사용자를 찾는다. Args: connection: DB 연결. email (str): 찾을 이메일. Returns: dict: 사용자 정보. Logics: 1. Parameterized query 사용. 2. 플레이스홀더(?)에 값 주입. 3. DB 드라이버가 자동 이스케이프. Example: >>> # ❌ 나쁜 예 (SQL Injection 취약) >>> query = f"SELECT * FROM users WHERE email='{email}'" >>> # ✅ 좋은 예 (안전) >>> query = "SELECT * FROM users WHERE email=%s" >>> cursor.execute(query, (email,)) """ # ✅ 올바른 방법: Parameterized query cursor = connection.cursor() query = sql.SQL( "SELECT id, email, name FROM users WHERE email=%s" ) try: cursor.execute(query, (email,)) result = cursor.fetchone() if result: return { 'id': result[0], 'email': result[1], 'name': result[2] } else: return None except Exception as e: logger.error(f"DB 조회 오류: {e}") raise finally: cursor.close() # ❌ 절대 금지 (SQL Injection 취약) def find_user_unsafe(connection, email: str): """위험한 쿼리 작성 (절대 금지)""" cursor = connection.cursor() # 이 방법은 SQL Injection 취약점 발생! query = f"SELECT * FROM users WHERE email='{email}'" cursor.execute(query) return cursor.fetchone()
SQL Injection 예시:
# 정상 입력 email = "user@example.com" # 쿼리: SELECT * FROM users WHERE email='user@example.com' # 공격자 입력 email = "' OR '1'='1" # 쿼리: SELECT * FROM users WHERE email='' OR '1'='1' # → 모든 사용자 조회 (심각!)
3. XSS (Cross-Site Scripting) 방지
3.1 HTML 이스케이핑
규칙: 사용자 입력을 HTML에 렌더링하기 전에 이스케이프
from markupsafe import escape import html def render_user_comment( comment: str, ) -> str: """사용자 댓글을 안전하게 렌더링한다. Args: comment (str): 원본 댓글. Returns: str: 이스케이프된 HTML. Logics: 1. HTML 특수 문자를 엔티티로 변환. 2. 스크립트 태그 무효화. 3. 안전한 HTML 생성. Example: >>> render_user_comment('<script>alert(1)</script>') '<script>alert(1)</script>' """ # ❌ 위험 (XSS 취약) # html_output = f"<p>{comment}</p>" # ✅ 안전 (이스케이프) safe_comment = escape(comment) html_output = f"<p>{safe_comment}</p>" return html_output # 예시 comment = '<script>alert("XSS Attack")</script>' safe_html = render_user_comment(comment) print(safe_html) # 출력: <p><script>alert("XSS Attack")</script></p>
3.2 CSRF (Cross-Site Request Forgery) 방지
웹 프로젝트에서 필수
from flask import Flask, request, session app = Flask(__name__) app.secret_key = 'your-secret-key' @app.route('/form', methods=['GET']) def show_form(): """CSRF 토큰을 포함한 폼을 표시한다. Logics: 1. 세션에 CSRF 토큰 생성. 2. 폼에 숨겨진 필드로 토큰 포함. 3. POST 시 토큰 검증. """ from uuid import uuid4 # CSRF 토큰 생성 (없으면) if '_csrf_token' not in session: session['_csrf_token'] = str(uuid4()) return f""" <form method="POST" action="/submit"> <input type="hidden" name="_csrf_token" value="{session['_csrf_token']}"> <input type="text" name="data"> <button type="submit">제출</button> </form> """ @app.route('/submit', methods=['POST']) def submit_form(): """CSRF 토큰을 검증한 후 처리한다. Logics: 1. 요청의 토큰 추출. 2. 세션의 토큰과 비교. 3. 일치하면 처리, 아니면 거부. """ token = request.form.get('_csrf_token') # ✅ CSRF 검증 if token != session.get('_csrf_token'): return "CSRF 토큰 불일치", 403 # 안전하게 처리 data = request.form.get('data') return f"처리됨: {escape(data)}"
4. 인증 & 권한 관리 (Authentication & Authorization)
4.1 비밀번호 해싱
규칙: 비밀번호는 평문으로 저장하지 않는다
from werkzeug.security import generate_password_hash, check_password_hash import bcrypt def hash_password_safe( password: str, method: str = 'bcrypt', ) -> str: """비밀번호를 안전하게 해싱한다. Args: password (str): 평문 비밀번호. method (str): 해싱 방법 ('bcrypt', 'scrypt', 'pbkdf2'). Returns: str: 해싱된 비밀번호. Logics: 1. Bcrypt 또는 scrypt 사용 (느린 알고리즘 권장). 2. Salt 자동 포함. 3. 반복 횟수 설정 (기본 12). Example: >>> hashed = hash_password_safe('MyPassword123!') >>> # 저장: UPDATE users SET password=hashed """ if method == 'bcrypt': # Bcrypt: 가장 안전한 방법 hashed = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt(rounds=12) # 12 라운드 ).decode('utf-8') else: # werkzeug (Django 호환) hashed = generate_password_hash( password, method='pbkdf2:sha256', salt_length=16 ) return hashed def verify_password( password: str, hashed: str, ) -> bool: """입력한 비밀번호가 해시된 비밀번호와 일치하는지 확인. Args: password (str): 입력 비밀번호. hashed (str): 저장된 해시. Returns: bool: 일치하면 True. Logics: 1. 입력 비밀번호를 같은 방식으로 해싱. 2. 해시 비교 (시간 공격 방지). """ try: # Bcrypt 검증 return bcrypt.checkpw( password.encode('utf-8'), hashed.encode('utf-8') ) except Exception: # 대체: werkzeug 검증 return check_password_hash(hashed, password)
4.2 JWT 기반 토큰 인증
from datetime import datetime, timedelta import jwt def create_jwt_token( user_id: int, email: str, secret_key: str, expires_in: int = 3600, # 1시간 ) -> str: """JWT 토큰을 생성한다. Args: user_id (int): 사용자 ID. email (str): 사용자 이메일. secret_key (str): 서명 키 (환경 변수에서). expires_in (int): 만료 시간 (초). Returns: str: JWT 토큰. Logics: 1. 페이로드 생성 (user_id, email, exp). 2. HS256으로 서명. 3. 토큰 반환. """ payload = { 'user_id': user_id, 'email': email, 'exp': datetime.utcnow() + timedelta( seconds=expires_in ), 'iat': datetime.utcnow() } token = jwt.encode( payload, secret_key, algorithm='HS256' ) return token def verify_jwt_token( token: str, secret_key: str, ) -> dict: """JWT 토큰을 검증한다. Args: token (str): JWT 토큰. secret_key (str): 서명 키. Returns: dict: 페이로드 (user_id, email 등). Raises: jwt.InvalidTokenError: 토큰 무효 시. Logics: 1. 토큰 서명 검증. 2. 만료 시간 확인. 3. 페이로드 반환. """ try: payload = jwt.decode( token, secret_key, algorithms=['HS256'] ) return payload except jwt.ExpiredSignatureError: raise ValueError("토큰 만료됨") except jwt.InvalidTokenError as e: raise ValueError(f"토큰 무효: {e}")
5. 암호화 (Encryption)
5.1 민감한 데이터 암호화
from cryptography.fernet import Fernet import os def encrypt_sensitive_data( data: str, key: str = None, ) -> str: """민감한 데이터를 암호화한다. Args: data (str): 암호화할 데이터. key (str): 암호화 키 (없으면 환경변수에서). Returns: str: 암호화된 데이터 (Base64). Logics: 1. 키 생성 또는 로드. 2. Fernet으로 대칭 암호화. 3. 암호문 반환. Example: >>> encrypted = encrypt_sensitive_data('user_ssn') >>> # 저장: UPDATE users SET ssn=encrypted """ if not key: key = os.getenv('ENCRYPTION_KEY') if not key: # 키 생성 (첫 실행 시만) key = Fernet.generate_key().decode() print(f"생성된 키: {key}") cipher = Fernet(key.encode()) encrypted = cipher.encrypt(data.encode()) return encrypted.decode() def decrypt_sensitive_data( encrypted_data: str, key: str = None, ) -> str: """암호화된 데이터를 복호화한다. Args: encrypted_data (str): 암호화된 데이터. key (str): 암호화 키. Returns: str: 평문 데이터. """ if not key: key = os.getenv('ENCRYPTION_KEY') cipher = Fernet(key.encode()) decrypted = cipher.decrypt(encrypted_data.encode()) return decrypted.decode()
6. 보안 체크리스트
구현 후 다음을 확인하세요:
프로덕션 배포 전
- 모든 입력 검증 (Whitelist 방식)
- SQL Injection 방지 (Parameterized query)
- XSS 방지 (HTML escape)
- CSRF 방지 (토큰 검증)
- 비밀번호 해싱 (Bcrypt/scrypt)
- HTTPS 필수
- 비밀키 환경 변수 관리
- 에러 메시지 최소화 (민감정보 노출 방지)
- 로깅 보안 감시 활성화
- Bandit 보안 검사 통과
지속적 모니터링
- 비정상 로그인 감지
- API 속도 제한 (Rate limiting)
- 권한 변경 감시
- 정기 보안 감사
관련 스킬
| 스킬 | 역할 |
|---|---|
| [@skills/quality-bandit/SKILL.md] | 보안 검사 자동화 |
| [@skills/convention-logging/SKILL.md] | 보안 로깅 |
| [@skills/check-security/SKILL.md] | 보안 취약점 검증 |
참고
- OWASP Top 10: https://owasp.org/www-project-top-ten/
- CWE Top 25: https://cwe.mitre.org/top25/
- NIST Cybersecurity Framework: https://www.nist.gov/cyberframework/
Changelog
| 날짜 | 버전 | 변경 내용 |
|---|---|---|
| 2026-01-21 | 1.0.0 | 초기 생성 - 입력 검증, SQL injection, XSS, 인증, 암호화 |
Gotchas (실패 포인트)
- SQL injection: f-string으로 쿼리 조합 절대 금지 — parameterized query 사용
- XSS: 사용자 입력을 HTML에 직접 삽입 금지
- secrets를 환경 변수 대신 코드에 하드코딩 시 git history에 영구 기록
- HTTPS 없는 API 엔드포인트에 인증 토큰 전송 금지