fix: accept the user's own gh/glab CLI install - #630
Conversation
GPT 5.6 Review — ✅ human override acceptedHuman judgment by @dwu96 overrides the GPT 5.6 finding for This comment is updated in place on each push. The model was not re-run because an authorized human decision supersedes it. False positive or not applicable? A repository writer can comment: |
Arbiter — ✅ no blocking findingsArbiter found no unresolved long-term items that require action before merging Second-order review for Review detailsBoth files read. The line-level reviewers raised nothing (Opus 5: no findings; GPT 5.6: single finding overridden by an authorized human), and the design review passed with two explicitly follow-up-scoped suggestions. Neither suggestion is a one-way door (an env-var knob and a duplicated trust policy are both freely changeable in a later PR — the governance Arbiter-Verdict: PASS No sub-threshold finding meets the long-term-impact bar. Suggested follow-ups (open as issues — non-blocking)
[ARBITER-REVIEWED] ea3f95b False positive or not applicable? A repository writer can comment: For a broader accepted-risk deferral, apply |
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS A real adoption-killing policy is replaced with a proportionate, well-benchmarked trust model; the weakened guarantee is explicitly documented and reversible via strict mode. The security delta is genuinely near-zero for this deployment shape: the agent can already run the user's Suggestions
[DESIGN-REVIEWED] ea3f95b |
Opus 5 Review — ✅ no blocking findingsReviewed No findings. Verdict recorded via the action's structured output for commit False positive or not applicable? A repository writer can comment: |
|
Pushed Accepted and fixed
Not taking: GPT 5.6 #1 — "restore rejection of gateway-writable executables and ancestors" That is the exact behaviour this PR exists to remove, so restoring it would revert the PR rather than fix it. The requirement driving the change is: if The comparison point is deliberate: Codex applies no binary-provenance check whatsoever — shell tool calls become So the residual risk is stated rather than eliminated: an executable the agent plants in a user-owned Happy to take a narrower hardening if reviewers prefer one — e.g. adding a deny rule so the agent cannot write a file named |
23ba58c to
b2740cc
Compare
The PR/MR panel and Issue Radar refused any provider CLI whose binary or parent directories were not root-owned and unwritable by the gateway user. A stock `brew install gh` can never satisfy that: /opt/homebrew is owned by the user and bin/gh is a symlink into Cellar/. Users who had gh installed and authenticated were told to sudo-copy it into a privileged libexec dir and re-copy it after every CLI upgrade, so most simply could not use the feature. Codex, for comparison, applies no provenance check at all: shell tool calls resolve through the user's PATH and trust is carried by the OS sandbox, approval gates, and host-bound credential brokering. Its one anti-tampering check rejects a binary found *inside the workspace*. Adopt that model. Resolution is now override -> well-known install dirs -> ambient PATH, and validation accepts the gateway user's own install (symlinked Homebrew/Linuxbrew/asdf layouts included) while refusing provenance the user did not choose: a binary or ancestor owned by another unprivileged account, anything world-writable (a world-writable directory is tolerated only when sticky, where the owner check still decides), and anything inside the agent-writable project checkout or workspace root. A root gateway stays refused outright in both modes: every process it spawns is root too, which would make those checks vacuous. Containment carries the boundary instead -- provider children still get only a minimal provider-scoped env and every spawn stays SEL-audited. Dev Fleet's separate git/gh resolver gains the Homebrew and Linuxbrew prefixes for the same reason, and now pins the RESOLVED target rather than the bin-dir entry it searched, so a user-writable symlink cannot be repointed between validation and execution. KIROCREW_PROVIDER_BIN_STRICT=1 restores the previous root-owned, symlink-free policy for shared or multi-tenant hosts, and its setup error keeps the privileged-copy instructions.
b2740cc to
ea3f95b
Compare
|
Pushed Fixed — the launcher regression was real and my fault. Opus 5, the Arbiter and GPT 5.6 #2 all flagged the same hunk: Also in this revision (from the previous round): unconditional root-gateway refusal restored, Dev Fleet now caches the resolved target rather than the bin-dir symlink, and a sticky world-writable ancestor ( Not taking — GPT 5.6's remaining item ("restore Issue Radar's root-owned candidate resolution"). That is the change this PR exists to make, so implementing it would revert the PR rather than fix it. The requirement: if The Arbiter reached the same conclusion and explicitly declined to escalate it: "this is the PR's deliberate, documented policy change, not an accident … reversible by flipping an env default later; it is not a one-way door." Residual risk is stated rather than removed: an executable planted in a user-owned Gates: Follow-up accepted as a separate issue, not this PR: converge Dev Fleet's |
|
/ai-review override gpt ea3f95b: Accepting a user-owned gh/glab is this PR's deliberate, documented trust-model change (a Homebrew/asdf install is gateway-writable by construction, so restoring the root-owned rule would revert the PR) — provenance is replaced by containment the PR keeps: agent project/workspace-tree refusal, foreign-owner and world-writable refusal, unconditional root-gateway refusal, a minimal provider-scoped child env with no inherited PATH, per-spawn SEL audit, and KIROCREW_PROVIDER_BIN_STRICT=1 to restore the old policy; the Arbiter reviewed the same finding and explicitly declined to escalate it as "the PR's deliberate, documented policy change … not a one-way door". |
|
AI-review override not recorded: keep the reason to 500 characters or fewer. |
|
/ai-review override gpt ea3f95b: Accepting a user-owned gh is this PR's deliberate documented policy change (a Homebrew/asdf install is gateway-writable by construction, so restoring the root-owned rule would revert the PR); containment is kept — agent-tree, foreign-owner, world-writable and root-gateway refusals, minimal provider-scoped env, SEL audit, plus KIROCREW_PROVIDER_BIN_STRICT=1 to restore the old policy — and the Arbiter reviewed this same finding and declined to escalate it. |
Human judgment recorded@dwu96 marked the gpt AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
|
This change is based on offline discussions with @CrysisDeu to offer better UX when users using GitHub Human reviewer please check PR description, AI review comments and overriding information. |
The PR/MR panel and Issue Radar refused any provider CLI whose binary or parent directories were not root-owned and unwritable by the gateway user. A stock `brew install gh` can never satisfy that: /opt/homebrew is owned by the user and bin/gh is a symlink into Cellar/. Users who had gh installed and authenticated were told to sudo-copy it into a privileged libexec dir and re-copy it after every CLI upgrade, so most simply could not use the feature. Codex, for comparison, applies no provenance check at all: shell tool calls resolve through the user's PATH and trust is carried by the OS sandbox, approval gates, and host-bound credential brokering. Its one anti-tampering check rejects a binary found *inside the workspace*. Adopt that model. Resolution is now override -> well-known install dirs -> ambient PATH, and validation accepts the gateway user's own install (symlinked Homebrew/Linuxbrew/asdf layouts included) while refusing provenance the user did not choose: a binary or ancestor owned by another unprivileged account, anything world-writable (a world-writable directory is tolerated only when sticky, where the owner check still decides), and anything inside the agent-writable project checkout or workspace root. A root gateway stays refused outright in both modes: every process it spawns is root too, which would make those checks vacuous. Containment carries the boundary instead -- provider children still get only a minimal provider-scoped env and every spawn stays SEL-audited. Dev Fleet's separate git/gh resolver gains the Homebrew and Linuxbrew prefixes for the same reason, and now pins the RESOLVED target rather than the bin-dir entry it searched, so a user-writable symlink cannot be repointed between validation and execution. KIROCREW_PROVIDER_BIN_STRICT=1 restores the previous root-owned, symlink-free policy for shared or multi-tenant hosts, and its setup error keeps the privileged-copy instructions.
Problem
The Sidebar PR/MR panel and Issue Radar refuse any
gh/glabwhose binary and every parent directory is not root-owned and unwritable by the gateway user (_validate_provider_executable). A stock Homebrew install can never satisfy that:So a user who has
ghinstalled andgh auth login-ed still got a wall ofsudoinstructions — copy the binary into a privilegedlibexecdir,chown root, and repeat after everybrew upgrade. In practice most people just could not use the feature.What Codex does
Investigated
openai/codexbefore changing policy:zsh -lc "<cmd>"(codex-rs/core/src/shell.rs) andghis resolved by the shell from the user'sPATH. Its own probes call baregh(codex-rs/tui/src/branch_summary.rs).TrustLevelis per project dir) and command shapes (is_safe_command), never to binary provenance.bwrap+seccomp (which explicitly(allow process-exec)), network off by default, and host-bound credential brokering that replaces the real GitHub credential with a shaped dummy in the child env.bwrapdiscovery — rejects a candidate only when it resolves inside the workspace (codex-rs/sandboxing/src/bwrap.rs), i.e. it defends against a malicious repo shadowing the helper, not against user-installed tooling.Change
Same model, minus the parts we cannot rely on:
Resolution — override (
KIROCREW_GH_BIN/KIROCREW_GLAB_BIN/KIROCREW_ISSUE_RADAR_GH) → well-known install dirs → ambientPATH. A managed root-owned copy still wins when one exists; otherwise the install the user already runs from their terminal is found (Homebrew, Linuxbrew, asdf/mise,~/.local/bin).Validation — accept the gateway user's own install, including symlinked layouts. Refuse only provenance the user did not choose:
/tmp-style plantingbwraprule)Unchanged — provider children still receive only the minimal provider-scoped environment (no AWS/Slack/gateway secrets, no inherited
PATH), host pinning, subprocess/payload bounds, and the SEL audit event per spawn.Escape hatch —
KIROCREW_PROVIDER_BIN_STRICT=1restores the previous policy exactly (canonical, symlink-free, root-owned through every ancestor,PATHnever consulted) and its setup error keeps the privileged-copy instructions.Dev Fleet has a separate resolver (
_trusted_bin) whose dir list was/usr/local/bin:/usr/bin:/bin— no Homebrew prefix at all, so a brewghwas invisible there too. It gains/opt/homebrew/binand/home/linuxbrew/.linuxbrew/bin; its existing resolved-target checks (not writable by us, never under$HOME) already accept a brew binary (-r-xr-xr-x) and reject a planted shim.The tradeoff is explicit: a shim the user (or the agent) plants in a user-owned
PATHdir outside the project/workspace tree — e.g.~/.local/bin— is now accepted, exactly as it is when the user typesghin their terminal. That is the requirement this PR implements.Testing
test/test_source_providers.py— rewritten provider-executable suite: accepts user-owned and symlinked (Homebrew-layout) installs; rejects foreign-uid, world-writable file, world-writable parent, and agent-writable-tree shims; PATH hits appended after the well-known dirs; PATH ignored in strict mode; install-guidance vs strict sudo-guidance error copy.test/test_issue_radar_gh_bin.py(new) — Issue Radar's_gh_bin: PATH-found user-owned install accepted, agent-tree shim refused, missing-ghguidance, strict mode still root-owned-only, override failure →GhSetupError(reason="not_installed"), cache behaviour.test/test_dev_fleet_app.py— trusted-dir list now asserts the Homebrew/Linuxbrew prefixes are present in both_TRUSTED_BIN_DIRSand the pinned_TRUSTED_PATH.gh(user-owned symlink intoCellar/) now resolves; strict mode rejects it; a shim inside the workspace tree and a0777/tmpshim are rejected.flake8clean,mypy src/kiro_crewclean (496 files), full suite18959 passed— the 4 remaining failures (test_mcp_apps_call_endpointAF_UNIX path-length,test_module_loaderreload) reproduce unchanged on pristinemainin this environment.Docs:
docs/system-specs/modules/issue-radar.md,docs/system-specs/modules/learn-cron-dashboard.md, CHANGELOG entry under Unreleased → Fixes.