You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(http): address review feedback on connectionInfo docs
- Add <VersionBadge type="changed" version="v5.2.0" /> to the protocol
row, matching the file's convention for documenting behavior changes
to existing surface (Gemini + Ethan Arrowood, PR #614).
- Fix the peerCertificate link: certificate-verification.md is about
CRL/OCSP revocation and never mentions peerCertificate or the
authenticated-user flow (Ethan Arrowood).
- Tighten the connectionInfo undefined/runtime caveat: it's undefined
whenever no TLS-bearing TLV decoded (not just "no v2 header"), and
it's Node-runtime only — Bun and uWebSockets never populate it
(Ethan Arrowood).
- Flag alpn/authority/ja3/ja4 as string-decoded from raw, unvalidated
TLV payloads with no grammar/length validation, matching the harper
source docblock (server/serverHelpers/proxyProtocol.ts) that
introduces these fields, and warn against placing them unsanitized
in headers, logs, or cache keys. Switch the JA4 example's denial
from a generic `throw new Error` (reads as a 500) to the repo's
established `new Response(..., { status: 403 })` policy-denial
convention (see reference/resources/resource-api.md).
- Correct the verified-mTLS gating guidance: point at `request.authorized`
(the chain-gated boolean the proxy layer populates with TLSSocket
semantics — proxyProtocol.ts's own comment: "For the standard
'verified mTLS client' gate use request.authorized") instead of
`request.peerCertificate` + "the authenticated user". Note it's
Node-runtime only, same as connectionInfo.
- Fix a real accuracy bug surfaced by independent review: `clientCertChain`
was documented as "verified," but proxyProtocol.ts attaches the chain
whenever the client presented a certificate, independent of the
proxy's verify result — `tls.verified` and `clientCertChain`
presence are two different signals, and treating chain presence as
proof of verification accepts unverified clients. Corrected the
property table, the ConnectionInfo interface comment, and the trust
paragraph to say so explicitly.
- Narrow the `protocol` row to Node-runtime SSL-TLV behavior; Bun and
uWebSockets derive the scheme from X-Forwarded-Proto/the listener's
secure flag instead.
- Scope PROXY v2 decoding to Harper Fabric's proxy-protocol UDS
mirrors specifically, not ordinary http.port/securePort listeners.
The static-vs-instance-method suggestion on the get() example was
deliberately left as-is: the file's other Resource example (line 148)
also uses an instance method, and Ethan confirmed no such rule exists
in AGENTS.md/CONTRIBUTING.md.
Refs #614
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|`pathname`| string | Path portion of the URL, without query string |
87
-
|`protocol`| string |`http` or `https`. Behind a TLS-terminating proxy that forwards PROXY protocol v2, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext. |
88
-
|`data`| any | Deserialized body, based on `Content-Type` header |
89
-
|`ip`| string | Remote IP address of the client (or last proxy) |
90
-
|`host`| string | Host from the request headers |
91
-
|`session`| object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. |
92
-
|`connectionInfo`| object \| undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the verified mTLS client certificate chain. `undefined` for a direct connection or one without a v2 header. See [Connection info](#connection-info). <VersionBadgeversion="v5.2.0" /> |
|`pathname`| string | Path portion of the URL, without query string |
87
+
|`protocol`| string |`http` or `https`. On the Node runtime, behind a TLS-terminating proxy that forwards the PROXY v2 SSL TLV, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext; a proxy forwarding only address info (no SSL TLV) still reads as `http`. Bun and uWebSockets derive this from `X-Forwarded-Proto`/the listener's secure flag instead, independent of the SSL TLV. <VersionBadgetype="changed"version="v5.2.0" />|
88
+
|`data`| any | Deserialized body, based on `Content-Type` header |
89
+
|`ip`| string | Remote IP address of the client (or last proxy) |
90
+
|`host`| string | Host from the request headers |
91
+
|`session`| object | Current cookie-based session (a `Table` record instance). Update with `request.session.update({ key: value })`. A cookie is set automatically the first time a session is updated or a login occurs. |
92
+
|`connectionInfo`| object \| undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the client's mTLS certificate chain (presented, not necessarily verified — see below). `undefined` for a direct connection or one without a v2 header. See [Connection info](#connection-info). <VersionBadgeversion="v5.2.0" />|
93
93
94
94
### Connection info
95
95
96
96
<VersionBadgeversion="v5.2.0" />
97
97
98
-
When Harper runs behind a TLS-terminating proxy (for example, Harper Fabric's Symphony edge), the TLS handshake happens at the proxy, so facts about the client's TLS connection — its fingerprint, negotiated protocol, and any client certificate — are otherwise invisible to your application. When the proxy is configured to forward them over PROXY protocol v2, Harper decodes them and exposes them on `request.connectionInfo`:
98
+
When Harper runs behind a TLS-terminating proxy (for example, Harper Fabric's Symphony edge), the TLS handshake happens at the proxy, so facts about the client's TLS connection — its fingerprint, negotiated protocol, and any client certificate — are otherwise invisible to your application. Harper Fabric's proxy-protocol Unix domain socket mirrors decode this from a forwarded PROXY protocol v2 header and expose it on `request.connectionInfo`; a PROXY v2 header sent to an ordinary `http.port`/`securePort` listener isn't decoded (and isn't valid HTTP, so the connection fails).
99
99
100
100
```ts
101
101
interfaceConnectionInfo {
102
-
alpn?:string; // negotiated ALPN protocol, e.g. "h2"
103
-
authority?:string; // SNI hostname from the ClientHello
102
+
alpn?:string; // negotiated ALPN protocol, e.g. "h2" — raw, unvalidated
103
+
authority?:string; // SNI hostname from the ClientHello — raw, unvalidated
104
104
tls?: {
105
105
version?:string; // e.g. "TLSv1.3"
106
106
cipher?:string; // negotiated cipher suite
107
107
verified?:boolean; // a client certificate was presented and the proxy verified it
`request.connectionInfo` is `undefined` unless a PROXY v2 header was decoded; each field is present only when the proxy actually forwarded it. Reading the client's JA4 fingerprint — e.g. to make a bot or abuse decision — is simply:
115
+
`request.connectionInfo` is `undefined` unless a PROXY v2 header carrying TLS facts was decoded; each field is present only when the proxy actually forwarded it. On the Bun and uWebSockets runtimes it is always `undefined`. `alpn`, `authority`, `ja3`, and `ja4` are string-decoded from the TLV payload with no grammar or length validation beyond the header's own bounds, so validate them before placing them in response headers, logs, cache keys, or other security-sensitive contexts. Reading the client's JA4 fingerprint — e.g. to make a bot or abuse decision — is simply:
116
116
117
117
```javascript
118
118
classOrigin {
119
119
get(request) {
120
120
constja4=request.connectionInfo?.ja4;
121
-
if (ja4 &&abuseList.has(ja4)) thrownewError('blocked');
121
+
if (ja4 &&abuseList.has(ja4)) returnnewResponse('blocked', { status:403 });
122
122
returnfetch(request);
123
123
}
124
124
}
125
125
```
126
126
127
-
**Trust and mTLS.** `connectionInfo` is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so a client cannot forge it. `tls.verified` reflects the proxy's verify bit: it can be `true` even when `clientCertChain` is absent (a proxy may verify a certificate but omit an oversized chain). For gating on a verified mTLS client identity, use the parsed [`request.peerCertificate`](../security/certificate-verification.md) and the connection's authenticated user, not `connectionInfo.tls.verified` alone.
127
+
**Trust and mTLS.** `connectionInfo` is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so the channel itself cannot be forged by a client (its _contents_ are client-controlled, per above). `clientCertChain` and `tls.verified` are independent signals, not one another's proof: a chain is attached whenever the client presented a certificate, whether or not the proxy verified it, and `tls.verified` can be `true` while `clientCertChain` is absent (a proxy may verify a certificate but omit an oversized chain). Treating chain presence alone as proof of a verified client accepts unverified certificates. For gating on a verified mTLS client, use `request.authorized` — a chain-gated boolean the proxy layer populates with the same semantics as a directly-terminated TLS connection — rather than `connectionInfo.tls.verified` or `clientCertChain` presence alone. Like `connectionInfo`, this is Node-runtime only: `request.authorized` is always `undefined` on Bun and uWebSockets.
0 commit comments