Skip to content

Deferred minor findings from the #43/#47/#50/#51 review cycles #55

Description

@abienkowski

Problem

The review processes for #45 (PR #47), #24 (PR #43), #39 (PR #50) and #48 (PR #51) produced a set of deliberately deferred Minor findings — real but not merge-blocking. Recording them here so they stop living only in session logs.

Cross-language parity (smallest first)

  • HEAD on a named container: HEAD /containers/x — Go and TS allow via the GET/HEAD passthrough; Rust denies in its lifecycle branch (rs/src/proxy.rs catch-all). Related to Routing parity: TS path-wide exec deny; Go matchEndpoint accepts endpoint subpaths #49.
  • Numeric gid edge parity (--listen-socket-group): negative values — Go rejects with "negative gid", Rust/TS fail via name lookup; leading + — all three now reject (post-feat!: dockerd-parity listening socket, Unix path only #47 fix) but via different paths; oversized values fail differently per language. Behaviourally all deny; messages differ.
  • Name-lookup gid range: Go's pure-Go resolver and TS's /etc/group parser don't range-check a gid obtained by name lookup (the digits-only path does). Exploiting it requires control of /etc/group.
  • Probe "other connect error" messages differ by OS error wording (Go connect: permission denied vs Rust Permission denied (os error 13) vs Node connect EACCES); tests assert the refusing to remove prefix only.
  • Linux full-backlog probe: a live listener with a full backlog → Rust times out → "in use by another process"; Go gets EAGAIN → "refusing to remove … resource temporarily unavailable". Both safe (nothing unlinked); message diverges.

Listener (from #45's reviews)

  • macOS full-backlog live listener can return ECONNREFUSED → probe treats it as stale (only lockless peers exposed; Go/Rust hold the flock).
  • Lstat→Remove window: a lockless process's swapped-in file could be removed (tiny race, design accepts it).
  • Rust leaves the 0600 socket file on disk when chown/chmod fails (Go's Close unlinks); pre-existing parity gap.
  • SIGKILL test child can leak on t.Fatal in Go (add t.Cleanup); GC test passes partly by construction.
  • Concurrency-test losers in Go assert a substring ("is in use by another"), Rust asserts the exact message.
  • EPERM tests could also skip when getegid()==0.

Tests & tooling

  • Integration checks assert HTTP status only; asserting the deny body ("not allowed") would stop a daemon-side 403 masking a proxy regression (no authz plugin in the test stack today, so theoretical).
  • Rust/TS route-table tests stop at the first failing row; Go uses t.Run per row.
  • Go's extractContainerName empty-string test documents the contract but can't fail for the Rust extract_container_name treats empty segment as a container name (DELETE /containers/ is forwarded) #48 cause ("" is both "no name" and the raw value).
  • Go flag-removal test builds the binary in-test; TS VALUE_FLAGS/BOOL_FLAGS exported mutable.
  • ROUTER_SPEC := vs ?= used by the other Makefile spec vars.
  • test-sock.sh has a duplicate "GET /_ping -> 200" label (granted vs default-group).

Docs

Proposed solution

Work through these in one or two chore:/docs:/test: PRs, or pick items off when touching the files anyway. None changes behaviour except the parity items, which should each get the #24/#48 treatment (decide canonical row, converge, pin with same-named tests).

Which implementation(s) would this affect?

  • Go
  • Rust
  • TypeScript
  • Quint specification
  • CI / infrastructure

Activity

  1. added
    Type: MaintenanceAdded to issues and PRs when a change is for repository maintenance , such as CI or linter changes.
    on Oct 7, 2026
  2. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    Adding a cross-language item found while pinning #52 (verified by running the Go router; Rust by reading strip_api_version, rs/src/proxy.rs:200-207):

  3. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    The API-version over-strip item is now tracked as its own bug: #57 (P2). Docker confirmed that DELETE /volumes/containers/foo is handled as a volume removal, while Go and Rust classify it as a container delete.

  4. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    One more item: no formatter check in make lint-*. lint-go runs only go vet, lint-rs only cargo check, and lint-ts only typecheck. On main (a654ff8), gofmt -l go/internal/proxy/ lists router.go and transport_test.go, ts/src/proxy.test.ts fails prettier --check, and cargo fmt --check reports diffs. Proposal: one formatting commit per language, then add gofmt -l (fail if it prints anything), cargo fmt --check and prettier --check to the lint targets and CI. Keep it separate from behaviour changes so the diffs stay reviewable.

  5. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    One more item, found in the #53 review: the audit uri field differs across implementations (pre-existing). Go logs r.RequestURI (raw, with the query), Rust logs path (no query), and TS logs req.url (raw, with the query). After #53, Go's extra.path is the escaped routing path, so Go's audit line carries both forms. Proposal: log the raw request target (path + query) as uri in all three, and pin it with a shared audit test.

  6. abienkowski commented on Oct 7, 2026

    @abienkowski
    CollaboratorAuthor

    From the #54 final review (non-blocking): in release.yml's version step, the || true on the latest-tag search also hides a real git tag failure. That would fall through to the no-tag path and could cut a stray v0.0.1. It is unlikely with fetch-depth: 0. The tag-selection pipeline (strict filter, version sort, --merged HEAD) also has no automated test; only a one-off simulation covered it.

  7. abienkowski commented on Oct 8, 2026

    @abienkowski
    CollaboratorAuthor

    From the #49 review: ExecGate can never fire, and the README overstates it. All three ExecGates check the substring /exec (go/internal/middleware/exec.go:14, rs/src/middleware.rs:84, ts/src/middleware.ts:54). The middleware chain runs only on POST /containers/create, so the gate never sees an exec path; the router denies exec first (segment-exact since #49). The README middleware row ("Denies POST /containers/*/exec and POST /exec/*/start") describes something the gate never does. Options: (a) drop the gate; (b) keep it as defence in depth and reword the README row; (c) make it segment-exact like the router, so a future chain caller cannot reintroduce the exec-runner false positive. If the gate stays, (c) matters.

  8. abienkowski commented on Oct 8, 2026

    @abienkowski
    CollaboratorAuthor

    From the #49 final review: spec/docker_socket_policy.qnt endpointsTable (~:181, and the literal list at ~:453) still lists only POST /containers/:name/exec. After #49, exec is denied for every method on /containers/<name>/exec and on the whole /exec/* namespace. The table is documentation only (a self-tautology, see spec/README Modeling Notes), so the gap is cosmetic. Update it together with the ExecGate item above.

  9. abienkowski commented on Oct 8, 2026

    @abienkowski
    CollaboratorAuthor

    From the #49 review: double slash before exec skips the exec check in all three routers. GET //exec/<id>/json (and /v1.45//exec/...) has an empty first segment, so isExecPath does not match, and the GET passthrough forwards it. Pre-#49 TS (includes("/exec")) denied it, so TS regressed slightly. No data leaks. A live daemon (29.4.0, with a real running exec instance) answers 301 with an empty body and Location: /exec/<id>/json for every double-slash form, and a client that follows the redirect re-enters the proxy on the clean path and is denied. Non-GET methods hit the default deny. Proposed shared fix for all three routers plus the Quint model: deny or normalise paths with empty interior segments. This is the same family as the #48 empty-segment rule.

  10. abienkowski commented on Oct 9, 2026

    @abienkowski
    CollaboratorAuthor

    From the PR #68 review, deferred (not blocking #68):

    • TS runtime image ships devDependencies. ts/Dockerfile:26 copies the build stage's node_modules into the runtime image, so typescript, @types/node and now prettier (~8 MB) ship in a security-sensitive image. Proposal: RUN npm prune --omit=dev after npx tsc in the build stage, then verify the reproducible build and the integration suites.
    • rs/rustfmt.toml has no style_edition. A future edition bump to 2024 would change import ordering and reformat the crate. Consider pinning style_edition = "2021" (check that Rust 1.85's rustfmt accepts it) when the edition changes.
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

    Type: MaintenanceAdded to issues and PRs when a change is for repository maintenance , such as CI or linter changes.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions