fix(authz): gate the cli-tools status and skills execution routes to LOCAL_ONLY - #13745
Merged
Merged
Conversation
…LOCAL_ONLY GHSA-35fw-cv32-2373 and GHSA-jx89-f37j-pq89 — the same defect class as /api/acp/agents (GHSA-hf57): a route whose handler chain spawns a host process was classified Tier 3 MANAGEMENT only, and requireManagementAuth() waives auth when requireLogin=false. Hard Rules #15/#17 require the LOCAL_ONLY gate, which runs on the stamped real peer before any auth check. cli-tools (GHSA-35fw): 14 routes reach getCliRuntimeStatus() -> locateCommand() -> runProcess("sh", ["-c", 'command -v -- "$1"']) -> spawn(), exactly like their six gated siblings (forge/grok-build/jcode/qwen/omp/letta-settings): all-statuses, status, and the claude/cline/codewhale/codex/crush/deepseek-tui/ droid/kilo/openclaw/pi/smelt-settings routes. The advisory counted 13; it missed /api/cli-tools/detect, which is heavier — detectAllTools() runs execFile(binary, ["--version"]) and execFile("which") per tool. skills (GHSA-jx89): POST /api/skills/install stores the request's handlerCode verbatim as the skill handler with no allowlist, so a value equal to a built-in name (execute_command / eval_code) aliases the real sandboxed built-in; POST /api/skills/executions then runs it. The sandbox is a real container, but the spawn is transitive, which is why the 6A.8 source scan never flagged it. Entries are exact paths, not a /api/cli-tools/ blanket prefix: apply, backups, config, guide-settings, hermes-agent-settings, keys, logs, openclaw/auto-order and codex-profiles do not spawn and remote dashboards use them. All 16 are mirrored into SPAWN_CAPABLE_PREFIXES (no manage-scope bypass) and added to the route-guard-membership roots so the gate enforces them from now on. Functional trade-off, same one already accepted for grok/forge/jcode/qwen: a dashboard served through a tunnel no longer shows the CLI Tools status badges. Tests are red-first. Two existing negative controls pointed at routes that turn out to spawn (/api/cli-tools/all-statuses, /api/skills/install); they now point at routes that genuinely do not (/api/cli-tools/config, /api/skills/marketplace, /api/skills/skillssh/install), so the non-over-gating assertions are kept.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…LOCAL_ONLY (diegosouzapw#13745) GHSA-35fw-cv32-2373 and GHSA-jx89-f37j-pq89 — the same defect class as /api/acp/agents (GHSA-hf57): a route whose handler chain spawns a host process was classified Tier 3 MANAGEMENT only, and requireManagementAuth() waives auth when requireLogin=false. Hard Rules diegosouzapw#15/diegosouzapw#17 require the LOCAL_ONLY gate, which runs on the stamped real peer before any auth check. cli-tools (GHSA-35fw): 14 routes reach getCliRuntimeStatus() -> locateCommand() -> runProcess("sh", ["-c", 'command -v -- "$1"']) -> spawn(), exactly like their six gated siblings (forge/grok-build/jcode/qwen/omp/letta-settings): all-statuses, status, and the claude/cline/codewhale/codex/crush/deepseek-tui/ droid/kilo/openclaw/pi/smelt-settings routes. The advisory counted 13; it missed /api/cli-tools/detect, which is heavier — detectAllTools() runs execFile(binary, ["--version"]) and execFile("which") per tool. skills (GHSA-jx89): POST /api/skills/install stores the request's handlerCode verbatim as the skill handler with no allowlist, so a value equal to a built-in name (execute_command / eval_code) aliases the real sandboxed built-in; POST /api/skills/executions then runs it. The sandbox is a real container, but the spawn is transitive, which is why the 6A.8 source scan never flagged it. Entries are exact paths, not a /api/cli-tools/ blanket prefix: apply, backups, config, guide-settings, hermes-agent-settings, keys, logs, openclaw/auto-order and codex-profiles do not spawn and remote dashboards use them. All 16 are mirrored into SPAWN_CAPABLE_PREFIXES (no manage-scope bypass) and added to the route-guard-membership roots so the gate enforces them from now on. Functional trade-off, same one already accepted for grok/forge/jcode/qwen: a dashboard served through a tunnel no longer shows the CLI Tools status badges. Tests are red-first. Two existing negative controls pointed at routes that turn out to spawn (/api/cli-tools/all-statuses, /api/skills/install); they now point at routes that genuinely do not (/api/cli-tools/config, /api/skills/marketplace, /api/skills/skillssh/install), so the non-over-gating assertions are kept.
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.
Fixes two privately reported advisories of the same class as
/api/acp/agents(GHSA-hf57):GHSA-35fw-cv32-2373 and GHSA-jx89-f37j-pq89. A route whose handler chain spawns a host
process sat on Tier 3 MANAGEMENT only, and
requireManagementAuth()waives auth underrequireLogin=false. Hard Rules #15/#17 require the LOCAL_ONLY gate, which runs on the stampedreal peer before any auth check.
GHSA-35fw — 14 cli-tools routes
All reach
getCliRuntimeStatus() → locateCommand() → runProcess("sh", ["-c", 'command -v -- "$1"']) → spawn(),exactly like the six siblings already gated (forge / grok-build / jcode / qwen / omp / letta):
all-statuses,statusclaude/cline/codewhale/codex/crush/deepseek-tui/droid/kilo/openclaw/pi/smelt-settingsdetect— missed by the advisory, and the heaviest:detectAllTools()runsexecFile(binary, ["--version"])andexecFile("which")per tool.The advisory's precondition is also narrower than stated: a fresh install is only open from
loopback, so the realistic exposure is an instance explicitly running with
requireLogin=false.GHSA-jx89 — skills install + executions
POST /api/skills/installstoreshandlerCodeverbatim as the handler with no allowlist, so avalue equal to a built-in name (
execute_command,eval_code) aliases the real sandboxedbuilt-in;
POST /api/skills/executionsthen runs it. The sandbox is a genuine container(
--network none,--cap-drop ALL, read-only, allowlisted images), but the spawn is transitive,so the 6A.8 route-guard source scan never saw it.
Change
LOCAL_ONLY_API_PREFIXES, mirrored inSPAWN_CAPABLE_PREFIXES(no manage-scope bypass).check-route-guard-membership.tsroots, so the gate now enforces them./api/cli-tools/prefix —apply,backups,config,guide-settings,hermes-agent-settings,keys,logs,openclaw/auto-orderandcodex-profilesdo not spawnand remote dashboards use them.
ROUTE_GUARD_TIERS.md,x-loopback-onlyinopenapi.yaml, changelog fragment.Functional trade-off (same one already accepted for grok/forge/jcode/qwen): a dashboard served
through a tunnel no longer shows the CLI Tools status badges, and skill execution history is
loopback/LAN only.
Validation (TDD)
route-guard-cli-tools-settings-local-only+route-guard-skills-execute-local-onlyon the tipcheck:route-guard-membershipcheck:openapi-security-tierstypecheck:core,check:docs-sync, prettierTwo existing negative controls pointed at routes that turn out to spawn
(
/api/cli-tools/all-statuses,/api/skills/install). They were replaced, not removed, withroutes that genuinely do not spawn (
/api/cli-tools/config,/api/skills/marketplace,/api/skills/skillssh/install), so the non-over-gating assertions still hold.