Skip to content

JA4T

JA4T fingerprints one TCP SYN packet. The value describes the TCP stack of the client, and it reads no payload. The image that specifies it titles itself JA4T/S: TCP Fingerprint, so one image specifies JA4T and JA4TS together.

The value

One JA4T value holds five parts, and one underscore separates each pair of parts. R4 and R5 of docs/specs/foxio/JA4T.md hold the reading, and R3 records the image example JA4T=65535_2-1-3-1-1-4_1460_8_1-2-4-8-R6.

Part What it holds Rules
a The TCP window size. R6 and R7
b The TCP option kinds, in order. R8 to R10
c The TCP maximum segment size. R11
d The TCP window scale. R12
e The retransmission timings. R13 to R18

Part a writes the raw window size, and no step applies the window-scale multiplier. R6 and R7 hold it.

Part b writes each option kind, and a hyphen separates each pair. R8 to R10 hold it.

Part e carries the retransmission timings, and the image labels it for the scanner method. R13 records the image label:

TCP Retransmission Timings (only on JA4TScan)

So a JA4T value of this library holds four parts, and it writes no part e. The JA4TS page holds part e, because R16 records that the Zeek package and the Wireshark dissector each write it on a JA4TS value.

What this library emits

JA4TFingerprinter produces the value. NewJA4T builds one, and ProcessPacket reads one packet. ComputeJA4T reads one packet and returns one value.

The fingerprinter holds no state. Reset and CleanupConnection each do nothing, because one packet decides one value.

The value takes the form {window}_{options}_{mss}_{wscale}. generateTCPFingerprint in ja4t.go writes it, and the same function writes the JA4TS value.

Field What the library writes for an absent value
The option list 00
The maximum segment size Two digits, zero-padded
The window scale 00

The Type field holds ja4t.

Which packet the fingerprinter reads

The fingerprinter reads a TCP packet that carries the SYN flag and no ACK flag. It tests the two flag bits, and it tests no other bit of the flag byte. So a SYN that also carries an ECN flag still produces a value.

The maintainer ruled that selection at issue #126 on 2026-08-13, which is the reading of the reference Rust over the split that R29 records. ja4t.go holds the comment, and ja4t_syn_selection_test.go holds the tests. The measurement that earned the ruling: 2 of 38 captures of the corpus carry an ECN-marked SYN, across 4 packets.

The fingerprinter also reads a TCP header that an ICMP error quotes, under ruling #484, and ja4t_icmp_quoted_test.go holds it.

Seven questions that the FoxIO implementations split on

docs/specs/foxio/JA4T.md records each one.

What splits Rule
Whether part b writes one entry for each option byte, or skips a pad byte. R10
How a part e delay rounds. R24
How a reset delay rounds. R25
What an empty option list writes. R26
What a maximum segment size of zero writes. R27
What a window scale of zero writes. R28
Whether the SYN selection tests two bits or the whole flag byte. R29

R30 records one more split, and it reaches the reset packet of a JA4TS value.

The maintainer ruled the option-byte question at issue #297 on 2026-08-12, and this library writes one entry for each option byte. The Zeek package is the one outlier.

Where the register records a difference

testdata/deviations.json holds no entry whose key names JA4T, measured on this branch. So this method carries no accepted difference from a FoxIO value.