| [ |
| { |
| "id": "COMP-ABSOLUTE-FORM", |
| "description": "Absolute-form request-target — server should accept per RFC", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2.2", |
| "rfcLevel": "Should", |
| "expected": "2xx preferred; 400/close/timeout = warn", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET http://127.0.0.1:<PORT>/ HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-ACCEPT-NONSENSE", |
| "description": "Unrecognized Accept value — server may return 406 or default representation", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §12.5.1", |
| "rfcLevel": "Should", |
| "expected": "406 or 2xx", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nAccept: application/x-nonsense\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK", |
| "behavioralNote": "Server ignored unrecognized Accept — returned default representation" |
| }, |
| { |
| "id": "COMP-ASTERISK-WITH-GET", |
| "description": "Asterisk-form (*) request-target with GET must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2.4", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Fail", |
| "statusCode": 500, |
| "rawRequest": "GET * HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 500 Internal Server Error\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 21\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nInternal Server Error" |
| }, |
| { |
| "id": "COMP-BASELINE", |
| "description": "Valid GET request — confirms server is reachable", |
| "category": "Compliance", |
| "rfcLevel": "NotApplicable", |
| "expected": "2xx", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-CHUNKED-BODY", |
| "description": "Valid single-chunk POST must be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1", |
| "rfcLevel": "Must", |
| "expected": "2xx + echo", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n", |
| "behavioralNote": "Echoed correctly" |
| }, |
| { |
| "id": "COMP-CHUNKED-EMPTY", |
| "description": "Zero-length chunked body (just terminator) must be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1", |
| "rfcLevel": "Must", |
| "expected": "2xx or close", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n0\r\n\r\n" |
| }, |
| { |
| "id": "COMP-CHUNKED-EXTENSION", |
| "description": "Chunk extension (valid per RFC) — server should accept or may reject", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1.1", |
| "rfcLevel": "Must", |
| "expected": "2xx preferred; 400 warns", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n5;ext=value\r\nhello\r\n0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n", |
| "behavioralNote": "Echoed correctly" |
| }, |
| { |
| "id": "COMP-CHUNKED-HEX-UPPERCASE", |
| "description": "Chunk size with uppercase hex (A = 10) should be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1", |
| "rfcLevel": "Must", |
| "expected": "2xx + echo", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\nA\r\nhelloworld\r\n0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nA\r\nhelloworld\r\n0\r\n\r\n", |
| "behavioralNote": "Echoed correctly" |
| }, |
| { |
| "id": "COMP-CHUNKED-MULTI", |
| "description": "Valid multi-chunk POST must be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1", |
| "rfcLevel": "Must", |
| "expected": "2xx + echo", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n", |
| "behavioralNote": "Echoed correctly" |
| }, |
| { |
| "id": "COMP-CHUNKED-NO-FINAL", |
| "description": "Chunked body without zero terminator — incomplete transfer", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1", |
| "rfcLevel": "Must", |
| "expected": "400/close/timeout", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n" |
| }, |
| { |
| "id": "COMP-CHUNKED-TRAILER-VALID", |
| "description": "Valid chunked body with trailer field should be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §7.1.2", |
| "rfcLevel": "Must", |
| "expected": "2xx + echo", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\nX-Checksum: abc\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n", |
| "behavioralNote": "Empty body — server did not echo" |
| }, |
| { |
| "id": "COMP-CONNECTION-CLOSE", |
| "description": "Server must close connection after responding to Connection: close", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §9.3", |
| "rfcLevel": "Must", |
| "expected": "2xx + close", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nConnection: close\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nconnection: close\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-CONTENT-TYPE", |
| "description": "Response with content should include Content-Type header", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §8.3", |
| "rfcLevel": "Should", |
| "expected": "2xx with Content-Type", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-DATE-FORMAT", |
| "description": "Date header should use IMF-fixdate format", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §5.6.7", |
| "rfcLevel": "Should", |
| "expected": "IMF-fixdate format", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK", |
| "behavioralNote": "IMF-fixdate: <DATE>" |
| }, |
| { |
| "id": "COMP-DATE-HEADER", |
| "description": "Origin server must include Date header in responses", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §6.6.1", |
| "rfcLevel": "Must", |
| "expected": "2xx with Date header", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-DUPLICATE-CT", |
| "description": "Duplicate Content-Type headers with different values", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §5.3", |
| "rfcLevel": "Should", |
| "expected": "400 or 2xx", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\nContent-Type: text/plain\r\nContent-Type: text/html\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n" |
| }, |
| { |
| "id": "COMP-DUPLICATE-HOST-SAME", |
| "description": "Duplicate Host headers with identical values must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-EXPECT-UNKNOWN", |
| "description": "Unknown Expect value should be rejected with 417", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §10.1.1", |
| "rfcLevel": "May", |
| "expected": "417 or 2xx", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nExpect: 200-ok\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-GET-WITH-CL-BODY", |
| "description": "GET with Content-Length and body — semantically unusual", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.1", |
| "rfcLevel": "May", |
| "expected": "400 or 2xx", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-HEAD-NO-BODY", |
| "description": "HEAD response must not contain a message body", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.2", |
| "rfcLevel": "Must", |
| "expected": "2xx with no body", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "HEAD / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n" |
| }, |
| { |
| "id": "COMP-HOST-EMPTY-VALUE", |
| "description": "Empty Host header value must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: \r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-HOST-WITH-PATH", |
| "description": "Host header with path component must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>/path\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-HOST-WITH-USERINFO", |
| "description": "Host header with userinfo (user@host) must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: user@127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-HTTP10-DEFAULT-CLOSE", |
| "description": "HTTP/1.0 without keep-alive — server should close connection after response", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §9.3", |
| "rfcLevel": "Should", |
| "expected": "2xx + close", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.0\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.0 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-HTTP10-NO-HOST", |
| "description": "HTTP/1.0 without Host header — valid per HTTP/1.0", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "May", |
| "expected": "200 or 400", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.0\r\n\r\n", |
| "rawResponse": "HTTP/1.0 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-HTTP12-VERSION", |
| "description": "HTTP/1.2 — higher minor version should be accepted as HTTP/1.x compatible", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "May", |
| "expected": "200 or 505", |
| "verdict": "Pass", |
| "rawRequest": "GET / HTTP/1.2\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-LEADING-CRLF", |
| "description": "Leading CRLF before request-line — server may ignore per RFC", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.2", |
| "rfcLevel": "Should", |
| "expected": "400 or 2xx; close/timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "\r\n\r\nGET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-LONG-URL-OK", |
| "description": "Server should accept request-lines of at least 8000 octets", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3", |
| "rfcLevel": "Should", |
| "expected": "not 414; close/timeout = warn", |
| "verdict": "Pass", |
| "statusCode": 404, |
| "rawRequest": "GET /aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-METHOD-CASE", |
| "description": "Lowercase method 'get' — methods are case-sensitive per RFC", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.1", |
| "rfcLevel": "Should", |
| "expected": "400/405/501 or 2xx; close/timeout = warn", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "get / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-METHOD-CONNECT", |
| "description": "CONNECT to an origin server must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.6", |
| "rfcLevel": "Should", |
| "expected": "400/405/501 or close", |
| "verdict": "Pass", |
| "statusCode": 400, |
| "rawRequest": "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n", |
| "rawResponse": "HTTP/1.1 400 Bad Request\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 11\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nBad Request" |
| }, |
| { |
| "id": "COMP-METHOD-TRACE", |
| "description": "TRACE request — should be disabled in production", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.8", |
| "rfcLevel": "Should", |
| "expected": "405/501 or 2xx", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "TRACE / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-NO-1XX-HTTP10", |
| "description": "Server must not send 1xx responses to an HTTP/1.0 client", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §15.2", |
| "rfcLevel": "Must", |
| "expected": "non-1xx response", |
| "verdict": "Pass", |
| "rawRequest": "POST / HTTP/1.0\r\nHost: 127.0.0.1:<PORT>\r\nExpect: 100-continue\r\nContent-Length: 5\r\n\r\nhello" |
| }, |
| { |
| "id": "COMP-OPTIONS-ALLOW", |
| "description": "OPTIONS response should include Allow header listing supported methods", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.7", |
| "rfcLevel": "Should", |
| "expected": "2xx with Allow header, or 405", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "OPTIONS / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-OPTIONS-STAR", |
| "description": "OPTIONS * is the only valid asterisk-form request", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2.4", |
| "rfcLevel": "Must", |
| "expected": "2xx or 405; close/timeout = warn", |
| "verdict": "Fail", |
| "statusCode": 500, |
| "rawRequest": "OPTIONS * HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 500 Internal Server Error\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 21\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nInternal Server Error" |
| }, |
| { |
| "id": "COMP-POST-CL-BODY", |
| "description": "POST with Content-Length and matching body must be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.2", |
| "rfcLevel": "Must", |
| "expected": "2xx + echo", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n", |
| "behavioralNote": "Echoed correctly" |
| }, |
| { |
| "id": "COMP-POST-CL-UNDERSEND", |
| "description": "POST with Content-Length: 10 but only 5 bytes sent — incomplete body", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.2", |
| "rfcLevel": "Must", |
| "expected": "400/close/timeout", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 10\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n" |
| }, |
| { |
| "id": "COMP-POST-CL-ZERO", |
| "description": "POST with Content-Length: 0 and no body must be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.2", |
| "rfcLevel": "Must", |
| "expected": "2xx or close", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 0\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n0\r\n\r\n" |
| }, |
| { |
| "id": "COMP-POST-NO-CL-NO-TE", |
| "description": "POST with neither Content-Length nor Transfer-Encoding — implicit zero-length body", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.3", |
| "rfcLevel": "Must", |
| "expected": "2xx or close", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n0\r\n\r\n" |
| }, |
| { |
| "id": "COMP-POST-UNSUPPORTED-CT", |
| "description": "POST with unrecognized Content-Type — server may return 415", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §15.5.16", |
| "rfcLevel": "May", |
| "expected": "415 or 2xx", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\nContent-Type: application/x-nonsense\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n", |
| "behavioralNote": "Server accepted unknown Content-Type" |
| }, |
| { |
| "id": "COMP-RANGE-INVALID", |
| "description": "Invalid Range header syntax should be ignored", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §14.2", |
| "rfcLevel": "May", |
| "expected": "200 or 416", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nRange: bytes=abc-xyz\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "COMP-RANGE-POST", |
| "description": "Range header on POST must be ignored — Range only applies to GET", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §14.2", |
| "rfcLevel": "Must", |
| "expected": "2xx (Range ignored)", |
| "verdict": "Pass", |
| "statusCode": 200, |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\nRange: bytes=0-10\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ntransfer-encoding: chunked\r\ndate: <DATE>\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\n5\r\nhello\r\n0\r\n\r\n" |
| }, |
| { |
| "id": "COMP-REQUEST-LINE-TAB", |
| "description": "Tab as request-line delimiter — SHOULD reject but MAY parse on whitespace", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3", |
| "rfcLevel": "Should", |
| "expected": "400 or 2xx; close/timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET\t/ HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-SPACE-IN-TARGET", |
| "description": "Whitespace inside request-target is invalid", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET /pa th HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-TRACE-SENSITIVE", |
| "description": "TRACE should exclude sensitive headers from echoed response", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.8", |
| "rfcLevel": "Should", |
| "expected": "405/501, or 200 without Auth", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "TRACE / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nAuthorization: Bearer secret-token-123\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-TRACE-WITH-BODY", |
| "description": "TRACE with Content-Length body should be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.3.8", |
| "rfcLevel": "Should", |
| "expected": "400/405 or 200", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "TRACE / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: 5\r\n\r\nhello", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-UNKNOWN-METHOD", |
| "description": "Unrecognized method should be rejected with 501 or 405", |
| "category": "Compliance", |
| "rfcReference": "RFC 9110 §9.1", |
| "rfcLevel": "Should", |
| "expected": "501/405/400 or close", |
| "verdict": "Fail", |
| "statusCode": 404, |
| "rawRequest": "FOOBAR / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 404 Not Found\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 9\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nNot Found" |
| }, |
| { |
| "id": "COMP-UNKNOWN-TE-501", |
| "description": "Unknown Transfer-Encoding without CL should be rejected with 501", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.1", |
| "rfcLevel": "Must", |
| "expected": "400/501 or close", |
| "verdict": "Pass", |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nTransfer-Encoding: gzip\r\n\r\n" |
| }, |
| { |
| "id": "COMP-VERSION-CASE", |
| "description": "HTTP version is case-sensitive — lowercase 'http' must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / http/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-VERSION-LEADING-ZEROS", |
| "description": "HTTP/01.01 — leading zeros in version digits are invalid", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/01.01\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-VERSION-MISSING-MINOR", |
| "description": "HTTP/1 with no minor version digit is invalid", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-VERSION-WHITESPACE", |
| "description": "HTTP/ 1.1 — whitespace inside version token is invalid", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/ 1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "COMP-WHITESPACE-BEFORE-HEADERS", |
| "description": "Whitespace before first header line must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.2", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "GET / HTTP/1.1\r\n \r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "RFC9110-5.4-DUPLICATE-HOST", |
| "description": "Duplicate Host headers with different values must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nHost: other.example.com\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "RFC9110-5.6.2-SP-BEFORE-COLON", |
| "description": "Whitespace between header name and colon must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §5", |
| "rfcLevel": "Must", |
| "expected": "400", |
| "verdict": "Fail", |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nX-Test : value\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-2.2-BARE-LF-HEADER", |
| "description": "Bare LF in header should be rejected, but MAY be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.2", |
| "rfcLevel": "May", |
| "expected": "400 or close (pass), 2xx (warn)", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\nX-Test: value\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "RFC9112-2.2-BARE-LF-REQUEST-LINE", |
| "description": "Bare LF in request line should be rejected, but MAY be accepted", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.2", |
| "rfcLevel": "May", |
| "expected": "400 or close (pass), 2xx (warn)", |
| "verdict": "Warn", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "RFC9112-2.3-HTTP09-REQUEST", |
| "description": "HTTP/0.9 request (no version) must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Should", |
| "expected": "400/close/timeout", |
| "verdict": "Pass", |
| "rawRequest": "GET /\r\n" |
| }, |
| { |
| "id": "RFC9112-2.3-INVALID-VERSION", |
| "description": "Invalid HTTP version must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.3", |
| "rfcLevel": "Should", |
| "expected": "400/505, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/9.9\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-3-CR-ONLY-LINE-ENDING", |
| "description": "CR without LF as line ending must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §2.2", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/1.1\rHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-3-MISSING-TARGET", |
| "description": "Request line with no target (space but no path) must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3", |
| "rfcLevel": "Must", |
| "expected": "400, close, or timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-3-MULTI-SP-REQUEST-LINE", |
| "description": "Multiple spaces between request-line components — SHOULD reject but MAY parse leniently", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3", |
| "rfcLevel": "Should", |
| "expected": "400 or 2xx; close/timeout = warn", |
| "verdict": "Warn", |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-3.2-FRAGMENT-IN-TARGET", |
| "description": "Fragment (#) in request-target — not part of origin-form grammar", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Should", |
| "expected": "400 or 2xx; 404 = warn", |
| "verdict": "Pass", |
| "statusCode": 400, |
| "rawRequest": "GET /path#frag HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n\r\n", |
| "rawResponse": "HTTP/1.1 400 Bad Request\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 11\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nBad Request" |
| }, |
| { |
| "id": "RFC9112-5-EMPTY-HEADER-NAME", |
| "description": "Empty header name (leading colon) must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §5", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\n: empty-name\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-5-HEADER-NO-COLON", |
| "description": "Header line without colon must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §5", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nNoColonHere\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-5-INVALID-HEADER-NAME", |
| "description": "Header name with invalid characters (brackets) must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §5", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nBad[Name: value\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-5.1-OBS-FOLD", |
| "description": "Obs-fold (line folding) in headers should be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §5.1", |
| "rfcLevel": "Must", |
| "expected": "400", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nX-Test: value\r\n continued\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| }, |
| { |
| "id": "RFC9112-6.1-CL-NON-NUMERIC", |
| "description": "Non-numeric Content-Length must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.1", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: abc\r\n\r\n" |
| }, |
| { |
| "id": "RFC9112-6.1-CL-PLUS-SIGN", |
| "description": "Content-Length with plus sign must be rejected", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §6.1", |
| "rfcLevel": "Must", |
| "expected": "400 or close", |
| "verdict": "Pass", |
| "rawRequest": "POST / HTTP/1.1\r\nHost: 127.0.0.1:<PORT>\r\nContent-Length: +5\r\n\r\nhello" |
| }, |
| { |
| "id": "RFC9112-7.1-MISSING-HOST", |
| "description": "Request without Host header must be rejected with 400", |
| "category": "Compliance", |
| "rfcReference": "RFC 9112 §3.2", |
| "rfcLevel": "Must", |
| "expected": "400", |
| "verdict": "Fail", |
| "statusCode": 200, |
| "rawRequest": "GET / HTTP/1.1\r\n\r\n", |
| "rawResponse": "HTTP/1.1 200 OK\r\nx-powered-by: Dart with package:shelf\r\ndate: <DATE>\r\ncontent-length: 2\r\nx-frame-options: SAMEORIGIN\r\ncontent-type: text/plain; charset=utf-8\r\nx-xss-protection: 1; mode=block\r\nx-content-type-options: nosniff\r\n\r\nOK" |
| } |
| ] |