Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,19 @@ export function checkHSTS(headers: RawHeaders): HeaderFinding {
// includeSubDomains / preload only add protection when HSTS is actually
// enforced; awarding their bonuses under max-age=0 would mask a revocation.
if (maxAge > 0) {
if (/includesubdomains/i.test(raw)) { score += 3; }
const hasIncludeSubDomains = /includesubdomains/i.test(raw);
if (hasIncludeSubDomains) { score += 3; }
else { findings.push('includeSubDomains not set'); recommendations.push('Add includeSubDomains directive'); }
if (/preload/i.test(raw)) score += 2;
if (/preload/i.test(raw)) {
if (hasIncludeSubDomains) {
score += 2;
} else {
// preload without includeSubDomains is rejected by the HSTS preload list
// (hstspreload.org requires both). The directive is inert in this config.
findings.push('preload requires includeSubDomains — this config is not eligible for the HSTS preload list');
recommendations.push('Add includeSubDomains alongside preload to qualify for the HSTS preload list');
}
}
}

return { header: 'Strict-Transport-Security', score, maxScore: 20, status: score >= 15 ? 'good' : 'warning', raw, findings, recommendations };
Expand Down
9 changes: 8 additions & 1 deletion test/analyzer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,19 @@ describe('checkHSTS', () => {
expect(r.score).toBe(15);
});

it('preload adds 2 bonus points', () => {
it('preload adds 2 bonus points when includeSubDomains is also set', () => {
const withPreload = checkHSTS({ 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload' });
const withoutPreload = checkHSTS({ 'strict-transport-security': 'max-age=31536000; includeSubDomains' });
expect(withPreload.score).toBe(withoutPreload.score + 2);
});

it('preload without includeSubDomains earns no bonus and flags a finding', () => {
const r = checkHSTS({ 'strict-transport-security': 'max-age=31536000; preload' });
// 10 (base) + 5 (max-age ≥ 1yr) + 0 (no includeSubDomains) + 0 (preload inert) = 15
expect(r.score).toBe(15);
expect(r.findings.some(f => /preload.*includeSubDomains/i.test(f))).toBe(true);
});

it('case-insensitive header name matching', () => {
const r = checkHSTS({ 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload' });
expect(r.score).toBe(20);
Expand Down
Loading