Skip to content

fix: strip only numeric API-version prefixes in Go and Rust (#57) - #58

Merged
abienkowski merged 5 commits into
mainfrom
fix/version-prefix-overstrip
Oct 7, 2026
Merged

abienkowski merged 5 commits into
mainfrom
fix/version-prefix-overstrip

Conversation

@abienkowski

Copy link
Copy Markdown
Collaborator

Description

Go stripAPIVersion and Rust strip_api_version stripped any first path segment starting with /v, as if it were an API version. So DELETE /volumes/containers/foo was classified as "delete container foo", allowed, and forwarded. The daemon runs it as a volume removal: a direct test returned 404 get containers/foo: no such volume. The proxy and the daemon disagreed about which resource the request acts on.

Fix: Go and Rust now strip one leading /v<N>/ or /v<N>.<M>/, with ASCII digits only. That is the rule TypeScript has used since #52 (^/v\d+(\.\d+)?/). Anything else is left as is, so non-GET requests fall to the default deny. Both fixes are short byte scans with no regex and no new dependencies. TS production code is unchanged.

Closes #57

Before / after (Go and Rust router)

                                    before   after
DELETE /volumes/containers/foo      Allow    Deny
DELETE /version/containers/foo      Allow    Deny
DELETE /v1.2.3/containers/foo       Allow    Deny
DELETE /v1.43/containers/foo        Allow    Allow

In Go, a real HTTP request DELETE /v%D9%A1/containers/foo reaches the router as /v١/…, because net/http decodes r.URL.Path. Before this change Go allowed it. Now the handler returns 403 and doesn't forward the request. Rust and TS route the raw request target, so they see %D9%A1. That decoding difference already existed, isn't introduced here, and is tracked in #53.

Tests (written first; RED commits 9c1a21d, d5567ec)

  • One 16-row table, identical in all three languages: Go TestRouteVersionedPaths, Rust test_route_versioned_paths, TS routes dotted API-version paths like the unversioned ones. It extends the TypeScript proxy denies dotted API-version paths (/v1.43/...) that the Docker CLI always sends #52 table, and the TypeScript proxy denies dotted API-version paths (/v1.43/...) that the Docker CLI always sends #52 "TS-only, do not copy as parity" rows are now ordinary shared rows.
    • Allow: /v1/, /v1.43/, /v10.0/ (multi-digit major).
    • Deny: /volumes/…, /version/…, /v1.2.3/…, /vabc/…, /v/…, /v1./…, /v١/… (non-ASCII digit), and /v1/v1.43/… (strip once).
    • Before the fix: TS passed. Go failed 7 of the 8 Deny rows with Allow; the strip-once row passed, because the old code also stripped only once. Rust failed at the first of them.
    • Wrong fixes the table catches: undotted-only, dot-required, single-digit major, the daemon's looser ^/v[0-9.]+/, \d*, Unicode \d, and stripping more than once.
  • deploy/test.sh, 35 → 36 checks: DELETE /volumes/containers/no-such-container → 403. Before the fix, Go and Rust forwarded it and the daemon answered 404 (35 PASSED, 1 FAILED). TS already passed it.
  • Quint: no change. spec/router.qnt models paths after the version prefix is stripped.

Alternative rejected: copying the daemon's ^/v[0-9.]+/ exactly. It would have meant loosening TS and reversing #52's /v1.2.3/ row. The stricter rule fails closed: a form the daemon accepts but no client sends, such as /v1.45.0/, gets default-denied.

Also in this PR: c8d9e76 corrects a pre-existing docs drift. go/main_test.go has 24 tests, but the docs said 23 (Go total 102 → 103) ever since #47. It's a separate commit because it touches the same lines.

Squash-merge required: the RED commits fail in Go and Rust by design.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Implementation(s) changed

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

Testing

  • Unit tests pass (make test-all):
    ok  	github.com/ChainSafe/docker-socket-policy/go	1.626s
    ok  	github.com/ChainSafe/docker-socket-policy/go/internal/audit	0.383s
    ok  	github.com/ChainSafe/docker-socket-policy/go/internal/middleware	0.555s
    ok  	github.com/ChainSafe/docker-socket-policy/go/internal/policy	0.682s
    ok  	github.com/ChainSafe/docker-socket-policy/go/internal/proxy	0.859s
    test result: ok. 140 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s
    # tests 157
    # pass 156
    # fail 0
    # skipped 1
    
  • Integration tests pass: make test-integration, make test-integration-rs and make test-integration-ts each print ALL 36 TESTS PASSED. That includes GET /v1.45/_ping -> 200, which confirms nothing is now under-stripped.
  • Quint verification (make verify): not run locally, since the spec is unchanged. It runs in CI. make test-spec passes all four instances (11 passing, 10 passing, 9 passing, 7 passing; exit 0).
  • Lint passes: make lint-all exits 0 (go vet, cargo check, tsc --noEmit)
  • 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 (integration count, Go unit count)

@abienkowski abienkowski added Priority: P2 Added to issues and PRs relating to a medium severity bugs. Type: Bug Added to issues and PRs if they are addressing a bug labels Oct 7, 2026
@abienkowski abienkowski self-assigned this Oct 7, 2026
@abienkowski
abienkowski merged commit 204f24a into main Oct 7, 2026
6 checks passed
@abienkowski
abienkowski deleted the fix/version-prefix-overstrip branch October 7, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P2 Added to issues and PRs relating to a medium 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/Rust strip any /v…/ first segment as an API version (/volumes/… routed as a container action)

1 participant