HarperFast / HarperFast/symphony
Allow forwardFingerprint to carry both JA3 and JA4
- Dominant language
- Rust
- Stars
- 1
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
`forwardFingerprint` is a single-valued enum — `'ja3' | 'ja4' | 'none'` — so a route can forward one fingerprint or the other, never both. Please allow both.
## Why
Symphony already computes both in `PeekInfo` (`src/sni.rs`), for every connection, whether or not either is forwarded. The two carriers are already distinct and non-conflicting:
- PROXY v2 TLV: `0xE0` = JA3, `0xE1` = JA4 — separate types, both fit the existing TLV section.
- HTTP header fallback: `X-JA3` and `X-JA4` — separate names, and `header_rewrites` already returns a `Vec`.
So the values exist and the wire formats exist; the enum is the only thing preventing both from being emitted.
## Why a downstream would want both
WAF and abuse-detection operators generally need both during any realistic transition period:
- **JA3** matches existing blocklists, historical logs, and third-party threat-intel feeds, which are overwhelmingly JA3-keyed.
- **JA4** is the value you actually want to make decisions on going forward, for exactly the reason your README documents — Chrome and other modern browsers randomize ClientHello extension order, so a single browser produces many JA3 hashes and per-browser JA3 blocking is unreliable.
Forwarding only JA4 means an operator's accumulated JA3 corpus goes dark on cutover. Forwarding only JA3 means adopting a fingerprint your own docs describe as unreliable. Both, for a period, is the honest configuration — and the cost here is one extra TLV (or one extra header) per connection on routes that opt in.
Your README already notes that JA3 values collected from earlier Symphony builds are not comparable after the GREASE-handling fix, which is another reason operators will want to run both concurrently while they rebuild a corpus against the corrected implementation.
## Suggested shape
Relax the field to accept a set while keeping the current scalar form working:
```typescript
forwardFingerprint: 'ja4' // unchanged
forwardFingerprint: ['ja3', 'ja4'] // new
```
`header_rewrites` already builds a vector, so the header path looks like appending a second `HeaderRewrite`. The TLV path appends a second TLV. The existing "no viable carrier" startup warning (`src/proxy.rs:744`) should presumably fire if *any* requested fingerprint has no carrier.
## Context
We're deploying Harper Fabric behind Symphony in front of a WooCommerce origin ([HarperFast/harper-woo-cache](https://github.com/HarperFast/harper-woo-cache)), where the origin's WAF consumes the fingerprint for rate limiting and bot classification. Our intake work is tracked in [harper-woo-cache#26](https://github.com/HarperFast/harper-woo-cache/issues/26).
Worth noting for anyone finding this issue later: our actual blocker is **not** Symphony. It is that Harper core parses PROXY protocol **v1 only** — verified in both `harper@5.1.23` and `harper@5.2.0-beta.2` (`dist/server/http.js`, `enableProxyProtocol`) — so the v2 TLV carrier, which is the correct and h2-safe way to move a fingerprint, cannot be consumed yet. That's a Harper-side ask and is filed there. This issue is only the "both fingerprints" enhancement.
One small doc nit while I'm here: the comment at `src/proxy_conn.rs:191` refers to "Harper's `-h2.sock` mirror". No such mirror exists in 5.1.23 or 5.2.0-beta.2 — Harper's UDS mirror is a plain `node:http` server (HTTP/1.1 only) and its HTTP/2 support lives only on the secure TCP port. The `destination_h2` split-upstream feature is fine; the example target just isn't real yet.
Contributor guide
Research direction
Start by tracing forwardFingerprint through the route configuration and forwarding paths, beginning with src/sni.rs and the warning at src/proxy.rs:744. Review the existing header_rewrites and PROXY v2 TLV handling, then verify that scalar configuration remains valid and that selecting both fingerprints emits both carriers when available, with the warning covering any unavailable request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 66/100