Skip to content

fix: align maxUselessRecords with OpenSSL/BoringSSL (16 → 32) to mitigate TLS stack fingerprinting#28

Draft
6Kmfi6HP wants to merge 1 commit intoXTLS:mainfrom
6Kmfi6HP:fix/align-max-useless-records
Draft

fix: align maxUselessRecords with OpenSSL/BoringSSL (16 → 32) to mitigate TLS stack fingerprinting#28
6Kmfi6HP wants to merge 1 commit intoXTLS:mainfrom
6Kmfi6HP:fix/align-max-useless-records

Conversation

@6Kmfi6HP
Copy link

Problem

The current maxUselessRecords value is set to 16, directly copied from Go's crypto/tls standard library. However, the mainstream TLS stacks used by most web servers have a different threshold:

TLS Stack ChangeCipherSpec / Empty AppData threshold
BoringSSL (Chrome, Cloudflare) 32
OpenSSL 32
Apple Security Framework (Safari) 32
Go crypto/tls 16
XTLS REALITY (current) 16

This discrepancy creates a detectable behavioral fingerprint. An active prober can:

  1. Replay a captured VLESS REALITY Client Hello, inject ChangeCipherSpec records, and observe the server error at record Add post-handshake records detection #17 (Go behavior).
  2. Send a tampered Client Hello to trigger the fallback mechanism, inject CCS records, and observe the fallback destination error at record #33 (OpenSSL/BoringSSL behavior).
  3. The mismatch between 16 and 32 across two probes on the same port definitively reveals that two different TLS stacks coexist — a strong indicator of REALITY's fallback architecture.

Fix

Change maxUselessRecords from 16 to 32 to align with the behavior of BoringSSL, OpenSSL, Safari, and Cloudflare.

-	maxUselessRecords          = 16           // maximum number of consecutive non-advancing records
+	maxUselessRecords          = 32           // maximum number of consecutive non-advancing records

Rationale for value 32

  • BoringSSL: shared counter of 32 for CCS + empty AppData (source)
  • OpenSSL 1.1.1+: Same threshold of 32 for the shared non-advancing record counter

Note on prior art

This same discrepancy was already identified and fixed on the client side in uTLS via refraction-networking/utls#171, but the corresponding server-side code in XTLS/REALITY was never updated.

Scope

This is a necessary but not sufficient fix:

  • Eliminates the CCS counter discrepancy for this specific fingerprinting vector
  • Other behavioral differences between TLS stacks may still exist and should be addressed separately

Impact

  • Security: Eliminates one known fingerprinting vector used in active probing
  • Compatibility: No impact on normal TLS operation; 32 is still a conservative DoS protection threshold
  • Performance: Negligible

…gate TLS stack fingerprinting

Go's crypto/tls uses maxUselessRecords=16, while BoringSSL, OpenSSL, and Apple
Security Framework all use a threshold of 32. This discrepancy allows active
probers to distinguish REALITY servers from real web servers by comparing the
ChangeCipherSpec error threshold before and after triggering the fallback.

Aligning to 32 eliminates this specific fingerprinting vector while maintaining
conservative DoS protection.

Reference: refraction-networking/utls#171 (client-side fix by @RPRX)
@6c67b9
Copy link

6c67b9 commented Feb 16, 2026

#27 duplicated

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