agentsclimarketplace

Traefik hardening

Skill air-gapped/skills/.claude/skills/traefik-hardening

Claude Code plugin marketplace — 58 installable reference skills across vLLM/SGLang inference, Kubernetes & Harvester, GPU host bring-up, observability, security, and agent workflows.

Install
npx -y skills add air-gapped/skills --skill traefik-hardening

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Harden a Traefik 2.x/3.x reverse proxy against abusive or unwanted traffic — per-identity rate + concurrency limiting, IP allowlisting, request-body buffering, middleware chaining, keying limits on a JWT claim/header instead of raw source IP, air-gapped plugin loading (localPlugins/WASM), and detection/response via access logs. Built on the principle that a proxy usually CANNOT reliably tell a "bad" client from a "good" one (User-Agent and even TLS/JA3 fingerprints are spoofable; real humans burst while abusive scripts crawl) — so cap per-identity resource use regardless of client type rather than trying to classify. Covers the 2.x→3.x middleware deltas (IPWhiteList→IPAllowList, Redis-backed distributed RateLimit, status-based Retry), the single-leader-counting trap, and where Traefik's job ends and the app/backend must take over.

SKILL.md

10.3 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

Traefik hardening — capping abusive traffic at the ingress

Harden a Traefik reverse proxy (standalone or Kubernetes ingress, 2.x or 3.x) against traffic that is authorized but abusive — a real user replaying their session token from a script, one client opening 50 parallel streams, request floods, scrapers — the cases a login wall doesn't stop.

The one idea to internalize first

A proxy usually cannot reliably tell a "bad" client from a "good" one — so don't try to classify; cap per-identity resource use regardless of client type.

Every instinct to detect the abuser fails on inspection:

  • User-Agent is one line to spoof (curl -A "Mozilla/…").
  • TLS fingerprint (JA3/JA4) genuinely separates a browser stack from python-requests — but Traefik cannot see it (the plugin API never receives the ClientHello; see references/detection-and-response.md), and curl-impersonate defeats it anyway.
  • Request rate doesn't cleanly separate: a human dragging their mouse across a chat list fires hundreds of cheap GETs in seconds, while an agentic script doing long-context calls may crawl. Rate ≠ client type.

So the durable controls are the ones that apply per identity, regardless of client: cap how much of the scarce resource any one identity may consume, on the expensive paths only. Detection (§ below) then exists to decide whom to throttle or ban, not to gate requests in real time.

Decision flow

  1. Name the scarce resource and the expensive path(s). GPU inference, a heavy DB query, an LLM completion endpoint. Scope every limit to those paths only — never throttle the whole app, or cheap list/poll GETs get caught and real users trip limits while the expensive path stays open. In Kubernetes this means a separate, higher-priority Ingress/IngressRoute owning just the expensive paths (references/deployment.md).
  2. Pick the identity key, cheapest-to-most-accurate: source IP (blunt — shared NAT/egress makes it collateral-heavy) → a request header/cookie value → a verified JWT claim (best: the one thing an attacker can't forge without invalidating their token). Traefik's sourceCriterion keys on IP, a header, or host — not a cookie directly; keying per-user means decoding the token's claim into a header first (references/identity-keying.md).
  3. Cap concurrency first, then rate. InFlightReq (concurrent requests per key) is the lever against resource starvation — a streaming response counts as one in-flight request for its whole duration, so amount literally bounds how many generations one identity runs at once. RateLimit (requests per period) handles floods. Budget for legitimate fan-out (one user action can fire several concurrent backend calls).
  4. Check the topology. In-memory counters (InFlightReq, non-Redis RateLimit) are per-Traefik-pod. If one node receives all ingress traffic (single LoadBalancer leader, externalTrafficPolicy: Local + L2), counters are effectively global and accurate. If traffic fans across replicas, use Redis-backed RateLimit (v3.4+) or push the cap into the app/backend — InFlightReq has no Redis backend.
  5. Add the perimeter: IPAllowList for intranet CIDRs (blunt but cheap), Buffering to cap request-body size against amplification. Never cap the response body on a streaming path — it breaks SSE.
  6. Wire detection for response, not gating: access logs + 429/413 metrics identify who to act on; the hard stop is banning/suspending them at the identity provider. Triangulate proxy logs (status codes) + app audit (identity + client) + backend (cost/tokens) — each covers the others' blind spots.
  7. Know where Traefik stops. Per-user concurrency across a fan-out LB, cryptographic session validation, and reshaping app behavior (e.g. reducing a chat's fan-out) live in the app or backend, not the proxy. A hardening plan that's proxy-only is incomplete; say so.

Middleware quick map (details: references/middleware-primitives.md)

MiddlewareBuysKey gotcha
InFlightReqper-identity concurrency cap (streaming = 1 slot for its duration)no Redis backend → per-pod; accurate only single-leader
RateLimitper-identity request rate (± Redis in v3.4+)default period is 1s — omit it and "30" means 30/sec
IPAllowList (v3; was IPWhiteList in v2)perimeter CIDR gateblunt behind shared NAT; set ipStrategy.depth if behind another proxy
Bufferingrequest-body size cap (413)never set maxResponseBodyBytes on a streaming/SSE path
ForwardAuthdelegate an allow/deny to a sidecarone real round-trip per request, no caching; maxBodySize unbounded by default in v3
Chaincompose the above in orderlater middleware sees earlier ones' header mutations; reject-cheap-first

Identity keying (details: references/identity-keying.md)

Authorization-keyed limits are per token — a user with N sessions gets N buckets. To key on the actual user, decode a JWT claim (e.g. sub/id) into a header with a JWT plugin, then point sourceCriterion.requestHeaderName at that header. This is safe even if the plugin only decodes (doesn't verify): a forged claim just lands in a different bucket, and the tampered token fails the app's own signature check → 401, so no expensive work happens. Only validly-signed tokens (carrying the real, unforgeable claim) ever reach the backend, and they share that user's bucket.

What Traefik genuinely cannot do (say this out loud)

  • See TLS/JA3/JA4 fingerprints — declined at the plugin layer upstream (traefik/traefik #8627, #12421). Needs a TLS-terminating layer in front (nginx+JA4 module, HAProxy, a CDN) that hands Traefik a header.
  • Native WAF (Coraza) in open-source — that's Traefik Hub (commercial). OSS gets the community coraza-http-wasm plugin, which its own maintainers flag as not production-grade.
  • Per-user concurrency across a fan-out LBInFlightReq is per-pod with no Redis; move that cap to the app or backend.
  • Reliably distinguish browser from script — header/UA checks are a speed bump (trivially spoofed), never a control. Use them to raise the cost of casual abuse and to generate intent evidence on an intranet, not as a gate.

Reference routing

TaskRead
Exact middleware config (file + Kubernetes CRD), 2.x↔3.x field deltas, per-middleware gotchasreferences/middleware-primitives.md
Per-user vs per-token keying, JWT-claim→header plugins, forwarded-identity headersreferences/identity-keying.md
Where the JWT and its claims come from, if the issuer is Keycloakthe keycloak-iam skill (k8s plugin)
Browser-vs-script signals (Sec-Fetch/UA/JA3/HTTP2), CrowdSec fit, log triangulation & response runbookreferences/detection-and-response.md
Loading plugins with no internet (localPlugins Yaegi vs WASM), plugin-catalog maturity, CRD-vs-annotation chaining, single-leader counting, scoped-Ingress patternreferences/air-gapped-plugins.md, references/deployment.md
A full worked example on a real productreferences/known-products/open-webui-api-abuse.md (dated)

Load-bearing gotchas (keep visible while working)

Beyond the per-middleware traps in the quick map above:

  • Disabling one auth path ≠ blocking another. Turning off API keys doesn't stop session-JWT replay — different code path in most apps.
  • period/keying/topology interact. A per-header limit without a claim→header decode is per-token, not per-user; pair with short token lifetimes or add the JWT plugin.
  • Cross-namespace middleware refs need Traefik allowCrossNamespace for the CRD path — but the plain-Ingress annotation path bypasses that flag entirely (a security asymmetry worth auditing). See references/deployment.md.

How to use this skill

Lead with the mental model, then walk the decision flow for the specific deployment: identify the scarce resource + expensive path, choose the identity key, layer concurrency + rate caps scoped to that path, verify the topology assumption, and name explicitly which parts must live in the app/backend rather than Traefik. Pull exact config from the reference files. When the target is a specific product, check references/known-products/ for a dated worked example before re-deriving — but keep product specifics out of the generic reference files.

What ships with it: 9 files

48.3 KB alongside SKILL.md

evals/

Keep looking

Skills are one crate of 326,984. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.