관찰과 추측을 분리한 기록
90분 안팎
학습 목표
재현 환경과 관찰 결과를 구분해 기록합니다.
개념
증거가 답해야 하는 질문
보안 보고서의 독자는 작성자 옆에서 실행 장면을 보지 못합니다. 그래서 성공 화면 한 장보다 어떤 코드와 입력으로 어떤 결과를 얻었는지 다시 따라갈 수 있는 기록이 필요합니다. 이 레슨에서는 첫 자료 생성·조회 결과를 E01이라는 증거로 남깁니다. 증거는 결론을 꾸미는 부록이 아니라 결론이 어디까지 성립하는지 확인하는 자료입니다. 관찰한 값과 해석한 판단을 다른 필드에 적습니다.
“자료 조회 200”은 관찰이고 “모든 사용자의 자료가 안전하다”는 판단입니다. 전자는 요청 조건과 응답을 연결하면 확인할 수 있지만 후자는 현재 앱의 인증·인가 검증이 없어 뒷받침되지 않습니다. 한 건의 성공으로 전체 안전성을 주장하면 후속 점검의 우선순위도 흐려집니다. 근거가 없는 판단은 hypothesis에 미검증이라고 남기고 다음 확인 과제로 전환합니다.
E01의 최소 필드
evidence.json에는 id, scopeId, observedAt, environment, request, response, readResponse, observation, hypothesis를 둡니다. id는 보고서의 증거 목록에서 찾을 이름이고 scopeId는 해당 실행의 허가 문서를 연결합니다. observedAt은 실행 시각을 ISO 형식으로 기록합니다. environment에는 실제 Node.js 버전과 transport를 넣습니다. 이 실행의 transport는 function-call이며 소켓 요청을 보냈다는 표현을 쓰지 않습니다.
request는 메서드와 경로, 합성 본문을 포함합니다. response는 생성 상태와 생성한 자료를 담고 readResponse는 그 id로 조회한 결과를 담습니다. 생성 결과의 id와 조회 대상이 같아야 두 요청이 연결됩니다. 다른 id의 성공 응답을 붙여 놓으면 재현 흐름을 설명할 수 없습니다. 이번 앱은 고정 owner와 합성 문자열을 사용하므로 원문 요청을 보관할 수 있지만 실자료에는 같은 방식을 그대로 적용하지 않습니다.
observedAt에 임의 날짜를 적으면 다른 실행과 구분하기 어려워집니다. 증거 생성 코드는 현재 시각과 process.version을 읽습니다. 예시 테스트의 고정 시각은 범위 검사 재현용이고, 실제 증거의 관찰 시각과 용도가 다릅니다. 보고서에서 두 시각의 역할을 적으면 오래된 fixture가 오늘의 승인으로 오해되는 일을 줄일 수 있습니다. 작업 기간을 바꾸는 것은 증거를 꾸미는 대신 범위를 다시 정하는 절차입니다.
요청과 응답을 재현 가능한 형태로 묶기
최초 증거는 node demo.cjs가 보여 준 자료 생성·조회 흐름과 같은 합성 본문을 사용합니다. evidence.cjs가 createApp을 호출하고 생성 결과의 id로 다시 조회한 뒤 JSON 파일을 씁니다. 성공한 요청을 사람이 따로 복사하면서 id를 잘못 옮기는 일을 줄입니다. 하지만 생성기가 잘못 구현될 수도 있으므로 verify-report.cjs는 응답 상태와 본문 일치를 독립된 assertion으로 확인합니다.
check.sh는 먼저 앱 테스트와 범위 테스트를 실행합니다. 실패가 있으면 set -e에 의해 뒤 단계로 진행하지 않습니다. 성공하면 증거를 새로 만들고 보고서의 필수 절, E01, scope-001 참조를 검사합니다. 마지막 성공 문구는 이 자동 검사들이 통과했다는 뜻입니다. 자연어의 판단 근거가 충분한지와 허가 동의서가 실제 승인됐는지는 사람 리뷰가 필요하며 테스트 성공을 승인으로 대체하지 않습니다.
보고서 골격 작성하기
report.md의 허가 범위 절은 scope.json을 가리키고 대상과 행위 제한을 요약합니다. 재현 환경 절에는 어떤 파일과 런타임으로 실행했는지 적습니다. 증거 목록 절에서 E01을 evidence.json에 연결합니다. 관찰과 판단 절에서는 확인한 201과 200, 그리고 확인하지 못한 접근 통제를 분리합니다. 남은 위험 절은 메모리 자료의 소실과 후속 인증·인가 과제를 담습니다.
절 제목만 있어도 자동 검사를 통과할 수 있는 부분이 있습니다. 따라서 리뷰어는 각 절의 문장을 읽고 근거가 이어지는지 확인합니다. “정상” 대신 생성 응답의 id와 조회 본문이 같았다는 사실을 적습니다. “취약”이라는 단어만 적는 대신 어떤 조건에서 무엇이 달라졌는지 설명합니다. 현재 단계는 취약점 재현보다 첫 기능과 증거 체계를 준비하는 단계이므로 발견하지 않은 결함을 만들어 쓰지 않습니다.
관찰과 추측을 나누는 연습
자료 생성 201 뒤 조회 404를 보았다면 “생성 후 동일 id 조회에서 not_found를 받았습니다”라고 기록합니다. “네트워크가 차단되었습니다”는 현재 함수 호출에서는 근거 없는 추측입니다. 원인 후보로 메모리 저장 누락을 적고 코드를 확인한 후 수정 결과로 조회 200을 붙입니다. 원인 후보를 사실로 바꾸려면 그 후보를 구분할 추가 근거가 있어야 합니다.
assertion에서 actual이 404, expected가 200인 것은 코드 동작과 계약이 달랐다는 뜻입니다. 오류 메시지에 있는 숫자를 수정해 테스트를 통과시키는 것은 원인을 고친 것이 아닙니다. 생성 자료를 Map에 저장한 뒤 같은 입력으로 재실행하여 차이가 없어졌는지 확인합니다. 수정 전후는 입력과 관찰 범위를 같게 유지하고 변경한 코드 부분을 설명해야 비교할 수 있습니다.
민감 정보 없이 필요한 근거 남기기
증거의 목적은 사건을 재현하는 것이므로 관계없는 파일 전체나 환경변수 전체를 모을 필요가 없습니다. 실제 이메일이나 토큰이 섞이면 보고서 공유 범위가 넓어질 때 노출도 커질 수 있습니다. 이번 자료는 합성 문자열만 쓰고 environment에는 런타임 버전과 실행 방식만 담습니다. 나중에 로그를 추가할 때도 필요한 필드를 먼저 정하고 값의 출처를 확인합니다.
개인정보가 나타나면 일부를 가린 후 계속 실험하는 선택보다 앞 레슨의 중단 조건을 먼저 적용합니다. 이미 수집한 자료의 처리도 소유자에게 확인합니다. 더 자세한 값이 있어야 분석할 수 있다는 주장을 하기 전에 합성 입력으로 같은 동작을 재현할 수 있는지 살핍니다. 이번 프로젝트는 실사용 데이터 없이 코드의 결함과 수정 결과를 설명하는 훈련입니다.
증거와 연결 오류 읽기
JSON.parse 오류는 evidence.json의 내용이 JSON 형식이 아니라는 뜻일 수 있습니다. 실행 출력 전체를 파일에 붙여 넣었는지, 따옴표나 쉼표가 손상됐는지 확인합니다. ENOENT는 파일이 없거나 위치가 다른 경우입니다. ZIP 루트에서 실행했는지와 evidence.cjs가 성공했는지부터 봅니다. report links 검사의 assertion은 필수 참조나 절이 빠졌을 때 어떤 조건이 실패했는지 알려 줍니다.
오래된 evidence.json만 열어 성공이라고 판단하지 않습니다. 새 실행이 실패했는데 이전 성공 파일이 남아 있을 수 있습니다. check.sh의 종료 코드와 전체 흐름을 먼저 확인하고 파일의 observedAt을 대조합니다. 검증기는 성공 실행에서 증거를 다시 생성합니다. 보고서 인계 시에는 실행 명령, 해당 실행 시각, 수정 파일을 함께 알려 다른 사람이 오래된 파일과 새 결과를 구분하게 합니다.
다음 모듈로 넘기는 기준
인계할 때 앱과 테스트, scope.json, 보고서, 증거 생성 코드를 한 묶음으로 유지합니다. 다음 모듈은 파일 권한과 프로세스 관찰을 추가하므로 기존 API 계약이 계속 통과해야 합니다. 실행 방식이 function-call에서 실제 HTTP로 바뀌면 기존 E01을 덮어 다른 의미로 만들지 않고 새로운 증거 id를 추가합니다. 같은 이름의 증거가 다른 대상을 뜻하면 보고서의 참조를 믿기 어려워집니다.
마지막으로 두 가지 질문에 답해 봅니다. 다른 사람이 이 입력으로 같은 상태와 자료를 얻을 수 있는지, 이 근거만으로 주장할 수 없는 내용은 무엇인지입니다. 첫 질문은 재현성을, 두 번째는 판단의 한계를 점검합니다. 둘 모두 답할 수 있을 때 최초 증거 목록이 준비된 것입니다. 이후 해시와 보존 절차를 추가해도 내용의 의미와 관찰 범위를 정확히 쓰는 이 습관은 계속 필요합니다.
따라하기
관찰과 판단 필드 분리
import json
e = {"id":"E01","scopeId":"scope-001","transport":"function-call","observation":"생성 201, 조회 200","hypothesis":"접근 통제 미검증"}
print(json.dumps(e,ensure_ascii=False,sort_keys=True))실행 결과
{"hypothesis": "접근 통제 미검증", "id": "E01", "observation": "생성 201, 조회 200", "scopeId": "scope-001", "transport": "function-call"}
동일 자료 비교
created = {"id":1,"title":"합성 메모","content":"demo only","owner":"demo-alice"}
read_back = dict(created)
print("same id:", created["id"] == read_back["id"])
print("same body:", created == read_back)실행 결과
same id: True same body: True
증거 생성과 참조 검사
미션 solution ZIP 루트에서 bash check.sh를 실행합니다. evidence.json written: E01, report links verified: scope-001 / E01, PASS: app scope evidence report를 확인합니다. evidence.json의 observedAt과 environment.node는 본인의 이번 실행값이어야 합니다. starter는 앱 저장 TODO를 먼저 해결해야 합니다.
사람 리뷰로 마무리
report.md를 읽어 scope-001, E01 참조가 올바른 파일을 가리키는지 확인합니다. 관찰한 사실과 추측을 각각 한 문장으로 적고, 메모리 소실·고정 owner·HTTP 미검증을 남은 과제로 씁니다. 자동 검사가 확인하지 못하는 승인 근거와 문장 의미를 직접 검토합니다.
확인 문제
실습
미션의 evidence.cjs로 생성한 E01과 report.md를 제출합니다. 요청·응답·조회 응답·실행 시각·실제 런타임·function-call·scope-001을 연결합니다. 관찰 문장, 원인 후보, 미검증 영역을 구분하고 실자료가 없는지 검토합니다. 자동 검사와 사람 리뷰의 확인 범위를 한 문장씩 적습니다.
더 읽기
면접 질문
- 취약점 수정 전후의 테스트 내용을 설명합니다.
- 증거에서 관찰과 추측을 어떻게 구분하나요?