How testssl.sh Tests HTTP Security Headers: Deep Dive into HSTS, HPKP, and Cookie Flag Detection

testssl.sh analyzes HTTP security headers by downloading the raw response to $HEADERFILE, then parsing it through specialized functions—run_hsts, run_hpkp, and run_cookie_flags—that validate Strict-Transport-Security, Public-Key-Pins, and Set-Cookie directives against configurable minimum thresholds like HSTS_MIN and HPKP_MIN.

The drwetter/testssl.sh repository provides comprehensive TLS/SSL testing through a single bash script. When invoked with the -h or --headers option, the tool executes dedicated routines that download, parse, and grade HTTP security headers to identify configuration weaknesses. This analysis examines how testssl.sh implements HSTS, HPKP, and cookie flag validation directly from the source code.

The HTTP Header Testing Architecture in testssl.sh

The script follows a three-phase pattern for all header inspections. First, run_http_header (defined in testssl.sh) opens a TCP connection or uses curl/openssl s_client to write the complete HTTP response block to the temporary file $HEADERFILE.

Next, individual test routines call match_httpheader_key (located at line 2850 in testssl.sh) to perform a case-insensitive grep for specific header keys. This helper returns the first matching value with normalized whitespace, storing it in HEADERVALUE for further processing.

Finally, each validation routine extracts parameters—such as max-age or pin-sha256—compares them against built-in minima, and reports results through severity-graded output helpers like pr_svrty_* and fileout.

How testssl.sh Validates HSTS (Strict-Transport-Security)

The HSTS implementation resides in run_hsts, which begins at line 2904 in testssl.sh.

Header Extraction and max-age Parsing

The function invokes match_httpheader_key "Strict-Transport-Security" "HSTS" "$spaces" "true" to retrieve the header line. It then extracts the max-age directive using parameter expansion:

hsts_age_sec="${HEADERVALUE#*=}"

This strips everything up to the first = character. The script removes trailing semicolons and surrounding quotes, then validates the result with an is_number check. If the value is non-numeric, testssl.sh reports a misconfiguration.

Security Directive Analysis

The extracted age is compared against HSTS_MIN, which defaults to 180 days (15,552,000 seconds) unless overridden via environment variable:

HSTS_MIN=${HSTS_MIN:-180}

Based on this comparison, testssl.sh assigns severity grades: OK for compliant durations, MEDIUM for values below the minimum, and LOW for disabled or invalid configurations.

The routine also checks for optional security-enhancing directives using helper functions. includeSubDomains (lines 2844–2851) searches for the includeSubDomains string, while preload (lines 2949–2955) detects the preload flag. Both contribute to the final grading output.

How testssl.sh Analyzes HPKP (Public Key Pinning)

Although deprecated in modern browsers, HPKP validation is fully implemented in run_hpkp starting at line 2979 in testssl.sh.

Pin Extraction and Key Counting

The function searches for both Public-Key-Pins and Public-Key-Pins-Report-Only headers, emitting a MEDIUM warning if multiple instances are detected. It normalizes the header value by replacing semicolons with newlines and removing quotes, producing one pin-sha256= entry per line.

The script counts pinned keys using:

hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE)

A single pinned key triggers a HIGH severity warning due to the risk of future breakage. Two or more keys are required for a passing grade.

max-age and SPKI Verification

run_hpkp extracts the max-age value using awk -F= and strips non-digit characters with sed. This value is compared against HPKP_MIN, which defaults to 30 days (2,592,000 seconds), with results graded as OK, MEDIUM, or HIGH (misconfiguration).

The routine performs deep SPKI verification by extracting base64-encoded pin values and comparing them against the host certificate's public key and known CA hashes stored in etc/ca_hashes.txt. Missing matches generate WARN or HIGH severity findings.

Cookie analysis is handled by run_cookie_flags, starting at line 3501 in testssl.sh.

Secure and HttpOnly Flag Detection

The function first collects all Set-Cookie lines from $HEADERFILE into a temporary file:

grep -ai '^Set-Cookie' $HEADERFILE >$TMPFILE

It then counts total cookies and identifies security attributes:

nr_secure=$(grep -iac secure $TMPFILE)
nr_httponly=$(grep -cai httponly $TMPFILE)

The ratio of secure to total cookies determines the grade: OK when all cookies carry the Secure flag, MEDIUM otherwise. The same logic applies to HttpOnly flags, which mitigate XSS attacks by restricting JavaScript access.

The routine includes specialized parsing via sub_f5_bigip_check (called at line 3560) to extract pool-member information from proprietary F5 BIG-IP cookie formats, though this operates separately from the generic flag validation.

Running HTTP Header Tests with testssl.sh

Basic Header Scan

Execute the HTTP security header checks using the -h flag:

./testssl.sh -h https://example.com

This sequentially invokes run_hsts, run_hpkp, and run_cookie_flags (around lines 25418–25420 in testssl.sh), displaying severity-graded results for each category.

Custom HSTS Minimum Threshold

Override the default 180-day requirement by setting the HSTS_MIN environment variable:

export HSTS_MIN=365
./testssl.sh -h https://example.com

Now run_hsts compares max-age values against 365 days (31,536,000 seconds), downgrading sites that meet only the default threshold.

Machine-Readable JSON Output

Extract structured data for automated processing:

./testssl.sh -h -J https://example.com > result.json
jq '.hsts, .hpkp, .cookie' result.json

The JSON output uses the same identifiers defined in the fileout calls (HSTS_time, HPKP_age, cookie_secure, cookie_httponly) for consistent key naming.

Summary

  • testssl.sh stores raw HTTP responses in $HEADERFILE before parsing headers individually.
  • The match_httpheader_key helper at line 2850 performs case-insensitive header extraction with whitespace normalization.
  • HSTS validation in run_hsts (line 2904) enforces a default 180-day minimum via HSTS_MIN, checking includeSubDomains and preload directives.
  • HPKP validation in run_hpkp (line 2979) counts pin-sha256 entries, verifies against HPKP_MIN (default 30 days), and matches pins against etc/ca_hashes.txt.
  • Cookie analysis in run_cookie_flags (line 3501) counts Secure and HttpOnly flags across all Set-Cookie directives to assess session security.

Frequently Asked Questions

What command-line option enables HTTP security header testing in testssl.sh?

Use the -h, --header, or --headers option. According to the source code in testssl.sh, this flag triggers the sequential execution of run_hsts, run_hpkp, and run_cookie_flags around lines 25418–25420.

How does testssl.sh determine if an HSTS max-age value is sufficient?

The run_hsts function compares the extracted max-age value against the HSTS_MIN environment variable, which defaults to 180 days (15,552,000 seconds). Values below this threshold receive a MEDIUM severity rating, while missing or invalid values are graded LOW.

Can testssl.sh validate HPKP pins against certificate authorities?

Yes. The run_hpkp function (starting at line 2979) extracts base64-encoded SPKI values from pin-sha256 directives and compares them against the host certificate's public key and the known CA hashes stored in etc/ca_hashes.txt. Pins that fail to match generate WARN or HIGH severity findings.

Does testssl.sh support JSON output for header analysis?

Yes. When invoked with the -J flag, testssl.sh emits machine-readable JSON containing the header grades under keys like hsts, hpkp, and cookie. These correspond to the fileout identifiers used internally (HSTS_time, cookie_secure, etc.) and can be parsed with tools like jq for automated security monitoring.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →