fix(security): run the connection test's local CLI probe only for local callers (GHSA-jmq6-8j86-8xqj) - #14995
Merged
diegosouzapw merged 4 commits intoSep 28, 2026
Conversation
…al callers (GHSA-jmq6-8j86-8xqj) testSingleConnection() calls getCliRuntimeStatus() for cline / qoder connections, which spawns sh -c 'command -v -- "$1"' on the host — the LOCAL_ONLY capability of Hard Rules #15/#17. It is reached from three routes that stay remotely reachable (a tunnel-served dashboard tests connections): POST /api/providers/{id}/test, POST /api/providers/test-batch and the background test after POST /api/providers. None is in LOCAL_ONLY or SPAWN_CAPABLE, so on requireLogin=false an anonymous remote caller could trigger the spawn and learn whether the CLI is installed. Gate the probe, not the route: getRequestPeerLocality() (apiAuth, same trusted signals and order as isLoopbackRequest, which now delegates to it) returns loopback | lan | remote, and the three routes pass allowLocalRuntimeProbe = locality !== "remote" — the LOCAL_ONLY semantics. Remote callers get the upstream test without the local runtime diagnosis; the credential-health scheduler keeps the probe (default true). Tests (5) fail on the old code; the ambiguous-runtime, activation, docker bootstrap loopback and keyless /v1/models suites stay green. Reported-by: zer0d4y5
Refs GHSA-jmq6-8j86-8xqj
…s guard The guard splits the route source on the getProviderRuntimeStatus() call; the call now passes the caller-locality options (GHSA-jmq6-8j86-8xqj). The early-return it pins is unchanged. Refs GHSA-jmq6-8j86-8xqj
…er-test-local-runtime-probe # Conflicts: # stryker.conf.json
diegosouzapw
pushed a commit
that referenced
this pull request
Sep 28, 2026
…d connections (#14941) Maintainer rework: real merge of release/v3.8.51, keeping the #14995 local-caller restriction on the CLI runtime probe alongside the operator-disable marker. Trimmed test/route.ts to fit its frozen file-size ceiling with no behavior change. The 3 new tests fail on the base route and pass with this change. Focused suites pass 44/44 (15/15 re-run on the latest tip). typecheck:core, open-sse typecheck, eslint and file-size are clean; the only red, response-sanitizer.test.ts over the file-size cap, is inherited from the tip. Thank you @shipsfromrio!
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 GHSA-jmq6-8j86-8xqj (reported by @zer0d4y5).
Problem
For
cline/qoderconnections,testSingleConnection()callsgetCliRuntimeStatus(), which spawnssh -c 'command -v -- "$1"'on the host. That is the LOCAL_ONLY capability that Hard Rules #15/#17 keep away from non-local callers. Three remotely reachable routes run it:POST /api/providers/{id}/test(the one in the report);POST /api/providers/test-batch;POST /api/providers.None of them is in
LOCAL_ONLY_*orSPAWN_CAPABLE_*. On an instance withrequireLogin=false, an anonymous remote caller could trigger the spawn and learn whether the CLI is installed. As the reporter says, this is not command injection: the tool id comes from a fixed map and is passed as an argv element.Fix: gate the probe, not the route
Adding only
/testto the local-only lists would leavetest-batchand provider creation open. It would also remove the "Test connection" button from tunnel-served dashboards for every provider. Instead:getRequestPeerLocality(request)insrc/shared/utils/apiAuth.tsreturnsloopback | lan | remote. It uses the same trusted signals, in the same order, asisLoopbackRequest(), which now delegates to it with identical verdicts. It fails closed toremote, and client-supplied locality headers are not trusted.allowLocalRuntimeProbe = locality !== "remote", which matches the LOCAL_ONLY semantics (loopback or private LAN, never through a reverse proxy).true).The reporter's other suggestion, a build-time check that derives spawn reachability and fails when a route reaches a spawn sink without being gated, would have caught this class earlier. It is worth doing as its own change; it is not part of this fix.
Validation
tests/unit/provider-test-local-runtime-probe.test.ts, 5 cases:provider-401-ambiguous-runtime,verified-connection-activation-11446,docker-bootstrap-loopback-14296,v1-models-keyless-loopback-13354(20 tests).