Skip to content

Build1 publisher2 min readPublished

Spoofed admin headers hit Traefik's catchall 500 times out of 500

Traefik 3.6's release material says child routers cannot be called directly. A test on 3.6.25 sent the exact header a child router matches, on a path outside the parent prefix, 500 times, and none of it reached the admin service.

The Engineer · Build desk

Illustration accompanying Spoofed admin headers hit Traefik's catchall 500 times out of 500

What happened

  • Traefik 3.6 shipped in September 2026 with multi-layer routing, where a parent router runs middleware and child routers attach to it through a new parentRefs field instead of their own entry point.
  • The release material's line that child routers cannot be called directly was tested against Traefik 3.6.25 in Docker, using the file provider and three whoami containers as backends.
  • A curl carrying X-Role: admin, the exact header the child router's rule matches, sent to a path outside /api, was served by the catchall's default backend.
  • Across 500 requests at 50 concurrent workers, all spoofing that header outside /api, the admin service took zero of them and the default service took all 500.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • capability One auth step in front of a parent router can gate a whole set of child routers, and each child rule can key off the header that step injects without restating the parent's own match.
  • exposure Teams that swap the fixed header middleware for forward-auth take on the question the fixed one answered for them: whether a failed check still discards a client-supplied X-Role.
  • decision Any config template that stamps entryPoints onto every router has to special-case children, or the child is disabled and its traffic goes to whichever router is next by priority.
  • constraint A team gating an admin path on this boundary still owns its own soak test, because a run sized to catch a deterministic routing bug is not sized to catch a rare one.

The table the entry point matches against holds parent rules only. In the posted config, `parent-api` owns the `/api` prefix on the `web` entry point, while `child-admin` has no entry point of its own and only a `parentRefs` pointer at the parent [4]. Its `HeaderRegexp` on `X-Role` is therefore consulted only for a request that already matched the parent's rule and already ran the parent's middleware chain [1]. The spoofed `GET /` never got that far. It fell to the catchall sitting at priority 1, and the response came back from container `64d56d8ef346`, the default-svc whoami [8] [4].

On paths that do match the parent, the header the child sees comes from the middleware. `role-admin` is a headers middleware using `customRequestHeaders`, and it sets `X-Role: admin` on everything matching `parent-api` whatever the client sent [5]. A request carrying `X-Role: user` to `/api/anything` arrived with `X-Role: admin` [7].

The post says a real deployment would replace `role-admin` with a forward-auth middleware that sets the header from an actual login check, with everything downstream trusting the header [6]. That substitution is where this result stops applying. An unconditional `customRequestHeaders` overwrite discards the client's value on every matching request [5]. A forward-auth step that adds a header on success and otherwise passes the request through is a different configuration, and this test did not exercise it.

Five hundred requests across fifty workers is ten per worker [16], which is the size the hypothesis actually under test calls for. If child rules were evaluated like ordinary routers, every one of those requests carried exactly the header `child-admin` matches, so the expected count on admin-svc is 500 [18]. The observed count was zero [10]. Zero in 500 does not separate a true zero from a leak rate under 0.2% [17].

The author wrote that "the specific claim in Traefik's release material checked out under an actual attempt to break it, not just under a docs example" [11]. Getting to a working config took three wrong turns [15]. The first was giving `child-admin` its own `entryPoints`, the way any ordinary router needs one, which produced `non-root router cannot have Entrypoints configuration` [12]. A child inherits its entry point from the parent [14]. Traefik reports the disabled router to the API as `"status": "disabled"`, and it drops out of the request path [13].

Anyone planning to gate an admin route on `parentRefs` is trusting an evaluation-order property verified on one host: Traefik 3.6.25 under Docker, the file provider, three whoami containers, and a router table with three routers in it [2] [4].

What to watch

  • Whether the same spoofing test holds when role-admin is replaced by a forward-auth middleware that sets the header only on a successful check.
  • Whether a second, independent router naming admin-svc can reach that service without passing through parent-api.
  • Traefik point releases after 3.6.25 that touch parentRefs evaluation or the non-root entryPoints validation.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories