Skip to content

Go/Rust strip any /v…/ first segment as an API version (/volumes/… routed as a container action) #57

Description

@abienkowski

Describe the bug

Go and Rust strip any first path segment that starts with v as if it were an API-version prefix:

  • Go stripAPIVersion, go/internal/proxy/router.go:161-169: HasPrefix(path, "/v") + SplitN
  • Rust strip_api_version, rs/src/proxy.rs:200-207: strip_prefix("/v") + first /

As a result, the router classifies a request differently from the Docker daemon. DELETE /volumes/containers/foo is routed as "delete container foo", allowed if foo matches a policy, and forwarded with its original path. The daemon runs a volume removal for a volume named containers/foo.

This bypasses the default-deny: volume DELETEs are otherwise denied. It is harmless with the built-in local driver, because volume names cannot contain /. A volume plugin that accepts / in names would let a caller delete a volume through a path the policy treats as a container action.

TypeScript is not affected. Since #52 it strips only ^/v\d+(\.\d+)?/, and ts/src/proxy.test.ts pins that with TS-only rows.

Affected implementation(s)

  • Go
  • Rust
  • TypeScript
  • All

To Reproduce

Go router on main (a654ff8):

DELETE /volumes/containers/foo  -> Allow (routed as container "foo")
DELETE /v1.2.3/containers/foo   -> Allow
DELETE /version/containers/foo  -> Allow

Same requests sent straight to the daemon (Docker 29.4.0, API 1.54, min 1.40):

DELETE /volumes/containers/foo     404 {"message":"get containers/foo: no such volume"}   <- volume route
DELETE /version/containers/foo     404 page not found
DELETE /v1.45/containers/no-such   404 No such container: no-such
DELETE /v1.45.0/containers/no-such 404 No such container: no-such   <- daemon version segment is [0-9.]+
DELETE /v1.2.3/containers/no-such  400 client version 1.2.3 is too old
DELETE /vabc/… and /v/…            404 page not found

Expected behavior

The proxy strips a prefix only when the daemon would also treat it as a version. All three languages use one rule, ^/v\d+(\.\d+)?/, the one TS already uses. Every other first segment is routed as is, so a non-GET request falls to the default deny.

Proposed solution

  • Change Go stripAPIVersion and Rust strip_api_version to the TS rule. TS production code is unchanged.
  • Tests first:
    • Unit tests: one table, identical in all three languages:

      • Allow: DELETE /v1/containers/foo, DELETE /v1.43/containers/foo
      • Deny: DELETE /volumes/containers/foo, /version/containers/foo, /v1.2.3/containers/foo, /vabc/containers/foo, /v/containers/foo, /v1./containers/foo

      The TS-only rows and their "do not copy as parity" comments from TypeScript proxy denies dotted API-version paths (/v1.43/...) that the Docker CLI always sends #52 move into this shared table.

    • Integration: add DELETE /volumes/containers/x → 403 to deploy/test.sh. Go and Rust currently forward it, and the daemon answers 404.

  • Quint: no change. spec/router.qnt models paths after the prefix is stripped.

Alternatives considered

  • Mirror the daemon exactly (^/v[0-9.]+/). Rejected. It would match the daemon's routing perfectly, but it means loosening TS and reversing the /v1.2.3/ row that TypeScript proxy denies dotted API-version paths (/v1.43/...) that the Docker CLI always sends #52 just pinned. The stricter rule fails closed: a daemon-valid form such as /v1.45.0/ is default-denied, and no real client sends it.
  • Document the divergence only. Rejected. The proxy and the daemon disagree about which resource a request acts on, which is the class of bug the default-deny design exists to rule out.

Environment

  • OS: macOS (OrbStack)
  • Docker version: 29.4.0 (API 1.54)
  • Implementation version/commit: a654ff8

Additional context

Split from #55, where the comment from the #52 work records the Go router evidence. See also #24 and #48, which were the same kind of cross-language convergence.

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: P2Added to issues and PRs relating to a medium 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