Repository navigation
Routing parity: TS path-wide exec deny; Go matchEndpoint accepts endpoint subpaths #49
Description
Activity
- addedPriority: P3Added to issues and PRs relating to a low severity bugs.Added to issues and PRs relating to a low severity bugs.Type: BugAdded to issues and PRs if they are addressing a bugAdded to issues and PRs if they are addressing a bug
on Oct 2, 2026 A third case in this family, confirmed during the #51 merge-gate review by running the Go router directly:
DELETE /containers/<name>/exec— Go's exec check (matchEndpoint(path, "containers", "exec")) matchesexeconly in the name position, so this path takes the lifecycle branch and is allowed and forwarded. Rust (path.contains("/exec")after the/containers/prefix check) and TypeScript (path.includes("/exec")) both deny it.- Related:
POST /containers/<name>/execis denied in Go too, but by default-deny rather than the exec check — the three implementations agree on the status for that one, for different reasons.
Observed outputs (Go router, built from main):
DELETE /containers/mycontainer/exec -> Allow (forwarded) POST /containers/mycontainer/exec -> Deny "endpoint ... is not allowed" DELETE /containers/exec -> Deny "exec is not allowed"So when converging the exec rule, the decision isn't only about TS being too broad — Go is too narrow.
spec/README.md's Modeling Notes (post-#51) document this divergence and point here. Notedeploy/test.sh'sPOST /containers/*/exec -> 403check passes for different reasons per language today.Decided design (2026-10-08). Probe of all three routers on
main:Request Go Rust TS DELETE /containers/mycontainer/execAllow Deny Deny POST /containers/exec-runner/startAllow Deny Deny DELETE /containers/exec-runnerAllow Deny Deny GET /containers/exec-runner/jsonAllow Deny Deny GET /images/execAllow Allow Deny GET /exec/abc/jsonAllow Allow Deny POST /containers/create/extraCreateContainer Deny Deny POST /images/create/extraAllow Deny Deny New case: Rust (
contains("/exec")) and TS (includes("/exec")) are substring checks. A container whose name starts withexec(exec-runner,executor) therefore cannot be started, removed or inspected through them, although the daemon allows such names.Rule, matching whole path segments (after the version strip):
- Exec is denied when the segments are
containers/<name>/exec, for any method, or when the first segment isexec, for every method including GET. Exec inspect leaks command lines: container inspect listsExecIDs, andGET /exec/<id>/jsonthen shows the full arguments, including inline secrets (verified live)./containers/execstays a reserved name. - Exact endpoints:
POST /containers/createandPOST /images/creatematch only with exactly two segments. Anything longer falls to the default deny. - Spec rows first (
spec/router.qnt), then a shared test table in all three languages plus integration checks.
The per-socket exec feature is split out to #65.
- Exec is denied when the segments are
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Problem
Two further pre-existing routing divergences between the equal-peer implementations, surfaced during the merge-gate review of #43 (both set aside there as out of scope):
execanywhere in the path.ts/src/proxy.ts(~line 35) rejects any path containing an/execsegment, so e.g.GET /images/execis denied in TypeScript but allowed (passthrough) in Go and Rust.matchEndpointaccepts subpaths of exact endpoints.POST /containers/create/extrareachesrouteCreatein Go, while Rust and TypeScript exact-match the endpoint and fall through to default-deny.Impact
Low severity. Neither is exploitable by itself (gates still apply to whatever is routed), but which requests reach the daemon — and which gate chain they go through — depends on the implementation language, which the equal-peers rule exists to prevent. Behaviour should converge; in both cases the stricter behaviour looks correct (deny
POST /containers/create/extraeverywhere; scope TS's exec check to the positions Go/Rust check).Proposed solution
Decide the canonical row for each case, converge all three implementations, and pin each row with same-named unit tests in all three languages plus integration checks — the #24/#43 pattern. The Quint routing model should gain the two rows as well so the table stays the source of truth.
Alternatives considered
Which implementation(s) would this affect?