Skip to content

fix(audit): client IP trust-proxy 정합 + 감사 로그 IP 배선 (ALS 요청 컨텍스트)#124

Merged
chanwoo7 merged 2 commits into
developfrom
refactor/client-ip-trust-proxy-and-audit-wiring
Jun 3, 2026
Merged

fix(audit): client IP trust-proxy 정합 + 감사 로그 IP 배선 (ALS 요청 컨텍스트)#124
chanwoo7 merged 2 commits into
developfrom
refactor/client-ip-trust-proxy-and-audit-wiring

Conversation

@chanwoo7
Copy link
Copy Markdown
Member

@chanwoo7 chanwoo7 commented Jun 3, 2026

Summary

리팩토링 플랜 감사 중 발견한 #119(IP/UA 단일화 + trust proxy) PR의 두 가지 미완결을 바로잡습니다.

  1. 보안: tryClientIp 가 raw X-Forwarded-For(맨 왼쪽)·X-Real-IP 헤더를 req.ip 보다 먼저 신뢰해, main.tsTRUST_PROXY_HOPS 설정이 사실상 죽은 config 였고 클라이언트가 IP 를 위조할 수 있었습니다. Express trust proxy 가 계산한 req.ip 단일 소스로 교정합니다.
  2. 배선 누락: seller 도메인의 감사 로그 35개 호출부가 ipAddress 를 전달하지 않아 audit_log.ip_address 가 항상 null 로 기록되고 있었습니다. 요청 컨텍스트(AsyncLocalStorage)를 도입해 단일 write 진입점에서 자동 보강하도록 했습니다.

IP 는 transport 계층 메타데이터라 도메인 서비스 시그니처에 인자로 흘려보내면 SoC 를 해칩니다. 그래서 명시적 threading 대신 요청 컨텍스트(ALS) 로 암묵 전파하여, 도메인 서비스/리졸버 시그니처를 전혀 건드리지 않고 AuditLogRepository 가 컨텍스트에서 IP/UA 를 읽도록 했습니다.

Scope

신규

  • RequestContextService (src/global/request-context/request-context.service.ts) — AsyncLocalStorage 기반 요청 컨텍스트 래퍼. run() / get() / getClientIp() / getUserAgent()
  • RequestContextMiddleware — 요청 진입 시 tryClientIp/tryUserAgent 로 컨텍스트를 열고 next() 를 그 안에서 호출 (REST·GraphQL 공통)
  • RequestContextModule (@Global) + barrel
  • 신규 spec: request-context.service.spec.ts, request-context.middleware.spec.ts

변경

  • http-meta.ts tryClientIp — raw XFF/X-Real-IP 분기 제거, req.ip → socket fallback 만 사용
  • app.module.tsRequestContextModule import + RequestContextMiddleware 를 모든 라우트에 가장 먼저 적용
  • AuditLogRepository.createAuditLogip_address/user_agentargs → 요청 컨텍스트 → null 순으로 보강 (명시 인자 우선)
  • testing-module.builder.tsRequestContextService 기본 제공 (실DB spec 들이 실 AuditLogRepository 를 resolve 하도록)
  • spec 갱신: http-meta.spec.ts(spoofing 가드), audit-log.repository.spec.ts(ALS 보강/우선순위/컨텍스트 밖 null)

도메인 서비스/리졸버: 변경 없음.

진행 상황

리팩토링 플랜 P2 잔여 작업 진행 중, 플랜·머지 PR 비판적 감사에서 도출된 항목입니다.

Impact

  • FE: 없음. SDL/REST 경로/응답·에러 페이로드 동일.
  • DB: audit_log.ip_address / user_agent 가 운영 trust proxy 환경에서 실제 client IP/UA 로 기록됨 (이전엔 seller 도메인 전부 null). 스키마 변경 없음.
  • 보안: TRUST_PROXY_HOPS 정확 설정 시 XFF 위조 차단. 미설정(0) 시 socket 주소 사용(안전 기본).
  • Coverage: 신규 global/request-context/* 100%. yarn validate 전체 통과 (146 suites / 1251 tests).

Test plan

  • tryClientIp/clientIpOf — req.ip 우선, 위조 XFF/X-Real-IP 무시(spoofing 가드), socket fallback, undefined
  • RequestContextService — run 내부/외부, async 경계 유지, 중첩 run 복원, 반환값 전달
  • RequestContextMiddleware — next 시점 컨텍스트 적재, 위조 XFF 무시, async 핸들러 유지, 추출 실패 시 undefined
  • AuditLogRepository (실DB) — 컨텍스트에서 ip/ua 보강, 명시 인자 우선, 컨텍스트 밖 null
  • yarn validate (lint + tsc + dto:check + test:cov) 통과
  • CI 통과 확인

- tryClientIp: raw X-Forwarded-For/X-Real-IP 직접 신뢰 제거 → Express trust proxy 가
  계산한 req.ip 단일 소스 사용. TRUST_PROXY_HOPS 설정이 비로소 유효해지고 IP 위조 차단
- RequestContextService(AsyncLocalStorage) + RequestContextMiddleware 신설:
  요청 진입 시 client IP/UA 를 요청 컨텍스트에 적재 (REST·GraphQL 공통)
- AuditLogRepository 가 단일 write 진입점에서 컨텍스트의 IP/UA 자동 보강
  (명시 인자가 있으면 우선). seller 감사 로그 ip_address 가 항상 null 이던 문제 해소
- 도메인 서비스 시그니처 무변경 (transport 메타데이터 누수 없음)
- testing-module.builder 가 RequestContextService 기본 제공
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c4d7c90-6896-4c5c-bac6-34a32064d2f2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/client-ip-trust-proxy-and-audit-wiring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.38% 3563/3659
🟢 Branches 93.83% 1125/1199
🟢 Functions 93.84% 655/698
🟢 Lines 97.6% 3259/3339

Test suite run success

1253 tests passing in 146 suites.

Report generated by 🧪jest coverage report action from ab05fad

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8de8900779

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

after_json: args.afterJson === null ? Prisma.JsonNull : args.afterJson,
ip_address: args.ipAddress ?? null,
user_agent: args.userAgent ?? null,
ip_address: args.ipAddress ?? ctx?.clientIp ?? null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clamp context IP before persisting audit logs

When TRUST_PROXY_HOPS is enabled, ctx.clientIp comes from req.ip, which can still reflect the proxy-provided X-Forwarded-For value; if an upstream proxy forwards a malformed or overlong value, this new fallback writes it verbatim into audit_log.ip_address (@db.VarChar(64) in prisma/schema.prisma). That makes any seller mutation that creates an audit log fail with a database value-too-long error instead of completing, whereas those calls previously stored null; please validate/truncate the context IP before using it as the default.

Useful? React with 👍 / 👎.

- ip_address(VarChar(64)) 저장 전 net.isIP 로 검증 — trust proxy 가 넘긴
  malformed·overlong 값이 컬럼 길이 초과로 insert 를 깨뜨리는 것을 방지.
  유효 IPv4/IPv6 가 아니면 null 로 저장 (쓰레기 IP 적재 방지)
- user_agent(VarChar(512)) 도 저장 전 512 자로 절단 (defense-in-depth)
@chanwoo7 chanwoo7 merged commit 08a8357 into develop Jun 3, 2026
8 checks passed
@chanwoo7 chanwoo7 deleted the refactor/client-ip-trust-proxy-and-audit-wiring branch June 3, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant