fix: prevent STQL injection and reachable panics in service check/discovery - #145
Conversation
…covery The service snapshot query interpolated the service id raw into an STQL expression (id = "<id>") which was then hand-formatted into a JSON request body. A service id containing quotes/backslashes could break out of the STQL string literal and/or corrupt the JSON body. Escape the id as an STQL string literal and build the body by JSON-encoding the query value, closing the injection at both layers. Also harden several reachable panics: - guard against an empty Components slice before indexing [0] in the service status check - guard service.Identifiers and a short ApiBaseUrl before indexing in toMetric - use strings.TrimPrefix instead of manual slice offsets when parsing cluster/namespace identifiers in discovery (the old form could panic on an unexpected identifier and crash the discovery refresh) - stop calling res.String() on the transport-error path, where resty can return a nil response
|
Claude finished @joshiste's task in 2m 2s —— View job PR Review
SummaryThe fixes are correct and well-scoped. No prior review threads to resolve. STQL/JSON injection fix (
One minor note: Panic guards (
Tests ( Good coverage: unit test for No blocking issues. The PR is well-reasoned and addresses all five audit findings cleanly. |
getAllServices called res.String() on the transport-error path, where resty can return a nil response (the same issue fixed in the service check). Drop res.String() there too.
|
Good catch — fixed in 5a7608e. The other two notes are intentionally left as-is: |
|



Addresses the stackstate audit MAJOR findings.
1. STQL / JSON injection (MAJOR)
GetServiceSnapshotinterpolated the service id raw into an STQL expression(id = "<id>"), andexecuteSnapshotQuerythen hand-built the JSON request body with"query": "%v". A service id (a target attribute) containing quotes/backslashes could break out of the STQL string literal and/or corrupt the JSON body.Fix:
stqlString(serviceId)renders the id as an escaped string literal (via JSON string escaping) so it can't break out of the STQL literal.executeSnapshotQuerynow JSON-encodes the query value into the body ("query": %s) instead of raw interpolation. The static metadata envelope is unchanged (it has no dynamic values — confirmed the body template has exactly one format verb).2. Reachable panics (MAJOR)
Components[0]indexed without an empty check → now returns a clear error when StackState returns no components.service.Identifiers[0]indexed without a check → now guarded (URL attribute degrades to empty).ApiBaseUrl[:len-3]could underflow on a short URL → now length-guarded.service_discovery.toServicestripped the cluster/namespace URN prefixes with manual slice offsets that panic on an unexpected identifier (crashing the background discovery refresh) → nowstrings.TrimPrefix.res.String()was called on the transport-error path where resty can return a nil response → removed there (kept on the!IsSuccess()path whereresis non-nil).Testing
go build ./...,go vet,gofmtclean.extservice/common_test.go:stqlStringescaping, and a malicious service id (1") OR (1=1) keeps the request body valid JSON and stays inside a single escaped STQL literal.go test ./extservice/passes (existing mocked tests + new ones)./simplify
4-agent pass: reuse/simplification/efficiency clean. Altitude confirmed the body template has no other dynamic value (injection fully closed) and the other guards are right-depth. Two notes:
stqlStringcomment to not overclaim general STQL escape-semantics (the ids in practice are numeric/URN, so it's effective regardless).ApiBaseUrl[:len-3]chop is kept behavior-preserving (length-guarded) here, but it leaves a latent issue — for the documented…/apibase URL it strips to…/and the built UI link gets a double slash. A cleaner fix isstrings.TrimSuffix(ApiBaseUrl, "/api"). Left out to avoid a behavior change in this security PR; happy to do it as a follow-up.