A key you did not make
A device fingerprint is a hash the browser computes and the server has to take on faith. The useful question is not whether to trust it, but what a server does once it accepts that it cannot.
4 min readGoldVault · Risk stage
On the payments platform a deposit request carries a device fingerprint: a hash the browser computes from the signals it can see about itself, sent along with the amount. The server uses it in two ways. It asks whether the same device has appeared on other customers' accounts, which is the strongest single fraud signal that platform has. And it counts how many distinct devices one customer has used in a day.
Both uses rest on a value the client made up. There is no way around that with device fingerprints: the server cannot see the browser, so it has to be told. The design question is not whether to trust the value. It is what to do with it, knowing you cannot.
What a bad value does
The fingerprint is a key into a graph. Store it as sent, and every row carrying the same string is linked. A malformed one, an empty one, the word undefined from a client bug: each becomes a node that every account sending the same junk hangs off. The shared-device query, which counts other customers on this device, then counts them. And the freeze path, which copies a frozen customer's most recent fingerprint onto the blocklist, would promote that junk string into a block that catches every other customer who happened to send it. Garbage in the graph is not noise. It is false edges between strangers.
Absent, not whatever was sent
The decision landed on 6 July 2026 and is four lines in the deposit route. The fingerprint is checked against the one shape a real one can have, sixty-four hexadecimal characters, and anything else becomes null before it reaches the guard or the consent record. The comment gives two reasons: so it cannot pollute the shared-device graph, and so it trips the missing-fingerprint signal instead. The second reason is the one most implementations forget.
Omission is not free
If an invalid fingerprint simply vanished, an attacker would have a clean move: send nothing. No shared-device match is possible for a value that does not exist. So the risk score penalises a missing fingerprint, and the comment on that branch says why in plain words: the fingerprint is client-supplied, so an attacker can omit it to dodge the shared-device check; penalise the omission so that dropping it is not a free way to look clean. The penalty is small, nowhere near a decision on its own. It exists so that absent and present-and-clean are not the same state.
What the server trusts more
In the same change, the address a request arrives from gained a shared-across-accounts signal of its own. The commit says why: it is observed by the server, so it cannot be rotated away like the client hash. That is the real hierarchy. A client hash tells you something only when it matches, and it cannot be relied on to be absent, present or unique. An address is weaker evidence per request and far harder to lie about per account. The platform leans on the thing it saw.
What it costs
A customer whose browser cannot produce a valid fingerprint gets less protection, and more of it than the anatomy row first admitted. The omission costs a few points. The shared-device signal cannot fire. And the per-day distinct-device limit is skipped for that request, because the rule only runs when a fingerprint is present. Absent is weaker evidence than a match, and it also switches one rule off. It is the right direction to fail. The alternative was a graph with forged edges in it, and a forged edge is worse than a missing one because it produces confident wrong answers about people who did nothing.
Two things the re-read found
First, the validator stops garbage, not forgery. A random value of the right shape passes, and nothing on the server can tell. That is not a flaw in the check. It is the ceiling on what any client-supplied fingerprint can ever be, and it is why the score weights the address above it. Second, the client's own fallback does not pass its own server's check. When a browser lacks the Web Crypto API, the fingerprint service falls back to an eight-character hash, which the sixty-four-character rule rejects every time. On a secure origin that path never runs, so in production it is dormant. It is two halves of one feature disagreeing, written by the same hand, and the kind of thing you only see when you read both ends in one sitting.
One older deposit route, with no callers left in the app, still passes the raw value through to the same tables. It predates the change and the change did not touch it. It should be deleted, and this decision is the argument for deleting it: a rule enforced in one route is not a rule.
Why it belongs on this page
The fingerprint crosses a hashing service in the browser, a request schema, a risk scorer, two tables with a velocity rule between them. At each layer the decision is small, and it is only correct as a whole. Validate at the edge or the graph fills with junk. Penalise the absence or the validation becomes the exploit. Weight the server-observed signal higher or the penalty is theatre. Any one of those on its own is decoration. They only hold each other up when the person writing the regex is also the person writing the score, and knows what the other hand did.