fix(setup): ask for the unsandboxed-exec opt-in on a backend-less host - #1527
Conversation
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Consent-at-setup fixes the discoverability gap without weakening the fail-closed posture; classification logic correctly stays in [DESIGN-REVIEWED] 56be8ea |
Opus 5 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: |
d41c130 to
679c40c
Compare
Disposition of every concern raised on
|
679c40c to
c997398
Compare
c997398 to
451725e
Compare
Round 2 dispositions —
|
451725e to
84e87d3
Compare
Round 3 dispositions —
|
A fresh install on a host with no OS-level sandbox backend -- any Windows host, or a Linux kernel that refuses user namespaces -- had no working MCP tooling: wrap_argv fail-closes, so `kirocrew doctor` reported all three managed servers failing and dev-fleet answered "no reachable backend". The remedy existed but was undiscoverable. Defaulting agent.sandbox_allow_unsandboxed_exec to true on win32 would fix that symptom by deleting a deny-by-default authorization. The mode="strict" callers depend on it: dashboard/handlers/worktree.py picks strict precisely so a repo-controlled include.path cannot make git read ~/.aws/credentials, and the Papyrus compile path picks it so a crafted .tex cannot typeset a secret into a PDF. On win32 detect_backend() is always "none", so a platform default would unwrap every one of those spawns with nothing in its place. So the default stays platform-independent False and `kirocrew setup` gains a consent step. It asks detect_backend() -- platform knowledge stays with the probe rather than moving into the config layer -- and on "none" states what becomes unconfined (~/.aws, ~/.ssh) before prompting, defaulting to no. The key is written only on an explicit yes; declining, a bare Enter, and a non-interactive EOF all leave the config untouched, so the effective default stays fail-closed. It never re-asks once the key is present in either state, and runs before the --agent-only early return because the servers it unblocks are the ones install_agent() just wrote. security.md, security-deep-dive.md and windows-install.md are updated in the same commit: the posture they document is unchanged, but the wizard is now the primary path to the opt-in rather than hand-editing config.json. Closes #1525
84e87d3 to
56be8ea
Compare
Round 4 dispositions —
|
kirodotdev#1527) A fresh install on a host with no OS-level sandbox backend -- any Windows host, or a Linux kernel that refuses user namespaces -- had no working MCP tooling: wrap_argv fail-closes, so `kirocrew doctor` reported all three managed servers failing and dev-fleet answered "no reachable backend". The remedy existed but was undiscoverable. Defaulting agent.sandbox_allow_unsandboxed_exec to true on win32 would fix that symptom by deleting a deny-by-default authorization. The mode="strict" callers depend on it: dashboard/handlers/worktree.py picks strict precisely so a repo-controlled include.path cannot make git read ~/.aws/credentials, and the Papyrus compile path picks it so a crafted .tex cannot typeset a secret into a PDF. On win32 detect_backend() is always "none", so a platform default would unwrap every one of those spawns with nothing in its place. So the default stays platform-independent False and `kirocrew setup` gains a consent step. It asks detect_backend() -- platform knowledge stays with the probe rather than moving into the config layer -- and on "none" states what becomes unconfined (~/.aws, ~/.ssh) before prompting, defaulting to no. The key is written only on an explicit yes; declining, a bare Enter, and a non-interactive EOF all leave the config untouched, so the effective default stays fail-closed. It never re-asks once the key is present in either state, and runs before the --agent-only early return because the servers it unblocks are the ones install_agent() just wrote. security.md, security-deep-dive.md and windows-install.md are updated in the same commit: the posture they document is unchanged, but the wizard is now the primary path to the opt-in rather than hand-editing config.json. Closes kirodotdev#1525
What is the problem?
On a host with no OS-level sandbox backend — any Windows host, or a Linux kernel that refuses user namespaces — a fresh install has zero working MCP tooling.
wrap_argv()fail-closes, sokirocrew doctorreports every managed server failing:and Dev Fleet answers
{"error": "app 'dev-fleet' has no reachable backend"}.The refusal is correct. What was missing is discoverability: the one config key that resolves it was something the user had to find on their own.
Why this issue matters to the user
Every Windows user hit this on first run, and the failure looked like a bug rather than a decision awaiting their input. Note that the message half of this has since been fixed on
main(_PROBE_ERROR_MAX_CHARS = 1200— the old 200-char cap truncated the remedy sentence mid-word), so the remedy text now reachesdoctor. What remains is that acting on it still means hand-editingconfig.jsonbefore anything works.How our fix solves it (symptom → root cause → change)
Symptom: managed MCP servers, Dev Fleet and the Papyrus compiler all refuse to spawn on a fresh Windows install.
Root cause:
detect_backend()is always"none"onwin32, andagent.sandbox_allow_unsandboxed_execdefaults toFalse, sowrap_argv()raises for every non-offmode. Correct posture, undiscoverable remedy.Why not a platform default. Deriving the fallback from
sys.platformdeletes a deny-by-default authorization and puts nothing in its place. Themode="strict"callers depend on it specifically:dashboard/handlers/worktree.pypicksstrictso a repo-controlledinclude.pathcannot makegitread~/.aws/credentials.texcannot typeset a secret into a PDFOn
win32those would all have become unconfined spawns that no operator ever declared.The change. The default stays platform-independent
False, andkirocrew setupgains_setup_sandbox_consent():detect_backend()— platform knowledge stays with the probe rather than moving into the config layer (per the Design Review suggestion)"none", states what becomes unconfined (~/.aws,~/.ssh) and prompts, defaulting to no_input_or_skip→None) all leave the config untouched--agent-onlyearly return, because the servers it unblocks are the onesinstall_agent()just wroteagentsection rather than clobbering itNet effect: the posture the specs describe is unchanged — unconfined execution stays operator-declared — but the decision is now presented once, in context, with its cost stated.
What tests we did
New —
test/test_sandbox_unsandboxed_exec_consent.py(12 tests): no prompt when a backend exists; no prompt when the key is alreadytrueor alreadyfalse;y/YESwrite the opt-in; unrelated config keys preserved across the write;n, bare Enter, and EOF all write nothing; the decline path names both the config key and the absolute config path; the prompt names~/.awsand~/.ssh; a non-dictagentsection is left untouched.Extended —
test_config_loader.py::test_sandbox_allow_unsandboxed_exec_default_is_platform_independent: a ratchet asserting the default isFalseunderwin32,linuxanddarwin, with and without anagentsection. A futuresys.platform == "win32"fallback now fails this test instead of shipping.Local gate:
pyteston the 10 files that touchcli_setup→ 302 passed, 2 skipped.isort --check-only,flake8 -j 1,mypyon both changed modules → clean.scripts/docs_lint.py --test,scripts/docs-lint.sh(181 files),scripts/scrub-lint.sh --no-history→ all pass.Docs updated in the same commit (the same-commit spec rule):
docs/system-specs/modules/security.md,docs/architecture/security-deep-dive.md,docs/guides/windows-install.md. The posture they document is unchanged; the wizard is now the primary path to the opt-in and hand-editingconfig.jsonis documented as the manual equivalent.Rebased onto current
main(was 13 behind).Any other suggestions on the work
worktree.pyand Papyrus stay fail-closed. That means adding an authorization axis to a security-critical function with ~30 call sites, so it wants its own design pass rather than riding along here.kirocrew setupfrom a terminal (the packaged Electron app). Same fail-closed default, same explicit yes; only the surface differs.kirocrew setupon a backend-less host and viakirocrew doctorbefore/after.Closes #1525