Skip to content

fix(hooks): block-destructive 가 뒤에 붙은 df -h / 때문에 스크래치패드 삭제를 CATASTROPHIC 으로 막던 것 — rm 이 든 단순 명령 안에서만 봐요 - #29

Merged
bluecheat merged 2 commits into
mainfrom
fix/block-destructive-segment-scope
Oct 3, 2026

Conversation

@bluecheat

@bluecheat bluecheat commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

무엇이 좋아지나

  • 세션 스크래치패드의 빌드 캐시를 지울 수 있어요. 실측(one-tenth): 디스크가 120MB 남은 상태에서 에이전트가 자기 Xcode 빌드 캐시(20GB)를 지우려던 명령이 "🚨 CATASTROPHIC 명령 차단: 재귀 rm + 루트/시스템 경로" 로 막혔어요
  • heredoc 으로 파일을 쓰는 명령이 본문 낱말 때문에 CATASTROPHIC 으로 막히던 것도 같이 풀려요 (같은 프로젝트 트랜스크립트에서 6건)
  • 루트·시스템·홈 삭제는 그대로 막고, 예전엔 우연히만 잡히던 cd / && rm -rf * 를 이제 의도적으로 잡아요

원인 — 경로가 아니라 범위였어요

막힌 명령 원문:

cd /private/tmp/claude-502/<project>/<session>/scratchpad/ios-dd && rm -rf Build/Intermediates.noindex Index.noindex ModuleCache.noindex SDKStatCaches.noindex Logs; du -sh . ; df -h / | tail -1

CATASTROPHIC 의 세 조건(rm 호출 · 재귀 플래그 · 루트/시스템 경로)을 명령 문자열 전체에 따로 걸어서, rm -rf 와 무관한 df -h / 의 / 가 "루트 경로" 조건을 채웠어요. 스크래치패드 경로(/private/tmp/...)는 원래 루트 패턴에 안 맞아요.

어떻게

  • ; && || | & ( ) 백틱 줄바꿈에서 단순 명령(세그먼트)으로 자르고, 세 조건을 같은 세그먼트 안에서만 봐요. python 없이 bash+awk 로만 해요 (CATASTROPHIC 은 끌 수 없는 층이라 의존을 늘리지 않았어요)
  • 세그먼트로 가르면 생길 구멍 하나를 같이 막아요: 루트·시스템·홈으로 cd 한 뒤의 rm -rf * / . / ./* 는 CATASTROPHIC. 다른 곳으로 다시 cd 하면 풀려요
  • (rm -rf ~) 처럼 괄호에 붙은 홈 삭제도 이제 잡혀요 (예전엔 ~) 라서 놓쳤어요)
  • 상위 경로(..) 경고도 같은 범위로 — rm -rf build && cd .. 는 경고하지 않아요

스크래치패드 허용 목록은 두지 않았어요. 브리프는 /private/tmp/claude-*/**/scratchpad/** 허용을 제안했지만, 범위만 고치면 스크래치패드 삭제는 이미 통과하고, CATASTROPHIC 앞에 경로 허용 목록을 두면 …/scratchpad/../../.. 같은 표기가 새 우회로가 돼요. 스크래치패드는 프로젝트 밖이라 destructive-facts 도 묻지 않아요 (테스트로 고정).

바뀐 파일

  • templates/default/.ax/hooks/pre-bash/block-destructive.sh
  • tests/smoke.sh §15 · §41 · §51

검증 (빨강 → 초록)

수정 전 훅에 새 테스트만 얹었을 때 (§15.1):

✗ block-destructive 잘못 차단/경고 (exit=2, ...CATASTROPHIC 명령 차단 (mode 무관): 재귀 rm + 루트/시스템 경로  ← 실측 스크래치패드 명령
✗ block-destructive 잘못 차단/경고 (exit=2, ...)  ← /tmp/claude-* 스크래치패드 + 뒤의 ls /
✗ block-destructive 잘못 차단/경고 (exit=2, ...)  ← 변수 경로 + df -h /
✗ block-destructive 잘못 차단/경고 (exit=2, ...)  ← heredoc 본문의 낱말
✗ block-destructive 미차단 (exit=0): subshell 안 rm -rf ~ ('(rm -rf ~)')
✗ block-destructive 미차단 (exit=0): cd ~ 뒤 rm -rf . ('cd ~; rm -rf .')
✗ block-destructive 잘못 차단/경고 (exit=2, ...)  ← 루트에 갔다가 다른 곳으로 cd 한 뒤 rm -rf *
FAILS=7

§41: ✗ block-destructive — 일상 명령 'rm -rf build && cd ..' 오탐 (FAILS=1)

수정 후: §15 FAILS=0 (✓ 45개), §41 FAILS=0, §51 ✓ destructive-facts — 스크래치패드(프로젝트 밖)는 통과 · 프로젝트 루트 자체는 차단.
그대로 막는 것(회귀 고정): rm -rf / · /* · "/" · /usr /etc · ~ · --recursive --force / · find / -delete · chmod -R 777 /, 그리고 새로 rm -rf / ; echo done · ls && rm -rf /* · df -h / && sudo rm -rf /usr · (rm -rf ~) · cd / && rm -rf * · cd ~; rm -rf . · cd /usr && rm -rf ./*.

  • 전체 smoke 748 통과 (main 734 + 14) — macOS(bash 3.2) 로컬 · CI smoke macos/ubuntu 둘 다 pass
  • §53 "CATASTROPHIC 차단이 끄기 스위치보다 앞" 그대로 통과

버전·changelog 는 올리지 않았어요 — 열린 #26·#27(0.7.3)·#28(0.7.4) 이 버전을 쥐고 있고 이번에 PR 셋이 같이 열려서, 머지 순서가 정해질 때 한 번에 올리는 게 충돌이 적어요.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C8rZ6V3FW3nwYA9T7iygPd

bluecheat and others added 2 commits October 3, 2026 23:09
…/` 로 스크래치패드 삭제가 막히던 것

CATASTROPHIC 의 세 조건(rm · 재귀 플래그 · 루트/시스템 경로)을 명령 문자열 전체에 따로 걸어서,
서로 다른 명령의 조각이 합쳐졌어요. 실측(one-tenth): 세션 스크래치패드의 20GB 빌드 캐시를 지우는
`cd <scratchpad>/ios-dd && rm -rf Build/... Logs; du -sh . ; df -h / | tail -1` 이 `df -h /` 의 ` /`
때문에 "재귀 rm + 루트/시스템 경로" 로 막혔고 디스크가 120MB 까지 찼어요. heredoc 으로 파일을 쓰는
명령도 본문 낱말로 같은 이유로 여러 번 막혔어요.

- `;` `&&` `||` `|` `&` `(` `)` 백틱 줄바꿈에서 세그먼트로 자르고, 세 조건을 같은 세그먼트에서만 봐요
- 세그먼트로 가르며 생길 구멍 하나를 막아요: `cd /`·`cd ~`·`cd /usr` 뒤 `rm -rf *`/`.`/`./*` 는 CATASTROPHIC
  (예전엔 `cd /` 의 ` /` 에 우연히 걸렸어요). `(rm -rf ~)` 처럼 괄호에 붙은 홈 삭제도 이제 잡아요
- 상위 경로(..) 경고도 같은 범위로 — `rm -rf build && cd ..` 는 경고하지 않아요
- 스크래치패드 경로 허용 목록은 두지 않았어요. 원인이 경로가 아니라 범위였고, CATASTROPHIC 앞에
  허용 목록을 두면 `.../scratchpad/../../..` 같은 표기가 새 우회로가 돼요

smoke §15 에 회귀 13종(스크래치패드 실측 명령 · 절대 경로 · /tmp/claude-* · 변수 경로 · heredoc 본문 통과,
같은 세그먼트 rm -rf / · /* · sudo /usr · (~) · cd / 뒤 * · cd ~ 뒤 . · cd /usr 뒤 ./* 차단, cd 로 벗어나면 통과),
§41 에 `rm -rf build && cd ..`, §51 에 스크래치패드 통과 · 프로젝트 루트 자체 차단.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C8rZ6V3FW3nwYA9T7iygPd
… 붙여요

세그먼트를 줄바꿈에서 자르면서 `rm -rf \⏎ /` 가 두 세그먼트로 갈라져 루트 삭제가 통과했어요
(main 은 NORM 에서 줄바꿈을 공백으로 바꿔 잡고 있었어요). `pushd / && rm -rf *` 도 cd 만 보던 탓에 통과했어요.

- 자르기 전에 `\` + 줄바꿈을 공백으로 붙여요
- cd 와 같이 pushd 도 다음 세그먼트의 상대 경로 기준으로 봐요
- smoke §15: pushd 1건 · 줄 이어쓰기 3건 (jq 로 JSON 을 만들어 줄바꿈을 그대로 넘겨요)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MW9uw5kSzPQhuMePpnsJYt
@bluecheat
bluecheat force-pushed the fix/block-destructive-segment-scope branch from 3928264 to 2634073 Compare October 3, 2026 14:14
@bluecheat

Copy link
Copy Markdown
Owner Author

리뷰에서 찾은 CATASTROPHIC 우회 2건을 이 브랜치에서 고쳤어요 (c 커밋 fix(hooks): 줄 이어쓰기·pushd …).

  • rm -rf \⏎ / — 세그먼트를 줄바꿈에서 자르면서 이어진 줄이 갈라졌어요 (main 은 막던 것). 자르기 전에 \+줄바꿈을 붙여요
  • pushd / && rm -rf * — cd 만 보던 것을 pushd 까지
    smoke §15 에 4건 더했고 전체 통과해요.

@bluecheat
bluecheat merged commit fb76138 into main Oct 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant