Run against its first real target the script reported "ok
strict-transport-security present". The response carried two of them:
strict-transport-security: max-age=63072000; includeSubDomains
strict-transport-security: max-age=63072000; preload
RFC 6797 section 8.1 -- more than one and the agent MUST process only the first
-- so what was in force was includeSubDomains without preload, and preload had
never once applied while the headers read, to a person, as though the site were
preload-ready. Two layers each adding their own is all it takes, and the second
is discarded in silence.
Each security header is now counted, and more than one is a finding naming the
directives that actually survive.
Two details that each took a wrong answer to get right, both the same class of
error the check exists to catch -- a tool answering confidently and wrongly:
- The value comes from the FIRST occurrence of the FINAL response block. Using
the last named the second header as the one in force, which is precisely
backwards, and curl -L concatenates every hop so an unscoped search quotes a
redirect's copy rather than the page's.
- It is quoted from the original headers rather than the lowercased copy used
for matching. Reporting `includesubdomains` to somebody who wrote
`includeSubDomains` shows them a value they never sent.
Verified against a local server serving each shape, and against the origin that
prompted it, where it now names 'max-age=63072000; includeSubDomains' as in
force -- matching the wire byte for byte.
closes#12
Four things auditors of applications of this kind report seeing over and over,
each of them mechanical: a header that is absent, a scheme that is plain, a
login that answers a thousand guesses, a reset form that confirms which
addresses have accounts. None needs understanding to be checked, which is why
they belong in a script rather than a page somebody re-reads before a release
and then does not.
*(precautionary)* -- none of it has bitten a project here. The checks are cheap
and the evidence is somebody else's.
**Passive by default.** A bare run sends two GETs and could not be mistaken for
anything. Rate limiting and enumeration are behind --auth, because one of them
deliberately generates a dozen failed authentications.
**It refuses any host but its configured origin.** There is no URL argument that
can point it elsewhere: the target is PREFLIGHT_ORIGIN, and a URL on the command
line must match it. status.sh makes this argument for having no --host flag;
here there is more at stake, since a mistake there reads the wrong machine and a
mistake here hammers somebody else's login form from your address. The login and
reset paths are configured too, never guessed -- a POST to an assumed /login on
the wrong app posts to whatever is actually there.
Verified: exit 2 unconfigured, for a foreign host, and for an unreachable one;
exit 0 for --dry-run; exit 1 with the finding named. Run passively against
privacyllc.dev it correctly reported a strong CSP, a framing policy and HSTS,
and found that plain http answers 200 with the full page rather than
redirecting -- which is the class of finding this exists for, on its first real
target.
closes#9
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>