분위기는 판결이 아니다: '모른다'라고 말할 수 있는 도구를 만들었다
요약
사용자의 직감을 넘어 객관적인 판단을 돕기 위해 Rust로 개발된 오프라인 IP 평판 엔진 'kilo'를 소개합니다. 이 도구는 보안 위협 데이터를 로컬에 인덱싱하여 네트워크 호출 없이도 신속하고 안전하게 IP의 신뢰성을 검사합니다.
핵심 포인트
- Rust 언어로 작성된 12MB 크기의 단일 바이너리 도구
- 네트워크 호출이 없는 오프라인 방식의 IP 평판 검사
- 수집, 정규화, 컴파일, 검사로 이어지는 4단계 파이프라인
- 데이터 유출 방지 및 오프라인 환경에서의 보안성 확보
도구는 한 가지 일을 해야 하고, 그것을 잘 해내야 하며 — 모두가 잊어버리는 이 부분 — 자신의 지식이 어디서 끝나는지를 정확히 알아야 한다.
나는 오늘 같은 아침을 위해 kilo를 만들었다. 다만 이런 아침이 이렇게 빨리 올 줄은 몰랐을 뿐이다.
나를 기다리고 있던 메시지
잠에서 깨어 커피를 따르고, 완전히 깨기 전에는 하지 말아야 할 행동을 했다. 바로 메시지를 확인하는 것이다. 그리고 그곳에, 마치 의도적으로 배치된 것처럼 받은 편지함 상단에 그것이 놓여 있었다. 차가운 리크루터(Recruiter)의 제안이었다.
항공우주 기업의 품질 엔지니어(Quality Engineer) 직무에 대한 "흥미로운 기회"였다. 따뜻하고, 전문적이며, 약간의 긴박함이 느껴졌다. 직무 기술서를 확인하고 지원하려면 아래 링크를 클릭하십시오. 링크는 불투명한 토큰과 끝에 붙은 source 및 method 파라미터가 포함된 긴 추적 URL이었다.
그리고 서명이었다. 단 한 줄로 물리치료사, 콜센터 매니저, 이커머스 파워 셀러, 화물 배차원이라고 주장하는 한 명의 "리크루터" — 서로 관련 없는 6가지 직업이 하나의 직함에 쌓여 있었고, 이제는 항공우주 기업에 품질 엔지니어를 배치하려 하고 있었다. 그것은 이력서가 아니다. 그것은 슬롯머신이다.
바로 그것이었다. 내가 이 도구를 만든 바로 그 상황이, 커피를 다 마시기도 전에 내 편지함에 도착한 것이다.
왜냐하면 보통 그다음에 일어날 일은 다음과 같기 때문이다. 내 직감은 **사기(Scam)**라고 말한다. 내 직감은 대개 맞다. 하지만 "대개 맞다"는 식의 판단은 결국 사기가 아닌 단 하나의 링크를 클릭하게 만드는 원인이 된다. 직감은 분위기(Vibe)일 뿐이며, 분위기는 판결(Verdict)이 아니다. 그 좌절감이 바로 몇 달 전 내가 앉아서 kilo를 작성한 정확한 이유였다 — 이메일 주소에 대해 느낌을 갖는 것을 멈추고, 그것에 대해 알기 시작하는 방법 말이다. 그래서 오늘 아침은 짜증 나는 일이 아니었다. 그것은 마침내 실험이 스스로 실행되는 순간이었다.
나는 커피를 내려놓고 터미널(Terminal)을 열었다.
내가 실제로 만든 것, 그리고 그 이유
잠시 뒤로 돌아가 kilo가 무엇인지 말씀드리겠다. 왜냐하면 그 설계가 곧 논점이기 때문이다.
KiloCheck는 오프라인 IP 평판 엔진 (IP-reputation engine)입니다. 단일 바이너리, 약 12MB 크기, Rust 언어로 작성되었습니다. 이 도구의 전체 세계관은 4단계 파이프라인으로 구성됩니다: 서명되고 버전이 지정된 위협 릴리스 (threat release)를 수집 (ingest) → 유형화된 관측값 (typed observations)을 정규화 (normalize) → 불변의 로컬 인덱스 (immutable local index)를 컴파일 (compile) → 해당 인덱스를 기준으로 IP를 검사 (check). 입력 단계에서 암호학적으로 검증되며, 출력 단계에서는 네트워크 호출이 전혀 발생하지 않습니다.
저는 의도적으로 오프라인 방식으로 만들었습니다. 일반적인 검사는 인텔리전스 API (intelligence API)에 도달하지 못하는 경우가 많습니다. 수상한 무언가를 분류 (triaging)할 때, 저는 제 쿼리가 유출되는 것을 원치 않고, 누군가에게 눈치를 채게 하고 싶지 않으며, 비행기 안에서의 새벽 3시에도 책상 앞에서와 동일한 답변을 얻기를 원합니다. 위협 지식은 서명된 로컬 스냅샷 (local snapshot)에 구워져(baked) 있습니다. 검사는 이미 신뢰하고 있는 증거를 대상으로 하는 단순한 조회 (lookup)일 뿐입니다.
설치는 체크섬 (checksum) 확인과 복사뿐입니다. 도구가 마땅히 그래야 하는 방식이죠:
curl -fsSL https://raw.githubusercontent.com/copyleftdev/kilocheck/v0.2.0/scripts/install.sh | sh
kilo update # 서명된 데이터 릴리스를 가져옴 → "Claims 3160" 출력
kilo status --json
...
이것이 도구였습니다. 그리고 이것이 그날 아침이었습니다. 이제 하나를 다른 하나에 적용해 볼 차례입니다.
실험 1단계: 미끼를 숫자로 바꾸기
kilo는 단 하나의 질문, 즉 _"이 IP가 알려진 악성 행위자인가?"_에 답합니다. 그래서 먼저 그 추적 링크를 주소로 바꿔야 했습니다. 그것이 dig의 역할입니다. URL 뒤에 숨겨진 호스트 이름을 실제로 서비스를 제공하는 IP로 분해(resolve)하는 것이죠.
$ dig +short <the-redirect-host>
203.0.113.90
그다음, 제가 답변하도록 만든 질문을 도구에게 던졌습니다. 이 순간은 실험이 설계를 입증하거나, 혹은 저를 당혹스럽게 만들 순간입니다:
$ kilo check 203.0.113.90 --json | jq '.data[0].verdict'
{
"disposition": "unknown",
"confidence": 0.0,
...
NOT_OBSERVED (관측되지 않음). 신뢰도 (confidence) 0.0.
저는 미소를 지었습니다. 왜냐하면 이것이 바로 제가 설계한 동작이며, 성능이 떨어지는 도구라면 틀렸을 결과였기 때문입니다.
그 순간의 압박감을 생각해 보십시오. 저는 그것이 명백히 사기라고 생각하고 있습니다. 분위기가 압도적입니다. 운영자를 기쁘게 하기 위해 만들어진 도구였다면, 그 압박을 느끼고 제가 이미 믿고 있는 것을 확인해 줄 무서운 빨간색 판결을 내렸을 것입니다. 하지만 kilo는 거부합니다. 그것은 이렇게 말합니다: 이 주소에 대한 증거가 없으며, 당신을 똑똑하게 만들어 주기 위해 증거를 지어내지 않겠습니다.
그 거절이야말로 제가 이 도구에 심어놓은 가장 중요한 요소입니다. 도구는 "안전함(safe)", "나쁨(bad)", 그리고 "모름(I don't know)"의 차이를 알아야 하며, 이들을 하나로 뭉뚱그리는 것을 거부해야 합니다. 누락됨(Missing), 알 수 없음(unknown), 오래됨(stale), 불완전함(incomplete), 안전하지 않음(unsafe): 제 모델에서는 이들이 동일한 어깨 으쓱임의 세 가지 색조가 아닌, 다섯 가지의 뚜렷한 상태입니다. 오늘 아침, 이 도구는 거짓말을 하고 싶은 극심한 유혹 속에서도 정직한 상태를 선택했습니다.
2단계: 여전히 날카로울 수 있음을 증명하기
정직한 "모름"은 동일한 도구가 단호한 "예"라고 말할 수 있을 때에만 가치가 있습니다. 만약 kilo가 모든 것에 대해 unknown을 반환했다면, 그것은 겸손한 것이 아니라 고장 난 것이었을 겁니다. 그래서 저는 잠옷 차림 그대로 대조 실험을 실행했습니다. 의심스러운 호스트, 알려진 명령 및 제어(Command-and-Control, C2) 노드, 그리고 평범한 공용 DNS 리졸버(resolver)를 한 번에 테스트했습니다. JSON에서 추출한 결과는 다음과 같습니다:
203.0.113.90 → unknown monitor NOT_OBSERVED (0 sources)
162.243.103.246 → CRITICAL block COMMAND_AND_CONTROL (1 source, conf 0.99)
8.8.8.8 → unknown monitor NOT_OBSERVED (0 sources)
보십시오. 알려진 C2 노드는 출처(provenance)가 첨부된 critical / block / 0.99를 받았습니다. 공용 리졸버와 의심스러운 호스트는 모두 차분하게 NOT_OBSERVED를 받았습니다. 깨끗한 인프라에 대한 오보도 없었고, 실제 위협에 대한 경보 누락도 없었습니다. 실험은 성공적이었습니다: 도구의 침묵이 의미를 갖는 이유는, 도구의 경보가 정확히 의미를 갖기 때문입니다.
3단계: 제 도구가 저에게 가리켜 준 반전
결과적으로 인프라는 깨끗한 것으로 나왔습니다. 제 직감이 틀렸던 걸까요? 아닙니다. 잘못된 대상을 겨냥하고 있었을 뿐입니다. 그리고 그것이 바로 제가 예상치 못했던 부분이었습니다.
kilo의 정직한 부정적(negative) 결과는 사건을 종결시키지 않았습니다. 대신 사건을 _이동_시켰습니다. "이 인프라에는 위험이 없다"라는 말은 하나의 좌표였습니다. 그것은 저에게 다른 곳을 찾아보라고 알려주었습니다. 그래서 저는 제 도구 벨트에 있는 이웃 도구들을 활용해 나머지 흔적을 따라갔습니다.
curl은 리다이렉트(redirect)를 따라갔습니다. 헤더(headers)만 확인했을 뿐, 바디(body)에는 전혀 손을 대지 않았습니다:
HTTP/2 302 → /<captcha-gate>?...&source=...&method=...
openssl은 네트워크 선(wire)에서 인증서(cert)를 직접 읽어 들였습니다. 그것은 합법적이고 잘 알려진 채용 트래픽 애그리게이터(job-traffic aggregator)를 위한, 실제로 올바르게 발급된 인증서였습니다. 그리고 빠른 조사를 통해 고리가 연결되었습니다. 제안서에 등장한 항공우주 기업은 실존하며 실제 엔지니어링 채용 공고를 내고 있었고, 추적 링크는 애그리게이터 자체의 클릭 사기 방지 게이트(anti-click-fraud gate)를 거쳐 리다이렉트되는 제휴 _클릭 트래커(click-tracker)_였습니다. 페이로드(payload)는 없었습니다. 자격 증명 탈취(credential harvest)도 없었습니다. 체인의 어디에도 악성 IP는 없었습니다.
그렇다면 실제 위협은 무엇이었을까요? 그것은 패킷(packets) 안에 있었던 것이 아니었습니다. 그것은 발신자였습니다. 소셜 플랫폼 전반에 제휴 수수료 링크를 뿌리며, 여섯 가지 이상의 서로 관련 없는 직업들을 짜깁기해 채용 담당자라는 가면을 쓴 프리랜서 리드 생성(lead-gen) 스패머였습니다.
그리고 제가 이 글을 쓰는 이유는 바로 이것 때문입니다: 제가 만든 도구가 '모르는 척하기'를 거부함으로써 사건을 해결했기 때문입니다. 도구의 0.0 결과는 저를 악성코드(malware)라는 토끼굴에서 멀어지게 하여, 진실이 실제로 존재하는 사회 공학(social-engineering) 신호 쪽으로 인도했습니다. 만약 kilo가 제 기분에 맞춰 깨끗한 인프라를 보고도 양치기 소년처럼 경고를 울렸다면, 저는 채용 게시판을 해부하며 오전 시간을 허비했을 것입니다. 하지만 도구가 정직함을 유지했기에, 첫 번째 실마리를 잡자마자 저에게 실마리를 건네주었습니다.
그것이 바로 제가 이 도구를 실행하도록 설계한 정확한 실험이었습니다. 그리고 도구는 통과했습니다.
오늘 아침이 실제로 내게 가르쳐준 것
채용 담당자는 결코 핵심이 아니었습니다. 오늘 아침의 흐름이 핵심이었습니다.
저는 이 문제를 단 하나의 뛰어난 도구로 해결한 것이 아닙니다. 작고 정직한 도구들의 체인(chain)으로 해결했습니다. dig가 이름을 주소로 변환하여 kilo가 확인할 대상을 제공했고, kilo의 정직한 부정(negative) 응답이 질문의 틀을 재구성하여 curl과 openssl이 어디를 살펴봐야 할지 알게 했습니다. 그리고 그 도구들 모두가 **안정적인 JSON (stable JSON)**을 출력했습니다. 덕분에 저는 산문을 파싱(parsing)하는 것이 아니라 _계약 (contracts)_을 소비할 수 있었습니다. 종료 코드(Exit codes)는 중요합니다. 스키마(Schemas)도 중요합니다. 실패는 읽을 수 있는 상태로 유지되어야 합니다.
이것이 제가 이런 것들을 만들면서 계속 되새기는 선언문입니다: 분위기(vibes)가 아니라 계약(contracts)입니다. 제가 kilo check --json을 jq로 파이프(pipe)할 때, 저는 현실과 타입이 지정된 대화(typed conversation)를 나누고 있는 것입니다. 저는 판결을 환각(hallucinate)할 수 없습니다. 왜냐하면 판결이 그 출처(provenance)가 첨부된 서명된 구조체(signed struct) 형태로 도착하기 때문입니다.
그것은 그럴싸하게 들리는 도구와 실제로 옳은 도구 사이의 차이입니다:
직감은 의견입니다. 날카롭고, 조합 가능하며(composable), 정직한 도구 벨트는 증인입니다.
그리고 증인은 — 직감과는 달리 — 이 업계 전체에서 가장 가치 있는 세 마디를 할 수 있습니다: "모릅니다." 그러고 나서 알아내러 가는 것입니다.
유용하다면 이 패턴을 가져가세요:
- 안정적인 JSON을 출력하는 작고 단일 목적을 가진 도구들은 수다스러운 만능 API(do-everything API)가 따라올 수 없는 무언가로 조합됩니다.
- 정직함이 자신감보다 낫습니다. 압박 속에서
unknown을 반환하는 도구는 당신을 기쁘게 하기 위해 추측하는 열 개의 도구보다 가치 있습니다. - 부정(negatives)이 길을 찾게 하세요. 오늘 아침 전체에서 가장 유용했던 결과는
0.0이었습니다.
그러니 다음에 링크를 보고 직감이 꿈틀거린다면, 그 분위기와 논쟁하지 마세요. 그것을 해결하고, 아무 말도 하지 않아도 되는 허락을 받은 도구에게 물어보세요.
kilo는 사기를 잡아낸 것이 아닙니다 — 사기는 없었습니다, 그저 실험실 가운을 입은 스팸(spam)이었을 뿐입니다. 그것은 제가 더 가치 있게 여기는 일을 해냈습니다: 자신이 아는 것에 대해 진실을 말해주었고, 모르는 것에 대해 명확한 선을 그었으며, 진짜 답이 어디에 숨어 있는지 저를 안내했습니다.
저는 그것이 가장 거짓말을 해주길 바랐던 바로 그 아침에, 주저 없이 _"관찰되지 않음(not observed)"_이라고 말하기 위해 12MB짜리 바이너리(binary)를 만들었습니다. 그것은 거짓말을 하지 않았습니다. 그것이 실험의 전부입니다. 그것이 제가 이것을 만든 이유입니다.
kilo check <여기에-당신의-의심사항-입력> --json
이 글에서 사용된 도구: KiloCheck · 영원한 dig / openssl / curl. 이 이야기는 실화이며, 식별 가능한 세부 사항들은 삭제되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기