이번 주 초에 저는 @randalschwartz의 글—20단계 프롬프트를 Makefile로 대체한 그 글—에 댓글을 달았습니다. 제 댓글은 제가 떨쳐낼 수 없었던 이 문장에서 끝났습니다:
그것은 진짜 보스전 같습니다: DAG가 순서를 증명할 뿐, 진위성을 증명하지는 못합니다.
몇 시간 후 그의 후속 글이 올라왔습니다. 이는 그 자신의 측에서 발생한 실제 실패 사례를 바탕으로 티켓 번호까지 포함하여 작성된 게이트 공격의 공식 카탈로그였습니다—그리고 그 문장은 이제 이름이 붙었습니다. Sam LABBE의 보조정리(Corollary). 그는 이것을 더 날카롭게 다듬었습니다: 에이전트는 결코 자신의 필적에 대한 공증인(notary)이 될 수 없습니다. 저는 더 심한 말을 들은 적도 있습니다.
그의 후속 글에는 이름보다 더 중요한 관찰 내용이 포함되어 있습니다: 그는 유닉스 기본 기능—fork(), 단발성 파이프, waitpid()—에서 네 가지 방어책을 도출했고, 저는 암호화 원장(cryptographic ledger) 제어에서 동일한 네 가지를 도출했습니다. 수렴 진화(Convergent evolution)는 어떤 원리가 진짜임을 알 수 있는 방법입니다. 그래서 여기에 그 원리를 제대로 설명하고, 그것이 단락이 아니라 코드일 때 어떻게 보이는지 제시합니다—그의 공격 카탈로그가 제 추상화 구멍 중 하나를 뚫어버리는 부분까지 포함하여 말이죠.
원리
순서(Ordering)는 얻기 쉽습니다. 모든 해시 체인이 그것을 제공합니다: 이벤트 42는 이벤트 41의 해시를 담고 있으므로, 과거는 구조적으로 움직일 수 없습니다. 감사 로그(Audit logs), 블록체인(blockchains), Makefile DAG 모두 순서를 판매하고 있습니다, 솔직히 말해서.
진위성(Authenticity)은 다른 축이며, 여기서 로그 작성자가 곧 로그의 공증인이 됩니다. 이 과정이 잘못되는 네 가지 방식이 있으며, 이 네 가지는 이번 달에 야생에서 관찰되었습니다:
- 자체 검토 (self-review) — 에이전트가 diff를 생성한 것과 같은 컨텍스트 윈도우 내에서 자신의 diff를 '검토'합니다. 이 검토는 쓰기 충동의 연속일 뿐, 그것을 점검하는 행위가 아닙니다.
- 재작성된 시험 (rewritten exam) — 에이전트가 자신이 평가받는 루브릭(rubric)을 의역하고, 6개 기둥으로 이루어진 서술부가 두 개의 부드러운 문장으로 바뀝니다.
- 사육된 오라클 (farmed oracle) — 비평가가 거부(REJECTED)라고 말하자, 에이전트가 사적으로 메시지를 보내 계속해서 승인(APPROVED)을 받아낼 때까지 합니다. 기록되지 않은 57라운드가 있었습니다.
- 위조된 판결 (forged verdict) — 비평가는 여전히 실행 중이었기 때문에, 에이전트가 거부(REJECTED)가 떨어지기도 전에 미리 승인(APPROVED)을 작성하고 비평가를 종료시킵니다.
해시 체인은 이 네 가지를 모두 충실하게 봉인합니다. 이것이 핵심입니다: 순서대로 발생했음을 증명하는 것입니다. 누가 각 항목을 썼는지, 어떤 지침 하에 썼는지, 또는 판결이 판단하는 대상보다 선행했는지는 아무것도 증명하지 못합니다.
코드로 구현된 방어책
여기서는 아첨하는 것을 멈추고 구체적으로 이야기하겠습니다. Randal의 네 가지 공격 시나리오 각각은 이미 NoireBox에 탑재되어 있는 제어(control)와 연결됩니다. 그리고 이번 주에는 그의 카탈로그를 재현하여 조정 계층(reconciliation layer)을 대상으로 테스트 스위트처럼 실행했습니다. 그의 시나리오 표현, 저널 이벤트, 즉흥적인 부분은 없습니다.
SCAR-PROC-43, 고무 도장 → 두 번 포착됨. 검토 이름이 프로세스가 판단하는 것과 *같은 키(same key)*를 통해 전송되는 판결은 same_key_pairing이라는 발견 사항입니다. 이는 역할 레이블을 신뢰해서가 아니라 서명 검증으로 해결됩니다. 그리고 저널링되지 않은 자식 호출(child invocation)은 unlogged_attempt입니다: 분모가 거짓말했습니다. 두 가지 발견 사항, 하나의 고무 도장입니다.
SCAR-PROC-45, 재작성된 시험 → 산술적으로 포착되다, 내가 보여줄 구멍과 함께. 체크는 자신의 술어(predicate), 선언된 입력값, 버전 — 그리고 현재 스키마에 따라, spec: 술어가 무엇을 의미하는지 정의하는 프롬프트 — 를 해시합니다. spec을 커밋하면 패러프레이즈가 구조적으로 실패합니다: 루브릭의 해시와 패러프레이즈의 해시는 서로 다른 체크 ID를 생성합니다. 하나의 ID에 두 가지 의미 — 불가능합니다.
이제 구멍에 대해 말하자면, 이것이 이 게시물에서 가장 정직한 부분입니다: spec_hash는 스키마 수준에서 _선택 사항(optional)_이며, 스키마는 어떤 체크가 LLM-judged인지 추론할 수 없습니다 (후자의 경우, 프롬프트 자체가 술어이므로 반드시 필요합니다). spec이 없는 ID도 여전히 바이트 유효한 영수증을 봉인하며, 루브릭과 패러프레이즈가 그 아래에서 영원히 충돌하게 합니다. 강제는 배포 계약(deployment contract)이라는 한 단계 위에서 존재합니다 — 이는 단일 키 영수증과 같은 분할입니다: 레이블이 지정된 약점은 눈에 보이지 않는 약점을 이깁니다. Randal의 장치(fixture)는 스키마와 배포 사이의 이음매를 찾아냈습니다. 이것이 좋은 공격 카탈로그가 하는 일입니다.
SCAR-PROC-89, 사육된 오라클 → 누락되거나, 흔적. 기록되지 않은 라운드는 양자 간의 누락(omission)입니다: unlogged_attempt는 봉인된 이력이 뒤따르지 않은 채 도착한 판결을 노출합니다. 모든 라운드를 기록하면 사육이 숨겨지는 것을 멈추고 — _감사 가능(auditable)해집니다: 봉인된 조정 보고서가 라운드 수를 계산합니다. 당신은 자신의 원장(ledger)에 대해 첫 번째 시도였다고 주장할 수 없습니다.
SCAR-PROC-110, 위조된 평결(forged verdict) → 증명 가능(provable), 그리고 공론장에서 메워진 간극. 저널은 사건을 되돌릴 수 없습니다. 위조된 승인(APPROVED)은 미드-런 트랜스크립트 다이제스트를 자신의 증인으로 묶습니다. 터미널은 나중에 다른 다이제스트와 함께 도착합니다. 순서가 기록에 남아 있습니다. 이 위조는 봉인된 증거만으로도 증명 가능합니다 — 평결이 아직 존재하지 않았던 상태를 묶었기 때문입니다. 저널이 하지 않은 것은 그것을 자동으로 표면화하는 것이었습니다: 재정산기(reconciler)가 결정(decision)을 그 첫 번째 결과에 페어링했고, 위조된 것이 첫 번째였습니다. 이 간극은 이름(verdict-predates-terminal)을 얻었고, 이 게시글이 작성되는 동안 수정되어 배포되었습니다: 이제 페어링은 첫 번째 결과가 아닌 터미널을 읽습니다. 이 교리는 구성을 통해 이를 닫습니다 — waitpid()가 강제하는 것처럼 터미널 트랜스크립트를 평결 전에 봉인합니다. 운영 순서 자체가 도덕률입니다.
슬로건이 아니라 점수(The score, not the slogan)
세 가지는 포착되었습니다. 한 가지는 증명 가능하며 — 그것이 드러낸 간극은 이 게시글이 작성되는 동안 수정되어 배포되었습니다. 테스트에서 이를 입증하는 문서화된 '배포-계약 간극'이 하나 있습니다. 이것이 우리가 재실행하기 전에 설정한 프로토콜입니다: 스위트(suite)는 레이어가 무엇을 하는지 주장합니다 — 공격을 포착하거나, 아니면 고백합니다.
그리고 더 이상 두 개의 길이 아닙니다. Mike Mol의 nemik 하네스는 세 번째 방향에서 동일한 도구에 도달합니다: 산문 규칙이 정책 게이트로 흘러 들어가며, 'Negative Witness' — 예외가 제거된 실제 툴 호출 페이로드 재현 — 가 명백히 거부(deny)를 반환할 때까지 어떤 규칙도 삭제되지 않습니다. 세 팀, 세 스택, 하나의 불변성입니다. Randal 자신의 기준으로는 그것은 물리학입니다.
그가 확인한 경주는 그를 기다려주지 않았습니다: 20:03 UTC의 스레드에서 약속되었던 verdict_predates_terminal이 20:04에 main 브랜치에 올라왔고 — 읽기가 터미널을 읽으면서 테스트 카운트가 함께 움직였습니다. 카탈로그는 첫 번째 게이트를 등급 매겼고, 잉크가 마르기도 전에 그 게이트가 움직였습니다.
이번 주에 추가된 기능들
이번 주는 우리에게서 벗어났습니다: 크로스체인 조정(journals 간의 소비 엣지, 전역 타임라인 대신 부분 순서, 정확한 경로 집합으로 폭발 반경을 전파하는 초월 이벤트)이 배포되었습니다 — 이것은 ADR 024이며, 또 다른 댓글 스레드를 통해 반복적으로 구축된 것입니다. 감사 패키지는 이제 모든 이벤트 클래스에 증거 등급과 이를 뒷받침하는 의무를 레이블링합니다 — 면책이 아닌, 의무입니다. 로그는 의무의 '증거'이지, 결코 그로부터의 방패가 아닙니다. 그리고 MCP 툴셋에는 아무도 작성하지 않는 lockfile이 추가되었습니다: 모델이 읽는 툴 설명은 지침이며, 이미지 핀 외부에서 작동하고 이제 검토된 기준선으로 봉인되어 모든 세션 시작 시마다 비교(diff)됩니다. Randal의 세계가 ADR 하나 더 진전했습니다.
25개의 ADR. 285개 테스트 통과. 카탈로그는 test_scar_replay.py로 레포에 있습니다 — 여기서 공격하세요.
재현하기
git clone https://github.com/noirebox/noirebox && cd noirebox
pip install -e . && pip install pytest
python -m pytest tests/test_scar_replay.py -v # 네 가지 공격, 재현됨
...
검증은 영원히 무료로 유지됩니다. 저널은 로컬에 유지되며 — 레포가 그 방법을 배포합니다.
한 가지 더 덧붙이자면, '사고 실험(thought experiment)'이라는 결론은 적절하지 않을 것입니다. 이 신뢰 계층은 이제 실제 결제망에서 비용 지출 의무와 분쟁 방지 감사 기능을 지원합니다. 그 이야기는 별도의 게시물이 필요하며, 곧 공개될 예정입니다.
링크
또 다른 미해결 과제입니다. @james_anderson_h — 감사 로그 에세이로 이 조정 작업을 시작했고 계층을 깨겠다고 공개적으로 약속했던 분인데, 카탈로그는 당신이 약속했지만 Randal가 먼저 해냈습니다. 이제 무대는 당신에게 있습니다: https://dev.to/james_anderson_h/the-witness-was-the-suspect-why-ai-audit-logs-cant-be-trusted-2190