From 85fa65a52b8d55ebd646536b369b78c2fef72ae3 Mon Sep 17 00:00:00 2001 From: Grant Whitmer Date: Thu, 13 Aug 2026 22:52:56 -0400 Subject: [PATCH] canary: continuously verify the forged-token bypass stays closed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds must_refuse checks: a 2xx is the alarm, a 401/403/503 is health. The forged alg:none agent token is probed every 10 min; if it ever returns 2xx the canary goes red and pages. Proven both ways — 503 reads ok, a 200 endpoint alarms 'ACCEPTED — this MUST be refused'. The security property is now enforced by a running check, not assumed. Co-Authored-By: Claude (Fable 5) --- scripts/canary.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/scripts/canary.py b/scripts/canary.py index aa84c53..40b8b5d 100755 --- a/scripts/canary.py +++ b/scripts/canary.py @@ -65,6 +65,9 @@ class Check: body: dict | None = None headers: dict = field(default_factory=dict) warn_seconds: float | None = None + # When True this check INVERTS: a 2xx is a critical failure (a security + # control opened) and a 401/403/503 is the healthy, expected outcome. + must_refuse: bool = False def _probe(c: Check) -> Result: @@ -77,6 +80,12 @@ def _probe(c: Check) -> Result: try: with urllib.request.urlopen(req, timeout=TIMEOUT) as r: elapsed = time.monotonic() - start + if c.must_refuse: + # A 2xx here means a control that should reject accepted. That is + # the alarm, not the absence of one. + return Result(c.name, "down", + f"ACCEPTED (HTTP {r.status}) — this MUST be refused", + elapsed, c.what_it_proves) if r.status >= 400: return Result(c.name, "down", f"HTTP {r.status}", elapsed, c.what_it_proves) warn = c.warn_seconds @@ -87,6 +96,9 @@ def _probe(c: Check) -> Result: ) return Result(c.name, "ok", f"HTTP {r.status} in {elapsed:.1f}s", elapsed, c.what_it_proves) except urllib.error.HTTPError as e: + if c.must_refuse and e.code in (401, 403, 503): + return Result(c.name, "ok", f"correctly refused (HTTP {e.code})", + time.monotonic() - start, c.what_it_proves) return Result(c.name, "down", f"HTTP {e.code}", time.monotonic() - start, c.what_it_proves) except Exception as e: # noqa: BLE001 — a probe must never raise upward return Result( @@ -129,6 +141,23 @@ def build_checks() -> list[Check]: ), ] + # SECURITY REGRESSION GUARD. A forged, unsigned token naming a real passport + # must be refused. On 2026-08-13 this returned HTTP 200 (full agent + # impersonation). If it ever returns 2xx again, the bypass is back. + _forged = ( + "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0" + ".eyJwYXNzcG9ydCI6IkVUMjYtMUVGOS1WSkFOIn0.x" + ) + checks.append( + Check( + "security.forged_agent_token", + "https://api.windygit.com/api/v1/repos", + "an unsigned token cannot impersonate an agent", + headers={"Authorization": f"Bearer {_forged}"}, + must_refuse=True, + ) + ) + # THE important one. /health was 200 for the entire 2026-08-12 outage while # this was timing out. A canary that skips it is decorative. pw = os.environ.get("CANARY_LOGIN_PASSWORD", "")