Problem
src/rules.ts checks 7 categories (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin policies) but never inspects Set-Cookie. grep -ri "set-cookie" src/ returns nothing — there is no rule, no test, and no README mention of cookie attributes at all.
Every comparable tool in this space checks this:
- securityheaders.com flags missing
Secure/HttpOnly/SameSite on session-looking cookies.
- Mozilla HTTP Observatory has a dedicated Cookie test.
- OWASP's Secure Headers project treats cookie flags as part of the same "response header hardening" surface as CSP/HSTS.
Concretely, today:
import { analyzeHeaders } from '@hailbytes/security-headers';
const r = analyzeHeaders({
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
'content-security-policy': "default-src 'self'; form-action 'self'; base-uri 'self'",
'x-frame-options': 'DENY',
'x-content-type-options': 'nosniff',
'referrer-policy': 'strict-origin-when-cross-origin',
'permissions-policy': 'camera=(), microphone=(), geolocation=()',
'set-cookie': 'sessionid=abc123; Path=/', // no Secure, no HttpOnly, no SameSite
});
console.log(r.grade); // A+ — with a session cookie wide open to theft via XSS and CSRF
A site can score A+ while shipping a session cookie with none of Secure, HttpOnly, or SameSite set — i.e. readable by any injected script, replayable over plain HTTP, and attached to cross-site requests. The tool's own stated purpose ("analyze HTTP security headers... A-F grading... remediation steps") implies exactly this class of misconfiguration should be caught, and currently isn't.
Why this is high-leverage
- It's a well-scoped, self-contained addition (new
checkSetCookie in rules.ts, wired into analyzer.ts, no changes to fetch.ts/cli.ts plumbing) — same shape as the existing 7 rules.
- It closes a real blind spot rather than refining an existing one: right now this tool can hand out a top grade to a session-hijack-friendly response, which undercuts the "A-F grade reflects your security posture" promise the README makes.
fetchHeaders (src/fetch.ts:15-16) already builds a flat Record<string, string> via res.headers.forEach. Multiple Set-Cookie headers get silently collapsed by the underlying Headers object's iteration (they're normally combined or only the last one wins depending on the fetch implementation) — worth confirming/handling as part of this work, since a page setting several cookies should have each one evaluated independently.
Proposed approach
- In
src/rules.ts, add checkSetCookie(headers):
- Parse the
Set-Cookie value(s) for Secure, HttpOnly, and SameSite=Strict|Lax (flag SameSite=None without Secure as broken, per spec — browsers reject that combination).
- Missing header entirely → treat as N/A (
status: 'good', 0 findings) rather than missing, since not every response sets cookies and penalizing absence would be a false positive.
- Present but missing
Secure and/or HttpOnly and/or a safe SameSite → warning with one finding + remediation per missing attribute.
- If multiple cookies are present, evaluate each and roll up the worst-case findings (don't just check the first).
- Wire it into
analyzer.ts's checks array and give it a maxScore (10 seems consistent with X-Content-Type-Options/Referrer-Policy/Permissions-Policy). Since grading is percentage-based (score/maxScore*100), adding a category doesn't require rebalancing existing weights, but it does mean:
- The
maxScore is 100 test in test/analyzer.test.ts needs updating to the new total.
- The hand-computed grade-boundary tests (
describe('grade boundaries', ...)) need their header fixtures/comments updated since they currently assume a 100-point total.
- Update the README's "Headers Checked" table with the new row and grading example.
- Add unit tests in
test/analyzer.test.ts mirroring the existing per-rule describe blocks: no cookie → good/N/A, cookie missing all three attributes → warning with 3 findings, cookie with all three set correctly → good, SameSite=None without Secure → flagged as broken.
Acceptance criteria
Problem
src/rules.tschecks 7 categories (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin policies) but never inspectsSet-Cookie.grep -ri "set-cookie" src/returns nothing — there is no rule, no test, and no README mention of cookie attributes at all.Every comparable tool in this space checks this:
Secure/HttpOnly/SameSiteon session-looking cookies.Concretely, today:
A site can score A+ while shipping a session cookie with none of
Secure,HttpOnly, orSameSiteset — i.e. readable by any injected script, replayable over plain HTTP, and attached to cross-site requests. The tool's own stated purpose ("analyze HTTP security headers... A-F grading... remediation steps") implies exactly this class of misconfiguration should be caught, and currently isn't.Why this is high-leverage
checkSetCookieinrules.ts, wired intoanalyzer.ts, no changes tofetch.ts/cli.tsplumbing) — same shape as the existing 7 rules.fetchHeaders(src/fetch.ts:15-16) already builds a flatRecord<string, string>viares.headers.forEach. MultipleSet-Cookieheaders get silently collapsed by the underlyingHeadersobject's iteration (they're normally combined or only the last one wins depending on the fetch implementation) — worth confirming/handling as part of this work, since a page setting several cookies should have each one evaluated independently.Proposed approach
src/rules.ts, addcheckSetCookie(headers):Set-Cookievalue(s) forSecure,HttpOnly, andSameSite=Strict|Lax(flagSameSite=NonewithoutSecureas broken, per spec — browsers reject that combination).status: 'good', 0 findings) rather thanmissing, since not every response sets cookies and penalizing absence would be a false positive.Secureand/orHttpOnlyand/or a safeSameSite→warningwith one finding + remediation per missing attribute.analyzer.ts'schecksarray and give it amaxScore(10 seems consistent withX-Content-Type-Options/Referrer-Policy/Permissions-Policy). Since grading is percentage-based (score/maxScore*100), adding a category doesn't require rebalancing existing weights, but it does mean:maxScore is 100test intest/analyzer.test.tsneeds updating to the new total.describe('grade boundaries', ...)) need their header fixtures/comments updated since they currently assume a 100-point total.test/analyzer.test.tsmirroring the existing per-ruledescribeblocks: no cookie → good/N/A, cookie missing all three attributes → warning with 3 findings, cookie with all three set correctly → good,SameSite=NonewithoutSecure→ flagged as broken.Acceptance criteria
checkSetCookieexists insrc/rules.tsand is included inanalyzeHeaders's checksSet-Cookieheader does not lose points (no false-positivemissing)Secure/HttpOnly/SameSiteproduces distinct findings + remediations for each missing attributeSameSite=NonewithoutSecureis flagged as an invalid/broken combinationSet-Cookievalues (if present) are each evaluated, not just the firstmaxScore/grade-boundary tests updated to the new total