Skip to content

Commit 840e27f

Browse files
MDA2AVclaude
andcommitted
Docs — replace Glossary with comprehensive per-test documentation
Replace the single Glossary page with 54 markdown files across 8 sections: Line Endings, Request Line, Headers, Host Header, Content-Length, Request Smuggling, and Malformed Input. Each test has its own page with RFC references, requirement levels, and security context. Also applies strict RFC compliance to probe scoring (MUST = 400 only, SHOULD = 400 or close, MAY = either acceptable). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bad092f commit 840e27f

59 files changed

Lines changed: 1493 additions & 195 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/probe.yml

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -143,57 +143,57 @@ jobs:
143143
'RFC9112-2.2-BARE-LF-REQUEST-LINE': {
144144
'accept': [400], 'close_ok': True, 'timeout_ok': False,
145145
'expected': '400 or close',
146-
'reason': 'Bare LF in request-line is a framing violation (RFC 9112 §2.2)'
146+
'reason': 'Bare LF — recipient MAY accept but rejection is stricter (RFC 9112 §2.2)'
147147
},
148148
'RFC9112-2.2-BARE-LF-HEADER': {
149149
'accept': [400], 'close_ok': True, 'timeout_ok': False,
150150
'expected': '400 or close',
151-
'reason': 'Bare LF in header field is a framing violation (RFC 9112 §2.2)'
151+
'reason': 'Bare LF — recipient MAY accept but rejection is stricter (RFC 9112 §2.2)'
152152
},
153153
'RFC9112-5.1-OBS-FOLD': {
154-
'accept': [400], 'close_ok': True, 'timeout_ok': False,
155-
'expected': '400 or close',
156-
'reason': 'Obs-fold (line folding) is deprecated and must be rejected (RFC 9112 §5.1)'
154+
'accept': [400], 'close_ok': False, 'timeout_ok': False,
155+
'expected': '400',
156+
'reason': 'MUST reject by sending 400 or replace with SP (RFC 9112 §5.1)'
157157
},
158158
'RFC9110-5.6.2-SP-BEFORE-COLON': {
159-
'accept': [400], 'close_ok': True, 'timeout_ok': False,
160-
'expected': '400 or close',
161-
'reason': 'Space between header name and colon is invalid (RFC 9110 §5.6.2)'
159+
'accept': [400], 'close_ok': False, 'timeout_ok': False,
160+
'expected': '400',
161+
'reason': 'MUST reject with 400 (RFC 9112 §5)'
162162
},
163163
'RFC9112-3-MULTI-SP-REQUEST-LINE': {
164164
'accept': [400], 'close_ok': True, 'timeout_ok': False,
165165
'expected': '400 or close',
166-
'reason': 'Multiple SP in request-line is malformed (RFC 9112 §3)'
166+
'reason': 'SHOULD respond with 400 (RFC 9112 §3)'
167167
},
168168
'RFC9112-7.1-MISSING-HOST': {
169-
'accept': [400], 'close_ok': True, 'timeout_ok': False,
170-
'expected': '400 or close',
171-
'reason': 'Missing Host header requires 400 (RFC 9112 §7.1)'
169+
'accept': [400], 'close_ok': False, 'timeout_ok': False,
170+
'expected': '400',
171+
'reason': 'MUST respond with 400 (RFC 9112 §3.2)'
172172
},
173173
'RFC9112-2.3-INVALID-VERSION': {
174174
'accept': [400, 505], 'close_ok': True, 'timeout_ok': False,
175175
'expected': '400/505 or close',
176-
'reason': 'Invalid HTTP version must be rejected (RFC 9112 §2.3)'
176+
'reason': 'No MUST — 505 is available but not mandated (RFC 9112 §2.3)'
177177
},
178178
'RFC9112-5-EMPTY-HEADER-NAME': {
179179
'accept': [400], 'close_ok': True, 'timeout_ok': False,
180180
'expected': '400 or close',
181181
'reason': 'Empty header name (leading colon) is invalid (RFC 9112 §5)'
182182
},
183183
'RFC9112-3-CR-ONLY-LINE-ENDING': {
184-
'accept': [400], 'close_ok': True, 'timeout_ok': False,
185-
'expected': '400 or close',
186-
'reason': 'CR without LF is a framing violation (RFC 9112 §2.2)'
184+
'accept': [400], 'close_ok': False, 'timeout_ok': False,
185+
'expected': '400',
186+
'reason': 'MUST consider invalid or replace with SP (RFC 9112 §2.2)'
187187
},
188188
'SMUG-CL-TE-BOTH': {
189189
'accept': [400], 'close_ok': True, 'timeout_ok': False,
190190
'expected': '400 or close',
191-
'reason': 'CL + TE together enables smuggling — must reject (RFC 9112 §6.1)'
191+
'reason': 'CL + TE together "ought to" be handled as error (RFC 9112 §6.3)'
192192
},
193193
'SMUG-DUPLICATE-CL': {
194194
'accept': [400], 'close_ok': True, 'timeout_ok': False,
195195
'expected': '400 or close',
196-
'reason': 'Conflicting Content-Length values enable smuggling (RFC 9110 §8.6)'
196+
'reason': 'MUST treat as unrecoverable error (RFC 9112 §6.3)'
197197
},
198198
'SMUG-CL-LEADING-ZEROS': {
199199
'accept': [400], 'close_ok': True, 'timeout_ok': False,
@@ -268,17 +268,17 @@ jobs:
268268
'RFC9112-3-MISSING-TARGET': {
269269
'accept': [400], 'close_ok': True, 'timeout_ok': False,
270270
'expected': '400 or close',
271-
'reason': 'Missing request-target is malformed (RFC 9112 §3)'
271+
'reason': 'SHOULD respond with 400 (RFC 9112 §3)'
272272
},
273273
'RFC9112-3.2-FRAGMENT-IN-TARGET': {
274274
'accept': [400], 'close_ok': True, 'timeout_ok': False,
275275
'expected': '400 or close',
276-
'reason': 'Fragment in request-target is invalid (RFC 9112 §3.2)'
276+
'reason': 'SHOULD respond with 400 (RFC 9112 §3)'
277277
},
278278
'RFC9112-2.3-HTTP09-REQUEST': {
279279
'accept': [400], 'close_ok': True, 'timeout_ok': True,
280280
'expected': '400/close/timeout',
281-
'reason': 'HTTP/0.9 requests must be rejected (RFC 9112 §2.3)'
281+
'reason': 'Invalid request-line — SHOULD respond with 400 (RFC 9112 §3)'
282282
},
283283
'RFC9112-5-INVALID-HEADER-NAME': {
284284
'accept': [400], 'close_ok': True, 'timeout_ok': False,
@@ -291,19 +291,19 @@ jobs:
291291
'reason': 'Header line without colon is malformed (RFC 9112 §5)'
292292
},
293293
'RFC9110-5.4-DUPLICATE-HOST': {
294-
'accept': [400], 'close_ok': True, 'timeout_ok': False,
295-
'expected': '400 or close',
296-
'reason': 'Duplicate Host headers with different values must be rejected (RFC 9110 §5.4)'
294+
'accept': [400], 'close_ok': False, 'timeout_ok': False,
295+
'expected': '400',
296+
'reason': 'MUST respond with 400 (RFC 9112 §3.2)'
297297
},
298298
'RFC9112-6.1-CL-NON-NUMERIC': {
299299
'accept': [400], 'close_ok': True, 'timeout_ok': False,
300300
'expected': '400 or close',
301-
'reason': 'Non-numeric Content-Length is invalid (RFC 9112 §6.1)'
301+
'reason': 'MUST treat as unrecoverable error (RFC 9112 §6.3)'
302302
},
303303
'RFC9112-6.1-CL-PLUS-SIGN': {
304304
'accept': [400], 'close_ok': True, 'timeout_ok': False,
305305
'expected': '400 or close',
306-
'reason': 'Content-Length with + sign is invalid (RFC 9112 §6.1)'
306+
'reason': 'MUST treat as unrecoverable error (RFC 9112 §6.3)'
307307
},
308308
'SMUG-TECL-PIPELINE': {
309309
'accept': [400], 'close_ok': True, 'timeout_ok': False,

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,8 @@ CodeCoverage/
5151
# NUnit
5252
*.VisualState.xml
5353
TestResult.xml
54-
nunit-*.xml
54+
nunit-*.xml
55+
# Hugo build artifacts
56+
docs/public/
57+
docs/.hugo_build.lock
58+
docs/resources/

docs/assets/css/custom.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ a[href$="/probe-results/"] {
8080
font-size: 1.1em !important;
8181
}
8282

83-
/* Separator before Glossary */
84-
a[href$="/glossary"],
85-
a[href$="/glossary/"] {
83+
/* Separator before Docs */
84+
a[href$="/docs"],
85+
a[href$="/docs/"] {
8686
margin-left: 12px !important;
8787
padding-left: 14px !important;
8888
border-left: 1px solid rgba(128, 128, 128, 0.3) !important;

docs/content/docs/_index.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
title: Docs
3+
sidebar:
4+
open: true
5+
---
6+
7+
Reference documentation for every test in Http11Probe, organized by topic. Each page explains the RFC requirement, what the test sends, what response is expected, and why it matters.
8+
9+
{{< cards >}}
10+
{{< 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" >}}
11+
{{< 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" >}}
12+
{{< card link="request-line" title="Request Line" subtitle="Request-line format, multiple spaces, missing target, fragments, HTTP version validation." icon="terminal" >}}
13+
{{< card link="headers" title="Header Syntax" subtitle="Obs-fold, space before colon, empty names, invalid characters, missing colon." icon="document-text" >}}
14+
{{< card link="host-header" title="Host Header" subtitle="Missing Host, duplicate Host — the only tests where RFC explicitly mandates 400." icon="server" >}}
15+
{{< card link="content-length" title="Content-Length" subtitle="Non-numeric CL, plus sign, integer overflow, leading zeros, negative values." icon="calculator" >}}
16+
{{< card link="smuggling" title="Request Smuggling" subtitle="CL+TE conflicts, TE obfuscation, pipeline injection, and why ambiguous framing is dangerous." icon="shield-exclamation" >}}
17+
{{< card link="malformed-input" title="Malformed Input" subtitle="Binary garbage, oversized fields, control characters, incomplete requests." icon="lightning-bolt" >}}
18+
{{< /cards >}}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: Content-Length
3+
weight: 6
4+
sidebar:
5+
open: true
6+
---
7+
8+
The `Content-Length` header indicates the size of the message body in bytes. Its grammar is strict: `Content-Length = 1*DIGIT`. Any deviation — non-numeric characters, plus signs, leading zeros, negative values, overflow — can cause parsers to disagree on body boundaries.
9+
10+
## Key Rules
11+
12+
**Grammar**: `1*DIGIT` means one or more ASCII digits (`0-9`). No signs, no spaces, no hex.
13+
14+
> “If a message is received without Transfer-Encoding and with an invalid Content-Length header field, then the message framing is invalid and the recipient **MUST** treat it as an unrecoverable error...” — RFC 9112 Section 6.3
15+
16+
## Tests
17+
18+
{{< cards >}}
19+
{{< card link="cl-non-numeric" title="CL-NON-NUMERIC" subtitle="Non-numeric Content-Length value." >}}
20+
{{< card link="cl-plus-sign" title="CL-PLUS-SIGN" subtitle="Content-Length with a + prefix." >}}
21+
{{< /cards >}}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: "CL-NON-NUMERIC"
3+
weight: 1
4+
---
5+
6+
| | |
7+
|---|---|
8+
| **Test ID** | `RFC9112-6.1-CL-NON-NUMERIC` |
9+
| **Category** | Compliance |
10+
| **RFC** | [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1), [Section 6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3) |
11+
| **Requirement** | MUST |
12+
| **Expected** | `400` or close |
13+
14+
## What it sends
15+
16+
A request with a non-numeric `Content-Length` value, e.g., `Content-Length: abc`.
17+
18+
## What the RFC says
19+
20+
Content-Length is defined as `1*DIGIT`. A value containing non-digit characters does not match this grammar.
21+
22+
> “If a message is received without Transfer-Encoding and with an invalid Content-Length header field, then the message framing is invalid and the recipient **MUST** treat it as an unrecoverable error...”
23+
24+
“Unrecoverable error” means the server must reject — either with a 400 response or by closing the connection. It cannot attempt to parse the body.
25+
26+
## Sources
27+
28+
- [RFC 9110 Section 8.6 — Content-Length](https://www.rfc-editor.org/rfc/rfc9110#section-8.6)
29+
- [RFC 9112 Section 6.3 — Message Body Length](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: "CL-PLUS-SIGN"
3+
weight: 2
4+
---
5+
6+
| | |
7+
|---|---|
8+
| **Test ID** | `RFC9112-6.1-CL-PLUS-SIGN` |
9+
| **Category** | Compliance |
10+
| **RFC** | [RFC 9112 Section 6.1](https://www.rfc-editor.org/rfc/rfc9112#section-6.1), [Section 6.3](https://www.rfc-editor.org/rfc/rfc9112#section-6.3) |
11+
| **Requirement** | MUST |
12+
| **Expected** | `400` or close |
13+
14+
## What it sends
15+
16+
A request with a plus sign in the Content-Length value: `Content-Length: +42`.
17+
18+
## What the RFC says
19+
20+
The `+` character is not in the DIGIT set (`%x30-39`), so `+42` does not match `1*DIGIT`. This is an invalid Content-Length and MUST be treated as an unrecoverable error.
21+
22+
## Why it matters
23+
24+
Many programming languages’ integer parsers accept leading `+` signs (e.g., `parseInt("+42")` returns `42` in JavaScript). A server that blindly passes Content-Length through such a parser may accept this value while another server in the chain rejects it — creating a framing disagreement.
25+
26+
## Sources
27+
28+
- [RFC 9110 Section 8.6 — Content-Length](https://www.rfc-editor.org/rfc/rfc9110#section-8.6)
29+
- [RFC 9112 Section 6.3 — Message Body Length](https://www.rfc-editor.org/rfc/rfc9112#section-6.3)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: Header Syntax
3+
weight: 4
4+
sidebar:
5+
open: true
6+
---
7+
8+
HTTP header fields follow a strict grammar: `field-name ":" OWS field-value OWS`. RFC 9112 Section 5 and RFC 9110 Section 5.6.2 define what constitutes a valid header. Violations can lead to parser disagreements and smuggling.
9+
10+
## Key Rules
11+
12+
**Space before colon** — the only header syntax violation with an explicit MUST-400:
13+
14+
> "A server **MUST** reject, with a response status code of 400 (Bad Request), any received request message that contains whitespace between a header field name and colon." — RFC 9112 Section 5
15+
16+
**Obs-fold** (line folding with leading whitespace):
17+
18+
> "A server that receives an obs-fold in a request message that is not within a message/http container **MUST** either reject the message by sending a 400 (Bad Request)... or replace each received obs-fold with one or more SP octets prior to interpreting the field value." — RFC 9112 Section 5.1
19+
20+
**Field name** must be a `token` = `1*tchar`, meaning at least one valid token character. Empty names, non-ASCII bytes, and special characters are all violations.
21+
22+
## Tests
23+
24+
{{< cards >}}
25+
{{< card link="sp-before-colon" title="SP-BEFORE-COLON" subtitle="Space between field name and colon. MUST reject with 400." >}}
26+
{{< card link="obs-fold" title="OBS-FOLD" subtitle="Obsolete line folding. MUST reject with 400 or replace with SP." >}}
27+
{{< card link="empty-header-name" title="EMPTY-HEADER-NAME" subtitle="Leading colon with no field name." >}}
28+
{{< card link="invalid-header-name" title="INVALID-HEADER-NAME" subtitle="Non-token characters in field name." >}}
29+
{{< card link="header-no-colon" title="HEADER-NO-COLON" subtitle="Header line with no colon separator." >}}
30+
{{< /cards >}}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: "EMPTY-HEADER-NAME"
3+
weight: 3
4+
---
5+
6+
| | |
7+
|---|---|
8+
| **Test ID** | `RFC9112-5-EMPTY-HEADER-NAME` |
9+
| **Category** | Compliance |
10+
| **RFC** | [RFC 9112 Section 5](https://www.rfc-editor.org/rfc/rfc9112#section-5) |
11+
| **Requirement** | Implicit MUST (grammar violation) |
12+
| **Expected** | `400` or close |
13+
14+
## What it sends
15+
16+
A header line starting with a colon — effectively an empty field name: `: value`.
17+
18+
## What the RFC says
19+
20+
Field names are defined as `token = 1*tchar`, requiring at least one valid token character. An empty string does not match `1*tchar`. While there is no explicit "MUST reject empty field names with 400" statement, a line starting with `:` fails to match the `field-line` grammar entirely.
21+
22+
## Sources
23+
24+
- [RFC 9112 Section 5 — Field Syntax](https://www.rfc-editor.org/rfc/rfc9112#section-5)
25+
- [RFC 9110 Section 5.1 — Field Names](https://www.rfc-editor.org/rfc/rfc9110#section-5.1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
title: "HEADER-NO-COLON"
3+
weight: 5
4+
---
5+
6+
| | |
7+
|---|---|
8+
| **Test ID** | `RFC9112-5-HEADER-NO-COLON` |
9+
| **Category** | Compliance |
10+
| **RFC** | [RFC 9112 Section 5](https://www.rfc-editor.org/rfc/rfc9112#section-5) |
11+
| **Requirement** | Implicit MUST (grammar violation) |
12+
| **Expected** | `400` or close |
13+
14+
## What it sends
15+
16+
A header line with no colon: `InvalidHeaderNoColon`.
17+
18+
## What the RFC says
19+
20+
The field-line grammar is `field-name ":" OWS field-value OWS`. A line without a colon does not match this grammar. It could be misinterpreted as a continuation line, a new request, or garbage — any of which is dangerous.
21+
22+
## Sources
23+
24+
- [RFC 9112 Section 5 — Field Syntax](https://www.rfc-editor.org/rfc/rfc9112#section-5)

0 commit comments

Comments
 (0)