Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

32장. 인증과 보안 — Token · Secret · Injection · 개인정보

보안 결함에는 다른 결함과 구별되는 성질이 하나 있다.

조용하다.

권한 체크가 빠진 API는 테스트를 통과한다.
빌드도 되고, 응답도 정상이고, 아무도 모른다.

누군가 찾아낼 때까지.

Agent가 만드는 코드에서
이 성질이 특히 위험해진다.


가장 흔한 실패는 권한 누락이다

26장에서 API를 복제할 때 이런 일이 생긴다.

// 참고한 API
@GetMapping("/orders/{id}")
@PreAuthorize("@orderAuth.canRead(#id)")
fun getOrder(@PathVariable id: Long) = ...

// Agent가 만든 API
@GetMapping("/orders/{id}/payments")
fun getPayments(@PathVariable id: Long) = ...   // 권한 체크 없음

⚠️ 애노테이션 하나가 빠졌다.

기능은 완벽하게 동작한다.
남의 주문 결제 내역도 아주 잘 조회된다.

이것을 막는 방법은 셋이다.

# CLAUDE.md
- 모든 조회·수정 API는 리소스 소유자 검증을 거친다
- 신규 엔드포인트는 `@PreAuthorize` 또는 명시적 권한 검사가 필수다
- 권한 검사를 생략해야 하는 경우 이유를 주석으로 남긴다
# 작업 지시
이 API에 필요한 권한 검사를 명시해줘.
참고한 API와 권한 정책이 같아야 해.
// 테스트
@Test
fun `다른 사용자의 주문 결제 내역은 조회할 수 없다`() { ... }

🔥 세 번째가 가장 강하다.

권한 테스트는 API마다 한 건씩 쌓아두면
Agent가 새 API를 만들 때 같은 패턴을 복제한다.


시크릿은 Context에 넣지 않는다

7장에서 deny 목록에 넣은 이유다.

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./src/main/resources/application-prod.yml)",
      "Read(**/*credentials*)",
      "Bash(env)",
      "Bash(printenv:*)"
    ]
  }
}

한 번 읽히면 그 세션 내내 Context에 남는다.
대화 기록에도 남는다.

⚠️ 그리고 Agent가 만든 코드에서도 확인한다.

이번 변경에서 다음이 없는지 확인해줘.

- 하드코딩된 키·토큰·비밀번호
- 테스트 코드의 실제 자격증명
- 설정 파일에 추가된 평문 값
- 로그에 시크릿이 찍히는 코드

두 번째가 자주 나온다.

Agent가 테스트를 만들면서
"test-api-key-abc123" 같은 값을 넣는데,
그게 실제 개발 환경 키인 경우가 있다.


Injection

Agent는 대개 파라미터 바인딩을 쓴다.

문제는 레거시를 흉내 낼 때다.

// 기존 코드에 이런 게 있으면
val sql = "SELECT * FROM orders WHERE status = '$status'"

// Agent가 같은 스타일로 새 쿼리를 만든다

26장에서 말한 그 문제다.
지목한 파일이 나쁘면 나쁜 패턴이 복제된다.

- 네이티브 쿼리에 문자열 연결을 쓰지 않는다
  기존 코드에 있어도 새 코드는 파라미터 바인딩을 쓴다
- 동적 정렬·필터는 화이트리스트로 검증한다

두 번째가 놓치기 쉽다.

// 정렬 컬럼을 그대로 받으면 SQL이 뚫린다
fun findOrders(sortBy: String) = 
    jdbcTemplate.query("SELECT * FROM orders ORDER BY $sortBy")

개인정보가 새는 세 곳

경로흔한 사고
로그디버깅용 log.info("request: $request")
응답엔티티 직접 반환 시 불필요한 필드 노출
테스트 fixture실제 고객 데이터를 복사해 넣음

첫 번째가 압도적으로 많다.

24장에서 로그를 피드백으로 쓰라고 했는데,
그 로그에 주민번호가 들어가면 안 된다.

- 요청·응답 객체를 통째로 로그에 찍지 않는다
- 개인정보(이름·연락처·주소·카드번호)는 마스킹한다
- 마스킹 유틸은 `common/log/Masking.kt` 를 쓴다

두 번째는 26장의 “엔티티 직접 반환” 과 같은 뿌리다.

한 규칙이 두 문제를 막는다.


보안 리뷰는 별도 세션으로

18장에서 구현 세션과 Review 세션을 나눴다.

보안은 그 분리가 특히 중요하다.

구현 세션에는
“이렇게 하기로 했다” 는 합의가 쌓여 있다.

같은 Context에서 보안 검토를 시키면
그 합의를 근거로 “문제 없습니다” 를 확인한다.

# 새 세션에서
@.git 의 최근 커밋 3개 Diff만 읽고 보안 관점에서 검토해줘.

확인할 것:
- 인증·인가 검사 누락
- 입력 검증 누락
- 시크릿 하드코딩
- 개인정보 로깅·노출
- SQL/커맨드 injection
- 권한 상승 가능 경로

각 항목마다 확인했음/문제있음 을 명시하고,
문제는 파일과 줄 번호를 함께 알려줘.

⚠️ 마지막 요구가 중요하다.

“특별한 문제가 없습니다” 만 오면
검토를 한 것인지 알 수 없다.

항목별로 답하게 하면 빠뜨린 것이 보인다.

이 요청은 48장에서 Skill로 만들기 좋은 형태다.


Agent에게 맡기지 않는 것

이 영역에는 위임 금지선이 있다.

맡기지 않는다이유
인증 방식 설계틀리면 전면 재작업
암호화 알고리즘 선택직접 구현은 특히 금지
권한 모델 정의도메인 판단
시크릿 로테이션되돌릴 수 없다
보안 취약점 대응 배포사람이 검증

Agent가 잘하는 것은 다른 쪽이다.

  • 누락 찾기 (권한 체크 없는 엔드포인트 목록)
  • 패턴 검색 (문자열 연결 쿼리 전수 조사)
  • 일관성 확인 (같은 리소스인데 정책이 다른 곳)

보안에서 Agent는 설계자가 아니라
아주 성실한 점검자다.


이 장의 핵심

  • 보안 결함은 조용하다 — 테스트도 통과하고 응답도 정상이다
  • 가장 흔한 실패는 API 복제 과정의 권한 검사 누락이다
  • 권한 테스트를 API마다 쌓아두면 Agent가 그 패턴을 복제한다
  • 시크릿은 한 번 읽히면 세션 내내 Context와 대화 기록에 남는다
  • 테스트 코드에 실제 개발 환경 키가 들어가는 사고가 자주 난다
  • 나쁜 레거시 패턴을 지목하면 injection 취약점이 복제된다
  • 동적 정렬·필터 컬럼은 화이트리스트로 검증한다
  • 개인정보는 로그·응답·테스트 fixture 세 곳으로 샌다
  • 보안 검토는 구현과 다른 세션에서, 항목별로 답하게 한다
  • Agent는 보안의 설계자가 아니라 성실한 점검자다