You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Consolidated follow-ups from the six-leg swarm review of the #76 final head (6347d1a) — full report + per-finding dispositions on #76. Related: #67–#84 (delivery), PR #85 + #86 + #87 (already-remediated findings), #71 (the capability-registry direction several of these feed).
Security
S1 (HIGH) — repo-weaponization threat model is undocumented and the opencode permission-abort boundary is event-driven, not policy-enforced. An owned opencode serve runs with cwd = target repo + full process.env; opencode merges the project'sopencode.json, so a malicious repo can ship {"permission":{"bash":"allow"}} (no permission.updated event ever fires → the adapter's abort never trips) and hostile mcp entries, while AGENTS.md content flows into the worker prompt. claude/codex workers analogously inherit the repo's .claude/settings.json under the user's trust posture. Options: pin a serve-time restrictive permission policy for owned servers (e.g. --permission scoped to edit-only within the cwd), or document the "ak run only in repos you trust with full user privileges" contract in ADR-0018 + run help. (src/lib/execution/opencode.mjs:158,258-262,285-287)
S2 (MED) — port-reservation TOCTOU can hand a rogue local process the per-run Basic credential, the full worker prompt, and the ability to fabricate terminal SSE evidence. Probe-bind-close-rebind (defaultReservePort) races under maxConcurrent. Harden: watch child.exitCode during waitForHealth, or serve --port 0 and parse the bound port from child stdout. (src/lib/execution/opencode.mjs:21-30,122-133,255-258)
S3 (LOW) — SSE per-line buffer is unbounded (memory-DoS amplifier; owned-server context). Cap like the 256 KB subprocess cap. (opencode.mjs:167-169)
Execution correctness
Windows shim-awareness gap in the execution layer (two reviewers converged): resolveShim (PATHEXT-aware) is used by have() but never exported or applied at launch; bare spawnFn('claude'|'codex'|'opencode') and 'codex' missing from CMD_SHIMS. Windows: readiness passes, launch ENOENTs. Fix: export + apply in subprocess/opencode launch paths, add codex to CMD_SHIMS, add a win32 spawn assertion. (src/lib/exec.mjs:21-38, subprocess.mjs:99, opencode.mjs:258)
maxTurns is silently dropped by ak run — templates carry per-node caps (6–15) that claude-flow-codex dual honored; no execution adapter reads them. Translate to --max-turns in claude/codex argumentsFor (opencode has no equivalent — document) or drop the field. (routing.mjs:398-424,452)
$-pattern substitution corrupts the opencode worker prompt — template.replace('{{task}}', worker.prompt) treats $&$`$' as metachars. Use replacer functions. (opencode.mjs:72)
--timeout has no upper bound — values >2³¹−1 ms are clamped by Node to ~1 ms, turning a huge timeout into an instant timeout for every worker. Cap in positiveInt. (run.mjs:37-41)
Persisted dualRouting is never validated at load — a hand-edited kit.json with {model:123} crashes plan printing far from the cause. Schema-check in buildRunPlan. (run.mjs, routing.mjs)
cancel/cleanup HTTP is unbounded — /abort + /instance/dispose have no timeout (a wedged server hangs the runner's timeout branch). Same class as court B4 (launch-phase fetches); fix together. (opencode.mjs:299,307)
No construction invariant tying canRouteActivities hosts to EXECUTION_ADAPTERS keys — a routable host with no execution adapter loads, validates, and materializes, then every worker fails cli_unavailable at runtime. Same failure class as court A3, one axis over; exactly Architecture: capability-driven host, provider, binding, and observability adapters #71's trap. (execution/adapters.mjs:7-11)
Test coverage gaps (verified — each names the missing assertion)
Session-isolation: foreign sessionID SSE events must be ignored (currently untested — deleting the filter terminates on any session's idle). (opencode-execution.test.mjs)
Plan-validation guards: duplicate id / unknown dep / self-dep / bad maxConcurrent / dependency-cycle throw. (execution-runner.test.mjs)
Summary
Consolidated follow-ups from the six-leg swarm review of the #76 final head (
6347d1a) — full report + per-finding dispositions on #76. Related: #67–#84 (delivery), PR #85 + #86 + #87 (already-remediated findings), #71 (the capability-registry direction several of these feed).Security
opencode serveruns withcwd= target repo + fullprocess.env; opencode merges the project'sopencode.json, so a malicious repo can ship{"permission":{"bash":"allow"}}(nopermission.updatedevent ever fires → the adapter's abort never trips) and hostilemcpentries, whileAGENTS.mdcontent flows into the worker prompt. claude/codex workers analogously inherit the repo's.claude/settings.jsonunder the user's trust posture. Options: pin a serve-time restrictive permission policy for owned servers (e.g.--permissionscoped to edit-only within the cwd), or document the "ak runonly in repos you trust with full user privileges" contract in ADR-0018 + run help. (src/lib/execution/opencode.mjs:158,258-262,285-287)defaultReservePort) races undermaxConcurrent. Harden: watchchild.exitCodeduringwaitForHealth, or serve--port 0and parse the bound port from child stdout. (src/lib/execution/opencode.mjs:21-30,122-133,255-258)opencode.mjs:167-169)Execution correctness
resolveShim(PATHEXT-aware) is used byhave()but never exported or applied at launch; barespawnFn('claude'|'codex'|'opencode')and'codex'missing fromCMD_SHIMS. Windows: readiness passes, launch ENOENTs. Fix: export + apply insubprocess/opencodelaunch paths, addcodextoCMD_SHIMS, add a win32 spawn assertion. (src/lib/exec.mjs:21-38,subprocess.mjs:99,opencode.mjs:258)maxTurnsis silently dropped byak run— templates carry per-node caps (6–15) thatclaude-flow-codex dualhonored; no execution adapter reads them. Translate to--max-turnsin claude/codexargumentsFor(opencode has no equivalent — document) or drop the field. (routing.mjs:398-424,452)$-pattern substitution corrupts the opencode worker prompt —template.replace('{{task}}', worker.prompt)treats$&$`$'as metachars. Use replacer functions. (opencode.mjs:72)--timeouthas no upper bound — values >2³¹−1 ms are clamped by Node to ~1 ms, turning a huge timeout into an instant timeout for every worker. Cap inpositiveInt. (run.mjs:37-41)dualRoutingis never validated at load — a hand-edited kit.json with{model:123}crashes plan printing far from the cause. Schema-check inbuildRunPlan. (run.mjs,routing.mjs)/abort+/instance/disposehave no timeout (a wedged server hangs the runner's timeout branch). Same class as court B4 (launch-phase fetches); fix together. (opencode.mjs:299,307)canRouteActivitieshosts toEXECUTION_ADAPTERSkeys — a routable host with no execution adapter loads, validates, and materializes, then every worker failscli_unavailableat runtime. Same failure class as court A3, one axis over; exactly Architecture: capability-driven host, provider, binding, and observability adapters #71's trap. (execution/adapters.mjs:7-11)Test coverage gaps (verified — each names the missing assertion)
sessionIDSSE events must be ignored (currently untested — deleting the filter terminates on any session's idle). (opencode-execution.test.mjs)execution-runner.test.mjs)routing.test.mjs)cli_unavailablepath. (runner suites)['SIGTERM','SIGKILL'], not just the orphan verdict). (opencode-execution.test.mjs:120-137)auth_required/cancelledcategories,session.errormapping, SSE multi-chunk splits,run.mjsexit-1/2 paths, schema type rejections + frozen immutability.Docs/migration notes (LOW)
HOST_PROVIDERduplicated in two places,swapHostModelis two-host-only) — fold into Architecture: capability-driven host, provider, binding, and observability adapters #71's redesign rather than patch.--parallelis silently dropped onak run(informational).Acceptance
$-prompt / timeout-clamp / dualRouting-load-validation / cancel-cleanup bounds fixed.