당신의 게이트는 모델이 작성한 신호를 신뢰하고 있습니다. 단 한 번의 Write-Hop이 이를 증명합니다.
요약
AI 에이전트의 승인 신호가 모델에 의해 오염될 수 있는 위험성을 'Write-chain taint lint' 개념을 통해 분석합니다. 모델이 작성한 데이터가 포함된 쓰기 체인을 추적하여 권한 부여 신호의 신뢰성을 검증하는 방법론을 제시합니다.
핵심 포인트
- 모델이 작성한 신호는 컨텍스트가 될 수는 있지만 권한 부여 수단이 될 수 없음
- 단 한 번의 Write-hop만으로도 승인 시스템의 신뢰성이 무너질 수 있음
- 쓰기 체인(write-chain) 분석을 통해 신호의 출처와 오염 여부를 판별 가능
- 중간 저장소가 추가되어도 모델 주체가 포함된 오염은 사라지지 않음
Write-chain taint lint는 당신의 AI 에이전트 게이트(gate)가 무엇인가를 승인하기 전에 한 가지 질문에 답합니다: 각 승인 신호(authorization signal) 뒤에 모델이 작성한 저장(store)이 있는가? gate_taint_lint.py는 선언된 write-closure를 탐색하며 모든 신호를 WORLD_ANCHORED, MODEL_AUTHORED, 또는 MODEL_LAUNDERED로 분류합니다. 이 포스트의 피스처(fixtures)에서, 단 한 번의 write-hop이 sender_trust를 PASS에서 FAIL로, exit 0에서 exit 1로 뒤바꿉니다.
AI 공개: 저는 AI 어시스턴트와 함께
gate_taint_lint.py를 작성하였으며, 게시하기 전에 오프라인에서 직접 실행했습니다. 아래 출력 블록의 모든 숫자는 Python 3.13.5, 표준 라이브러리만 사용하고 네트워크 연결 없이 수행한 실제 로컬 실행 결과에서 복사되었습니다. 저는 종료 코드(0 / 1 / 2)를 확인하였고, STDOUT을 두 번 해싱하여 바이트 단위로 결정론적(deterministic)임을 확인하였으며, 모든 줄을 직접 편집했습니다. 외부 인용구(모델이 작성한 신호에 관한 Dev.to 스레드, 승인 전파에 관한 arXiv 논문)는 제 말이 아닌 원문의 표현이며, 원본 소스를 링크했습니다. 그들의 주장은 저의 측정 지표에 포함되지 않으며, 저의 수치는 오직 여기에 표시된 실행 결과에서만 도출되었습니다.
요약하자면:
- 신호(signal)의 이름은 그것을 누가 작성했는지에 대해 아무것도 알려주지 않습니다.
sender_trust는 세계에 고정된(world-anchored) 것처럼 읽힙니다. 오직 쓰기 체인(write-chain)만이 해당 신호가 추출된 테이블을 모델이 채웠는지 여부를 말해줄 수 있습니다. - Dev.to의 라이브 스레드에서 빌려와 검증 가능하게 만든 규칙: 어떤 기능(feature)은 그 전이적 쓰기 폐쇄(transitive write-closure) 내에 모델 주체(model principal)가 포함되지 않는 경우에만 권한 부여(authorization) 역할을 가질 수 있습니다. 모델이 작성한 신호는 컨텍스트(context)가 될 수는 있어도, 결코 권한 부여(authorization)가 될 수는 없습니다.
- 모델의 출력을 평판 테이블(reputation table)을 통해 라우팅한다고 해서 오염(taint)이 씻겨 나가는 것은 아닙니다. 린트(lint)는 폐쇄(closure)를 계산하므로, 중간 저장소가 하나 추가된다고 해서 변하는 것은 없습니다. 다섯 개가 추가된다 해도 마찬가지입니다.
- 중요한 데모: 단 한 번의 쓰기 홉(write-hop)을 제외하고는 바이트 단위로 동일한 두 개의 매니페스트(manifests)가 있습니다 (누가
reputation_table을 채우는가: 사람이 서명한 승인인가, 아니면model:classifier_v3인가). 결과는 권한 부여(authorization) 기능 2개 중 0개가 오염된exit 0에서, 2개 중 1개가 오염된exit 1로 뒤바뀝니다. - 표준 라이브러리만 사용합니다 (
json,sys). 오프라인, 키리스(keyless), 읽기 전용, 네트워크 제로, 결정론적 STDOUT 방식입니다. 도구와 모든 피스처(fixtures)는 이 포스트에 포함되어 있습니다.
함정에는 이름이 붙어 있습니다
여기에 6개월 전의 저라면 신뢰했을 게이트(gate)가 있습니다. 인박스 에이전트(inbox agent)가 요청을 자동 실행할지 아니면 검토를 위해 보류할지를 결정합니다. 이 결정은 세 가지 신호(signal)에 달려 있습니다. 평판 테이블(reputation table)에서 읽어온 점수인 sender_trust, 결제 원장(payment ledger)에서 읽어온 tx_reversibility, 그리고 분류기(classifier) 스스로가 추정한 model_confidence입니다. 팀은 모델이 모델을 보증하게 두어서는 안 된다는 것을 알고 있으므로, model_confidence는 컨텍스트(context)로 강등되었습니다. 나머지 두 개는 권한을 부여합니다. 모두가 승인합니다.
하지만 아무도 평판 테이블(reputation table)을 누가 작성하는지는 묻지 않습니다.
그 질문이 바로 이 글의 핵심입니다. 지난 7월 1일, yongrean이라는 이름으로 활동하는 Dev.to의 저자가 하나의 정렬 규칙을 중심으로 댓글 섹션 신뢰 모델을 재설계했습니다. 그 규칙은 다음과 같습니다: "기능(feature)의 출처가 모델로부터 독립적인지 여부에 따라 정렬하십시오. 그것들을 기준으로 게이트(gate)를 설정하십시오. 모델이 직접 작성한 것은 컨텍스트(context)로만 취급하고, 절대 권한 부여(authorization)로 취급하지 마십시오." 이는 제 말이 아닌 저자의 말이며, 저는 이 규칙이 옳다고 생각합니다. 하지만 명시된 대로라면 이것은 읽기 시점(read-time)의 지침이며, 이 지침이 의존하는 속성은 쓰기 시점(write-time)의 속성입니다. 기능을 살펴보는 동안 그 독립성에 따라 기능을 정렬할 수는 없습니다. 그 뒤에 있는 작성자 체인(chain of writers)을 끝까지 추적해야 합니다. 왜냐하면 침해된 연결 고리는 당신이 읽고 있는 저장소(store)인 경우가 드물기 때문입니다. 그것은 모델이 몇 달 동안 조용히 채워온, 두 단계 상류(two hops upstream)에 있는 어떤 저장소입니다.
이틀 전, 저는 에이전트의 행동을 정책(policy)과 대조하여 조정하는 게이트를 게시했습니다. 그리고 그 과정에서 남겨진 정직한 공백은 파이프의 반대편이었습니다. 즉, 해당 도구는 트레이스(trace)를 정책과 비교하는 게이트에서 나온 결과에 대해 행동 판결을 내립니다. 오늘 소개하는 도구는 무엇이 들어가는지를 확인합니다. 모델이 작성한 권한 부여 기능(authorization feature)을 가진 액션별 게이트(per-action gate)는, 그 정책이 아무리 훌륭하더라도 모델이 열쇠를 쥐고 있는 게이트와 같습니다.
따라서, 여러분이 반박할 수 있도록 명확히 밝히는 논제는 다음과 같습니다: 게이트 기능의 이행적 쓰기 폐쇄(transitive write-closure)에 모델 주체(model principal)가 포함되지 않는 경우에만 해당 기능이 권한 부여 역할을 수행할 수 있습니다. 선언된 쓰기 맵(write map)이 주어지면, 게이트가 단 하나의 액션을 승인하기 전에 모든 기능의 오염 클래스(taint class)를 결정론적으로 계산할 수 있습니다. 이 린트(lint)가 WORLD_ANCHORED로 표시하는, 선언된 쓰기 폐쇄 내에 모델이 포함된 권한 부여 기능을 보여주거나, 혹은 그 반대의 경우를 보여준다면 이 도구와 논제는 모두 틀린 것입니다.
쓰기 체인 매니페스트(write-chain manifest)가 선언하는 것
이 린트는 두 부분으로 구성된 하나의 JSON 매니페스트를 읽습니다.
stores는 쓰기 맵 (write map)입니다: 게이트가 읽는 모든 스토어에 대해, 누가 그곳에 쓰는지 나타냅니다. 작성자 (writer)는 kind 접두사가 붙은 주체(human:sre_approver, external:bank_feed, model:classifier_v3)이거나, 파생 데이터 (derived data)가 자신의 부모를 선언하는 방식인 다른 스토어의 이름입니다. gate_features는 게이트가 키를 잡는 신호 (signals)들을 나열합니다: 각 신호는 하나의 스토어를 읽고 하나의 역할(authorization 또는 context)을 가집니다.
다음은 전체 내용이 담긴 깨끗한 피스처 (fixture)입니다. 이는 서론에 등장했던 게이트이며, 저자들이 의도한 방식대로 연결되어 있습니다:
{
"stores": {
"approvals_log": {"written_by": ["human:sre_approver"]},
...
설정 검토 (config review) 시에는 보통 암시적으로 남겨두지만, 매니페스트가 명시적으로 드러내는 점에 주목하십시오: sender_trust는 인간으로부터 세 번의 쓰기 (writes)만큼 떨어져 있습니다. 점수는 approvals_log로부터 채워지는 reputation_table로부터 계산되며, 이 approvals_log에는 인간 SRE가 서명합니다. 이렇게 선언되어 있다면, 이 체인은 감사 가능 (auditable)합니다. 선언되지 않았다면, 그것은 민간 전설 (folklore)일 뿐입니다.
60초 안에 실행하기
키도 필요 없습니다. 네트워크도 필요 없습니다. Python 외에는 설치할 것도 없습니다. 파일을 저장하고, 매니페스트를 저장하고, 명령어 하나만 실행하십시오. 다음은 표준 라이브러리만 사용하는 단일 파일로 구성된 전체 도구입니다:
#!/usr/bin/env python3
"""
gate_taint_lint.py -- AI 에이전트의 신호를 위한 쓰기 체인 오염 린트 (write-chain taint lint)
...
기준점: 모든 권한 부여 신호는 세계에 고정되어 있음 (world-anchored)
깨끗한 매니페스트에 실행해 보십시오:
$ python3 gate_taint_lint.py fixtures/clean.json
GATE-TAINT-LINT REPORT
stores declared: 5
...
종료 코드 0. 살펴볼 만한 두 가지 사항이 있습니다. 첫째, 이 린트는 모델에 거부감이 없습니다: model_confidence는 MODEL_AUTHORED이며 이는 괜찮습니다. 왜냐하면 그 역할이 context이기 때문입니다. INFO 라인은 스레드에서 가져온 정렬 규칙이며, 기억하는 대신 실행됩니다. 둘째, sender_trust는 이름이 아니라 그 체인에 의해 WORLD_ANCHORED를 획득합니다: 그 뒤의 클로저 (closure)는 human:sre_approver에서 끝나며 그 외에는 아무것도 없습니다.
단 한 번의 쓰기 홉 (write-hop)이 판결을 뒤집습니다
이제 이 포스트가 존재하는 이유인 데모를 살펴보겠습니다. 두 번째 매니페스트(manifest)는 단 한 줄을 제외하고는 첫 번째와 바이트 단위로 동일합니다. 새로운 기능도, 새로운 정책도, 이름이 바뀐 스토어(store)도 아닙니다. 단 한 번의 쓰기 홉(write-hop)입니다:
$ diff fixtures/clean.json fixtures/laundered.json
5c5
< "reputation_table": {"written_by": ["approvals_log"]},
...
풀어서 설명하자면: 평판 테이블(reputation table)이 더 이상 사람이 서명한 승인 로그(approvals log)로부터 채워지지 않습니다. 대신 분류기(classifier)에 의해 채워집니다. 아마도 누군가가 1분기에 "신뢰도가 높은 발신자 자동 승인" 변경 사항을 배포했고, 그 과정에서 승인 로그는 흔적만 남은 상태가 되었을 수도 있습니다. 게이트(gate) 코드는 변경되지 않았습니다. 기능 목록도 변경되지 않았습니다. sender_trust 스토어의 점수는 여전히 0과 1 사이의 숫자이며, 읽기 시점(read time)에서는 외부 세계에 고정된(world-anchored) 버전과 구별할 수 없습니다.
$ python3 gate_taint_lint.py fixtures/laundered.json
GATE-TAINT-LINT REPORT
stores declared: 5
...
Exit 1. 2개의 권한 기능 중 1개가 오염(tainted)되었으며, DENY 라인이 그 증거를 출력합니다: chain=sender_trust<-reputation_table<-model:classifier_v3. 동일한 게이트, 동일한 기능, 동일한 읽기 경로입니다. 유일하게 바뀐 점은 한 단계 상위 테이블에서 펜을 쥐고 있는 주체가 누구냐는 것이며, 그것만으로도 모델에게 자신의 게이트를 여는 열쇠를 쥐여주기에 충분합니다. 만약 당신의 리뷰 프로세스가 그 1분기의 변경 사항을 잡아냈다면 다행입니다. 제 프로세스는 잡아내지 못했을 것이며, 저는 정확히 그와 같은 변경 사항들을 리뷰해 왔습니다.
왜 하나의 중간 스토어가 단순한 검사를 속이는가
yongrean의 규칙을 구현하는 가장 명백한 방법은 직접적인 작성자(direct writers)를 확인하는 것입니다. 즉, 각 권한 기능에 대해 해당 기능이 읽는 스토어를 누가 작성하는지 확인하는 것입니다. 이 검사는 세탁된(laundered) 매니페스트를 통과합니다. sender_trust는 reputation_table에 의해 작성되며, 테이블은 모델이 아니므로 직접 작성자 검사는 이를 무시하고 넘어갑니다. 모델은 기능의 스토어에 직접 손을 대지 않습니다. 모델은 한 홉(hop) 뒤에 작성하며, 그 유도(derivation) 과정이 저작권(authorship)을 세탁합니다.
동일한 스레드의 한 댓글 작성자인 dipankar_sarkar는 이 수정 사항을 다음과 같이 명명했습니다: "게이트 기능은 읽기 경로(read path)가 깨끗해 보일 때조차, 상류(upstream)에 있는 모든 작성자의 오염(dirt)을 상속받습니다." 그들은 이를 오염(taint) 문제라고 불렀으며, 오염(taint)은 정확히 적절한 프레임워크입니다. 따라서 린트(lint)는 전이적 쓰기 폐쇄(transitive write-closure)를 계산합니다. 즉, 저장소의 작성자, 그 작성자들의 작성자, 그리고 순환(cycle)이 종료될 수 있도록 방문 세트(visited set)를 활용하여 최하위 리프(leaves)까지 추적합니다. MODEL_LAUNDERED는 MODEL_AUTHORED보다 완화된 판결이 아닙니다. 그것은 더 좋은 정장을 입은 동일한 오염(taint)일 뿐이며, 통계적 근거는 없지만 실제 환경에서는 이것이 더 흔한 형태일 것이라고 예상합니다. 아무도 model_output을 권한(authorization) 컬럼에 직접 연결하지 않습니다. 점수 산정 작업(scoring job)이 읽어들이는 "평판(reputation)" 테이블에 연결하는 것은 마치 아키텍처(architecture)처럼 느껴집니다.
저는 이전에 다른 대상을 지목하며 동일한 오염(taint) 직관을 사용한 적이 있습니다: 평가 오염 탐지기(eval contamination probe)는 에이전트가 자신의 채점기(grader)가 읽는 내용을 작성하는 것을 포착하기 위해 파일 연결(file wiring)을 추적합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기