secure-code-auditor v1.53.0
Three corrections, in three references. Each one closes the gap between what a
section already required and what it gave a reader to act on, and each was
settled by running the code rather than by reading about it. No claim, control,
or example is removed, and the frontmatter description is unchanged.
The SSRF address check now has a mechanical form
references/a01-broken-access-control.md has always required a check on the
resolved address, and has always argued that a hand-written list of refused
ranges is the list that must be complete and never is. It did not say what to
write instead. It does now:
not ip.is_global or ip.is_multicastThat refuses loopback, every private range, the link-local range that carries
169.254.169.254, the shared address space 100.64.0.0/10, and each
IPv4-mapped IPv6 form of those. The multicast half is not decoration:
ipaddress reports a multicast address as global, so a test on is_global
alone lets 224.0.0.1 through.
The floor is Python 3.12.4, and the gap below it is real. CPython's own
documentation attributes the fix to 3.13, which is the feature release rather
than the first release that carries it. On Python 3.12.3, 2002:a9fe:a9fe:: —
the 6to4 form of the cloud metadata address — is reported as global, and the
check passes it. On 3.12.4 it is refused. The same fix is in 3.9.20, 3.10.15,
and 3.11.10. If your runtime is pinned anywhere in 3.12.0 to 3.12.3, this
predicate does not cover the 6to4 form.
It is still not a complete refusal list, and the section does not pretend
otherwise. 64:ff9b::/96 is reported as global on every release through
3.14.0, so behind a NAT64 gateway 64:ff9b::a00:1 still reaches 10.0.0.1.
The allowlist keeps the lead. That was the section's argument before this
release, and it now carries a worked example of its own limits.
Three request-size limits are labelled with the server that has them
references/deployment-and-runtime.md listed --limit-request-line (4094),
--limit-request-fields (100), and --limit-request-field-size (8190)
directly beneath the bullet that describes uvicorn and Daphne. They are
Gunicorn options. A reader on ASGI could take them as theirs and conclude they
had a header bound they did not have.
Under uvicorn, none of the three exists. The counterpart is
--h11-max-incomplete-event-size, whose 16384 default comes from h11, and
only the h11 implementation reads it. The httptools implementation has no
equivalent, and --http auto selects httptools wherever it is installed —
which is what uvicorn[standard] installs. Under uvicorn with httptools,
the reverse proxy is the only header bound there is.
Read off Gunicorn 26.2.0, uvicorn 0.52.4, h11 0.16.0, and httptools 0.8.0.
SECRET_KEY rotation names its unit, and the table is current to Django 6.1
The SECRET_KEY_FALLBACKS subsystem table was annotated against Django 6.0 and
5.2. Every row was re-executed at 6.1, 6.0.8, and 5.2.17, and none of them
moved: the signer's fallback default, the session-hash match that cycles the
key and re-stamps the session in place, secret_fallbacks on the reset-token
generator, the cookie signer, and the CSRF secret that is random rather than
derived and that a rotation therefore never invalidates. The annotation now
names 6.1.
The rotation procedure itself was missing something. Its rolling-fleet split
counted instances of one deploy, so a worker fleet, a scheduled job, or a
second service that verifies the same Django-signed values fell outside it. The
procedure now states that every process family which verifies a Django-signed
value is a fleet of the rotation, that the fallbacks reach every fleet before
the primary key changes in any fleet, and that the window closes only after the
last fleet carries the new key.
This matters most on the rotation you least want to get wrong. A compromise
rotation deliberately skips the fallback, so that everything the attacker could
forge becomes invalid at once. A fleet left on the old key still accepts what
the leaked key signs, and the hard cut does not cut.
Verification
Both repository workflows pass on the finished tree: 25 reference files, every
link resolving, no orphan, balanced fences, SKILL.md at 32,436 of the 40,960
bytes allowed, the description at 1013 characters, and the
dangerous_patterns.py self-test at 49 fixtures with all three scanners
exiting 0.
The library-index date does not move.