Skip to content

fix: match exec and create endpoints on whole path segments (#49) - #67

Merged
abienkowski merged 8 commits into
mainfrom
fix/exec-and-exact-endpoints
Oct 9, 2026
Merged

abienkowski merged 8 commits into
mainfrom
fix/exec-and-exact-endpoints

Conversation

@abienkowski

@abienkowski abienkowski commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The three routers disagreed about exec and the create endpoints, and in some cases each was wrong in its own way:

Request Go Rust TS Now (all three)
DELETE /containers/mycontainer/exec Allow Deny Deny Deny
POST /containers/exec-runner/start Allow Deny Deny Allow
DELETE /containers/exec-runner Allow Deny Deny Allow
GET /containers/exec-runner/json Allow Deny Deny Allow
GET /images/exec Allow Allow Deny Allow
GET /exec/abc/json Allow Allow Deny Deny
POST /containers/create/extra CreateContainer Deny Deny Deny
POST /images/create/extra Allow Deny Deny Deny
  • Go: matchEndpoint looked only at the first two segments. As a result, exec was matched only as the container name, and create accepted trailing segments.
  • Rust and TS: they used substring checks for /exec. A container whose name starts with exec (exec-runner, executor) could therefore not be started, removed or inspected through them, even though Docker allows such names.

Rule: after the #53 percent check and the version strip, match on whole path segments.

  • Exec is denied for every method, with exec is not allowed, when either:
    • the first segment is exec, or
    • the first segment is containers and any later segment is exactly exec.
  • Create is exact: POST /containers/create and POST /images/create match only with exactly two segments.
  • Code changes:
    • Go: matchEndpoint is replaced by isExecPath and exact matches, and removed.
    • Rust: is_exec_path.
    • TS: isExecPath.

Closes #49

Behaviour change, not breaking: Go and Rust now deny exec inspect (GET /exec/<id>/json), as TS already did. No Docker CLI command uses it. Through the raw API it leaked the full command line of exec instances started directly on the daemon, including inline secrets: I checked this live, by finding exec IDs with container inspect and then reading the instance. The per-socket exec feature is tracked separately in #65.

Spec first (spec/router.qnt)

  • The exec check and the exact create route come before the lifecycle branch and the GET passthrough, with a new outcome, denyExec.
  • Eight shared rows (execSubpathDeleteDenied, execSubpathPostDenied, execPrefixName{Start,Delete,Get}Allowed, execNamespace{Get,Post}Denied, createSubpathDenied) and properties execNeverAllowed, execPrefixNamesRoute, createOnlyExact.
  • Non-vacuous: each property fails under Rust/TS's substring rule, under Go's name-only rule, or under Go's prefix match for create.
  • README: the old Modeling Note documenting the DELETE …/exec divergence is replaced.
  • make test-spec: listener_locked 11, listener_unlocked 10, router 27, router_pre48 15, router_pre53 15 passing.

Tests (written first; RED commits 3ff44e2, 7ae6d5d)

  • Router table, identical in Go, Rust and TS: the 8 spec rows plus language-only rows: GET /images/exec, POST /images/create/extra, GET /containers/myexec/json, DELETE /containers/executor, the reserved GET /containers/exec/json, and GET /executor.
    • Exec deny rows compare the exact message. Go's generic default-deny message (endpoint POST /containers/x/exec is not allowed) also ends in "exec is not allowed", so a substring check would have passed Go before the fix.
    • Before the fix, each language failed 6 of the 14 rows.
  • deploy/test.sh, 39 → 43 checks: DELETE /containers/no-such/exec → 403, POST /containers/create/extra → 403, POST /containers/exec-nosuch/start → 404 (the daemon answered), and GET /exec/<id>/json → 403. Before the fix: Go 40/3, Rust 41/2, TS 42/1, each failing exactly its known divergences.

Verification

  • Re-probe after the fix: all three routers agree on 19 paths, including /v1.45/exec/abc/json, trailing slashes (/containers/x/exec/, /containers/create/, /images/create/), /executor, /images/exec, and /. Go's POST /containers/create/ (trailing slash) is now denied, matching Rust and TS. The Docker CLI never sends it.
  • Docker CLI through the Go proxy:
    • docker start exec-nosuch returns the daemon's "No such container".
    • docker exec <running container> true returns exec is not allowed.
    • docker version succeeds.

Follow-ups recorded

Squash-merge required: the RED commits fail by design. It needs no footers, so the default squash message (PR title plus commit bullets) is fine and releases v0.3.1.

Type of change

  • Bug fix

Implementation(s) changed

  • Go
  • Rust
  • TypeScript
  • Quint specification
  • CI / infrastructure (deploy/test.sh)

Testing

  • Unit tests pass (make test-all): Go 108, Rust 145, TS 162 (1 skipped)
  • Integration tests pass: make test-integration, -rs and -ts each ALL 43 TESTS PASSED
  • Quint: make test-spec (above); make verify runs in CI
  • Lint passes: make lint-all
  • New tests added for the change

Checklist

  • I have read CONTRIBUTING.md
  • My code follows the project's coding style
  • I have updated documentation as needed (README Endpoint Access, spec/README.md, AGENTS.md counts)

@abienkowski abienkowski added Priority: P3 Added to issues and PRs relating to a low severity bugs. Type: Bug Added to issues and PRs if they are addressing a bug labels Oct 8, 2026
@abienkowski abienkowski self-assigned this Oct 9, 2026
@abienkowski
abienkowski merged commit 3a410f1 into main Oct 9, 2026
7 checks passed
@abienkowski
abienkowski deleted the fix/exec-and-exact-endpoints branch October 9, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P3 Added to issues and PRs relating to a low severity bugs. Type: Bug Added to issues and PRs if they are addressing a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant