← 전체 글로 돌아가기

Next.js

로그에 토큰을 남겼던 실수를 마스킹 규칙과 테스트로 막기

디버깅 로그에 인증 토큰이 남았던 상황을 계기로 민감 정보 마스킹 위치와 회귀 테스트를 정한 기록입니다

편한 디버깅이 사고의 시작이었다

요청 객체를 통째로 출력하면 문제를 빨리 찾을 수 있다고 생각했습니다. 로그 검색 중 authorization 헤더와 refresh token이 함께 저장된 것을 발견했습니다. 일부만 잘라도 안전하다고 단정할 수 없었습니다.

공통 로거에서 가리기

호출부마다 로그를 고치는 방식은 누락되기 쉽습니다. 공통 로거가 객체를 복사해 민감한 키를 마스킹하도록 했습니다.

const SECRET_KEYS = ['authorization', 'cookie', 'password', 'token', 'refreshtoken'];
function redact(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(redact);
  if (!value || typeof value !== 'object') return value;
  return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([key, item]) => [
    key, SECRET_KEYS.includes(key.toLowerCase()) ? '[REDACTED]' : redact(item),
  ]));
}

access_token 같은 별칭과 중첩 헤더를 실제 요청 구조에 맞춰 추가했고, URL query의 code와 state도 별도로 제거했습니다. 비밀번호는 마스킹보다 애초에 로거에 전달하지 않는 편이 낫습니다.

테스트가 잡은 누락

최초 테스트는 최상위 token만 확인하고 중첩된 Authorization을 놓쳤습니다. 실패 픽스처를 추가했습니다.

const output = redact(input);
expect(JSON.stringify(output)).not.toContain('Bearer secret-value');
expect(JSON.stringify(output)).toContain('[REDACTED]');

이미 남은 운영 로그는 코드 수정만으로 없어지지 않습니다. 보존 기간과 접근 권한을 줄이고 노출된 토큰은 폐기했습니다.

배포 전 다섯 가지

  1. 요청·응답 전체를 출력하는 로그가 없는가
  2. 대소문자와 중첩 객체를 처리하는가
  3. URL, 쿠키, 헤더, 예외 객체를 시험했는가
  4. 테스트 로그에도 비밀값이 없는가
  5. 기존 토큰을 폐기했는가

로그는 원본을 많이 담는다고 좋은 로그가 되지 않습니다. 필요한 필드만 구조화하고 민감한 값은 수집 전에 제거하는 규칙을 유지합니다.