Skip to content

fix(proxy): exclude reserved path segments from Go container name extraction - #43

Merged
abienkowski merged 2 commits into
mainfrom
fix/reserved-path-segments
Oct 3, 2026
Merged

abienkowski merged 2 commits into
mainfrom
fix/reserved-path-segments

Conversation

@abienkowski

@abienkowski abienkowski commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #24.

The divergence

/containers/<x> is ambiguous: <x> is usually a container name, but Docker also has reserved endpoints at that position — /containers/json lists, /containers/create creates. Rust (rs/src/proxy.rs:212) and TypeScript (ts/src/proxy.ts:125) already excluded create/json/exec. Go excluded nothing:

extractContainerName("/containers/json")   = "json"
extractContainerName("/containers/create") = "create"
extractContainerName("/containers/exec")  = "exec"

It was worse than the issue described

#24 said Go returned Allow where Rust/TS returned Deny, and flagged that this was diffed rather than tested. Testing it showed the practical effect is sharper: Allow here means the request is actually forwarded to the Docker daemon.

Reverting the fix and running the integration suite:

FAIL: DELETE /containers/json -> 403 (reserved, not a container) (expected 403, got 404)

That 404 comes from Docker, not from the proxy — Go proxied a DELETE through to the daemon that Rust and TypeScript refused to forward at all. It happens to be harmless because no container is named json, but the request reached the daemon.

Path: reserved word taken as a container name → lifecycle branch → routeByContainerName → unknown container → ActionAllow.

The fix

Go now excludes the same three segments, with a comment pointing at the Rust and TypeScript equivalents so the three stay linked.

GET /containers/json is unaffected: with the reserved word excluded it falls through to the GET/HEAD passthrough, so listing still works. That is asserted in all three languages and in the integration suite.

Tests

Counts against current main (05320aa), re-measured after rebasing onto it on 2026-10-02:

before after
Go 97 99
Rust 135 137
TypeScript 154 (1 skipped) 155 (1 skipped) — proxy suite 26 → 27
integration 27 ×3 30 ×3

After the rebase, make test-all and make lint-all pass, and all three integration suites (make test-integration{,-rs,-ts}) pass 30/30, including the three new reserved-segment checks. AGENTS.md's Test Coverage section is updated in this PR.

The same table of cases is asserted in all three languages, so the parity is now pinned rather than incidental:

DELETE /containers/json Deny
DELETE /containers/create Deny
GET /containers/json Allow (list endpoint)
DELETE /containers/mycontainer Allow (real container)
GET /containers/mycontainer/json Allow (reserved only in the name position)

Verified the tests fail without the fix — Go unit tests RED on all three reserved words, and the integration suite reports 404 instead of 403.

Verification

  • make test-all, make lint-all — pass
  • make test-integration{,-rs,-ts} — 30/30 each, identical output across all three

Scope note

Docker also has POST /containers/prune, which none of the three excludes. I checked it rather than assuming: it is consistently denied in all three (POST, no suffix match, falls to default-deny), so there is no parity gap and no behaviour change needed. Left alone deliberately.

…raction

/containers/<x> is ambiguous: <x> is usually a container name, but Docker also
has reserved endpoints at that position — /containers/json lists containers and
/containers/create creates one. Rust and TypeScript already excluded create,
json and exec; Go did not.

The consequence was not just a different verdict. Treating the reserved word as
a container name sends the request down the lifecycle branch, where an unknown
container falls through to Allow, so Go forwarded DELETE /containers/json to
the daemon. Confirmed end to end: before the fix the integration suite gets 404
back from Docker itself, where Rust and TypeScript return 403 without ever
forwarding.

GET /containers/json is unaffected — with the reserved word excluded it reaches
the GET/HEAD passthrough, so listing still works.

Adds the same parity tests to all three languages plus three integration cases,
as the issue suggested. Verified the tests fail without the fix: the Go unit
tests report "create"/"json"/"exec" as container names, and the integration
suite reports 404 instead of 403.

Closes #24
@abienkowski
abienkowski force-pushed the fix/reserved-path-segments branch from 68ba311 to b4dc457 Compare October 2, 2026 22:12
@abienkowski

Copy link
Copy Markdown
Collaborator Author

Rebased onto 05320aa (post-#47 main) and re-verified on 2026-10-02: make test-all / make lint-all green, integration suites 30/30 in Go, Rust and TypeScript. Added b4dc457 updating AGENTS.md's test counts. Review follow-ups filed as #48 (Rust forwards DELETE /containers/ — empty name treated as a container) and #49 (TS path-wide exec deny; Go matchEndpoint subpath acceptance).

@abienkowski
abienkowski merged commit 36059fa into main Oct 3, 2026
6 checks passed
@abienkowski
abienkowski deleted the fix/reserved-path-segments branch October 3, 2026 11:20
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.

Go router doesn't exclude reserved path segments in extractContainerName (cross-language parity)

1 participant