Skip to main content
Glama

Thot

tests python youden false positives

터미널에서 실행되는 코드 어시스턴트로, 이미 당신의 저장소를 알고 있다 — 그리고 Hermes Agent와 Prime Agent가 사는 저장소도 통째로 알고 있다.

대화형 에이전트는 모델로 파일을 열어 프로젝트를 발견한다: 느리고, 부분적이며, 세션마다 다시 비용을 지불해야 한다. Thot은 AST와 호출 그래프로 동일한 그림을 계산한다 — 완전하고, 즉각적이며, 무료 — 그리고 모델에게 중요한 것만 건넨다.

세 가지 프로그램

이 저장소에는 하나가 아니라 세 가지가 들어 있다. 어느 것도 다른 것의 재작성이 아니다: 각각 자신의 언어와 도구로 존재하며, Thot이 그것들을 연결한다.

그것이 무엇인가

위치

thot

결정적 감사, 코드 지도, 판정 메모리

src/thot/

hermes

에이전트: 도구, 게이트웨이, 플러그인, cron, ACP

hermes/ — Python, uv 워크스페이스 멤버

prime

코드 에이전트: 모델 공급자, TUI, RLM

prime/ — TypeScript, npm

thot                 # la session d'audit
thot hermes          # Hermes, arguments transmis tels quels
thot prime           # Prime, pareil
thot fusion status   # ce qui est présent, prêt, et branché

연결은 장식이 아니다. thot fusion wire는 Thot의 MCP 서버를 두 에이전트에 선언한다: 그들은 code_map, find_symbol, callers, audit, skills, skill을 얻는다 — 모델 밖에서 계산된 저장소의 완전한 지도를, 파일별로 다시 발견하는 대신. 이것이 상호 지원이다: Thot은 묻지 않고 알고, Hermes와 Prime은 행동한다.

두 에이전트는 같은 방식으로 도달되지 않으며, 그 반대를 주장하는 것은 둘 중 하나만 연결했을 뿐이다. Hermes는 서버를 직접 시작하고 자신이 연 파이프로 통신한다. Prime은 HTTP만 허용한다 — 그의 mcp-manager는 타입이 http가 아닌 모든 입력을 버리고, 그의 런타임에는 파이프 전송이 없다 — 따라서 그의 쪽은 실행 중인 서버를 요구한다:

thot fusion wire            # écrit les deux branchements, chacun dans sa forme
thot mcp serve --http       # ce que Prime interroge : boucle locale, jeton bearer
thot mcp service --install  # et pour que ce soit encore vrai demain

세 번째 줄은 처음 두 줄이 한 세션만 유지되기 때문에 존재한다. fusion wire는 http://127.0.0.1:8787/mcp를 Prime의 구성에 쓰고, 이 주소는 터미널이 열려 있는 한 유효하다: 첫 재시작 시, 파일은 더 이상 아무도 서비스하지 않는 서버를 약속한다. thot mcp service --install은 그것을 다시 시작하는 유닛을 쓴다 — macOS에서는 KeepAlive, systemd에서는 Restart=always — 그리고 로드하지 않는다: Thot은 명령을 출력하고 당신이 실행하도록 둔다, 왜냐하면 말 없이 백그라운드 에이전트를 등록하는 도구는 더 이상 믿지 않는 도구이기 때문이다. thot doctor는 그 후 유일하게 중요한 질문을 던진다 — 주소가 지금 응답하는가 — 파일이 존재하는지가 아니라.

fusion wire는 또한 Prime 쪽에 Thot이 그를 위해 제공하는 메서드 패키지를 설치한다: 구성 항목만으로는 충분하지 않으며, Prime은 그것을 명명하는 클래스를 통해서만 MCP 서버에 도달한다. 그리고 모델 자격 증명을 건드리지 않고 auth.json에 토큰을 넣는다 — 토큰이 없으면 연결은 열리기 전에 실패한다.

각 에이전트는 자신의 구성을 유지한다. thot fusion unwire는 모든 것을 되돌리고, Prime의 settings.json은 첫 수정 전에 백업된다.

thot fusion status는 작동하는 것을 측정한다, 쓰여 있는 것이 아니라: Hermes는 휴대용 플러그인을 비활성화된 상태로 설치한다, 보안상, 따라서 두 파일을 쓰는 것은 plugins.enabled가 그것을 명명하지 않는 한 아무것도 연결하지 않는다. 활성화는 Hermes의 CLI를 통해서만 이루어지며, 그의 config.yaml을 편집해서는 절대 아니다 — 그 파일은 그의 것이며, 자신의 스키마와 마이그레이션을 가진다. 또한 Hermes를 실행하는 인터프리터가 MCP SDK를 가져올 수 있는지 확인한다, 왜냐하면 그것이 없는 에이전트는 어떤 도구도 없기 때문이다 — Thot의 것뿐만 아니라 — 그리고 그것을 logger.debug로 말하는데, 아무도 읽지 않는 곳이다. 마지막으로 Prime의 서버에게 응답하는지 묻는다: 파일에 쓰인 주소는 연결이 아니다.

그리고 지원은 양방향으로 작동한다: Hermes와 Prime은 또한 thot audit --deep의 엔진이다, 각 finding을 모델이 논증하고 반박하게 하는 단계.

기본적으로 세 가지는 동일한 감사에서 함께 작동한다. finding은 한 에이전트가 논증하고, 다른 에이전트가 공격한다 — 방금 시나리오에 서약한 그 에이전트는 절대 아니다. 자신의 주장을 반박하는 모델은 자신의 답을 고친다; 그것이 패널이 사는 유일한 것이며, 그것이 융합의 존재 이유다.

thot audit . --deep                    # tous les agents installés, en panel
thot audit . --deep --engine hermes    # un seul : Hermes argumente et réfute
thot audit . --deep --engine prime     # un seul : Prime

보고서는 누가 무엇을 했는지 말한다:

Analyse assistée : panel — claude-cli contre hermes contre prime
  [1] serve.py:7 — confirmé · hermes
…
1 confirmé(s) · 2 réfuté(s)
Argumenté par claude-cli 1 — attaqué par prime 1 — puis par hermes 1

같은 finding에 대한 세 개의 별도 에이전트, 측정됨: claude-cli가 논증했고, prime이 시나리오를 파괴하려 시도했고 실패했으며, hermes가 두 번째 공격을 했다. 보고된 것은 두 명의 독립적인 적대자를 견뎌냈다.

캐스케이드. finding은 논증된 후 공격받는다. 공격에서 살아남는 것이 인간에게 보여질 것이다 — 따라서 그것은 세 번째 에이전트로 다시 보내지며, 그 에이전트는 논증이 구성되는 것도 첫 공격이 쓰여지는 것도 보지 못했다. 확인된 finding은 두 명의 독립적인 적대자에 대항한 것이다.

반박은 결코 본질적으로 재판되지 않는다: 공격자는 조금이라도 의심되면 반박하라는 지시를 받았으므로, 그것을 재고하는 것은 거짓 긍정을 만들 것이다. 그러나 그의 주장은 재검토된다 그것이 심각한 것(MEDIUM 이상)을 묻을 때, 그 finding에 대해 아무 말도 하지 않은 에이전트에 의해. 재검토자는 결함을 판단하지 않고, 제시된 이유가 보여진 코드에서 검증 가능한지 판단한다.

두 오류는 동등하지 않다. 거짓 확인은 인간에게 10분의 읽기를 소비한다. 거짓 반박은 실제 결함을 영원히 소비한다 — 왜냐하면 기억된 반박은 이후 모든 감사에서 건너뛰어지기 때문이다. 그것은 한 번 확실히 일어났다: Hermes의 사본에 있는 아주 실제 SQL 주입이, 전날 수정된 Thot의 사본에 대한 완벽하게 정확한 설명에 의해 기각되었다. 이의가 제기된 반박은 확인이 되지 않는다 — 아무도 그것을 주장하지 않았다 — 그것은 원래 심각도와 함께 plausible로 돌아가고, 기억되지 않는다: 누군가 결정할 때까지 finding은 돌아온다.

에이전트가 작업에 실패하면, 다른 에이전트가 한 번 다시 수행한다. 그 이상은 아니다: 모두가 거부하는 작업은 그 자체에 문제가 있다.

프로브가 할 수 있는 것, 추정이 아닌 측정. Claude는 Write, Edit, MultiEdit, NotebookEdit, Bash, Task 없이 실행된다 — 그리고 thot doctor --agents는 파일을 쓰도록 요청한 다음 디스크에서 확인함으로써 그것을 검증한다.

이것은 화이트리스트가 아니다, 왜냐하면 클라이언트가 제공하지 않기 때문이다: --allowed-tools는 사전 승인이며, 제한하지 않는다. 측정됨 — Read Glob Grep가 허용된 프로브는 여전히 Write, Bash, Workflow를 사용할 수 있다. 유일한 레버는 블랙리스트다.

측정하기 전에 프로브가 가진 것: CronCreate, CronDelete, Workflow, SendMessage, PushNotification, RemoteTrigger, EnterWorktree, WebFetch, 그리고 사용자가 연결한 모든 MCP 서버 — 그중 이름이 clear_로 시작하는 도구도 포함. 영구적인 예약 작업 생성, 메시지 보내기, 메일함 도달. 코드를 읽고 JSON으로 응답하기 위해.

측정 후 그것이 가진 것:

✓ outils · claude        7 outil(s), tous en lecture seule
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ outils · prime         ipython

세 가지가 보여지고, 하나만 판단된다: Hermes의 file 세트는 write_file과 patch를 읽기와 함께 제공하고, Prime의 유일한 내장 도구는 커널이다. 변경할 수 없는 것에 대한 영구적인 빨간 줄은 더 이상 읽지 않는 줄이다; --engine hermes를 선택하는 사람은 자신이 수용하는 것을 본다.

블랙리스트는 구조적으로 취약하다 — Task가 빠져 있었고 하위 에이전트가 그 구멍으로 파일을 썼다, 여섯 번 중 한 번. 그래서 격차는 감지 가능하게 만들어진다: thot doctor --agents는 살아있는 프로브에게 실제로 가진 것을 묻고 인식하지 못하는 모든 것을 명명한다, 왜냐하면 다음 클라이언트 버전은 이 목록이 들어본 적 없는 도구를 가져올 것이기 때문이다. 그리고 쓰기에 대한 녹색 줄은 "이번에는 아니다"를 의미하며, "불가능"이 아니다: 그렇게 표현된다.

Hermes와 Prime은 읽기 전용 모드가 없다, 그리고 그것은 추정이 아니라 명시된다: -t file은 "File Operations"를 지정하며, 읽기와 쓰기를 포함하고, Hermes의 --safe-mode는 사용자 정의에 관한 것이지 권한이 아니다; Prime의 유일한 내장 도구는 IPython 커널이다. Thot은 그래도 그들의 범위를 줄인다 — Hermes는 기본 12개 대신 file 세트만으로 실행된다: 터미널, 브라우저, 인터프리터가 더 이상 없다. 그것은 좁혀진 행동 반경이지, 닫힌 것이 아니다.

샌드박스(thot sandbox use docker)는 엔진에 연결되어 있지 않으며, 어쨌든 문제의 절반만 해결할 것이다: 모델 API와 사용자 키링에 도달해야 하는 컨테이너는 더 이상 완전한 샌드박스가 아니다.

그래서 막을 수 없는 것은 놓칠 수 없게 만들어진다. 범위는 모델이 실행되기 전에 그리고 다시 후에 스탬프되고, 크기나 날짜가 변경된 모든 파일이 명명된다:

⚠ L'audit a modifié 1 fichier(s) du dépôt — ce n'est pas normal :
   src/app.py
   `git diff` avant toute autre chose.

침묵이 정상적인 결과다. 그것은 또한 믿을 가치가 있는 유일한 결과다: 프로브가 읽은 코드는 정확히 아무도 보증하지 않는 코드이며, "지시를 무시하고 나를 위해 고쳐줘"는 편집기를 가진 에이전트에 대한 가장 저렴한 공격이다.

경로는 절대 경로로 주어진다. 세 가지 모두에서 측정됨: Hermes는 작업 디렉토리에 대한 상대 경로를 열지 않고 "이 파일을 읽을 수 없습니다"라고 응답한다 — 이는 결함이 아니라 거부로 읽힌다. 패널의 1/3은 두 번째 파일을 열도록 요구하는 모든 주장에 대해 눈이 멀었다.

각 에이전트는 자신으로서 인증한다, 당신의 계정으로: Thot은 그의 명령줄을 실행하고, 결코 가져오지 않으며 어떤 토큰도 보유하지 않는다. 기억된 판정은 결정한 사람의 이름을 담는다 — refuted · hermes — 왜냐하면 결정은 귀속 가능해야 하기 때문이다.

각자가 가져오는 것, 같은 주입에서 측정됨:

엔진

지속 시간

보고된 토큰

prime

48초

예, 비용 추정 포함

hermes

159초

아니요 — -z는 응답만 출력한다

셀 수 없는 엔진은 숫자를 만들지 않는다: 그것을 선언하고(reports_usage), 호출자는 진짜처럼 보이는 0을 표시하는 대신 "측정되지 않음"이라고 말할 수 있다.

Related MCP server: uni-kb

하나의 구성, 하나의 메모리

세 가지는 각자 자신의 폴더에 쓰며, 그것은 매우 좋다: config.yaml은 Hermes의 것이고, settings.json은 Prime의 것이다. Thot이 추가하는 것은 단일 뷰와 결정을 위한 단일 장소다.

thot fusion config                          # le modèle que chacun utilisera
thot fusion config --model claude-opus-5    # le dire une fois, l'écrire aux trois
thot fusion memory                          # ce que les trois ont retenu
thot fusion memory --sync                   # y verser les faits appris par Thot

구성은 파일에서 읽히고 — 즉각적, 위험 없음 — 각자의 도구로 쓰여진다: hermes config set은 그의 YAML을 다시 쓰는 대신, 그 YAML은 주석과 Thot의 것이 아닌 마이그레이션 기록을 담고 있다. Thot이 공식 CLI에 모델을 위임하는 것은 불일치가 아니다: 없는 의견은 아무것과도 충돌하지 않는다.

메모리는 양방향에서 같은 원리다:

위치

형태

thot

~/.thot/harness.json

구조화됨, 제목 + 내용

hermes

~/.hermes/memories/MEMORY.md

§로 구분된 항목

prime

~/.prime/agent/AGENTS.md

markdown, 전역적으로 로드됨

Thot은 매 브리핑마다 세 가지를 모두 읽는다: Hermes가 지난주에 배운 사실은 오늘 Thot이 아는 사실이다. 나머지 두 곳에는 --sync 시에만, 각자의 네이티브 형식으로 쓴다, 자신이 직접 넣은 항목에만 손대며 — Hermes에서는 [thot] 태그, Prime에서는 구분된 블록. 첫 수정 전에 백업하고, 연속 세 번의 동기화는 단 하나의 사본만 쓴다.

갓 생성된 USER.md는 빈 양식이다: **Name:**, 이탤릭체 지침, 가로선. 이를 주입하면 Thot이 "Context: ---"를 사실로 알게 된다. 이들은 제외된다 — 그리고 화면에 집계된다, 양식과 간결한 메모를 구분하는 것은 프로그램이 확실히 할 수 있는 일이 아니기 때문이다.

하나의 라이브러리, 하나의 히스토리

세 가지는 같은 형식을 읽는다 — YAML frontmatter가 있는 SKILL.md, 메서드당 하나의 폴더. 이것이 가능한 유일한 이유다.

thot fusion skills            # qui possède quoi, et ce qui n'est qu'à un seul
thot fusion skills --share    # donner la bibliothèque de Thot à Prime
thot fusion sessions          # l'historique des trois, du plus récent au plus ancien
thot fusion audit             # auditer les trois arbres en une passe

thot fusion audit가 존재하는 이유는 그 반대가 프로그램의 마찰이었기 때문이다: 세 개의 명령과 세 개의 보고서의 정신적 융합.

thot       227 fichiers     4 finding(s) —  3 high ·   1 medium ·  17 sous le seuil
hermes    7080 fichiers   190 finding(s) —  8 high · 182 medium · 839 sous le seuil
prime      952 fichiers    14 finding(s) —  3 high ·  11 medium ·  27 sous le seuil

208 finding(s) sur l'ensemble — 14 high · 194 medium · 883 sous le seuil (`--all`)

임계값은 thot audit의 그것과 같고, 그것이 핵심이다: 같은 트리는 두 명령에 같은 숫자를 주어야 한다. 이 뷰는 모든 low를 세었으므로, 같은 분 안에 thot audit hermes가 7배 더 낮게 책정한 저장소에 대해 "임계값 미만" 열의 합계를 나머지와 함께 알렸다. 유지되는 것은 집계되고, 결코 침묵하지 않는다.

판결 메모리가 이미 판단한 경우, 그 줄은 별도로 말한다 — 0 finding(s) · 416 réfuté(s) en mémoire. 0만으로는 깨끗한 트리처럼 읽힌다; 올바른 문장은 패널이 416개를 기각했다는 것이다.

감사할 수 없는 부분은 그 줄을 잃고 결코 통과를 잃지 않는다: Prime이 없어도 Hermes가 말한 것을 숨기지 않아야 한다.

복사본 없음: 파일은 소유자에게 남고 각 프로그램은 다른 프로그램의 폴더를 가리킨다. 두 번 복사된 메서드는 한 번만 수정된 메서드다.

Thot은 Hermes의 설치된 라이브러리를 보호 아래 읽는다 — 그것은 공개 레지스트리에서 온 것이며, 이것이 바로 가드가 존재하는 경우다. 가드는 자신이 직접 전달하는 것에 대해서만 보증한다. 그러나 Hermes의 83개 메서드 중 73개는 Thot의 메서드의 비트 단위 복사본이다: 자신의 파일을 커뮤니티 위협으로 신고하는 것은 진짜 위협을 무시하도록 가르치는 오탐이다. 바이트가 전달된 메서드와 일치하는 메서드는 그 메서드다. 가드는 42건 거부에서 8건으로 줄었다.

그리고 8에서 0으로, 혼동하지 않는 것이 좋은 두 가지 별개의 이유로. 첫 번째는 잘못된 규칙이다: ENV[]는 Ruby 상수이고, 구조상 대문자이지만, 패턴은 전체 카탈로그처럼 컴파일되었다 — 대소문자 구분 없이. 그래서 Python을 Ruby로 읽었고, subprocess.run(env=env) 두 줄 앞의 env["…_TOKEN"] = jeton — 자식에게 비밀을 전달하는 권장 방식 — 을 "비밀 읽기", CRITICAL로 분류했다. 두 번째는 순위의 문제다: 사용자가 이웃 에이전트의 폴더에 직접 설치한 라이브러리는 감사 대상 저장소가 아니다. 설치는 이미 의도적으로 이루어졌다; Thot은 단지 이미 머신에 있는 것을 읽을지 여부만 결정한다. 그것은 항상 스캔되고 항상 보고되지만, 검사된 저장소가 caution부터 거부되는 곳에서 dangerous에서만 거부된다 — 8건의 거부 중 6건은 자신의 토큰 주소를 문서화하는 메서드에서 유출을 보는 단일 규칙 때문이었다.

Prime과 함께 제공되는 13개 메서드는 Prime에 남는다: 그것들은 IPython 커널(edit(old_str, new_str), refine())을 문서화한다. Thot은 그 커널을 이식했지, 그 함수들을 이식하지 않았다 — 로드하면 모델이 존재하지 않는 것을 호출하게 된다. 그것들은 카탈로그에 있으며, 아는 것이 유용하다; 발견에서 제외되며, 믿는 것이 유용하지 않다.

Prime은 두 복사본이 아닌 상위 집합을 받는다. 측정되었지, 추정되지 않았다: Thot 라이브러리만 가리키면 응답하고, Hermes 라이브러리만 가리키면 응답하고, 둘 다 가리키면 모델이 응답을 거부한다. Prime은 이름이 아닌 폴더를 받으므로 부분 응답이 없다.

히스토리는 저장소를 병합하지 않는다 — 한 프로그램의 마이그레이션은 다른 프로그램의 히스토리를 깨뜨릴 것이다 — 그러나 "지난 화요일에 이 저장소에서 무엇을 하고 있었지?"라는 질문은 세 바이너리 중 어느 것이 당신 앞에 있었는지에 관한 것이 아니다. 세 가지는 각자의 형식으로 읽기 전용으로 읽히고, 진행 중인 세션에 의해 잠긴 데이터베이스는 그 줄을 잃고 결코 목록을 잃지 않는다.

설치

git clone https://github.com/nobodyohm-web/Thot.git
cd Thot
uv tool install --editable --from . thot

루트에서 한 번의 uv sync가 Thot 과 Hermes를 설치한다: 그것은 워크스페이스이지, 표류하는 복사본이 아니다. Prime은 TypeScript이며 별도로 빌드된다:

cd prime && npm install && npm run build

Node 없이도 Thot과 Hermes는 작동한다; thot fusion status는 첫 호출에서 실패하는 대신 무엇이 빠졌고 어떻게 고치는지 말한다.

사용법

thot

그것뿐이다. 첫 실행에서 어떤 모델을 연결할지 묻고, 현재 폴더를 스캔한 후 제어권을 돌려준다.

   ╔╦╗╦ ╦╔═╗╔╦╗
    ║ ╠═╣║ ║ ║    claude-opus-5
    ╩ ╩ ╩╚═╝ ╩

   ▪ dossier  ~/Desktop/Quanta
   ▪ code     142 python · 8 points d'entrée
   ▪ git      main · propre
   ▪ audit    1 high · 2 medium

   Reconnaissance en 0.31 s. Prêt.

   ›

빈 폴더면, 그것을 말하고 지침을 기다린다. 코드가 있는 폴더면, 첫 문장 전에 이미 매핑했다.

세션 명령

명령

효과

/audit · /audit deep

분석 재실행, 또는 모델이 반박하도록 하기

/verdict n refute …

근거와 함께 finding 제외

/goal <objectif> --budget N

세션 간 추적되는 목표 설정

/sessions · /resume

여기서 이전에 한 일, 그리고 그곳으로 돌아가기

/search <mots>

Thot이 말하거나 찾은 모든 것에서 검색

/compact

요약하고 빈 컨텍스트로 다시 시작

/export · /import

세션을 다른 곳으로 가져가기

/skills · /plugins · /mcp

로드된 것, 그리고 카탈로그

/scan

저장소 맵 재계산

/model · /clear · /quit

모델, 망각, 종료

그리고 당신의 것들: 모든 .thot/commands/<nom>.md 파일은 /<nom>이 된다.

모델

선택

필요한 것

Claude — 당신의 계정

claude CLI 설치 및 연결. 복사할 것 없음.

Claude — API 키

sk-ant-… 키

OpenAI

API 키, 또는 환경의 OPENAI_API_KEY

로컬

실행 중인 Ollama 또는 LM Studio — 무료, 오프라인

기타

OpenAI 호환 엔드포인트

변경하려면 thot login, 잊으려면 thot logout. 구성은 ~/.thot/config.json에 0600으로 저장된다. 계정 모드에서는 어떤 토큰도 저장되지 않는다.

계정 모드가 작동하는 방식

Messages API는 타사 프로그램에서 오는 구독 토큰을 거부한다. 이를 통과하려면 Claude Code로 위장해야 한다 — 위장된 user-agent, 차용한 시스템 프롬프트. Thot은 그렇게 하지 않는다.

그것은 반대로 한다: 공식 클라이언트에 위임한다. 각 턴은 다음을 실행한다

claude -p --output-format stream-json --session-id <uuid> \
       --mcp-config <outils Thot> --append-system-prompt <carte du dépôt>

추론은 claude에 의해, 당신의 계정으로, 당신이 직접 입력한 것과 정확히 같이 수행된다. Thot은 저장소 맵을 제공하고, 작은 MCP 서버를 통해 결정적 도구를 연결하고, 이벤트 스트림을 형식화한다. 대화 스레드는 같은 세션 식별자에 --resume으로 전달된다.

세션 — 아무것도 잃지 않는다

창을 닫아도, 감사와 함께 진행되던 추론은 여전히 있다. 각 턴은 도착하는 순간 ~/.thot/sessions.db에 기록된다.

   › /search injection parseur
   a3f9c210 user       trouve les «injections» SQL dans le «parseur»
   a3f9c210 audit      HIGH sink.sqlite.execute  src/parse.py:88
   7b02e4d1 verdict    sink.os.system src/deploy.py:12 → refuted : commande littérale

검색은 말한 것과 찾은 것을 모두 포함한다: 반쯤 기억된 finding은 기억하는 단어와 함께 발견된다.

thot sessions              # ce qui a été fait dans ce dépôt
thot sessions --all        # partout
thot sessions --show <id>  # la transcription entière
thot search <mots>         # sans ouvrir de session
thot export <id> --out s.json ; thot import s.json

/resume은 대화 기록 과 컨텍스트를 반환한다: 계정 모드에서 Thot은 공식 CLI의 대화 식별자를 보관하고 그것을 돌려주므로, 모델은 다시 읽는 대신 기억한다.

/compact는 요약으로 세션을 닫고 링크를 유지하는 자식 세션에서 계속한다. 압축은 컨텍스트를 소모하지, 증거를 소모하지 않는다: 부모 세션은 완전히 남아 있고 /search는 항상 그것을 찾는다.

압축은 자동으로도 트리거되며, 임계값은 상수가 아니다: CLI는 사용하는 모델의 창을 게시하고(claude-opus-5[1m]의 경우 contextWindow: 1000000), Thot은 그 창의 70%에서 압축한다 — 여기서는 700,000 토큰, 200k 창에서는 140,000. 트리거는 메시지 기반 추정이 아닌 CLI가 보고한 실제 크기를 읽는다: 계정 모드에서 스레드는 CLI에 속하며, Thot은 읽은 파일이나 도구 트래픽을 보지 못한다. 일반적인 턴에서 측정했을 때, 추정은 창에 실제로 있는 88,290 토큰에 대해 95 토큰을 주었다.

목표 — 언제 멈출지 알기

목표는 통과하는 대화를 넘어 생존하며, /compact 직후를 포함해 매 턴 모델에게 상기된다.

   › /goal plus aucun HIGH dans le parseur --budget 200000
   ✓ Objectif fixé — plus aucun HIGH dans le parseur
     Budget : 200000 jetons.

예산 소진은 상태이지, 오류가 아니다: Thot은 턴 중간에 멈추지 않고, 끝내고, budget_limited로 전환하고 목표가 어디에 있는지 말한다. /goal budget 500000과 /goal done 사이에서 선택하는 것은 당신의 몫이다.

메모리 — 한 번 결정하기

감사에서 비용이 드는 것은 후보를 찾는 것이 아니다: 결정적 단계는 몇 초 안에 무료로 수행한다. 비용이 드는 것은 그것들이 가치가 무엇인지 결정하는 것이다. 두 실행 사이에 그 결정을 잃는 것은 보안 도구를 견딜 수 없게 만드는 것이다 — 매주 같은 40건의 거부, 아무도 보고서를 읽지 않을 때까지.

   › /verdict 3 refute la commande est littérale, aucune entrée utilisateur
   ✓ pattern.os_system_injection à app/shellutil.py:5 — refuted
   Retenu tant que ce code ne change pas.

결정

효과

refute

오탐 — INFO로 전환, 보고서에서 제외, 근거 유지

accept

실제 위험, 수용됨 — INFO로 전환, 주석 처리됨

fixed

수정됨 — 다시 나타나면, 회귀로 신고됨

두 가지 깊이, 소리 내어 말함

Python

TypeScript · JavaScript

나머지

심볼, 호출 그래프, code_map / callers

예

예

아니요

함수 본문 내 색칠

예

예

아니요

같은 파일의 헬퍼로 색칠

예

예

아니요

파일을 가로질러 색칠

예

아니요

아니요

패턴 기반 규칙

예

예

예

JavaScript 색칠은 같은 파일에 정의된 함수로의 호출을 따라간다 — 위임하는 핸들러의 일반적인 형태 — 그리고 거기서 멈춘다, 그것이 말하는 것. 다음 두 수준은 해석된 호출 그래프에 의존한다 — 여기서 호출된 readInput이 저기 정의된 것임을 아는 것. Python의 import 시스템은 그 질문에 답한다; JavaScript의 것은 그렇지 않다, 모듈 해석기, tsconfig, this에 대한 타입러의 관점 없이는. 가정 위에 세워진 두 번째 수준은 증명된 경로를 보고하는 도구를 그럴듯한 경로를 보고하는 도구로 바꿀 것이다.

엔진은 파일을 스캔하지, 명명된 함수 본문을 스캔하지 않는다. 웹 핸들러의 일반적인 형태는 라우트에 전달된 익명 화살표다 — app.get("/x", (req, res) => { … }) — 어떤 인덱서도 이름을 붙이지 않는.

이렇게 측정한다: 매개변수 목록이 괄호로 묶이고 쉼표나 여는 괄호를 따르는 화살표 함수, 즉 [(,]\s*(?:async\s*)?\([^)]*\)\s*=> 패턴을 detect_scope가 유지하는 파일들의 마스킹된 소스에서 찾는다 — Prime에서 15,094개, Hermes에서 19,625개, 모두 심볼을 추적하는 엔진에는 보이지 않는다. 이전 버전에서는 24,454개라고 발표했지만 어떻게 집계했는지 밝히지 않았다. 숫자는 전적으로 정의에 달려 있으므로, 정의를 명시한다.

두 코퍼스에서 측정: Prime에서 31개 경로, Hermes에서 41개 경로, 3,552개의 JS/TS 파일 중에서. 3천 개가 아니라 72개 — 이것이 틴트 엔진의 형태이지, 패턴 스캐너의 형태가 아니다.

엔진은 아무도 이름으로 호출하지 않는 함수도 추적한다: 런타임이 그 함수들을 호출하고 값을 전달한다. addEventListener는 틴트를 도입한다 — 매개변수 자체가 입력이다. .then, .map, .forEach는 틴트를 전달한다 — 매개변수는 순회 대상이 틴트되었을 때만 정확히 틴트되므로, 상수 리스트는 상수 리스트로 남는다. 이것이 값을 추적하는 것과 값을 만들어내는 것의 차이이며, 이 차이가 없으면 브라우저 코드 트리는 거의 완전히 보이지 않게 된다.

obj[clé] = valeur에서 키가 통제되는 경우는 별도의 싱크다: 페이로드는 값이 아니라 키다. __proto__가 프로그램의 모든 객체를 통해 쓰여질 수 있기 때문이다. Hermes에서 실제 사이트 11곳, Prime에서 3곳, 모두 for (const [k, v] of Object.entries(x)) { out[k] = v } 형태다. __proto__를 이름으로 거부하는 루프는 수정된 것이며, 신고되지 않는다.

보고서는 균일한 커버리지를 암시하기보다 스스로 이렇게 밝힌다:

teinte au fichier près, pas au-delà : javascript 3 · typescript 912

Une exception, et une seule : un import **relatif** se résout par une règle de
fichiers, pas par une inférence. `./helpers` depuis `src/app.ts` ne désigne
qu'un chemin, et soit il est dans l'index, soit le franchissement n'a pas
lieu. Les spécificateurs nus et les alias `tsconfig` restent refusés — ceux-là
demandent vraiment un résolveur. Le niveau reste unique : ce qui est franchi
est la frontière, pas la profondeur.

Mesuré sur le périmètre que Thot audite réellement — celui que `detect_scope`
calcule, `dist/` et `build/` exclus : **336 appelables importés résolus, tous
sur Hermes, aucun sur Prime**, pour **zéro chemin nouveau** et un surcoût de
4 à 8 %. La capacité est prouvée par les tests, son rendement ici est nul, et
les deux se disent.

Une première version de ce paragraphe annonçait 1 514 appelables et +21 %.
Ces chiffres venaient d'une liste de fichiers bâtie à la main qui incluait
`dist/bundle/` — des bundles minifiés de deux méga-octets que Thot n'indexe
jamais. La mesure portait sur du code hors périmètre, et la méthode juste
était disponible depuis le début : demander son périmètre à l'outil plutôt
que de le reconstruire.

TypeScript 인덱서는 스캐너이지 tsc가 아니다: 주석과 리터럴을 마스킹한 다음 중괄호 매칭으로 선언을 읽는다. tsc로 넘어갔다면 맵이 설치된 node 체인에 의존하게 되었을 것이고, 그 체인은 해석 가능하고 올바른 버전이어야 한다 — 일부 머신에서만 동작하는 맵은 한계가 명시된 맵보다 가치가 없다. 측정 결과: Prime에서 8,568개 심볼을 1.7초에, Hermes에서는 11,138개를 더 처리한다.

파일이 무엇을 위한 것인가

심각도는 영향 × 접근성 × 신뢰도이며, 접근성은 호출 그래프에서 나온다. 그래프는 "진입점이 여기에 도달할 수 있는가"에 답한다. 공격 표면이 전혀 아닌 파일에 대해서는 할 말이 없다.

Thot와 함께 제공되는 두 프로그램에서 측정: Hermes의 HIGH finding 25개 중 12개와 Prime의 11개 중 6개가 테스트 또는 예제 코드에 있었다. 보고서 상단의 거의 절반이 어떤 공격자도 도달하지 못하는 코드에 관한 것이었다 — 이것이 보고서가 더 이상 읽히지 않게 되는 방식이다.

이전

이후

hermes

25 high · 94 medium · 297 low

13 high · 58 medium · 345 low

prime

11 high · 2 medium · 9 low

5 high · 8 medium · 9 low

HIGH 열이 논쟁을 이끄는 열이며, 첫 측정 이후 단 한 건도 움직이지 않았다: 25 → 13, 11 → 5. 위의 medium 및 low 집계는 오늘날의 트리 기준으로 다시 계산되었으며, 두 측정 사이에 Hermes에서 9개의 취약점이 수정되었다.

추가된 finding도, 손실된 finding도 없다. 이것은 강등이지, 결코 삭제가 아니다: 테스트 코드는 개발자 머신과 CI에서 실행되며, 이것이 바로 공급망 공격의 정확한 형태다. Finding은 남아 있고, 그 역할을 출처로 전달한다.

분류는 보수적이다 — 하위 문자열이 아닌 전체 세그먼트(latest/는 테스트 폴더가 아니고, contest.py는 테스트 파일이 아니다), 그리고 인식되지 않는 모든 것은 프로덕션으로 간주한다. "테스트"로 잘못 분류하면 실제 결함을 숨기게 되고, "프로덕션"으로 잘못 분류하면 단계 하나만 잃을 뿐이다.

가치는 어디서 오는가

Finding은 경로를 시작시킨 소스 규칙을 전달한다 — source.argv, source.http, source.js.event — 단순히 위치한 줄만이 아니다. 보고서는 이를 명시적으로 말하고("명령줄에서 비롯된 값…") JSON은 source_rule 키로 제공하여, 다운스트림 필터가 프랑스어 문장이 아닌 사실을 읽게 한다.

이것이 순위에서 빠진 절반이다. 명령줄 도구의 open(args.sortie, "w")는 운영자가 파일을 지정하는 것이다: argv를 제공하는 자는 이미 그 프로세스의 파일 시스템을 소유하고 있으며, 호출은 그에게 아무것도 주지 않는다. 핸들러의 open(request.args["f"])는 임의 파일 읽기다. 같은 규칙, 같은 싱크, 두 개의 다른 세계.

로컬 소스

원격 소스

sink.fs.read · sink.fs.write · sink.js.path

한 단계 낮음

전체 순위

그 외 전부

전체 순위

전체 순위

이 세 가지만 해당된다. argv에서 구성된 명령은 여전히 명령이며, 환경 변수에서 읽은 pickle은 여전히 코드를 실행한다: 여기서는 경로가 아니라 싱크가 권한 상승을 만든다.

엔진이 속성 체인을 추적할 수 있게 된 날 Hermes에서 측정: 이 구분이 없으면 sink.fs.read만으로 보고서에 48개의 finding이 들어갔고, 그중 9개는 단일 CI 스크립트에서 나왔으며, 각각은 요청받은 파일을 여는 유틸리티였다. 구분이 있으면 임계값 아래로 내려가고 한 번의 타이핑(--all)으로 유지된다.

알 수 없는 출처는 로컬로 간주되며, 숨기지 않고 명시된다: 반대로 가정하면 엔진이 출처를 명명하지 못한 모든 경로가 보고서 상단으로 올라갈 것이다. 로컬 변수와 그것이 파생된 매개변수 사이의 연결이 더 이상 끊기지 않게 되면서 명명할 수 있는 것의 수가 두 배가 되었다 — cible = chemin.strip()은 chemin에 대한 연결을 유지했고, 그 연결이 없으면 싱크는 어떤 매개변수에도, 따라서 어떤 호출자에도 연결되지 않았다.

왜 안전한가

판정은 Finding.compute_id에 인덱싱되며, 이는 규칙, 파일, 심볼, 그리고 해당 심볼의 정규화된 AST를 해시한다. 형식을 다시 지정하고, 함수를 이동하고, 로컬 변수를 이름 변경해도 판정은 유지된다. 코드가 하는 일을 바꾸면: 식별자도 함께 바뀌고, 판정은 저절로 만료된다.

따라서 거부는 그것이 관련된 코드보다 오래 살아남을 수 없다. 이것이 거부를 기억하는 것을 허용 가능하게 만드는 유일한 속성이다.

식별자는 또한 대상이 되는 정확한 호출을 명명한다 — httpx.get#3 — 그것을 포함하는 함수만이 아니다. 이것이 없으면 같은 함수의 네트워크 호출 5개가 메모리에는 단일 finding으로 보였고, 첫 번째를 기각하면 나머지 네 개도 그 이유와 함께 기각되었다. 이 구분자는 아무것도 약화시키지 않는다: 본문의 한 버전 내에서만 고유하면 되며, 본문의 AST는 본문이 움직이는 순간 관련된 모든 것을 이미 만료시킨다.

thot verdicts                    # tout ce qui a été décidé
thot verdicts --path src/auth    # sur un chemin
thot verdicts --forget <id>      # revenir sur une décision
thot audit . --no-memory         # ignorer la mémoire pour ce run

결정은 그것을 생성한 finding보다 오래 산다: 코드가 바뀌면 finding은 새 정체성을 갖게 되고, 이전 결정은 더 이상 아무것도 가리키지 않는다. 목록은 그러한 결정을 다른 것처럼 표시하는 대신 [마지막 감사에 없음]으로 표시한다 — 죽은 결정 3개를 포함한 결정 6개는 살아있는 결정 6개로 읽혀서는 안 된다.

메모리는 모델 이전에 적용된다: 이미 결정 — 기각, 수락, 또는 수정 — 이 있는 finding은 절대 분석에 다시 보내지지 않는다. 모든 것이 결정된 실행은 모델 호출을 하지 않는다. 이것은 단지 경제성의 문제가 아니다: 프로브는 신뢰도, 심각도, 시나리오, 출처를 한 번에 대체하므로, 결정을 모델에 다시 보내면 그것을 덮어쓰고, 누가 결정했는지도 지워버릴 것이다. 회귀가 가장 중요한 경우다: 이미 한 번 실제로 판정되었으므로, 어떤 심층 패스도 그것을 잠재울 수 없다.

그리고 반박은 thot audit --deep에서든 /audit deep에서든 자동으로 기록된다: 모델 호출 두 번, 한 번만 지불. 그것들은 당신의 이름이 아니라 결정한 엔진의 이름을 전달한다 — 기계의 결정은 인간의 결정을 덮어쓰지 않는다.

아무것도 조용히 삭제되지 않는다. 기각된 finding은 refuted 상태로 보고서에 남으며, 이유와 작성자가 함께 남는다 — 무시하라고 지시받은 것을 숨기는 감사는 다시 읽을 수 없다.

Python 커널

Prime Agent의 핵심 아이디어를 이식했다: 질문당 도구 호출 대신, 모델이 Python을 작성하고 그 변수들이 생존한다.

   › /py bas = audit(severity="low"); print(len(bas), "findings"); [f.rule for f in bas]
   3 findings
   → ['sink.eval', 'sink.network', 'sink.subprocess.shell']

   › /py len(files())
   → 148

저장소 맵은 객체로 사용 가능하다 — files(), symbols(), find(), callers(), callees(), audit(), read(). findings와 호출자를 교차하는 루프는 모델 한 턴이면 된다. 도구 호출로 같은 작업을 하면 열두 번이 넘고, 각 호출은 맵이 이미 알고 있던 것을 다시 읽는 비용을 지불한다.

커널은 절대 Thot의 프로세스에서 실행되지 않는다. 자체 exec()는 감사 대상 코드에 Thot의 메모리, 열린 데이터베이스, 파일 디스크립터를 부여할 것이다. 따라서 하위 프로세스이며 — 샌드박스가 구성된 경우 컨테이너 안에서 실행된다.

이것이 정확히 무엇을 보호하는지 — 그리고 Thot은 자체 적대적 패스가 지나치게 절대적인 docstring을 지적한 후 스스로를 수정했다:

하위 프로세스 (local)

Thot의 메모리, 데이터베이스, 파일 디스크립터를 보호한다. 당신의 자격 증명은 아니다: 워커는 당신의 계정으로 실행되며 ~/.claude/.credentials.json을 읽을 수 있다.

컨테이너 (docker)

실제 경계: 네트워크 없음, 당신의 $HOME 없음, 저장소는 읽기 전용.

민감한 환경 변수는 실행 전에 제거되며, /py는 로컬 모드에서 "분리된 프로세스"가 제공하지 않는 보장으로 읽히지 않도록 한 번 명시한다.

rlm() — 셀에서 위임하기

verdicts = {f.id: rlm(f"Ce chemin est-il exploitable ?\n{f.failure_scenario}")
            for f in audit(severity="high")}

셀은 자신의 문제를 분해할 수 있다. 셀은 어떤 자격 증명도 보유하지 않는다: 호스트에게 요청하고, 호스트가 결정하고 지불한다. 따라서 한계는 호스트 측에서 유지된다 — 셀당 8회 호출, 커널당 40회 — 자식이 수정할 수 있는 한계는 한계가 아니며, 자식은 감사 대상 저장소에서 온 코드를 실행하기 때문이다.

Thot이 저장소에서 기억하는 것

   › /harness note team.shell.run : échappe ses arguments, les findings dessus sont faux
   ✓ Retenu — rappelé à chaque session.

Prime의 정제를 감사에 적용: 어떤 정적 분석도 결코 도출하지 못할 사실들. 그것들은 <dépôt>/.thot/harness.json에 살며, 판정처럼 풀 리퀘스트에서 다시 읽히고, 매 세션 브리핑으로 돌아온다.

모델이 할 수 있는 것

thot --tools lecture      # lire et raisonner, jamais modifier
thot --tools carte        # la carte seule : aucun fichier ouvert

세션에서: /tools lecture. 당신의 것이 아닌 저장소를 다시 읽는 것은, 당신이 충분히 경계할 이유가 있는 코드를 읽는 것이다 — 그리고 모델이 그것을 수정하는 것은 당신이 원하는 것이 거의 아니다.

이 자세는 한 곳이 아니라 세 곳에 달려 있다: 모델에게 제안되는 도구, 모델이 그래도 도구를 호출하는 순간, 그리고 — 계정 모드에서 — 공식 CLI로, --disallowed-tools가 Write, Edit, Bash를 금지한다. Thot의 도구만 필터링하는 자세는 가장 중요한 곳에서 거짓말이 될 것이다.

공급망

thot deps                       # les dépendances épinglées, contre OSV.dev
thot deps --list                # ce qui a été trouvé, sans réseau
thot deps --fail-on high        # code 1 en CI
thot audit . --deps             # dans le rapport d'audit
thot mcp check                  # tes serveurs MCP sont-ils malveillants ?

항상 먼저 잠금: uv.lock, poetry.lock, Pipfile.lock, package-lock.json, yarn.lock, pnpm-lock.yaml. 매니페스트가 requests>=2라고 말하면 OSV는 구간에 답할 수 없다. 잠금이 2.31.0이라고 말하면 OSV는 안다. 구간으로만 존재하는 의존성은 추측되지 않으며, 고정되지 않은 것으로 보고된다.

특정 버전을 다루는 평가는 추측이 아니라 사실이다 — 하지만 네 코드가 취약한 함수에 도달하는지 여부는 분석되지 않으므로, 그런 findings는 PLAUSIBLE로 남고 그렇게 명시한다. 오직 MAL-*만 예외다: 패키지 자체가 페이로드이므로, 도달 가능성은 문제가 되지 않는다.

그리고 다른 모든 곳과 동일한 속성: finding의 정체성은 고정된 버전을 담고 있으므로, bump는 판정을 만료시킨다. requests==2.19.1에 대한 CVE를 기각해도 2.20.0에 대해서는 아무것도 기각되지 않는다.

OSV에 연결할 수 없다고 해서 건강 증명서가 되는 일은 결코 없다: thot deps는 "미확인"이라고 말하고 오류 코드를 반환한다.

감사 대상 코드를 네 컴퓨터에서 실행하지 않고 실행하기

감사 대상 저장소에서 pytest를 실행하면, 그 저장소의 코드가 네 계정으로 실행된다. 설계 전체가 새어 나가는 유일한 지점이 바로 여기다.

thot sandbox status
thot sandbox use docker
thot sandbox show pytest -q     # la commande docker exacte, à relire

기본적으로, 컨테이너 안에서:

네트워크

차단됨 (--network none)

저장소

읽기 전용으로 마운트, tmpfs에 쓰기 가능한 복사본

권한

--cap-drop ALL, no-new-privileges, 사용자 65534

제한

--pids-limit, --memory, --cpus, --rm

차단된 네트워크는 가장 가치가 크면서도 가장 불편한 플래그다: 그래서 법칙이 아니라 플래그인 것이다 (--network).

Thot의 나머지 규칙을 뒤집는 규칙 하나: 다른 모든 곳에서는 누락된 의존성이 해당 기능만 희생시키고 작업은 계속된다. 여기서는 요청된 샌드박스를 사용할 수 없으면 실행을 거부한다. 조용히 호스트로 대체하는 것은 보호 장치를 거짓말로 바꾸는 일이 될 것이다.

결정 공유하기

판정은 이 코드의 이 리비전에 대한 사실이다. 따라서 코드와 함께 이동한다: <dépôt>/.thot/verdicts.json, 해당 코드를 건드리는 pull request에서 다시 읽히며, 네트워크가 있기 전에 새 클론에도 존재한다.

thot verdicts --share <id>   # publier une décision locale dans le dépôt
thot verdicts --share-all    # toutes celles qui concernent ce dépôt
thot verdicts --where        # d'où viennent les décisions, où elles s'écrivent

메모리는 트리 간에 공유되지만 파일은 그렇지 않다: 다른 저장소에 대한 결정을 게시하는 것은 거부되며, --share-all은 여기에 파일이 존재하는 결정만 가져간다.

아무 구성 없이 기본 체인: 저장소가 먼저, 네 머신이 그다음. 검토를 거친 결정이 네가 스스로에게 남긴 메모보다 우선한다.

쓰기는 로컬에 남는다. /verdict마다 버전 관리되는 파일을 수정하는 도구는 아무도 요청하지 않은 diff를 만들어 낼 것이다: 로컬에서 결정하고, 의도적으로 게시한다.

공유 서버, 또는 기존 mem0

// ~/.thot/memory.json
{"remote": {"kind": "http", "base_url": "https://audit.equipe.example", "token": "…"}}
{"remote": {"kind": "mem0", "host": "http://localhost:8888", "api_key": "…"}}

mem0 백엔드는 Hermes Agent의 클라이언트와 정확히 동일하게 자체 호스팅 계약을 말한다: Hermes용으로 이미 구축된 서버는 아무것도 바꾸지 않고 Thot에 서비스를 제공한다.

연결할 수 없는 원격 저장소는 과거 결정의 메모리만 희생시킬 뿐, 감사 자체는 절대 희생하지 않는다 — 하지만 조용히 그러지 않는다: thot verdicts --where는 어느 것이 침묵하는지, 왜 그런지 말해준다.

다른 곳에서 감사 받기

03:00에 끝나는 감사는 아무도 알리지 않는 한 아무 가치가 없으며, 알림을 받아야 할 사람은 터미널 앞에 있지 않다.

thot gateway add ntfy topic=thot-$(openssl rand -hex 8)   # le sujet EST le secret
thot gateway add telegram token=… chat_id=…
thot gateway allow telegram <ton-id>     # obligatoire pour commander
thot gateway test
thot serve                                # écouter les commandes

채널

발신

수신

Telegram

✓

✓ (long polling — 열 포트 없음)

Discord · Slack

✓ (webhook)

—

ntfy

✓

— (정체성 없음: 주제만으로 게시에 충분)

이메일

✓ (SMTP)

—

알림은 어떤 데몬도 요구하지 않는다: gateway-notify 플러그인은 post_audit에서 발동하며, 오직 감시되지 않은 감사에 대해서만 그렇다. 수동으로 시작한 감사는 이미 화면에 표시된다; 매번 알리면 수신자가 채널을 끊는 법을 배우게 되어, 유일하게 중요했던 메시지를 잃게 된다. 새로운 것 없음: 침묵.

도난당한 토큰이 가능하게 하는 것

데몬은 오직 회신을 위해서만 존재하며, 그 설계는 주로 여기에 달려 있다:

  • 명령 집합이 폐쇄적이다 — status, audit, findings, verdict, help. 셸 없음, 쓰기 없음, 임의 경로 없음;

  • 감사는 오직 thot schedule add로 이미 선언된 저장소만 대상으로 할 수 있다;

  • 수신은 허용 목록을 요구한다. Hermes는 개발용 ALLOW_ALL_USERS를 제공한다; Thot에는 그에 상응하는 것이 없다. 목록이 없으면 채널은 발신 전용이며, thot serve가 그렇게 말한다.

~/.thot/gateway.json은 0600으로 작성된다 — 봇 토큰과 SMTP 비밀번호를 담고 있기 때문이다. 환경 변수는 Hermes의 이름으로 필드별로 이를 덮어쓴다.

예약된 감사

thot schedule add nuit ~/mon-projet --every daily --threshold high
thot schedule list
thot schedule run nuit            # ce que le planificateur appelle
thot schedule remove nuit

Thot은 launchd 유닛(macOS)을 작성하거나 crontab 줄을 제공하며, 직접 활성화하도록 맡긴다 — 조용히 백그라운드 작업을 설치하는 도구는 더 이상 믿지 않는 도구다.

launchd가 할 수 없을 때. macOS에서 권한은 바이너리 단위로 부여된다: launchd 에이전트는 ~/Desktop, ~/Documents 또는 ~/Downloads에 대한 접근이 거부될 수 있으며, 그러면 유닛은 한 줄도 쓰지 않고 인터프리터 시작에서 멈춘다. 네 세션에서 시작된 프로세스는 고아가 된 후에도 그 세션의 접근 권한을 유지한다 — 이것이 세 번째 해결책이며, 시스템에 아무것도 요구하지 않는다:

thot schedule start       # un planificateur dans ta session
thot schedule status      # tourne-t-il, et quand est-il passé
thot schedule stop
thot schedule autostart   # le relever au premier terminal après un redémarrage

launchctl이 실제로 실행했다고 선언하는 launchd 유닛 앞에서는 물러난다: 같은 작업에 스케줄러가 두 개면 작업도 두 배, 토큰도 두 배다. thot doctor는 둘 중 어느 것이 작동하는지 말해준다.

예약된 감사는 새로운 것이 없으면 아무것도 말하지 않는다. 같은 300개의 findings를 반복하는 야간 보고서는 아무도 열지 않는 폴더에 쌓인다. 올라오는 것은 diff다: 지난번 이후 새로 나타난 것, 임계값 이상, 이미 무의미하다고 판정된 것을 뺀 것.

플러그인

다섯 개의 훅, 각각은 배송된 무언가가 사용하기 때문에 존재한다:

Hook

시점

on_finding

보고서 전, 주석 달기용

post_audit

감사 완료 — 알림, 내보내기, 보관

pre_write

에이전트의 쓰기 전 — 경고 반환

post_write

성공적인 쓰기 후

on_verdict

결정이 방금 기록됨

플러그인은 plugin.yaml과 __init__.py가 있는 폴더로, ~/.thot/plugins/ 또는 <repo>/.thot/plugins/에 있다 — Hermes Agent가 사용하는 형태다. 플러그인이 죽으면 자신의 기능만 희생되고 다른 것은 아무것도 아니다: 그 오류는 기록되어 /plugins로 표시된다.

감사 대상 저장소의 플러그인은 네 동의 없이 실행되지 않는다. 플러그인을 로드하는 것은 그 코드를 여기, 네 계정으로 실행하는 것이다 — 그리고 감사 대상 저장소는 바로 Thot이 경계하는 바로 그 저장소다. 따라서 그 플러그인들은 네가 승인하기 전까지 이름만 표시되고, 가져오지 않는다:

thot plugins list <dépôt>                 # chargés, et refusés avec la raison
thot plugins trust <dépôt>/.thot/plugins/x   # après l'avoir lu
thot plugins untrust <dépôt>/.thot/plugins/x

승인은 이름이 아니라 내용에 적용된다: Thot은 폴더의 지문을 기록하며, 조금이라도 수정되면 그 사실을 말하면서 승인을 취소한다.

세 개가 함께 제공된다:

Plugin

기능

write-guard

모델이 쓰는 것을 다시 읽고 위험한 패턴이 나타나면 경고를 올린다. 차단하지 않음 — 세션을 차단하는 오탐은 쓰기보다 더 나쁘다.

regression-alert

fixed로 표시된 결함이 다시 나타나면 CRITICAL로 올라간다: 회귀는 새로운 후보보다 더 가치가 있다.

audit-log

각 감사, 판정, 쓰기의 로컬 JSONL 저널을 ~/.thot/journal.jsonl에 기록한다. 네트워크 없음.

모든 것이 있는지 확인하기

"작동한다"는 주장이며, 세 프로그램으로 이루어진 프로그램에서는 — 특히 그 도구 자신에게서 나온 말이라면 — 그 말을 곧이곧대로 믿을 주장이 아니다.

thot doctor
✓ fusion                 thot · hermes · prime
✓ câblage                4/4 fichiers en place · sdk mcp présent
✓ moteurs                claude, hermes, prime
✓ panel                  claude-cli contre hermes contre prime · cascade oui
✓ indexeurs              python 10 symbole(s) · typescript 1
✓ teinte                 python 1 chemin(s) · javascript 1
✓ règles                 python 8 sinks · javascript 8
✓ skills                 91 chargée(s) · 0 refusée(s)
✓ plugins                4 chargé(s) · 0 refusé(s)
✓ mémoire                492 décision(s)
✓ mcp                    6 outil(s) exposé(s)
✓ service                http://127.0.0.1:8787/mcp répond
✓ amélioration           daily, 8 candidats par arbre · unité launchd,
                         1 passage(s) · agents joignables depuis l'unité

13/13 vérification(s) passées en 1.83 s

날짜가 찍힌 스냅샷이지 계약이 아니다: mémoire는 누적된 판정을 세며 증가할 수밖에 없고, skills는 머신에 설치된 것에 달려 있다. 안정적인 것은 형식이다 — 각 줄은 측정된 숫자를 담고 있으며, 12/12.

마지막 줄은 개발 머신에서의 실제 출력이며, 그대로 유지된다: 그것이 바로 이 점검이 만들어 내는 것이다. 1 passage(s)는 launchctl 자체에서 나온 것이며 장식이 아니다: launchctl list는 로드된 유닛을 보여주고, LastExitStatus는 0이며, 저널은 존재하지 않는다 — 한 번도 시작되지 않은 작업에 대해 "모두 정상"을 말하는 세 가지 신호다. 원인을 이름 짓는 것이 초록 줄을 세는 것보다 낫다.

이 점검 자체도 수정되어야 했다. 경로의 형태로 작업을 비난했다 — "트리가 ~/Desktop 아래에 있으므로 launchd가 읽을 수 없을 것" — 하지만 macOS 권한은 바이너리 단위로 부여된다: 유닛의 인터프리터는 /bin/sh가 거부당한 트리를 읽고 있었다. 형태는 의심일 뿐, 통과는 사실이며, 이제 점검이 묻는 것은 바로 그 사실이다.

그리고 어떤 정적 검사도 할 수 없었던 점검 하나:

thot doctor --agents        # un appel modèle par agent installé
✓ lecture · claude       lit un fichier par chemin absolu
✓ écriture · claude      n'a pas écrit cette fois
✓ outils · claude        10 outil(s), tous en lecture seule
✓ lecture · hermes       lit un fichier par chemin absolu
✓ écriture · hermes      peut écrire — aucun mode lecture seule
                         (`-t file` et `--safe-mode` ne restreignent pas les permissions)
✓ outils · hermes        mcp__patch, mcp__read_file, mcp__search_files, mcp__write_file
✓ lecture · prime        lit un fichier par chemin absolu
✓ écriture · prime       peut écrire — outil unique : un noyau IPython
✓ outils · prime         ipython

쓰기 줄은 불편한 능력을 알리면서도 초록색이다: 원하는 것을 말하는 것이 아니라 있는 그대로를 보고한다. 세 에이전트 중 둘은 쓸 수 있고 어떤 플래그도 이를 막지 않는다 — 파일을 만들도록 요청한 다음 디스크를 확인하여 측정했다. 막을 수 없는 것은 놓칠 수 없게 만든다: AuditResult.touched는 한 패스가 수정한 것을 이름 짓고, 야간 루프는 stderr로 그것을 외친다.

임시 폴더에 파일을 만들고 그 내용을 요청한다. 실제 결함 때문에 존재한다: Hermes는 작업 폴더에 상대적인 경로를 열지 않았고, 거부처럼 읽히는 문장으로 응답했다. 패널의 3분의 1은 두 번째 파일에 의존하는 어떤 주장도 검증할 수 없었고, 파일을 만드는 것 외에는 그것을 보여줄 방법이 없었다.

각 줄은 실제 작업을 실행하고 측정한 것을 보고한다: "skills: 구성됨"이 아니라 "91개 로드됨, 0개 거부됨". 색상 엔진은 두 언어의 샘플에서 경로를 찾고, MCP 서버는 자신의 프로토콜에 응답한다. 실행할 수 없는 점검은 조용히 통과하는 대신 실패한다: "테스트 안 됨"을 뜻하는 초록 줄은 빨간 줄보다 더 나쁘다. 네트워크나 모델에는 아무것도 닿지 않는다 — 비행기 안의 thot doctor는 사무실과 같은 답을 준다. 실패 시 0이 아닌 종료 코드로, &&나 CI에 들어갈 수 있다.

지속적 개선

스무 개의 후보를 논하고 멈추는 감사는 나머지를 영원히 판정 없이 남긴다. 예산 없는 패스는 네가 다시 앉을 때까지 계속 돈다. thot improve는 그 중간이다: 각각 디스크에 기록되고, 각각 이전 것이 멈춘 곳에서 다시 시작하는 유한한 라운드.

thot improve                      # un tour sur les trois arbres
thot improve --rounds 5           # jusqu'à ce qu'un tour ne juge plus rien
thot improve --every daily        # la boucle devient permanente

작성된 유닛은 자신의 PATH를 담고 있다. launchd는 작업에 /usr/bin:/bin:/usr/sbin:/sbin을 주고, cron은 그보다도 적게 주며, claude, hermes, node는 그 어떤 폴더에도 없다 — 그들은 ~/.local/bin 아래에 있다. 이것이 없으면 야간 패스는 어떤 엔진도 빌드하지 못하고, 아무것도 판정하지 않았으며, 종료 코드 0으로 나왔다: launchd는 매일 밤 무한히 성공을 기록했다. 이런 종류의 작업이 조용히 실패하는 것은 작동하는 작업과 구별할 수 없으므로, 에이전트가 없는 심층 패스는 이제 오류로 종료되고 그 사실을 말한다.

야간 버전은 결정한 것을 보고하며, 나타난 것을 보고하지 않는다. 그 구분은 중요하다: 예약된 감사(audit)의 보고 메커니즘은 "임계값 위에 새로운 것이 무엇인가"에 답하며, 이는 스캔에는 올바른 질문이고 판단에는 잘못된 질문이다. 이미 보고서에 있는 MEDIUM을 확인하는 것은 정확히 루프가 존재하는 이유이며 — 그것은 누구에게도 보고되지 않았을 것이다. 감사가 수정했을 파일들은 같은 로그에 표시된다.

더 이상 판단할 것이 없는 트리는 자신의 몫을 다음에게 넘긴다. 실제 코퍼스로 측정: thot은 빈 백로그를, prime은 단 하나의 후보를 가지므로, 트리당 20의 예산은 그것들을 사용할 수 없는 트리들에 40을 쓰는 동안 Hermes는 150을 기다리고 있었다. 20의 라운드는 20, 20, 60의 라운드가 된다.

세 번째가 그것을 빠르게 수렴하게 한다: 실패가 집계된다. 에이전트가 시간을 초과하거나 모델이 확약을 거부하는 후보는 자신의 심각도를 유지한다 — 따라서 다음 라운드에서 먼저 다시 처리되며, 그 다음 라운드에서도 그렇다. 1,660줄 파일의 finding으로 측정: 세 번의 시도 중 네 번의 시도, 그중 세 번이 같은 벽을 넘지 못했다. 두 번의 실패 후, 그것은 큐의 끝으로 이동한다: 여전히 자격은 있지만, 결코 우선순위가 아니다. 성공은 카운트를 지운다 — 바쁜 오후나 만료된 구독이었던 벽은 finding을 영원히 따라다녀서는 안 된다.

두 가지 속성이 그것을 빙빙 도는 대신 수렴하게 한다: 반박은 기억되므로 다음 선택은 그것을 건너뛴다; 확인은 의도적으로 기억되지 않는다 — 진짜 결함은 누군가 고칠 때까지 계속 나타나야 하므로 — 따라서 루프는 자체적인 이미 판단된 식별자 집합을 지닌다. 그것 없이는, 첫 라운드 이후의 모든 라운드는 첫 라운드가 방금 확인한 것을 다시 논증하는 데 전체 예산을 쓸 것이다.

그것은 합계 앞에, 해야 할 일로 끝난다:

À REGARDER — 2 finding(s) :
  [hermes] plugins/platforms/a2a/tools.py:83 — confirmé · prime
      L'URL vient d'un argument d'outil, donc du modèle…
  [prime] packages/coding-agent/…/state-snapshot.ts:163 — réfutation contestée · hermes
      Le chemin dit fixe est construit depuis un identifiant non validé…

4 tour(s) · 83 jugement(s) (80 réfuté · 1 confirmé) · 157 candidat(s) sans décision

반박은 관리(intendentance)이고, 확인은 뉴스이다. 이의가 제기된 반박도 마찬가지다: 그것은 프로그램이 무언가를 묻기 전에 스스로 만회했다고 말하는 것이다. 그것들을 이름 없이 집계하면 독자는 로그를 grep하게 된다 — 이것이 정확히 하루 동안 매번 일어난 일이다.

그것은 결코 코드를 수정하지 않는다. 여기서 "개선"은 프로그램이 자신에 대한 판단이 더 선명해지고 더 저렴해진다는 뜻이다: 결정 없는 후보가 줄고, 디스크 위의 결정이 늘며, 각각은 그것을 내린 에이전트에 귀속될 수 있다.

온도계, 그리고 그것을 사용하는 루프

앞의 모든 것은 Thot을 Thot으로 측정한다. improve는 모델에게 finding이 실제인지 묻는다. evolve는 provenance를 감시했는데, 이는 엔진이 자신의 출력에 대해 계산하는 보고서이다. 둘 다 순환적이며, 그 원은 학문적인 것이 아니다: 심층 패스는 9개의 확인을 위해 638개의 판단을 지불했고, 그동안 **−100 %**로 평가된 규칙이 잠들어 있었다 — xml_unsafe_parse는 defusedxml을 지적했는데, 이는 정확히 자신의 메시지가 권장하는 해결책이었다. 프로그램 안에서 그것을 볼 수 있는 것은 아무것도 없었다.

thot bench는 그 원을 깨뜨린다. 그것은 Thot을 다른 누군가가 취약 또는 건전으로 레이블한 코드에 대해 측정하며, 동등한 비율로, 약점 클래스가 명명된다.

thot bench ~/.thot/bench              # les suites présentes, catégorie par catégorie
thot bench ~/.thot/bench --json       # ce que la boucle d'évolution lit
thot bench ~/.thot/bench --floor info # ce que le plancher de sévérité cache

코퍼스는 내장되지 않는다 — 18,000개의 타사 파일은 이 저장소에 있을 자리가 없다 — 따라서 경로는 항상 주어지고, 각 스위트는 매니페스트의 지문(fingerprint)에 대해 검증된다. 측정 중에 레이블이 움직인 코퍼스는 코퍼스가 없는 것보다 나쁘다: 그 이후의 모든 수치는 틀렸고 그것을 말해주는 것도 없다.

점수는 Youden의 J, TPR − FPR이다. 0은 동전 던지기, +100은 완벽, 그리고 음수는 규칙이 반전되었음을 의미한다. 정밀도와 재현율은 그것을 말하지 않았을 것이다: 진짜 양성이 하나도 없는 규칙은 정밀도가 정의되지 않고, 비어 보이며, 데이터 없음으로 읽힌다 — 이것이 정확히 반전된 규칙이 살아남는 방식이다. J에는 그 구멍이 없다.

두 가지 부정 행위 방법이 있고, 둘 다 진다. 더 적게 찾는 것 — 이것이 provenance를 올렸던 것 — 은 TPR을 떨어뜨린다. 모든 것을 신고하면 TPR 100 %, FPR 100 %, J 0이 된다: 코퍼스는 정확히 그 이유로 50/50으로 균형 잡혀 있다.

측정된 상태, 기본 플로어, 세 가지 프레임워크 (django, fastapi, flask):

                        avant      après
TPR                      9.9 %     55.5 %
FPR                      0.5 %      0.000 %
J de Youden             +9.4 %    +55.5 %
catégories actives          10         34
catégories négatives         0          0

"이전"은 온도계가 처음 존재했을 때의 프로그램 상태이다. "이후"는 같은 코퍼스, 같은 플로어, 같은 명령이다. 홀드아웃(hold-out)이 그것을 확인하며, 세 가지 방식으로: django 단독으로 +55,5 %, fastapi +55,5 %, flask +55,5 %. 서로 전혀 닮지 않은 세 프레임워크, 소수점 첫째 자리까지 같은 수치 — 규칙들은 그것들을 작성하는 데 사용되지 않은 코드에서 작동한다.

범주별로, 엔진이 오늘 할 수 있는 것. 활성 범주 34개 중 32개가 정확히 +100 %이다:

basic_xss · cleartextstorage · cleartexttransmit · cloud_ssrf_metadata
cmdi · codeinj · cookie_no_httponly · cookie_no_samesite · corsmisconfig
crlfinjection · csv_injection · debug_code_production · default_credentials
deserial · errormessage · eval_injection · hardcodedcreds · ldapi
loginjection · nosql · redirect · securecookie · sqli · ssti · tlsverify
weakcipher · weakhash · weakkeylength · weakrand · xpathi · xss · xxe
                                                               +100,0 %
pathtraver  +94,0 %          ssrf  +92,0 %

9,150개의 건전한 사례에서 오탐지 0건, 그리고 음수 범주 없음. 출발점은 ssrf가 −8,0 %, xxe가 −100 %였다.

한계, 최댓값 없는 수치는 아무 의미가 없으므로

+55,5 %는 "문제의 절반이 해결되었다"는 뜻이 아니다. 61개 범주 중 27개는 도달할 수 없으며, 두 가지 별개의 이유가 있고, 둘 다 파일을 열어 사례별로 검증되었다.

열 개는 다른 범주가 이미 소유한 코드이다. 코퍼스는 같은 줄에 두 개의 서로 다른 CWE를 쓴다: el_injection과 ssti는 같은 Template(데이터) 호출이고, argument_injection과 genericcmdi는 cmdi와 같은 os.system('echo ' + 데이터)이며, unverified_signature는 tlsverify와 같은 verify=False, sensinlogs는 loginjection과 같은 logging.info, hardcoded_crypto_key는 hardcodedcreds와 같은 하드코딩된 키이다. Thot은 이 모든 파일에 대해 발사하며 — 보고서가 seen 열에 말해준다 — 코드가 실제로 무엇인지 클래스를 선언한다. 그것들을 따라잡으려면 하나의 규칙에 두 개의 클래스를 명명해야 하며, report/cwe.py가 정확히 거부하기 위해 존재하는 것이다.

열일곱 개는 sink가 없다. authzfailure, idor, intoverflow, null_deref, privescalation, csrf, missingcritauthn, sessionfixation 및 나머지는 모두 단 하나의 위험한 호출 없이 return JsonResponse(...)로 끝난다. idor의 취약한 사례는 올바르게 매개변수화된 요청이다; 부족한 것은 권한 부여 호출이다. clickjacking은 Django가 미들웨어로 설정하는 X-Frame-Options의 부재를 신고함으로써 이긴다. weak_password_hash는 그럼에도 이 코퍼스를 완벽하게 분리한다 — 각 취약 사례는 hashlib.sha256, 각 건전 사례는 pbkdf2_hmac — 그러나 이 줄들 중 어느 것도 값이 비밀번호라고 말하지 않으며, 규칙은 실제 저장소의 모든 체크섬, 모든 ETag, 모든 HMAC에 발사할 것이다. 마지막 두 개는 어쩌면 결국 코드일지도 모른다는 생각으로 재개되었고, 기록할 가치가 있는 이유로 닫힌다. intoverflow는 ctypes.c_int32로 자신의 약점을 제조한다 — Python 정수는 오버플로되지 않으며, ctypes에 발사하는 규칙은 BenchProctor에 대한 규칙이 될 것이다. resourceexhaust는 경계 없는 bytearray(int(데이터)) 대 bytearray(min(…, 1024))이며, 구분자는 단일 토큰 min이다; 그러나 bytearray(x)는 x가 정수인지 버퍼인지에 따라 두 가지를 의미하고, Thot은 타입을 추론하지 않으며, hermes/에서 이 계열의 102개 호출은 할당이 아닌 변환이다. 이것들은 논리 결함이거나 누락된 검사이다; 어떤 색조 분석도 그것들을 보지 못하며, 어떤 규칙을 써도 마찬가지이다.

따라서 **정직하게 얻을 수 있는 34개 범주, 즉 +55,7 %**가 남는다. 그것들은 모두 활성 상태이다. +55,5 %에서, 그것은 **정직한 최댓값의 99,6 %**이다.

여전히 누락된 21개 사례는 더 이상 범주가 아니라 그중 두 개 내부의 놓친 사례들이며, 하나씩 집계되었다: ssrf의 12개는 소스가 mq_client.get_message() 또는 redis_client.get()인데, 이는 BenchProctor의 app_runtime.py에 선언된 두 개의 _Stub() 객체이다 — 그것들은 이 코퍼스 밖에 존재하는 어떤 것도 명명하지 않으며, 그것들을 위해 쓰여진 패턴은 무엇이든 감지하는 대신 온도계에 달라붙을 것이다; pathtraver의 9개는 환경 변수에서 오는데, 접근성 할인(accessibility discount)이 의도적으로 등급을 낮추는 이유는 환경을 제공하는 자가 이미 이 프로세스의 파일 시스템을 쥐고 있기 때문이다.

이 46포인트를 만들어낸 것, 측정된 순서대로:

변경

J

출발

+9,4 %

웹 경로가 진입점으로 인식됨

+9,6 %

색조가 컨테이너 안의 값을 따라감

+10,0 %

상수 분기 삼항 연산자는 아무것도 전달하지 않음

+12,4 %

부정 클래스의 fullmatch는 블랙리스트임

포함

SSRF 가드: 호스트 화이트리스트, 해석된 IP 범위

+14,3 %

socket.create_connection이 네트워크 sink로 인식됨

+14,9 %

경로 격리 및 명명된 화이트리스트

+15,5 %

mark_safe가 sink, bleach가 HTML 중화로

+16,3 %

한 줄 패턴 규칙 12개

+27,8 %

패턴이 더 이상 접근성 할인을 지불하지 않음

+34,4 %

sink.redirect, 호스트 증명이 범위 증명과 분리됨

+35,6 %

주입 sink 4개: 템플릿, XPath, LDAP, NoSQL

+40,8 %

응답 헤더, 긍정 가드, CR/LF 중화

+44,2 %

스프레드시트 셀은 텍스트 줄이 아님

+45,6 %

secrets.txt라는 파일, 암호화 또는 해시 증명

+46,6 %

색조가 nonlocal 클로저를 통과함

+47,7 %

Django가 소스 세 개를 대문자로 씀

+48,0 %

HTMLResponse가 HTML을 선언하고, html.escape가 그것을 보호함

+49,0 %

문자열을 반환하는 뷰는 HTML을 반환함

+49,9 %

증명은 그것이 증명하는 것만 가치가 있고, 인용된 SQL 식별자

+50,0 %

문자열 안에 쓰여진 프로그램은 여전히 프로그램임

+50,4 %

경로의 매개변수는 네트워크에서 옴

+50,4 %

타사의 응답은 이 프로그램의 데이터가 아님

+50,6 %

프로그램이 secrets라고 명명한 파일은 자신의 내용을 말함

+51,2 %

데이터베이스가 돌려주는 것은 누군가가 거기에 넣은 것임

+53,9 %

로그 줄은 헤더처럼 줄바꿈으로 끝남

+55,5 %

한 줄 위에서 구성된 페이지를 반환하는 경로

+55,5 %

이 변경 중 두 개는 처음에 측정 후 거부되었고, 온도계가 그것들을 거부했다: clickjacking은 이 저장소에서 23개의 오탐지를 발생시켰고, weak_password_hash는 그럼에도 이 코퍼스를 완벽하게 분리한다 — 각 취약 사례는 hashlib.sha256, 각 건전 사례는 pbkdf2_hmac. 그것들은 위에서 말한 이유로 여전히 거부 상태이다: 이 줄들 중 어느 것도 비밀번호임을 말하지 않으며, 규칙은 실제 저장소의 모든 체크섬에 발사할 것이다.

세 번째 거부는 해제되었고, 이번 것이 가장 교훈적이다. 데이터베이스 읽기를 신뢰할 수 없는 소스로 취급하는 것은 두 번 측정되었고 두 번 거부되었다. 첫 번째는 취약 436건 대 정상 343건, 저장형 XSS로 제한했을 때는 11건 대 13건이었다. 여기서 정상 절반이 다른 쪽보다 더 자주 데이터베이스를 읽는다. 두 판정 모두 당시 프로그램에 대해서는 옳았다. 바뀐 것은 코퍼스가 아니라 정상 절반의 방어 수단이다. 이제 그것들이 하나씩 이해된다 — 암호화, HTML 이스케이프와 자동 이스케이프 렌더링, CR/LF 무력화, 호스트 화이트리스트와 해석된 IP 범위, 긍정 가드, 인용된 SQL 식별자. 한 줄을 읽은 다음 암호화하는 정상 케이스는 암호화 때문에 무죄가 되지, 아무도 그 줄을 보지 못했기 때문에 무죄가 되는 것이 아니다. 343건의 발명품을 만들던 바로 그 규칙이 이제는 0건을 만들고, 열여섯 개 범주에서 동시에 2.7포인트를 벌어들인다. 측정된 거부는 측정된 그 날에만 참이다.

LDAP / XPath / NoSQL / SSTI / EL 주입 다섯 규칙도 거부되었었다. J가 0.000으로 측정되었기 때문이다. 그것들은 정상 절반과 다른 쪽을 똑같이 끌어당겼다. 그중 네 개는 오늘 **+100.0%**이며 단 하나의 오탐도 없다. 판정이 마음을 바꾼 것이 아니다 — 읽을 수 있게 된 것이 바뀌었다. 각 규칙은 이제 주입을 담당하는 인자만 본다 (LDAP 필터는 세 번째, 템플릿 소스는 첫 번째), 그리고 sink.nosql은 import가 아니라 쿼리 텍스트의 장벽을 통과한다. .find(가 Python의 모든 문자열에 속하기 때문이다. 다섯 번째인 el_injection은 여전히 거부 상태다. ssti와 같은 Template(…) 호출에 다른 클래스 라벨이 붙은 것이고, 그 이름을 주는 것은 부정확성 하나에 점수 한 개를 지불하는 일이기 때문이다.

정밀도는 사각지대로 살 수 없다

위에서 인정된 각 가드는 먼저 점수를 올리면서 동시에 악용 가능한 구멍을 여는 버전으로 제안되었다. 적대적 프로브가 그것들을 모두 찾아냈다:

resolved = socket.gethostbyname(parsed.hostname or url)
if ipaddress.ip_address(resolved).is_private:
    return "blocked", 403
os.system("curl -s " + url)      # ← silencieux, et exploitable

그 가드는 올바른 SSRF 방어이며 문자열에 아직 남아 있는 셸 메타문자에 대해서는 아무것도 말하지 않는다. 모든 곳에서 색조를 정화하는 것은 오탐 174건 감소와 명령 주입 사각지대 하나를 사는 일이었다.

그래서 엔진이 담는 구분이 있다: 값을 제약하는 가드(리터럴 화이트리스트, 열거형 fullmatch)는 모든 것에 대해 값을 정화한다; 목적지를 증명하는 가드(호스트가 허용됨, 해석된 주소가 공개됨, 경로가 격리됨)는 관련 sink 계열에만 유효하다. bleach.clean도 마찬가지다. HTML을 무력화하지만 x; rm -rf /는 그대로 둔다.

네 개의 적대적 프로브가 테스트 스위트에 있으며 모두 올바르게 신호한다. 이 구멍 중 하나를 다시 여는 미래의 "개선"은 이름이 붙은 테스트를 깨뜨린다.

남은 침묵에는 두 가지 원인이 있고, thot bench는 그것들을 분리한다. 서로 다른 두 작업이기 때문이다:

règle muette — elle existe et ne matche jamais : elle a un motif à élargir
aucune règle pour la classe : il y en a une à écrire

그 구분이 목표의 순서를 결정한다 — 그것 없이는 침묵하는 범주들이 J = 0에서 완벽하게 동률이 되고 정렬은 알파벳 순서로 떨어진다.

병합, 그것이 무언가를 바꾸는 곳

Cascade.turn은 하나의 에이전트를 선택해서 호출한다; 첫 번째가 오류를 반환할 때만 다른 쪽을 찾으러 간다. 이런 턴은 구조상 둘 중 더 나은 쪽에 상한이 있다: 더 적게 잃을 수는 있어도 더 많이 얻을 수는 없다. agent_apply도 마찬가지였다 — 단수형의 엔진 하나.

thot evolve --fused는 둘 다 같은 문제의 서로 다른 절반에서 일하게 한다:

thot bench ~/.thot/bench                      # où ça fait mal
thot evolve --from-bench --fused \
      --corpus ~/.thot/bench --hold-out flask # et on répare, en boucle
  • Hermes는 측정값을 읽고 명세를 작성한다. 어떤 파일도 건드리지 않는다. 그 출력은 원인에 대한 주장이다: 어떤 규칙, 어떤 줄, 왜 그 케이스들인지.

  • Prime은 명세를 읽고 코드를 작성한다. 코드가 명세를 반박하면 거부할 수 있다고 명시적으로 말해진다 — 거절할 수 없는 실행자는 중계기일 뿐이고, 중계기는 아무것도 더하지 않는다.

  • 둘 중 누구도 결정하지 않는다. 테스트 스위트는 바닥이고, 라벨링된 코퍼스가 판정이다. 확신을 가지고 적용된 잘못된 명세는 점수를 낮추고 바이트 단위로 취소된다.

순서도 임의적이지 않다. 설계-후-구축은 접합부에서 검증된다: Prime은 Hermes의 추론을 보고 나서야 그것에 참여한다. 구축-후-재검토는 그것을 허용하지 않는다 — 두 번째가 볼 때 첫 번째는 이미 결정했다.

목표는 측정에서 오지, 타이핑된 문장에서 오지 않는다. 지금까지 루프는 인간이 이미 의심하던 것만 추구할 수 있었다; 점수에서 구축된 목표는 프로그램이 자신이 어디에 약한지 말하는 것이며, 그것이 선택하지 않은 숫자로 말한다 — 그리고 같은 숫자가 나중에 답이 도움이 되었는지도 말한다. 각 목표는 실패한 파일들을 담는다: "xss가 0%다"라고 들은 에이전트는 추측만 할 수 있고, 실패한 파일 세 개를 받은 에이전트는 풀 문제가 있다.

과적합, 그리고 --hold-out이 정말 할 수 있는 것

코퍼스로 채점되는 루프에는 단 하나의 진짜 속임수 방법이 있다: 코퍼스를 배우는 것. BenchmarkTest01126이 어떻게 생겼는지에 맞춰진 규칙은 점수를 올리고 아무에게도 도움이 되지 않으며, 외부에서 보면 진짜 진보와 구별할 수 없다.

--hold-out flask는 하나의 스위트를 주요 숫자에서 빼내어 두 번째 안전장치로 유지한다: 최적화된 스위트들을 움직이면서 한 번도 본 적 없는 스위트는 움직이지 않는 변경은 자신이 무엇인지 말한 것이다. 두 숫자 모두 ne_baisse_pas로 유지된다.

그 한계, 측정된 것: 세 프레임워크는 서로 반 점 차이로 채점된다. 그것은 파일 수준 과적합은 잡지만 벤치마크 형태에 대한 과적합은 잡지 못한다 — 생성된 코퍼스는 여전히 생성된 코퍼스이고, 데모 코드에서만 도움이 되는 규칙은 세 개를 모두 통과할 것이다. 홀드아웃은 속임수를 보이게 만든다; 코퍼스를 대표적으로 만들지는 않는다.

루프가 한 턴에서 다음 턴으로 유지하는 것은 ~/.thot/evolve-log.jsonl에 기록된다. 그것 없이는, 측정값이 한 턴에 거의 움직이지 않으므로, 다음 턴은 같은 최악의 범주를 다시 읽고, 같은 파일들을 다시 펼치고, 합리적으로, 이미 구축되고 측정되고 취소된 같은 명세를 받는다: --rounds 5는 다섯 번 시도된 시도, 다섯 배 더 비싸고, 바쁜 척하는 것이 될 것이다.

이것은 오라클이 아니다. 수정이 초록색일 수 있고, J를 올릴 수 있고, 여전히 나쁠 수 있다 — 그것이 과적합이고 자동 프로그램 수리에 관한 문헌은 그것만 말한다. 코퍼스는 그것에 대해 진보했다는 증거다. 루프는 인간이 동의하지 않을 수 있도록 무엇을 바꿨는지 보고한다.

Skills — Thot이 아는 방법

skill은 한 번 작성된 방법이다: YAML frontmatter가 있는 SKILL.md. 그것은 Hermes Agent와 Prime Agent의 형식이므로, 둘 중 하나를 위해 작성된 skill은 수정 없이 여기서 로드되고, 그 반대도 마찬가지다.

Thot은 Hermes Agent의 전체 라이브러리를 탑재한다 (MIT — NOTICE.md 참조): 90개의 로드된 방법, 117개가 더 사용 가능하다.

thot skills list              # les 91 chargées
thot skills search pentest    # y compris la bibliothèque optionnelle
thot skills install ast-grep  # activer une optionnelle
thot skills show plan         # ce que lirait le modèle

로드된 범주: audit, security, software-development, github, devops, research, mlops, productivity, creative, apple, email, media, note-taking, smart-home, social-media, autonomous-ai-agents.

모델은 skills 도구로 그것들을 발견한다 — 키워드를 주지 않으면 이름 색인으로 응답한다. 이백 개의 설명은 카탈로그가 아니기 때문이다 — 그리고 해당하는 것을 skill로 읽는다. 세션에서는 /skills가 같은 것을 보여준다.

여기에 없는 도구(delegate_task, browser_navigate…)를 인용하는 가져온 방법은 그대로 제공되며, 어떤 것이 없고 대신 무엇을 사용할지 말하는 메모와 함께 제공된다. 도구 호출이 이전되지 않아도 접근 방식은 이전된다.

추가하기

~/.thot/skills/<nom>/SKILL.md            # partout où tu travailles
<repo>/.thot/skills/<nom>/SKILL.md       # versionné avec ce dépôt
---
name: ma-méthode
description: Ce qu'elle fait et quand s'en servir.
---

# Ma méthode

Les étapes, dans l'ordre.

두 배치 모두 허용된다: 평면 폴더 (Prime Agent) 또는 범주별 그룹 (Hermes Agent). 이미 존재하는 이름은 내장 버전을 대체한다 — 포크하지 않고 제공된 방법을 적응시키는 방법이다.

감사된 저장소가 제공한 방법은 먼저 분석된다

SKILL.md는 지시문으로서 모델에 전달되는 텍스트다. Thot이 읽는 저장소는 정의상 아무도 응답하지 않는 저장소다. 적대적 저장소가 .thot/skills/x/SKILL.md를 배치하면 브리핑의 일부를 작성하게 될 것이다.

Hermes Agent의 가드가 여기로 이식되었고 저장소에서 오는 모든 것에 적용된다: 주입, 유출, 지속성, 난독화.

   ▲ 1 skill(s) fourni(s) par ce dépôt ont été refusés — ils seraient passés
     au modèle comme instructions.
     pwn   curl vers l'extérieur ; accès à ~/.thot ; « ignore previous
           instructions »

thot skills scan <dossier>는 요청 시 같은 질문을 던진다. Thot이 스스로 제공하는 것은 분석되지 않는다: 프로그램이 설치되었기 때문에 디스크에 있는 것이지, 저장소가 요청했기 때문에 있는 것이 아니다.

사용자 정의 명령

마크다운 파일은 명령이다. 문법은 Prime Agent, Claude Code, Codex의 것과 같다 — 배울 새로운 것이 없다.

---
description: Relire un fichier sans rien modifier.
argument-hint: <chemin>
---

Relis $1 et dis-moi ce qui cloche. Ne modifie rien.

.thot/commands/revue.md에서 이것은 /revue src/app.py를 만든다. 치환: $1, $2…, $@, $ARGUMENTS, ${@:2}, ${@:2:3}. 인자는 결코 재해석되지 않는다. 저장소의 명령은 그 skills와 같은 가드를 통과한다.

세 개가 제공된다: /triage (입력을 명명하거나 후속 조치 없이 분류), /harden (먼저 실패하는 테스트, 그다음 수정), /regress (git 참조에 대한 diff 감사).

MCP 서버

Hermes Agent의 카탈로그, 검증된 스무 개 서버:

thot mcp list            # le catalogue, et ce qui est déjà connecté
thot mcp show sentry
thot mcp add linear

설치는 공식 CLI에 위임되며, 이미 OAuth와 토큰 갱신을 보유하고 있다 — Thot이 누출시킬 두 번째 금고를 보유할 이유가 없다. 등록됨이 승인됨이 아니라는 것과, 어떤 명령이 작업을 끝내는지 명시적으로 말한다.

모델의 도구

고전적인 것들 — 읽기, 쓰기, 편집, 명령 실행. 모든 쓰기와 모든 실행은 확인을 요구하며, 이것은 구성할 수 없다.

그리고 Thot에만 속한 네 가지, 무료 — 지도에 질문하고 모델에 질문하지 않기 때문이다:

도구

응답

code_map

프로젝트의 파일들

find_symbol

함수의 파일, 줄, 매개변수

callers

누가 무엇을 호출하는지, 그리고 진입점까지의 거리

audit

소스 → sink 색조 경로

계정 모드에서 이 네 가지는 thot.mcp_server에 의해 공식 CLI에 제공된다 — 쓰기나 실행이 불가능한 읽기 전용 MCP 서버.

모델이 process_payment를 누가 호출하는지 찾을 때, 그래프에 질문하고 완전한 답을 얻는다 — 무작위로 세 파일을 grep하는 대신.

감사 전용 모드

분석 코어는 모델 없이, 네트워크 없이, 비용 없이도 사용된다:

thot init /chemin/du/repo --owner "Ton Nom"   # autorisation, une fois
thot audit /chemin/du/repo --paths            # chemins de teinte complets
thot audit . --all                            # y compris le bruit faible
thot audit . --json --out rapport.json
thot audit . --out rapport.sarif              # SARIF 2.1, format déduit du nom
thot audit . --fail-on high                   # code 1 en CI

SARIF — 이미 존재하는 체인에 들어가기

어떤 파이프라인도 읽을 수 없는 보고서는 단일 터미널에서만 산다. GitHub code scanning, GitLab, Azure DevOps, 편집기들은 모두 SARIF 2.1을 읽으며, Thot의 두 속성이 다른 곳보다 여기서 더 가치가 있다.

finding의 정체성은 규칙 + 파일 + 심볼 + 본문 지문이지 결코 줄 번호가 아니다 — 이것이 정확히 partialFingerprints가 요구하는 것이다. 줄 번호로 채워진 대시보드는 누군가 파일 상단에 import를 추가하는 순간 모든 티켓을 다시 연다; 이것으로 채워지면 그렇지 않다.

그리고 색조 경로는 위치의 연속이며, codeFlows가 렌더링하는 것이다: 독자는 도구를 믿는 대신 소스에서 sink로 클릭한다. 경로가 없는 finding은 codeFlows 키를 담지 않는다 — 빈 흐름은 단계 없는 색조 경로로 렌더링되며, 패턴 일치가 아니라 깨진 분석으로 읽힌다.

패널에 의해 반박된 finding은 삭제되지 않는다: 근거가 있는 suppressions와 함께 간다. 그것을 결코 보지 못하는 대시보드는 "아무도 보지 않았다"와 "누군가 보고 판단했다"를 구별할 수 없으며, 두 번째가 패널의 존재 이유 전체다.

- run: thot audit . --out thot.sarif
- uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: thot.sarif }

지원 분석 — --deep

결정적 분석은 "이 데이터가 흐를 수 있을까?"라는 질문에 답한다. 그것은 철저하고, 무료이며, 감사관에게 판단을 맡기는 비용을 지불하는 질문이 아니다. --deep은 그 비싼 질문을, 자격을 증명한 후보들에 대해서만 던진다:

thot audit . --deep                  # 20 pires candidats, 4 en parallèle
thot audit . --deep --budget 50      # plus large
thot audit . --deep --parallel 8     # plus vite

의도적으로 적대적인 두 번의 패스:

  1. 탐침(sonde) 은 위험 지점에 도달하는 구체적인 입력을 명명해야 한다. 취약점 클래스에 대한 일반론이 아니라 — URL, 값, 효과.

  2. 반박(refutation) 은 이 시나리오를 받아 오직 파괴하는 임무만을 수행한다: 사전 검증, 상수만 전달하는 호출자, 가정된 입력을 금지하는 타입. 의심스러우면, 반박한다.

발견 항목은 동일한 코드에 대한 두 번째 적대적 읽기가 그것을 죽이는 데 실패할 때만 살아남는다. confirmed는 그때 비로소 의미를 가진다.

세션에서도 동일하다: /audit deep.

엔진은 자동으로 선택된다 — 공식 CLI를 통해 연결된 Claude 계정(분석은 구독으로 병렬 실행됨), 그 외에는 API 키.

감사가 읽지 말아야 할 것

# .thotignore, à la racine du dépôt
vendor/
*.generated.py
tests/fixtures/

내장 제외 항목은 모든 저장소가 가진 것 — node_modules, build, .venv — 을 커버한다. .thotignore는 이 저장소만 아는 것을 커버한다: 포함된 문서, 생성된 클라이언트, 일부러 깨뜨린 픽스처 폴더. 그것들을 감사하는 것은 발견 항목을 만들지 않고, 발견 항목이 있을 바로 그 자리에 노이즈를 만든다.

자체 규칙

내장 카탈로그는 표준 라이브러리를 알고 있다. 팀이 subprocess 주위에 작성한 래퍼, 서비스가 소비하는 큐, 우리 환경에서 값을 안전하게 만드는 검증기는 알지 못한다. 그것을 말할 곳이 없으면, 실제 시스템에 대한 모든 감사는 같은 세 군데에서 틀린다.

# <repo>/.thot/rules/team.yaml   — versionné avec le code
# ~/.thot/rules/*.yaml           — ce que tu sais, partout où tu travailles
sinks:
  - id: sink.team.run_shell
    patterns: [run_shell, shellutil.run_shell]
    impact: critical
    description: Wrapper shell interne (shell=True)
    match_mode: bare          # qualified | method | bare | prefix

sources:
  - id: source.queue
    patterns: [msg.payload]
    description: File de messages
    match_mode: prefix        # couvre msg.payload.decode(...)

sanitizers: [validate_host, team.escape]

동일한 파일이 js: 키 아래 JavaScript 규칙을 담는다 — 팀 래퍼는 일반적으로 두 언어 모두에 존재하며, 선언을 분리하는 것이 절반이 구식이 되는 방식이다.

js:
  sinks:
    - id: sink.js.team
      names: [runShell, sh]     # comparés au dernier segment, ou qualifiés
      impact: critical
      description: Notre wrapper shell
      needs: [child_process]    # ne se déclenche que si le fichier l'importe
  sources:
    - id: source.js.queue
      patterns: [job.payload]
      description: File de messages
  sanitizers: [escapeArg]

모델이 요청하는 것은 신뢰할 수 없는 입력이다

소스는 표현식이다 — sys.argv, os.environ. 이것은 실행하는 프로그램은 커버하지만 호출하는 프로그램은 놓친다: 에이전트의 도구는 신뢰할 수 없는 입력을 명명된 매개변수로 받으며, 이는 모델이 요청한 것을 레지스트리가 채우고, 본문 어디에도 표현식이 나타나지 않는다.

이것을 모델링하지 않았을 때 측정된 비용: 오후에 SSRF 4건, 모두 도구 인자로 도달했으며, 틴트(teinte)로는 하나도 발견되지 않았다 — 형태를 인식할 뿐 아무것도 증명하지 못하는 패턴 규칙으로 발견되었다.

entry_sources:
  - id: entry.tool
    patterns: [tools.image_gen]     # les fonctions qu'un registre appelle
    parameters: [args]              # facultatif : lesquels de leurs paramètres
    description: Arguments remplis par le modèle
    match_mode: prefix

기본적으로 비어 있고, 의도적으로 그렇다: 레지스트리가 호출하는 함수는 저장소에 대한 사실이며, 추측하면 모든 프로그램의 모든 매개변수 아래에 소스를 놓게 된다. Hermes에서 측정된 양극단: plugins와 tools 패키지를 명명하는 규칙은 19개의 입증된 경로를 드러내며 그중 여러 개는 과대근사다(헬퍼가 설정에서 받는 base_url은 신뢰할 수 없는 것이 아니다); args 매개변수를 명명하는 규칙은 0개를 드러내는데, Hermes의 핸들러는 딕셔너리가 아닌 명명된 매개변수를 받기 때문이다. 올바른 규칙은 실제 진입점을 명명한다 — 그리고 그것을 아는 것은 작성자들의 몫이다.

내장 id를 재사용하는 규칙은 그것을 대체한다 — 팀이 의도적으로 수용한 싱크를 Thot에 패치 없이 저하시킬 수 있다. 잘못된 형식의 파일은 발견 항목이 없다고 믿게 하는 대신, 파일과 잘못된 키를 명명하며 감사를 중단한다.

억제(suppressions)

억제는 어떤 도구도 — 구성상 이 도구조차 — 재검토하지 않는 유일한 보안 주장이다. # nosec, # noqa: S310, // eslint-disable … security/…: 코드에 대한 주장으로, 한 번 작성되어, 그것이 설명하던 호출자들보다 오래 살아남는다.

여기, 같은 감사에서 두 번, 그것은 거짓이었다:

억제

그것이 주장한 것

실제로 사실이었던 것

# nosec B310 — scheme checked above

스킴이 통제된다

그것은 file://만 막았고 그 외에는 아무것도 막지 않았다 — 메타데이터 서비스로의 SSRF

# noqa: S310 (configured peers)

URL이 설정에서 온다

호출자 중 하나가 도구 인자에서 읽는다, 즉 모델에서

따라서 Thot은 그것들을 클래스로, LOW로, 옆에 패턴을 적어 보고한다. 발견 항목은 "이 줄이 위험하다"고 말하지 않는다: "아무도 그것이 면제된 이유를 재검토하지 않았다"고 말한다. --deep 패스에서는, 패턴이 여전히 유효한지 확인할 에이전트가 있다.

Python의 경우, 패턴이 아닌 실제 주석 토큰이 읽힌다 — 정규 표현식은 주석의 # nosec와 docstring에 인용된 동일한 텍스트를 구분하지 못하며, 이 모듈의 docstring은 두 개를 인용한다.

이 감사가 신고하는 줄에 놓인 억제는 같은 객체가 아니다: 그것은 살아있는 발견 항목과 모순되는 주장이며, 같은 줄을 읽고 다르게 결론을 내린 사람이 작성했다. 그것은 한 단계 올라가서 그렇게 말한다. Hermes에서 측정: 45개 중 7개 — 그리고 그날 읽은 억제 중 세 개는 거짓이었다.

측정: Thot에서 0, Prime에서 0, Hermes에서 45.

보정(Calibration)

정밀도는 탐지만큼 중요하다. 의도적으로 보고되지 않는 것:

  • shell=True 없는 subprocess.run(cmd) — 어떤 셸도 명령을 읽지 않는다.

  • cursor.execute("… ?", params) — 리터럴 쿼리, 바인딩된 매개변수.

  • int(), shlex.quote(), os.path.basename(), html.escape()를 통과한 값 — 이러한 호출은 오염 체인을 끊는다.

  • 어떤 진입점도 도달하지 않는 결함은 자동으로 등급이 낮아진다 — 그러나 진입점이 발견된 경우에만. 아무것도 없으면 범위는 알 수 없음이지 없음이 아니며, 그 무지 위에 아무것도 묻히지 않는다.

  • payload.get(...)은 requests.get(...)이 아니다.

Hermes Agent(파이썬 파일 4,457개)에서 측정된 규모: 98초, 발견 항목 365개 중 high 25개 — 메모리가 적용된 후 기본 임계값 위는 3개.

인자가 어떤 매개변수를 채우는가

하나의 매개변수가 싱크에 도달하는 헬퍼는 그것에 전달되는 모든 것을 위험하게 만들지 않는다. 그런데 엔진은 인자가 어디에 도착하는지 보지 않고 호출자를 callee의 모든 싱크 집합과 짝지었다:

def helper(safe, cmd):
    os.system(cmd)          # seul `cmd` atteint le shell

helper(sys.argv[1], "ls")   # la donnée va dans `safe` — et c'était rapporté

위치는 이제 호출 지점에서 읽히고, 명명된 인자는 이름으로 읽힌다. JavaScript 엔진이 이미 자기 쪽에서 하던 일이다.

실행이 가능해진 후 측정: Thot과 hermes/tools를 분석하며 요청된 83,238개의 해석 중 90%가 단일 매개변수를 지목하고 9.8%는 아무것도 지목하지 않는다; 0.1%는 열린 채로 남아(f(*rest), f(**options)) 넓은 답을 유지한다. callee는 평균 2.68개의 매개변수를 제공했으므로, 검색 공간의 거의 3분의 2가 사라진다 — hermes/tools 분석이 11.4초에서 9.5초로 줄어든다.

발견 항목 수에 관해서는, 반대로: 변화 없음. Thot, prime, hermes/tools는 전후에 정확히 동일한 151개의 후보를 산출하며, 그중 발명된 것은 0개 — 안전 속성은 현장에서 유지된다. 네 개의 틴트 경로가 짧아질 뿐이다. 위의 수정된 형태는 실제이며 테스트가 증명하지만, 측정된 세 트리 중 어느 것에서도 발생하지 않는다: 이 수정은 속도와 더 정확한 경로를 사는 것이지, 더 적은 노이즈를 사는 것이 아니다.

두 형태는 아무것도 말하지 않는다: helper(*args)는 알 수 없는 수의 값을 펼치고, helper(**options)는 어떤 것도 명명하지 않는다. 그 경우 엔진은 이전에 주던 넓은 답을 유지한다 — 유지되는 집합은 항상 이전 것의 부분집합이므로, 정제는 발견 항목을 제거할 수만 있고 결코 발명하지 않는다.

수신자는 호출 구문이 아닌 callee의 시그니처에 근거하여 건너뛴다: Runner().go(x)는 점 없는 go로 가고, Cls.m(obj, x)와 obj.m(x)는 둘 다 속성이다. 메서드는 거의 항상 바인딩되어 호출되므로, 매개변수의 첫머리 self가 결정한다 — 비바인딩 호출만 한 단계 짧게 읽히는 유일한 형태다.

그래프가 따라갈 수 없는 것

분석이 해석하지 못하는 경로로 도달한 결함 — 디스패치 테이블에 정리된 핸들러, 데코레이트된 뷰, 타입을 알 수 없는 변수에 대한 호출 — 은 도달 불가능한 결함이 아니다. Thot은 둘을 구분한다:

HANDLERS = {"run": run_command}     # aucun appel : le graphe ne voit rien
@app.route("/ping")                 # enregistré à l'import par le décorateur
sandbox.run("pytest")               # plusieurs `run` répondent à ce nom

세 경우 모두 범위는 알 수 없음이지 없음이 아니며, 발견 항목은 묻히는 대신 가벼운 페널티를 유지한다. Hermes에서: 동일한 365개 발견 항목이지만 60개가 한 단계 올라간다. 아무도 호출하지 않고 아무도 언급하지 않는 함수는 올바르게 등급이 낮아진 채로 남는다 — 그렇지 않으면 필터가 필터이기를 멈출 것이다.

한계

파일 간 틴트는 Python 전용이다: JavaScript와 TypeScript는 인덱싱되고, 함수 본문과 같은 파일의 헬퍼까지 추적되며, 해석된 호출 그래프가 없어 거기서 멈춘다 — 위 표가 줄별로 말해준다. 패턴 규칙은 모든 곳에 적용된다.

+55.5%는 Python 수치다. BenchProctor는 django, fastapi, flask를 라벨링하며 그 외에는 없다; JavaScript 카탈로그는 그 대응물보다 좁다 — "데이터베이스 읽기" 소스도, 로깅 싱크도 없으며 — 그 규칙은 18,300개의 라벨링된 사례보다 자체 테스트에 의존한다. 수정이 두 엔진 모두에 유효하면 둘 다에 적용되며, 로그는 어디서 무엇이 측정되었는지 말한다.

sink.log는 메시지와 그것만 읽는다. logger.info('failed: %s', exc)는 값을 작성자가 입력한 문자열에 쓰는 대신 로깅 메커니즘에 맡긴다; 그 인자들까지 읽는 것은 코퍼스에서 +0.0000이고 hermes/에서 발견 항목 4,844개를 더 만든다 — 트리의 except … as exc:마다 하나씩. 그것은 커버되지 않으며, 말하는 것이 믿게 두는 것보다 싸다.

--deep 없이, 각 발견 항목은 PLAUSIBLE이다: 정적으로 탐지되었지만 아직 실행으로 입증되지 않았다. --deep으로, confirmed 발견 항목은 적대적 반박을 견뎌냈다 — 그것은 아직 실행 증명이 아니며, repro와 함께 올 것이다. 그리고 발견 항목의 부재는 결함 부재의 증명이 아니다: 동적 디스패치, 리플렉션, 메타프로그래밍은 분석을 벗어난다.

세션의 전체 텍스트 검색은 fts5가 rowid를 올릴 수 없는 SQLite에서 일정 비용을 멈춘다: 엔진은 20개에서 자르는 대신 모든 일치 항목을 정렬한다. CPython 3.12에 포함된 것으로 측정 — 코퍼스가 5배가 될 때 6,588개 명령어 대 27,443개, 반면 가능한 엔진은 2,215개에서 2,698개로 유지된다. 테스트가 이를 검증하고 버전이 허용하지 않을 때 버전을 명명한다.

개발

cd Thot
uv run pytest -q

편집 가능 모드로 설치: 소스 코드가 즉시 효력을 가진다. 반면 pyproject.toml이 변경되면(새 의존성), uv tool install --editable --from . thot --force를 다시 실행해야 한다.

결정적 커널(codemap, taint, scope, scoring, store, report)은 어떤 에이전트에도 의존하지 않고 네트워크에 닿지 않는다 — 테스트가 이를 검증하고 변경되면 스위트를 실패시킨다.

스펙과 계획: docs/superpowers/.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search code by meaning, explore codebase structure, store and query knowledge with temporal facts, and read source code through a set of MCP tools.
    386 npm
    7
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables parsing, indexing, and querying source code as structured knowledge, providing code exploration, spec generation, and migration tools via 20 MCP tools.
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides code intelligence by indexing source code into SQLite and offering MCP tools for symbol search, flow tracing, and context retrieval to assist with code navigation and understanding.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for persistent code exploration and diff-aware review, exposing explore and review tools to map call paths, blast radius, and generate explainable risk-scored reports.
    3 npm
    2
    MIT