ESC
> PressHaven alpha
Home › Docs ›Detection › Device & GDPR Signature

Device & GDPR Signature

A consent-gated, one-way identifier that re-recognises a returning visitor without storing anything about them

What It Is

The gdpr_signature is a second, privacy-preserving way for the reverse proxy to recognise a returning visitor — one that survives the visitor clearing both their cookies and their localStorage. It is layered alongside the signed SERVERID session cookie, never replacing it, and it only activates with the visitor's explicit, revocable consent. The server never stores anything identifiable about the visitor — only a one-way hash, and only when it matters.

Opt-in Consent-gated No token is generated or sent until the visitor clicks Agree. Decline, and the session cookie stays the only identifier.
No PII at rest One-way Browser characteristics become a salted SHA-256 hash. Only the hash is stored, and only for honeypot-bound sessions.
Recovery Deterministic The browser reproduces the same inputs live, so the same device yields the same signature after a storage wipe.

Why It Exists

The signed session cookie and the passive recovery fingerprint (lua/session_identity_rules.lua) can both be shed by a client that clears storage and presents a different TLS/HTTP fingerprint. The signature adds an orthogonal, consent-based recovery path that the browser itself reproduces deterministically, so a diverted adversary who wipes everything and comes back is routed straight back to the honeypot.

The Flow

The source of truth is ids/GDPR.md; the client half lives in gdpr_client.js and the server half in lua/gdpr_handler.lua.

Client half (browser)

  1. On GET / the proxy injects a <script> and serves /__gdpr/config. gdpr_client.js shows a consent banner and fetches /__gdpr/config → { enabled, publicKey, ... }.
  2. Only if the visitor clicks Agree, and immediately:
    • build the payload { s: "<screenW>x<screenH>", t: <now> };
    • compute gdpr_token = base64( RSA-OAEP-SHA256( payload, publicKey ) );
    • set the gdpr_token cookie and reload.

The encryption in gdpr_client.js:

function buildToken() {
  if (!publicKey) return Promise.reject(new Error("no public key"));
  var payload = collectCharacteristics();          // { s: screen.width + "x" + screen.height }
  payload.t = Math.floor(Date.now() / 1000);       // fresh timestamp each time
  var data = new TextEncoder().encode(JSON.stringify(payload));
  return window.crypto.subtle
    .encrypt({ name: "RSA-OAEP" }, publicKey, data)
    .then(function (cipher) { return bytesToB64(cipher); });
}

Server half (reverse proxy, per request, in memory only)

  1. Decrypt: plaintext = RSA-OAEP-SHA256-decrypt( gdpr_token, PRIVATE key ). The private key never leaves the proxy.
  2. Append the client IP server-side: payload.ip = $remote_addr — the one characteristic the browser cannot supply or forge.
  3. If the timestamp is within GDPR_TIMESTAMP_WINDOW, build a stable canonical form of the characteristics (including the appended IP, timestamp stripped, keys sorted) and compute the one-way signature = SHA256( deployment-salt | canonical ).
  4. If Redis holds an unexpired gdpr_honeypot_sig:<signature>, prolong its TTL and route the session to the honeypot (router Stage 1a). Otherwise fall through to the normal session-token routing.
  5. The plaintext and characteristics are discarded immediately; only the hash may persist.

And the one-way hash in gdpr_handler.lua:

local function compute_signature(canonical)
    local h = sha256:new()
    h:update(_sig_secret)
    h:update("|")
    h:update(canonical)
    return str.to_hex(h:final())
end
Binding the signature

Whenever a request that carried a valid signature is routed to the honeypot for any reason, the proxy writes gdpr_honeypot_sig:<signature> with a GDPR_BINDING_TTL. A later storage wipe plus re-consent reproduces the same signature and lands the visitor right back on the honeypot.

Why It Is Privacy-Preserving

  • Consent first, and only then. No token is generated until the visitor clicks Agree. Decline or never decide, and the gdpr_token cookie is never set; the SERVERID session stays the only identifier.
  • The browser is the only thing that can produce a token. The plaintext characteristics are recomputed live each time; only ciphertext is ever at rest, and it cannot be decrypted without the private key, which never leaves the proxy.
  • The server never stores PII. The token is decrypted only in memory, only long enough to verify the timestamp and compute a one-way SHA-256 hash. Only that hash is stored, and only for honeypot-bound signatures, as a Redis key with a TTL.
  • Revocation is "just stop sending it." The only server-side record is a one-way hash that expires on its own, so there is nothing identifiable to delete.

Returning-Visitor Recovery

Because the signature is derived from stable device characteristics plus the server-observed IP, the same device from the same screen size reproduces the same signature. That is how a returning visitor is re-recognised without storing anything about them: the honeypot binding keyed on the hash is still in Redis, so the visitor is diverted again before the normal session routing even runs. Change the screen size (or the IP), and the signature changes — this is a recovery path, not a precise fingerprint.

Configuration

VariableEffect
GDPR_TIMESTAMP_WINDOWHow fresh a token's timestamp must be for the proxy to derive a signature from it, bounding replay of a captured token.
GDPR_BINDING_TTLHow long a gdpr_honeypot_sig:<signature> binding lives in Redis before it expires on its own.
/__gdpr/config enabledWhether the consent banner and token flow are served to the browser at all.

The signature only ever adds to honeypot routing; it is never used to send a session back to production, and it is independent of the risk score.

See It Live

The dashboard's Signature Simulation page reproduces the whole flow against the running proxy: resize the visitor window, press Agree & derive signature, and watch the hex signature the proxy logs. Resize to a new size for a different signature, then resize back to the first size to see the original signature — and the re-recognition — return.