위즈코리아
경력근거: QA 팀의 피드백 전 AI를 활용해 단위/통합 테스트 코드 작성
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 AI 를 1차 보안 검수자로 정의하고 QA 피드백 전에 개발자가 스스로 검증한다는 태도를 자격요건에 두며, 팀이 함께 쓰는 표준 프롬프트와 워크플로우를 만들라고 적습니다. 이 포트폴리오는 그 자가 검증을 명령 한 줄로 만든 것입니다. 판정 기준을 규칙표 파일로 빼내 사람마다 다르게 기억하던 절차를 팀이 고쳐 쓰는 한 곳에 고정합니다. 다만 이 포트폴리오는 diff 파싱과 규칙 판정과 CLI 구현을 보여줍니다. 취약점 분석이나 침투 시험에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
회사가 AI 를 보조 도구가 아니라 동료 개발자이자 1차 보안 검수자로 정의한다고 직무개요 첫 줄에 적습니다. 도구를 쓰는 방식 자체를 설계해야 하는 자리입니다.
QA 팀의 피드백 전에 AI 를 활용해 테스트 코드를 쓰고 로직 무결성을 검증하며, 내가 작성한 코드는 반드시 스스로 검증한다는 태도를 자격요건에 둡니다.
팀 내 표준 프롬프트와 AI 워크플로우를 구축하고 코드 리뷰 자동화와 자동 문서화에 참여한다고 적습니다. 개인의 요령이 아니라 팀이 쓰는 절차를 만드는 일입니다.
개발자가 git show 로 뽑은 diff 파일을 넘기면, 바뀐 파일마다 종류를 가르고 그 종류에 필요한 점검이 붙었는지 판정합니다. 무엇이 필요한지는 경로 규칙과 추가된 줄의 패턴이 정하고, 모델은 어떤 점검이 왜 빠졌는지 개발자가 읽을 한 줄로 요약합니다.
저는 보안 제품의 백엔드를 만듭니다. AI 도구로 하루에 커밋을 여러 개 올리는데, 요청 값을 확인하는 한 줄이나 테스트가 빠진 채로 리뷰에 올라갑니다. 리뷰어가 그걸 잡으면 다시 손보느라 하루가 밀립니다.
빠진 점검이 늦게 발견되는 이유는 기준이 없어서가 아니라 커밋마다 무엇이 필요한지 사람이 기억해야 하기 때문입니다. 바뀐 파일의 경로와 추가된 줄을 규칙이 읽어 판정하면, 올리기 전에 스스로 채웁니다.
CLI 하나가 완결됩니다. git show 로 뽑은 diff 파일을 받아 파일 헤더와 헝크 헤더(@@ 로 시작하는 줄)와 추가된 줄을 나눠 읽고, 경로 규칙으로 파일 종류를 정한 뒤 그 종류에 필요한 점검이 붙었는지 판정해 표로 냅니다. 저장소에서 커밋을 직접 뽑는 수집은 파일을 받는 것으로 대신하고 3주차로 미룹니다.
공개 저장소 커밋 120건에 돌려 리뷰에서 지적받은 항목과 판정을 맞춰 보고, 규칙이 놓친 유형을 표로 냅니다. 그 유형이 다음 규칙표 행 후보가 됩니다.
저장소에서 diff 를 직접 뽑아 커밋 직전에 돌고, 필수 점검이 빠지면 실패 코드로 끝나 커밋이 만들어지지 않습니다.
개발자가 git show 로 뽑은 diff 파일을 넘기면 파일마다 종류와 빠진 점검이 표로 나오고 판정 근거가 된 줄이 붙습니다.
이름만 바뀌어 본문 변경이 없는 커밋과, 규칙표의 패턴 칸이 빈 행과, 어느 경로 규칙에도 걸리지 않는 파일이 섞인 diff 30종을 넘겨도 실행이 멈추지 않고 판정할 수 없는 점검은 사유로 남습니다.
공개 저장소 커밋 120건으로 판정 일치율을 냅니다. 단, 규칙표와 정답을 모두 작성자가 만든 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 작성자가 정답을 붙인 자가채점이며 실제 결함 차단 효과는 위즈코리아의 저장소 이력과 리뷰 기록이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 작성자가 정답을 붙인 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 본문 변경이 없는 이름 변경 커밋과 규칙표의 빈 칸과 분류 밖 경로를 표본에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
점검이 붙었다는 사실이 그 코드가 안전하다는 뜻은 아닙니다. 규칙은 테스트 파일과 검증 호출의 존재만 보므로 내용의 품질은 사람이 봐야 하며, 판정을 결론으로 읽지 않게 근거 줄을 항상 같이 냅니다.
저장소마다 테스트 위치와 파일 배치 규약이 달라 같은 규칙표가 그대로 서지 않습니다. 예외가 늘면 규칙표를 팀이 고치는 파일로 두고 저장소별로 나눠야 하며, 그것은 도입 단계 과제로 분리합니다.
취약점을 찾아내는 정적 분석은 이 포트폴리오의 범위 밖입니다. 여기서는 팀이 정한 점검이 커밋에 붙었는지 보는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
git show 로 뽑은 커밋 diff 원문을 줄 그대로 담습니다. 판정의 입력이며 본문 변경이 없는 이름 변경 커밋과 어느 경로 규칙에도 걸리지 않는 파일을 의도적으로 포함합니다.
| commit_sha | line_no | text |
|---|---|---|
| 9f3c1ab | 1 | diff --git a/src/api/coupon_handler.py… |
| 9f3c1ab | 2 | index 3b1f0aa..7c2e918 100644 |
| 9f3c1ab | 3 | --- a/src/api/coupon_handler.py |
| 9f3c1ab | 4 | +++ b/src/api/coupon_handler.py |
| 9f3c1ab | 5 | @@ -12,6 +12,9 @@ from .store import l… |
아래 JSON Schema 에 맞는 커밋 diff 줄을 더 만들어 주세요. {"type":"object","properties":{"commit_sha":{"type":"string"},"line_no":{"type":"integer","minimum":1},"text":{"type":"string"}},"required":["commit_sha","line_no","text"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) similarity index 100% 로 본문 변경이 하나도 없는 이름 변경 커밋, (2) 어느 경로 규칙에도 걸리지 않는 경로의 새 파일, (3) 한 커밋에 파일이 셋 이상 들어간 diff, (4) 헝크 헤더 뒤에 함수 이름이 붙은 줄과 붙지 않은 줄. 줄 순서는 git 이 내보내는 순서(경로 사전순, 파일마다 헤더 다음에 헝크)를 지키고, 생성한 줄은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"commit_sha":{"type":"string"},"line_no":{"type":"integer","minimum":1},"text":{"type":"string"}},"required":["commit_sha","line_no","text"]}파일 종류와 필요한 점검을 정하는 팀 규칙표입니다. 판정의 기준이며 규칙 종류에 따라 비는 칸과 패턴 칸이 빈 행을 의도적으로 포함합니다.
| rule_no | rule_kind | path_glob | line_pattern | target_class | check_code |
|---|---|---|---|---|---|
| CR-01 | classify | src/api/** | — | api | file_class |
| CR-02 | classify | src/ui/** | — | ui | file_class |
| CR-03 | classify | src/batch/** | — | batch | file_class |
| CR-04 | classify | deploy/**/*.yaml | — | config | file_class |
| CR-05 | classify | tests/** | — | test | file_class |
아래 JSON Schema 에 맞는 규칙표 행을 더 만들어 주세요. {"type":"object","properties":{"rule_no":{"type":"string"},"rule_kind":{"type":"string","enum":["classify","pair","pattern"]},"path_glob":{"type":["string","null"]},"line_pattern":{"type":["string","null"]},"target_class":{"type":"string","enum":["api","ui","batch","config","test"]},"check_code":{"type":"string","enum":["file_class","test_pair","validator_call"]}},"required":["rule_no","rule_kind","target_class","check_code"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) rule_kind 가 pattern 인데 line_pattern 칸이 빈 행, (2) path_glob 이 서로 겹쳐 한 파일이 두 규칙에 걸리는 행 두 개, (3) 어떤 파일도 걸리지 않는 경로를 적은 행, (4) 괄호·역슬래시 같은 정규식 특수문자가 들어간 line_pattern. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"rule_no":{"type":"string"},"rule_kind":{"type":"string","enum":["classify","pair","pattern"]},"path_glob":{"type":["string","null"]},"line_pattern":{"type":["string","null"]},"target_class":{"type":"string","enum":["api","ui","batch","config","test"]},"check_code":{"type":"string","enum":["file_class","test_pair","validator_call"]}},"required":["rule_no","rule_kind","target_class","check_code"]}| TypeScript | CLI 본체의 언어입니다. diff 를 줄 단위로 읽는 파서와 판정 규칙이 여기 있습니다. |
| Node.js | diff 파일을 읽고 판정을 돌리는 실행 환경입니다. |
| Git | 입력을 만드는 명령입니다. git show 와 git diff 의 unified 출력을 그대로 받아 씁니다. |
| Jest | 파서와 규칙 엔진의 단위 테스트입니다. 헝크 헤더 형태별로 입력을 넣어 파싱 결과를 고정합니다. |
| GitHub Copilot | 이 도구로 쓴 커밋을 시연 입력으로 씁니다. 생성 코드가 섞인 diff 에서 규칙이 어디서 흔들리는지 봅니다. |
| CI/CD | 3주차에 커밋 전 검사로 걸어 필수 점검이 빠지면 실패로 끝냅니다. |
| LLM | 판정이 끝난 뒤 어떤 점검이 왜 빠졌는지 개발자가 읽을 한 줄로 요약합니다. 무엇이 필요한지는 규칙표가 정하고 모델은 여기에 개입하지 않습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 공개 저장소 커밋으로 리뷰 지적과 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 커밋 직전에 자동으로 돌고 막는 단계로 확장합니다.
커밋 한 건에서 파일 하나에 대한 점검 결과입니다. 어떤 규칙으로 판정했는지와 근거가 된 줄을 함께 남깁니다.
{"type":"object","properties":{"result_no":{"type":"string"},"commit_sha":{"type":"string"},"file_path":{"type":"string"},"file_class":{"type":"string","enum":["api","ui","batch","config","test","unresolved"]},"check_code":{"type":"string","enum":["file_class","test_pair","validator_call"]},"rule_no":{"type":["string","null"]},"result_code":{"type":"string","enum":["check_passed","check_missing","rule_incomplete","class_unresolved","no_added_lines"]},"added_lines":{"type":"integer","minimum":0,"description":"그 파일 구간에서 + 로 시작하되 +++ 가 아닌 줄의 수"},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"],"enum":["count","sum","avg","min","max",null]},"value":{"type":["string","number","null"]}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["result_no","commit_sha","file_path","file_class","check_code","result_code","evidence","summary"]}[
{
"result_no": "CK-0001",
"commit_sha": "9f3c1ab",
"file_path": "src/api/coupon_handler.py",
"file_class": "api",
"check_code": "test_pair",
"rule_no": "CR-06",
"result_code": "check_passed",
"added_lines": 3,
"evidence": [
{
"field": "path_glob",
"agg": null,
"value": "tests/api/**"
},
{
"field": "text",
"agg": null,
"value": "diff --git a/tests/api/test_coupon_handler.py b/tests/api/test_coupon_handler.py"
}
],
"summary": "같은 커밋에서 tests/api 아래 테스트 파일이 함께 바뀌어 동반 테스트가 붙었습니다.",
"hedge": null
},
{
"result_no": "CK-0002",
"commit_sha": "9f3c1ab",
"file_path": "src/api/coupon_handler.py",
"file_class": "api",
"check_code": "validator_call",
"rule_no": "CR-09",
"result_code": "check_passed",
"added_lines": 3,
"evidence": [
{
"field": "line_pattern",
"agg": null,
"value": "^\\+\\s*validate_[a-z_]+\\("
},
{
"field": "text",
"agg": null,
"value": "+ validate_coupon_payload(payload)"
}
],
"summary": "추가된 3줄 가운데 검증 함수 호출이 있어 입력 검증이 붙었습니다.",
"hedge": null
},
{
"result_no": "CK-0003",
"commit_sha": "4e08c52",
"file_path": "src/api/refund_handler.py",
"file_class": "api",
"check_code": "test_pair",
"rule_no": "CR-06",
"result_code": "check_missing",
"added_lines": 4,
"evidence": [
{
"field": "path_glob",
"agg": null,
"value": "tests/api/**"
},
{
"field": "text",
"agg": "count",
"value": 0
}
],
"summary": "API 파일을 고쳤는데 같은 커밋에 tests/api 아래 파일이 하나도 없어 테스트를 채우고 올려야 합니다.",
"hedge": null
},
{
"result_no": "CK-0004",
"commit_sha": "4e08c52",
"file_path": "src/api/refund_handler.py",
"file_class": "api",
"check_code": "validator_call",
"rule_no": "CR-10",
"result_code": "check_missing",
"added_lines": 4,
"evidence": [
{
"field": "line_pattern",
"agg": null,
"value": "^\\+\\s*if not isinstance\\("
},
{
"field": "text",
"agg": "count",
"value": 0
}
],
"summary": "추가된 4줄 어디에도 검증 패턴이 걸리지 않아 요청 값을 확인하는 코드를 넣어야 합니다.",
"hedge": null
},
{
"result_no": "CK-0005",
"commit_sha": "b7d1f60",
"file_path": "src/batch/settle_job.py",
"file_class": "batch",
"check_code": "validator_call",
"rule_no": "CR-12",
"result_code": "rule_incomplete",
"added_lines": 3,
"evidence": [
{
"field": "line_pattern",
"agg": "count",
"value": 0
},
{
"field": "text",
"agg": null,
"value": "+ for row in rows:"
}
],
"summary": "규칙표의 배치 패턴 행에 찾을 패턴이 적혀 있지 않아 이 점검을 돌리지 못했습니다.",
"hedge": "규칙표의 빈 칸을 채우면 그때부터 판정이 나옵니다."
},
{
"result_no": "CK-0006",
"commit_sha": "b7d1f60",
"file_path": "scripts/oneoff_backfill.py",
"file_class": "unresolved",
"check_code": "file_class",
"rule_no": null,
"result_code": "class_unresolved",
"added_lines": 2,
"evidence": [
{
"field": "path_glob",
"agg": "count",
"value": 0
},
{
"field": "text",
"agg": null,
"value": "diff --git a/scripts/oneoff_backfill.py b/scripts/oneoff_backfill.py"
}
],
"summary": "어느 경로 규칙에도 걸리지 않아 어떤 점검이 필요한지 정하지 못했습니다.",
"hedge": "scripts 경로를 규칙표에 더할지는 팀이 정합니다."
},
{
"result_no": "CK-0007",
"commit_sha": "2a55e9c",
"file_path": "src/ui/coupon_banner.tsx",
"file_class": "ui",
"check_code": "test_pair",
"rule_no": "CR-08",
"result_code": "no_added_lines",
"added_lines": 0,
"evidence": [
{
"field": "path_glob",
"agg": null,
"value": "tests/ui/**"
},
{
"field": "text",
"agg": null,
"value": "similarity index 100%"
}
],
"summary": "이름만 바뀌고 본문은 그대로라 추가된 줄이 없어 점검을 돌리지 않았습니다.",
"hedge": null
},
{
"result_no": "CK-0008",
"commit_sha": "b7d1f60",
"file_path": "src/batch/settle_job.py",
"file_class": "batch",
"check_code": "test_pair",
"rule_no": "CR-07",
"result_code": "check_passed",
"added_lines": 3,
"evidence": [
{
"field": "path_glob",
"agg": null,
"value": "tests/batch/**"
},
{
"field": "text",
"agg": null,
"value": "diff --git a/tests/batch/test_settle_job.py b/tests/batch/test_settle_job.py"
}
],
"summary": "배치 파일과 같은 커밋에 tests/batch 아래 테스트가 함께 올라왔습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. result_code 가 enum 다섯 값 중 하나인지 확인합니다. 2. result_no·commit_sha·file_path·check_code·result_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. added_lines 가 그 파일 구간에서 + 로 시작하되 +++ 가 아닌 줄의 수와 같은지 다시 셉니다. 4. evidence 의 field 이름이 입력 diff 또는 규칙표에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 줄 또는 그 규칙 행에 적힌 값과 같은지 확인합니다. 6. 규칙표에서 line_pattern 칸이 빈 행을 만나면 그 점검만 rule_incomplete 로 남고 나머지 점검은 그대로 도는지 확인합니다. 7. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 제 공개 저장소 커밋 120건에 돌려 리뷰에서 지적받은 항목과 result_code 를 비교해 일치율과 규칙이 놓친 유형을 냅니다. [수동 검증 · 사람이 봅니다] 1. 이름만 바뀐 커밋에 테스트를 요구할지 표본 20건을 눈으로 확인합니다. 2. 어떤 줄 패턴을 입력 검증으로 볼지는 팀 코딩 규약을 따라 담당자가 정합니다.