Skip to content

feat: TLS fingerprint on sessions, hashed; change audited (1.3.0) - #8

Merged
BackStacked merged 1 commit into
mainfrom
claude/project-thread-9efqn4
Oct 15, 2026
Merged

BackStacked merged 1 commit into
mainfrom
claude/project-thread-9efqn4

Conversation

@BackStacked

Copy link
Copy Markdown
Contributor

Requested by Himanshu · project thread

Before: sessions were bound to the client's device signals only.

After: login and refresh also take tlsFingerprint (JA4/JA3, supplied by the server from security-kit's readTlsFingerprint, never by the client). Only its SHA-256 is stored, as Session.tlsFingerprintHash. On refresh, a different fingerprint is audited as auth.tls_fingerprint_changed (both hashes) and the latest is kept; auth.device_mismatch now records tls: "same" | "changed" | null.

It never blocks a refresh: browser updates and TLS-inspecting networks change the fingerprint, the same reasoning as the client fingerprint. A refresh without one keeps the stored hash; a session without one is bound by the first refresh that has one.

How: hashTlsFingerprint (validated, SHA-256 base64url) in src/auth.ts, a new optional Session field, 8 new checks run on memory and Redis stores (126 total); the Redis "no raw token" check also covers the fingerprint. No error codes changed. Version 1.3.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VT1kmGY9yJ2fLxetUn2D5c


Generated by Claude Code

login and refresh take tlsFingerprint (JA4/JA3 from the server's trusted TLS
proxy). Only its SHA-256 is stored (Session.tlsFingerprintHash). A change on
refresh is audited as auth.tls_fingerprint_changed and never blocks;
auth.device_mismatch records whether the TLS fingerprint matched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VT1kmGY9yJ2fLxetUn2D5c
@BackStacked BackStacked self-assigned this Oct 15, 2026
@BackStacked
BackStacked merged commit 012aefb into main Oct 15, 2026
1 check passed
@BackStacked
BackStacked deleted the claude/project-thread-9efqn4 branch October 15, 2026 20:52
BackStacked added a commit to mgpl-diagnostics/api that referenced this pull request Oct 25, 2026
…NT_HEADER) (#9)

<!-- claude-projects-attribution: {"github_login":"BackStacked"} -->
_Requested by **Himanshu** · [project
thread](https://claude.ai/code/project/chan_016JXeBZ2UxSi4N5VU2C26fQ?thread=cmsg_016JXeBZ2UxSi4N5VU2C26fQXQfucx3itZeBWpxHXi3jWG)_

Before: login and refresh bound a session to the client's device signals
only.

After: with `TLS_FINGERPRINT_HEADER=x-ja4` (and `TRUST_PROXY` ≥ 1), the
JA4/JA3 TLS fingerprint the proxy computed is passed to auth-kit at
`login` and `refreshSession`, stored as a SHA-256 hash on the session,
and a change on refresh is audited as `auth.tls_fingerprint_changed`.
`auth.device_mismatch` also records whether the TLS fingerprint matched.
Nothing changes in the GraphQL schema or the client.

A TLS fingerprint comes from the ClientHello, so only the TLS terminator
can compute it. The header is read with security-kit's trusted-proxy
rule, the same as X-Forwarded-For, and the api refuses to start with the
header set but no trusted proxy. A missing fingerprint is never an
error, and a change never blocks (browser updates and TLS-inspecting
networks change it).

How: `src/config/env.ts` (new variable and its check), `src/server.ts`
(`fastifySecurity({ tlsFingerprintHeader })` → context),
`ctx.tlsFingerprint` passed by the auth controller and service. Docs in
06 (proxy setup table), 09, 12, `.env.example`. 4 new end-to-end checks
(hash only stored, change audited, header ignored without the setting,
startup refusal); `TEST_BACKENDS=pg npm test` passes locally with the
kits built from source (112 checks).

**Draft until the kits are published:** needs security-kit 0.3.0
(mgpl-diagnostics/security-kit#3) and auth-kit 1.3.0
(mgpl-diagnostics/auth-kit#8), both merged. After publishing them, run
`npm install` here to update `package-lock.json` and push; CI will then
go green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VT1kmGY9yJ2fLxetUn2D5c

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VT1kmGY9yJ2fLxetUn2D5c)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants