Security

What Weir protects, the invariants every build holds, the risks that remain, and how to report a vulnerability.

A shared cache decides which stored response a user receives. A bug in that decision can hand one user's page to another, or let one request change what everyone sees. Weir treats the cache key as a security boundary and keeps a written threat model. This page is the summary; the threat table has all 45 rows and the normative document is docs/06-threat-model.md.

Reporting a vulnerability

Use GitHub private vulnerability reporting: the "Report a vulnerability" button under the repository's Security tab. Please do not open a public issue.

Include the Weir version or commit, the configuration involved (Forward, Key, Storable settings), and a request and origin response that reproduce the problem. A failing Go test is the most useful form. You should get an acknowledgement within 7 days. Fixes ship as a patch release with an advisory that credits you unless you ask otherwise.

The latest v1.x minor release gets security fixes. All four modules are in scope and are versioned together.

What is being protected

AssetHarm if lost
Integrity of stored responsesone attacker request changes what every later visitor sees (poisoning)
Confidentiality of per-user responsesone user's private page is served to others (deception, leakage)
Origin availabilitythe origin is overloaded through the cache (busting, stampede)
Engine availabilitymemory or CPU exhaustion in the process hosting Weir
Purge correctnesscontent that must disappear (legal takedown, security fix) keeps being served

Who is assumed hostile

ActorTrustCapabilities
A1 anonymous clientuntrustedany request bytes the adapter accepts, high rate, many source addresses
A2 authenticated useruntrusted for other users' dataas A1 plus valid credentials and session cookies
A3 tenant on a shared originuntrusted for other tenantscontrols some responses' headers, including Cache-Groups, Cache-Group-Invalidation, Vary
Operatortrustedconfiguration, Purge calls (through an adapter that authenticates them)
Origintrusted to mean what it says in headers; may be buggyresponse headers and bodies
Store backendtrusted infrastructure; may fail or be slowavailability only

Out of scope: network attackers (TLS is the adapter's job), HTTP request smuggling and desync (the HTTP server's parser is the defense; Weir never parses wire bytes), compromised origins, compromised stores.

Threat families

The model groups its 45 threats into three families:

  • Poisoning and deception. Getting a wrong or private response stored and served to others. Answered mainly by strict forwarding (the origin never sees unkeyed input) and by storability rules that refuse private, Set-Cookie, credentialed and Vary: * responses. Start at T-1.
  • Availability. Making the origin or the engine do unbounded work: cache busting, stampedes, slow uploads, memory growth. Answered by the limiter, coalescing bounds, per-path fairness and a stated bound on every in-memory structure. Start at T-11.
  • Multi-tenant and operational. One tenant affecting another, purges that do not reach, snapshots that could be planted. Start at T-25.

Invariants

These hold for every build. Each has at least one test that fails if it breaks.

  • INV-1 (key-forward consistency). For a cacheable, non-bypassed request, every header, path byte, query byte and body byte in the forwarded request is either a key input or was allowed by Forward.Allow or is one of Authorization, Cache-Control, Pragma, traceparent, tracestate, X-Request-Id (FR-FWD-6, D29), or the Accept-Encoding bucket, which enters the key through Vary unless Key.Headers names it (01 §5.2.3), or a validator Weir added. Property test: random requests, random config; recompute the key from the forwarded request alone and compare.
  • INV-2 (injective key). Distinct keyed-input tuples produce distinct canonical encodings.
  • INV-3 (malformed means absent). A keyed header whose normalizer rejects the value is absent from both key and forwarded request.
  • INV-4 (no unsafe storage). No stored entry came from a response with no-store (without must-understand), private, Vary: *, unstripped Set-Cookie, or from an Authorization request without public, s-maxage or must-revalidate.
  • INV-5 (bounded). Every structure in NFR-3 stays within its bound under the adversarial load tests.
  • INV-6 (no panic). No input panics the engine (fuzzing).
  • INV-7 (budgeted origin). The number of concurrent Origin.Fetch calls holding slots never exceeds MaxConcurrent, and never exceeds MaxPerPartition for one partition (checked by the test origin on every call).

Residual risks, in plain language

No design removes every risk. These are the ones that remain after the defaults have done their work. If one of them matters to your deployment, it needs an answer outside Weir.

IDWhat can still go wrongWhat to do about it
R-1Your origin changes its response based on something that is not in the request Weir forwards: the client's IP address, the time, a header another proxy adds on the way. Weir cannot key what it cannot see, so the first response is served to everyoneMake the origin say so with Vary or Cache-Control: private, or bypass the route
R-2Someone requests thousands of different paths. Each is a legitimate miss, and together they use the whole origin budget. The origin survives, because the budget holds, but real users queuePut a rate limiter in front of Weir
R-3You turned a protection off: ForwardAll, StripSetCookie, VaryAllow: [Cookie], a long Forward.AllowKnow which one and why. The tuning page lists each trade
R-4With a remote store, the lookup that checks whether an entry was purged can itself fail. Weir then serves the entry and emits an event, because failing closed would turn a brief store problem into a full miss stormAfter a store incident, hard-purge everything
R-5Your origin marks a per-user page as publicly cacheable. Weir believes it, as any cache wouldFix the origin's headers. No cache can detect this
R-6An in-process origin reads values that middleware in front of Weir put in the request context, such as the logged-in user. The response is built for that user and sharedPut that middleware behind Weir, or bypass the route
R-7A request carrying unkeyed input (Cache-Control, a Forward.Allow header) leads a coalesced fetch, the origin answers it with a 5xx, and the followers of that one fetch receive the same error. Nothing is storedAccepted: the reach is one flight, bounded by the origin timeout
R-8With MissRate.Throttle on, a client flooding one path with distinct queries keeps that path's legitimate misses throttled for as long as the flood lastsLeave Throttle off unless you have read the warnings from a run without it

Out of scope

Network attackers (TLS is the adapter's job), HTTP request smuggling (the HTTP server's parser is the defense, since Weir never parses wire bytes), and compromised origins or stores.

How it is checked

Each threat row names a test. Parsers of request or origin bytes have fuzz targets, 29 in all, and each ran for an hour before the release with no new crasher. The tests for the failure modes ran 100 times under the race detector. The root module imports only the Go standard library, enforced by deps_test.go, so its supply chain is the Go toolchain.