티오리
경력근거: αprism의 네트워크 트래픽 파서 및 프록시 서버를 개발합니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고가 αprism 팀의 일로 '프롬프트를 통한 개인정보·민감정보·기밀정보 유출을 자동으로 식별하고 차단하는 기술' 을 앞세우고, 주요 업무에 'αprism의 네트워크 트래픽 파서 및 프록시 서버를 개발합니다' 와 '각종 LLM 서비스 및 AI IDE의 통신 프로토콜을 역공학합니다' 를 나란히 적었습니다. 서비스마다 다른 본문을 펼쳐 어느 자리에 무엇이 실렸는지 가르는 일이 이 포지션의 자리이고, 이 포트폴리오는 그 식별을 규칙표와 파서로 만듭니다. '고객사 온프레미스 고유 환경에서 발생하는 문제를 해결합니다' 는 대목도 캡처가 안 잡히는 경우를 따로 빼 두는 설계로 받았습니다. 이 포트폴리오가 보여주는 것은 요청 본문을 서비스별 구조대로 펼쳐 규칙과 맞추는 기술이며, 오펜시브 보안 연구나 역공학 실적은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
'프롬프트를 통한 개인정보·민감정보·기밀정보 유출을 자동으로 식별하고 차단하는 기술'. 그 식별 단계가 이 도구의 자리입니다.
'αprism의 네트워크 트래픽 파서 및 프록시 서버를 개발합니다'. 캡처한 요청 본문을 읽는 일이 그대로 업무입니다.
'각종 LLM 서비스 및 AI IDE의 통신 프로토콜을 역공학합니다'. 서비스마다 본문 구조가 달라 필드 경로를 따로 잡아야 합니다.
LLM 요청 본문에 민감정보가 실렸는지 가릅니다. 호스트별 탐지 규칙을 표로 두고 캡처한 요청 본문을 파싱해, 값 패턴이 걸리면 그 자리를, 필드 경로가 걸리면 어느 경로인지를 사유로 남깁니다. base64 로 감싼 첨부는 풀어서 다시 훑어 겉으로 안 보이던 값을 잡고, 본문이 안 잡힌 캡처는 통과로 넘기지 않고 따로 빼며, 규칙에 없는 호스트는 미매핑으로 남겨 규칙을 먼저 채우게 합니다. 캡처는 고객사 트래픽 대신 지원자가 자기 계정으로 로컬 프록시를 세워 직접 뜬 것을 가정합니다.
고객사 온프레미스에 프록시를 놓고 사내에서 나가는 LLM 요청을 살피는 자리에 앉는 FDE 입니다. 서비스마다 본문 구조가 달라 어느 자리에 무엇이 실려 나갔는지를 매번 요청을 펼쳐 눈으로 훑던 사람입니다.
호스트별 탐지 규칙을 표로 두고 캡처 본문과 맞추면, 내보내도 되는 요청과 막을 요청이 규칙으로 갈리고 걸린 자리마다 어느 규칙에 왜 걸렸는지가 근거로 남는다는 가정입니다. 규칙에 없는 호스트는 미매핑으로 빠지므로 아직 규칙이 없는 서비스가 그대로 드러납니다.
캡처 파일과 탐지 규칙표를 받아 요청마다 통과·차단을 판정하고, 걸렸으면 어느 필드 경로의 어느 값이 어느 규칙에 걸렸는지 붙여 호스트별로 묶은 판정 리포트를 터미널과 Markdown 파일로 내는 CLI 가 도는 완결품입니다.
직접 뜬 캡처 100건에 손으로 붙인 정답 판정과 대조해 일치율과 놓친 건수를 내고, 미매핑이 몰리는 호스트를 모아 규칙이 없는 구간을 목록으로 냅니다.
걸린 자리만 가린 본문을 다시 조립해 원래 요청과 나란히 내고, 가린 뒤에도 규칙이 다시 걸리지 않는지 확인하는 단계입니다.
FDE 가 캡처 파일과 탐지 규칙표를 넣으면 요청마다 통과·차단과 걸린 필드 경로가 호스트별로 묶여 나오는 과정을 시연합니다.
직접 뜬 캡처 100건과 손으로 붙인 정답 판정을 대조해 일치율과 놓친 건수를 냅니다. 단, 정답과 캡처를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
본문이 비었거나 호스트 표기가 흔들리거나 규칙에 없는 호스트가 섞인 입력 30종을 넣어도 출력 형식이 안 깨지고 사유별로 빠지는 비율을 보여줍니다.
규칙을 고쳤을 때 옛 판정이 어떻게 뒤집히는지 회귀로 확인합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 캡처의 자가채점이며, 실제 유출 차단 성능은 티오리 고객사의 실 트래픽과 실 판정 기준이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 붙였으니 잘 맞는 게 당연하므로, 본문 결측과 감싼 첨부, 규칙에 없는 호스트를 일부러 섞어 쉬운 요청만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 요청 본문을 서비스별 구조대로 펼쳐 규칙과 맞추는 기술이며, 오펜시브 보안 연구나 역공학 실적은 주장하지 않습니다. 판정은 규칙 대조가 하고 LLM 은 담당자가 읽을 사유 문장만 맡으며 본문 값은 모델로 보내지 않습니다.
규칙에 안 걸렸다고 안전하다는 뜻은 아니어서, 통과 판정에도 훑은 필드 경로를 함께 실어 무엇을 못 봤는지 남깁니다.
실제 도입 때는 고객사마다 금지 항목과 쓰는 서비스가 다르므로, 규칙표를 새로 채우는 일을 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
로컬 프록시가 뜬 LLM 요청 캡처입니다. 호스트 표기 원문과 메서드, 콘텐츠 타입, 본문, 본문 크기, 보낸 앱 이름을 한 행에 둡니다. 호스트 표기는 캡처 도구가 헤더를 그대로 적어 'api.openai.com'과 'API.OpenAI.com'처럼 흔들립니다. 본문이 안 잡힌 캡처, base64 로 감싼 첨부, 규칙표에 없는 사내 호스트를 일부러 섞어 두어 사유가 갈리게 둡니다. 값은 모두 형식만 맞춘 합성이라 실재하는 개인정보가 아닙니다.
| capture_no | captured_at | host_raw | method | content_type_raw | body_text | body_bytes | client_app_name |
|---|---|---|---|---|---|---|---|
| PC-1001 | 2026-08-11T09:12:04 | api.openai.com | POST | application/json | {"model":"gpt-4o","messages":[{"role":… | 118 | vscode-copilot |
| PC-1002 | 2026-08-11T09:20:41 | API.OpenAI.com | POST | application/json | {"model":"gpt-4o","messages":[{"role":… | 142 | cursor |
| PC-1003 | 2026-08-11T10:02:17 | api.anthropic.com | POST | application/json | {"model":"claude","messages":[{"role":… | 214 | cursor |
| PC-1004 | 2026-08-11T10:31:55 | api.anthropic.com | POST | multipart/form-data | --bnd\nContent-Disposition: form-data;… | 186 | internal-agent |
| PC-1005 | 2026-08-11T11:04:09 | api.openai.com | POST | application/json | {"model":"gpt-4o","messages":[{"role":… | 131 | vscode-copilot |
아래 JSON Schema 를 그대로 만족하는 캡처 행을 60개 더 만들어 주세요. 호스트 표기는 'api.openai.com'과 'API.OpenAI.com'처럼 대소문자를 흔들어 적습니다. 2할은 본문을 비우고, 몇 건은 첨부를 base64 로 감싸며, 몇 건은 규칙표에 없는 사내 호스트를 씁니다. 값은 모두 형식만 맞춘 합성으로 두고 실재하는 개인정보를 넣지 마세요. 걸림 여부나 심각도는 넣지 마세요.
{"type": "object", "properties": {"capture_no": {"type": "string"}, "captured_at": {"type": "string"}, "host_raw": {"type": "string"}, "method": {"type": "string"}, "content_type_raw": {"type": "string"}, "body_text": {"type": ["string", "null"]}, "body_bytes": {"type": "integer"}, "client_app_name": {"type": ["string", "null"]}}, "required": ["capture_no", "captured_at", "host_raw", "method", "content_type_raw", "body_text", "body_bytes"]}{"type": "object", "properties": {"capture_no": {"type": "string"}, "captured_at": {"type": "string"}, "host_raw": {"type": "string"}, "method": {"type": "string"}, "content_type_raw": {"type": "string"}, "body_text": {"type": ["string", "null"]}, "body_bytes": {"type": "integer"}, "client_app_name": {"type": ["string", "null"]}}, "required": ["capture_no", "captured_at", "host_raw", "method", "content_type_raw", "body_text", "body_bytes"]}호스트별 탐지 규칙표입니다. 신호 이름과 적용 호스트 표기, 찾을 방식과 패턴, 심각도, 담당을 한 행에 둡니다. 값 패턴과 필드 경로 두 방식을 섞어 두어 걸림이 값 때문인지 자리 때문인지가 갈립니다. 캡처에 나오는 호스트 중 사내 호스트는 일부러 넣지 않아 미매핑이 도출되게 둡니다.
| rule_no | signal_name | host_raw | match_kind | pattern | severity_level | owner_name |
|---|---|---|---|---|---|---|
| RD-1001 | 주민등록번호 형식 | api.openai.com | regex | [0-9]{6}-[1-4][0-9]{6} | high | 보안 담당 |
| RD-1002 | 이메일 주소 | api.openai.com | regex | [a-z0-9._%+-]+@[a-z0-9.-]+ | medium | 보안 담당 |
| RD-1003 | 클라우드 접근 키 | api.openai.com | regex | AKIA[0-9A-Z]{16} | high | 플랫폼 |
| RD-1004 | 사내 공유 경로 | api.anthropic.com | regex | //fileserver/[a-z]+/ | high | 보안 담당 |
| RD-1005 | 시스템 역할 본문 | api.anthropic.com | field_path | messages[*].content | low | 보안 담당 |
아래 JSON Schema 를 그대로 만족하는 탐지 규칙 행을 30개 더 만들어 주세요. 호스트마다 값 패턴과 필드 경로를 섞어 붙이고 심각도를 세 값으로 고루 씁니다. 캡처에 나오는 호스트 중 일부는 일부러 넣지 마세요 — 미매핑이 있어야 규칙의 구멍이 드러납니다.
{"type": "object", "properties": {"rule_no": {"type": "string"}, "signal_name": {"type": "string"}, "host_raw": {"type": "string"}, "match_kind": {"type": "string", "enum": ["regex", "literal", "field_path"]}, "pattern": {"type": "string"}, "severity_level": {"type": "string", "enum": ["high", "medium", "low"]}, "owner_name": {"type": "string"}}, "required": ["rule_no", "signal_name", "host_raw", "match_kind", "pattern", "severity_level"]}{"type": "object", "properties": {"rule_no": {"type": "string"}, "signal_name": {"type": "string"}, "host_raw": {"type": "string"}, "match_kind": {"type": "string", "enum": ["regex", "literal", "field_path"]}, "pattern": {"type": "string"}, "severity_level": {"type": "string", "enum": ["high", "medium", "low"]}, "owner_name": {"type": "string"}}, "required": ["rule_no", "signal_name", "host_raw", "match_kind", "pattern", "severity_level"]}| Go | 본문을 파싱하고 규칙과 맞춰 걸린 자리를 정하는 판정 CLI 를 맡습니다. |
| HTTP·TLS | 요청 본문이 어느 계층에서 보이는지 가르고, 본문이 안 잡힌 캡처를 따로 뺍니다. |
| mitmproxy | 요청을 파일로 뜨는 캡처 단계에 씁니다. 판정 자체는 캡처 도구에 기대지 않습니다. |
| Markdown | 호스트별로 묶은 판정 리포트를 파일로 냅니다. |
✗ fail → 미매핑이 절반을 넘으면 규칙표를 먼저 채웁니다.
✓ pass → 2주차 정답 대조로 넘어갑니다.
✗ fail → 놓친 유형을 모아 필드 경로 정의를 다시 봅니다.
✓ pass → 3주차 가린 본문 조립으로 넘어갑니다.
캡처 한 건이 탐지 규칙과 만나는 자리마다 내보낼지 막을지를 정합니다. 걸렸으면 어느 필드 경로의 어느 규칙에 걸렸는지 싣고, 본문이 안 잡히면 통과로 넘기지 않고 따로 뺍니다. 화면과 리포트는 이 사유 코드로 묶습니다.
{"type": "object", "properties": {"verdict_no": {"type": "string"}, "capture_no": {"type": "string"}, "host_raw": {"type": "string"}, "hit_rule_no": {"type": ["string", "null"]}, "signal_name": {"type": ["string", "null"]}, "leak_code": {"type": "string", "enum": ["clean", "pattern_hit", "field_path_hit", "encoded_payload", "body_missing", "host_unmapped"]}, "hit_field_path": {"type": ["string", "null"]}, "severity_level": {"type": ["string", "null"]}, "owner_name": {"type": ["string", "null"]}, "evidence": {"type": "array", "items": {"type": "object", "properties": {"field": {"type": "string"}, "agg": {"type": ["string", "null"]}, "value": {}}, "required": ["field", "value"]}}, "summary": {"type": "string"}, "hedge": {"type": ["string", "null"]}}, "required": ["verdict_no", "capture_no", "host_raw", "leak_code", "evidence", "summary"]}[
{
"verdict_no": "LV-001",
"capture_no": "PC-1011",
"host_raw": "api.openai.com",
"hit_rule_no": null,
"signal_name": null,
"leak_code": "clean",
"hit_field_path": "messages[0].content",
"severity_level": null,
"owner_name": null,
"evidence": [
{
"field": "body_text",
"agg": null,
"value": "{\"model\":\"gpt-4o\",\"messages\":[{\"role\":\"user\",\"content\":\"이 SQL 인덱스 어떻게 잡을까\"}]}"
},
{
"field": "rule_no",
"agg": "count",
"value": 0
}
],
"summary": "본문 전체를 훑어도 걸리는 규칙이 없어 내보내도 되는 요청입니다.",
"hedge": null
},
{
"verdict_no": "LV-002",
"capture_no": "PC-1005",
"host_raw": "api.openai.com",
"hit_rule_no": "RD-1010",
"signal_name": "마스킹 표기",
"leak_code": "clean",
"hit_field_path": "messages[0].content",
"severity_level": "low",
"owner_name": "보안 담당",
"evidence": [
{
"field": "pattern",
"agg": null,
"value": "[REDACTED]"
},
{
"field": "client_app_name",
"agg": null,
"value": "vscode-copilot"
}
],
"summary": "연락처 자리가 이미 마스킹 표기로 바뀌어 있어 내보내도 되는 요청입니다.",
"hedge": null
},
{
"verdict_no": "LV-003",
"capture_no": "PC-1002",
"host_raw": "API.OpenAI.com",
"hit_rule_no": "RD-1001",
"signal_name": "주민등록번호 형식",
"leak_code": "pattern_hit",
"hit_field_path": "messages[0].content",
"severity_level": "high",
"owner_name": "보안 담당",
"evidence": [
{
"field": "pattern",
"agg": null,
"value": "[0-9]{6}-[1-4][0-9]{6}"
},
{
"field": "body_bytes",
"agg": null,
"value": 142
}
],
"summary": "사용자 본문에 주민등록번호 형식이 그대로 실려 있어 내보내기 전에 막아야 합니다.",
"hedge": null
},
{
"verdict_no": "LV-004",
"capture_no": "PC-1003",
"host_raw": "api.anthropic.com",
"hit_rule_no": "RD-1004",
"signal_name": "사내 공유 경로",
"leak_code": "field_path_hit",
"hit_field_path": "messages[1].content",
"severity_level": "high",
"owner_name": "보안 담당",
"evidence": [
{
"field": "pattern",
"agg": null,
"value": "//fileserver/[a-z]+/"
},
{
"field": "client_app_name",
"agg": null,
"value": "cursor"
}
],
"summary": "두 번째 메시지 본문에 사내 공유 경로가 들어 있어 문서 위치가 그대로 나갑니다.",
"hedge": "경로만으로 파일이 열리지는 않으나 조직 구조가 드러나 높음으로 둡니다."
},
{
"verdict_no": "LV-005",
"capture_no": "PC-1004",
"host_raw": "api.anthropic.com",
"hit_rule_no": "RD-1006",
"signal_name": "첨부 본문",
"leak_code": "encoded_payload",
"hit_field_path": "multipart.part[0].body",
"severity_level": "medium",
"owner_name": "보안 담당",
"evidence": [
{
"field": "content_type_raw",
"agg": null,
"value": "multipart/form-data"
},
{
"field": "body_bytes",
"agg": null,
"value": 186
}
],
"summary": "첨부 본문을 base64 로 풀자 이메일 주소가 나와 겉으로는 안 보이던 유출입니다.",
"hedge": null
},
{
"verdict_no": "LV-006",
"capture_no": "PC-1008",
"host_raw": "api.anthropic.com",
"hit_rule_no": null,
"signal_name": null,
"leak_code": "body_missing",
"hit_field_path": null,
"severity_level": null,
"owner_name": null,
"evidence": [
{
"field": "body_text",
"agg": null,
"value": null
},
{
"field": "body_bytes",
"agg": null,
"value": 0
}
],
"summary": "본문이 잡히지 않아 판정하지 않고 캡처 설정을 먼저 확인합니다.",
"hedge": "본문이 비었다고 안전하다는 뜻이 아니어서 통과로 넘기지 않습니다."
},
{
"verdict_no": "LV-007",
"capture_no": "PC-1009",
"host_raw": "llm.internal.corp",
"hit_rule_no": null,
"signal_name": null,
"leak_code": "host_unmapped",
"hit_field_path": null,
"severity_level": null,
"owner_name": null,
"evidence": [
{
"field": "host_raw",
"agg": null,
"value": "llm.internal.corp"
},
{
"field": "rule_no",
"agg": "count",
"value": 12
}
],
"summary": "규칙 열두 행에 이 호스트가 없어 미매핑으로 남기고 규칙을 먼저 채웁니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. leak_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. verdict_no·capture_no·host_raw·leak_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. capture_no 가 캡처에 실재하는 번호인지 대조합니다. 4. hit_rule_no 가 있으면 규칙표에 실재하는 행 번호인지 대조합니다. 5. hit_rule_no 가 있으면 signal_name·severity_level·owner_name 이 그 규칙 행의 값과 같은지 대조합니다. 6. leak_code 가 pattern_hit 이면 그 패턴을 본문에 다시 돌려 걸리는지 확인합니다. 7. leak_code 가 field_path_hit 이면 hit_field_path 가 본문을 펼쳤을 때 실재하는 경로인지 확인합니다. 8. leak_code 가 encoded_payload 이면 첨부를 풀기 전에는 안 걸리고 푼 뒤에 걸리는지 두 번 돌려 확인합니다. 9. leak_code 가 clean 이면 규칙표의 모든 패턴을 다시 돌려 하나도 걸리지 않는지 확인합니다. 10. body_text 가 비어 있으면 leak_code 가 body_missing 인지 확인합니다. 11. host_raw 를 공백·기호를 지우고 대소문자를 접은 뒤 규칙표의 호스트와 맞고, 맞는 행이 없으면 leak_code 가 host_unmapped 인지 확인합니다. 12. evidence 의 field 이름이 캡처와 규칙표에 실재하는 컬럼인지 대조합니다. 13. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 확인합니다. 14. summary 에 본문에서 걸린 값 자체가 그대로 실리지 않았는지 확인합니다. 15. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 직접 뜬 캡처 100건에 손으로 붙인 정답 판정과 대조해 일치율과 놓친 건수, 사유 코드별 분포를 내고, 미매핑이 몰리는 호스트를 모아 규칙이 없는 구간을 목록으로 냅니다. [수동 검증 · 사람이 봅니다] 1. 심각도 등급을 어디서 가를지 고객사 정책에 맞춰 담당자가 정합니다. 2. 통과로 나온 요청 중 무엇을 표본으로 다시 볼지 담당자가 고릅니다.