로그와 화면 증거 정리
70분 안팎
학습 목표
토큰·이메일·비밀번호를 가린 증거로 요청을 추적합니다.
개념
증거를 공유하기 전에 공개본을 만듭니다
실패 화면과 로그에는 문제를 설명하는 단서와 공유하면 안 되는 값이 함께 있습니다. Authorization 값은 요청을 추적하는 데 필요하지 않은데도 계정 접근에 쓰일 수 있습니다. QA는 원본을 그대로 붙이는 대신 재현에 필요한 조건과 추적값을 보존한 공개본을 만듭니다. 이번 목표는 중첩된 JSON에서 비밀과 이메일을 가리고, requestId를 유지해 보고서와 요청을 연결하는 것입니다.
실습은 실제 자격증명이 아닌 합성 문자열만 사용합니다. 합성 자료라도 공개본 생성 절차를 연습해야 실무에서 실수를 줄일 수 있습니다. fixtures/private-log.json은 교육용 원본이고 evidence/public-log.json만 공유 대상으로 봅니다. 이름이 private라고 파일 권한이나 접근 통제가 자동으로 생기는 것은 아닙니다. 원본이 필요하면 권한이 제한된 위치에서 관리하고 공개 제출에 섞지 않는 정책을 따릅니다.
가릴 값과 유지할 값을 계약으로 정합니다
이 실습의 민감 키는 authorization, password, email, cookie, token이며 대소문자를 구별하지 않습니다. 값을 길이에 관계없이 [REDACTED]로 바꿉니다. 이메일 일부를 남기는 방식은 식별 가능성이 있어 사용하지 않습니다. 키 이름 자체는 남겨 어떤 필드가 있었는지 알 수 있게 합니다. 값 전체가 객체여도 민감 키에 해당하면 내부를 탐색하기 전에 전체 값을 표시자로 바꿉니다.
requestId·status·method·판정 값은 유지합니다. requestId는 사건을 연결하는 표식이며 비밀번호처럼 사용자 인증을 수행하는 비밀이 아닙니다. 공개본과 보고서에서 같은 값을 찾아 요청을 대조합니다. 다만 실제 서비스의 추적 ID가 개인정보를 담는다면 그 구조도 검토해야 합니다. 이번 합성 요청 ID는 req-log-01처럼 계정 정보를 넣지 않는 형식으로 제공합니다.
이메일을 가린 뒤 합성 회원 A·B 또는 서로 다른 fixture ID로 관계를 설명합니다. 두 계정이 다른 사람이라는 조건이 사라지면 권한 결함 재현이 어려워집니다. 구체 이메일 원문을 공개하지 않고 사전 조건의 역할과 대응 관계를 적습니다. 마스킹 때문에 길이 결함의 입력 정보가 사라지는 경우에는 ASCII 64자처럼 비교에 필요한 성질만 보고서에 따로 남깁니다.
문자열 치환보다 구조를 순회합니다
JSON 문자열에 password라는 글자가 있는지 찾아 한 번 바꾸면 body 안이나 배열 안의 민감값을 놓칠 수 있습니다. JSON을 구조로 읽고 객체의 각 키·값을 처리합니다. 배열은 항목마다 같은 처리를 적용하며 null과 숫자·문자열·Boolean은 그대로 돌려줍니다. 이 구조에서는 requestId 같은 비민감 키의 값이 원래대로 남으므로 마스킹 전후 연결을 확인하기 쉽습니다.
브라우저 실습 입력은 JSON 한 개이며 객체·배열·null을 포함할 수 있습니다. 출력은 마스킹한 JSON 한 줄입니다. 키 순서는 입력의 순서를 따르고 들여쓰기는 넣지 않습니다. starter는 최상위 키만 가려 중첩 객체와 배열 테스트에 실패합니다. solution은 모든 깊이를 순회합니다. JavaScript 구현을 외우기보다 최상위·중첩·배열·빈 값의 네 조건이 왜 필요한지 설명합니다.
대소문자를 비교할 때만 키를 소문자로 바꾸고 출력 키를 일괄 변환하지 않습니다. Authorization이 authorization으로 바뀌면 원본 구조가 변하고 비교나 로그 도구가 기대하는 필드와 달라질 수 있습니다. 객체에 key가 password라고 쓰였으면 빈 값이어도 표시자로 바꿉니다. 원래 비밀이 비어 있었다는 정보를 특별히 공개할 이유가 없으므로 같은 규칙을 적용합니다.
오류를 읽고 검사 범위를 한정합니다
JSON.parse에서 SyntaxError가 나면 먼저 입력 따옴표·쉼표·괄호를 봅니다. 이는 마스킹 결과 불일치와 다릅니다. JSON null에서 Object.entries를 바로 부르면 타입 관련 오류가 날 수 있으므로 null을 객체 순회보다 먼저 처리합니다. Array.isArray 확인 없이 배열을 객체처럼 만들면 출력이 배열에서 숫자 키 객체로 바뀝니다. 채점은 민감값 제거뿐 아니라 원래 자료형 보존도 비교합니다.
알려진 민감 키를 잘 가렸다고 자유 텍스트의 모든 개인정보가 없어졌다고 말하지 않습니다. message에 이메일이 들어 있거나 userPassword처럼 다른 이름을 사용하면 이번 규칙은 놓칩니다. 실제 공유 전에는 필드 목록과 자유 문장을 별도로 검토합니다. 필요 없는 요청 본문을 처음부터 수집하지 않는 것이 마스킹만 믿는 방식보다 오류를 줄입니다. 로깅의 일반 설계는 더 읽기에서 확인합니다.
이번 테스트는 JSON 문서만 처리하며 파일 첨부·화면 이미지·동영상의 민감값을 자동 검사하지 않습니다. 브라우저 개발자 도구의 Copy as cURL에는 인증 헤더가 포함될 수 있으므로 그대로 보고서에 붙이지 않습니다. 요청 경로의 쿼리 문자열도 값을 포함할 수 있습니다. 실제 파일을 공유하기 전에는 데이터 형식마다 무엇이 남는지 따로 확인해야 합니다.
화면 증거에도 같은 원칙을 적용합니다
화면을 찍기 전에 실제 계정 대신 합성 계정을 사용하고 문제 영역만 남깁니다. 입력칸뿐 아니라 상단 로그인 이름, 주소창, 자동완성 목록, 개발자 도구 헤더, 다른 탭 제목을 확인합니다. 비밀번호 입력칸이 점으로 보여도 이메일과 세션 쿠키는 다른 위치에서 보일 수 있습니다. 캡처 도구가 어디까지 포함하는지 보고 공개용 파일에서 다시 검토합니다.
화면을 가릴 때는 불투명한 영역으로 덮고 최종 이미지로 내보내 확인합니다. 편집 가능한 레이어를 남긴 파일이나 희미한 흐림 효과는 원래 값이 복원되거나 읽힐 수 있습니다. requestId와 오류 문구는 문제 영역에 남기고 민감 내용이 섞였다면 문구를 안전하게 다시 적어 설명합니다. 이 레슨에서는 이미지 파일을 실행 증거로 제공하지 않으며 수동 확인 기준만 제시합니다.
공개본 검토에는 제거와 보존 두 질문이 있습니다. 제공 합성 토큰·비밀번호·이메일·쿠키가 0건인가, 그리고 요청 ID와 결과가 여전히 연결되는가를 함께 확인합니다. 로그를 모두 지우면 유출은 줄지만 개발자가 어떤 요청인지 찾지 못합니다. 반대로 requestId만 확인하고 원본 값이 남았는지 보지 않으면 공개 전에 해야 할 검토의 절반을 놓칩니다.
보고서와 마스킹 증거를 끝까지 대조합니다
미션 검사기는 공개 로그의 구조가 원본과 같고 민감 키의 값만 바뀌었는지 확인합니다. 정해진 합성 비밀 문자열의 잔존도 검사합니다. 이 검사는 실습 자료에 대한 확인이며 실제 개인정보 탐지 도구가 아닙니다. report의 requestId와 evidence의 requestId가 다르면 다른 요청 파일을 붙였을 가능성을 먼저 봅니다. 민감값을 가린다고 ID까지 새로 만들지 않습니다.
잘못 공유한 실제 비밀을 발견하면 보고서 편집만으로 해결되었다고 판단하지 않습니다. 접근 범위와 노출 위치를 팀에 알리고 정해진 비밀 교체·삭제 절차를 따릅니다. 실습에서는 실제 비밀을 사용하지 않으므로 합성 공개본과 원본을 대조하는 데 집중합니다. 원본에 접근하지 않아도 동료가 요구 조건과 실패를 이해할 수 있는지 최종 보고서로 확인합니다.
마무리 제출에는 두 결함 증거, 마스킹한 공개 로그, 환경 문서, 탐색 노트가 함께 연결되어 있어야 합니다. 개인정보를 지웠다는 선언보다 공개 파일에서 제거·보존 검사를 다시 실행한 결과가 도움이 됩니다. 사람이 읽어야 하는 자유 문장과 이미지의 검토 범위도 남깁니다. 같은 요청을 추적할 수 있으면서 필요 없는 값을 공유하지 않는 상태가 이 레슨의 완료 기준입니다.
따라하기
최상위 값 제거
요청 추적값은 유지하고 합성 민감값을 공개본으로 바꿉니다. 코드는 실제 실행한 마스킹 예제입니다.
const raw={Authorization:"Bearer fake",requestId:"req-01",status:400};
const safe={...raw,Authorization:"[REDACTED]"};console.log(JSON.stringify(safe));실행 결과
{"Authorization":"[REDACTED]","requestId":"req-01","status":400}
배열과 null 경계 확인
배열 항목과 null이 원래 자료형으로 남는지 확인합니다. 여기서는 합성 password 키만 다룹니다. 전체 민감 키 규칙은 실습에서 구현합니다.
function clean(x){if(x===null||typeof x!=="object")return x;if(Array.isArray(x))return x.map(clean);return Object.fromEntries(Object.entries(x).map(([k,v])=>[k,k.toLowerCase()==="password"?"[REDACTED]":clean(v)]));}
console.log(JSON.stringify(clean([{body:{Password:"fake"},requestId:"req-02"},null])));실행 결과
[{"body":{"Password":"[REDACTED]"},"requestId":"req-02"},null]
제거와 보존을 따로 비교
민감값 제거 확인과 요청 연결 확인은 둘 다 필요합니다. 이 합성 자료는 두 질문을 각각 Boolean으로 출력합니다.
const safe={password:"[REDACTED]",requestId:"req-03"};console.log("secretRemoved="+(safe.password==="[REDACTED]"));console.log("requestPreserved="+(safe.requestId==="req-03"));실행 결과
secretRemoved=true requestPreserved=true
미션 공개본 작성
fixtures/private-log.json을 읽어 evidence/public-log.json에 공개본을 씁니다. requestId·status는 그대로 두고 민감값만 가립니다. bash check.sh의 마스킹 검사를 실행하고 자유 문장과 화면은 별도 검토합니다.
확인 문제
실습
JSON 한 개를 읽어 authorization·password·email·cookie·token 키 값을 대소문자 구별 없이 [REDACTED]로 바꿉니다. 모든 중첩 객체와 배열에 적용하며 민감 키의 값 전체를 가립니다. null과 원시값 및 비민감 키·자료형을 보존합니다. requestId도 보존합니다. 결과 JSON을 들여쓰기 없이 한 줄 출력합니다. 자유 문장 속 비밀 탐지는 이번 계약 밖입니다.
모범 답안
const fs=require('fs');
const value=JSON.parse(fs.readFileSync(0,'utf8'));
const hidden=new Set(['authorization','password','email','cookie','token']);
function redact(x){
if(x===null || typeof x!=='object')return x;
if(Array.isArray(x))return x.map(redact);
return Object.fromEntries(Object.entries(x).map(([k,v])=>[k,hidden.has(k.toLowerCase())?'[REDACTED]':redact(v)]));
}
console.log(JSON.stringify(redact(value)));
더 읽기
면접 질문
- 재현하기 좋은 결함 보고서의 구성을 설명해 주시면 됩니다.