Repository navigation
Re-pin the threat model to b1de40b, absorb what merged, and trim it to what a triager needs - #1295
Conversation
7b4bc71 to
956c984
Compare
…es to the PR Motivation: The document had grown to ten thousand words, and the growth was not in its claims but in narrative about how each claim came to be stated: four "how P-n came to be stated" essays under the §8 table, §14 answers that re-argued the reasoning the owning section had already absorbed, §9 bullets that explained each fixed defect's mechanism, a Confidence paragraph recounting which questions turned out to be corrections, and the same fixes listed in three or four places each. Its readers are triagers and the report-generating tools that cite it; they need the properties, the limits, the dispositions and the non-findings, and they need them fast. The review history is real but belongs in the pull request, not in the artefact reporters read. Modification: Cut to 6,500 words with no claim, ruling, disposition, table row or default removed. The four §8 notes become one verification paragraph naming each defect and its PR in a clause; §14 is retitled "Maintainer rulings" and each answer reduced to its ruling, with the reasoning kept only where it is the ruling (Q3's three tiers); §9's essays are cut to the boundary they draw plus one PR reference per fixed case; the Confidence paragraph is replaced by one sentence; the §5b.4 tally, the §12 near-miss narrative and the §5 shutdown-hook caveat are each reduced to a sentence; line-number citations are dropped in favour of file or symbol names, since the pin fixes the commit and the numbers rot on every merge. Every PR a claim depends on is still referenced, once. Result: Same model, 36% shorter, with §14 no longer presenting settled rulings as open questions. Tests: Not run - docs only References: Refs apache#1295
956c984 to
891a532
Compare
…es to the PR Motivation: The document had grown to ten thousand words, and the growth was not in its claims but in narrative about how each claim came to be stated: four "how P-n came to be stated" essays under the §8 table, §14 answers that re-argued the reasoning the owning section had already absorbed, §9 bullets that explained each fixed defect's mechanism, a Confidence paragraph recounting which questions turned out to be corrections, and the same fixes listed in three or four places each. Its readers are triagers and the report-generating tools that cite it; they need the properties, the limits, the dispositions and the non-findings, and they need them fast. The review history is real but belongs in the pull request, not in the artefact reporters read. Modification: Cut to 6,500 words with no claim, ruling, disposition, table row or default removed. The four §8 notes become one verification paragraph naming each defect and its PR in a clause; §14 is retitled "Maintainer rulings" and each answer reduced to its ruling, with the reasoning kept only where it is the ruling (Q3's three tiers); §9's essays are cut to the boundary they draw plus one PR reference per fixed case; the Confidence paragraph is replaced by one sentence; the §5b.4 tally, the §12 near-miss narrative and the §5 shutdown-hook caveat are each reduced to a sentence; line-number citations are dropped in favour of file or symbol names, since the pin fixes the commit and the numbers rot on every merge. Every PR a claim depends on is still referenced, once. Result: Same model, 36% shorter, with §14 no longer presenting settled rulings as open questions. Tests: Not run - docs only References: Refs apache#1295
|
If someone has time to review this, it would be useful to keep the security model up to date as we do get a regular stream of reports from security reporters, especially now that AI makes this much easier. |
|
Taking a look at it this weekend |
There was a problem hiding this comment.
🟡 Changes recommended
Several security properties overstate implementation guarantees or cite incomplete evidence.
6 open findings
Avoid attributing all parse failures to the request target · New NUL filtering promise exceeds chunk extension implementation · New Scope request-timeout promise to fully received entities · New Narrow stream isolation claim to model-level parsing failures · New Cite the source chain proving raw target logging · New Scope P9 back-map to model-level field parsing failures · New
What changed in this PR
Re-pins and streamlines the security threat model while incorporating recently merged protections and maintainer rulings.
Changes:
- Updates the model to commit
478c58b. - Adds HTTP/2, multipart, timeout, and request-target security guidance.
- Condenses historical analysis into triage-focused rules.
| File | Description |
|---|---|
THREAT_MODEL.md |
Updates and shortens the project threat model. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Motivation: Nineteen commits landed on main after the model was pinned to `6740cbd`, ten of which touch a claim it makes. One changes a triage line: apache#1194 documents `idle-timeout` as a bidirectional inactivity timeout that a client sending bytes inside every window keeps alive by design, where §9 called such a connection an evasion and therefore in scope. Two others defend a property the model never stated: that a malformed request on one HTTP/2 stream is answered on that stream and leaves the connection, its HPACK state and its other streams alone (apache#1252, apache#1297). Checking the error path for apache#1246 also surfaced a shipped default the model never mentioned: `error-logging-verbosity = full` writes the failing request target into the log at warning level, independent of the client-facing `verbose-error-messages = off` that P3 rests on. Modification: Re-pin to `40b07a2`. Add P9, HTTP/2 stream isolation, cited to `RequestErrorFlow`, with a note recording the two paths around it that review found and closed: a header that failed to parse unwound out of the HPACK decoder before the dynamic table was updated, desynchronising every later HEADERS frame on the connection (apache#1252, merged through apache#1251); and a field the HTTP/1.1 header parser rejects was reported with an exception type nothing on the HTTP/2 side caught, failing the connection (apache#1297, which also closed the RFC 9113 8.2.1 gap of a CR LF value being accepted silently truncated). Restate P5 and the §9 slow-loris bullet around inactivity rather than evasion, citing the new `reference.conf` and `timeouts.md` wording, and record apache#1284's leak of one scheduled task per open request as the in-scope shape. Add apache#1257 to the §9 smuggling bullet beside apache#1267, and apache#1281 to the P1 note beside apache#1259 as the third discard path. Add `max-part-count` (apache#1266) to §5a and a multipart row to §6, which had no multipart input at all, noting apache#1279's header-state bleed between parts. Add `error-logging-verbosity` to §5a, a §9 false-friend entry explaining what `verbose-error-messages` does not govern, and §10.9 recommending `simple` where logs are shipped or alerted on. Add two §11 misuse patterns: building `Raw-Request-URI` from request input, and echoing `IllegalRequestContext.rawRequestTarget` unescaped. Turn §5b.4's two concrete examples into a running tally of the defects review has found of that shape. Extend §15 to match. Result: Every claim is verified against `40b07a2`. Provenance is 23 documented / 33 maintainer / 0 inferred. The P1 frame-size gap stays open; apache#1264 has not merged. Tests: Not run - docs only References: Refs apache#1194, apache#1246, apache#1251, apache#1252, apache#1257, apache#1266, apache#1279, apache#1280, apache#1281, apache#1284, apache#1297
…es to the PR Motivation: The document had grown to ten thousand words, and the growth was not in its claims but in narrative about how each claim came to be stated: four "how P-n came to be stated" essays under the §8 table, §14 answers that re-argued the reasoning the owning section had already absorbed, §9 bullets that explained each fixed defect's mechanism, a Confidence paragraph recounting which questions turned out to be corrections, and the same fixes listed in three or four places each. Its readers are triagers and the report-generating tools that cite it; they need the properties, the limits, the dispositions and the non-findings, and they need them fast. The review history is real but belongs in the pull request, not in the artefact reporters read. Modification: Cut to 6,500 words with no claim, ruling, disposition, table row or default removed. The four §8 notes become one verification paragraph naming each defect and its PR in a clause; §14 is retitled "Maintainer rulings" and each answer reduced to its ruling, with the reasoning kept only where it is the ruling (Q3's three tiers); §9's essays are cut to the boundary they draw plus one PR reference per fixed case; the Confidence paragraph is replaced by one sentence; the §5b.4 tally, the §12 near-miss narrative and the §5 shutdown-hook caveat are each reduced to a sentence; line-number citations are dropped in favour of file or symbol names, since the pin fixes the commit and the numbers rot on every merge. Every PR a claim depends on is still referenced, once. Result: Same model, 36% shorter, with §14 no longer presenting settled rulings as open questions. Tests: Not run - docs only References: Refs apache#1295
Motivation: Twenty commits landed on main after the model was pinned to `40b07a2`. Two touch a claim it makes. apache#1264 closes the one P1 gap the model recorded as open: the HTTP/2 frame parser now rejects a frame over `max-frame-size` (512kB) on its frame header, before buffering the payload. apache#1296 validates the two application-supplied parts of the request line at construction, which changes what the §11 `Raw-Request-URI` misuse can reach. The rest do not move a claim: apache#1316 adds `max-connection-age`, default `infinite`; apache#1301 encodes HPACK literals as ISO-8859-1, which still substitutes '?' above 0xFF, and the P2 guard checks the rendered bytes; apache#1305, apache#1298, apache#1318 and the dependency updates are not security-relevant. Modification: Re-pin to `478c58b`. Add `max-frame-size` to §5a, §6 and the §15 back-map, add apache#1264 to the P1 verification paragraph, and drop the "one P1 gap is open" paragraph. Restate the §11 `Raw-Request-URI` misuse: injection is now rejected at construction, what remains is client bytes choosing an unnormalized target, and a value that passes construction yet corrupts the request line is `VALID` under §5b.4. Add the matching §15 row. Result: The model is verified against `478c58b` and records no open P1 gap. Tests: Not run - docs only References: Refs apache#1264, apache#1296
Motivation: Review of apache#1295 checked five claims against the code and found each broader than the implementation. P9 promised a per-stream 400 for any malformed HTTP/2 request, but pseudo-header and connection-specific header violations go through RequestParsing.protocolError, which throws Http2ProtocolException and closes the connection with GOAWAY. P2 promised NUL filtering on chunk extensions, but RenderSupport.renderChunk drops only extensions containing CR or LF. P5 said request-timeout starts once a request is received; RequestTimeoutSupport schedules it only after the entity completes. §6 described rawRequestTarget as the bytes that failed to parse, but it is populated whenever the target was read, including when the failure is a later header. The §9 log-hygiene claim cited ErrorInfo.scala, which does not establish the logging chain. Modification: Narrow P9 and its §15 row to model-level field parsing failures and name the GOAWAY class. Restrict P2's NUL claim to headers and trailers. Scope P5's request-timeout to a fully received entity. Reword the §6 rawRequestTarget row. Cite UriParser.fail, DefaultParsingErrorHandler and logParsingError for the error-logging-verbosity claim in §9 and §15. Result: Every changed claim matches the code at the pinned commit. Tests: Not run - docs only References: Refs apache#1295
891a532 to
3c1f52d
Compare
Motivation: Narrowing P9 left a gap in triage: the model now says that a missing, duplicate, misplaced or unknown pseudo-header, or a connection-specific header, closes the connection with GOAWAY rather than the stream error RFC 9113 8.1.1 asks for, but gives a report of that no disposition. Modification: Add §14 Q11: only the violating peer's connection is lost, a multiplexing proxy owns producing conformant pseudo-headers, and a report is BY-DESIGN: property-disclaimed; moving to RST_STREAM is a compliance improvement for an ordinary issue. Add the matching §11a non-finding and point P9 at the ruling. Result: A report of this shape routes to a disposition instead of MODEL-GAP. Tests: Not run - docs only References: Refs apache#1295
Motivation: Narrowing P9 left a gap in triage: the model now says that a missing, duplicate, misplaced or unknown pseudo-header, or a connection-specific header, closes the connection with GOAWAY rather than the stream error RFC 9113 8.1.1 asks for, but gives a report of that no disposition. Modification: Add §14 Q11: only the violating peer's connection is lost, a multiplexing proxy owns producing conformant pseudo-headers, and a report is BY-DESIGN: property-disclaimed; moving to RST_STREAM is a compliance improvement for an ordinary issue. Add the matching §11a non-finding and point P9 at the ruling. Result: A report of this shape routes to a disposition instead of MODEL-GAP. Tests: Not run - docs only References: Refs apache#1295
4e53b6d to
9965270
Compare
Motivation: Narrowing P9 left a gap in triage: the model now says that a missing, duplicate, misplaced or unknown pseudo-header, or a connection-specific header, closes the connection with GOAWAY rather than the stream error RFC 9113 8.1.1 asks for, but gives a report of that no disposition. Modification: Add §14 Q11: only the violating peer's connection is lost, a multiplexing proxy owns producing conformant pseudo-headers, and a report is BY-DESIGN: property-disclaimed; moving to RST_STREAM is a compliance improvement for an ordinary issue. Add the matching §11a non-finding and point P9 at the ruling. Result: A report of this shape routes to a disposition instead of MODEL-GAP. Tests: Not run - docs only References: Refs apache#1295
9965270 to
4793f56
Compare
Motivation: Nine commits landed on main after the model was pinned to `478c58b`. None moves a claim. apache#1320 and apache#1323 add the client-only `persistent-connection-max-age`, default `infinite`, and reword a comment on the server's `max-connection-age` drain, a setting the model does not cite. The `Http2Blueprint` change only adds parentheses; the stage order is unchanged. The rest are syntax cleanups, release notes, build and dependency updates. Modification: Re-pin to `b1de40b`. Result: The model, including the claims narrowed in review, is verified against `b1de40b`. Tests: Not run - docs only References: Refs apache#1295, apache#1320, apache#1323
samueleresca
left a comment
There was a problem hiding this comment.
Looks good overall. I left few nits.
| | P5 | **Connection and request lifetimes are bounded** — `idle-timeout 60s` closes a connection with no traffic in *either* direction for that long; `request-timeout 20s` bounds handling once a request's entity has been fully received (a body still arriving is not covered; §9); `max-connections 1024`; `pipelining-limit 1`. `idle-timeout` is an inactivity timeout, not a receive deadline (§9) | A connection with no traffic outliving `idle-timeout`; a fully received request outliving `request-timeout` with no response; resources held past either bound after the connection is gone | High | *(documented — `reference.conf`, `timeouts.md`)* | | ||
| | P6 | **HTTP/2 concurrency and stream churn are bounded** — `max-concurrent-streams = 256` caps open streams, and `frame-type-throttle` rate-limits `RST_STREAM` (100/s, burst 100) so a peer cannot cycle through that cap for free (Rapid Reset, CVE-2023-44487) | Unbounded stream/state growth on one connection, or unbounded churn through a bounded concurrency limit | High | *(documented — `reference.conf`)* | | ||
| | P7 | **HTTP/2 header blocks are bounded** — `max-header-list-size = 64 KiB` caps the decompressed header list *and* the accumulated HEADERS + CONTINUATION fragments; over-limit blocks get `GOAWAY(ENHANCE_YOUR_CALM)` rather than being buffered | Unbounded buffering from a CONTINUATION flood or an oversized header list | **Critical** | *(documented — `reference.conf`)* | | ||
| | P8 | **Credential comparison is constant-time** where the verifier calls `Credentials.verify` — `secure_==` XOR-accumulates over the full length after a length check | Secret recoverable byte-by-byte from response timing against a `verify`-based verifier | High | *(documented — `SecurityDirectives.scala`, `EnhancedByteArray.scala`)* | |
There was a problem hiding this comment.
small note here from AI scan: " The length leak is documented in the code but absent from P8."
It is referring to: https://github.com/apache/pekko-http/blame/main/http-core/src/main/scala/org/apache/pekko/http/impl/util/EnhancedByteArray.scala#L31-L35
Theoretically, an attacker could infer the length of a stored secret as the length checks early exit. In practice, I'm not sure how this would be feasible. Specially considering a Pekko HTTP service behind a proxy, it's a long shot.
So I'm pointing this out, but I'm also happy to leave as-is.
There was a problem hiding this comment.
Good catch. The leak is real and documented on secure_==: a length mismatch returns before the XOR loop. I agree it is hard to exploit, but the model should say so rather than leave it implied. In b0ca9dc, P8 now states that whether the presented secret matches the stored secret's length is not hidden, and that verify(secret, hasher) over fixed-length digests removes even that. §14 Q5 adds that a length-only report describes documented behaviour, not a P8 violation, so a report of this shape has a disposition.
Co-authored-by: Samuele Resca <samuele.resca@gmail.com>
Motivation: Review of apache#1295 noted that `secure_==` returns early on a length mismatch, which its own scaladoc documents as leaking whether the two secrets have the same length, while P8 mentioned the length check without its consequence. The applied Client IP suggestion also left trailing whitespace and a missing space before a citation. Modification: State the length disclosure in P8, noting that `verify(secret, hasher)` over fixed-length digests removes it. Add to §14 Q5 that a length-only report describes documented behaviour, not a P8 violation. Tidy the Client IP bullet. Result: P8 matches `EnhancedByteArray.secure_==` as documented, and a length-disclosure report has a disposition. Tests: Not run - docs only References: Refs apache#1295
Motivation: renderChunk writes a chunk extension raw into the chunk-size line and drops it if it contains CR or LF, but not NUL. Since apache#1260 the header and trailer renderers drop all three via Rendering.isIllegalHeaderChar, so a NUL in an application-supplied chunk extension was the one place it still reached the wire. Review of the threat model in apache#1295 found the gap: P2 had to be narrowed to exclude chunk extensions from its NUL claim. Modification: Check the extension with Rendering.isIllegalHeaderChar, the predicate the header and trailer paths use. Add a ResponseRendererSpec case beside the existing CRLF one. Result: A chunk extension containing CR, LF or NUL is dropped on both the server response and client request paths, which share renderChunk. Tests: - sbt "http-core / Test / testOnly ...ResponseRendererSpec -- -z NUL": failed before the fix (rendered "7;ok\0bad") - sbt "http-core / Test / testOnly ...ResponseRendererSpec ...RequestRendererSpec": 63 passed - scalafmt --mode diff-ref=upstream/main: no changes References: Refs apache#1256, apache#1260, apache#1295

Motivation
Two things, plus a re-pin to current
main, in separate commits so they can be reviewed separately; commits 4, 5 and 7 answer review, and commit 6 re-pins to the currentmain.Content. Nineteen commits landed on
mainafter the model was pinned to6740cbd, ten of which touch a claim it makes. One changes a triage line: #1194 documentsidle-timeoutas a bidirectional inactivity timeout that a client sending bytes inside every window keeps alive by design, where §9 called such a connection an "evasion" and therefore in scope. Two others defend a property the model never stated: that a malformed request on one HTTP/2 stream is answered on that stream and leaves the connection alone (#1252, #1297).Length. The document had grown to ~10,000 words, and the growth was narrative about how each claim came to be stated rather than the claims themselves. Its readers are triagers and the report-generating tools that cite it; they need the properties, limits, dispositions and non-findings, fast. The review history is real but belongs here, in the PR — which is where this description puts it.
Modification
Commit 1 — re-pin to
40b07a2and absorb what merged:400on its own stream and the connection carries on (RequestErrorFlow, since #59). Two paths around it were found and closed during review — #1252 and #1297, belowreference.confandtimeouts.md(#1194). A trickling client isBY-DESIGN: property-disclaimedwith a documented citation; the in-scope shape is a connection with no traffic outliving the timeout, or a bound whose cleanup leaks (#1284)max-part-count = 10000,error-logging-verbosity = fullIllegalRequestContext.rawRequestTargetrowverbose-error-messages = offgoverns the client, not the log — what reaches the log iserror-logging-verbosityerror-logging-verbosity = simplewhere logs are shipped or alerted onRaw-Request-URIfrom request input (#1280 extended it to HTTP/2:path); echoingrawRequestTargetunescaped from a customParsingErrorHandlerCommit 2 — trim to 6,500 words, no claim removed:
Commit 3 — re-pin to
478c58band absorb what merged since40b07a2:Twenty commits landed after the
40b07a2pin; two move a claim.max-frame-size = 512kB(#1264): an HTTP/2 frame over the limit is rejected on its frame header, before the payload is buffered; a limit on what is accepted, not advertised asSETTINGS_MAX_FRAME_SIZERaw-Request-URImisuse restated (#1296): the header must be visible ASCII and a customHttpMethoda token, checked at construction, so building either from request input no longer reaches request-line injection. What remains is client bytes choosing an unnormalized target; a value that passes construction yet corrupts the request line isVALIDunder §5b.4No change from the rest: #1316's
max-connection-agedefaults toinfinite; #1301 encodes HPACK literals as ISO-8859-1, which still substitutes?above 0xFF, and the P2 guard checks the rendered bytes; #1305, #1298, #1318 and the dependency updates are not security-relevant.Commit 4 — narrow claims the code does not back (from review):
RequestParsing.protocolError→Http2ProtocolException→GOAWAY, and are named as not coveredRenderSupport.renderChunkdrops chunk extensions on CR or LF, not NULrequest-timeoutstarts once the entity is fully received (requestEnd), not when the request is deliveredrawRequestTargetis the target as received, present whenever it was read; the failure may be a later header or the entityUriParser.fail→DefaultParsingErrorHandler→logParsingErrorinstead ofErrorInfo.scalaCommit 5 — §14 Q11 ruling: an HTTP/2 framing violation closing the violating peer's own connection with
GOAWAYrather thanRST_STREAM(RFC 9113 §8.1.1) isBY-DESIGN: property-disclaimed; moving toRST_STREAMis a compliance improvement, not a security fix. Matching §11a non-finding.Commit 6 — re-pin to
b1de40b: nine commits landed after478c58band none moves a claim. #1320 and #1323 add the client-onlypersistent-connection-max-age(defaultinfinite) and reword a comment on the server'smax-connection-agedrain, which the model does not cite; theHttp2Blueprintchange only adds parentheses and keeps the stage order; the rest are syntax cleanups, release notes, build and dependency updates.Commit 7 — P8 states the length disclosure (from review):
secure_==returns early on a length mismatch, as its scaladoc documents, soverifydoes not hide whether the presented secret has the stored secret's length;verify(secret, hasher)over fixed-length digests removes even that. §14 Q5 adds that a length-only report is documented behaviour, not a P8 violation. The applied Client IP suggestion is tidied in the same commit.#1344 makes
renderChunkdrop NUL in chunk extensions; once it merges, P2 can claim NUL for chunk extensions again.The review history this document no longer carries
For the record — the model's claims were checked against the code as it was written, and the check kept finding defects. All are fixed at or before the pinned commit unless noted.
P1 — inbound messages are bounded. Asserted from the §5a table, then checked:
max-chunk-counthad noelsebranch, sofailEntityStream's result was discarded and the parser trampolined into the next chunk — the limit emitted an error per remaining chunk instead of stopping (Stop parsing chunks once the chunk count limit is reached #1220).max-header-countwas not incremented on the branch merging repeatedConnectionheaders, so that header could be repeated without limit (fix: count repeated Connection headers towards max-header-count #1255).totalBufferedDatawas never decremented when a buffered stream was discarded onRST_STREAM, downstream cancellation, or a stream-levelFLOW_CONTROL_ERROR; once the leak passed half ofincoming-connection-level-buffer-sizethe connection window drained to zero and every stream stalled (fix: release connection-level flow control accounting for discarded buffered data #1259, fix: release buffered data accounting when an incoming HTTP/2 stream is shut down #1281).max-content-lengthpacked with minimal parts ran to well over a hundred thousand of them (feat: bound the number of parts in a multipart entity #1266).Http2FrameParsingtook a frame of any declared length, up to 16 MiB − 1, before any §5a bound applied (feat: bound the size of an incoming HTTP/2 frame #1264, which addsmax-frame-size). This was open at40b07a2and is closed at478c58b.P2 — response splitting is blocked. Asserted from the two
illegal-response-header-*-processing-modedefaults, then checked: the guard lived in one~~(HttpHeader)overload, so chunked-response trailers and chunk extensions rendered around it (#1256); it tested CR and LF but not NUL (#1260); the HTTP/2 header path had no equivalent check, so a mitigation relied on under HTTP/1.1 disappeared on upgrade (#1258). The rule now lives in one predicate,Rendering.isIllegalHeaderChar.P9 — HTTP/2 stream isolation. A header that failed to parse unwound out of the HPACK decoder before the entry reached the dynamic table and before the rest of the block was read, desynchronising the table from the peer's encoder for every later
HEADERSframe on the connection; an unknown method was enough (#1252, merged through #1251). A field the HTTP/1.1 header parser rejects — NUL in a value, an illegal character in a name, a value overmax-header-value-length— was reported with that parser's internal exception type, which nothing on the HTTP/2 side caught, failing the connection instead of the stream; the same change closed the RFC 9113 §8.2.1 gap of a CR LF value being accepted silently truncated (#1297).Q3 — path traversal. The draft asserted containment held.
checkIsSafeDescendantcompared canonical paths as strings, so a symlink resolving into/var/www-privatepassed for a root of/var/www(#1218). Reviewing the other directives found three tiers, not one:safeDirectoryChildPath(segment filter + containment) for the directory-serving directives;safeJoinPathsonly forgetFromResourceDirectory; nothing forgetFromFileandgetFromResource.Q4, Q5, Q9 — corrections to the draft's facts. Q4: the draft said Pekko HTTP does not parse forwarding headers;
extractClientIPdoes, and the point is that it never does so implicitly. Q5: the draft had constant-time comparison backwards;Credentials.verifydoes usesecure_==. Q9: the draft said Pekko HTTP writes no files; the upload directives do. The dispositions survived in all three; the stated basis did not.CORS enforcement.
validateOriginsaccepted a request if any origin matched while the response echoed every origin it was given (#1262). The document now distinguishes the permissive default (BY-DESIGN) from a restrictive setting that fails to restrict (VALID).#1217 near-miss. Originally registered a JVM shutdown hook from Pekko HTTP itself, which would have falsified §5's "registers no shutdown hook"; reworked to a
CoordinatedShutdowntask before merging.#1246 and the error path. Checking whether the default
ParsingErrorHandlerlogs the newrawRequestTarget(it does not) surfaced thaterror-logging-verbosity = fullhas always written the failing request target into the log at warning level viaUriParser.fail's error line — pre-existing, undocumented in the model, and the reason §5a, §9 and §10.9 gained their entries. #1294 escapes control characters in that line; once it lands the §9 false-friend narrows from "injection and flooding" to flooding.Result
Every claim is verified against
b1de40b, the currentmain. Provenance is 23 documented / 33 maintainer / 0 inferred at the content commit; the trim removes tags only on sentences that go with them, and commit 3 adds one documented tag (§11Raw-Request-URI) commit 5 one maintainer tag (§14 Q11), and commit 7 one documented tag (§14 Q5).#1264 has landed and is absorbed in commit 3. #1294 is not waited on: once it lands, the §9
verbose-error-messagesfalse friend narrows from log injection and flooding to flooding alone, a one-line follow-up.Tests
Not run - docs only
References
Refs #1194, #1246, #1251, #1252, #1257, #1264, #1266, #1279, #1280, #1281, #1284, #1296, #1297