Kritt-ai/open-kritt · 라이선스 AGPL-3.0 · 엔진 Python 3.12 · 백/프론트 Node.js·React · 버전 v1.2.0 (2026-07-23) · 최초 오픈소스 공개 2026-07-15(v1.0.0) · ★ 약 370 · 제작 Kritt(Blockian) 팀 · TrendShift Daily #22)
open·kritt는 "보안 리서치를 작은 작업 단위로 쪼개, 여러 AI 코딩 에이전트에게 격리된 일회용 컨테이너 안에서 병렬로 시키고, 그 결과를 일관된 스키마·중복 제거·심각도 순위로 정리해 주는 셀프호스팅 오케스트레이션 플랫폼"이다. 사용자는 웹 UI에서 워크플로우(프롬프트들의 사슬)를 짜고, 분석할 저장소를 지정해 스캔(scan)을 돌린다. 엔진은 그 스캔을 잡(job)으로 쪼개 에이전트들에게 분배하고, 나온 findings를 검증·순위화해 대시보드에 쌓는다.
거대한 코드베이스를 AI 모델 하나에게 통째로 주고 "여기서 취약점 찾아"라고 하는 건 탐정 한 명에게 "이 도시 어딘가에 범인이 있으니 찾아오라"고 시키는 것과 같다. 단서가 너무 흩어져 있고, 탐정의 기억(컨텍스트 창)은 도시 전체를 담기엔 작아서, 결국 얕게 훑고 중요한 걸 놓친다.
open·kritt는 수사본부처럼 움직인다. 사건을 "구역별 탐문", "용의자 알리바이 검증", "증거물 대조" 같은 좁고 명확한 작업(step)으로 쪼갠 다음, 각 작업을 서로 격리된 방(일회용 컨테이너)에 있는 여러 수사관(AI 에이전트)에게 동시에 배정한다. 각 수사관은 정해진 양식(JSON 스키마)으로 보고서를 제출하고, 본부는 중복 제보를 합치고, 심각도 순으로 사건을 정렬해 수사 지휘부(사용자)에게 올린다.
결정적 차이: 수사관 한 명 한 명은 자기 방에서 도구를 설치하고 실험(PoC 빌드)까지 할 수 있지만, 그 방은 사건이 끝나면 통째로 폐기(disposable)된다. 격리와 병렬성을 동시에 얻는 구조다.
기술적으로 정확히 말하면, open·kritt는 Postgres를 중앙 상태 저장소·작업 큐로 삼는 5개 서비스의 docker-compose 시스템이다. React 프론트엔드가 워크플로우 빌더/대시보드를 그리고, Express+Prisma 백엔드가 저장(persist) API를 맡으며, Python으로 짠 open_kritt_engine이 실제 스캔을 집행한다. 엔진은 자기 안에 Docker CLI를 품고 있어, 스캔마다 사이드카(sibling) 잡 컨테이너를 새로 띄워 그 안에서 코딩 에이전트를 실행한다. "기능 = 스텝 = 갈아끼울 수 있는 프롬프트 부품"이라는 등식이 이 저장소 전체를 관통한다.
첫째, 핵심 주장이 반(反)직관적이고 설득력 있다. README 첫 문단이 대놓고 말한다 — "모델에게 저장소 전체를 가리키며 취약점을 찾으라고 하면 거의 잘 안 된다." 대부분의 "AI 보안 도구"가 더 크고 똑똑한 모델을 자랑할 때, open·kritt는 방향을 튼다: 리서치를 작고 잘 정의된 작업으로 쪼개, 에이전트들에게 병렬로 돌리고, 출력을 합쳐 검증·우선순위화한다. 이건 프롬프트 요령이 아니라 작업 분해(task decomposition) + 앙상블이라는 시스템 설계다.
둘째, "진짜 리서치에서 나온 도구"라는 출처가 강하다. 만든 Kritt 팀은 버그바운티 이름 Blockian으로 Immunefi·HackenProof에서 150만 달러 이상 보상을 받은 실전 리서처들이다. open·kritt는 그들의 내부 파이프라인을 오픈소스로 증류한 것 — 즉 "이론상 좋아 보이는 설계"가 아니라 돈을 벌어 온 실전 워크플로우가 코드가 됐다.
셋째, "통제권을 사용자에게 준다". 프롬프트·워크플로우·모델 공급자·인프라를 전부 사용자가 소유한다. 모델은 BYO(Bring Your Own) — Codex 로그인이든 OpenAI·Anthropic·OpenRouter API 키든 원하는 걸 꽂는다. AGPL-3.0 라이선스로 완전 오픈소스이고, 데이터는 내 Postgres에만 남는다. "블랙박스 SaaS 스캐너"와 정반대 지점이다.
보통의 LLM 스캐너는 거대한 프롬프트 하나 + 자유 서술 출력이라 결과가 들쭉날쭉하고 비교가 안 된다. open·kritt는 반대다. 워크플로우를 깊이(depth)별 스텝으로 나눠 각 스텝이 선언된 JSON 스키마로만 답하게 강제하고, 마지막(terminal) 스텝은 고정된 finding 스키마(explanation·file_path·line·summary·trigger_flow·vulnerability_type 등)를 반드시 뱉게 한다. 그 덕에 findings가 서로 비교·중복 제거·순위화 가능한 데이터가 된다. "출력을 구조로 강제해 하류(下流) 자동화를 가능케 한다"는, LLM 엔지니어링의 정석 패턴이다.
넷째, 격리를 진지하게 설계했다. 코드 분석 에이전트는 대상 저장소의 코드를 실제로 실행·컴파일해 봐야 할 때가 있는데(=신뢰할 수 없는 코드 실행), open·kritt는 이를 일회용(disposable) 잡 컨테이너 안에 가두고, 자격증명(credential)은 chmod 0700 경계 뒤에 숨긴다. "AI에게 도구를 쥐여 주되, 폭발 반경(blast radius)을 컨테이너로 제한한다"는 실무 감각이 코드에 녹아 있다.
open·kritt의 첫 번째 놀라움은 "한 시스템 안에 언어가 두 개"라는 점이다. 무거운 오케스트레이션·프로세스 제어는 Python 엔진이 맡고, 사용자와 맞닿는 API·UI는 Node.js/React가 맡는다. 이건 흔한 실무 선택 — "동시성 많은 IO·서브프로세스 관리는 Python이 편하고, 웹 UI는 JS 생태계가 압도적"이라는 현실을 그대로 반영한다.
| 영역 | 기술 | 버전/근거 | 역할 |
|---|---|---|---|
| 엔진(코어) | Python + psycopg + jsonschema | py3.12 / ruff | 스캔 집행·워커풀·잡 컨테이너 생성·후처리 |
| 백엔드 API | Express 5 + Prisma 5 | express ^5.2 / prisma ^5.22 | 워크플로우·스캔 저장(persist), UI용 REST |
| 프론트엔드 | React 18 + Vite 8 + React Router 7 | react ^18.3 / vite ^8.1 | 워크플로우 빌더·스캔 대시보드(SPA) |
| 라이브 뷰 | Python HTTP 서버 | executor-view/server.py | 실행 중 에이전트 로그 실시간 스트리밍 |
| 데이터베이스 | PostgreSQL | 커스텀 이미지 / :5432 | 중앙 상태 저장소 + 작업 큐 + 락 |
| CLI | Node.js 스크립트 | kritt → scripts/kritt.mjs | ./kritt setup · ./kritt start 부트스트랩 |
| 에이전트 런타임 | Claude Code · Codex · Cursor Agent | claude-code@2.1.215 등 | 잡 컨테이너 안에서 실제 코드 분석 수행 |
| 오케스트레이션 | Docker Compose (+ DooD) | docker-compose.yml | 5개 서비스 기동 + 엔진의 사이드카 컨테이너 생성 |
백엔드(open-kritt-backend)는 의외로 얇다. Express 5(최신 메이저)와 Prisma(타입세이프 ORM)로 Postgres 앞에 REST를 씌운 게 전부다. 라우트 목록이 곧 이 앱의 도메인 지도다: workflows·steps·scans·vulnerabilities·postScripts·severityRankers·agentSkills·generations·modelProviders·accounts·settings. 핵심 설계 결정은 "무거운 일은 절대 백엔드가 하지 않는다"는 것 — 백엔드는 워크플로우와 스캔 요청을 DB에 저장만 하고, 실제 실행은 Postgres 큐를 폴링하는 Python 엔진이 가져간다. 로깅은 pino(고성능 JSON 로거)를 쓴다.
schema.prisma 파일에 모델을 선언하면 타입 안전한 DB 클라이언트를 생성해 주는 Node용 ORM. REST는 /scans, /workflows 같은 URL로 자원을 주고받는 웹 API 방식. 폴링은 "새 일감 있어?"를 주기적으로 되묻는 것 — 엔진은 ENGINE_POLL_SECONDS(기본 5초)마다 Postgres 큐를 확인한다.프론트엔드(open-kritt-frontend)는 React 18 + Vite 8 SPA다. 컴포넌트 이름만 봐도 이 앱이 뭘 하는지 드러난다 — PromptEditor(프롬프트 편집), SchemaEditor(출력 스키마 편집), WorkflowModelConfiguration(스텝별 모델 선택), CommandPalette(⌘K 빠른 이동), Markdown(findings 렌더). 의존성이 React·React-DOM·React-Router 딱 3개로 극도로 절제돼 있는 게 인상적이다. 상태관리 라이브러리(Redux 등)도, UI 키트도 없이 순수 React로 간다 — "도구는 최소, 로직은 명확히"라는 취향이 보인다. 테스트는 Vitest로 컴포넌트마다 .test.jsx가 짝지어 붙어 있다.
진짜 복잡도는 여기 있다. open_kritt_engine 패키지는 파일 하나가 7만 줄에 육박하는 모듈들의 집합이다: worker.py(워커 루프)·workspace.py(잡 작업공간 준비)·harnesses.py(에이전트 어댑터)·db.py(DB·재시도)·post_processing.py(후처리)·generation.py(AI로 워크플로우 초안 생성)·prompting.py(템플릿 렌더). Postgres는 Prisma 없이 psycopg로 직접 다루고, 출력 검증은 jsonschema Draft 2020-12로 한다. 이 엔진이 하는 일을 다음 섹션에서 해부한다.
백엔드/프론트엔드 = 프런트오피스(주문 접수·전광판), 엔진 = 백오피스 공장(실제 제조). 손님(사용자)은 프런트오피스에서 워크플로우를 짜고 스캔을 주문한다. 주문서는 Postgres라는 공용 게시판에 붙는다. 공장(엔진)은 게시판을 계속 보다가 새 주문을 떼어 가 제조(스캔)하고, 완성품(findings)을 다시 게시판에 올린다. 두 세계는 서로 직접 말하지 않고 Postgres를 통해서만 소통한다 — 느슨하게 결합된(decoupled) 구조다.
open·kritt의 아키텍처는 세 개의 핵심 개념 위에 서 있다: ① 스텝(step)으로 쪼갠 워크플로우, ② 이질적 에이전트를 감싸는 하네스(harness), ③ 격리·폐기되는 잡 컨테이너. 전체 그림부터 보자.
open·kritt의 워크플로우는 사실상 "프롬프트로 짜는 데이터 파이프라인"이다. 워크플로우는 여러 스텝(step)으로 이뤄지고, 각 스텝은 하나의 프롬프트 + 그 프롬프트가 반드시 내야 하는 구조화된 출력(output format) 선언이다. 스텝은 깊이(depth)로 묶여 순서대로 실행되며, 한 깊이의 출력이 다음 깊이의 입력으로 흐른다.
# 스텝 하나의 구성 요소 (docs: "What are steps?")
Name # 사람이 읽는 라벨
Content # 에이전트에 보낼 프롬프트, {{변수}} 자리표시자 포함
Output format # 이 스텝이 낼 key와 타입 (string/number/boolean/array/object)
Depth # 몇 번째 층인가 (0 = 진입점)
Multi-output # 입력 하나당 결과 여러 개를 낼 수 있는가
Consume all # 이전 층 전체를 한 배치로 받는가
여기서 가장 영리한 제약은 "마지막(terminal) 스텝은 특별하다"는 규칙이다. 가장 깊은 스텝은 반드시 고정된 finding 스키마를 뱉어야 하고, 빌더는 그 스키마가 갖춰지기 전엔 저장을 막는다. 필수 키는 이렇게 정해져 있다:
explanation, file_path, line, summary, trigger_flow,
vulnerability_type, malicious_input_example, malicious_actor
(+ 선택: exploitable)
이 한 줄이 시스템 전체를 지탱한다. 모든 스캔의 최종 산출물이 같은 모양이기 때문에, 그 위에서 중복 제거·심각도 랭킹·대시보드 렌더 같은 자동화가 전부 가능해진다. "자유로운 자연어 출력"을 "타입이 정해진 레코드"로 강제 변환하는 것 — 이게 LLM을 파이프라인 부품으로 쓰는 핵심 기술이다.
Claude Code, Codex, Cursor Agent는 각각 다른 회사가 만든 다른 CLI다 — 실행법도, 실패 방식도, 인증도 제각각이다. harnesses.py(7만 줄 규모)는 이들을 "하네스"라는 공통 어댑터로 감싸, 엔진 입장에선 "어떤 에이전트든 똑같이 호출"할 수 있게 만든다. 특히 인상적인 건 실패를 1급 시민으로 분류한다는 점이다:
# harnesses.py — 재시도 불가 실패 taxonomy(발췌)
NON_RETRYABLE_HARNESS_FAILURES = {
"auth_failed", "configuration_error", "cyber_safety_blocked",
"invalid_output_schema", "model_access_denied",
"model_unavailable", "quota_exceeded", "start_failed", ...
}
# ↔ 일시적(재시도 가능) 실패: 속도제한(rate limit) 계열은 따로 분류
왜 중요한가? 수십 개 잡을 병렬로 돌리면 실패가 예외가 아니라 일상이다. 어떤 실패는 재시도하면 낫고(속도 제한), 어떤 실패는 재시도해봤자 소용없다(인증 실패·스키마 불일치). 이 둘을 코드 레벨에서 명시적 집합(frozenset)으로 구분해 두면, 워커는 "이건 잠시 뒤 다시, 저건 즉시 포기"를 자동 판단할 수 있다. 견고한 분산 작업 시스템의 전형적 설계다.
엔진 컨테이너의 Dockerfile을 보면 docker:27-cli를 통째로 복사해 넣는다. 즉 엔진은 자기 안에서 docker run을 호출해 형제(sibling) 컨테이너를 새로 띄운다 — 이 패턴을 DooD(Docker-out-of-Docker)라 부른다. 스캔이 시작되면 엔진은 대상 레포의 쓰기 가능한 사본을 만들고, Claude Code/Codex/Cursor가 설치된 일회용 컨테이너를 띄워 그 안에서 에이전트를 root로 실행한다.
README가 명시한다 — "도구 사용이 가능한 에이전트는 일회용 잡 컨테이너 안에서 root로 실행되며, 쓰기 가능한 레포 사본과 직접적인 인터넷 접근을 갖는다(도구 설치·타깃 컴파일·테스트 실행·PoC 빌드를 위해)." 이건 강력하지만 위험하다. 그래서 open·kritt는 이를 폐기형 컨테이너에 가두고, 자격증명을 /run/open-kritt-secrets(chmod 0700, root-only 경계) 뒤에 숨긴다. 문서는 "전용 Docker 호스트나 VM에서 돌리고, 신뢰할 수 없는 코드를 스캔하기 전 threat model 문서를 읽으라"고 경고한다.
open·kritt는 별도의 메시지 브로커(RabbitMQ·Redis 등)를 쓰지 않는다. Postgres 하나가 큐·상태 저장소·동시성 제어를 전부 겸한다. db.py에는 이 철학이 코드로 박혀 있다 — 속도 제한 시 지수 백오프(exponential backoff)로 재시도하고(기본 60초 → 최대 10분), 쿼터 소진은 최대 8일까지 끈질기게 재시도하며, 스캔 진입은 Postgres 자문 락(advisory lock)으로 조율한다.
# db.py — 재시도 정책 상수(발췌)
RATE_LIMIT_RETRY_BASE_SECONDS = 60.0 # 첫 재시도 대기
RATE_LIMIT_RETRY_MAX_SECONDS = 10 * 60.0 # 상한 10분
QUOTA_RETRY_MAX_SECONDS = 8 * 24 * 60 * 60 # 쿼터 소진은 최대 8일 재시도
QUEUED_SCAN_ADMISSION_LOCK = (0x6B726974, ...) # 스캔 진입 자문 락
이 선택의 장점은 운영 단순성이다. 셀프호스팅 사용자가 브로커를 따로 세울 필요 없이 docker compose up 하나로 전부 뜬다. "규모가 극단적으로 크지 않다면, Postgres가 큐도 겸하게 하라"는 요즘 백엔드 트렌드(예: SELECT ... FOR UPDATE SKIP LOCKED)의 실전 사례다.
스텝(①)이 작업을 잘게 쪼개 병렬성을 만들고, 하네스(②)가 이질적 에이전트를 균일하게 다루며, 잡 컨테이너(③)가 각 병렬 작업을 안전하게 격리하고, Postgres 큐(④)가 이 모든 걸 조율·재시도·기록한다. 그 결과 "수십 개의 좁은 AI 작업을, 실패에 강하게, 격리된 채로, 구조화된 결과로 수확"하는 시스템이 완성된다.
이 구조에서 읽어야 할 신호는 세 가지다. 첫째, 언어 경계가 폴더 경계와 일치한다 — engine/는 순수 Python, backend/·frontend/는 JS. 둘째, 엔진 폴더 하나에 복잡도가 몰려 있다 — 나머지는 얇고, 진짜 알맹이는 open_kritt_engine/에 있다. 셋째, 문서(docs-site/)가 코드와 같은 저장소에 있어, 개념(스텝·랭커·post-script)을 코드와 대조하며 읽을 수 있다.
schema.prisma에 model Scan { ... }처럼 선언한 테이블 정의 — open·kritt엔 Workflow·Step·Scan·Vulnerability·VulnerabilityEnrichment·PostScript·AgentSkill·SeverityRanker·Triage 등 15개 안팎이 있다. 자문 락(advisory lock)은 Postgres가 제공하는 "약속된 이름표로 거는 잠금" — 행을 잠그지 않고도 "이 스캔은 지금 한 번만 진입"을 조율한다. release-please는 커밋 메시지(feat/fix)를 읽어 버전·CHANGELOG를 자동으로 올려 주는 구글 도구 — VERSION 파일이 단일 진실원이다.schema.prisma의 모델 이름을 나열하면 open·kritt의 세계관이 그대로 드러난다:
| 모델(테이블) | 의미 |
|---|---|
| Workflow / Step | 스캔의 설계도와 그 구성 단계(프롬프트 사슬) |
| Scan | 특정 저장소에 대한 한 번의 분석 실행 |
| Vulnerability / …Enrichment | 발견된 취약점과 그에 덧붙는 보강 정보 |
| PostScript | findings를 검증·PoC 빌드·리포트화하는 후처리 스크립트 |
| SeverityRanker | findings를 정렬하는 Markdown 규칙 세트 |
| AgentSkill | 프롬프트에 주입하는 재사용 가능한 "스킬" |
| Generation | AI가 만든 워크플로우/post-script 초안 |
| StepResult / …Metadata | 스텝 실행 결과와 실행 메타데이터 |
| Triage | findings의 분류·검토 상태 |
open·kritt에서 가장 이식성 높은 교훈은 "자연어 출력을 스키마로 강제하라"다. schema.py는 사용자가 선언한 output format을 JSON Schema(Draft 2020-12)로 변환하고, 에이전트가 그 스키마에 맞는 JSON을 낼 때까지 검증한다. 배울 점: (1) LLM을 파이프라인에 넣으려면 출력이 파싱 가능한 레코드여야 한다, (2) "마지막 단계는 고정 스키마"라는 제약 하나로 하류 자동화 전체가 열린다, (3) 스키마 위반을 invalid_output_schema라는 재시도 불가 실패로 분류해 무한 재시도를 막는다.
worker.py·db.py·queue.py는 "실패에 강한 작업 시스템"의 살아 있는 예제다. 배울 점: 지수 백오프(속도 제한 대응), 실패 taxonomy(재시도 가능/불가 구분), 자문 락(중복 실행 방지), 워커 자동 스케일링(ENGINE_AUTOSCALE_SCAN_WORKERS_ON_PROVIDER_CAPACITY — 프로바이더 여력에 따라 워커 수 조절). 이 패턴들은 보안과 무관하게 어떤 배치 처리 시스템에도 그대로 옮겨진다.
engine/Dockerfile과 workspace.py에서 Docker-out-of-Docker로 일회용 컨테이너를 띄우는 법을 볼 수 있다. 배울 점: (1) 신뢰할 수 없는 작업은 폐기형 컨테이너에 가둔다, (2) 자격증명은 chmod 0700 경계·읽기전용 마운트·심볼릭 링크로 계층 분리한다, (3) 멀티스테이지 빌드로 docker-cli + node + python + 에이전트 CLI 3종을 한 이미지에 조립한다. "AI에게 도구를 주되 샌드박스로 가둔다"는 에이전트 인프라의 필수 감각이다.
왜 한 언어로 통일하지 않았을까? 배울 점: 무거운 서브프로세스·동시성·프로세스 제어는 Python이 라이브러리(psycopg, subprocess)와 함께 편하고, 웹 UI·타입세이프 API는 JS 생태계(React, Prisma)가 압도적이다. 두 세계를 Postgres라는 공용 계약으로만 잇고 서로 직접 호출하지 않게 하면, 각 팀/각 언어가 독립적으로 진화할 수 있다.
상태관리 라이브러리도 UI 킷도 없이 React+Router 3개 의존성으로 복잡한 빌더 UI를 만든 사례다. 배울 점: lib/에 순수 함수(폴링·드래프트·모델 오버라이드)를 모으고 컴포넌트는 얇게 유지하는 법, 컴포넌트마다 .test.jsx를 짝지어 붙이는 테스트 문화. "도구를 늘리기 전에 플랫폼(React) 자체로 얼마나 갈 수 있는지"를 보여 준다.
open·kritt는 "AI 에이전트를 공장 노동자로 고용하는 법" 매뉴얼이다. 노동자에게 명확한 작업지시서(스텝)를 주고, 표준 보고 양식(스키마)을 강제하고, 안전한 작업장(컨테이너)에 배치하고, 현장 관리(큐·재시도)로 조율한다. 여기서 "노동자가 하는 일(보안)"을 "번역·데이터추출·코드리뷰"로 바꿔도 매뉴얼은 그대로 작동한다.
open·kritt는 GPU가 필요 없다 — 모델 추론은 전부 외부 프로바이더(Anthropic·OpenAI·OpenRouter 등)가 대신하기 때문이다. 대신 Docker를 활발히 쓰고, 에이전트가 root로 인터넷에 접근하므로 "성능"보다 "격리"가 요구사항의 중심이다.
| 항목 | 요구/기본값 | 근거 |
|---|---|---|
| 필수 도구 | Git · Docker(+Compose) · Node.js 20+ | README "Getting started" |
| 여유 저장공간 | ≥ 20 GB | ENGINE_MIN_FREE_STORAGE_GB=20 |
| 모델 접근 | 1개 이상(Codex 로그인 또는 API 키) | ./kritt setup이 안내 |
| GPU | 불필요 | 추론은 외부 프로바이더가 수행 |
| 기본 바인딩 | 모든 포트 127.0.0.1 | 앱 인증 없음 → 외부 노출 금지 |
| 권장 실행 환경 | 전용 Docker 호스트 / VM | 에이전트가 root·인터넷 접근 |
| 포트 | 5173(FE)·3002(BE)·8090(뷰)·5432(DB) | docker-compose / .env.example |
| GitHub 토큰 | 선택(비공개 레포 스캔 시) | GITHUB_TOKEN |
README가 분명히 한다 — "기본 포트는 127.0.0.1에 바인딩되며, 백엔드는 애플리케이션 인증을 포함하지 않는다. 스택을 비공개로 유지하라." 즉 이 도구는 공용 인터넷에 그대로 띄우면 안 된다. 게다가 에이전트가 root·인터넷 접근을 갖는 일회용 컨테이너에서 돈다는 점 때문에, 신뢰할 수 없는 코드를 스캔하기 전 반드시 저장소의 threat model 문서를 읽고, 전용 VM에 격리해야 한다. 이건 "무거운 도구"가 아니라 "권한이 센 도구"다.
동시성 관련 노브도 풍부하다: ENGINE_WORKER_COUNT(엔진 이미지 기본 25, .env.example은 2로 보수적), ENGINE_MAX_CONCURRENT_SCANS, ENGINE_WORKERS_PER_ACCOUNT(계정당 15), ENGINE_HARNESS_TIMEOUT_SECONDS(잡 타임아웃 7200초=2시간). 즉 "한 계정으로 몇 개까지 병렬로 밀지"를 프로바이더 여력에 맞춰 조절하는 게 튜닝의 핵심이다.
아래 실습은 "보안 시스템을 어떻게 만드는가"를 배우기 위한 것이지, 남의 시스템을 공격하기 위한 게 아니다. 실제 스캔은 당신이 직접 만든 연습용 저장소, 회사가 허가한 사내 코드, 또는 공식 버그바운티 프로그램이 명시적으로 범위(scope)로 지정한 대상에만 돌려라. 무허가 스캔은 법적·윤리적 문제가 된다.
코드를 실행하지 않고도 할 수 있는 첫 과제. docker-compose.yml을 열어 5개 서비스가 각각 어떤 포트·볼륨·환경변수를 갖는지 표로 정리하고, depends_on을 따라 기동 순서 그래프를 그려 보라. 특히 engine 서비스가 왜 Docker 소켓·자격증명 볼륨을 마운트하는지 추적하면 DooD 구조가 손에 잡힌다.
./kritt setup → ./kritt start로 스택을 띄운 뒤, 일부러 취약점을 심은 작은 연습용 저장소(예: SQL 문자열 결합, 하드코딩된 시크릿)를 직접 만들어 local_repos/에 넣고 스캔한다. 목표는 취약점 발견이 아니라 "스캔 → 잡 → finding"의 데이터 흐름을 UI와 로그로 관찰하는 것. executor-view(:8090)에서 에이전트가 실시간으로 뭘 하는지 지켜보라.
보안이 아닌 주제로 스텝 개념을 실습해 보자. depth 0: "이 저장소에서 TODO 주석을 모두 찾아 파일·라인을 배열로 반환", depth 1(terminal): "각 TODO를 우선순위·담당영역으로 분류". output format을 명시적으로 선언하고, terminal 스텝이 고정 스키마를 요구하는 규칙을 직접 부딪혀 보라. "{{변수}}로 이전 스텝 출력을 다음 스텝에 흘리는" 감각을 얻는 게 핵심이다.
docs의 예시처럼 Markdown 규칙 세트로 나만의 랭커를 만든다("인증 없는 원격 실행이 항상 최상위", "로컬 접근이 필요한 건 강등" 등). 같은 findings 세트에 서로 다른 랭커를 적용했을 때 순위가 어떻게 바뀌는지 비교하라. 이건 "LLM에게 판단 기준을 코드가 아닌 자연어 규칙으로 주입"하는 패턴의 실습이다.
harnesses.py의 NON_RETRYABLE_HARNESS_FAILURES와 재시도 경로를 읽고, 새로운 실패 유형 하나를 추가해 재시도/포기 분기가 어떻게 달라지는지 추적한다. 더 나아가 model_catalog.py가 OpenRouter·Anthropic 모델 목록을 어떻게 정규화하는지 보고, 새 프로바이더 어댑터의 최소 골격을 스케치해 보라. 엔진의 "확장 지점(extension point)"을 이해하는 최고의 방법이다.
이미지 vs 컨테이너, 볼륨·네트워크·환경변수, docker compose로 다중 서비스 띄우기. 목표: open·kritt의 docker-compose.yml을 줄 단위로 읽을 수 있게 되기. 심화로 Docker-out-of-Docker vs Docker-in-Docker 차이와 소켓 마운트의 보안 함의를 정리한다.
JSON Schema(Draft 2020-12) 문법, 함수 호출/구조화 출력으로 LLM을 강제하는 법, Claude Code·Codex 같은 코딩 에이전트 CLI를 직접 써 보기. 목표: schema.py·prompting.py가 왜 필요한지 몸으로 이해. 실습: 아무 API로 "반드시 이 JSON만 반환" 프롬프트를 만들어 실패·성공을 관찰.
작업 큐 개념, FOR UPDATE SKIP LOCKED로 Postgres를 큐로 쓰기, 지수 백오프·멱등성(idempotency)·자문 락. 목표: worker.py·db.py의 재시도 정책을 읽고 "왜 8일까지 재시도하나"를 설명할 수 있게 되기. 비교: Celery/Sidekiq 같은 전용 큐와 "Postgres-as-queue"의 트레이드오프.
취약점 분류(OWASP Top 10), 트리아지(triage)·심각도 산정(CVSS 개념), 책임 있는 공개(responsible disclosure)와 버그바운티 scope 준수. 목표: open·kritt의 finding 스키마(vulnerability_type·trigger_flow·exploitable) 필드가 실제 리서치 실무의 무엇을 담는지 이해. 공격이 아니라 "발견을 어떻게 구조화·검증·우선순위화하는가"에 초점.
| 키워드 | 뜻 |
|---|---|
| 오케스트레이션 | 여러 부품(에이전트·컨테이너·큐)을 조율해 하나의 작업을 완성하는 것 |
| 셀프호스팅 | 외부 SaaS가 아니라 내 서버/PC에서 직접 실행 — 코드·데이터를 소유 |
| 워크플로우 / 스텝 / 깊이 | 스캔의 설계도 / 프롬프트 한 단위 / 스텝을 묶는 실행 층(0=진입) |
| terminal 스텝 | 가장 깊은 스텝. 고정된 finding 스키마를 반드시 출력해야 함 |
| finding(발견 항목) | 스캔이 찾은 취약점 후보. explanation·file_path·line·… 로 구조화 |
| 하네스(harness) | 서로 다른 에이전트 CLI를 공통 인터페이스로 감싸는 어댑터 |
| 실패 taxonomy | 재시도 가능/불가로 나눈 실패 분류(속도제한 vs 인증실패 등) |
| DooD(Docker-out-of-Docker) | 컨테이너 안에서 docker CLI로 형제 컨테이너를 띄우는 패턴 |
| 일회용 잡 컨테이너 | 스캔마다 새로 띄우고 끝나면 폐기하는 격리된 실행 방 |
| 자문 락(advisory lock) | 행을 잠그지 않고 "약속된 이름표"로 거는 Postgres 동시성 제어 |
| 지수 백오프 | 재시도 간격을 60초→2배씩 늘려 상한(10분)까지 대기 |
| 심각도 랭커 | findings 정렬 기준을 Markdown 규칙으로 적어 모델에 주입 |
| post-script | findings를 검증·PoC 빌드·리포트화하는 후처리 스크립트 |
| BYO 모델 | Bring Your Own — Codex/OpenAI/Anthropic/OpenRouter 중 원하는 걸 꽂기 |
| 폴리글랏 아키텍처 | 한 시스템에서 일에 맞춰 여러 언어(Python+JS)를 함께 쓰는 것 |
| AGPL-3.0 | 네트워크로 제공해도 소스 공개 의무가 있는 강한 카피레프트 라이선스 |
docs-site/workflows/ · docs-site/severity-ranker/ · docs-site/post-scripts/docs/threat-model.md