You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Go/Rust strip any /v…/ first segment as an API version (/volumes/… routed as a container action) #57
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.
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:
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.
Describe the bug
Go and Rust strip any first path segment that starts with
vas if it were an API-version prefix:stripAPIVersion,go/internal/proxy/router.go:161-169:HasPrefix(path, "/v")+SplitNstrip_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/foois routed as "delete containerfoo", allowed iffoomatches a policy, and forwarded with its original path. The daemon runs a volume removal for a volume namedcontainers/foo.This bypasses the default-deny: volume DELETEs are otherwise denied. It is harmless with the built-in
localdriver, 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+)?/, andts/src/proxy.test.tspins that with TS-only rows.Affected implementation(s)
To Reproduce
Go router on
main(a654ff8):Same requests sent straight to the daemon (Docker 29.4.0, API 1.54, min 1.40):
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
stripAPIVersionand Ruststrip_api_versionto the TS rule. TS production code is unchanged.Unit tests: one table, identical in all three languages:
DELETE /v1/containers/foo,DELETE /v1.43/containers/fooDELETE /volumes/containers/foo,/version/containers/foo,/v1.2.3/containers/foo,/vabc/containers/foo,/v/containers/foo,/v1./containers/fooThe 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 todeploy/test.sh. Go and Rust currently forward it, and the daemon answers 404.spec/router.qntmodels paths after the prefix is stripped.Alternatives considered
^/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.Environment
a654ff8Additional 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.