Skip to content
Issue docs

HSTS does not cover subdomains

Suggestionhsts_missing_include_subdomainsIssue 171

What is this issue?

This is advice, not a defect. The page sends an HSTS policy, but without the includeSubDomains directive, so the policy protects this exact host name only.

Strict-Transport-Security: max-age=31536000                      ← this host only
Strict-Transport-Security: max-age=31536000; includeSubDomains   ← this host and every subdomain

For a page to pass this check:

  • Its HSTS header includes includeSubDomains.

Why it is a suggestion. includeSubDomains is right for most sites, but it is a promise about every subdomain. If legacy.example.com or an internal tool is still served over plain HTTP, adding the directive makes it unreachable in browsers that have seen the policy. Only you know whether that is safe, so this check points it out and deducts nothing.

When this check stays silent

  • Plain HTTP pages. Browsers ignore HSTS sent over HTTP (RFC 6797 §8.1), and the page is already reported as not served over HTTPS.
  • No usable policy. A missing header, a header with no max-age, and max-age=0 are all reported by "No Strict-Transport-Security header" (#41). This check only looks at policies that browsers would actually apply, so the two never describe the same header.

Why it matters

  • Cookies leak across subdomains. A cookie set for .example.com is sent to every subdomain. If shop.example.com is reachable over HTTP, an attacker can use it to read or plant cookies that the main site trusts.

  • Attackers can invent subdomains. Without includeSubDomains, an attacker on the network can send a victim to http://login.example.com — a name that never existed — and serve whatever they like there, under your domain.

  • It is required for preloading. The browser preload list only accepts policies with includeSubDomains.

Effect on the health score

None. This is a suggestion and never deducts.

How to fix it

  1. List your subdomains — DNS records, certificates issued for your domain (search it on crt.sh), and anything internal.

  2. Make sure each one serves HTTPS and redirects HTTP to HTTPS.

  3. Add the directive:

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  4. Send it from the bare domain too. The policy for example.com is what covers *.example.com, so the apex must send the header, not just www.

When to leave it alone

If a subdomain genuinely cannot move to HTTPS yet, leave the directive off and come back to it once it can.

Examples

Example 1: Host-only policy

Strict-Transport-Security: max-age=31536000

Suggested: subdomains are not covered.

Example 2: Full coverage

Strict-Transport-Security: max-age=31536000; includeSubDomains

Passes.

Example 3: Different casing

Strict-Transport-Security: max-age=31536000; INCLUDESUBDOMAINS

Passes: directive names ignore case.

Example 4: The directive only in a second header

Strict-Transport-Security: max-age=31536000
Strict-Transport-Security: max-age=31536000; includeSubDomains

Suggested: browsers use only the first header field.

How PixyScan detects this

  1. Only HTTPS pages with a usable HSTS policy are checked. No header, no max-age and max-age=0 belong to "No Strict-Transport-Security header".

  2. Looks for the includeSubDomains directive in the first header field, ignoring case. A token that merely contains the name, such as includeSubDomainsLater, does not count.

  3. Raises the suggestion when it is absent.

How the header is read

PixyScan reads the header the way RFC 6797 tells a browser to:

  • Only the first header field counts. If the server sends the header twice, browsers use the first one and ignore the rest, and so does this check.
  • Directive names ignore case. includeSubDomains, INCLUDESUBDOMAINS and includesubdomains are the same directive.
  • Values may be quoted. max-age="31536000" is valid.
  • A repeated directive makes the header invalid. Browsers ignore a header like max-age=600; max-age=700 entirely, so this check says nothing about it rather than giving advice about a policy no browser applies.

What we store

Storage Level

Page Level


Database Table / Prisma Model

PageSecurityHeader, and the finding on audit_issues.details


Fields Used

Field Type Description
page_security_headers.hsts_header String The Strict-Transport-Security header exactly as the page sent it

Stored Fields on the finding

Field Type Description
message String What is missing from the policy and why it matters
hstsHeader String The header as sent

Detection Dependencies

  • HTTP Response headers
  • HTTPS (the check only applies to pages served over HTTPS)

Further reading