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

> Discover how testssl.sh analyzes HTTP security headers like HSTS, HPKP, and cookie flags. Learn about its specialized parsing functions and validation against minimum thresholds.

- Repository: [Dirk Wetter/testssl.sh](https://github.com/drwetter/testssl.sh)
- Tags: deep-dive
- Published: 2026-03-01

---

**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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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:

```bash
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`](https://github.com/drwetter/testssl.sh/blob/main/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:

```bash
HSTS_MIN=${HSTS_MIN:-180}

```

Based on this comparison, [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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:

```bash
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`](https://github.com/drwetter/testssl.sh/blob/main/etc/ca_hashes.txt). Missing matches generate **WARN** or **HIGH** severity findings.

## How testssl.sh Checks Cookie Security Flags

Cookie analysis is handled by `run_cookie_flags`, starting at line 3501 in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/testssl.sh).

### Secure and HttpOnly Flag Detection

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

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

```

It then counts total cookies and identifies security attributes:

```bash
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:

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

```

This sequentially invokes `run_hsts`, `run_hpkp`, and `run_cookie_flags` (around lines 25418–25420 in [`testssl.sh`](https://github.com/drwetter/testssl.sh/blob/main/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:

```bash
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:

```bash
./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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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`](https://github.com/drwetter/testssl.sh/blob/main/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.