Skip to content

Add JA4/TLS fingerprint debug endpoint at /_ts/debug/ja4#646

Open
prk-Jr wants to merge 7 commits intomainfrom
feat/ja4-debug-endpoint
Open

Add JA4/TLS fingerprint debug endpoint at /_ts/debug/ja4#646
prk-Jr wants to merge 7 commits intomainfrom
feat/ja4-debug-endpoint

Conversation

@prk-Jr
Copy link
Copy Markdown
Collaborator

@prk-Jr prk-Jr commented Apr 20, 2026

Summary

  • Adds a temporary GET /_ts/debug/ja4 endpoint to the Fastly adapter to surface JA4 TLS fingerprint, H2 fingerprint, cipher suite, TLS version, user-agent, and selected Client Hints for browser fingerprint evaluation
  • Keeps the endpoint response readable when Fastly omits individual TLS or Client Hint values by using explicit fallback text
  • Adds route-level regression coverage so the adapter test suite exercises both the response builder and the /_ts/debug/ja4 router path

Changes

File Change
crates/trusted-server-adapter-fastly/src/main.rs Added build_ja4_debug_response, registered GET /_ts/debug/ja4, and added unit coverage for fallback response fields
crates/trusted-server-adapter-fastly/src/route_tests.rs Added a route-level test covering /_ts/debug/ja4 dispatch, content type, and fallback body values

Closes

Closes #645

Test plan

  • cargo fmt --all -- --check
  • cargo test --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • GitHub Actions checks green for cargo test, vitest, integration tests, browser integration tests, format-docs, format-typescript, cargo fmt, and CodeQL
  • Manual testing via fastly compute serve

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code - use expect("should ...")
  • Uses log macros (not println!)
  • New code has tests
  • No secrets or credentials committed

@prk-Jr prk-Jr self-assigned this Apr 20, 2026
@aram356 aram356 marked this pull request as draft April 20, 2026 15:41
@prk-Jr prk-Jr changed the title Add temporary JA4/TLS debug endpoint Add JA4/TLS fingerprint debug endpoint at /_ts/debug/ja4 Apr 29, 2026
@prk-Jr prk-Jr marked this pull request as ready for review April 29, 2026 13:40
@prk-Jr prk-Jr requested review from ChristianPavilonis, aram356 and jevansnyc and removed request for ChristianPavilonis April 29, 2026 13:58
Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Thanks for adding the temporary JA4/TLS debug endpoint and route coverage. The implementation is straightforward and CI is green, but before this ships I think the endpoint should be explicitly gated so it cannot become a public same-origin fingerprint reflection API by default.

Additional finding folded into body

Document the debug endpoint and config gate

If this endpoint is retained behind a new trusted-server.toml debug flag, please also document the flag and endpoint behavior in the API/config docs. The docs should make clear that this is temporary/debug-only, disabled by default, what fields are returned (ja4, h2_fp, cipher, tls_version, user-agent, ch-mobile, ch-platform), the fallback values, and that the response uses Cache-Control: no-store, private.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Copy link
Copy Markdown
Collaborator

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds a GET /_ts/debug/ja4 endpoint to the Fastly adapter for inspecting JA4/H2/TLS fingerprint metadata and Client Hints. CI is green and the route-level test correctly exercises dispatch. Two blocking concerns remain before this can ship: the endpoint is reachable unauthenticated by default (carried over from the prior review round), and its Cache-Control: no-store, private header is silently overridable by operator [response_headers] because the response flows through finalize_response.

Blocking

🔧 wrench

  • Public-by-default fingerprint reflection oracle: /_ts/debug/ja4 is not protected by basic-auth in the default config and reflects values JS cannot otherwise read directly (main.rs:217). Gate behind a debug flag, restrict to staging, or move under an admin path.
  • Cache-Control overridable by operator [response_headers]: handler-set no-store, private is replaced by finalize_response if operators set Cache-Control in [response_headers] (main.rs:134 → main.rs:349-351). Handle the route alongside /health before route_request runs.

Non-blocking

♻️ refactor

  • Unit test duplicates the route-level test: identical assertions; route-level test is strictly stronger (main.rs:359-411 vs route_tests.rs:253-317).

🤔 thinking

  • Defensive Vary header: User-Agent, Client-Hints, and TLS metadata vary the response — useful backstop if the override on Cache-Control is fixed (main.rs:133-136).

🌱 seedling

  • Track removal: PR description and code call this "temporary" but neither has a removal trigger or follow-up issue. Add a // TODO: remove after JA4 evaluation completes — see #645 and consider a scheduled cleanup PR.

📝 note

  • Bypasses platform abstraction for fields that have one: tls_protocol and tls_cipher are available via runtime_services.client_info() (main.rs:115-116). JA4/H2 are Fastly-specific so direct calls are unavoidable for those; mixing in the abstraction for fields that have one would be more consistent.

⛏ nitpick

  • Use header::CACHE_CONTROL in test assertions instead of "cache-control" (main.rs:376, route_tests.rs:282).
  • Extract magic strings to consts: "unavailable", "not sent", "none" and label strings are duplicated across production and both tests (main.rs:113-130).

CI Status

  • fmt: PASS
  • clippy: PASS
  • rust tests: PASS
  • js tests: PASS
  • wasm32-wasip1 release build: PASS

Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
);

Response::from_status(fastly::http::StatusCode::OK)
.with_header(header::CACHE_CONTROL, "no-store, private")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 wrenchCache-Control is silently overridable by operator [response_headers]

The handler sets Cache-Control: no-store, private, but the response then flows through finalize_response, whose final loop unconditionally calls response.set_header(key, value) for every operator-configured [response_headers] entry (crates/trusted-server-adapter-fastly/src/main.rs:349-351). If an operator configures Cache-Control = "public, max-age=..." (a common edge default), our no-store, private is silently replaced and a sensitive fingerprint response can land in shared caches.

The route-level test does not catch this because create_test_settings() does not configure [response_headers]. The finalize_response doc says "operators can intentionally override any managed header" — that contract is acceptable for cosmetic headers, not for cache-control on a debug endpoint that exposes TLS metadata.

Fix: Handle this route the same way /health is handled — before route_request/finalize_response (top of main(), after init_logger):

if req.get_method() == Method::GET && req.get_path() == "/_ts/debug/ja4" {
    build_ja4_debug_response(&req).send_to_client();
    return;
}

That also avoids paying the cost of building the auction orchestrator and integration registry for a simple debug probe, and pairs naturally with the staging-only gate suggested in the other 🔧.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — handler moved to main() after get_settings() but before build_orchestrator(), same pattern as /health. Calls send_to_client() directly and returns, bypassing finalize_response entirely. Both the enabled and disabled (404) cases are handled there.

Comment thread crates/trusted-server-adapter-fastly/src/main.rs
Comment thread crates/trusted-server-adapter-fastly/src/main.rs
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Comment thread crates/trusted-server-adapter-fastly/src/main.rs
Comment thread crates/trusted-server-adapter-fastly/src/main.rs Outdated
Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

I reviewed the current branch again and am only leaving findings that do not appear to duplicate the existing pending review comments. These are non-blocking cleanup/documentation accuracy items.

Comment thread crates/trusted-server-adapter-fastly/src/route_tests.rs Outdated
Comment thread trusted-server.toml Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add JA4/TLS fingerprint debug endpoint

3 participants