canary: continuously verify the forged-token bypass stays closed
All checks were successful
check / gate (push) Successful in 18s

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) <noreply@anthropic.com>
This commit is contained in:
Grant Whitmer
2026-08-13 22:52:56 -04:00
parent d8deffe4db
commit 85fa65a52b

View File

@@ -65,6 +65,9 @@ class Check:
body: dict | None = None body: dict | None = None
headers: dict = field(default_factory=dict) headers: dict = field(default_factory=dict)
warn_seconds: float | None = None 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: def _probe(c: Check) -> Result:
@@ -77,6 +80,12 @@ def _probe(c: Check) -> Result:
try: try:
with urllib.request.urlopen(req, timeout=TIMEOUT) as r: with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
elapsed = time.monotonic() - start 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: if r.status >= 400:
return Result(c.name, "down", f"HTTP {r.status}", elapsed, c.what_it_proves) return Result(c.name, "down", f"HTTP {r.status}", elapsed, c.what_it_proves)
warn = c.warn_seconds 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) return Result(c.name, "ok", f"HTTP {r.status} in {elapsed:.1f}s", elapsed, c.what_it_proves)
except urllib.error.HTTPError as e: 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) 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 except Exception as e: # noqa: BLE001 — a probe must never raise upward
return Result( 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 # 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. # this was timing out. A canary that skips it is decorative.
pw = os.environ.get("CANARY_LOGIN_PASSWORD", "") pw = os.environ.get("CANARY_LOGIN_PASSWORD", "")