# 개발자를 위한 보안 기초 가이드

웹 애플리케이션을 개발할 때 반드시 적용해야 할 **핵심 보안 원칙과 실천 방법**을 단계별로 정리한 가이드입니다.

***

### 왜 필요한가?

보안 취약점은 코드 배포 이후에 발견될수록 수정 비용이 기하급수적으로 늘어납니다. OWASP 기준, 전체 보안 침해 사고의 60% 이상이 **알려진 취약점을 패치하지 않은 의존성** 또는 **잘못된 시크릿 관리**에서 발생합니다.

개발 단계에서 보안을 설계하지 않으면 다음 상황이 발생합니다.

* `.env` 파일이 공개 저장소에 커밋되어 API 키 유출
* SQL 인젝션으로 전체 사용자 데이터베이스 노출
* 만료되지 않는 세션 토큰으로 계정 탈취 지속
* 서드파티 라이브러리 취약점을 통한 공급망 공격

이 가이드는 **프로젝트 초기 설정 단계**부터 적용할 수 있는 실천 목록을 제공합니다.

***

### 개념 정리

#### 주요 보안 위협 분류

| 위협 유형         | 설명                    | 대표 사례                            |
| ------------- | --------------------- | -------------------------------- |
| **인젝션**       | 신뢰되지 않은 데이터가 명령어로 실행됨 | SQL Injection, Command Injection |
| **인증 결함**     | 세션 관리·토큰 검증 부재        | 만료 없는 JWT, 평문 쿠키                 |
| **민감 데이터 노출** | 암호화 없는 데이터 전송·저장      | HTTP 전송, 평문 비밀번호                 |
| **접근 제어 결함**  | 권한 외 리소스 접근 허용        | IDOR, 관리자 API 미보호                |
| **보안 설정 오류**  | 기본값 그대로 사용            | 디버그 모드 활성화, CORS `*`             |
| **취약한 의존성**   | 알려진 CVE가 있는 패키지 사용    | 미패치 npm/pip 패키지                  |

#### 인증 vs 인가

| 개념                      | 의미                  | 질문 형태          | 구현 위치        |
| ----------------------- | ------------------- | -------------- | ------------ |
| **인증 (Authentication)** | 사용자가 누구인가 확인        | "당신이 맞습니까?"    | 로그인, JWT 발급  |
| **인가 (Authorization)**  | 사용자가 무엇을 할 수 있는가 확인 | "이 작업이 허용됩니까?" | 미들웨어, 라우트 보호 |

***

### 핵심 보안 실천

#### 1단계: 시크릿 관리 설정

프로젝트 시작 전 시크릿이 저장소에 커밋되지 않도록 구조를 설정합니다.

```bash
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo ".env.*.local" >> .gitignore
```

`.env.example` 파일에 키 이름만 남기고 값은 비워 공유합니다.

```bash
# .env.example — 값 없이 키 이름만 기록
DATABASE_URL=
JWT_SECRET=
STRIPE_SECRET_KEY=
```

커밋 이전 시크릿 스캔 도구를 설치합니다.

```bash
pip install detect-secrets
detect-secrets scan > .secrets.baseline
```

**확인:** `git status`에서 `.env`가 추적 파일 목록에 없어야 합니다.

***

#### 2단계: 의존성 취약점 점검

프로젝트의 서드파티 패키지에 알려진 CVE가 있는지 점검합니다.

```bash
npm audit
```

```bash
pip-audit
```

취약점이 발견되면 즉시 패치를 적용합니다.

```bash
npm audit fix
```

자동 수정이 불가능한 경우 대체 패키지를 검토합니다.

```bash
npm audit --json | jq '.vulnerabilities | keys[]'
```

**확인:** `npm audit`이 `found 0 vulnerabilities`를 출력해야 합니다.

***

#### 3단계: 입력 검증 및 인젝션 방어

모든 외부 입력은 **사용 직전에 검증**합니다. 쿼리에 사용자 입력을 직접 삽입하지 않습니다.

**잘못된 방식:**

```javascript
const result = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
```

**올바른 방식 (파라미터 바인딩):**

```javascript
const result = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
```

스키마 기반 입력 검증 라이브러리를 사용합니다.

```javascript
import { z } from "zod";

const UserSchema = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

const parsed = UserSchema.safeParse(req.body);
if (!parsed.success) return res.status(400).json(parsed.error);
```

***

#### 4단계: 인증 및 세션 보안 설정

JWT를 사용할 때 만료 시간과 서명 알고리즘을 명시합니다.

```javascript
import jwt from "jsonwebtoken";

const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: "1h", algorithm: "HS256" }
);
```

쿠키 기반 세션을 사용할 때 보안 플래그를 설정합니다.

```javascript
res.cookie("session", token, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",
  sameSite: "strict",
  maxAge: 60 * 60 * 1000,
});
```

| 플래그                | 역할                             |
| ------------------ | ------------------------------ |
| `httpOnly`         | JavaScript에서 쿠키 접근 차단 (XSS 방어) |
| `secure`           | HTTPS 연결에서만 전송                 |
| `sameSite: strict` | 외부 사이트 요청에 쿠키 미포함 (CSRF 방어)    |

***

#### 5단계: HTTP 보안 헤더 설정

서버 응답에 보안 헤더를 추가합니다. Express 기준:

```bash
npm install helmet
```

```javascript
import helmet from "helmet";

app.use(helmet());
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    objectSrc: ["'none'"],
  },
}));
```

**확인:** `curl -I https://your-app.com` 응답에 `X-Content-Type-Options`, `Strict-Transport-Security` 헤더가 포함되어야 합니다.

***

#### 6단계: CORS 정책 명시

```javascript
import cors from "cors";

app.use(cors({
  origin: ["https://your-frontend.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
}));
```

`origin: "*"`은 **공개 API가 아닌 이상 사용하지 않습니다.** 인증이 필요한 API에서 `*`를 허용하면 CSRF 공격 표면이 됩니다.

***

### 커스터마이징

#### 환경별 보안 정책 분리

| 설정 항목         | 개발 환경    | 운영 환경     |
| ------------- | -------- | --------- |
| HTTPS         | 선택 ⚠️    | 필수 ✅      |
| `secure` 쿠키   | 비활성화 가능  | 반드시 활성화 ✅ |
| 상세 에러 메시지     | 허용       | 비활성화 ✅    |
| CORS          | 로컬호스트 허용 | 도메인 명시 ✅  |
| Rate Limiting | 완화       | 엄격 적용 ✅   |

#### Rate Limiting 추가

```bash
npm install express-rate-limit
```

```javascript
import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
});

app.use("/api/", limiter);
```

로그인 엔드포인트에는 별도로 더 엄격한 제한을 적용합니다.

```javascript
const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10 });
app.use("/api/auth/login", loginLimiter);
```

***

### 트러블슈팅

#### JWT가 만료되었는데도 요청이 통과됩니다

**체크 1: 서버에서 토큰 검증 여부 확인**

```javascript
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
  if (err) return res.status(401).json({ error: "Invalid token" });
  req.user = decoded;
  next();
});
```

* `jwt.decode(token)`만 사용 중이라면 → `jwt.verify()`로 교체합니다. `decode`는 서명 검증을 하지 않습니다.
* 미들웨어가 특정 라우트에 적용되지 않았다면 → 라우터 등록 순서를 확인합니다.

**체크 2: 토큰 만료 시간 설정 확인**

```bash
node -e "const jwt=require('jsonwebtoken'); console.log(jwt.decode('YOUR_TOKEN'))"
```

* `exp` 필드가 없다면 → `jwt.sign()` 호출 시 `expiresIn` 옵션이 누락된 것입니다.

***

#### `npm audit`이 fix되지 않는 취약점을 보고합니다

**체크 1: 취약점 심각도와 경로 확인**

```bash
npm audit --json | jq '.vulnerabilities | to_entries[] | {name: .key, severity: .value.severity, via: .value.via}'
```

* `severity: "low"` 이고 `dev dependency`라면 → 프로덕션 영향 없음, 문서화 후 다음 버전 업데이트를 기다립니다.
* `severity: "critical"` 이라면 → 해당 패키지를 사용하지 않는 대안을 즉시 검토합니다.

**체크 2: 강제 업데이트 시도**

```bash
npm audit fix --force
```

브레이킹 체인지가 발생할 수 있으므로 테스트 실행으로 회귀 여부를 확인합니다.

***

#### CORS 에러가 운영 환경에서만 발생합니다

**체크 1: 허용된 오리진 목록 확인**

요청 헤더의 `Origin` 값과 서버의 `origin` 배열을 정확히 비교합니다.

* `https://www.example.com` ≠ `https://example.com` — 서브도메인은 별개입니다.
* `http://` ≠ `https://` — 프로토콜이 다릅니다.

**체크 2: Preflight 요청 처리 확인**

```javascript
app.options("*", cors());
```

`OPTIONS` 메서드에 CORS 미들웨어가 적용되지 않으면 Preflight 요청이 실패합니다.

***

### 정리

* **시크릿은 코드에 절대 포함하지 않습니다.** `.gitignore`와 `.env.example`을 프로젝트 시작과 동시에 설정합니다.
* **모든 외부 입력을 검증합니다.** 쿼리 파라미터, 헤더, 요청 바디 모두 포함됩니다.
* **의존성 취약점을 주기적으로 점검합니다.** CI 파이프라인에 `npm audit`을 포함합니다.
* **JWT는 반드시 서버에서 `verify()`로 검증합니다.** 만료 시간 설정을 빠뜨리지 않습니다.
* **보안 헤더와 CORS를 명시적으로 설정합니다.** 기본값을 신뢰하지 않습니다.

***

### 관련 공식 문서

* [OWASP Top 10](https://owasp.org/www-project-top-ten/) — 가장 위험한 웹 보안 취약점 목록
* [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/) — 주제별 보안 실천 가이드
* [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) — Node.js 공식 보안 가이드
* [Helmet.js 문서](https://helmetjs.github.io/) — Express HTTP 보안 헤더 설정
* [jsonwebtoken 문서](https://github.com/auth0/node-jsonwebtoken#readme) — JWT 서명·검증 옵션
* [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework) — 조직 수준 보안 프레임워크


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://judy-valentine.gitbook.io/judy/technical-notes/security/undefined.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
