fix(crush): bridge 레인에 owner-operator 권한 설정을 준다 (#940 정정) - #944
Merged
Conversation
#940 에서 bridge 가 crush 설정을 못 읽는 문제를 고치면서 레포의 crush/crushrc.readonly 를 물렸다. 그 파일은 이름 그대로 **agent-cron 비대화형 러너용**이고 bash/edit/write 를 막는다. 그래서 owner 봇이 셸을 못 쓰게 됐다. dungae 실사용: "Crush 환경에서 bash 도구가 비활성화되어 있어 직접 셸 명령을 실행할 수 없습니다" 정작 그 레인의 실제 정책은 반대다(dungae 실측): execution_profile=owner-operator bash_policy=auto-approve host_scope=true 같은 정책에서 Codex 레인은 approval=never + sandbox=dangerFullAccess 를 받는다(bot_access.py `_codex_approval_policy`/`_codex_sandbox_policy`). crush 만 읽기 전용으로 두면 프로바이더를 바꿨을 때 능력이 줄어든다. crushrc.bridge 를 추가하고 crush_runtime 의 기본값을 그리로 옮긴다. provider/model 정의는 동일하고 permissions 만 다르다 — question 만 막는다 (bridge 에 응답 경로가 없어 모델이 쓰면 턴 전체가 실패한다, #934). 안전장치는 유지된다: crush 의 permission 요청은 여전히 `_decide_permission` -> bridge approval handler 로 가고 운영자의 bash_policy 가 판단한다 (auto-approve=허용, approve-each=매번 확인, 그 외=거부). 이 파일은 무엇을 제안할 수 있는가를 정할 뿐 실행 여부는 bridge 정책이 정한다. 실측: crushrc.bridge 로 26개 전체 도구 노출(bash/edit/write/download 포함), question 없음. crushrc.readonly 는 그대로 22개. ※ permissions 는 나중 줄이 이기지 않는다 — deny 뒤에 allow 를 붙여도 뒤집히지 않아서 파일 분리가 필요했다(실측). 테스트 4건 추가: bridge 는 셸 도구를 막지 않고 question 은 막는다 / readonly 는 여전히 전부 막는다 / 두 파일의 provider 정의가 동일하다 (드리프트 가드) / 런타임 기본값이 crushrc.bridge 다. 전체 스위트 2238 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
앞 커밋은 도구를 열어줬지만 승인 왕복은 남겼다. 실측해 보니 crush 는
도구마다 다르게 굴었다(dungae, 2026-08-04, DENY 핸들러로 확인):
bash(echo) crush 가 묻지 않고 실행
write crush 가 승인을 요청 -> 거부되니 파일도 안 생기고 출력도
비었는데 **에러는 없다**(조용한 실패)
그 왕복에는 실패 경로가 하나 더 있다 — `_decide_permission` 은
`turn_ready` 를 5초 기다리고 타임아웃이면 DENY 한다. 부하가 걸리면
승인될 도구가 조용히 거부될 수 있다.
Codex 는 같은 정책에서 approval=never 를 받아 아예 묻지 않는다. crush 도
같은 자리에 두려면 왕복 자체를 없애야 한다.
crushrc 에 박지 않고 bash_policy 에서 파생시킨다 — 파일에 allow 를 적으면
approve-each 로 조여도 crush 가 묻지 않아 가로챌 수 없다. 정책은 한
곳에서만 나와야 한다.
auto-approve -> permissions allow <26개 도구> 를 스테이징 시 덧붙임
approve-each -> 덧붙이지 않음. crush 가 묻고 bridge 가 매번 확인
그 외 -> 덧붙이지 않음. crush 가 묻고 bridge 가 거부
question 은 어느 경우에도 허용 목록에 없다.
실측(preapprove_tools=True, DENY 핸들러):
승인요청 없음 · 파일 생성 True 'WROTE-5521' · OUTPUT 'WROTE-5521'
전체 스위트 2239 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
| 도구 | crush 동작 |
|---|---|
bash (echo) |
묻지 않고 실행 |
write |
승인 요청 → 거부되니 파일도 안 생기고 출력도 비었는데 에러는 없음 (조용한 실패) |
그 왕복에는 실패 경로가 하나 더 있습니다:
await asyncio.wait_for(active.turn_ready.wait(), timeout=5.0)
except asyncio.TimeoutError:
return ApprovalDecision.DENY부하가 걸리면 승인될 도구가 조용히 거부될 수 있습니다.
수정 — crushrc에 박지 않고 bash_policy에서 파생
파일에 allow를 적으면 approve-each로 조여도 crush가 묻지 않아 가로챌 수 없습니다. 정책은 한 곳에서만 나와야 합니다.
bash_policy |
스테이징 결과 | 동작 |
|---|---|---|
auto-approve |
permissions allow <26개> 덧붙임 |
crush가 안 물음 (Codex never 동등) |
approve-each |
안 덧붙임 | crush가 묻고 bridge가 매번 확인 |
| 그 외 | 안 덧붙임 | crush가 묻고 bridge가 거부 |
question은 어느 경우에도 허용 목록에 없습니다.
이건 Codex의 _codex_approval_policy()가 bash_policy를 never/on-request/untrusted로 매핑하는 것과 같은 파생입니다.
실측 (dungae, preapprove_tools=True, DENY 핸들러)
승인요청 : 없음 (DENY 핸들러인데도 안 물음 = never)
errors : 없음
파일 생성 : True 'WROTE-5521'
OUTPUT : 'WROTE-5521'
거부하도록 만든 핸들러를 걸어놨는데도 호출조차 안 됐습니다. 진짜 never입니다.
테스트
tests/test_crush_model_pin.py 26 passed
전체 bridge 스위트 2239 passed, 1 skipped
preapprove_tools가 꺼져 있으면 permissions allow가 안 붙고, 켜져 있으면 붙되 question은 여전히 deny인 것까지 검증합니다.
애매한 변수명 l -> line, 플레이스홀더 없는 f-string 제거. 로컬에서 pytest 만 돌리고 ruff 를 안 돌려 CI python-lint 에서 잡혔다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
seoseo-ai
approved these changes
Aug 4, 2026
seoseo-ai
left a comment
Collaborator
There was a problem hiding this comment.
Approved after explicit operator authorization using the local seoseo-ai credential.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
등애를 crush로 전환한 뒤 owner 봇이 이렇게 답했습니다:
봇 말이 맞고, 원인은 #940입니다.
무엇이 잘못됐나
#940에서 bridge가 crush 설정을 못 읽는 문제를 고치면서 레포의
crush/crushrc.readonly를 물렸습니다. 그 파일은 이름 그대로 agent-cron 비대화형 러너용입니다:정작 그 레인의 실제 정책은 반대입니다 (dungae 실측):
같은 정책에서 **Codex 레인은
approval=never+sandbox=dangerFullAccess**를 받습니다 (bot_access.py의_codex_approval_policy/_codex_sandbox_policy). crush만 읽기 전용으로 두면 프로바이더를 바꿨을 때 능력이 줄어듭니다.#938이슈에서 제가 이걸 *"bridge 레인에만 읽기전용 경계가 없다"*고 보안 구멍으로 규정했는데, 반대였습니다. 그 레인은 의도적으로 넓은 레인입니다.수정
crush/crushrc.bridge를 추가하고crush_runtime의 기본값을 그리로 옮깁니다.question만 막습니다 — bridge에 응답 경로가 없어 모델이 쓰면 턴 전체가 실패합니다(fix(crush): 비대화형 경로에서 question 도구 숨김 #934)안전장치는 그대로입니다
crush의 permission 요청은 여전히
_decide_permission→ bridge approval handler로 가고, 운영자의bash_policy가 판단합니다:auto-approveApprovalDecision.ALLOW(안 물음)approve-eachDENY이 파일은 "무엇을 제안할 수 있는가"만 정하고, 실행 여부는 bridge 정책이 정합니다. 오히려 Codex보다 통제 지점이 하나 더 있습니다 — Codex는
never면 아예 안 묻지만, crush는approve-each로 바꾸면 매번 확인이 됩니다.실측
crushrc.bridgecrushrc.readonly테스트
4건 추가:
question은 막는다crushrc.bridge다관련
🤖 Generated with Claude Code