fix(hooks): keep the tool-approval import chain free of the cryptography wheel - #8819
fix(hooks): keep the tool-approval import chain free of the cryptography wheel#8819bolichen97 wants to merge 2 commits into
Conversation
…phy wheel hooks.on_tool_call -- the function every ACP tool call passes through for its verdict -- lazily imports kiro_crew.slack.gateway for _is_read_only_tool. That module reaches, at import time, wecom.media and weixin.media (through channels) and secrets.vault (through autonudge -> irq -> cron_script), and all three imported cryptography.hazmat at module top for their AES routines. So on a host whose `cryptography` native wheel does not load (a platform-mismatched build: the AL2 x86_64 wheel on an Apple-silicon Mac, where dlopen says "slice is not valid mach-o file") EVERY tool approval raised ImportError -- not just a WeCom media download. Move the three imports inside the functions that use them. The dependency did not go away, it moved to the only paths that pay for it: decrypting a WeCom/Weixin media file and reading or writing a vault entry. Tests run in a subprocess with `cryptography` made unimportable: the approval hook's import chain loads and _is_read_only_tool answers; each decrypt path still fails clearly with an ImportError naming cryptography; and a static AST check over wecom/, weixin/, slack/, secrets/ and channels.py refuses a top-level import from growing back (auth/store.py is not on the chain and keeps its).
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Fixes the cryptography instance but keeps the structural cause: the approval verdict still imports the entire channel tree to reach a string heuristic. Watch
Suggestions
[DESIGN-REVIEWED] e6b3994 |
GPT 5.6 Review — 🔴 changes requested (blocking)GPT 5.6 found at least one blocking issue that must be resolved before merging This comment is updated in place on each push. BLOCKING -- test/test_approval_chain_no_cryptography.py:46 -- subprocess writes bytecode outside test isolation FINDING -- src/kiro_crew/wecom/media.py:124 -- function-local [GPT-REVIEWED] e6b3994 Adjudication (Opus 4.8) — is blocking on each finding proportionate?Adjudicable block is empty (0 findings). One fenced finding, F1. F1 —
Verdict: FLAG — the finding is real but its residual risk (gitignored bytecode the residue guard cannot even see, produced by any Python run) is one a repository writer would plausibly accept; the trivial False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS The fix is real but stops one level short: the approval verdict still imports the entire channel tree to run a 30-line string check that lives in What this change shipsIntent: make every tool approval survive a host where the
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] e6b3994 |
A python child on Windows writes its traceback in whatever code page it has; decoding it with text=True and no encoding= raised UnicodeDecodeError inside the assertion instead of showing the traceback.
UX Review (Fable 5) — ✅ PASSUX-level review of All evidence reviewed: the diff, all six theme screenshots, both terminal-menu screenshots, and the surrounding SidePanel/theming code. Emitting the review. UX-Verdict: PASS White-canvas swap and per-chat Terminal are coherent, screenshot-proven, and every invisible-surface edge case (bubble, capsules, edit box, pinned prompt, steer) is handled and pinned. Suggestions
[UX-REVIEWED] e6b3994 |
|
Folded into #8799 (commits cherry-picked verbatim: |
Summary
hooks.on_tool_call— the verdict path for every ACP tool call — lazily importskiro_crew.slack.gatewayfor_is_read_only_tool. At import time that module reacheswecom.mediaandweixin.media(viachannels) andsecrets.vault(viaautonudge → irq → cron_script), and all three importedcryptography.hazmatat module top for their AES routines. On a host whosecryptographynative wheel does not load (a platform-mismatched build — the AL2 x86_64 wheel on an Apple-silicon Mac,dlopen: slice is not valid mach-o file) every tool approval raisedImportError, not just a WeCom media download. Found because the Amazon edition's end-to-end drafter tests, which run the real approval chain, failed on every Mac dev host.Change
The three imports move inside the functions that use them (
wecom.media.decrypt_media,weixin.media.decrypt_aes_ecb,SecretVault._encrypt_entry/_decrypt_entry). The dependency did not go away; it moved to the only paths that pay for it.Tests
test/test_approval_chain_no_cryptography.py, in a subprocess withcryptographymade unimportable via ameta_pathfinder:slack.gateway,channels, bothmediamodules,secrets.vault) loads and_is_read_only_tool("Read")answers;ImportErrornamingcryptography;wecom/,weixin/,slack/,secrets/andchannels.pyrefuses a top-level import from growing back (auth/store.pyis not on the chain and keeps its).Existing
test_wecom_media.py/test_secret*.py: 202 pass. flake8, isort, black gate clean.Pattern harvest
on_tool_callwas careful to importslack.gatewaylazily, but the transitive top-level imports made the security path depend on a native wheel that had nothing to do with approving a tool. When a hot or security-critical path imports a large module lazily, the property to protect is "that module's import chain has no optional/native dependency at module scope" — and it is cheap to pin with a subprocess that blocks the dependency.try: import cryptography except ImportError: cryptography = None) makes every caller check forNone; a function-scope import keeps the failure exactly where the capability is exercised and the error message names the missing piece.Rule candidate: a module imported lazily on a security-critical or hot path must itself have no optional/native dependency at module scope -- pin it with a subprocess test that blocks the dependency and imports the chain.
Not generalizable: which specific modules sat on this chain (wecom/weixin media, secrets.vault) is repository history, not a rule.