Live Mentions 딥다이브 · 2026-06-24

aflock 딥다이브
— AI 에이전트에게 "여기까지만 해"라고 못 박고, 그걸 어겼는지 암호로 증명하는 디지털 계약서

aflock-ai/aflock. package-lock.json이 의존성 버전을 잠그듯, .aflock 파일은 "이 에이전트가 무엇을 할 수 있는지"를 잠급니다. 요즘 AI 에이전트는 스스로 명령을 실행하고, API를 호출하고, 파일을 고치고, 심지어 하위 에이전트(sub-agent)까지 만들어 냅니다. aflock은 사람이 암호로 서명한 정책 파일로 에이전트의 행동에 울타리(지출 한도·도구 제한·파일 접근 범위)를 치고, 에이전트가 그 울타리 안에서만 움직였다는 증거(서명된 증언, attestation)를 행동마다 남깁니다. 핵심은 에이전트가 자기 정체성을 속일 수 없고, 서명 키에는 절대 손댈 수 없다는 점입니다. Claude Code의 훅(hook) 시스템에 끼워 넣는 Go 플러그인이며, 공급망 보안 회사 TestifySec이 만들었습니다. (저장소: aflock-ai/aflock · Go · ★251 · Apache-2.0)

목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 (정책 한 줄이 서명된 증언이 되기까지)
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

"AI 직원에게 회사 법인카드를 쥐여 주되, 한도·사용처를 적은 계약서에 사장이 직접 도장을 찍고, 모든 결제 영수증에 위조 불가 도장을 남기는 시스템."

한 줄로

aflock은 AI 에이전트의 행동을 제약하는 '암호로 서명된 정책 파일(.aflock)'이고, 동시에 그 정책을 강제하고 행동마다 위조 불가능한 증거를 남기는 Claude Code 플러그인입니다.

AI 에이전트는 점점 더 자율적으로 움직입니다. 명령을 실행하고, API를 부르고, 파일을 고치죠. 문제는 네 가지입니다 — ① 제약(무엇까지 허용할까), ② 증명(범위 안에서 움직였음을 어떻게 보일까), ③ 검증(나중에 사후 확인이 가능한가), ④ 위임(하위 에이전트에게 더 빡빡한 제약을 물려줄 수 있나). aflock은 이 넷을 '사람이 서명한 정책 + 서명된 증언(attestation)'으로 한 번에 풉니다.

핵심 특징 셋: ① 정책은 사람이 암호 서명(에이전트가 자기 족쇄를 못 푼다), ② 정체성은 부여가 아니라 유도(에이전트의 설정·실행 환경으로부터 신분을 계산 → 거짓말 불가), ③ JWT와 서명 키의 분리(에이전트는 신분증은 받지만 도장은 절대 못 만진다).

용어
AI 에이전트 (AI Agent)
사람의 지시를 받아 스스로 여러 단계의 작업을 수행하는 AI입니다. 단순히 답만 적어 주는 챗봇과 달리, 터미널 명령을 실행하고 파일을 읽고 쓰며 외부 API를 호출합니다(예: Claude Code). 권한이 큰 만큼 "통제되지 않으면 위험한 직원"이라, 울타리가 필요합니다.
용어
정책 (Policy) · .aflock 파일
에이전트가 지켜야 할 규칙을 적은 JSON 설정 파일입니다. "한 세션에 $10까지만 쓴다", "Bash·Read 도구만 허용한다", ".env 파일은 절대 못 읽는다" 같은 한도·허가·제약을 담습니다. package-lock.json이 라이브러리 버전을 못 바꾸게 못 박듯, .aflock에이전트가 할 수 있는 일을 못 박습니다.
용어
증언 / 증명 (Attestation)
"이런 일이, 이때, 이 정책 아래에서 일어났다"를 적고 암호 키로 서명한 기록입니다. 도장이 찍힌 공증 영수증이라고 보면 됩니다. 서명이 있으니 나중에 위조 없이 검증할 수 있고, 누가 봐도 "이 도장은 진짜다"를 수학적으로 확인할 수 있습니다.

이 프로젝트의 진짜 코드는 모두 Go 언어cmd/internal/ 아래에 있습니다. aflock이라는 명령줄 도구 하나가 Claude Code의 훅(hook)에 연결되어, 도구를 쓰기 전(PreToolUse)에 정책을 검사하고, 쓴 뒤(PostToolUse)에 증언을 남기며, 세션이 끝날 때 전체가 규칙을 지켰는지 검증(verify)합니다. README가 "현재는 비공개 개발·명세 단계(Specification phase)"라고 밝히듯 아직 초기 단계지만, 그 안에 담긴 보안 설계는 실무에서 검증된 표준들(SPIFFE/SPIRE·in-toto·OPA)을 조합한 견고한 교본입니다.

이 문서가 파고드는 건 "어떻게 설치하나"가 아니라 그 안의 보안 설계입니다 — "에이전트가 어떻게 자기 신분을 못 속이게 만드는가", "왜 신분증(JWT)과 도장(서명 키)을 굳이 분리하는가", "하위 에이전트에게 권한을 어떻게 더 좁혀서 물려주는가", "행동 순서를 빠뜨리거나 뒤바꾸지 않았음을 머클 트리로 어떻게 증명하는가". AI 에이전트 보안과 공급망 신뢰(supply-chain trust)를 동시에 공부하려는 사람에게 밀도 높은 실전 교본입니다.

2왜 주목받는가

"신뢰하지 말고 검증하라"를 AI 에이전트에 처음으로 제대로 적용한, 검증된 보안 표준 위에 세운 설계.

'AI 에이전트 가드레일'을 표방하는 도구는 점점 늘고 있지만, aflock이 라이브 멘션을 타고 주목받은 이유는 대부분이 "프롬프트로 부탁하는" 수준에 머무를 때, 이쪽은 암호학으로 강제하고 증명하기 때문입니다. 다섯 가지로 정리하면:

① "프롬프트 부탁"이 아니라 암호학적 강제

흔한 가드레일은 시스템 프롬프트에 "위험한 일은 하지 마"라고 적는 식입니다. 하지만 그건 에이전트가 마음만 먹으면 무시할 수 있는 부탁입니다. aflock은 Claude Code 을 가로채 도구 실행 자체를 코드 레벨에서 차단(deny)하고, 정책 파일은 사람이 서명해서 에이전트가 못 고치게 만듭니다.

② 에이전트가 신분을 속일 수 없다 (PID 내성검사)

보통 시스템은 "내가 GPT야"라고 스스로 주장하는 값을 믿습니다. aflock은 반대로, MCP 소켓에서 운영체제 PID(프로세스 번호)를 얻어 그 프로세스를 직접 들여다봅니다(UID·컨테이너 ID·실행 파일 경로). 신분은 주장이 아니라 설정으로부터 계산되는 값이라, 거짓말이 원천 봉쇄됩니다.

③ 검증된 보안 표준의 조합 (바퀴를 새로 만들지 않음)

가장 신뢰가 가는 대목입니다. 정체성은 SPIFFE/SPIRE(클라우드 워크로드 신분 표준), 증언은 in-toto / DSSE(소프트웨어 공급망 보안 표준)와 Rookery(witness 프로젝트의 보안 강화 포크), 정책 평가는 OPA/Rego(쿠버네티스 업계 표준 정책 엔진)에 기댑니다. "검증된 것에 맡기고, 에이전트에 맞는 접착제만 새로 짠다"는 철학입니다.

④ 하위 에이전트까지 재귀적으로 통제 (sublayouts)

에이전트가 하위 에이전트를 만들 때, 자식의 권한은 부모보다 반드시 더 좁아야 하고, 자식이 쓴 비용은 부모 총액에 합산됩니다. 검증은 하위 계층으로 재귀적으로 파고듭니다. in-toto의 sublayout 개념을 에이전트 위임에 그대로 적용한 흔치 않은 구현입니다.

⑤ 적대적 테스트가 풍부 (공격자 시점으로 자기 검증)

저장소에는 handler_adversarial_test.go·handler_forgery_test.go·matcher_security_test.go·fuzz_test.go 같은 "내 시스템을 어떻게 뚫을까"를 검증하는 테스트가 잔뜩 있습니다(증언 위조·신분 충돌·경로 탈출·Bash 우회 등). 보안 도구가 스스로를 적대적으로 공격해 보는 자세는 드문 신뢰 신호입니다.

다른 접근과의 비교

비교 대상성격aflock과의 차이
시스템 프롬프트 가드레일"하지 마"라고 글로 부탁aflock은 훅에서 코드로 차단 + 정책을 사람이 서명해 못 고치게 한다. 부탁이 아니라 강제.
컨테이너 샌드박스OS 수준 격리(권한 박스)샌드박스는 "무엇이 가능한가"만 막는다. aflock은 그 위에 지출·도구·데이터 흐름 정책 + 위조 불가 증언을 더한다.
in-toto / witness소프트웨어 공급망 증언 프레임워크aflock이 그 위에 선다(Rookery 경유). 대상이 빌드 단계가 아니라 에이전트 실행이라는 점이 새롭다.
여기서 배울 점
"신뢰하지 말고 검증하라(Zero Trust)"를 에이전트에 적용한 모범

전통 보안의 큰 흐름은 "안에 있으니 믿는다"에서 "매 요청마다 신분을 확인한다"로 옮겨 왔습니다(제로 트러스트). aflock은 이 원칙을 AI 에이전트에 그대로 가져옵니다 — 에이전트의 말을 믿지 않고, 프로세스를 직접 검사하고, 행동마다 서명으로 증명을 남긴다. "강력한 자율 시스템일수록, 신뢰는 주장이 아니라 검증에서 나와야 한다"는 설계 철학을 코드로 보여 줍니다.

솔직한 주의점 — 공부 전에 알고 가기
1. 아직 "명세 단계"의 초기 프로젝트다

README가 스스로 "Private development. Specification phase."라고 밝힙니다. 별 251개로 화제는 됐지만, '프로덕션에서 검증된 도구'가 아니라 '잘 설계된 참조 구현'으로 접근하는 게 정확합니다. 개념과 코드 패턴을 배우기엔 훌륭하지만, 그대로 운영에 올리긴 이릅니다.

2. 로컬 빌드는 형제 저장소(rookery)가 필요할 수 있다

go.mod 맨 끝에 replace ... => ../rookery/... 지시가 있습니다. 즉 일부 의존성을 상위 폴더에 나란히 받아 둔 rookery 저장소에서 끌어다 씁니다. 무작정 go build만 하면 막힐 수 있으니, 빌드를 따라 하려면 aflock-ai/rookery도 함께 받아 두세요.

3. 정책이 없으면 '전부 허용'이다 (opt-in 모델)

핵심 동작이 옵트인(opt-in)입니다 — .aflock 파일이 없으면 aflock은 아무것도 막지 않고 전부 허용합니다(코드 주석: "No policy file exists — opt-in model, allow everything"). 보호를 받으려면 반드시 정책 파일을 두어야 하며, 파일이 손상되면 반대로 닫아 버립니다(fail-closed). 이 비대칭을 알고 써야 합니다.

3기술 스택 전체 지도

단일 Go 바이너리 + 검증된 보안 라이브러리들. 웹 서버가 아니라 '정책 강제 엔진'이라 층 구분이 조금 다르다.

aflock은 화면이 있는 앱이 아니라 명령줄 도구이자 MCP 서버입니다. 그래서 백엔드/프론트엔드 대신 ① 진입·통합 층 ② 보안 코어(신분·서명·정책) ③ 인프라·배포로 나눠 보는 게 자연스럽습니다. 언어는 Go 1.26 단일 구성입니다(메모리 안전 + 단일 정적 바이너리 + 강력한 표준 암호 라이브러리라 보안 도구에 잘 맞습니다).

① 진입 · 통합 층

패키지 / 도구역할
spf13/cobraCLI 프레임워크 — aflock hook / verify / init 같은 하위 명령 구성
mark3labs/mcp-goMCP 서버/클라이언트 — 에이전트가 도구를 부를 때 aflock과 대화하는 통로
Claude Code Hooks8개 훅(SessionStart·PreToolUse·PostToolUse·Stop·SubagentStop·SessionEnd 등)에 연결되는 통합 지점
용어
MCP (Model Context Protocol)
AI 모델이 외부 도구·데이터와 표준 방식으로 대화하도록 정한 프로토콜(약속된 통신 규약)입니다. USB처럼 "어떤 도구든 같은 꽂이로 연결"하는 규격이라고 보면 됩니다. aflock은 MCP 서버가 되어, 에이전트가 연결해 올 때 그 소켓에서 운영체제 PID를 얻어 신분을 알아냅니다.
용어
훅 (Hook)
어떤 일이 일어나는 특정 순간에 자동으로 끼어들어 실행되는 외부 명령입니다. Claude Code는 "도구를 쓰기 직전(PreToolUse)", "쓴 직후(PostToolUse)", "세션 끝(SessionEnd)" 같은 순간마다 등록된 훅을 부릅니다. aflock은 이 틈을 파고들어 행동을 검사·차단하고 증언을 남깁니다.

② 보안 코어 (신분 · 서명 · 정책)

패키지역할
spiffe/go-spiffe/v2SPIFFE/SPIRE 워크로드 신분 — 에이전트에게 표준 ID(SPIFFE ID) 부여
aflock-ai/rookery/*증언 생성/서명의 핵심 — in-toto 증언, 환경·명령실행·git·산출물 어테스터(attestor)
signers/fulcio + sigstore키리스(keyless) 서명 — 미리 만든 키 없이 OIDC 신원으로 단기 인증서를 받아 서명
golang-jwt/jwt/v5JWT 발급/검증 — 에이전트에게 주는 단기 인가 토큰(ES256 고정)
open-policy-agent/opaRego 정책 엔진 — "지출이 한도를 넘으면 거부" 같은 규칙을 코드로 평가
transparency-dev/merkle머클 트리 — 행동 순서·누락 없음을 암호로 증명(세션 JSONL 정렬 증명)
gobwas/globglob 패턴 매칭 — src/**·**/.env 같은 파일/도구 패턴 검사
cyberphone/json-canonicalizationJSON 정규화(JCS) — 서명·해시 전에 JSON을 한 가지 표준 형태로 통일
용어
SPIFFE / SPIRE
서버·컨테이너 같은 '사람이 아닌 워크로드'에 신분증을 발급하는 업계 표준(SPIFFE)과 그 구현체(SPIRE)입니다. 사람에게 주민등록증이 있듯, 프로그램에게 SPIFFE ID(예: spiffe://aflock.ai/...)를 줍니다. aflock은 에이전트를 이 표준 위에 올려 "이 에이전트가 정확히 누구인가"를 검증합니다.
용어
키리스 서명 (Keyless Signing) · Fulcio
서명용 비밀 키를 미리 만들어 보관하지 않고, 서명할 때마다 OIDC 로그인(예: 구글 계정)으로 신원을 증명하면 몇 분만 유효한 임시 인증서를 받아 서명하는 방식입니다(Sigstore의 Fulcio). 키를 들고 있다가 유출될 위험 자체를 없애는 현대적 서명 기법입니다.
용어
머클 트리 (Merkle Tree)
데이터 조각들을 둘씩 짝지어 해시(지문)를 쌓아 올려 맨 위 하나의 '뿌리 해시'로 전체를 대표하게 만든 자료구조입니다. 중간에 한 글자만 바뀌어도 뿌리 해시가 통째로 달라져 변조가 즉시 드러납니다. aflock은 이걸로 "행동 3번이 0~2번 다음에 일어났고, 빠진 단계가 없다"는 순서·완전성을 증명합니다.

③ 인프라 · 배포

실제 시연 환경은 docker-compose.yml에 담겨 있습니다. SPIRE 서버 + SPIRE 에이전트 + aflock 테스트 워크로드 세 컨테이너가 뜨고, SPIRE 에이전트가 pid: "host"로 호스트 프로세스를 들여다볼 수 있게 설정됩니다(앞서 말한 PID 내성검사의 토대). 문서 사이트(docs-website/, Docusaurus)는 wrangler.toml을 통해 Cloudflare Pages로 배포되며(빌드는 GitHub Actions에서 수행), 빌드/테스트는 Makefile이 묶습니다(make build·make test·make lint). 코드 품질은 .golangci.yml의 엄격한 린트 규칙으로 강제합니다.

한눈에

cobra/mcp-go(에이전트와 만나는 입구) → identity/auth(신분 확인 + 신분증 발급) → policy/rego(규칙 판정) → attestation/merkle(도장 찍고 순서 증명) → verify(나중에 검증). "입구에서 신분 확인 → 규칙 적용 → 영수증에 도장 → 사후 감사"라는, 보안 검문소를 그대로 코드로 옮긴 구조입니다.

4아키텍처 심화 분석

정책 한 줄이 어떻게 "신분 확인 → 차단 결정 → 서명된 증언 → 사후 검증"으로 흐르는가.

전체 구조의 핵심은 "에이전트는 신분증(JWT)만 받고, 진짜 도장(서명 키)은 서버가 쥔다. 모든 행동은 훅을 거치며 정책으로 걸러지고, 통과한 행동에는 위조 불가능한 증언이 남는다"입니다. 먼저 숲(전체 그림)을 보고, 그다음 대표 흐름 한 줄기를 끝까지 따라가 보겠습니다.

독수리 시점 — 전체 그림 한 장

┌──────────────────────────┐ MCP ┌────────────────────────────┐ │ AI 에이전트 (Claude Code)│ <═══════════════> │ aflock 서버 / 훅 핸들러 │ │ │ (소켓 = PID) │ │ │ · 도구 호출(Read/Bash…) │ │ · PID 내성검사 → 신분유도 │ │ · JWT 신분증 소지 │ │ · 정책 강제 (allow/deny) │ │ · 서명 키는 절대 못 봄 │ │ · 증언 서명 (도장) │ └──────────────────────────┘ └──────────────┬─────────────┘ │ ┌──────────────────────────────────────────────────────┼───────────────┐ ▼ ▼ ▼ ▼ ┌────────────────┐ ┌──────────────────┐ ┌────────────────┐ ┌──────────────┐ │ identity/ │ │ policy/ + rego/ │ │ attestation/ │ │ state/ │ │ discover.go │ │ evaluator.go │ │ signer.go │ │ session.go │ │ agent.go │ │ matcher.go │ │ fulcio.go │ │ (세션 상태 │ │ spire.go │ │ bash_analyzer │ │ attestors.go │ │ + 파일 잠금)│ │ SHA256(model‖ │ │ glob 매칭 + │ │ in-toto/DSSE │ └──────┬───────┘ │ env‖tools‖정책│ │ Rego 규칙 평가 │ │ + Rookery 서명 │ │ │ ‖parent) │ └──────────────────┘ └────────────────┘ ▼ └───────┬────────┘ │ ┌──────────────┐ │ 신분 해시 │ 서명 │ merkle/ │ ▼ ▼ │ merkle.go │ ┌────────────────┐ ┌────────────────┐ │ 행동 순서· │ │ auth/jwt.go │ │ verify/ │ │ 누락없음 증명│ │ ES256 JWT 발급 │ ───── 신분증 ──────────────────> │ verifier.go │ └──────────────┘ │ (서명키 분리) │ │ keyless.go │ └────────────────┘ │ 사후 검증·재귀 │ └────────────────┘ [하위 에이전트] state/propagation.go — 부모 정책·한도를 자식에게 더 좁혀 물려줌(sublayout)

대표 흐름 한 줄기 — "에이전트가 Bash: rm config.json을 실행하려 할 때"

가장 흔한 동작 하나를 입력부터 결과까지 손으로 따라가 봅니다. 어느 패키지·파일이 어떤 순서로 일하는지에 집중하세요(핵심 단계만; 곁가지는 생략).

① [세션 시작] Claude Code → "aflock hook SessionStart" 호출 internal/hooks/handler.go · handleSessionStart() ├─ policy.Load(cwd) : .aflock 정책 파일을 찾아 읽음 (없으면 전부 허용) ├─ identity.DiscoverAgentIdentity() : 소켓 PID로 프로세스 내성검사 → 신분 유도 │ 신분해시 = SHA256(model ‖ env ‖ tools ‖ policyDigest ‖ parent) └─ auth.NewTokenIssuer().IssueToken(...) : 이 세션용 JWT 발급(서명키는 서버 보관) │ ▼ ② [도구 실행 직전] Claude Code → "aflock hook PreToolUse" 호출 handler.go · handlePreToolUse() 입력: {tool:"Bash", input:{command:"rm config.json"}} ├─ LockSession() : 동시 호출 경쟁(TOCTOU) 방지 파일 잠금 ├─ policy.NewEvaluator(pol).EvaluatePreToolUse("Bash", input) │ internal/policy/evaluator.go → matcher.go (glob) + bash_analyzer.go │ · "Bash" 도구가 allow 목록에 있나? → 예 │ · requireApproval 의 "Bash:rm *" 패턴에 걸리나? → 예! ⇒ ASK(승인 요구) │ · files.deny 의 "**/.env" 같은 위험 경로인가? → 아니오 └─ 결정 = ASK ⇒ output.PreToolUseAsk("승인 필요: rm 명령") ← 사람에게 확인 │ (사람이 승인) ▼ ③ [도구 실행 직후] Claude Code → "aflock hook PostToolUse" 호출 handler.go · handlePostToolUse() ├─ attestation.Signer.CreateAttestation(action) : in-toto 문장 작성 │ predicate = "https://aflock.ai/attestations/action/v0.1" ├─ Rookery + Fulcio(키리스)로 DSSE 서명 : 위조 불가 도장 └─ state.RecordAction() + merkle 갱신 : 행동을 순서대로 트리에 추가 │ ▼ ④ [세션 종료] Claude Code → "aflock hook SessionEnd" 호출 handler.go · handleSessionEnd() → internal/verify/verifier.go ├─ 누적 한도 검사(post-hoc): maxTurns 등 "끝에 확인" 항목 검증 ├─ merkle 뿌리 해시로 행동 순서·누락 없음 확인 └─ sublayout(하위 에이전트) 있으면 재귀 검증 + 자식 지출을 부모 총액에 합산

흐름을 한 문장으로 요약하면: "세션이 열리면 신분을 유도해 JWT를 쥐여 주고(①), 도구를 쓸 때마다 정책으로 거르고(②), 통과한 행동엔 서명 도장을 찍어 순서대로 쌓고(③), 끝에 전부 규칙을 지켰는지 검증한다(④)." 이 레포에서 'PreToolUse'는 막는 단계, 'PostToolUse'는 증거를 남기는 단계라는 한 쌍의 패턴을 알면 코드가 훨씬 잘 읽힙니다.

핵심 설계 패턴 5가지

① 신분 유도 (Derived, not Asserted Identity)

가장 근본적인 패턴입니다. 에이전트가 "내가 누구다"라고 주장하는 값을 믿지 않고, 설정·실행 환경에서 신분을 계산합니다. internal/identity/agent.goDeriveIdentity()는 모델·바이너리·환경·도구·정책 다이제스트·부모를 이어 붙여 SHA256 해시 하나로 만듭니다. 도구 순서가 바뀌어도 같은 해시가 나오도록 정렬하는 등(테스트 ToolOrderDoesNotMatter), "같은 에이전트면 항상 같은 신분, 다르면 반드시 다른 신분"을 보장합니다.

② JWT와 서명 키의 분리 (권한과 증명의 분리)

에이전트는 JWT(인가 토큰)는 받지만, 증언을 만드는 서명 키는 절대 못 만집니다. JWT로 "나 이거 해도 돼?"를 제시할 뿐, 도장은 항상 aflock 서버를 거쳐 찍힙니다. 신분증을 위조해도 공증 도장은 못 찍게 만든 셈입니다(§6에서 코드).

③ Fail-Open vs Fail-Closed의 의도적 비대칭

정책 파일이 없으면 전부 허용(opt-in, fail-open)하지만, 정책 파일이 있는데 깨졌으면 전부 거부합니다(fail-closed). "보호를 켜는 건 사용자 선택이되, 한번 켠 보호는 고장 시 안전한 쪽(차단)으로 닫힌다"는 보안 설계의 정석입니다. 신분 확인이 필요한 정책인데 SessionStart가 생략됐다면 역시 차단합니다.

④ 다층 정책 평가 (도구 → 권한 → 데이터 흐름)

handlePreToolUse는 한 번에 끝내지 않고 세 겹으로 거릅니다: ⓐ 도구/파일 접근(EvaluatePreToolUse) → ⓑ 자원 권한(EvaluateGrants: 비밀·API·저장소) → ⓒ 데이터 흐름(EvaluateDataFlow: 민감 데이터가 외부로 새는지). 한 겹이라도 거부면 거부입니다.

⑤ 하위 에이전트 위임 (Sublayouts + Propagation)

에이전트가 하위 에이전트를 만들면(isSubagentSpawn 감지), state/propagation.go부모의 자료(materials)와 한도를 자식에게 더 좁혀(attenuate) 전달합니다. "자식은 부모보다 빡빡해야 한다"는 규칙을 강제하고, 검증 때 자식 지출을 부모 총액에 합산합니다.

설계에서 배울 점
"권한을 주는 것"과 "그걸로 도장 찍는 것"을 분리하라

보안 시스템을 짤 때 가장 흔한 실수는 한 주체에게 권한과 증명 능력을 동시에 주는 것입니다. 그러면 그 주체가 뚫리는 순간 증거까지 위조됩니다. aflock처럼 에이전트에겐 단기 신분증(JWT)만 주고, 위조 불가능한 도장(서명 키)은 별도 주체가 쥐면, 에이전트가 탈취돼도 과거 증언은 위조할 수 없습니다. 이 "능력 분리"는 거의 모든 보안 설계에 적용되는 일급 원칙입니다.

5디렉토리 구조 해부

진짜 로직은 전부 internal/ 아래. Go의 관례상 internal/은 "이 모듈 안에서만 쓰는 비공개 코드"라는 뜻이다.

aflock/ ├── README.md 비전 + 정책 예시 + SPIRE 기반 아키텍처 설명 ├── go.mod / go.sum Go 1.26 · 의존성(spiffe·rookery·opa·jwt·merkle…) │ ⚠ 끝에 replace …=>../rookery 지시(형제 저장소 필요) ├── Makefile build·test·lint·test-hooks(수동 훅 점검) 묶음 ├── docker-compose.yml SPIRE 서버 + SPIRE 에이전트 + aflock 테스트 워크로드 ├── wrangler.toml docs-website → Cloudflare Pages 배포 설정 ├── LICENSE Apache-2.0 (Copyright 2025 TestifySec, Inc.) ├── SECURITY.md 취약점 신고: cole@testifysec.com │ ├── cmd/ │ ├── aflock/main.go ★ 진입점 — cobra CLI(hook/verify/init/serve) │ └── test-discovery/main.go 신분 유도(discovery) 수동 점검용 도구 │ ├── pkg/aflock/ │ └── types.go ★ 공개 타입 — Policy·HookInput·SessionState· │ LimitsPolicy·ToolsPolicy·ActionRecord(스키마의 뼈대) │ ├── internal/ ★★★ 실제 구현 (이 모듈 전용 비공개) │ ├── hooks/handler.go 훅 8종 디스패치(PreToolUse·PostToolUse…) — 심장부 │ │ + handler_adversarial/forgery/security_test.go ← 적대적 테스트 │ ├── identity/ 신분 유도 │ │ ├── agent.go DeriveIdentity() = SHA256(model‖env‖tools‖…) │ │ ├── discover.go PID 내성검사로 모델/환경 발견 │ │ └── spire.go SPIFFE/SPIRE 연동 │ ├── auth/jwt.go ES256 JWT 발급/검증(서명키 분리, 알고리즘 혼동 방지) │ ├── policy/ │ │ ├── policy.go .aflock 로드/파싱 + 정책 다이제스트 │ │ ├── evaluator.go 도구·권한·데이터흐름 평가(allow/deny/ask) │ │ ├── matcher.go glob 패턴 매칭 + 보안 테스트 │ │ └── bash_analyzer.go Bash 명령 분석(rm·git push 등 위험 탐지·우회 차단) │ ├── rego/evaluator.go OPA/Rego 규칙 평가(maxSpend 등 코드형 정책) │ ├── attestation/ │ │ ├── signer.go in-toto/DSSE 증언 생성·서명 │ │ ├── fulcio.go Fulcio 키리스 서명 │ │ └── attestors.go 환경·git·명령실행 어테스터 묶기(Rookery) │ ├── merkle/merkle.go 행동 순서·완전성 머클 증명 │ ├── state/ │ │ ├── session.go 세션 상태 저장 + 파일 잠금(동시성 안전) │ │ └── propagation.go 부모→자식 한도/자료 전파(sublayout) │ ├── verify/ 사후 검증 │ │ ├── verifier.go 정책 준수 검증(재귀: 하위 계층까지) │ │ └── keyless.go 키리스 인증서 체인 검증 │ ├── aieval/evaluator.go AI 평가자(프롬프트로 "production-ready인가" 판정) │ ├── replay/ 세션 JSONL 재생·시뮬레이션(읽기전용 안전 모드) │ ├── plan/ 계획(plan) → 정책 자동 생성 │ └── output/json.go 훅 출력(allow/deny/ask) JSON 직렬화 │ ├── plugin/ Claude Code 플러그인 패키지 │ ├── .claude-plugin/plugin.json 플러그인 메타(이름·버전·훅·스킬) │ ├── hooks/hooks.json 8개 훅 → "aflock --hook X" 매핑 │ ├── templates/default.aflock 기본 정책 템플릿 │ └── skills/plan-to-policy.md "계획을 정책으로" 스킬 안내 │ ├── examples/ 예시 정책(compliance-evaluation·todo-app-verification…) │ └── attestation-demo/ 실제로 돌려 보는 데모(run-demo.sh + Go 코드) ├── config/mcporter.json MCP 연동 설정 ├── docs/ + docs-website/ 명세서(specification.md) + Docusaurus 문서 사이트 ├── paper/aflock.tex/.pdf 설계를 정리한 학술 스타일 논문 └── test/ 통합 테스트(simulate-verify·simulate-all-phases)

읽는 순서를 추천하자면: pkg/aflock/types.go(데이터 모양 = 정책 스키마) → README.md의 정책 예시(무엇을 표현하는지) → internal/hooks/handler.go(전체 흐름의 심장) → internal/identity/agent.go + internal/auth/jwt.go(신분과 토큰) → internal/attestation/signer.go(서명) 순입니다. paper/aflock.pdf전체 설계를 글로 정리한 보너스 자료라 개념을 한 번에 잡기 좋습니다.

6학습 포인트 (기술별)

이 저장소의 진짜 가치는 "AI 에이전트 보안 + 공급망 신뢰 표준"을 한곳에서 코드로 볼 수 있다는 것.

학습 포인트 ①

정책을 데이터로 설계하는 법 — .aflock 스키마

먼저 "무엇을 표현할 수 있는가"를 봅니다. 정책은 사람이 읽고 서명하는 JSON이며, 한도(limits)·허가(grants)·도구(tools)·파일(files)·평가자(evaluators)·하위정책(sublayouts)으로 구성됩니다. 특히 한도마다 집행 방식(enforcement)을 따로 정하는 점이 인상적입니다:

{
  "limits": {
    "maxSpendUSD":  { "value": 10.00, "enforcement": "fail-fast" },  // 넘으면 즉시 중단
    "maxTurns":     { "value": 50,    "enforcement": "post-hoc"  }   // 끝에 확인
  },
  "tools": {
    "allow": ["Read", "Edit", "Bash", "Glob", "Grep"],
    "deny":  ["Task"],
    "requireApproval": ["Bash:rm -rf *", "Bash:git push --force"]    // 사람 승인 필요
  },
  "files": { "allow": ["src/**"], "deny": ["**/.env", "**/secrets/**"] }
}

"즉시 차단(fail-fast)"과 "사후 검증(post-hoc)"을 한도별로 나누는 발상은, "돈은 새기 전에 막고, 횟수는 끝에 세도 된다"는 실무 감각을 코드로 옮긴 좋은 예입니다.

학습 포인트 ②

신분을 '계산'하는 법 — internal/identity/agent.go

aflock의 가장 독창적인 부분입니다. 에이전트의 신분은 부여받는 게 아니라 구성요소를 이어 붙여 해시한 값입니다:

// 개념(README): 신분 = 구성요소들의 해시
AgentIdentity = SHA256(model ‖ environment ‖ tools ‖ policyDigest ‖ parent)
구성요소어디서 알아내나
Model--model 플래그 · 환경변수
EnvironmentPID 내성검사(UID·컨테이너 ID·바이너리 경로)
ToolsMCP가 보고하는 도구 목록
Policy작업 폴더의 .aflock 다이제스트
Parent(하위 에이전트면) 부모의 신분

도구 순서가 바뀌어도 같은 해시가 나오게 정렬하고(ToolOrderDoesNotMatter 테스트), 같은 입력이면 항상 같은 64자 해시(Deterministic 테스트)가 나오게 보장합니다. "같으면 같고, 다르면 반드시 다른" 해시 설계의 교본입니다.

참고: 저장소의 agent_audit_test.go는 이 방식의 약점도 스스로 지적합니다 — 구분자로 (파이프)를 쓰면 값 안에 파이프가 섞일 때 충돌 위험이 있어 "길이 접두사나 구조적 직렬화가 더 안전하다"고 적어 둡니다. 자기 설계를 적대적으로 점검하는 좋은 습관입니다.
학습 포인트 ③

JWT와 서명 키 분리 — internal/auth/jwt.go

에이전트는 JWT로 "나 이 권한 있어"를 제시할 뿐, 증언 서명은 못 합니다. 코드 주석이 설계 의도를 명확히 밝힙니다:

// 서명은 ECDSA P-256(ES256)만 사용 → "알고리즘 혼동(alg confusion)" 공격 차단
type AflockClaims struct {
    jwt.RegisteredClaims
    AgentID      string   // 누구인가
    IdentityHash string   // 신분 해시(②에서 계산한 값)
    AllowedTools []string // 허용 도구(정책에서 범위 좁힘)
    PolicyDigest string   // 이 정책에 한해서만 유효
}
// 발급 키는 메모리에만 살고 프로세스 종료 시 사라짐(ephemeral)

두 가지 보안 포인트: ① ES256 알고리즘 고정으로 "검증 알고리즘을 none으로 바꿔치기"하는 고전 JWT 공격을 막고, ② 토큰에 정책 다이제스트를 박아 "다른 정책엔 못 쓰게" 묶습니다. 신분증은 주되, 도장은 안 준다가 핵심입니다.

학습 포인트 ④

Bash 명령을 안전하게 검사하는 법 — internal/policy/bash_analyzer.go

도구 차단에서 가장 까다로운 게 Bash입니다. rm -rf *를 막아도 r''m -rf */bin/rm으로 우회할 수 있기 때문입니다. 그래서 단순 문자열 비교가 아니라 명령을 분석합니다. 저장소에 evaluator_bash_bypass_test.go·matcher_security_test.go·evaluator_path_traversal_test.go가 따로 있는 이유입니다:

// 개념: 위험 명령은 "패턴 매칭 + 정규화"로 우회까지 잡는다
" Bash: rm -rf *"      → requireApproval 매치  ⇒ ASK   // 승인 요구
" Read: src/.env"      → files.deny "**/.env"  ⇒ DENY  // 차단
" Read: ../../etc/pwd" → 경로 탈출(path traversal) 탐지 ⇒ DENY

"악의적 입력을 가정하고, 우회 경로까지 테스트로 막는다"는 보안 코딩의 실전 자세를 통째로 볼 수 있습니다.

학습 포인트 ⑤

Rego로 '코드형 정책' 쓰기 — internal/rego/evaluator.go

JSON으로 표현하기 힘든 복잡한 규칙은 OPA의 Rego 언어로 직접 적습니다. 예를 들어 "지출 합계가 한도를 넘으면 거부":

# Rego: deny 규칙(쿠버네티스 업계에서 검증된 정책 엔진)
deny contains msg if {
    sum_spend > input.limits.maxSpendUSD.value     # > 는 비교 연산
    msg := "spend limit exceeded"
}

정책을 데이터(JSON) + 코드(Rego) + AI 평가(aieval) 세 층으로 표현할 수 있게 한 점이 영리합니다. 간단한 건 JSON, 논리가 필요하면 Rego, 주관적 판단("이 코드가 production-ready인가?")은 AI 평가자에게 맡깁니다.

학습 포인트 ⑥

훅 핸들러 디스패치 — internal/hooks/handler.go

모든 흐름이 모이는 심장부입니다. Claude Code가 8개 훅을 부르면 하나의 Handle()이 받아 분기합니다. 입력은 stdin의 JSON, 출력도 stdout의 JSON이라는 단순한 계약입니다:

func (h *Handler) Handle(hookName string) error {
    data, _ := io.ReadAll(io.LimitReader(os.Stdin, maxStdinSize+1)) // OOM 방지(10MB 제한)
    var input aflock.HookInput
    json.Unmarshal(data, &input)
    switch aflock.HookEventName(hookName) {
    case aflock.HookPreToolUse:  return h.handlePreToolUse(&input)   // 막는 단계
    case aflock.HookPostToolUse: return h.handlePostToolUse(&input)  // 증거 남기는 단계
    // … SessionStart / Stop / SubagentStop / SessionEnd …
    }
}

"stdin으로 JSON 받아 stdout으로 JSON 뱉는다"는 단순 계약 덕에 어떤 호스트와도 느슨하게 연결되고, 입력 크기 제한 같은 방어 코드(/dev/urandom 공격 대비)도 자연스럽게 들어갑니다.

7시스템 요구사항

순수 소프트웨어라 특별한 하드웨어는 없다. 다만 빌드·실행에 필요한 환경이 조금 까다롭다.

항목요구사항 / 비고
언어/런타임Go 1.26 이상 (go.mod 기준). 단일 정적 바이너리로 빌드됨
형제 저장소aflock-ai/rookery를 상위 폴더(../rookery)에 함께 받아야 빌드 가능(go.modreplace 지시)
호스트 에이전트Claude Code — 훅 시스템을 통해 통합. 플러그인 형태로 설치
전체 시연(선택)Docker + docker-compose — SPIRE 서버·에이전트 + 테스트 워크로드 3컨테이너
PID 내성검사리눅스 컨테이너 환경 권장. SPIRE 에이전트가 pid:"host"로 프로세스 검사
키리스 서명(선택)OIDC 발급자(예: 구글 계정) — Fulcio로 임시 인증서 발급 시 필요
빌드 도구make(Makefile), golangci-lint(엄격한 린트), jq(test-hooks 시)
문서 사이트(선택)Node.js(Docusaurus 빌드) + Cloudflare(wrangler) — 문서 기여 시에만

가볍게 코드만 읽고 개념을 익히려면 아무것도 설치할 필요 없습니다(GitHub에서 읽으면 됨). aflock 바이너리를 직접 빌드해 훅을 시험해 보려면 Go와 rookery가 필요하고, "서명된 증언이 진짜로 만들어지는" 전체 그림을 보려면 Docker로 SPIRE까지 띄우는 게 좋습니다.

8직접 해볼 수 있는 실습 과제

읽기만 하면 금방 잊는다. 난이도순으로 손을 움직여 보자.

1. 정책 파일 한 장 직접 써 보기 난이도 ★☆☆ 입문

plugin/templates/default.aflock를 베껴, "내 프로젝트라면 어떤 규칙을 걸까"를 상상해 직접 한 장 작성해 보세요. tools.denyTask를 넣어 하위 에이전트 생성을 막고, files.deny**/*.key를 추가해 키 파일 접근을 차단해 봅니다. "무엇을 막고 무엇을 허용할지"를 데이터로 표현하는 감각을 잡는 게 목표입니다.

확장: cmd/aflock/main.goinit 명령이 만들어 주는 기본 템플릿과 내가 쓴 걸 비교해 보세요. 무엇이 빠졌나요?

2. 신분 해시 직접 계산해 보기 난이도 ★☆☆ 입문

아무 언어로든 SHA256("claude-opus" + "|" + "local" + "|" + "Read,Bash" + "|" + "정책해시")를 계산해 64자 16진수가 나오는지 확인해 보세요. 그런 다음 도구 목록의 순서만 바꿔 다시 계산해 봅니다. aflock은 왜 도구를 정렬해서 해시할까요? "같은 에이전트는 항상 같은 신분이어야 한다"는 요구를 직접 체감하는 과제입니다.

3. 빌드하고 훅을 수동으로 때려 보기 난이도 ★★☆ 중급

aflock-ai/rookery를 상위 폴더에 받은 뒤 make build로 빌드하고, Makefiletest-hooks 타깃처럼 JSON을 stdin으로 직접 흘려 결과를 봅니다. 예: Read src/main.go(허용)와 Read src/.env(차단), Task(차단)를 각각 넣어 allow/deny 출력이 어떻게 갈리는지 확인하세요. 훅이 stdin→stdout JSON 계약으로 동작한다는 걸 손으로 확인하는 게 핵심입니다.

4. 적대적 테스트 읽고 한 개 추가하기 난이도 ★★☆ 중급

internal/policy/evaluator_bash_bypass_test.gomatcher_security_test.go를 읽어 보세요. "rm -rf * 차단을 어떻게 우회하려 시도하고, 코드가 그걸 어떻게 막는지"를 따라간 뒤, 새로운 우회 시도 하나를 테스트로 추가해 봅니다(예: 탭/개행을 끼운 명령, 경로 변형). 통과하나요, 뚫리나요? 공격자 시점으로 생각하는 보안 마인드셋을 기르는 과제입니다.

5. Docker로 SPIRE까지 띄워 전체 그림 보기 난이도 ★★★ 고급

docker compose up으로 SPIRE 서버·에이전트·aflock 워크로드를 띄우고, examples/attestation-demo/run-demo.sh를 돌려 실제로 서명된 증언이 만들어지는 전 과정을 관찰하세요. examples/scripts/inspect-attestation.sh로 만들어진 증언을 들여다보고, aflock verify로 검증해 봅니다. "정책 → 행동 → 서명 → 검증"의 한 사이클을 눈으로 완주하는 게 목표입니다.

6. sublayout(하위 에이전트 위임) 추적하기 난이도 ★★★ 고급

internal/state/propagation.gointernal/hooks/handler_sublayout_test.go를 읽고, 부모의 한도가 자식에게 어떻게 더 좁혀져 전달되는지(attenuateLimits) 코드로 따라가 보세요. 부모가 $10, 자식이 $2를 받으면 부모의 남은 예산은 어떻게 계산될까요? "자식은 부모보다 빡빡해야 한다"는 규칙이 코드 어디서 강제되는지 찾아내는 게 과제입니다.

9관련 기술 심화 학습 로드맵

aflock을 제대로 소화하려면 '에이전트 보안 + 공급망 신뢰'의 토대를 함께 쌓는 게 좋다. 4주 코스 제안.

주차주제무엇을 / 어디서
1주차Go + 암호 기초Go 표준 crypto/sha256·crypto/ecdsa 실습. 해시·서명·검증의 차이 체득. A Tour of Go + aflock identity/auth 패키지 정독
2주차JWT와 토큰 보안JWT 구조(헤더·페이로드·서명), alg=none·알고리즘 혼동 공격. jwt.io로 토큰 뜯어보기 → aflock auth/jwt.go 비교
3주차공급망 신뢰: in-toto·SLSA·Sigstorein-toto 증언·DSSE 봉투, Sigstore/Fulcio 키리스 서명, SLSA 레벨. 공식 문서 + aflock attestation 패키지
4주차워크로드 신분 + 정책엔진SPIFFE/SPIRE 개념과 데모, OPA/Rego 튜토리얼(Rego Playground). aflock rego/identity/spire.go로 연결
보너스AI 에이전트 위협 모델프롬프트 인젝션·과잉 권한·도구 오용 등 에이전트 특유의 위협. aflock paper/aflock.pdf + 적대적 테스트들 정독

10핵심 키워드 사전

문서·코드에서 자주 만나는 용어를 한 번에. 모르는 단어가 나오면 여기로.

용어뜻 (한 줄)
Attestation (증언)"이런 일이 일어났다"를 적고 암호 키로 서명한 위조 불가 기록(공증 영수증)
Policy / .aflock에이전트가 지켜야 할 한도·허가·제약을 적은 JSON 파일(사람이 서명)
JWTJSON Web Token. 서명된 단기 신분증·인가 토큰. aflock은 ES256만 사용
Signing Key (서명 키)증언에 도장을 찍는 비밀 키. 에이전트는 절대 접근 못 함(서버가 보관)
MCPModel Context Protocol. AI와 외부 도구를 잇는 표준 통신 규약
Hook (훅)특정 순간(도구 실행 전/후 등)에 자동 실행되는 외부 명령
SPIFFE / SPIRE프로그램(워크로드)에 표준 신분증(SPIFFE ID)을 발급하는 표준/구현체
Identity Derivation신분을 부여받지 않고 구성요소(모델·환경·도구·정책)로 계산해 내는 것
Keyless Signing비밀 키 보관 없이 OIDC 신원으로 임시 인증서를 받아 서명(Sigstore/Fulcio)
in-toto / DSSE소프트웨어 공급망 증언 표준 / 그 증언을 담는 서명 봉투 형식
Rookeryaflock이 쓰는 증언 프레임워크. witness의 보안 강화 포크(TestifySec)
OPA / Rego정책을 코드로 쓰는 엔진(OPA)과 그 전용 언어(Rego). 쿠버네티스 업계 표준
Merkle Tree해시를 쌓아 뿌리 하나로 전체를 대표 → 순서·완전성·변조여부 증명
Sublayout하위 에이전트용 더 좁은 정책. 자식은 부모보다 빡빡해야 함(in-toto 개념)
Materials증언이 다루는 입력물(세션 JSONL·git 트리 등). 순서/완전성 증명의 대상
fail-fast / post-hoc한도 집행 방식 — 즉시 중단 / 끝에 검증
Zero Trust"안에 있어도 믿지 않고 매번 검증"하는 보안 원칙. aflock의 바탕 철학
PID Introspection프로세스 번호로 그 프로그램을 직접 들여다봄(UID·컨테이너·경로) → 신분 확인
JCSJSON Canonicalization. 서명/해시 전 JSON을 한 가지 표준 형태로 통일

11참고 링크

원문과 함께 보면 좋은 표준·문서들.