#1134: retire the seven stale corpus-size claims, and record the measurement that settles ASVS 6.2.4 - #810
Conversation
…urement that settles ASVS 6.2.4 The corpus rebuild grew the bundled breach list past ten thousand entries. Seven shipped sites kept describing it by the old size. None of them is a control, so nothing broke and nothing reported it -- they asserted a number that had stopped being true, in the places an operator reads before deciding whether to configure a larger corpus. THE MEASUREMENT, because the premise every brief on this item carries is false. Those briefs say the bundled corpus holds 10,000 entries of which 18 reach 15 characters. Measured on this tree: 15,256 lines, 15,045 distinct entries, and 5,274 clearing PasswordPolicy(check_breached=False, check_username=False) at the shipped password_min_length of 15, against an ASVS 6.2.4 bar of 3,000. The cell is satisfied on the strict reading with no corpus change. Two instruments agree, each with a control. awk over raw lines gives 5,274 at 15-plus characters; the shipped loader gives 5,274 clearing the full policy. The file carries zero comment or blank lines, length>=1 returns the full 15,256, and the policy count tracks the floor (9,135 at 8, 3,173 at 16, 1,735 at 17, 512 at 20) rather than sitting constant. The two counts agree exactly because the deny-list is not a binding constraint: removing the context clause changes the count by 0, and zero of the 5,274 carry a CONTEXT_WORDS term -- because the build filtered them out, not because the clause is inert. It still rejects a planted probe, and adding `qwerty` as a term would cut 98 entries, so a re-score trigger must name password_check_context beside password_min_length. The stale sites, by symbol rather than line number, because every line anchor in the older briefs has drifted: the messagefoundry.auth.policy module docstring; the operator-facing startup warning in _warn_if_corpus_unreadable; the AuthSettings.password_breach_corpus_file comment; the password_check_breached and password_breach_corpus_file rows of docs/CONFIGURATION.md; and two paragraphs of docs/SECURITY.md "Password policy". The briefs named four. There were seven. Counts now live only beside the data, and the operator reference gains the caveat that made the bundled list short: size a corpus by how many entries clear the policy, never by its line count. The prose half is cherry-picked from 1d012f8f9, written for this item on 2026-08-22 and never merged. pyproject.toml named tests/test_password_*.py as the guard proving the corpus ships in the wheel. No such file has ever existed; the real guard is tests/test_auth_core.py, one name away. Per the 2026-09-03 scope split this touches none of the corpus tooling -- the regeneration script, the corpus, its NOTICE and tests/test_auth_core.py belong to a concurrent lane. Three findings measured here are handed to it in the backlog row, including that a recorded digest needs a .gitattributes pin: git hash-object returns 4482f23 under core.autocrlf=true and 7ca7ef4a under autocrlf=false, so an unpinned digest accuses a clean checkout of tampering. No behaviour changes: every edit is a comment, a docstring, a log format string or a Markdown cell. ruff check, ruff format, mypy --strict (267 files), and the auth, settings, doc-drift and citation suites (301 passed) are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
For the corpus tooling lane, not for this PR. Before the 2026-09-03 scope split this branch also carried the provenance and gate layer. Rather than delete it, it is pushed as What is in it, all measured and green at the time:
Two things it deliberately does not do, both wider than #1134: widen |
|
Correction to the Checks section: a full suite DID complete after I wrote that line. Result: 1,816 passed, 50 skipped, 1 failed in 35 minutes. The one failure was self-inflicted and is not a defect in either branch. What happened. I started that run on the pre-split tree, then restructured the branch about 20 minutes in. The branch switch reverted How I know that is the cause rather than a real defect. The traceback shows the digest computed as Verified rather than assumed. I replayed both gates against the stored blobs of The What this means for THIS PR. It contains none of the files involved in that failure. The 1,816 passing tests did include the seven prose corrections that are in this PR, so the run is positive evidence for it. The hosted legs still need reading, One instrument trap worth passing on. The background-task notification reported |
|
Final check status, replacing the "full suite not run to completion" line in the body. The second full-suite attempt was killed and produced no output, so there is still no completed whole-suite run against this PR's exact tree. Rather than claim one, I enumerated every test file that reads any of the seven files this PR changes and ran all of them here. Run on this branch, in three batches:
Zero failures. That is every test that reads a file this PR touches, plus the auth and settings suites. Two warnings appear and neither comes from this change: What still needs a hosted runner, unchanged: |
What this is
BACKLOG #1134, the prose-and-measurement half. Per the 2026-09-03 scope split, the corpus tooling lane owns the regeneration script, the corpus file,
common_passwords.NOTICEandtests/test_auth_core.py. This PR touches none of those four.The premise every brief on this item carries is false
Briefs cut from the 2026-08-20 research say the bundled corpus holds 10,000 entries of which 18 reach 15 characters. Re-measured on this tree before touching anything:
PasswordPolicy(check_breached=False, check_username=False)at the shipped floor of 15The cell is satisfied on the strict reading, with no corpus change. Regenerating the corpus, which the brief asked for, would have been a no-op on a corpus that was already correct. Re-measuring first is what saved the work.
Two instruments, each with a control that fires.
awkover raw lines gives 5,274 at 15-plus characters; the shipped loader gives 5,274 clearing the full policy. Controls: the file carries zero comment or blank lines,length >= 1returns the full 15,256, and the policy count tracks the floor (9,135 at 8, 6,020 at 10, 5,367 at 12, 3,173 at 16, 1,735 at 17, 512 at 20) rather than sitting constant.The deny-list is not a binding constraint, and the reason matters
The two counts agree exactly. Removing the context clause changes the count by 0, and zero of the 5,274 carry a
CONTEXT_WORDSterm -- because the build filtered them out, not because the clause is inert. Control: it still rejects a planted probe containingmessagefoundry. Adding a term would cut real entries (qwertyremoves 98,love78,201030), so any re-score trigger must namepassword_check_contextbesidepassword_min_length.Headroom is one step, which nobody had measured: the 3,000 bar clears at a floor of 16 and fails at 17.
Seven stale sites, not the four the briefs named
Verified by symbol and surrounding text, because every line anchor in the older briefs has drifted:
messagefoundry.auth.policymodule docstring_warn_if_corpus_unreadable(auth/service.py)AuthSettings.password_breach_corpus_filecommentpassword_check_breachedrow ofdocs/CONFIGURATION.mdpassword_breach_corpus_filerow ofdocs/CONFIGURATION.mddocs/SECURITY.md"Password policy"None is a control, so nothing broke and nothing reported it. Each now describes the list without a size, so counts live only beside the data. The operator reference gains the caveat that made the bundled list short: size a corpus by how many entries clear the policy, never by its line count.
This half is cherry-picked from
1d012f8f9, written for this item on 2026-08-22 and never merged.Reviewer, please read this: those three gates in
1d012f8f9that keep this prose from rotting again live intests/test_auth_core.py, which is the other lane's file. They ship without a gate here. The commit is worth taking from.One false packaging claim corrected in passing
pyproject.tomlnamedtests/test_password_*.pyas the guard proving the corpus ships in the wheel. No such file has ever existed, so the stated control pointed at nothing while a working one --tests/test_auth_core.py-- sat one name away.Three findings handed to the tooling lane, in the backlog row
.gitattributespin or it is true only by luck.git hash-objecton the corpus returns4482f231...undercore.autocrlf=trueand7ca7ef4a...underautocrlf=false-- same file, same commit. An unpinned digest accuses a clean checkout of tampering, and a wheel built on Windows ships CRLF matching no recorded checksum. Converting the corpus working tree to LF produces an empty content diff, so the pin costs nothing.messagefoundry/integrity.pyattests installed files against the wheel'sRECORD, but_ATTESTED_SUFFIXis".py", so shipped data is skipped. Pre-existing, not a regression here, needs its own item.Traps avoided
password_min_lengthis untouched. The hashed k-anonymity corpus is still ruled out in principle:auth/policy.pystores 40-hex digests, so an entry's length is unknowable and unfilterable.Checks
Green:
ruff check,ruff format --check,mypy messagefoundry(strict, 267 files), and pytest overtest_auth_core.py,test_auth_service.py,test_settings.py,test_security_doc_rate_limits.py,test_backlog_status_check.py,test_backlog_citation_check.py,test_citation_line_check.py,test_claude_section_citations.py-- 301 passed.backlog_status_check.pyreports 657 items each declaring one status.Not run to completion in this session: the full suite. Every edit is a comment, a docstring, a log format string or a Markdown cell, so there is no behaviour change, but the hosted legs still need reading -- in particular
windows-service-smoke, which never runs locally.🤖 Generated with Claude Code