# -*- coding: utf-8 -*- """Read ONE value out of ~/.config/credentials, correctly, and refuse loudly when it is not. Run: python3 scripts/read_credential.py [--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())