막는 구조
세 층이 각각 무엇을 맡고 어디까지만 맡는지, 그리고 정책 파일의 키가 무엇을 정하는지.
갱신 2026-09-03
왜 층으로 나눴나
조사에서 설계를 바꾼 사실이 하나 있었다. 승인 게이트와 실행 경계는 다른 물건이다.
승인 판단은 명령 문자열을 보고 내려진다. 그래서 이름이 실제 동작과 다르면 뚫린다. 실행 경계는 OS 가 걸기 때문에 그 판단이 틀려도 유지된다. 둘을 한 층에 섞으면 앞의 실패가 뒤까지 끌고 내려간다. 그래서 갈랐다.
두 번째 사실은 이식성에 관한 것이다. 커밋 차단은 이식 가능하고 쓰기 차단은 그렇지 않다. git 훅은 도구 중립이라 어디서나 같은 자리에서 돈다. 반면 쓰기 시점을 잡으려면 그 에이전트 도구의 훅 계약을 알아야 한다. 그래서 커밋 경계를 기본으로 두고 쓰기 경계를 그 위에 얹는 구조로 만들었다. 쓰기 경계가 없어도 층이 성립해야 한다 — 그렇지 않으면 남의 시스템에 못 깐다.
1차 — 커밋 경계
저장소의 git 훅이 부른다. 커밋 시점에 두 가지를 본다.
- 시크릿이 담긴 추가 줄. 스캐너는 자족 구현과 외부 도구 중에 고를 수 있고, 기본값은 외부 도구가 있으면 쓰고 없으면 자족으로 떨어지는 자동이다. 이미 저장소에 있는 것을 예외로 두는 baseline 파일을 따로 둔다.
- 배포에 실리면 안 되는 경로. 빌드 산출물이나 로컬 설정처럼 커밋에 섞이면 안 되는 것을 거른다.
한계. 커밋 전까지는 아무것도 막지 않는다. 파일에 이미 쓰인 시크릿은 커밋 시점에야 걸린다. 그 사이를 메우는 것이 2차다.
2차 — 쓰기 경계
에이전트 도구의 사전 훅이 부른다. 도구가 파일을 쓰기 전에 판정을 요청하고, 우리가 막으면 도구가 그 호출을 취소한다.
- 시크릿 실값 쓰기. 값이 파일에 닿기 전에 세운다.
- 자기 권한 확대. 에이전트가 자기 훅·권한 정의를 고쳐 스스로의 경계를 넓히는 것을 막는다. 보호 경로 목록과, 사람이 의도한 변경을 위한 예외 목록을 함께 둔다.
판정 본체는 하나이고 도구별 어댑터는 설정 형식과 배선만 흡수한다. 어댑터 하나를 붙이는 한계 비용은 100줄 안팎이다.
한계. 어댑터가 없는 도구에서는 이 층이 아예 안 붙는다. 그때는 조용히 통과시키지 않고 1차로 떨어진다. 그리고 이 층은 그 도구가 훅을 실제로 부를 때만 유효하다 — 도구를 거치지 않는 쓰기는 못 본다.
3차 — 상태 점검
이 층은 우리가 만들지 않는다. OS 실행 경계는 도구가 제공하는 샌드박스이고, 우리는 그것이 켜져 있어야 한다고 정책으로 요구하고 지금 켜져 있는지를 확인할 뿐이다. 격리를 직접 구현하는 것은 이 층의 일이 아니다.
같이 보는 것이 하나 더 있다. 문서상 정책과 실제 도구 설정의 어긋남을 양방향으로 본다. 정본에는 있는데 도구 설정에 안 실린 경우와, 도구 설정에만 있고 정본에 없는 경우 둘 다 어긋남 이다. 장치가 있는 것과 그 장치가 지금 도는 것은 다른 일이다.
그물 — 서버 측 검사
있으면 쓰고 없어도 층은 성립한다. 1차의 최후 방어다.
정책 파일
저장소 안 파일 하나가 정본이다. 형식은 TOML 이고, 층은 이 파일을 읽기만 한다.
파일 하나로 둔 이유는 정책이 도구별 설정에 흩어지면 문서상 정책과 실제 적용이 갈리기 때문이다. TOML 인 이유는 표준 라이브러리로 읽히고 주석을 달 수 있어서다 — 사람이 읽고 고치는 파일에 주석이 없는 형식은 쓰지 않는다.
절은 넷뿐이고 절 이름·키 이름·값이 모두 닫힌 어휘다. 모르는 것은 무시되지 않고 거부된다. 오타 난 정책이 조용히 무시되는 것이 이 층에서 가장 위험한 실패이기 때문이다.
[mode] — 실행 경계 등급
| 키 | 값 | 뜻 |
|---|---|---|
level |
읽기전용 · 작업공간 · 무제한 |
3차가 요구하는 실행 경계의 등급 |
두 에이전트 도구가 독립적으로 도달한 축을 그대로 쓴다. 새 어휘를 발명하지 않는다.
[rules] — 허용·질의·거부
| 키 | 값 | 뜻 |
|---|---|---|
deny |
목록 | 거부 |
ask |
목록 | 사람에게 묻기 |
allow |
목록 | 허용 |
평가 순서는 deny → ask → allow 로 고정이고, 넓은 deny 가 좁은 allow 를 이긴다.
이 순서를 정책이 바꿀 수 없게 둔 이유는, 예외로 구멍을 못 내는 것이 정책 언어의 최소 조건이기
때문이다.
[secrets] — 커밋 차단
| 키 | 뜻 |
|---|---|
scanner |
자족 구현 · 외부 도구 · 자동 중 하나 |
baseline |
이미 있는 것을 예외로 두는 파일의 경로 |
extra_commands |
이 저장소의 추가 검사. 셸 문자열이 아니라 인자 목록만 받는다 |
warn_as_block |
애매한 경고 판정을 커밋 거부로 올릴지. 기본은 거짓 |
allow_paths |
고의로 규칙을 어기는 예제가 사는 경로 |
require_publish_allowlist |
배포 허용 목록이 없는 저장소를 차단으로 다룰지. 기본은 거짓 |
artifact_blocked_paths |
기본 차단 목록에 더할 경로 |
artifact_allowed_paths |
팀이 의도적으로 커밋하는 설정 파일 |
protected_paths |
2차가 지키는 보호 경로. 기본 목록에 더한다 |
protected_allowed |
사람이 의도한 변경 경로의 예외 목록 |
warn_as_block 과 require_publish_allowlist 의 기본값이 왜 거짓인지가 이 절에서 제일 중요한
줄이다. 오탐이 정상 작업을 세우면 사람은 검사를 끈다. 꺼진 검사는 없는 검사다. 그래서
애매한 판정은 기본적으로 경고에 두고, 올릴지 말지를 저장소가 정하게 했다.
[audit] — 기록
| 키 | 값 | 뜻 |
|---|---|---|
enabled |
참·거짓 | 기록 여부 |
dir |
경로 | 기록이 쌓이는 곳 |
level |
결정만 · 상세 |
얼마나 남길지 |
값은 남기지 않는다. 결정·대상·규칙 id 만 남긴다. 무엇이 막혔는지를 알아보려고 막힌 그 값을 평문으로 기록에 남기면, 기록 파일이 새 유출 경로가 된다.
설치와 되돌림
설치기는 이 저장소 안에 자족적으로 있고 외부 경로를 참조하지 않는다. 하는 일은 셋이다 — 정책이 없으면 기본 정책을 깔고, 1차 훅을 걸고, 정책을 도구별 설정으로 내보낸다.
내보내는 방향인 이유는, 도구가 우리 형식을 읽어 주기를 기대할 수 없기 때문이다. 중립 정본 하나에서 각 도구 형식을 생성한다.
되돌림은 우리 표식이 있는 것만 지우고 무엇을 어떻게 처분했는지 출력한다. 남의 저장소에 깔리는 물건이 자기가 안 놓은 것까지 지우면 안 된다.
잘못 쓴 정책으로 저장소가 잠기는 경우를 대비한 복구 경로도 있다. 그 동작의 정확한 조건은 설치 시 함께 전달하는 문서에 적는다.