Skip to content

Support PROXY v2 + forward JA fingerprint downstream to upstreams #9

Description

@kriszyp

Summary

Two related edge-proxy enhancements, bundled because PROXY v2's TLV mechanism is the natural carrier for the forwarded fingerprint:

  1. Emit PROXY protocol v2 (binary, TLV-capable) in addition to the current v1.
  2. Forward the computed JA3/JA4 fingerprint downstream to the upstream/backend, so services behind Symphony can act on it themselves.

Tracking only — not prioritized yet.

Current state

  • PROXY protocol: v1 only (text). See write_proxy_v1_header() in src/upstream.rs. Default for UDS upstreams, opt-in for TCP via sourceAddressHeader: 'proxyProtocol'. v1 has no extension/TLV field, so it can carry src/dst IP+port and nothing else.
  • Fingerprint: Symphony computes a JA3 MD5 from the peeked ClientHello (src/sni.rs) and uses it internally only for the static ja3Blocklist (src/protection.rs). The fingerprint is never surfaced — not in the PROXY header, not as an injected HTTP header, not in the napi Blocked/Suspended events.
  • JA4: not implemented — tracked separately in Move to JA4 Fingerprints #2.

Part 1 — PROXY protocol v2

Add a v2 (binary) header encoder alongside the existing v1 path, selectable via the sourceAddressHeader config (e.g. a proxyProtocolV2 variant). v2 buys us:

  • A well-defined TLV section for carrying additional metadata (the fingerprint, below; also TLS/SNI/ALPN info).
  • Cleaner binary framing than the v1 text parse on the backend side.

Backends that already parse v1 (HAProxy, nginx, Harper's own UDS PROXY v1 reader) generally also speak v2, but this should stay opt-in so we don't break existing v1 consumers.

Part 2 — Forward the JA fingerprint downstream

Expose the fingerprint Symphony already computes to the upstream/backend, via one or both of:

  • PROXY v2 TLV — a custom/standard TLV carrying the JA3/JA4 value (depends on Part 1).
  • Injected HTTP header (e.g. `X-JA3` / `X-JA4`) for L7/HTTP upstreams where PROXY protocol isn't in play.

Make forwarding opt-in per route/upstream.

Motivating use case

Potential customer Automattic wants to send JA3/JA4 signals downstream behind us — i.e. their own application servers sit behind Symphony (we proxy), and they want the edge-computed fingerprint passed through so their app can make its own bot/abuse decisions. Today the value dies inside Symphony; there's no path out to the origin.

This is the outward/forwarding direction. The inward direction (Harper consuming a fingerprint to dynamically update Symphony's blocklist at runtime) is a separate gap — ja3Blocklist is read once at startup and updateConfig/JsHotConfig only updates routes — and could be a follow-up if we want a closed-loop story.

Relationship to JA4 (#2)

Forwarding should be fingerprint-agnostic: ship JA3 forwarding on what exists today, and have it carry JA4 once #2 lands. Automattic asked for "ja3/ja4", so JA4 (#2) is on the critical path for fully satisfying that request.

Suggested touch points

  • src/upstream.rs — v2 header encoder beside write_proxy_v1_header()
  • src/sni.rs — fingerprint is already computed here (PeekInfo.ja3)
  • ts/types.ts — extend sourceAddressHeader / route config for v2 + forwarding opt-in

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions