Skip to content

Routing parity: TS path-wide exec deny; Go matchEndpoint accepts endpoint subpaths #49

Description

@abienkowski

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):

  1. TS denies exec anywhere in the path. ts/src/proxy.ts (~line 35) rejects any path containing an /exec segment, so e.g. GET /images/exec is denied in TypeScript but allowed (passthrough) in Go and Rust.
  2. Go's matchEndpoint accepts subpaths of exact endpoints. POST /containers/create/extra reaches routeCreate in 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/extra everywhere; 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?

  • Go
  • Rust
  • TypeScript

Activity

  1. added
    Priority: P3Added to issues and PRs relating to a low severity bugs.
    Type: BugAdded to issues and PRs if they are addressing a bug
    on Oct 2, 2026
  2. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    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")) matches exec only 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>/exec is 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. Note deploy/test.sh's POST /containers/*/exec -> 403 check passes for different reasons per language today.

  3. abienkowski commented on Oct 8, 2026

    @abienkowski
    CollaboratorAuthor

    Decided design (2026-10-08). Probe of all three routers on main:

    Request Go Rust TS
    DELETE /containers/mycontainer/exec Allow Deny Deny
    POST /containers/exec-runner/start Allow Deny Deny
    DELETE /containers/exec-runner Allow Deny Deny
    GET /containers/exec-runner/json Allow Deny Deny
    GET /images/exec Allow Allow Deny
    GET /exec/abc/json Allow Allow Deny
    POST /containers/create/extra CreateContainer Deny Deny
    POST /images/create/extra Allow Deny Deny

    New case: Rust (contains("/exec")) and TS (includes("/exec")) are substring checks. A container whose name starts with exec (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 is exec, for every method including GET. Exec inspect leaks command lines: container inspect lists ExecIDs, and GET /exec/<id>/json then shows the full arguments, including inline secrets (verified live). /containers/exec stays a reserved name.
    • Exact endpoints: POST /containers/create and POST /images/create match 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Priority: P3Added to issues and PRs relating to a low severity bugs.Type: BugAdded to issues and PRs if they are addressing a bug

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions