execpolicy: 정책 파일에 테스트 케이스를 심어두기
화이트리스트를 잘못 쓰면, 보통 프로덕션에 가서야 압니다. Codex는 정례와 반례를 규칙 자체에 넣고, 로드할 때 바로 한 번 돌립니다. 규칙과 예제가 싸우면, 그 정책은 세션에 들어가지 않아요.
이 단계에서 보는 argv
정책이 들고 있는 테스트
- 정책 텍스트를 읽고 parse를 시작합니다parser.rs L57
- Starlark가 prefix_rule을 평가하고, decision이 없으면 기본은 allowparser.rs L348
- 규칙과 예제를 검사 대기 큐에 먼저 겁니다parser.rs L405
- 반례를 먼저 돌리고, 맞으면 ExampleDidMatchparser.rs L145
- 이어서 정례를 돌리고, 안 맞으면 ExampleDidNotMatchparser.rs L147
- 포장 명령은 안쪽 argv로 먼저 쪼갭니다exec_policy.rs L831
- 첫 token으로 규칙 버킷을 정확히 꺼냅니다policy.rs L334
- 여러 규칙이나 조합 명령은 가장 엄한 쪽을 고릅니다policy.rs L403
- 판정은 Skip, NeedsApproval, Forbidden으로 매핑됩니다exec_policy.rs L375
팀에 금지 한 줄을 쓰면서 pattern을 git 더하기 reset으로 적어요. 본뜻은 --hard를 막는 것이었죠. 일주일 뒤 누군가 --keep도 막혔다고 합니다. 접두가 짧아지면, 뒤에 무엇이 붙어도 같은 규칙에 맞아요.
또 일주일 뒤, 모델이 bash -lc로 한 겹 감쌉니다. 금지는 글자 그대로의 argv로 bash를 맞추고, 첫 token이 안 맞으면 명령은 휴리스틱 통로로 들어갑니다.
화이트리스트 한 줄은 늘 두 가지를 겁니다. 하나는 pattern이 막고 싶은 것을 정확히 덮는지. 다른 하나는 놓아주고 싶은 것을 오탐하지 않는지. 둘 다 보통 프로덕션에 가서야 알아요. 다음에 pattern을 고치는 사람도, 저자가 처음에 어떤 명령을 오탐할까 봐 두려워했는지를 볼 수 없습니다.
Codex는 정례와 반례를 규칙 자체에 넣습니다. match는 이 규칙이 반드시 맞춰야 하는 명령이고, not_match는 반드시 놓아야 하는 명령이에요. prefix_rule은 그 자리에서 예제를 돌리지 않고, 규칙과 예제를 검사 대기 큐에 먼저 넣습니다. Starlark 전체가 평가된 뒤에야 parse가 한꺼번에 검사해요.
순서는 고정입니다. 반례를 먼저, 그다음 정례. 반례가 잘못 맞으면 ExampleDidMatch. 정례가 하나도 안 맞으면 ExampleDidNotMatch. 둘 다 parse를 실패로 돌리고, Policy는 build되어 나가지 않아요.출처: codex-rs/execpolicy/src/parser.rs 57–78행, 그리고 133–151행
crate README는 한 줄로 적습니다. 로드 시점 검사용 예시 호출이고, 유닛 테스트처럼 쓰면 됩니다. 문자열과 token 배열 두 표기 모두 파서에 들어가면 token 목록이 되고, 문자열은 shlex로 쪼갭니다. 검사 때 휴리스틱 콜백은 비우므로, 정례는 진짜 접두 규칙에 맞아야 하고 휴리스틱으로 숫자를 맞출 수 없어요.
화이트리스트의 전형적인 실패는, 저자가 pattern이 이 뜻이라고 믿는 겁니다. 그 믿음을 예제로 쓰면 기계가 반대할 수 있어요. Starlark에 묶이지 않아서, JSON이나 YAML로 옮겨도 그대로 성립합니다.
규칙과 예제를 한곳에 두면, 정책 파일이 사용자 디렉터리로 복사되거나 overlay로 배포될 때 예제도 따라갑니다. 로더에는 규칙만 읽고 예제는 안 돌리는 분기가 없어요. 예제를 빼먹으면 테스트를 빼먹은 셈입니다. 로더가 대신 예제를 지어 주지 않고, 쓰면 강제 실행합니다.
정규식은 git reset 뒤에 아무 인자나 붙게 쓸 수도 있고, 저자 본인도 못 읽는 예외도 쓸 수 있어요. 겹친 규칙에서 가장 넓은 쪽을 고르면, 사용자 층의 느슨한 allow 한 줄이 시스템 층의 forbidden을 덮을 수 있습니다.
규칙 본체는 접두 한 토막입니다. 매칭은 정확한 문자열 동등이고, glob도 정규식도 없어요. git reset --hard는 뒤에 origin/main이 더 붙어도 먹습니다. 남은 token은 비교에 안 들어가니까요. 가운데 --config를 끼운 쓰기는 못 먹어요. 두 번째 token이 안 맞습니다.출처: codex-rs/execpolicy/src/rule.rs 46–59행
규칙은 첫 token으로 버킷에 넣습니다. 찾을 때는 argv 0번을 먼저 정확히 꺼냅니다. 없으면 절대 경로를 basename으로 접을 수 있어요. 여러 줄이 맞으면 decision에 max를 합니다. Decision은 Ord를 파생하고, variant를 쓴 순서가 곧 심각도입니다. Allow는 Prompt보다 작고, Prompt는 Forbidden보다 작아요.
조합 명령은 먼저 토막을 나누고 펼친 뒤, max를 한 번 더 합니다. 한 토막 git status가 Prompt이고 한 토막 git commit이 Forbidden이면, 파이프 전체는 여전히 Forbidden입니다.출처: codex-rs/execpolicy/src/policy.rs 265–287행, 그리고 402–411행
겹치면 더 엄해질 수만 있고, 느슨해질 수는 없어요. 그 순서는 enum variant에 있고, 런타임에 잘못 쓸 두 번째 우선순위 표는 없습니다. 다른 언어로 다시 써도, 문자열 셋을 순서 있는 enum으로 만들고 한 번 max로 접으면 됩니다.
접두는 의도를 token 수열로 떨어뜨리게 합니다. 대가는 가운데 끼운 인자가 규칙을 죽인다는 점이에요. 저자는 접두를 더 짧게 쓰거나 한 줄을 더 써야 합니다. 정례와 반례가 그 대가를 받아 줍니다. 짧게 쓰면 반례가 로드 시점에 먼저 무너져요.
금지는 git reset --hard로 쓰여 있어요. 모델은 bash -lc로 한 겹 감쌉니다. 글자 그대로의 argv로 맞추면 첫 token은 bash이고, 금지는 켜지지 않아요.
판정 전에 먼저 parse_shell_lc_plain_commands를 탑니다. 스크립트는 순수 단어 명령에 &&, ||, 세미콜론, 파이프만 허용하고, 리다이렉트·치환·괄호·제어 흐름은 거절합니다. 통과하면 명령 노드를 argv 토막으로 자릅니다. bash -lc가 git reset --hard를 감싸면 안쪽 세 token으로 쪼개진 뒤, 그걸로 접두를 맞춥니다.출처: codex-rs/core/src/exec_policy.rs 831–858행
쪼개지지 않으면 전체 argv를 명령 하나로 보고, 휴리스틱과 뒤의 샌드박스에 넘깁니다. 인자 안의 빈 따옴표는 아직 복원할 수 있어요. 명령 이름 안의 빈 따옴표는 word-only 파싱이 실패하고, 접두 규칙은 git을 보지 못하며, 이 명령은 휴리스틱으로 떨어집니다.
파서가 삼키지 못한 것을 인정하면, 스크립트를 이미 이해한 척하지 않습니다. 그게 fail closed의 일반적인 모양이에요. 파싱은 성공했는데 위험한 토막을 빠뜨리는 일을 막습니다. 분해가 실패한 뒤에도 휴리스틱과 샌드박스는 남아 있어요.
DeepSeek Harness: 노브 두 개와 드롭다운 하나
DSH는 명령 단위 pattern을 쓰지 않아요. 권한 프리셋이 샌드박스 모드와 승인 정책을 한 묶음으로 묶습니다. 기본 표는 두 줄뿐입니다. workspace-write에 ask, danger-full-access에 never. 승인 정책 자체도 ask와 never뿐입니다.
노브는 이해하기 쉬워요. 프리셋만으로는 git reset --hard만 금지하고 나머지 git은 그대로 둘 수 없습니다. 그런 예외는 샌드박스가 거절한 뒤의 승급 승인으로 가거나, 예약 이름 custom으로 보이는 사용자 노브 조합으로 갑니다. 드롭다운은 일상 전환을 덮지만, 이름을 찍어 금지하는 롱테일은 덮지 못해요.
Claude Code: 도구 이름에 선택 내용을 붙인 allowlist
규칙 문자열은 Bash, 또는 Bash(npm install), 또는 Bash(git *)처럼 생겼어요. 파서는 괄호로 도구 이름과 내용을 가릅니다. 셸 규칙은 다시 정확·접두·와일드카드 셋으로 나뉩니다.
규칙 필드로 match, not_match, example를 찾아도, 로드 시점 정례·반례 검사는 없습니다. git *가 allow에 들어가면 git reset --hard도 그 와일드카드에 먹히고, 더 구체적인 deny를 따로 써야 해요. Codex는 더 짧은 정확 접두와 반례로 예외를 로드 시점에 못 박습니다. Claude Code는 예외를 규칙 겹침 순서와 런타임 확인에 남겨 둬요.
접두가 짧을 때, 로드일까요 판정일까요
git reset --hard를 금지하는 pattern을 git 더하기 reset으로 짧게 쓰고, not_match는 그대로 --keep으로 둡니다. 로드 때 ExampleDidMatch일까요 ExampleDidNotMatch일까요. 명령은 판정에 들어갈 기회가 있을까요.
한 단계 더: 같은 나쁜 규칙 아래에서 ls -l이 먼저 allow가 될까요. 데모에서 정책을 “접두가 너무 짧음”으로 바꾼 뒤 다시 재생해, 당신의 추리와 맞춰 보세요.