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)
"AI 직원에게 회사 법인카드를 쥐여 주되, 한도·사용처를 적은 계약서에 사장이 직접 도장을 찍고, 모든 결제 영수증에 위조 불가 도장을 남기는 시스템."
AI 에이전트는 점점 더 자율적으로 움직입니다. 명령을 실행하고, API를 부르고, 파일을 고치죠. 문제는 네 가지입니다 — ① 제약(무엇까지 허용할까), ② 증명(범위 안에서 움직였음을 어떻게 보일까), ③ 검증(나중에 사후 확인이 가능한가), ④ 위임(하위 에이전트에게 더 빡빡한 제약을 물려줄 수 있나). aflock은 이 넷을 '사람이 서명한 정책 + 서명된 증언(attestation)'으로 한 번에 풉니다.
핵심 특징 셋: ① 정책은 사람이 암호 서명(에이전트가 자기 족쇄를 못 푼다), ② 정체성은 부여가 아니라 유도(에이전트의 설정·실행 환경으로부터 신분을 계산 → 거짓말 불가), ③ JWT와 서명 키의 분리(에이전트는 신분증은 받지만 도장은 절대 못 만진다).
.aflock 파일Bash·Read 도구만 허용한다", ".env 파일은 절대 못 읽는다" 같은 한도·허가·제약을 담습니다. package-lock.json이 라이브러리 버전을 못 바꾸게 못 박듯, .aflock은 에이전트가 할 수 있는 일을 못 박습니다.이 프로젝트의 진짜 코드는 모두 Go 언어로 cmd/와 internal/ 아래에 있습니다. aflock이라는 명령줄 도구 하나가 Claude Code의 훅(hook)에 연결되어, 도구를 쓰기 전(PreToolUse)에 정책을 검사하고, 쓴 뒤(PostToolUse)에 증언을 남기며, 세션이 끝날 때 전체가 규칙을 지켰는지 검증(verify)합니다. README가 "현재는 비공개 개발·명세 단계(Specification phase)"라고 밝히듯 아직 초기 단계지만, 그 안에 담긴 보안 설계는 실무에서 검증된 표준들(SPIFFE/SPIRE·in-toto·OPA)을 조합한 견고한 교본입니다.
이 문서가 파고드는 건 "어떻게 설치하나"가 아니라 그 안의 보안 설계입니다 — "에이전트가 어떻게 자기 신분을 못 속이게 만드는가", "왜 신분증(JWT)과 도장(서명 키)을 굳이 분리하는가", "하위 에이전트에게 권한을 어떻게 더 좁혀서 물려주는가", "행동 순서를 빠뜨리거나 뒤바꾸지 않았음을 머클 트리로 어떻게 증명하는가". AI 에이전트 보안과 공급망 신뢰(supply-chain trust)를 동시에 공부하려는 사람에게 밀도 높은 실전 교본입니다.
"신뢰하지 말고 검증하라"를 AI 에이전트에 처음으로 제대로 적용한, 검증된 보안 표준 위에 세운 설계.
'AI 에이전트 가드레일'을 표방하는 도구는 점점 늘고 있지만, aflock이 라이브 멘션을 타고 주목받은 이유는 대부분이 "프롬프트로 부탁하는" 수준에 머무를 때, 이쪽은 암호학으로 강제하고 증명하기 때문입니다. 다섯 가지로 정리하면:
흔한 가드레일은 시스템 프롬프트에 "위험한 일은 하지 마"라고 적는 식입니다. 하지만 그건 에이전트가 마음만 먹으면 무시할 수 있는 부탁입니다. aflock은 Claude Code 훅을 가로채 도구 실행 자체를 코드 레벨에서 차단(deny)하고, 정책 파일은 사람이 서명해서 에이전트가 못 고치게 만듭니다.
보통 시스템은 "내가 GPT야"라고 스스로 주장하는 값을 믿습니다. aflock은 반대로, MCP 소켓에서 운영체제 PID(프로세스 번호)를 얻어 그 프로세스를 직접 들여다봅니다(UID·컨테이너 ID·실행 파일 경로). 신분은 주장이 아니라 설정으로부터 계산되는 값이라, 거짓말이 원천 봉쇄됩니다.
가장 신뢰가 가는 대목입니다. 정체성은 SPIFFE/SPIRE(클라우드 워크로드 신분 표준), 증언은 in-toto / DSSE(소프트웨어 공급망 보안 표준)와 Rookery(witness 프로젝트의 보안 강화 포크), 정책 평가는 OPA/Rego(쿠버네티스 업계 표준 정책 엔진)에 기댑니다. "검증된 것에 맡기고, 에이전트에 맞는 접착제만 새로 짠다"는 철학입니다.
에이전트가 하위 에이전트를 만들 때, 자식의 권한은 부모보다 반드시 더 좁아야 하고, 자식이 쓴 비용은 부모 총액에 합산됩니다. 검증은 하위 계층으로 재귀적으로 파고듭니다. 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 경유). 대상이 빌드 단계가 아니라 에이전트 실행이라는 점이 새롭다. |
전통 보안의 큰 흐름은 "안에 있으니 믿는다"에서 "매 요청마다 신분을 확인한다"로 옮겨 왔습니다(제로 트러스트). aflock은 이 원칙을 AI 에이전트에 그대로 가져옵니다 — 에이전트의 말을 믿지 않고, 프로세스를 직접 검사하고, 행동마다 서명으로 증명을 남긴다. "강력한 자율 시스템일수록, 신뢰는 주장이 아니라 검증에서 나와야 한다"는 설계 철학을 코드로 보여 줍니다.
README가 스스로 "Private development. Specification phase."라고 밝힙니다. 별 251개로 화제는 됐지만, '프로덕션에서 검증된 도구'가 아니라 '잘 설계된 참조 구현'으로 접근하는 게 정확합니다. 개념과 코드 패턴을 배우기엔 훌륭하지만, 그대로 운영에 올리긴 이릅니다.
go.mod 맨 끝에 replace ... => ../rookery/... 지시가 있습니다. 즉 일부 의존성을 상위 폴더에 나란히 받아 둔 rookery 저장소에서 끌어다 씁니다. 무작정 go build만 하면 막힐 수 있으니, 빌드를 따라 하려면 aflock-ai/rookery도 함께 받아 두세요.
핵심 동작이 옵트인(opt-in)입니다 — .aflock 파일이 없으면 aflock은 아무것도 막지 않고 전부 허용합니다(코드 주석: "No policy file exists — opt-in model, allow everything"). 보호를 받으려면 반드시 정책 파일을 두어야 하며, 파일이 손상되면 반대로 닫아 버립니다(fail-closed). 이 비대칭을 알고 써야 합니다.
단일 Go 바이너리 + 검증된 보안 라이브러리들. 웹 서버가 아니라 '정책 강제 엔진'이라 층 구분이 조금 다르다.
aflock은 화면이 있는 앱이 아니라 명령줄 도구이자 MCP 서버입니다. 그래서 백엔드/프론트엔드 대신 ① 진입·통합 층 ② 보안 코어(신분·서명·정책) ③ 인프라·배포로 나눠 보는 게 자연스럽습니다. 언어는 Go 1.26 단일 구성입니다(메모리 안전 + 단일 정적 바이너리 + 강력한 표준 암호 라이브러리라 보안 도구에 잘 맞습니다).
| 패키지 / 도구 | 역할 |
|---|---|
| spf13/cobra | CLI 프레임워크 — aflock hook / verify / init 같은 하위 명령 구성 |
| mark3labs/mcp-go | MCP 서버/클라이언트 — 에이전트가 도구를 부를 때 aflock과 대화하는 통로 |
| Claude Code Hooks | 8개 훅(SessionStart·PreToolUse·PostToolUse·Stop·SubagentStop·SessionEnd 등)에 연결되는 통합 지점 |
| 패키지 | 역할 |
|---|---|
| spiffe/go-spiffe/v2 | SPIFFE/SPIRE 워크로드 신분 — 에이전트에게 표준 ID(SPIFFE ID) 부여 |
| aflock-ai/rookery/* | 증언 생성/서명의 핵심 — in-toto 증언, 환경·명령실행·git·산출물 어테스터(attestor) |
| signers/fulcio + sigstore | 키리스(keyless) 서명 — 미리 만든 키 없이 OIDC 신원으로 단기 인증서를 받아 서명 |
| golang-jwt/jwt/v5 | JWT 발급/검증 — 에이전트에게 주는 단기 인가 토큰(ES256 고정) |
| open-policy-agent/opa | Rego 정책 엔진 — "지출이 한도를 넘으면 거부" 같은 규칙을 코드로 평가 |
| transparency-dev/merkle | 머클 트리 — 행동 순서·누락 없음을 암호로 증명(세션 JSONL 정렬 증명) |
| gobwas/glob | glob 패턴 매칭 — src/**·**/.env 같은 파일/도구 패턴 검사 |
| cyberphone/json-canonicalization | JSON 정규화(JCS) — 서명·해시 전에 JSON을 한 가지 표준 형태로 통일 |
spiffe://aflock.ai/...)를 줍니다. aflock은 에이전트를 이 표준 위에 올려 "이 에이전트가 정확히 누구인가"를 검증합니다.실제 시연 환경은 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(나중에 검증). "입구에서 신분 확인 → 규칙 적용 → 영수증에 도장 → 사후 감사"라는, 보안 검문소를 그대로 코드로 옮긴 구조입니다.
정책 한 줄이 어떻게 "신분 확인 → 차단 결정 → 서명된 증언 → 사후 검증"으로 흐르는가.
전체 구조의 핵심은 "에이전트는 신분증(JWT)만 받고, 진짜 도장(서명 키)은 서버가 쥔다. 모든 행동은 훅을 거치며 정책으로 걸러지고, 통과한 행동에는 위조 불가능한 증언이 남는다"입니다. 먼저 숲(전체 그림)을 보고, 그다음 대표 흐름 한 줄기를 끝까지 따라가 보겠습니다.
Bash: rm config.json을 실행하려 할 때"가장 흔한 동작 하나를 입력부터 결과까지 손으로 따라가 봅니다. 어느 패키지·파일이 어떤 순서로 일하는지에 집중하세요(핵심 단계만; 곁가지는 생략).
흐름을 한 문장으로 요약하면: "세션이 열리면 신분을 유도해 JWT를 쥐여 주고(①), 도구를 쓸 때마다 정책으로 거르고(②), 통과한 행동엔 서명 도장을 찍어 순서대로 쌓고(③), 끝에 전부 규칙을 지켰는지 검증한다(④)." 이 레포에서 'PreToolUse'는 막는 단계, 'PostToolUse'는 증거를 남기는 단계라는 한 쌍의 패턴을 알면 코드가 훨씬 잘 읽힙니다.
가장 근본적인 패턴입니다. 에이전트가 "내가 누구다"라고 주장하는 값을 믿지 않고, 설정·실행 환경에서 신분을 계산합니다. internal/identity/agent.go의 DeriveIdentity()는 모델·바이너리·환경·도구·정책 다이제스트·부모를 이어 붙여 SHA256 해시 하나로 만듭니다. 도구 순서가 바뀌어도 같은 해시가 나오도록 정렬하는 등(테스트 ToolOrderDoesNotMatter), "같은 에이전트면 항상 같은 신분, 다르면 반드시 다른 신분"을 보장합니다.
에이전트는 JWT(인가 토큰)는 받지만, 증언을 만드는 서명 키는 절대 못 만집니다. JWT로 "나 이거 해도 돼?"를 제시할 뿐, 도장은 항상 aflock 서버를 거쳐 찍힙니다. 신분증을 위조해도 공증 도장은 못 찍게 만든 셈입니다(§6에서 코드).
정책 파일이 없으면 전부 허용(opt-in, fail-open)하지만, 정책 파일이 있는데 깨졌으면 전부 거부합니다(fail-closed). "보호를 켜는 건 사용자 선택이되, 한번 켠 보호는 고장 시 안전한 쪽(차단)으로 닫힌다"는 보안 설계의 정석입니다. 신분 확인이 필요한 정책인데 SessionStart가 생략됐다면 역시 차단합니다.
handlePreToolUse는 한 번에 끝내지 않고 세 겹으로 거릅니다: ⓐ 도구/파일 접근(EvaluatePreToolUse) → ⓑ 자원 권한(EvaluateGrants: 비밀·API·저장소) → ⓒ 데이터 흐름(EvaluateDataFlow: 민감 데이터가 외부로 새는지). 한 겹이라도 거부면 거부입니다.
에이전트가 하위 에이전트를 만들면(isSubagentSpawn 감지), state/propagation.go가 부모의 자료(materials)와 한도를 자식에게 더 좁혀(attenuate) 전달합니다. "자식은 부모보다 빡빡해야 한다"는 규칙을 강제하고, 검증 때 자식 지출을 부모 총액에 합산합니다.
보안 시스템을 짤 때 가장 흔한 실수는 한 주체에게 권한과 증명 능력을 동시에 주는 것입니다. 그러면 그 주체가 뚫리는 순간 증거까지 위조됩니다. aflock처럼 에이전트에겐 단기 신분증(JWT)만 주고, 위조 불가능한 도장(서명 키)은 별도 주체가 쥐면, 에이전트가 탈취돼도 과거 증언은 위조할 수 없습니다. 이 "능력 분리"는 거의 모든 보안 설계에 적용되는 일급 원칙입니다.
진짜 로직은 전부 internal/ 아래. Go의 관례상 internal/은 "이 모듈 안에서만 쓰는 비공개 코드"라는 뜻이다.
읽는 순서를 추천하자면: ① 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는 전체 설계를 글로 정리한 보너스 자료라 개념을 한 번에 잡기 좋습니다.
이 저장소의 진짜 가치는 "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.goaflock의 가장 독창적인 부분입니다. 에이전트의 신분은 부여받는 게 아니라 구성요소를 이어 붙여 해시한 값입니다:
// 개념(README): 신분 = 구성요소들의 해시
AgentIdentity = SHA256(model ‖ environment ‖ tools ‖ policyDigest ‖ parent)
| 구성요소 | 어디서 알아내나 |
|---|---|
| Model | --model 플래그 · 환경변수 |
| Environment | PID 내성검사(UID·컨테이너 ID·바이너리 경로) |
| Tools | MCP가 보고하는 도구 목록 |
| Policy | 작업 폴더의 .aflock 다이제스트 |
| Parent | (하위 에이전트면) 부모의 신분 |
도구 순서가 바뀌어도 같은 해시가 나오게 정렬하고(ToolOrderDoesNotMatter 테스트), 같은 입력이면 항상 같은 64자 해시(Deterministic 테스트)가 나오게 보장합니다. "같으면 같고, 다르면 반드시 다른" 해시 설계의 교본입니다.
agent_audit_test.go는 이 방식의 약점도 스스로 지적합니다 — 구분자로 ‖(파이프)를 쓰면 값 안에 파이프가 섞일 때 충돌 위험이 있어 "길이 접두사나 구조적 직렬화가 더 안전하다"고 적어 둡니다. 자기 설계를 적대적으로 점검하는 좋은 습관입니다.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 공격을 막고, ② 토큰에 정책 다이제스트를 박아 "다른 정책엔 못 쓰게" 묶습니다. 신분증은 주되, 도장은 안 준다가 핵심입니다.
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
"악의적 입력을 가정하고, 우회 경로까지 테스트로 막는다"는 보안 코딩의 실전 자세를 통째로 볼 수 있습니다.
internal/rego/evaluator.goJSON으로 표현하기 힘든 복잡한 규칙은 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 공격 대비)도 자연스럽게 들어갑니다.
순수 소프트웨어라 특별한 하드웨어는 없다. 다만 빌드·실행에 필요한 환경이 조금 까다롭다.
| 항목 | 요구사항 / 비고 |
|---|---|
| 언어/런타임 | Go 1.26 이상 (go.mod 기준). 단일 정적 바이너리로 빌드됨 |
| 형제 저장소 | aflock-ai/rookery를 상위 폴더(../rookery)에 함께 받아야 빌드 가능(go.mod의 replace 지시) |
| 호스트 에이전트 | 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까지 띄우는 게 좋습니다.
읽기만 하면 금방 잊는다. 난이도순으로 손을 움직여 보자.
plugin/templates/default.aflock를 베껴, "내 프로젝트라면 어떤 규칙을 걸까"를 상상해 직접 한 장 작성해 보세요. tools.deny에 Task를 넣어 하위 에이전트 생성을 막고, files.deny에 **/*.key를 추가해 키 파일 접근을 차단해 봅니다. "무엇을 막고 무엇을 허용할지"를 데이터로 표현하는 감각을 잡는 게 목표입니다.
cmd/aflock/main.go의 init 명령이 만들어 주는 기본 템플릿과 내가 쓴 걸 비교해 보세요. 무엇이 빠졌나요?아무 언어로든 SHA256("claude-opus" + "|" + "local" + "|" + "Read,Bash" + "|" + "정책해시")를 계산해 64자 16진수가 나오는지 확인해 보세요. 그런 다음 도구 목록의 순서만 바꿔 다시 계산해 봅니다. aflock은 왜 도구를 정렬해서 해시할까요? "같은 에이전트는 항상 같은 신분이어야 한다"는 요구를 직접 체감하는 과제입니다.
aflock-ai/rookery를 상위 폴더에 받은 뒤 make build로 빌드하고, Makefile의 test-hooks 타깃처럼 JSON을 stdin으로 직접 흘려 결과를 봅니다. 예: Read src/main.go(허용)와 Read src/.env(차단), Task(차단)를 각각 넣어 allow/deny 출력이 어떻게 갈리는지 확인하세요. 훅이 stdin→stdout JSON 계약으로 동작한다는 걸 손으로 확인하는 게 핵심입니다.
internal/policy/evaluator_bash_bypass_test.go와 matcher_security_test.go를 읽어 보세요. "rm -rf * 차단을 어떻게 우회하려 시도하고, 코드가 그걸 어떻게 막는지"를 따라간 뒤, 새로운 우회 시도 하나를 테스트로 추가해 봅니다(예: 탭/개행을 끼운 명령, 경로 변형). 통과하나요, 뚫리나요? 공격자 시점으로 생각하는 보안 마인드셋을 기르는 과제입니다.
docker compose up으로 SPIRE 서버·에이전트·aflock 워크로드를 띄우고, examples/attestation-demo/run-demo.sh를 돌려 실제로 서명된 증언이 만들어지는 전 과정을 관찰하세요. examples/scripts/inspect-attestation.sh로 만들어진 증언을 들여다보고, aflock verify로 검증해 봅니다. "정책 → 행동 → 서명 → 검증"의 한 사이클을 눈으로 완주하는 게 목표입니다.
internal/state/propagation.go와 internal/hooks/handler_sublayout_test.go를 읽고, 부모의 한도가 자식에게 어떻게 더 좁혀져 전달되는지(attenuateLimits) 코드로 따라가 보세요. 부모가 $10, 자식이 $2를 받으면 부모의 남은 예산은 어떻게 계산될까요? "자식은 부모보다 빡빡해야 한다"는 규칙이 코드 어디서 강제되는지 찾아내는 게 과제입니다.
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·Sigstore | in-toto 증언·DSSE 봉투, Sigstore/Fulcio 키리스 서명, SLSA 레벨. 공식 문서 + aflock attestation 패키지 |
| 4주차 | 워크로드 신분 + 정책엔진 | SPIFFE/SPIRE 개념과 데모, OPA/Rego 튜토리얼(Rego Playground). aflock rego/identity/spire.go로 연결 |
| 보너스 | AI 에이전트 위협 모델 | 프롬프트 인젝션·과잉 권한·도구 오용 등 에이전트 특유의 위협. aflock paper/aflock.pdf + 적대적 테스트들 정독 |
문서·코드에서 자주 만나는 용어를 한 번에. 모르는 단어가 나오면 여기로.
| 용어 | 뜻 (한 줄) |
|---|---|
| Attestation (증언) | "이런 일이 일어났다"를 적고 암호 키로 서명한 위조 불가 기록(공증 영수증) |
| Policy / .aflock | 에이전트가 지켜야 할 한도·허가·제약을 적은 JSON 파일(사람이 서명) |
| JWT | JSON Web Token. 서명된 단기 신분증·인가 토큰. aflock은 ES256만 사용 |
| Signing Key (서명 키) | 증언에 도장을 찍는 비밀 키. 에이전트는 절대 접근 못 함(서버가 보관) |
| MCP | Model Context Protocol. AI와 외부 도구를 잇는 표준 통신 규약 |
| Hook (훅) | 특정 순간(도구 실행 전/후 등)에 자동 실행되는 외부 명령 |
| SPIFFE / SPIRE | 프로그램(워크로드)에 표준 신분증(SPIFFE ID)을 발급하는 표준/구현체 |
| Identity Derivation | 신분을 부여받지 않고 구성요소(모델·환경·도구·정책)로 계산해 내는 것 |
| Keyless Signing | 비밀 키 보관 없이 OIDC 신원으로 임시 인증서를 받아 서명(Sigstore/Fulcio) |
| in-toto / DSSE | 소프트웨어 공급망 증언 표준 / 그 증언을 담는 서명 봉투 형식 |
| Rookery | aflock이 쓰는 증언 프레임워크. witness의 보안 강화 포크(TestifySec) |
| OPA / Rego | 정책을 코드로 쓰는 엔진(OPA)과 그 전용 언어(Rego). 쿠버네티스 업계 표준 |
| Merkle Tree | 해시를 쌓아 뿌리 하나로 전체를 대표 → 순서·완전성·변조여부 증명 |
| Sublayout | 하위 에이전트용 더 좁은 정책. 자식은 부모보다 빡빡해야 함(in-toto 개념) |
| Materials | 증언이 다루는 입력물(세션 JSONL·git 트리 등). 순서/완전성 증명의 대상 |
| fail-fast / post-hoc | 한도 집행 방식 — 즉시 중단 / 끝에 검증 |
| Zero Trust | "안에 있어도 믿지 않고 매번 검증"하는 보안 원칙. aflock의 바탕 철학 |
| PID Introspection | 프로세스 번호로 그 프로그램을 직접 들여다봄(UID·컨테이너·경로) → 신분 확인 |
| JCS | JSON Canonicalization. 서명/해시 전 JSON을 한 가지 표준 형태로 통일 |
원문과 함께 보면 좋은 표준·문서들.
docs/reference/specification.md + paper/aflock.pdf)