This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-http.git


The following commit(s) were added to refs/heads/main by this push:
     new 5954bdf94 Re-pin the threat model to b1de40b, absorb what merged, and 
trim it to what a triager needs (#1295)
5954bdf94 is described below

commit 5954bdf94fa2fc0e1495126ed8f68a39041cea09
Author: PJ Fanning <[email protected]>
AuthorDate: Sat Oct 10 15:39:56 2026 +0100

    Re-pin the threat model to b1de40b, absorb what merged, and trim it to what 
a triager needs (#1295)
    
    * Re-pin the threat model to 40b07a2 and absorb what merged since 6740cbd
    
    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: #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 (#1252, #1297).
    
    Checking the error path for #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 (#1252, merged through
    #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 (#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 #1284's leak of
    one scheduled task per open request as the in-scope shape. Add #1257 to
    the §9 smuggling bullet beside #1267, and #1281 to the P1 note beside
    #1259 as the third discard path. Add `max-part-count` (#1266) to §5a and
    a multipart row to §6, which had no multipart input at all, noting
    #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; #1264 has
    not merged.
    
    Tests:
    Not run - docs only
    
    References:
    Refs #1194, #1246, #1251, #1252, #1257, #1266, #1279, #1280, #1281, #1284, 
#1297
    
    * Trim the threat model to what a triager needs; the review history moves 
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 #1295
    
    * Re-pin the threat model to 478c58b and absorb what merged since 40b07a2
    
    Motivation:
    Twenty commits landed on main after the model was pinned to `40b07a2`.
    Two touch a claim it makes. #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. #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: #1316 adds `max-connection-age`, default
    `infinite`; #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.
    
    Modification:
    Re-pin to `478c58b`. Add `max-frame-size` to §5a, §6 and the §15 back-map,
    add #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 #1264, #1296
    
    * Narrow threat-model claims the code does not back
    
    Motivation:
    Review of #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 #1295
    
    * Rule HTTP/2 framing violations that close the connection out of scope
    
    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 #1295
    
    * Re-pin the threat model to b1de40b
    
    Motivation:
    Nine commits landed on main after the model was pinned to `478c58b`.
    None moves a claim. #1320 and #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 #1295, #1320, #1323
    
    * Update THREAT_MODEL.md
    
    Co-authored-by: Samuele Resca <[email protected]>
    
    * State that verify does not hide the secret's length
    
    Motivation:
    Review of #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 #1295
    
    ---------
    
    Co-authored-by: Samuele Resca <[email protected]>
---
 THREAT_MODEL.md | 240 +++++++++++++++++++++++++++-----------------------------
 1 file changed, 117 insertions(+), 123 deletions(-)

diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
index 0e83901b8..8242f528c 100644
--- a/THREAT_MODEL.md
+++ b/THREAT_MODEL.md
@@ -1,23 +1,18 @@
 # Apache Pekko HTTP — Threat Model
 
-**Status:** Reviewed by a Pekko maintainer. **All ten questions in §14 are 
answered** and are settled model, and §5b records the project's standing 
position on configuration defaults. One item is referred rather than settled: 
whether the upstream-coordination sentence in `security.md` should be promoted 
into `SECURITY.md` (§14 Q10). No claim in this document is now uncited: every 
assertion is either cited to Pekko HTTP's own source and configuration, or 
stated by a maintainer.
+**Status:** Reviewed by a Pekko maintainer. Every question put to the 
maintainers (§14) is answered and is settled model; §5b records the project's 
standing position on configuration defaults. Every claim is either cited to 
Pekko HTTP's own source and configuration, or stated by a maintainer. One item 
is referred rather than settled (§14 Q10).
 
 | | |
 | --- | --- |
 | **Project** | Apache Pekko HTTP |
-| **Written against** | commit `6740cbd`, `main` |
-| **Date** | 2026-08-27 |
+| **Written against** | commit `b1de40b`, `main` |
+| **Date** | 2026-10-02 |
 | **Authors** | ASF Security team, at the request of the Pekko PMC |
 | **Version binding** | Versioned alongside the project. A report against 
version *N* is triaged against the model as it stood at *N*, not at `main`. |
 | **Reporting** | Findings that violate a §8 property should be reported per 
[`SECURITY.md`](SECURITY.md). Findings under §3 or §9 will be closed citing 
this document. |
 | **Companion model** | Pekko HTTP is built on Pekko Streams and Actors. The 
actor, remoting and cluster layers are modeled in [`apache/pekko`'s 
`THREAT_MODEL.md`](https://github.com/apache/pekko/blob/main/THREAT_MODEL.md); 
this document does not restate them. |
 
-**Provenance legend.**
-*(documented)* — stated in Pekko HTTP's own docs or `reference.conf` comments, 
cited.
-*(maintainer)* — stated by a Pekko maintainer in review of this document.
-*(inferred)* — reasoned from code or config defaults, **not yet confirmed**; 
each has a matching question in §14.
-
-**Confidence:** 20 documented / 32 maintainer / 0 inferred — counting inline 
tags only. The §5a limits table and the §15 back-map carry a further ~40 
documented facts under a single collective citation each, so the document is 
more evidence-backed than the bare ratio suggests. **Nothing in the document is 
inferred any more.** Q1-Q9 were answered by a maintainer in review; Q3, Q5 and 
Q9 were resolved against the source first, the last of these replacing §5's 
negative claims with a cited s [...]
+**Provenance legend.** *(documented)* — stated in Pekko HTTP's own docs or 
`reference.conf`, cited. *(maintainer)* — stated by a Pekko maintainer in 
review. Nothing in this document is inferred: every claim first drafted from 
code or configuration was either confirmed by a maintainer or corrected against 
the source, and several were corrected — the review history is in the pull 
requests this document links to.
 
 Apache Pekko HTTP is a Scala/Java toolkit for building HTTP-based services and 
clients on top of Pekko Streams. It provides a full HTTP/1.1 and HTTP/2 
implementation — parsing, connection management, marshalling, and a routing DSL 
of composable "directives" — as an **embeddable library**, not a standalone 
server. The application supplies the routes, the authentication, and the 
deployment.
 
@@ -30,7 +25,7 @@ Pekko HTTP is a library the application embeds. There is no 
Pekko HTTP daemon to
 Caller roles:
 
 - **The embedding application** — fully trusted. Defines routes, supplies 
handlers, chooses configuration.
-- **The operator/deployer** — trusted for the instance. Chooses what sits in 
front of the service (see §4), TLS termination, and limits.
+- **The operator/deployer** — trusted for the instance. Chooses what sits in 
front of the service (§4), TLS termination, and limits.
 - **The HTTP client** — **untrusted**. This is the adversary the model is 
mostly about.
 
 ### Component families
@@ -47,17 +42,17 @@ Caller roles:
 | Benchmarks | `http-bench-jmh` | — | **no** — §3 |
 | Lint / build / docs | `http-scalafix`, `docs`, `project`, `scripts`, `legal` 
| — | **no** — §3 |
 
-*(maintainer — the in/out split is confirmed by the Pekko maintainers; see §14 
Q6)*
+*(maintainer — §14 Q6)*
 
 ---
 
 ## §3 Out of scope (explicit non-goals)
 
 - **Test kits, benchmarks, scalafix rules, build tooling and documentation 
sources.** A finding in `http-bench-jmh` or any `*-tests` module is 
`OUT-OF-MODEL: unsupported-component`. *(maintainer — §14 Q6)*
-- **Pekko HTTP is not a WAF, and not an edge-hardened server.** The 
documentation says so plainly: applications *"should not be exposed to the 
public internet directly"* and an *"enterprise grade routing solution"* or a 
load balancer such as Apache HTTP Server or Nginx *"would be safer"* 
*(documented — `security.md`)*. See §4.
-- **Pekko HTTP is not an authentication or authorization system.** It ships 
`authenticateBasic`, `authenticateOAuth2` and `authorize` directives, but these 
are *plumbing*: the credential check is a function the application supplies. 
Pekko HTTP has no user store, no session model, and no policy engine. 
*(maintainer — §14 Q7)*
-- **The actor, stream, remoting and cluster layers** are out of scope here and 
covered by `apache/pekko`'s threat model.
-- **Attackers who already control the embedding process** are out of scope. 
*(maintainer — §14 Q7)*
+- **Pekko HTTP is not a WAF, and not an edge-hardened server.** Applications 
*"should not be exposed to the public internet directly"*; an *"enterprise 
grade routing solution"* or a load balancer such as Apache HTTP Server or Nginx 
*"would be safer"* *(documented — `security.md`)*. See §4.
+- **Pekko HTTP is not an authentication or authorization system.** 
`authenticateBasic`, `authenticateOAuth2` and `authorize` are plumbing: the 
credential check is a function the application supplies. There is no user 
store, session model or policy engine. *(maintainer — §14 Q7)*
+- **The actor, stream, remoting and cluster layers** are covered by 
`apache/pekko`'s threat model.
+- **Attackers who already control the embedding process.** *(maintainer — §14 
Q7)*
 
 ---
 
@@ -65,19 +60,19 @@ Caller roles:
 
 **The trust boundary is the inbound HTTP request.** Everything derived from 
wire bytes — request line, headers, cookies, entity, HTTP/2 frames — is 
attacker-controlled until the application validates it.
 
-Pekko HTTP's documented posture is unusual and important enough to quote in 
full:
+Pekko HTTP's documented posture:
 
 > "Pekko HTTP-based applications should not be exposed to the public internet 
 > directly. We believe Pekko HTTP behaves pretty well under most known Denial 
 > of Service attacks, but if you want the best security, you should use an 
 > enterprise grade routing solution. Even using a load balancing solution like 
 > an up-to-date version of Apache HTTP Server or Nginx would be safer than 
 > exposing Pekko HTTP-based applications directly to the public internet."
 > — *(documented — `security.md`, "Security model")*
 
-Read carefully, this makes a **graded** claim rather than a binary one: Pekko 
HTTP asserts it *"behaves pretty well under most known"* DoS attacks — not that 
it is DoS-proof, and not that DoS is out of scope. That hedge was the single 
most consequential ambiguity in this model for triage; §14 Q1 has now resolved 
it into the content-vs-volume line, which is the rule triage actually applies.
+This is a graded claim, not a disclaimer of DoS as a class. §14 Q1 resolves it 
into the content-vs-volume line that triage applies.
 
 ### Reachability preconditions per family
 
-- **Core protocol / HTTP/2** — reachable from raw wire bytes. The strongest 
in-model surface: a parser defect here needs no application cooperation.
-- **Routing DSL** — reachable from a request that the application's own routes 
expose. A finding must name the directive and show a route shape a reasonable 
application would write.
+- **Core protocol / HTTP/2** — reachable from raw wire bytes. A parser defect 
here needs no application cooperation.
+- **Routing DSL** — reachable from a request the application's own routes 
expose. A finding must name the directive and show a route shape a reasonable 
application would write.
 - **Marshalling** — reachable from an entity body **only where the application 
has bound that marshaller to a route**. Findings in the underlying JSON/XML 
library belong to that library.
-- **CORS** — reachable only where the application has installed the `cors()` 
directive. It is opt-in, not on by default. See §5a.
+- **CORS** — reachable only where the application has installed `cors()`. 
Opt-in, not on by default.
 - **Caching** — reachable only where the application has installed a caching 
directive; cache-key correctness is the sharp edge.
 
 ---
@@ -85,27 +80,27 @@ Read carefully, this makes a **graded** claim rather than a 
binary one: Pekko HT
 ## §5 Assumptions about the environment
 
 - **Runtime.** A conformant JVM. Pekko HTTP does not defend against a hostile 
JVM or in-process attacker. *(maintainer — §14 Q7)*
-- **Fronting infrastructure.** The documented expectation is that something 
sits in front in production *(documented — `security.md`)*. Per §14 Q1 this is 
load-bearing for *volume* only: the proxy is relied on for flood and slow-loris 
defence, not for bounding a single request, which is P1's job.
-- **TLS.** Pekko HTTP can terminate TLS itself (`HttpsConnectionContext`) and 
this is supported, though the documented recommendation to front the service 
(§4) means termination is commonly the proxy's job in production. Cipher and 
protocol selection come from the JSSE context the application supplies — Pekko 
HTTP pins nothing and overrides no JDK default, so cipher strength is a 
deployment property. *(maintainer — §14 Q8)*
-- **Client IP.** `remote-address-attribute` ships `off` *(documented — 
`reference.conf`)*. When on, the attribute reflects the **socket** peer, which 
behind a proxy is the proxy; forwarding headers never feed it. Header-derived 
client IP is opt-in at the call site: `extractClientIP` reads `X-Forwarded-For` 
/ `X-Real-Ip` and is therefore client-controllable, while 
`extractDirectClientIP` reads the attribute alone and is not. Choosing between 
them is the application's decision (§14 Q4). *( [...]
+- **Fronting infrastructure.** Something sits in front in production 
*(documented — `security.md`)*. Per §14 Q1 this is load-bearing for *volume* 
only: the proxy is relied on for flood and slow-loris defence, not for bounding 
a single request, which is P1's job.
+- **TLS.** Pekko HTTP can terminate TLS itself (`HttpsConnectionContext`); in 
a fronted deployment termination is commonly the proxy's job. Cipher and 
protocol selection come from the JSSE context the application supplies — Pekko 
HTTP pins nothing and overrides no JDK default. *(maintainer — §14 Q8)*
+- **Client IP.** `remote-address-attribute` ships `off` *(documented — 
`reference.conf`)*. When on, the attribute holds the address of whoever opened 
the TCP connection to Pekko HTTP. With a load balancer or reverse proxy in 
front, that is the proxy's address.
+  - `extractClientIP` reads `X-Forwarded-For` / `X-Real-Ip` and is 
**client-controllable**;
+  - `extractDirectClientIP` reads the attribute alone and is not (§14 Q4). 
*(documented — `MiscDirectives.scala`)*
 
 ### What Pekko HTTP does not do to its host
 
-Negative claims, rarely written down and therefore verified against the source 
rather than asserted. The scan below covers the main sources of `http-core`, 
`http`, `parsing`, `http-caching` and `http-cors` *(documented — source scan, 
§14 Q9)*:
+Verified by scanning the main sources of `http-core`, `http`, `parsing`, 
`http-caching` and `http-cors` *(documented — source scan, §14 Q9)*:
 
-- **Binds no port until the application asks for one.** Binding is reachable 
only through `Http().newServerAt(...)` (`Http.scala:156`) and the 
`ServerBuilder` it returns — `bind`, `bindSync`, `bindFlow`, 
`connectionSource`; the implementations behind them are `private[http]` 
(`Http.scala:179-292`). Nothing binds at class or extension initialization.
-- **Installs no signal handlers and spawns no child processes.** No 
`sun.misc.Signal`/`SignalHandler`, `ProcessBuilder` or `Runtime.exec` in the 
main sources.
-- **Registers no JVM shutdown hook.** There is no `addShutdownHook` anywhere 
in Pekko HTTP's main sources. Any hook an integrator observes comes from the 
actor system, not from here — see the caveat below.
-- **Touches the file system only through directives the application 
installs.** The disk-serving directives (`getFromFile`, `getFromDirectory`) 
read. The upload directives **write**: `storeUploadedFile(s)` streams entity 
bytes to a destination the application's function chooses, and `fileUploadAll` 
buffers each part into a `Files.createTempFile` temp file registered with 
`deleteOnExit` (since [#1217](https://github.com/apache/pekko-http/pull/1217) 
landed, those temp files go into one dir [...]
+- **Binds no port until the application asks for one.** Binding is reachable 
only through `Http().newServerAt(...)` and the `ServerBuilder` it returns; 
nothing binds at class or extension initialization.
+- **Installs no signal handlers and spawns no child processes.** No 
`sun.misc.Signal`, `ProcessBuilder` or `Runtime.exec` in the main sources.
+- **Registers no JVM shutdown hook.** No `addShutdownHook` in the main 
sources. The `ActorSystem` it runs on *does* register hooks — 
`CoordinatedShutdown`'s, and Artery's when remoting is enabled — so an 
integrator will observe hooks in the process; they belong to `apache/pekko`'s 
model. *"Pekko HTTP registers no shutdown hook"* is the claim; *"a Pekko HTTP 
process has no shutdown hook"* is false. *(maintainer — §14 Q9)*
+- **Touches the file system only through directives the application 
installs.** `getFromFile` / `getFromDirectory` read. `storeUploadedFile(s)` 
writes to a destination the application chooses; `fileUploadAll` buffers parts 
into temp files in one directory per actor system, removed by a 
`CoordinatedShutdown` task 
([#1217](https://github.com/apache/pekko-http/pull/1217)). Nothing writes at 
initialization or outside an installed directive; beyond `FileUploadDirectives` 
the main sources cont [...]
 - **Does not mutate process-global state at initialization** — no 
`System.setProperty`, `Security.setProperty`, `Security.addProvider` or 
`setDefault(...)`.
 
-**One inherited caveat, highlighted because it is easy to misattribute.** The 
claims above cover Pekko HTTP's own modules, not the `ActorSystem` it runs on. 
`apache/pekko` *does* register JVM shutdown hooks — one in 
`CoordinatedShutdown`, and a second in Artery when remoting is enabled — so an 
integrator running a Pekko HTTP service **will** observe shutdown hooks in the 
process. They arrive with the actor system and are modeled in the companion 
document. **Pekko HTTP itself adds none**, [...]
-
 ---
 
 ## §5a Configuration variants that change the security envelope
 
-Pekko HTTP's resistance to malformed and abusive input is almost entirely a 
function of `pekko.http.server.parsing.*`, with two entries under 
`pekko.http.routing.*` bounding what the parsing limits cannot see — the size 
of an entity *after* it has been decompressed. These are the **documented, 
shipped** limits *(all documented — 
`http-core/src/main/resources/reference.conf`, and 
`http/src/main/resources/reference.conf` for the two `routing` entries)*:
+Pekko HTTP's resistance to malformed and abusive input is a function of 
`pekko.http.server.parsing.*`, plus two `pekko.http.routing.*` entries bounding 
what the parsing limits cannot see — an entity's size *after* decompression. 
These are the **documented, shipped** limits *(all documented — `http-core` and 
`http` `reference.conf`)*:
 
 | Setting | Default | What it bounds |
 | --- | --- | --- |
@@ -118,30 +113,33 @@ Pekko HTTP's resistance to malformed and abusive input is 
almost entirely a func
 | `max-chunk-size` | `1m` | Single chunk |
 | `max-chunk-count` | `100000` | Chunks per message |
 | `max-chunk-ext-length` | `256` | Chunk extension |
+| `max-part-count` | `10000` | Parts in one multipart entity — the 
amplification `max-content-length` does not bound |
 | `max-comment-parsing-depth` | `5` | Nested comment recursion |
 | `max-to-strict-bytes` | `8m` | `toStrict` materialization |
 | `max-concurrent-streams` | `256` | HTTP/2 concurrent streams |
 | `max-header-list-size` | `64 KiB` | HTTP/2 decompressed header list, **and** 
the accumulated HEADERS + CONTINUATION fragments for one header block |
+| `max-frame-size` | `512kB` | HTTP/2 single incoming frame, checked on its 
frame header before the payload is buffered; a limit on what is accepted, not 
advertised as `SETTINGS_MAX_FRAME_SIZE` |
 | `incoming-connection-level-buffer-size` | `10 MB` | HTTP/2 incoming data 
buffered across one connection |
 | `incoming-stream-level-buffer-size` | `512kB` | HTTP/2 incoming data 
buffered for one stream |
 | `outgoing-control-frame-buffer-size` | `1024` | HTTP/2 outgoing control 
frames queued before the connection fails |
-| `frame-type-throttle` | `frame-types = ["reset"]`, `cost = 100`, `burst = 
100`, `interval = 1 s` | HTTP/2 frames charged against a rate limit — 
`RST_STREAM` by default, mitigating HTTP/2 Rapid Reset (CVE-2023-44487) — 
**secure default** |
+| `frame-type-throttle` | `frame-types = ["reset"]`, `cost = 100`, `burst = 
100`, `interval = 1 s` | HTTP/2 frames rate-limited — `RST_STREAM` by default, 
against Rapid Reset (CVE-2023-44487) — **secure default** |
 | `max-connections` | `1024` | Server connections |
 | `pipelining-limit` | `1` | In-flight pipelined requests |
-| `idle-timeout` | `60 s` | Connection idle |
+| `idle-timeout` | `60 s` | Connection inactivity, in either direction (§9) |
 | `request-timeout` | `20 s` | Per-request handling |
 | `uri-parsing-mode` | `strict` | URI leniency |
 | `cookie-parsing-mode` | `rfc6265` | Cookie leniency |
 | `verbose-error-messages` | `off` | Whether parse errors leak detail to the 
client — **secure default** |
+| `error-logging-verbosity` | `full` | How much of a rejected request reaches 
the *server log* — with `full`, the raw request target (§9) |
 | `illegal-response-header-name-processing-mode` | `error` | Blocks 
response-splitting via header names — **secure default** |
 | `illegal-response-header-value-processing-mode` | `error` | Blocks 
response-splitting via header values — **secure default** |
 | `server-header` | `pekko-http/${version}` | Advertises product and version |
 | `remote-address-attribute` | `off` | Exposes socket peer address to routes |
 | `transparent-head-requests` | `off` | HEAD handled as GET |
 | `routing.decode-max-bytes-per-chunk` | `1m` | Single `ByteString` a decoding 
directive emits |
-| `routing.decode-max-size` | `8m` | Entity size **after** decoding — see §9 |
+| `routing.decode-max-size` | `8m` | Entity size **after** decoding (§9) |
 
-**These limits are the model's quantitative spine.** A report that a request 
*within* every documented limit causes disproportionate resource use is 
`VALID`; one that simply exceeds a limit is P1 working, and one that needs a 
limit raised is `OUT-OF-MODEL: non-default-build` (§14 Q1).
+**These limits are the model's quantitative spine.** A request *within* every 
documented limit that causes disproportionate resource use is `VALID`; one that 
simply exceeds a limit is P1 working; one that needs a limit raised is 
`OUT-OF-MODEL: non-default-build` (§14 Q1).
 
 ### The CORS defaults — ruled (§5b, §14 Q2)
 
@@ -155,24 +153,22 @@ Pekko HTTP's resistance to malformed and abusive input is 
almost entirely a func
 | `allowed-methods` | `["GET", "POST", "HEAD", "OPTIONS"]` |
 | `allow-generic-http-requests` | `yes` |
 
-The module's own documentation states the interaction precisely: *"if 
parameter is `*` and credentials are not allowed, a `*` is set in 
`Access-Control-Allow-Origin`. Otherwise, the origins given in the `Origin` 
request header are echoed."* *(documented — `http-cors/reference.conf`)*
-
-So with **both** defaults in force, the directive **echoes the requesting 
`Origin` and allows credentials** — the maximally permissive CORS posture. Two 
facts bound how alarming that is: the `cors()` directive is **opt-in**, so this 
affects only applications that chose to enable CORS; and it is a documented, 
deliberate default rather than an accident. It is nonetheless the most 
permissive default in the project. Its disposition follows §5b: the shipped 
value is a compatibility choice, an [...]
+*"If parameter is `*` and credentials are not allowed, a `*` is set in 
`Access-Control-Allow-Origin`. Otherwise, the origins given in the `Origin` 
request header are echoed."* *(documented — `http-cors/reference.conf`)* With 
both defaults in force the directive **echoes the requesting `Origin` and 
allows credentials** — the most permissive default in the project, bounded by 
`cors()` being opt-in and the default being deliberate. Its disposition follows 
§5b: a request to change it is not  [...]
 
-**That ruling is about the default, never about the enforcement.** Where an 
operator has restricted `allowed-origins`, the directive must honour what they 
set, and a case where it did not was fixed at the pinned commit: 
`validateOrigins` accepted a request as soon as **one** origin in the `Origin` 
header matched, while the response echoes back **every** origin it was given, 
so a request pairing an allowed origin with a disallowed one had the disallowed 
one echoed in `Access-Control-Allow [...]
+That ruling covers the default, never the enforcement. Where an operator has 
restricted `allowed-origins`, the directive must honour it: a request pairing 
an allowed origin with a disallowed one used to pass while every origin was 
echoed back ([#1262](https://github.com/apache/pekko-http/pull/1262)) — `VALID` 
under §5b.4. *(maintainer)*
 
 ---
 
 ## §5b Security posture: hardening, not secure-by-default
 
-Pekko HTTP is a long-lived toolkit whose deployment base is inherited from 
Akka HTTP, and `http-cors` carries a second inheritance on top of that — its 
defaults arrived with the code donated by Lomig Mégard 
(`legal/CorsNotice.txt`). Its configuration defaults are chosen for 
compatibility with those deployments, in which operators have already been 
tasked with fronting the service (§4, §10.1) and choosing limits appropriate to 
their traffic. Changing a default to a more restrictive value  [...]
+Pekko HTTP's deployment base is inherited from Akka HTTP, and `http-cors`'s 
defaults arrived with the code donated by Lomig Mégard 
(`legal/CorsNotice.txt`). Defaults are chosen for compatibility with those 
deployments, whose operators already front the service (§4, §10.1) and choose 
limits for their traffic; tightening a default breaks them on upgrade, often as 
requests that used to work failing with a 4xx.
 
 Pekko HTTP therefore takes the following position *(maintainer)*:
 
-1. **Defaults are compatibility choices, not security claims.** §5a lists 
every setting whose default affects the security envelope; §10 lists what the 
operator must do as a result. Read together they are the contract: Pekko HTTP 
states what it does not provide, and states what it expects of the operator 
instead.
-2. **A report that a default should be more restrictive is not a vulnerability 
report.** It is a change request, and is closed as `BY-DESIGN: 
default-configuration` (§13). This covers the recurring ones: the CORS pair in 
§5a, `server-header` disclosing a version, and every limit someone considers 
too generous.
-3. **Proposals to change a default are welcome, and belong on the development 
list.** The PMC will weigh them in good faith on their merits — the 
compatibility cost, whether a migration path exists, and whether a major 
version is in flight. Defaults can and do change; they change through project 
discussion, not as the remediation of a security report.
-4. **If an implementation is wrong, Pekko HTTP fixes it.** Where a control 
does not do what it is documented to do once enabled, that is a defect, in 
scope, at the severity §8 assigns. **This is the sharp end of the model.** The 
§5a limits and the §8 properties are only worth what their implementations 
deliver: a `max-header-list-size` that fails to bound CONTINUATION 
accumulation, an `illegal-response-header-value-processing-mode = error` that 
lets a CRLF through, a `safeDirectoryChildP [...]
+1. **Defaults are compatibility choices, not security claims.** §5a lists 
every setting whose default affects the security envelope; §10 lists what the 
operator must do as a result. Together they are the contract.
+2. **A report that a default should be more restrictive is not a vulnerability 
report.** It is a change request, closed as `BY-DESIGN: default-configuration` 
(§13). This covers the CORS pair, `server-header`, and every limit someone 
considers too generous.
+3. **Proposals to change a default are welcome, and belong on the development 
list.** The PMC weighs compatibility cost, migration path, and whether a major 
version is in flight. Defaults change through project discussion, not as the 
remediation of a security report.
+4. **If an implementation is wrong, Pekko HTTP fixes it.** A control that does 
not do what it is documented to do once enabled is a defect, in scope, at the 
severity §8 assigns. **This is the sharp end of the model:** a 
`max-header-list-size` that fails to bound CONTINUATION accumulation, an 
`illegal-response-header-value-processing-mode = error` that lets a CRLF 
through, a `safeDirectoryChildPath` that can be walked out of — those are the 
findings this project wants, and the second and  [...]
 
 **Users are free to strengthen any §5a setting**, and §10 says which ones 
matter most. What the project will not do is change the shipped value on their 
behalf.
 
@@ -186,10 +182,12 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 | Any bound route | Headers, incl. `Host`, `Cookie` | **Yes** | Pekko HTTP: 
count/length limits. App: semantic trust |
 | Any bound route | `X-Forwarded-*`, `X-Real-Ip` | **Yes** — trivially 
spoofable | **App** — surfaced by `extractClientIP`, never validated; use 
`extractDirectClientIP` for access control or rate limiting (§14 Q4) |
 | Any bound route | Entity body (fixed, chunked, streamed) | **Yes** | Pekko 
HTTP: size/chunk limits. App: content validation |
-| HTTP/2 | Frames, HPACK table, stream IDs | **Yes** | Pekko HTTP: 
`max-concurrent-streams` |
+| Route with a multipart unmarshaller | Body parts — boundaries, per-part 
headers, part count | **Yes** | Pekko HTTP: `max-part-count`, header limits per 
part, each part carrying only its own headers 
([#1279](https://github.com/apache/pekko-http/pull/1279)). App: per-part 
validation |
+| Custom `ParsingErrorHandler` | `IllegalRequestContext.rawRequestTarget` | 
**Yes** — the client's request-target bytes, as received; present whenever the 
target was read, so the failure may lie elsewhere (a header, the entity) | 
**App** — escape before logging or echoing; documented at the class |
+| HTTP/2 | Frames, HPACK table, stream IDs | **Yes** | Pekko HTTP: 
`max-frame-size`, `max-concurrent-streams`, `frame-type-throttle`, P9 |
 | Route with marshaller | Entity parsed to a domain type | **Yes** | 
Underlying JSON/XML library + app |
 | `cors()` | `Origin`, `Access-Control-Request-*` | **Yes** | Operator: §5a 
CORS config |
-| File-serving directives | Path segments | **Yes** | Pekko HTTP + app — see 
§14 Q3 |
+| File-serving directives | Path segments | **Yes** | Pekko HTTP + app — see 
§9, §14 Q3 |
 | Client API | Response from an upstream server | **Yes** if the upstream is 
untrusted | App: treat responses as untrusted |
 | Config | `application.conf` | **No** — trusted deployment input | Operator |
 
@@ -199,15 +197,15 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 
 **In scope:**
 
-- **The remote HTTP client.** Can send arbitrary bytes, malformed framing, 
oversized or deeply-nested input, many concurrent connections, and abusive 
HTTP/2 frame sequences. The primary adversary — though per §14 Q1 what this 
adversary achieves through sheer *volume* is the proxy's problem, not the 
library's. *(maintainer — §14 Q7)*
+- **The remote HTTP client.** Arbitrary bytes, malformed framing, oversized or 
deeply-nested input, many concurrent connections, abusive HTTP/2 frame 
sequences. The primary adversary — though what it achieves through sheer 
*volume* is the proxy's problem (§14 Q1). *(maintainer — §14 Q7)*
 - **A malicious upstream server**, where the application uses the client API 
against an untrusted endpoint. *(maintainer — §14 Q7)*
 - **A cross-origin web attacker**, where the application enables CORS. 
*(maintainer — §14 Q2)*
 
 **Explicitly out of scope:**
 
-- **Attackers with code execution in the embedding JVM.** Already inside.
+- **Attackers with code execution in the embedding JVM.**
 - **The embedding application itself.** A route that deliberately leaks data 
is an application bug.
-- **Side-channel observers.** No general timing guarantees are made about 
routing or parsing. Credential comparison is the exception: 
`Credentials.Provided.verify` compares in constant time (see §8 P8), so a 
timing finding there is in scope, while one against an application-supplied 
`provideVerify` is not. *(documented — `SecurityDirectives.scala`, 
`EnhancedByteArray.scala`)*
+- **Side-channel observers.** No general timing guarantees are made about 
routing or parsing. Credential comparison is the exception (P8): a timing 
finding against `Credentials.verify` is in scope; one against an 
application-supplied `provideVerify` is not. *(documented — 
`SecurityDirectives.scala`, `EnhancedByteArray.scala`)*
 
 ---
 
@@ -215,49 +213,47 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 
 | # | Property & conditions | Violation symptom | Severity | Provenance |
 | --- | --- | --- | --- | --- |
-| P1 | **Inbound messages are bounded** by the §5a limits: reaching a limit 
stops the parse rather than merely reporting it, every header a message carries 
counts towards the count limits, and buffered bytes are released from the 
accounting when the buffer is discarded | OOM or unbounded buffering from input 
*within* documented limits; a limit reached without the parse stopping | 
**Critical** | *(documented — `reference.conf`, `HttpMessageParser.scala`, 
`Http2StreamHandling.scala`)* |
-| P2 | **Response splitting is blocked**: illegal response header names and 
values are `error` by default, and the renderers drop any header whose rendered 
bytes contain CR, LF or NUL — on the HTTP/1.1 main header block, on 
chunked-response trailers and chunk extensions, and on the HTTP/2 HPACK path 
alike | CRLF or NUL in an application-supplied header, trailer or chunk 
extension reaching the wire | **Critical** | *(documented — `reference.conf`, 
`Rendering.scala:270`)* |
+| P1 | **Inbound messages are bounded** by the §5a limits: reaching a limit 
stops the parse, every header a message carries counts towards the count 
limits, and buffered bytes are released from the accounting when the buffer is 
discarded | OOM or unbounded buffering from input *within* documented limits; a 
limit reached without the parse stopping | **Critical** | *(documented — 
`reference.conf`, `HttpMessageParser.scala`, `Http2FrameParsing.scala`, 
`Http2StreamHandling.scala`)* |
+| P2 | **Response splitting is blocked**: illegal response header names and 
values are `error` by default, and the renderers drop any header whose rendered 
bytes contain CR, LF or NUL — on the HTTP/1.1 header block, chunked-response 
trailers and the HTTP/2 HPACK path alike — and any chunk extension containing 
CR or LF | CR or LF in an application-supplied header, trailer or chunk 
extension, or NUL in a header or trailer, reaching the wire | **Critical** | 
*(documented — `reference.conf`, [...]
 | P3 | **Parse errors do not leak detail to the client** — 
`verbose-error-messages = off` | Internal parse state or stack detail in a 400 
response under defaults | High | *(documented — `reference.conf`)* |
 | P4 | **Strict URI and RFC6265 cookie parsing by default**, rather than 
lenient normalization that invites smuggling | Two components disagreeing on a 
URI or cookie under `strict` | High | *(documented — `reference.conf`)* |
-| P5 | **Connection and request lifetimes are bounded** — `idle-timeout 60s`, 
`request-timeout 20s`, `max-connections 1024`, `pipelining-limit 1` | A client 
holding resources indefinitely under defaults | High | *(documented — 
`reference.conf`)* |
-| P6 | **HTTP/2 concurrency and stream churn are bounded** — 
`max-concurrent-streams = 256` caps streams open at once, and 
`frame-type-throttle` rate-limits the frames that let a peer cycle through that 
cap: `RST_STREAM` is charged by default at 100 per second with a burst of 100. 
Concurrency alone is not a bound on *work*, because a reset frees the slot 
immediately — this is the HTTP/2 Rapid Reset shape (CVE-2023-44487) | Unbounded 
stream/state growth on one connection, or unbounded str [...]
-| P7 | **HTTP/2 header blocks are bounded** — `max-header-list-size = 64 KiB` 
caps the decompressed header list *and* the accumulated HEADERS + CONTINUATION 
fragments, so a header block the peer never terminates with `END_HEADERS` 
cannot grow without bound; 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` — it compares via `secure_==`, which XOR-accumulates over 
the full length after a length check, rather than short-circuiting on the first 
differing byte | Secret recoverable byte-by-byte from response timing against a 
`verify`-based verifier | High | *(documented — `SecurityDirectives.scala`, 
`EnhancedByteArray.scala:37`)* |
-
-**P1-P7 are default-on properties** — a notable contrast with `apache/pekko`, 
where the strongest controls must be switched on. P8 is the exception: it holds 
only for a verifier that calls `Credentials.verify`, which is why §10.4 states 
it as a downstream responsibility. The boundary of the DoS claim, once this 
model's largest ambiguity, is now fixed by the §14 Q1 content-vs-volume line.
-
-**A note on how P1 came to be stated this precisely.** P1 has the same history 
as P2, and for the same reason: the draft asserted it from the §5a table, which 
records what each limit is *supposed* to bound. Three of those bounds did not 
hold as written, all fixed at the pinned commit. `max-chunk-count` was tested 
without an `else` branch, so the `StateResult` of `failEntityStream` was 
discarded and the parser trampolined into the next chunk — reaching the limit 
emitted an error per remai [...]
+| 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 `requ [...]
+| 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. The length check returns early, so whether the presented secret 
has the stored secret's length is not hidden; `verify(secret, hasher)` 
comparing fixed-length digests removes even that | Secret recoverable 
byte-by-byte from response timing against a `verify`-based verifier | High | 
*(documented — `SecurityDirectives.scala` [...]
+| P9 | **HTTP/2 streams are isolated from each other's malformed request 
fields**: a request whose method, target, authority or a header field fails to 
parse into the model is answered with a `400` on its own stream, and the 
connection — its HPACK state and every other stream — carries on. A connection 
a proxy multiplexes for many clients is what this protects. Not covered: HTTP/2 
framing violations — a missing, duplicate, misplaced or unknown pseudo-header, 
or a connection-specific head [...]
 
-**One bound is missing at the pinned commit, and P1 is stated with that gap 
open.** The HTTP/2 frame parser reads a frame's 24-bit length field and then 
takes that many bytes with no upper limit (`Http2FrameParsing.scala:193-200`), 
so a peer can make it buffer up to 16 MiB - 1 for a single frame. That happens 
*before* HPACK decoding and before the entity is assembled, so none of the 
three HTTP/2 buffer bounds in §5a constrain it, and the cost is multiplied by 
the number of connections. A [...]
+**P1–P7 and P9 are default-on.** P8 holds only for a verifier that calls 
`Credentials.verify` (§10.4). The DoS boundary is the §14 Q1 content-vs-volume 
line.
 
-**A note on how P2 came to be stated this precisely.** The draft asserted P2 
from the two `reference.conf` processing-mode defaults alone, without walking 
the path those settings are supposed to govern. Walking it found the guard was 
not where the claim assumed: it lived in one `~~(HttpHeader)` overload, so 
chunked-response **trailers** and **chunk extensions** were rendered around it 
([#1256](https://github.com/apache/pekko-http/pull/1256)), it tested for CR and 
LF but not **NUL** ([#12 [...]
+**How §8 was verified.** P1, P2 and P9 were first asserted from configuration 
and design, then checked against the code; each had defects, all fixed at or 
before the pinned commit. P1: `max-chunk-count` reported the limit without 
stopping the parse ([#1220](https://github.com/apache/pekko-http/pull/1220)); 
repeated `Connection` headers escaped `max-header-count` 
([#1255](https://github.com/apache/pekko-http/pull/1255)); HTTP/2 
connection-level buffer accounting leaked on discarded stream [...]
 
 ---
 
 ## §9 Security properties Pekko HTTP does **not** provide
 
-- **No claim of complete DoS resistance.** The documented wording is *"behaves 
pretty well under most known Denial of Service attacks"*, immediately followed 
by a recommendation to front it with a load balancer or enterprise routing 
solution *(documented — `security.md`)*. Per §14 Q1 this disclaimer is **scoped 
to volume**: Pekko HTTP does not claim to withstand floods, but it *does* claim 
that one in-limits request cannot provoke disproportionate work — that part is 
P1, and a violation  [...]
-- **No edge hardening.** Rate limiting, IP reputation, request scrubbing, 
connection-count throttling beyond `max-connections`, slow-loris mitigation 
beyond `idle-timeout` — none are provided, and none are planned. *(maintainer — 
§14 Q1)*
-- **No authentication or authorization.** The security directives are 
plumbing; the credential check is the application's function. *(maintainer — 
§14 Q7)*
-- **No CSRF protection.** No token issuance or verification is provided. 
*(maintainer — §14 Q7)*
+- **No claim of complete DoS resistance.** *"Behaves pretty well under most 
known Denial of Service attacks"* *(documented — `security.md`)* is scoped to 
**volume** (§14 Q1): Pekko HTTP does not claim to withstand floods, but does 
claim that one in-limits request cannot provoke disproportionate work — that is 
P1, and a violation is `VALID`.
+- **No edge hardening.** Rate limiting, IP reputation, request scrubbing, 
connection throttling beyond `max-connections`, slow-loris mitigation beyond 
`idle-timeout` — none provided, none planned. *(maintainer — §14 Q1)*
+- **No authentication or authorization.** The security directives are 
plumbing. *(maintainer — §14 Q7)*
+- **No CSRF protection.** *(maintainer — §14 Q7)*
 - **No output encoding / XSS defence.** Pekko HTTP renders what the 
application marshals. *(maintainer — §14 Q7)*
-- **No trusted client-IP derivation from headers.** `extractClientIP` surfaces 
`X-Forwarded-For` / `X-Real-Ip` without validating them, and Pekko HTTP has no 
trusted-proxy chain configuration. `extractDirectClientIP` is the trustworthy 
accessor, but it yields the last proxy rather than the client. See §5 and §14 
Q4.
+- **No trusted client-IP derivation from headers.** `extractClientIP` surfaces 
`X-Forwarded-For` / `X-Real-Ip` unvalidated; there is no trusted-proxy chain 
configuration. `extractDirectClientIP` is trustworthy but yields the last 
proxy, not the client (§14 Q4).
 
 ### False friends
 
-- **The security directives are not a security *system*.** `authenticateBasic` 
and `authenticateOAuth2` route credentials to an application-supplied verifier; 
they impose no password policy, no rate limiting and no lockout. They *do* 
supply a constant-time comparison (§8 P8) — but only to a verifier that calls 
`Credentials.verify`; one that pattern-matches the secret out and uses `==` 
gets none of it.
-- **`allowed-origins = "*"` does not mean "no credentials are exposed".** 
Combined with the shipped `allow-credentials = yes`, it echoes the caller's 
`Origin` (§5a).
+- **The security directives are not a security *system*.** `authenticateBasic` 
and `authenticateOAuth2` route credentials to an application-supplied verifier; 
no password policy, rate limiting or lockout. They supply a constant-time 
comparison (P8) only to a verifier that calls `Credentials.verify`.
+- **`allowed-origins = "*"` does not mean "no credentials are exposed".** With 
the shipped `allow-credentials = yes` it echoes the caller's `Origin` (§5a).
 - **`remote-address-attribute` is not the client IP behind a proxy.** It is 
the socket peer.
 - **`max-content-length` is not a global memory bound.** It bounds one entity; 
concurrent connections multiply it.
+- **`verbose-error-messages = off` is not a log-hygiene setting.** It governs 
what reaches the *client*. What reaches the *server log* is 
`error-logging-verbosity`, which ships `full`: a request whose target fails to 
parse has that target written to the log at warning level (`UriParser.fail` 
copies the failing input line into the `ErrorInfo` detail; 
`DefaultParsingErrorHandler` passes it to `logParsingError`, which logs 
`formatPretty` at warning under `full`). Attacker-chosen bytes in th [...]
 - **A rejection is not a failure.** The routing DSL's rejection mechanism is 
control flow, not a security control — an unhandled rejection can fall through 
to a different route.
 
 ### Well-known attack classes left to the caller
 
-- **Request smuggling / desync** between a fronting proxy and Pekko HTTP — 
inherently a two-party property; strict parsing (P4) helps but cannot settle it 
alone. What Pekko HTTP does own is its own framing decision, and a case where 
it framed a message by a different rule than a conformant upstream would is a 
defect here rather than a two-party stalemate: a `Transfer-Encoding` whose 
value failed to parse was degraded to a `RawHeader`, never reached the modelled 
`Transfer-Encoding` arm of [...]
-- **Slow-loris and connection exhaustion** — `idle-timeout` and 
`max-connections` (P5) bound what one connection holds and how many are 
accepted, but exhausting those bounds by volume is disclaimed (§14 Q1). A 
single connection that evades `idle-timeout` while holding resources is the 
in-scope version.
-- **Decompression bombs** are *not* wholly left to the caller. Where the 
application decodes through the routing DSL, `decodeRequest` / 
`decodeRequestWith` bound the result on both axes: the decoder's per-chunk 
output is set to `decode-max-bytes-per-chunk` (`1m`) and the decoded stream is 
wrapped in `withSizeLimit(decode-max-size)` (`8m`), so a small gzip body that 
expands past the limit fails the stream instead of filling the heap 
(`CodingDirectives.scala:93-102`). Every `decodeRequest* [...]
+- **Request smuggling / desync** between a fronting proxy and Pekko HTTP is 
inherently two-party; strict parsing (P4) helps but cannot settle it alone. 
What Pekko HTTP does own is its own framing decision: framing a message by a 
different rule than a conformant upstream would is a defect here, not a 
stalemate. An unparseable `Transfer-Encoding` framed by `Content-Length` 
([#1267](https://github.com/apache/pekko-http/pull/1267)) and whitespace 
tolerated *inside* a chunk size ([#1257](http [...]
+- **Slow-loris and connection exhaustion.** `idle-timeout` bounds *inactivity* 
in either direction: a client sending a byte inside every window keeps the 
connection alive by design, and a hard receive deadline needs a reverse proxy 
or an application-level entity timeout *(documented — `reference.conf`, 
`timeouts.md`)*. A trickling client is `BY-DESIGN: property-disclaimed`; 
exhausting the bounds by volume is disclaimed (§14 Q1). In scope: a connection 
holding resources with **no** traffi [...]
+- **Decompression bombs** are bounded where decoding goes through 
`decodeRequest` / `decodeRequestWith`: per-chunk output by 
`decode-max-bytes-per-chunk`, decoded size by `decode-max-size` (§5a, 
`CodingDirectives.scala`). Left to the caller: decoding outside those 
directives — `Coders.Gzip.decodeMessage`, or client-side response decoding — 
and raising `decode-max-size`, which may be set to `infinite`.
 - **SSRF** via the client API, where the application takes a URL from a 
request.
-- **Path traversal** is left to the caller in some of the file-serving 
directives and not others, and the split is worth stating precisely (§14 Q3). 
`getFromDirectory`, `listDirectoryContents` and the 
`getFromBrowseableDirector*` pair built on them run the unmatched path through 
`safeDirectoryChildPath`, which applies both a segment filter and canonical 
containment against the served root — containment is claimed there, so a 
genuine escape is `VALID` per §5b.4. `getFromResourceDirectory` [...]
+- **Path traversal** — three tiers (§14 Q3). `getFromDirectory`, 
`listDirectoryContents` and the `getFromBrowseableDirector*` pair run the 
unmatched path through `safeDirectoryChildPath`: segment filter plus canonical 
containment against the served root. Containment is claimed there, so an escape 
is `VALID` — one was found and fixed, a symlink into a sibling directory whose 
name shared the root as a string prefix 
([#1218](https://github.com/apache/pekko-http/pull/1218)). `getFromResource 
[...]
 - **XXE** in XML marshallers — a property of the underlying parser.
 
 ---
@@ -266,40 +262,44 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 
 1. **Put an enterprise-grade proxy or load balancer in front** of an 
internet-facing service *(documented — `security.md`)*.
 2. **Do not raise the §5a limits without understanding the memory cost** — 
each is multiplied by concurrent connections.
-3. **If CORS is enabled, set `allowed-origins` explicitly.** Do not ship the 
`"*"` + `allow-credentials = yes` combination to a credentialed API (§5a).
-4. **Compare credentials with `Credentials.verify`**, which is constant-time 
(§8 P8) — not with `==` on the secret, and not via `provideVerify` unless the 
supplied verifier is itself constant-time.
-5. **Use `extractDirectClientIP`, not `extractClientIP`, for access control, 
rate limiting or audit logging.** The latter reads `X-Forwarded-For` / 
`X-Real-Ip`, which the client controls unless a trusted proxy overwrites them; 
it requires `remote-address-attribute = on` to fall back usefully. Derive 
identity from `X-Forwarded-For` only where a trusted proxy sets it and you 
validate the chain yourself.
+3. **If CORS is enabled, set `allowed-origins` explicitly.** Do not ship `"*"` 
+ `allow-credentials = yes` to a credentialed API (§5a).
+4. **Compare credentials with `Credentials.verify`** (P8) — not `==` on the 
secret, and not `provideVerify` unless the supplied verifier is itself 
constant-time.
+5. **Use `extractDirectClientIP`, not `extractClientIP`, for access control, 
rate limiting or audit logging.** Derive identity from `X-Forwarded-For` only 
where a trusted proxy sets it and you validate the chain yourself.
 6. **Validate and canonicalize any request-derived path** before passing it to 
a file-serving directive.
 7. **Treat client-API responses from untrusted upstreams as untrusted input.**
 8. **Consider `server-header = ""`** if product/version disclosure matters to 
your threat model.
+9. **Consider `error-logging-verbosity = simple`** where logs are shipped or 
alerted on (§9).
 
 ---
 
 ## §11 Known misuse patterns
 
-- **Exposing a Pekko HTTP service directly to the internet** with no fronting 
proxy, contrary to the documented recommendation.
+- **Exposing a Pekko HTTP service directly to the internet** with no fronting 
proxy.
 - **Enabling `cors()` and leaving `allowed-origins = "*"`** on an API that 
uses cookies or bearer tokens.
-- **Reaching for `extractClientIP`** — the more discoverable name — for rate 
limiting, audit logging or access control, where `extractDirectClientIP` is the 
one that cannot be chosen by the caller.
+- **Reaching for `extractClientIP`** for rate limiting, audit logging or 
access control, where `extractDirectClientIP` is the one the caller cannot 
choose.
 - **Raising `max-content-length` to `infinite`** to accept large uploads, 
without a concurrency bound.
-- **Comparing credentials with `==`** inside an `authenticateBasic` verifier — 
or reaching for `provideVerify` with a non-constant-time verifier — instead of 
`Credentials.verify`.
-- **Passing a request path segment straight to `getFromFile` or 
`getFromResource`.** Neither filters what it is handed. `getFromResource` is 
the easier of the two to get wrong: it looks like the resource-side sibling of 
`getFromResourceDirectory`, but where that directive routes the request path 
through `safeJoinPaths` before delegating, `getFromResource` passes the name to 
`classLoader.getResource` with only a trailing-slash check to keep directory 
resources unserved (`FileAndResourceDi [...]
-- **Turning `verbose-error-messages = on`** in production to aid debugging.
+- **Comparing credentials with `==`** inside an `authenticateBasic` verifier, 
or `provideVerify` with a non-constant-time verifier.
+- **Passing a request path segment straight to `getFromFile` or 
`getFromResource`.** Neither filters what it is handed. `getFromResource` looks 
like the sibling of `getFromResourceDirectory` but skips the segment filter 
that directive applies, so `getFromResource(s"public/$name")` against a 
directory-backed class loader can be walked into `application.conf` or 
`logback.xml`.
+- **Turning `verbose-error-messages = on`** in production.
+- **Building a `Raw-Request-URI` header from request input.** It renders the 
request target verbatim — into the HTTP/1.1 request line, and since 
[#1280](https://github.com/apache/pekko-http/pull/1280) as the HTTP/2 `:path`. 
Since [#1296](https://github.com/apache/pekko-http/pull/1296) the header, and a 
custom `HttpMethod`, reject at construction anything that could corrupt the 
request line — the target must be visible ASCII, the method a token — so the 
misuse no longer reaches injection; [...]
+- **Echoing `IllegalRequestContext.rawRequestTarget` from a custom 
`ParsingErrorHandler`** into the response or log without escaping it. The class 
documents it as attacker-controlled; the default handler never reads it.
 
 ---
 
 ## §11a Known non-findings (recurring false positives)
 
-- **"`Server` header discloses the product and version."** Documented default, 
configurable via `server-header` (§5a). Not a vulnerability under this model; a 
request to blank it by default is `BY-DESIGN: default-configuration` per §5b.
+- **"`Server` header discloses the product and version."** Documented default, 
configurable via `server-header`. `BY-DESIGN: default-configuration` per §5b.
 - **"No authentication on routes."** Authentication is the application's 
responsibility (§9). A scan of this library cannot conclude a route is 
unauthenticated.
 - **"Request exceeding `max-uri-length` / `max-header-count` is rejected."** 
That is P1 working.
-- **"N concurrent connections / requests exhaust CPU, memory or sockets."** 
Volume-based resource exhaustion is `BY-DESIGN: property-disclaimed` per §14 Q1 
— defence belongs to the fronting proxy (§10.1). Reports must show *one* 
in-limits request doing disproportionate work, not many requests doing 
proportionate work. A load-generator result is not a finding.
-- **"`extractClientIP` trusts a client-supplied header."** By design and 
documented at the directive, with `extractDirectClientIP` provided as the 
trustworthy alternative (§14 Q4). `BY-DESIGN: property-disclaimed`. A report 
that `extractDirectClientIP` can be influenced by a header *is* in scope.
-- **"CORS allows any origin."** Reflects the shipped default and requires the 
application to have opted into `cors()`. A request to change the default is 
`BY-DESIGN: default-configuration` per §5b; a misconfigured deployment is a 
finding against the *application*, not the library.
-- **"`allow-credentials = yes` with `allowed-origins = "*"` sends 
`Access-Control-Allow-Origin: *` with credentials."** It does not — the literal 
`*` is sent only when `allowCredentials` is false, otherwise the request 
`Origin` is echoed (`CorsSettingsImpl.scala:63`, covered by 
`CorsDirectivesSpec`). Reports asserting the literal `*`-with-credentials 
combination are factually wrong.
-- **"Credential comparison is vulnerable to a timing attack."** Check which 
comparator the report exercises: `Credentials.verify` is constant-time (§8 P8), 
so the claim is wrong against it; against an application's own `provideVerify` 
comparator it is a finding in that application, not this library.
+- **"N concurrent connections / requests exhaust CPU, memory or sockets."** 
Volume-based exhaustion is `BY-DESIGN: property-disclaimed` per §14 Q1 — 
defence belongs to the fronting proxy (§10.1). A report must show *one* 
in-limits request doing disproportionate work. A load-generator result is not a 
finding.
+- **"`extractClientIP` trusts a client-supplied header."** By design and 
documented at the directive, with `extractDirectClientIP` as the trustworthy 
alternative (§14 Q4). `BY-DESIGN: property-disclaimed`. A report that 
`extractDirectClientIP` can be influenced by a header *is* in scope.
+- **"CORS allows any origin."** The shipped default, reachable only where the 
application opted into `cors()`. `BY-DESIGN: default-configuration` per §5b; a 
misconfigured deployment is a finding against the *application*.
+- **"`allow-credentials = yes` with `allowed-origins = "*"` sends 
`Access-Control-Allow-Origin: *` with credentials."** It does not — the literal 
`*` is sent only when credentials are not allowed; otherwise the request 
`Origin` is echoed (`CorsSettingsImpl.scala`, covered by `CorsDirectivesSpec`). 
Factually wrong.
+- **"Credential comparison is vulnerable to a timing attack."** Wrong against 
`Credentials.verify` (P8); against an application's own `provideVerify` 
comparator it is a finding in that application.
+- **"A malformed HTTP/2 pseudo-header closes the whole connection."** A 
framing violation from the peer closing that peer's own connection with 
`GOAWAY` rather than `RST_STREAM`. `BY-DESIGN: property-disclaimed` per §14 Q11.
 - **Findings in `*-tests`, `http-testkit*`, `http-bench-jmh`, `http-scalafix`, 
`docs`** — `OUT-OF-MODEL: unsupported-component` per §3.
-- **"The process registers JVM shutdown hooks."** Pekko HTTP registers none 
(§5, §14 Q9). The hooks are `CoordinatedShutdown`'s and, with remoting enabled, 
Artery's — they belong to `apache/pekko`'s model. `OUT-OF-MODEL: 
unsupported-component`.
-- **Findings in the actor or stream layer** — belongs to `apache/pekko`'s 
model, not this one.
+- **"The process registers JVM shutdown hooks."** Pekko HTTP registers none 
(§5). The hooks are `CoordinatedShutdown`'s and, with remoting, Artery's — 
`apache/pekko`'s model. `OUT-OF-MODEL: unsupported-component`.
+- **Findings in the actor or stream layer** — `apache/pekko`'s model, not this 
one.
 
 ---
 
@@ -310,7 +310,7 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 - Taking on any authentication, authorization, or rate-limiting responsibility 
currently disclaimed in §9.
 - A change to the documented "do not expose directly" posture in `security.md`.
 - Promotion of a §3 module into the supported surface.
-- A change to the §5 process-behaviour claims. One near-miss is on record: 
[#1217](https://github.com/apache/pekko-http/pull/1217) originally replaced the 
per-upload `deleteOnExit` with a raw JVM shutdown hook registered by Pekko HTTP 
itself, which would have invalidated the "registers no shutdown hook" claim in 
§5 and the matching §11a non-finding; it was reworked in review to register the 
cleanup as a `CoordinatedShutdown` task on the actor system instead, and merged 
in that form, so t [...]
+- A change to the §5 process-behaviour claims. One near-miss is on record: 
[#1217](https://github.com/apache/pekko-http/pull/1217) originally registered a 
JVM shutdown hook from Pekko HTTP itself and was reworked to a 
`CoordinatedShutdown` task before merging, so the claims stand.
 - **A report that cannot be routed to exactly one §13 disposition** — evidence 
of a model gap; revise the model rather than making an ad-hoc call.
 
 ---
@@ -324,7 +324,7 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 | `OUT-OF-MODEL: trusted-input` | Requires control of an input §6 marks 
trusted (configuration, application-supplied handlers) | §6 |
 | `OUT-OF-MODEL: adversary-not-in-scope` | Requires in-JVM code execution, or 
a malicious embedding application | §7 |
 | `OUT-OF-MODEL: unsupported-component` | Lands in a §3 module, or in the 
actor/stream layer | §3 |
-| `OUT-OF-MODEL: non-default-build` | Only manifests under a non-default §5a 
setting — including any resource-exhaustion report that needs a limit 
**raised** from its default. Distinct from `default-configuration`: this is a 
real defect reachable only off-default, that one is no defect at all | §5a, §14 
Q1 |
+| `OUT-OF-MODEL: non-default-build` | Only manifests under a non-default §5a 
setting — including any resource-exhaustion report that needs a limit 
**raised** from its default. A real defect reachable only off-default; contrast 
`default-configuration`, which is no defect at all | §5a, §14 Q1 |
 | `BY-DESIGN: default-configuration` | Asks that a §5a default be changed to a 
more restrictive value. Not a vulnerability; §5b.3 invites the proposal on the 
development list | §5b |
 | `BY-DESIGN: property-disclaimed` | Concerns a §9 property — authentication, 
CSRF, XSS, edge hardening — or depends on request **volume** rather than 
request **content** | §9, §14 Q1 |
 | `KNOWN-NON-FINDING` | Matches a §11a pattern | §11a |
@@ -332,54 +332,43 @@ Pekko HTTP therefore takes the following position 
*(maintainer)*:
 
 ---
 
-## §14 Open questions for the maintainers
+## §14 Maintainer rulings
 
-Each states a **proposed answer**; confirming or correcting is enough.
+Questions the draft put to the maintainers, with the ruling each received. All 
are settled model *(maintainer)*.
 
-**Q1 — Where exactly is the DoS line? — ANSWERED.** *(maintainer)*
-The proposed split was accepted as written. The **DoS line** is now settled 
model, restated here as the canonical form and applied throughout §4, §5a, §8, 
§9, §11a and §13:
+**Q1 — Where is the DoS line?** Content vs. volume:
 
-> A **single request within every §5a documented limit** that provokes 
super-linear CPU or memory is `VALID` — this is the P1 violation symptom.
+> A **single request within every §5a documented limit** that provokes 
super-linear CPU or memory is `VALID` — the P1 violation symptom.
 > A finding that requires a §5a limit to be **raised** from its shipped 
 > default is `OUT-OF-MODEL: non-default-build`.
 > A finding that depends on request **volume** rather than request **content** 
 > — connection floods, slow-loris at scale, aggregate bandwidth — is 
 > `BY-DESIGN: property-disclaimed`. Volume defence is the fronting proxy's job 
 > (§10.1).
 
-The operative test is *content vs. volume*: one well-formed, in-limits request 
doing disproportionate work is a bug in Pekko HTTP; many requests doing 
proportionate work is a deployment concern.
+**Q2 — The CORS defaults.** A compatibility default under §5b: the values 
arrived with the donated code and existing users depend on them; `cors()` is 
opt-in and an application that enables it is expected to configure it (§10.3). 
Changing the default is `BY-DESIGN: default-configuration`. The 
*implementation* is in scope: `cors()` admitting an origin its configuration 
should have rejected, or emitting credentials for one it should not, is a 
defect under §5b.4.
 
-**Q2 — The CORS defaults. ANSWERED *(maintainer)*.** **Answer:** a 
compatibility default under §5b. The values arrived with the donated 
`http-cors` code and existing users depend on them; `cors()` is opt-in, and an 
application that enables it is expected to configure it, with §10.3 stating 
what to set. A report that the shipped default should change is `BY-DESIGN: 
default-configuration` — welcome on the development list, not as a security 
report. What *is* in scope is the implementation: [...]
+**Q3 — File-serving directives.** Containment is claimed by 
`safeDirectoryChildPath` — segment filter (`..`, `/`, `\`) plus canonical 
containment of the resolved file under the served root — for 
`getFromDirectory`, `listDirectoryContents` and the 
`getFromBrowseableDirector*` pair. A genuine escape is `VALID` under §5b.4; 
review found one (string-prefix comparison of canonical paths, 
[#1218](https://github.com/apache/pekko-http/pull/1218)). 
`getFromResourceDirectory` gets the segment filt [...]
 
-**Q3 — File-serving directives. ANSWERED *(maintainer)*.** *Resolved from code 
— and, like Q4 and Q5, a correction: the draft asserted containment held, and 
reviewing the check found a defect in it.* `safeDirectoryChildPath` claims 
containment by two measures: a path segment must not be `..` and must not 
contain `/` or `\\`; and the resolved file's `File.getCanonicalPath` must be 
prefixed by the base path's. The second measure compared the canonical paths as 
**strings** (`canonicalFinalP [...]
+**Q4 — `X-Forwarded-For` and client identity.** `extractClientIP` resolves 
`X-Forwarded-For` (first address) → `X-Real-Ip` → the `remoteAddress` 
attribute, and its scaladoc says the headers are under the client's control 
unless a trusted proxy overwrites them. `extractDirectClientIP` reads the 
attribute alone. "`extractClientIP` trusts a spoofable header" is `BY-DESIGN: 
property-disclaimed`; anything client-controlled reaching 
`extractDirectClientIP` is `VALID`. *(documented — `MiscDirec [...]
 
-**Q4 — `X-Forwarded-For` and client identity. ANSWERED *(maintainer)*.** The 
disposition is confirmed, but the draft's stated basis for it was wrong and is 
corrected here. Pekko HTTP **does** parse forwarding headers: `extractClientIP` 
resolves `X-Forwarded-For` (first address) → `X-Real-Ip` → the `remoteAddress` 
attribute, in that order (`MiscDirectives.scala:142-145`). What is true is that 
it never does so *implicitly* — `remote-address-attribute` populates the 
attribute from the socke [...]
+**Q5 — Constant-time credential comparison.** `Credentials.Provided.verify` 
compares via constant-time `secure_==` — recorded as P8. A timing finding 
against `verify` is `VALID`; one against an application's own `provideVerify` 
comparator is `BY-DESIGN: property-disclaimed` — handing the raw secret to 
application code waives the guarantee (§10.4). A report that `verify` reveals 
the secret's *length* describes documented behaviour of `secure_==`, not a P8 
violation: P8 protects the conten [...]
 
-**Answer:** the spoofability is documented at the directive rather than 
defended against. `extractClientIP`'s own scaladoc warns that *"the headers are 
under the control of the client unless a trusted proxy in front of this server 
overwrites them"* and directs the reader to `extractDirectClientIP` *"where the 
address must not be chosen by the client, for example for access control or 
rate limiting"* — a directive added for exactly this purpose (#1219, `@since 
2.0.0`), which reads the att [...]
+**Q6 — Module in/out split.** The §2 table is the maintainers' own. 
`http-caching` is **in** — cache-key confusion is a real class. 
`http-scalafix`, the test kits, `http-bench-jmh`, `docs`, `project`, `scripts` 
and `legal` are **out**: `OUT-OF-MODEL: unsupported-component`.
 
-**Q5 — Constant-time credential comparison. ANSWERED *(maintainer)*.** 
*Resolved from code — this document's earlier draft had it backwards.* 
`Credentials.Provided.verify` does compare, via `secure_==` 
(`EnhancedByteArray.scala:37`), which is constant-time; the library therefore 
**does** provide the guarantee, recorded as §8 P8. It is conditional on the 
verifier calling `verify` — `provideVerify` hands the raw secret to application 
code and waives it. **Answer *(maintainer)*:** the split [...]
+**Q7 — Non-goals.** Confirmed as stated in §3, §7 and §9: no authentication 
system, authorization policy, CSRF protection or XSS/output encoding 
(`BY-DESIGN: property-disclaimed`). Out of the adversary model: in-JVM code 
execution, and a malicious embedding application. In: the remote client, a 
malicious upstream server behind the client API, a cross-origin attacker where 
`cors()` is enabled.
 
-**Q6 — Module in/out split (§2 table). ANSWERED *(maintainer)*.** **Answer:** 
the split shown in §2 is confirmed as the maintainers' own, not merely the ASF 
Security team's proposal. `http-caching` is **in** model — cache-key confusion 
is a real class and the directive ships as part of the supported surface. 
`http-scalafix` is correctly **out**, along with the test kits, 
`http-bench-jmh`, `docs`, `project`, `scripts` and `legal`: a finding in any of 
them is `OUT-OF-MODEL: unsupported-com [...]
+**Q8 — TLS.** Where Pekko HTTP terminates TLS via `HttpsConnectionContext`, 
protocol and cipher selection come from the JSSE context the application 
supplies; nothing is pinned or overridden. "Weak cipher accepted" is 
`OUT-OF-MODEL: trusted-input`. A defect in how Pekko HTTP *drives* the context 
— failing to apply a supplied restriction, continuing after a handshake failure 
— is `VALID`. In-process termination is supported; fronting is what the docs 
recommend.
 
-**Q7 — The §3/§7/§9 non-goals. ANSWERED *(maintainer)*.** **Answer:** the 
split is confirmed as stated. Pekko HTTP provides **no** authentication system, 
authorization policy, CSRF protection or XSS/output encoding — reports against 
those are `BY-DESIGN: property-disclaimed` per §9. **Out** of the adversary 
model: attackers with code execution in the embedding JVM, and a malicious 
embedding application (a route that deliberately leaks is an application bug). 
**In**: the remote HTTP clien [...]
+**Q9 — The §5 negative claims.** Verified by source scan, with one correction: 
the draft said Pekko HTTP writes no files, and the upload directives do — §5 
states the accurate boundary. Pekko HTTP registers no shutdown hook of its own; 
the `ActorSystem`'s hooks belong to `apache/pekko`'s model. A report that Pekko 
HTTP installs a hook is factually wrong (§11a).
 
-**Q8 — TLS. ANSWERED *(maintainer)*.** **Answer:** confirmed. Where Pekko HTTP 
terminates TLS via `HttpsConnectionContext`, protocol and cipher selection come 
from the JSSE context the application supplies; Pekko HTTP neither pins a 
cipher suite nor overrides the JDK's defaults. "Weak cipher accepted" is 
therefore a deployment finding against that context or the JDK, not a library 
one — `OUT-OF-MODEL: trusted-input` per §6, which marks configuration as 
operator-supplied. A defect in how  [...]
-
-On the second half: **in-process termination is supported**, and 
`HttpsConnectionContext` is a first-class API. It is not, however, the posture 
the documentation steers production deployments toward — §4's quoted 
recommendation to front the service with an enterprise-grade routing solution 
or load balancer applies to TLS as much as to volume defence (§14 Q1), and in 
such a deployment termination is commonly the proxy's job. Both are supported; 
the fronted one is what the docs recommend.
-
-**Q9 — The negative claims in §5. ANSWERED *(maintainer)*, with one 
correction.** *Resolved from code, then ruled on.* Scanned the main sources of 
`http-core`, `http`, `parsing`, `http-caching` and `http-cors`: no 
`addShutdownHook`, no `ProcessBuilder`/`Runtime.exec`, no 
`Signal`/`SignalHandler`, no `System.setProperty`/`Security.*`/`setDefault`, 
and no bind outside the public `Http().bind*` entry points. **The file-writing 
claim did not survive review:** the scan missed `FileUploadDirec [...]
-
-**Answer:** state the boundary here rather than deferring it. The 
`ActorSystem`'s shutdown hooks are worth **highlighting** in §5 so an 
integrator is not surprised by them, but the claim this document makes and 
stands behind is that **Pekko HTTP registers none of its own**. A report that 
Pekko HTTP installs a shutdown hook is factually wrong (§11a); one about the 
hooks `CoordinatedShutdown` or Artery register belongs to `apache/pekko`'s 
model, and is `OUT-OF-MODEL: unsupported-component` here.
-
-**Q10 — Coexistence. ANSWERED *(maintainer)*.** Following the split adopted in 
[`apache/pekko#3478`](https://github.com/apache/pekko/pull/3478). Three 
documents carry security information, each canonical for one thing:
+**Q10 — Coexistence of the security documents.** Following 
[`apache/pekko#3478`](https://github.com/apache/pekko/pull/3478):
 
 | Document | Canonical for | Reached by |
 | --- | --- | --- |
-| [`SECURITY.md`](SECURITY.md) | **The reporting policy.** The strongest and 
canonical statement of how to report and what the project undertakes | Anyone 
arriving via the repository, and every other document |
+| [`SECURITY.md`](SECURITY.md) | **The reporting policy** | Anyone arriving 
via the repository, and every other document |
 | `THREAT_MODEL.md` (this document) | **Scope** — what is and is not a 
vulnerability, and how a report is triaged | Reporters, triagers, scanning 
tools |
 | `docs/src/main/paradox/security.md` | Security announcements, and the 
documentation-site index of security material | Readers of the documentation 
site |
 
-Every other document **links** to `SECURITY.md` for the reporting policy and 
to this document for scope, rather than restating either. A change to the 
policy is therefore made in one place.
-
-**Note the correction to the draft's proposal.** It suggested that 
`security.md`'s "Security model" section be reduced to a pointer here. It is 
*not*, and must not be: §4 quotes that section verbatim as the documented 
source of the "should not be exposed to the public internet directly" posture, 
and §15 back-maps four separate claims to it. Reducing it to a pointer would 
delete the evidence this document is built on and leave §4 citing a redirect. 
The section stays; `security.md` gains l [...]
+Each links to the others rather than restating them. `security.md`'s "Security 
model" section stays as the documented source §4 quotes. **One statement is 
referred:** `security.md`'s upstream-coordination sentence (*"any issues 
affecting Apache Pekko and Akka should be reported to Apache team first…"*) is 
a reporting statement outside `SECURITY.md`; it is left in place pending a 
maintainer decision to promote it verbatim or drop it.
 
-**One statement is referred, not migrated.** `security.md` carries an 
upstream-coordination sentence that `SECURITY.md` does not: *"Ideally, any 
issues affecting Apache Pekko and Akka should be reported to Apache team first. 
We will share the report with the Lightbend Akka team."* Per the same ruling, a 
reporting statement living outside `SECURITY.md` is referred to the maintainers 
for a decision — promoted verbatim or dropped, never silently moved. It is left 
untouched in `security.md`  [...]
+**Q11 — HTTP/2 framing violations close the connection.** A missing, 
duplicate, misplaced or unknown pseudo-header, or a connection-specific header, 
is raised by `RequestParsing.protocolError` as `Http2ProtocolException` and 
answered with `GOAWAY`, not the stream error RFC 9113 §8.1.1 asks for. Only the 
violating peer's connection is lost; a proxy multiplexing many clients onto one 
connection owns generating conformant pseudo-headers and stripping 
connection-specific ones. A report of th [...]
 
 ---
 
@@ -388,21 +377,26 @@ Every other document **links** to `SECURITY.md` for the 
reporting policy and to
 | Existing statement | Source | Lands in |
 | --- | --- | --- |
 | Applications should not be exposed to the public internet directly | 
`security.md` | §3, §4, §10.1 |
-| Behaves "pretty well" under most known DoS attacks — scoped to *volume* by 
the §14 Q1 ruling | `security.md` + maintainer ruling | §4, §9, §11a, §13, §14 
Q1 |
+| Behaves "pretty well" under most known DoS attacks — scoped to *volume* by 
§14 Q1 | `security.md` + maintainer ruling | §4, §9, §11a, §13, §14 Q1 |
 | An enterprise-grade routing solution or LB (httpd, Nginx) is safer | 
`security.md` | §5, §10.1 |
 | Report privately per ASF guidelines; subscribe to announce@ | `security.md` 
| `SECURITY.md`, §1 |
-| Parsing limits (`max-uri-length`, `max-header-*`, `max-chunk-*`, …) | 
`http-core/reference.conf` | §5a, §8 P1 |
+| Parsing limits (`max-uri-length`, `max-header-*`, `max-chunk-*`, 
`max-part-count`, …) | `http-core/reference.conf` | §5a, §8 P1 |
+| `idle-timeout` is a bidirectional inactivity timeout, not a request-receive 
deadline | `http-core/reference.conf`, `timeouts.md` | §8 P5, §9 |
+| `error-logging-verbosity = full` logs the failing request target at warning 
level | `http-core/reference.conf`, `UriParser.fail`, 
`DefaultParsingErrorHandler`, `logParsingError` | §5a, §9, §10.9 |
+| `IllegalRequestContext.rawRequestTarget` is unvalidated, attacker-controlled 
input | `ParsingErrorHandler.scala` | §6, §11 |
 | `verbose-error-messages = off` | `http-core/reference.conf` | §5a, §8 P3 |
 | Illegal response header name/value processing = `error` | 
`http-core/reference.conf` | §5a, §8 P2 |
 | `uri-parsing-mode = strict`, `cookie-parsing-mode = rfc6265` | 
`http-core/reference.conf` | §5a, §8 P4 |
 | Timeouts and connection caps | `http-core/reference.conf` | §5a, §8 P5 |
 | `max-concurrent-streams = 256` | `http-core/reference.conf` | §5a, §8 P6 |
 | `max-header-list-size = 64 KiB`, bounding HEADERS + CONTINUATION 
accumulation | `http-core/reference.conf` | §5a, §8 P7 |
-| `frame-type-throttle` charging `RST_STREAM` by default, against HTTP/2 Rapid 
Reset (CVE-2023-44487) | `http-core/reference.conf` | §5a, §8 P6 |
-| HTTP/2 incoming buffer bounds (`incoming-connection-level-buffer-size = 10 
MB`, `incoming-stream-level-buffer-size = 512kB`) and 
`outgoing-control-frame-buffer-size = 1024` | `http-core/reference.conf` | §5a, 
§8 P1 |
-| Decoding limits (`decode-max-bytes-per-chunk = 1m`, `decode-max-size = 8m`), 
applied by the `decodeRequest*` directives | `http/reference.conf`, 
`CodingDirectives.scala` | §5a, §9 |
+| `frame-type-throttle` charging `RST_STREAM` by default, against Rapid Reset 
(CVE-2023-44487) | `http-core/reference.conf` | §5a, §8 P6 |
+| HTTP/2 `max-frame-size`, incoming buffer bounds and 
`outgoing-control-frame-buffer-size` | `http-core/reference.conf` | §5a, §8 P1 |
+| `Raw-Request-URI` must be visible ASCII and a custom method a token; neither 
should be built from request input | `http-model.md`, `headers.scala`, 
`HttpMethod.scala` | §11 |
+| Decoding limits (`decode-max-bytes-per-chunk`, `decode-max-size`), applied 
by the `decodeRequest*` directives | `http/reference.conf`, 
`CodingDirectives.scala` | §5a, §9 |
 | `Credentials.verify` compares via constant-time `secure_==` | 
`SecurityDirectives.scala`, `EnhancedByteArray.scala` | §7, §8 P8, §9, §10.4, 
§14 Q5 |
-| `safeDirectoryChildPath` rejects `..`/separator segments and enforces 
canonical containment (its string-prefix comparison was found defective in 
review; fixed by [#1218](https://github.com/apache/pekko-http/pull/1218)) | 
`FileAndResourceDirectives.scala` | §9, §14 Q3 |
+| A request field that fails to parse into the model is answered with a `400` 
on its own HTTP/2 stream; pseudo-header and connection-specific-header 
violations close the connection with `GOAWAY` | `RequestErrorFlow.scala`, 
`RequestParsing.protocolError`, `Http2Blueprint.errorHandling` | §8 P9 |
+| `safeDirectoryChildPath` rejects `..`/separator segments and enforces 
canonical containment | `FileAndResourceDirectives.scala` | §9, §14 Q3 |
 | `remote-address-attribute = off` | `http-core/reference.conf` | §5, §9, §14 
Q4 |
 | `extractClientIP` reads `X-Forwarded-For`/`X-Real-Ip`; 
`extractDirectClientIP` reads the attribute alone | `MiscDirectives.scala` | 
§5, §6, §9, §10.5, §11a, §14 Q4 |
 | CORS: `*` + credentials echoes the request `Origin` | 
`http-cors/reference.conf` | §5a, §9, §11a, §14 Q2 |


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to