Repository navigation
fix(rs): an empty path segment is not a container name - #51
Merged
Merged
Conversation
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #48.
Rust's
extract_container_namereturnedSome("")for paths with an empty name segment, so the lifecycle branch treated the empty string as an unknown container and forwarded the request to the Docker daemon. Go and TypeScript denied the same requests. Observed on the RED commit, from the daemon's answers through the Rust proxy:The fix is one condition in
rs/src/proxy.rs: an empty second segment is not a container name.Spec first, then a failing test, then the fix
The commits are in that order, deliberately:
spec/router.qnt(new). The existing spec routes on:nametemplates and never defines how a name is extracted from a path, which is why neither Go router doesn't exclude reserved path segments in extractContainerName (cross-language parity) #24 nor this bug could be stated in it. The new module models container-name extraction and the container-lifecycle routing branch over segment lists, with two instances:router(the intended rule) androuter_pre48(pre-fix Rust,ALLOW_EMPTY_NAME = true). A soundness property — a lifecycle route is only ever taken for a real, non-reserved name — holds onrouterand fails onrouter_pre48, so the property is load-bearing, not decorative. Eight table-rowruntests cover Rust extract_container_name treats empty segment as a container name (DELETE /containers/ is forwarded) #48 and Go router doesn't exclude reserved path segments in extractContainerName (cross-language parity) #24; the same row names appear as comments on the Go, Rust and TypeScript test cases, so every row is traceable across all four. Wired intomake typecheck,make test-spec, CI andrelease-verify.test:). Same-named unit tests in all three languages plus two integration checks. Go and TypeScript passed unchanged; Rust failed exactly the two new tests for the Rust extract_container_name treats empty segment as a container name (DELETE /containers/ is forwarded) #48 reason (left: Allow / right: Deny,left: Some("") / right: None), and Rust's integration run showed the two requests reaching the daemon. This commit is intentionally failing in Rust — see the merge note below.fix(rs):).!parts[1].is_empty()added to the existing condition. No Go or TypeScript production code changed; the reserved set stays exactlycreate/json/execin all three.Type of change
Implementation(s) changed
Testing
make test-all)make test-integration)make verifyunchanged;make test-speccovers the new module)runtestsAll three integration suites pass 32/32 after the fix, with the Rust run showing
DELETE /containers/ -> 403andPOST /containers//start -> 403. The RED-state evidence (Rust failing, Go/TS green at 32/32) is reproducible at642abd2(check out that commit and runcd rs && cargo test empty).Non-vacuity of the spec was checked both ways:
router_pre48failssoundTestand bothemptyName*rows when they are run against it, and a mutation check (dropping the reserved-set clause) failssoundTestplus the three reserved rows.Found during review (not in this PR)
Two cross-language divergences surfaced by the whole-branch review, both verified by running the code; follow-up issues to be filed:
stripAPIVersiononly matches undotted versions (/^\/v\d+\//), but the Docker CLI always sends dotted ones (/v1.43/…).DELETE /v1.43/containers/foo,POST /v1.43/containers/beacon/startandPOST /v1.43/containers/createare all denied by the TS proxy today. Existing tests miss it because their dotted-version requests are GETs, which the passthrough allows for the wrong reason.DELETE /containers/%2Fto an empty name and denies; Rust and TypeScript treat%2Fas a literal name and allow — the same empty-name family reached through URL encoding.Checklist