읽기 전용 폴더가 쓰기 가능해진 이유: Claude Code 2.1.260이 고친 권한 규칙 파서의 세 함정
권한 설정 파일에 Edit(path), Write(path), Read(path) 규칙을 적었다고 해서 그 정책이 실제로 적용됐다고 단정할 수는 없다. 규칙 문자열을 읽고, 경로와 명령을 분해하고, sandbox 정책으로 옮기는 코드가 같은 뜻을 보존해야 한다. 중간 파서가 괄호 하나를 문법으로 잘못 읽으면 "읽기 전용"이라고 믿은 폴더가 쓰기 가능해질 수 있다.
Anthropic이 UTC 기준 9월 3일, 한국 시각 9월 4일 공개한 Claude Code 2.1.260 release에는 권한 해석과 관련된 수정이 여러 개 들어갔다. 경로 안 괄호 때문에 file rule이 사라지는 문제, 닫히지 않은 [ 하나가 모든 편집을 막는 문제, zsh 특수 변수 대입 안의 command substitution이 자동 승인되는 문제가 핵심이다. 같은 release는 2.1.259에서 추가했던 Bash 인자에 대한 Read() deny 전파도 되돌렸다. 정상적인 npm run build까지 막는 과잉 차단이 생겼기 때문이다.
요약: 정책 문자열도 실행 코드처럼 테스트해야 한다
세 문제의 결과는 서로 다르다. 괄호가 든 경로 규칙을 invalid로 버리면 보호가 빠져 fail-open에 가까운 상태가 된다. 깨진 pattern 하나가 전체 matcher 생성을 실패시키면 관계없는 편집까지 멈추는 fail-closed 장애가 된다. shell 문법 안에 숨은 command substitution을 놓치면 위험한 하위 명령이 안전한 대입문처럼 보인다.
같은 권한 rule parser에서도 보호 규칙 유실, 전체 차단, 중첩 명령 누락이라는 서로 다른 실패가 발생했다. 출처: github.com/anthropics/claude-code/releases/tag/v2.1.260.
공식 permission 문서의 현재 규칙은 명확하다. 형식은 Tool 또는 Tool(specifier)이고, specifier 안의 괄호는 literal이다. 따라서 이름이 reports(archive)인 디렉터리를 보호하려고 괄호를 escape할 필요가 없다. deny, ask, allow가 함께 맞으면 deny가 먼저이며, 구체적인 rule이라고 우선순위가 올라가지 않는다. 이 계약은 설정 파일을 사람이 읽을 때만 맞아서는 안 된다. UI, CLI matcher, Bash sandbox까지 같은 방식으로 해석해야 한다.
괄호를 문법으로 읽으면 보호 규칙이 사라진다
2.1.260 release는 경로에 괄호가 포함된 Edit·Write·Read rule이 invalid로 폐기되거나 Bash sandbox에서 무시되던 문제를 고쳤다고 적는다. 영향은 단순한 경고 누락이 아니었다. 사용자가 read-only로 만들었다고 생각한 폴더가 실제로는 쓰기 가능할 수 있었다.
문제의 경계는 rule 바깥 괄호와 specifier 안 괄호를 구분하는 데 있다. Edit(./reports(archive)/**)에서 첫 괄호 쌍은 tool과 specifier를 나누는 문법이지만, reports(archive)의 괄호는 경로 문자다. 파서가 마지막 닫는 괄호를 찾지 않고 중간 괄호를 구조로 해석하거나, 여러 consumer가 서로 다른 escape 규칙을 쓰면 rule이 유실된다.
Windows 경로는 더 까다롭다. release는 Edit(C:\dir\(name)\**)처럼 \(가 path separator 뒤의 literal 괄호인지 escape인지 모호한 표기에 대해 더 분명한 spelling을 제안하도록 오류 메시지를 개선했다고 밝힌다. 여기서 중요한 운영 원칙은 "파서가 알아서 이해할 것"이 아니라 저장 직후 effective policy를 다시 읽는 것이다. 설정 JSON이 남아 있다는 사실은 matcher가 rule을 받아들였다는 증거가 아니다.
깨진 pattern 하나가 전체 편집을 멈췄다
반대 방향의 실패도 있었다. 닫히지 않은 [처럼 compile할 수 없는 pattern이 file permission rule 하나에 들어 있으면 모든 file edit가 Invalid regular expression으로 실패했다. 2.1.260부터는 그런 deny rule이 자신이 적은 literal path를 보호한다.
이 선택은 malformed deny를 조용히 버리는 것보다 안전하다. 잘못된 deny를 삭제해 버리면 보호하려던 경로가 열릴 수 있기 때문이다. 그렇다고 전체 편집을 멈추는 것도 운영상 비싸다. 한 저장소의 오타가 agent fleet 전체를 멈추고, 담당자가 급하게 permission을 넓히도록 유도할 수 있다. 수정된 동작은 보호 범위를 literal로 좁혀 의도를 보수적으로 유지하면서 장애 반경을 줄인다.
정책 compiler에는 세 가지 출력이 필요하다. 정상적으로 compile된 rule, literal fallback으로 격리된 malformed deny, 실제로 거부된 invalid 설정을 나눠 보여 줘야 한다. 로그에 rule 개수만 남기지 말고 원본 rule과 normalized form, 적용 consumer, fallback 여부를 함께 기록해야 원인을 찾을 수 있다.
안전한 대입문 속의 command substitution
세 번째 문제는 경로가 아니라 shell parser다. Claude Code는 zsh의 REPORTTIME, REPORTMEMORY, DIRSTACKSIZE 대입 안에 command substitution을 숨긴 명령을 자동 승인할 수 있었다. 2.1.260은 이런 형태를 승인 대상으로 보내도록 바꿨다.
겉 문자열의 시작만 보면 REPORTTIME=...은 shell 동작을 조절하는 변수 대입처럼 보인다. 하지만 값 안에 $(...)나 backtick이 있으면 그 안의 command가 실행된다. permission checker가 "알려진 안전 변수 대입"을 먼저 인정하고 내부 syntax tree를 더 보지 않으면, 하위 명령은 별도 규칙과 승인을 건너뛸 수 있다.
현재 공식 문서는 compound command의 각 subcommand를 독립적으로 맞춘다고 설명한다. &&, ||, ;, pipe, newline뿐 아니라 subshell, command substitution, loop body 안의 명령에도 deny와 ask가 적용된다. 예를 들어 ask rule의 git clean은 echo "$(git clean -f)" 안에 들어 있어도 질문해야 한다. 안전한 wrapper나 변수 대입을 벗겨 내는 최적화는 이 재귀 검사가 끝난 뒤에만 허용해야 한다.
설정 파일이 남아 있다는 사실만으로는 부족하다. parser, CLI, sandbox, 실제 중첩 명령까지 정책의 뜻이 보존되는지 확인해야 한다. 출처: code.claude.com/docs/en/permissions.
과잉 차단도 정책 오류다
2.1.259는 Read() deny rule을 Bash argument까지 넓혔다. 취지는 shell 명령이 deny 경로를 우회해 읽는 일을 막는 것이었지만, 2.1.260에서 되돌렸다. Read(./**/build/**) 하나가 모든 mode에서 npm run build를 막고, cd … && grep가 auto mode에서도 prompt를 띄우는 문제가 생겼기 때문이다.
파일 도구의 path argument와 arbitrary shell command의 문자열을 같은 방식으로 비교하면 의미가 흐려진다. npm run build의 build는 보호 디렉터리를 읽겠다는 path operand가 아닐 수 있다. 반대로 cat secret의 secret은 실제 path다. 단순 substring 전파는 누락을 줄일 수 있지만 거짓 양성을 크게 늘린다.
이 rollback은 "deny는 넓을수록 안전하다"는 생각이 운영 환경에서는 충분하지 않다는 사례다. 과잉 차단이 반복되면 사용자는 broad allow나 bypass mode로 이동한다. 정책 엔진은 공격 경로를 막는 동시에 정상 작업의 의미를 구분해야 한다. 파서가 확신하지 못하는 경우 prompt나 sandbox 같은 별도 통제로 보내고, 모든 문자열을 file path로 취급하지 않는 편이 낫다.
실전 적용: rule을 저장한 뒤 효과를 시험한다
첫째, 경로 fixture에 공백, 괄호, 대괄호, Unicode, Windows drive와 POSIX symlink를 넣는다. allow·ask·deny 각각이 CLI와 sandbox에서 같은 normalized path를 보는지 검사한다.
둘째, shell fixture에는 &&, pipe, subshell, $(...), backtick, loop, wrapper, 앞쪽 environment assignment를 넣는다. 겉 명령뿐 아니라 중첩된 모든 실행 node가 rule 평가를 받는지 본다.
셋째, negative test를 두 종류로 나눈다. 보호 규칙이 빠져 위험한 쓰기가 허용되는 fail-open과 정상 명령까지 막히는 false positive를 따로 세어야 한다. 보안 test가 "공격을 막았다"만 확인하면 전체 편집 불능을 성공으로 오판할 수 있다.
넷째, 배포 전 effective policy를 확인한다. 공식 문서상 /status는 현재 session이 읽은 settings source를 보여 주지만 각 key의 출처까지 보여 주지는 않는다. claude doctor는 거부된 entry를 나열한다. 따라서 두 진단 결과와 설정 원문을 저장하고, fixture 실행으로 sandbox의 실제 허용·거부를 따로 확인한다. production agent에는 테스트한 설정 hash만 허용하고, 버전 upgrade 때 같은 fixture를 다시 실행한다.
범위와 주의점
이 글은 Claude Code 2.1.260 공식 release와 현재 permission 문서를 대조한 설계 해설이다. Anthropic의 내부 구현 코드나 보안 보고서는 공개 release에 포함되지 않았고, 이 실행 환경에는 claude binary가 없어 취약 버전과 수정 버전을 직접 비교하지 못했다. 따라서 exploit 가능 범위나 영향을 받은 모든 플랫폼을 추정하지 않고, release가 명시한 증상과 현재 문서의 계약만 다룬다.
release 항목은 세 문제가 수정됐다는 사실을 말하지만, 기존 설정이 자동으로 모두 정규화되는지까지 보장하지 않는다. 업그레이드 뒤에도 effective rule을 다시 확인해야 한다. 특히 bypassPermissions는 prompt injection이나 의도하지 않은 행동을 막지 못한다고 공식 문서가 경고하므로, rule 오류를 피하려고 전체 검사를 끄는 대응은 맞지 않는다.


댓글
댓글 쓰기