Skip to content
Merged
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
22 changes: 21 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Http11Probe is an HTTP/1.1 compliance and security tester. It sends raw TCP requ

## TASK A: Add a new test

Adding a test requires changes to **4 locations** (sometimes 3 if URL mapping is automatic).
Adding a test requires changes to **5 locations** (sometimes 4 if URL mapping is automatic).

### Step 1 — Add the test case to the suite file

Expand Down Expand Up @@ -215,6 +215,26 @@ Find the `{{</* cards */>}}` block and add a new card entry. Place scored tests

The `link` value is the filename without `.md`.

### Step 5 — Add a row to the RFC Requirement Dashboard

**File:** `docs/content/docs/rfc-requirement-dashboard.md`

This page classifies every test by its RFC 2119 requirement level. You must:

1. **Add a row** to the correct table based on the test's requirement level:
- `MUST` / `MUST NOT` → "MUST-Level Requirements" table (use the "Reject with 400" sub-table if the RFC explicitly mandates 400, otherwise the "Reject (400 or Connection Close Acceptable)" sub-table)
- `SHOULD` / `SHOULD NOT` → "SHOULD-Level Requirements" table
- `MAY` → "MAY-Level Requirements" table
- `Scored = false` → "Unscored Tests" table (regardless of RFC keyword)

2. **Update the counts** in:
- The summary table at the top (increment the matching requirement level)
- The total test count in both the `description` frontmatter and the "Total: N tests" line
- The "Requirement Level by Suite" section (increment the matching suite + level)
- The "RFC Section Cross-Reference" table (increment existing section count or add a new row)

3. **Include** the test ID, suite name, RFC link, and an exact RFC quote with the keyword bolded (e.g., `**MUST**`).

### Verification checklist

After making all changes:
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

All notable changes to Http11Probe are documented in this file.

## [Unreleased]

### Added
- **9 new RFC 9110 compliance tests** sourced from [mohammed90/http-compliance-testing](https://github.com/mohammed90/http-compliance-testing):
- `COMP-HEAD-NO-BODY` — HEAD response must not contain a message body (RFC 9110 §9.3.2, MUST)
- `COMP-UNKNOWN-METHOD` — unrecognized method should be rejected with 501/405 (RFC 9110 §9.1, SHOULD)
- `COMP-405-ALLOW` — 405 response must include Allow header (RFC 9110 §15.5.6, MUST)
- `COMP-DATE-HEADER` — origin server must include Date header in responses (RFC 9110 §6.6.1, MUST)
- `COMP-NO-1XX-HTTP10` — server must not send 1xx to HTTP/1.0 client (RFC 9110 §15.2, MUST NOT)
- `COMP-NO-CL-IN-204` — Content-Length forbidden in 204 responses (RFC 9110 §8.6, MUST NOT)
- `SMUG-CL-COMMA-TRIPLE` — three comma-separated identical CL values (RFC 9110 §8.6, unscored)
- `COMP-OPTIONS-ALLOW` — OPTIONS response should include Allow header (RFC 9110 §9.3.7, SHOULD)
- `COMP-CONTENT-TYPE` — response with content should include Content-Type (RFC 9110 §8.3, SHOULD)

### Changed
- **AGENTS.md** — added Step 5 (RFC Requirement Dashboard) to the "Add a new test" task
- **RFC Requirement Dashboard** — updated with all 9 new tests, counts, and cross-references
- **Landing page cards** — removed hardcoded test count from RFC Requirement Dashboard subtitle

## [2026-02-14]

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/content/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ layout: hextra-home

{{< cards >}}
{{< card link="probe-results" title="Leaderboard" subtitle="See which frameworks pass the most tests, ranked from best to worst compliance." icon="chart-bar" >}}
{{< card link="docs/rfc-requirement-dashboard" title="RFC Requirement Dashboard" subtitle="All 148 tests classified by RFC 2119 level (MUST/SHOULD/MAY)." icon="document-search" >}}
{{< card link="docs/rfc-requirement-dashboard" title="RFC Requirement Dashboard" subtitle="Every test classified by RFC 2119 requirement level (MUST/SHOULD/MAY)." icon="document-search" >}}
{{< /cards >}}

<div style="height:60px"></div>
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Reference documentation for every test in Http11Probe, organized by topic. Each

{{< cards >}}
{{< card link="http-overview" title="Understanding HTTP" subtitle="What HTTP is, how HTTP/1.1 works at the wire level, its history from 0.9 to 3, and alternatives." icon="globe-alt" >}}
{{< card link="rfc-requirement-dashboard" title="RFC Requirement Dashboard" subtitle="All 148 tests classified by RFC 2119 level (MUST/SHOULD/MAY)." icon="document-search" >}}
{{< card link="rfc-requirement-dashboard" title="RFC Requirement Dashboard" subtitle="Every test classified by RFC 2119 requirement level (MUST/SHOULD/MAY)." icon="document-search" >}}
{{< card link="rfc-basics" title="RFC Basics" subtitle="What RFCs are, how to read requirement levels (MUST/SHOULD/MAY), and which RFCs define HTTP/1.1." icon="book-open" >}}
{{< card link="baseline" title="Baseline" subtitle="Sanity request used to confirm the target is reachable before running negative tests." icon="check-circle" >}}
{{< card link="line-endings" title="Line Endings" subtitle="CRLF requirements, bare LF handling, and bare CR rejection per RFC 9112 Section 2.2." icon="code" >}}
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/content-length/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ The `Content-Length` header indicates the size of the message body in bytes. Its
{{< cards >}}
{{< card link="cl-non-numeric" title="CL-NON-NUMERIC" subtitle="Non-numeric Content-Length value." >}}
{{< card link="cl-plus-sign" title="CL-PLUS-SIGN" subtitle="Content-Length with a + prefix." >}}
{{< card link="no-cl-in-204" title="NO-CL-IN-204" subtitle="Content-Length forbidden in 204 responses." >}}
{{< /cards >}}
35 changes: 35 additions & 0 deletions docs/content/docs/content-length/no-cl-in-204.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "NO-CL-IN-204"
description: "NO-CL-IN-204 test documentation"
weight: 3
---

| | |
|---|---|
| **Test ID** | `COMP-NO-CL-IN-204` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §8.6](https://www.rfc-editor.org/rfc/rfc9110#section-8.6) |
| **Requirement** | MUST NOT |
| **Expected** | `204` without `Content-Length` |

## What it sends

An OPTIONS request to the root path. Some servers respond with `204 No Content`, which triggers the validation.

```http
OPTIONS / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "A server MUST NOT send a Content-Length header field in any response with a status code of 1xx (Informational) or 204 (No Content)." -- RFC 9110 Section 8.6

## Why it matters

A `204 No Content` response explicitly signals that there is no body. Including `Content-Length` contradicts this, and some clients or proxies may attempt to read body bytes based on the Content-Length value. On persistent connections, this causes desync — the client reads the next response's bytes as body data for the 204, corrupting the entire connection. If the server does not return 204 for this request, the test reports a warning since the prohibition cannot be verified.

## Sources

- [RFC 9110 §8.6 -- Content-Length](https://www.rfc-editor.org/rfc/rfc9110#section-8.6)
3 changes: 3 additions & 0 deletions docs/content/docs/headers/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ HTTP header fields follow a strict grammar: `field-name ":" OWS field-value OWS`
{{< card link="header-no-colon" title="HEADER-NO-COLON" subtitle="Header line with no colon separator." >}}
{{< card link="whitespace-before-headers" title="WHITESPACE-BEFORE-HEADERS" subtitle="Whitespace before the first header line." >}}
{{< card link="expect-unknown" title="EXPECT-UNKNOWN" subtitle="Unknown Expect value. Should respond with 417." >}}
{{< card link="date-header" title="DATE-HEADER" subtitle="Origin server must include Date header in responses." >}}
{{< card link="no-1xx-http10" title="NO-1XX-HTTP10" subtitle="Server must not send 1xx to HTTP/1.0 client." >}}
{{< card link="content-type-presence" title="CONTENT-TYPE" subtitle="Response with content should include Content-Type." >}}
{{< /cards >}}
39 changes: 39 additions & 0 deletions docs/content/docs/headers/content-type-presence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: "CONTENT-TYPE"
description: "CONTENT-TYPE test documentation"
weight: 12
---

| | |
|---|---|
| **Test ID** | `COMP-CONTENT-TYPE` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §8.3](https://www.rfc-editor.org/rfc/rfc9110#section-8.3) |
| **Requirement** | SHOULD |
| **Expected** | `2xx` with `Content-Type` header |

## What it sends

A standard GET request. The test validates that the server includes a `Content-Type` header when the response contains a body.

```http
GET / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "A sender that generates a message containing content SHOULD generate a Content-Type header field in the message unless the intended media type of the enclosed representation is unknown to the sender." -- RFC 9110 Section 8.3

And:

> "If a Content-Type header field is not present, the recipient MAY either assume a media type of 'application/octet-stream' or examine the data to determine its type." -- RFC 9110 Section 8.3

## Why it matters

Without Content-Type, clients must guess the media type through content sniffing, which is a well-known security risk. Browsers performing MIME sniffing may interpret a response as HTML when it was intended as plain text, enabling XSS attacks. Including Content-Type is a baseline security practice.

## Sources

- [RFC 9110 §8.3 -- Content-Type](https://www.rfc-editor.org/rfc/rfc9110#section-8.3)
35 changes: 35 additions & 0 deletions docs/content/docs/headers/date-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "DATE-HEADER"
description: "DATE-HEADER test documentation"
weight: 10
---

| | |
|---|---|
| **Test ID** | `COMP-DATE-HEADER` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §6.6.1](https://www.rfc-editor.org/rfc/rfc9110#section-6.6.1) |
| **Requirement** | MUST |
| **Expected** | `2xx` with `Date` header |

## What it sends

A standard GET request. The test validates that the server includes a `Date` header in its response.

```http
GET / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "An origin server with a clock MUST generate a Date header field in all 2xx (Successful), 3xx (Redirection), and 4xx (Client Error) responses, and MAY generate a Date header field in 1xx (Informational) and 5xx (Server Error) responses." -- RFC 9110 Section 6.6.1

## Why it matters

The Date header is essential for HTTP caching. Caches use it to calculate age, determine freshness, and resolve clock skew between origin servers and intermediaries. Without it, caches cannot properly compute expiration times, leading to either stale content being served or unnecessary revalidation.

## Sources

- [RFC 9110 §6.6.1 -- Date](https://www.rfc-editor.org/rfc/rfc9110#section-6.6.1)
38 changes: 38 additions & 0 deletions docs/content/docs/headers/no-1xx-http10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: "NO-1XX-HTTP10"
description: "NO-1XX-HTTP10 test documentation"
weight: 11
---

| | |
|---|---|
| **Test ID** | `COMP-NO-1XX-HTTP10` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §15.2](https://www.rfc-editor.org/rfc/rfc9110#section-15.2) |
| **Requirement** | MUST NOT |
| **Expected** | Non-1xx response |

## What it sends

An HTTP/1.0 POST with `Expect: 100-continue` and a body, designed to test whether the server incorrectly sends a `100 Continue` interim response to an HTTP/1.0 client.

```http
POST / HTTP/1.0\r\n
Host: localhost:8080\r\n
Expect: 100-continue\r\n
Content-Length: 5\r\n
\r\n
hello
```

## What the RFC says

> "Since HTTP/1.0 did not define any 1xx status codes, a server MUST NOT send a 1xx response to an HTTP/1.0 client." -- RFC 9110 Section 15.2

## Why it matters

HTTP/1.0 clients do not understand interim responses. If a server sends `100 Continue` to an HTTP/1.0 client, the client may interpret the `100` status line as a malformed final response, discard it as garbage, or enter an undefined state. This is especially dangerous in proxy chains where an HTTP/1.0 hop cannot forward 1xx responses correctly.

## Sources

- [RFC 9110 §15.2 -- Informational 1xx](https://www.rfc-editor.org/rfc/rfc9110#section-15.2)
40 changes: 40 additions & 0 deletions docs/content/docs/request-line/405-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: "405-ALLOW"
description: "405-ALLOW test documentation"
weight: 17
---

| | |
|---|---|
| **Test ID** | `COMP-405-ALLOW` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §15.5.6](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.6) |
| **Requirement** | MUST |
| **Expected** | `405` with `Allow` header |

## What it sends

A DELETE request to the root path, which most servers do not support. This is intended to trigger a 405 response.

```http
DELETE / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "The origin server MUST generate an Allow header field in a 405 response containing a list of the target resource's currently supported methods." -- RFC 9110 Section 15.5.6

And:

> "An origin server MUST generate an Allow header field in a 405 (Method Not Allowed) response and MAY do so in any other response." -- RFC 9110 Section 10.2.1

## Why it matters

The Allow header in a 405 response tells clients which methods are actually supported. Without it, clients have no way to discover valid methods for the resource, forcing them to guess. Automated tools and API clients depend on this header for correct operation. If the server returns a status other than 405 (e.g., it accepts DELETE or returns 501), the test reports a warning since the Allow requirement cannot be verified.

## Sources

- [RFC 9110 §15.5.6 -- 405 Method Not Allowed](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.6)
- [RFC 9110 §10.2.1 -- Allow](https://www.rfc-editor.org/rfc/rfc9110#section-10.2.1)
4 changes: 4 additions & 0 deletions docs/content/docs/request-line/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Note this is a SHOULD, not a MUST. The RFC recommends 400 but does not mandate i
{{< card link="options-star" title="OPTIONS-STAR" subtitle="OPTIONS * — valid asterisk-form request." >}}
{{< card link="unknown-te-501" title="UNKNOWN-TE-501" subtitle="Unknown Transfer-Encoding without CL." >}}
{{< card link="method-connect" title="METHOD-CONNECT" subtitle="CONNECT to an origin server must be rejected." >}}
{{< card link="head-no-body" title="HEAD-NO-BODY" subtitle="HEAD response must not contain a message body." >}}
{{< card link="unknown-method" title="UNKNOWN-METHOD" subtitle="Unrecognized method should be rejected with 501 or 405." >}}
{{< card link="405-allow" title="405-ALLOW" subtitle="405 response must include an Allow header." >}}
{{< card link="options-allow" title="OPTIONS-ALLOW" subtitle="OPTIONS response should include Allow header." >}}
{{< /cards >}}

### Unscored
Expand Down
37 changes: 37 additions & 0 deletions docs/content/docs/request-line/head-no-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: "HEAD-NO-BODY"
description: "HEAD-NO-BODY test documentation"
weight: 15
---

| | |
|---|---|
| **Test ID** | `COMP-HEAD-NO-BODY` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §9.3.2](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.2) |
| **Requirement** | MUST |
| **Expected** | `2xx` with no body |

## What it sends

A standard HEAD request. The server must respond with headers only — no message body.

```http
HEAD / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "The HEAD method is identical to GET except that the server MUST NOT send content in the response." -- RFC 9110 Section 9.3.2

The server may include `Content-Length` or `Transfer-Encoding` headers to indicate what the body *would have been* for a GET request, but the actual response must contain zero body bytes.

## Why it matters

If a server sends body content in response to HEAD, it corrupts connection state on persistent connections. A client or proxy reading the connection will interpret those extra bytes as the start of the next response, leading to response desync. This is a particularly dangerous defect in proxy environments where multiple clients share connections.

## Sources

- [RFC 9110 §9.3.2 -- HEAD](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.2)
35 changes: 35 additions & 0 deletions docs/content/docs/request-line/options-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "OPTIONS-ALLOW"
description: "OPTIONS-ALLOW test documentation"
weight: 18
---

| | |
|---|---|
| **Test ID** | `COMP-OPTIONS-ALLOW` |
| **Category** | Compliance |
| **RFC** | [RFC 9110 §9.3.7](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.7) |
| **Requirement** | SHOULD |
| **Expected** | `2xx` with `Allow` header |

## What it sends

An OPTIONS request to the root path, asking the server to describe its capabilities for that resource.

```http
OPTIONS / HTTP/1.1\r\n
Host: localhost:8080\r\n
\r\n
```

## What the RFC says

> "A server generating a successful response to OPTIONS SHOULD send any header that might indicate optional features implemented by the server and applicable to the target resource (e.g., Allow)." -- RFC 9110 Section 9.3.7

## Why it matters

OPTIONS is the standard mechanism for clients to discover which methods a resource supports. The Allow header is the primary vehicle for this information. Without it, the OPTIONS response provides no actionable data. API clients and CORS preflight logic depend on this header to function correctly.

## Sources

- [RFC 9110 §9.3.7 -- OPTIONS](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.7)
Loading