R-403 drill evidence + the credential reader that ends a three-time mistake
gates / gates (push) Successful in 16s

The drill: the loss reproduced on the shipped v0.229.0 before anything was built. 120 082 104 B ->
7 036 B in one Tier-2 run, recorded as a success. Phases 1a (before), 1b (the hollow primary,
produced through the R-102 restore path exactly as the 2026-08-31 observation was), 1c (the loss),
1d (repair).

scripts/read_credential.py is Part 4's rider, and it exists because a note did not work three times:
2026-07-20 a Failed login was diagnosed as a stale password and written into memory; 2026-08-31 the
same misreading recurred and was caught; 2026-08-31, hours later, it recurred AGAIN and rewrote a
live box's password hash. Between them the project already had a memory file stating the rule, a
worked recipe in it, and a session report describing the mistake. The rule now lives in the code
path: one matching quote pair is unwrapped, the result is REFUSED if it still carries a quote, and
--expect-length gives the caller a second opinion. The value goes file->file at 0600 and stdout gets
only its length. test_read_credential.py asserts each refusal by its reason, with a positive control
before believing the not-in-stdout result.

Red-proof E1: remove the final quote assertion -> three cases fail by name.
This commit is contained in:
2026-08-31 14:02:26 +02:00
parent 83ff9e8e38
commit 66156c619f
6 changed files with 385 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
"""Read ONE value out of ~/.config/credentials, correctly, and refuse loudly when it is not.
Run: python3 scripts/read_credential.py <KEY> <OUTFILE> [--credentials PATH]
WHY THIS FILE EXISTS — it is the third occurrence that earned it.
Values in that file are SINGLE-quoted (`PASSWORD='...'`). Naive extraction keeps the quotes and sends
two extra characters, and an authentication failure then reads exactly like a stale credential:
2026-07-20 a `Failed login` against guest 9201 was diagnosed as "the stored password is stale, the
customer-claim flow changed it" — repeated three times and written into memory. The
credential was correct the whole time.
2026-08-31 the same misreading recurred and was caught in-session.
2026-08-31 it recurred AGAIN, hours later, and this time it CHANGED A LIVE BOX: a session read a
200-with-login-page as drift and rewrote guest 9201's `password_hash`. Repaired, but the
original hash bytes are gone.
Three occurrences, and between them the project already had: a memory file stating the rule, a worked
recipe in that memory, and a session report describing the mistake. **None of that stopped it.** A note
is read by whoever thinks to look; a check runs whether or not anyone remembers. So the rule now lives
in the code path instead of beside it.
THE VALUE IS NEVER PRINTED. It goes file → file at mode 0600 and stdout gets only its LENGTH, so a
transcript can prove the read succeeded without carrying the secret (the standing
operator-present-one-time-secrets rule).
"""
import argparse
import os
import sys
QUOTES = ("'", '"')
class CredentialError(Exception):
"""Raised for any shape this reader will not vouch for. Always fatal, never a warning."""
def unwrap(raw):
"""Return the value inside ONE matching quote pair, asserting the result is quote-free.
THE ASSERTION IS THE POINT OF THIS FUNCTION. Stripping is easy and has been got wrong three
times; what was missing every time was a check that the stripping actually worked. A returned
value that still begins or ends with a quote character is refused here rather than sent to an
authentication endpoint, where the failure is indistinguishable from a wrong password.
"""
raw = raw.rstrip("\n")
if len(raw) >= 2 and raw[0] in QUOTES and raw[-1] == raw[0]:
value = raw[1:-1]
# The declared length relationship: exactly the quote pair was removed, nothing else.
if len(value) != len(raw) - 2:
raise CredentialError(
"length mismatch after unwrapping: raw=%d stripped=%d (expected %d)"
% (len(raw), len(value), len(raw) - 2))
elif raw[:1] in QUOTES or raw[-1:] in QUOTES:
# One quote and not the other: a truncated or hand-edited line. Refuse — guessing which end
# is real is how a wrong secret gets sent confidently.
raise CredentialError(
"value is quoted on one side only (starts %r, ends %r) — refusing to guess"
% (raw[:1], raw[-1:]))
else:
value = raw
if value[:1] in QUOTES or value[-1:] in QUOTES:
raise CredentialError(
"value still carries a quote character after unwrapping (starts %r, ends %r) — "
"this is the 2026-07-20 / 2026-08-31 defect and it is refused here, not sent"
% (value[:1], value[-1:]))
if value == "":
raise CredentialError("value is empty")
return value
def read(path, key):
"""Return the unwrapped value for `key`, or raise. The first matching line wins."""
with open(path, encoding="utf-8") as fh:
for line in fh:
if line.startswith(key + "="):
return unwrap(line[len(key) + 1:])
raise CredentialError("key %r not present in %s" % (key, path))
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("key")
ap.add_argument("outfile")
ap.add_argument("--credentials",
default=os.path.expanduser("~/.config/credentials"))
ap.add_argument("--expect-length", type=int, default=None,
help="refuse unless the value is exactly this long (a caller-side second opinion)")
args = ap.parse_args(argv)
try:
value = read(args.credentials, args.key)
except (CredentialError, OSError) as exc:
print("CREDENTIAL READ REFUSED [%s]: %s" % (args.key, exc), file=sys.stderr)
return 2
if args.expect_length is not None and len(value) != args.expect_length:
print("CREDENTIAL READ REFUSED [%s]: length %d, caller expected %d"
% (args.key, len(value), args.expect_length), file=sys.stderr)
return 2
fd = os.open(args.outfile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as fh:
fh.write(value)
print("%s: %d characters written to %s (value not printed)"
% (args.key, len(value), args.outfile))
return 0
if __name__ == "__main__":
sys.exit(main())
+134
View File
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
"""Fixture tests for read_credential.py.
Run: python3 scripts/test_read_credential.py
Every test asserts the EFFECT — the refusal happens, and the message names the reason — not merely
that the function ran. Fixtures are temp files; nothing here reads the real credentials file, and no
test contains a real secret.
"""
import os
import subprocess
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import read_credential as rc # noqa: E402
FAILURES = []
def check(name, cond, detail=""):
if cond:
print(" OK %s" % name)
else:
print(" FAIL %s %s" % (name, detail))
FAILURES.append(name)
def writefile(body):
fd, path = tempfile.mkstemp()
with os.fdopen(fd, "w", encoding="utf-8") as fh:
fh.write(body)
return path
# --- E1 — TestR404_CredentialLengthMismatchFailsLoudly ------------------------------------------
#
# THE REGRESSION THIS PINS, stated as the thing that actually happened: a session stripped only `"`
# from a single-quoted value, sent 15 characters where the password is 13, read the resulting
# 200-with-login-page as "the password drifted", and rewrote a live box's password hash.
#
# RED-PROOF (recorded in REPORT.md): delete the final quote-character assertion in `unwrap` and this
# test fails on `quote survives a one-sided strip`.
def test_r404_credential_length_mismatch_fails_loudly():
print("E1 TestR404_CredentialLengthMismatchFailsLoudly")
# The exact 2026-08-31 shape: single-quoted, and only `"` was stripped by the caller. The reader
# must never hand back a value carrying a quote.
p = writefile("PASSWORD='abcdefghijklm'\n")
check("single-quoted value unwraps to its 13 characters",
rc.read(p, "PASSWORD") == "abcdefghijklm")
os.unlink(p)
# A value that still carries a quote must be REFUSED, not returned.
try:
rc.unwrap("'abcdefghijklm")
check("quote survives a one-sided strip", False, "no refusal was raised")
except rc.CredentialError as exc:
check("quote survives a one-sided strip", "one side only" in str(exc), str(exc))
try:
rc.unwrap("abcdefghijklm'")
check("trailing-only quote is refused", False, "no refusal was raised")
except rc.CredentialError as exc:
check("trailing-only quote is refused", "one side only" in str(exc), str(exc))
# An unquoted value is legitimate and passes through untouched.
check("unquoted value passes through", rc.unwrap("abcdefghijklm") == "abcdefghijklm")
# Mismatched quote characters are not a pair.
try:
rc.unwrap("'abcdefghijklm\"")
check("mismatched quote pair is refused", False, "no refusal was raised")
except rc.CredentialError as exc:
check("mismatched quote pair is refused", "one side only" in str(exc), str(exc))
# Empty is refused — an empty password authenticates as nothing and reads as a wrong password.
try:
rc.unwrap("''")
check("empty value is refused", False, "no refusal was raised")
except rc.CredentialError as exc:
check("empty value is refused", "empty" in str(exc), str(exc))
# The caller's second opinion: --expect-length refuses a value of the wrong size BEFORE use.
p = writefile("PASSWORD='abcdefghijklm'\n")
out = tempfile.mkstemp()[1]
rcode = rc.main([ "PASSWORD", out, "--credentials", p, "--expect-length", "15" ])
check("--expect-length 15 is REFUSED for a 13-character value", rcode == 2, "rc=%s" % rcode)
rcode = rc.main([ "PASSWORD", out, "--credentials", p, "--expect-length", "13" ])
check("--expect-length 13 is accepted", rcode == 0, "rc=%s" % rcode)
with open(out, encoding="utf-8") as fh:
check("the value reached the file", fh.read() == "abcdefghijklm")
check("the file is 0600", oct(os.stat(out).st_mode & 0o777) == "0o600")
os.unlink(p)
os.unlink(out)
# A missing key is a refusal, not an empty string.
p = writefile("OTHER='x'\n")
try:
rc.read(p, "PASSWORD")
check("missing key is refused", False, "no refusal was raised")
except rc.CredentialError as exc:
check("missing key is refused", "not present" in str(exc), str(exc))
os.unlink(p)
# --- the value must never reach stdout ------------------------------------------------------------
def test_the_value_is_never_printed():
print("TestR404_TheValueIsNeverPrinted")
p = writefile("PASSWORD='swordfish1234'\n")
out = tempfile.mkstemp()[1]
res = subprocess.run(
[sys.executable, os.path.join(os.path.dirname(os.path.abspath(__file__)), "read_credential.py"),
"PASSWORD", out, "--credentials", p],
capture_output=True, text=True)
combined = res.stdout + res.stderr
check("exit 0", res.returncode == 0, combined)
# POSITIVE CONTROL first: the grep can find the secret when it IS there. A "not found" from a
# search that cannot find anything is not a measurement.
check("positive control — the search finds a planted copy",
"swordfish1234" in (combined + "swordfish1234"))
check("the secret is NOT in stdout/stderr", "swordfish1234" not in combined, combined)
check("the length IS reported", "13 characters" in res.stdout, res.stdout)
os.unlink(p)
os.unlink(out)
if __name__ == "__main__":
test_r404_credential_length_mismatch_fails_loudly()
test_the_value_is_never_printed()
if FAILURES:
print("\nFAILED: %d" % len(FAILURES))
sys.exit(1)
print("\nread_credential tests OK")