Foil is security telemetry. It sees enough of the browser or native app runtime and the HTTP request to tell automation from humans, but not more than that — clients never receive verdicts, scores, or visitor IDs, and your backend is the only place final decisions are made. This page describes the data model, the cryptography, and the infrastructure posture. It is not legal advice; your final disclosure obligations depend on your deployment and jurisdiction.
Data flow at a glance
Foil is a three-hop system with clear trust boundaries at each hop:- Client → Foil API. The browser SDK (
t.js) or native SDK streams encrypted observation batches over HTTPS. Each session establishes a fresh ECDH key exchange with the API; batches are AES-256-GCM encrypted with keys derived from that exchange, and chain-hashed for integrity. - Foil API → your backend. When your app calls
getSession(), the API returns a{ sessionId, sealedToken }handoff. The client never sees a verdict. Your backend verifies the sealed token locally with your secret key, or fetches the durable session viaGET /v1/sessions/:sessionId. - Your backend → your database. You choose what to persist. Foil’s durable records are separate from yours.
What Foil collects
Foil collects three kinds of data during a session:| Class | Collected from | Used for |
|---|---|---|
| Environment observations | Browser probes (navigator, screen, WebGL, WebGPU, audio, fonts, permissions, client hints, CSS media queries, media devices, speech synthesis, …) and native OS/API facts (hardware, display, sensors, camera capability shape, storage, network interface shape, app metadata, bounded tooling allowlists) | Device fingerprinting, headless/automation detection, emulator/simulator detection, anti-tamper and anti-detect detection |
| Behavioral events | Pointer, keyboard, touch, clipboard, window, form, scroll, navigation, lifecycle, and sensor event streams | Human-vs-bot interaction scoring (mouse path, touch path, keystroke or native input cadence, focus lifecycle) |
| Request metadata | Headers, TLS fingerprints, HTTP/2 SETTINGS, client IP | Cross-channel consistency checks; network-level attribution (JA4, Akamai, etc.) |
- Form field values. Foil observes that a field received focus, was typed into, was pasted into, was blurred — not what was entered. Passwords, payment details, SSNs, and PII do not transit through Foil.
- Raw native form text or identifiers. Native SDKs hash field identity and round geometry; they do not transmit raw field text, raw placeholders, raw accessibility identifiers, or raw restoration/view IDs.
- Page content. The SDK does not scrape, serialize, or transmit DOM text, images, or your application’s rendered HTML.
- Cross-site browsing history. Each session is bound to the origin that loaded
t.js. There is no__utm-style cross-domain tracker. - Sensitive device identifiers and inventories. Native SDKs do not collect serial numbers, IMEI/MEID, advertising IDs, raw MAC addresses, SSID/BSSID, precise location, contacts, photos, raw proxy hostnames, or broad installed-app inventories.
Encryption and transport
In transit
The public API is reached over HTTPS atapi.usefoil.com. TLS terminates at our network edge, where network-level fingerprints (JA4, JA4_R, Akamai HTTP/2, and TCP SYN) are extracted from the handshake for attribution — without touching the request body. Let’s Encrypt certificates are rotated before expiry.
Inside the TLS tunnel, observation batches are additionally wrapped:
- Session key exchange. Each durable session performs an ECDH key exchange using P-256, producing a per-session shared secret.
- Batch encryption. Every observation batch is AES-256-GCM encrypted with a key derived from the ECDH shared secret.
- Session binding. The session ID and a monotonically-increasing batch sequence are attached as Additional Authenticated Data (AAD), so a batch cannot be replayed against a different session.
- Chain integrity. Each batch carries a chain hash that extends the previous batch’s hash. Any batch insertion, reorder, or replay invalidates the chain and fails closed on the server.
- Replay protection. The API issues a fresh nonce on each session handoff, and rejects any reused nonce.
At rest
Durable data is persisted in PostgreSQL behind our hosting provider’s managed infrastructure, with full-disk encryption at rest. Queued background work lives in Redis for the lifetime of a job. Sealed tokens exist only at handoff time — they are not retrievable from the durable session record, by design.Keys and secrets
Foil issues two key types per organization. Their responsibilities are distinct and should never overlap:| Key | Prefix | Where it lives | What it authorizes |
|---|---|---|---|
| Publishable key | pk_live_..., pk_test_... | Browser or native app binary | Starting a session, submitting observation batches, minting a sealed handoff |
| Secret key | sk_live_..., sk_test_... | Your server only, environment variable | Verifying sealed tokens, fetching durable sessions and fingerprints, organization and API key management |
pk_* limited to https://yourdomain.com cannot be used to spin up sessions from a different origin.
Both key types are rotatable without downtime via POST /v1/organizations/:organizationId/api-keys/:keyId/rotations. Rotations issue a new key while allowing the old key to continue functioning through a grace window, so you can update your deployment and then revoke.
Sealed token handoffs
foil.getSession() returns { sessionId, sealedToken }. The sealed token is the authoritative snapshot of the session’s verdict at that moment, encoded so only your secret key can open it.
Two integration properties fall out of this design:
- The browser can’t lie. A malicious or tampered-with client can alter
t.jsin memory, but it cannot forge a sealed token — the token is minted server-side, bound cryptographically to the session, and encrypted such that only yoursk_*can decrypt and verify it. - Your backend makes the decision. The verdict (
bot/human/inconclusive), the risk score, the attribution category, and the visitor fingerprint ID all live inside the sealed token. They are never visible to the browser or to any client-side script.
GET /v1/sessions/:sessionId with your secret key — useful for audit workflows, delayed verification, and reconciling chargebacks or incidents weeks after the fact.
Runtime integrity and bundle attestation
Foil’s public agent (t.js) is owned by the CDN release pipeline, not the API. The API keeps a rolling allowlist of valid bundle attestations in PostgreSQL and enforces it on every observation batch:
- Every batch carries an attestation token bound to the current bundle’s hash.
- The API checks the token against the allowlist on every request.
- Unrecognized or retired bundles fail closed — no score, no handoff.
Durable identification
For a returning browser on the same device, Foil derives a durable visitor fingerprint — a stable identifier that persists across sessions, cookie clears, and incognito mode. It is available to your backend via the sealed token (visitor_fingerprint.id) and the GET /v1/fingerprints/:visitorId endpoint. It is never exposed to the browser.
The visitor fingerprint is derived from stable environment properties (canvas, WebGL, audio, fonts, screen geometry) combined with storage anchors (cookies, localStorage, IndexedDB, service worker, window.name) when available. Hardened privacy browsers — Firefox in resistFingerprinting mode, Brave with aggressive shields, Tor — will often yield no durable ID, and Foil returns null rather than a low-confidence guess.
On native, visitor_id is device-backed within your organization. Foil keeps install_id separate from device_id so your backend can distinguish the same install, a new install on a known device, and a new device. Native App Attest and Android Key Attestation are positive-only by default: verified attestation strengthens continuity, while missing or unsupported attestation falls back gracefully unless your organization explicitly enables strict enforcement.
If your product does not need cross-session correlation, you can choose not to persist the visitor ID. Foil keeps its own durable record for attribution and rate-limiting purposes, but your application’s view of a user does not have to include it.
Infrastructure
- API edge. TLS terminates at our network edge with raw TCP passthrough so JA4, HTTP/2, and TCP SYN fingerprints are available to the scoring layer. This is network-edge fingerprinting only — payload bodies are not inspected at the edge.
- Certificates. Let’s Encrypt certificates issued via DNS-01 challenge and rotated on a 90-day cadence, with a documented runbook for emergency rotation.
- Compute. API and worker processes run on our hosting provider in separate process groups with private networking between them. Dashboard, marketing site, and docs are deployed as independent surfaces.
- Storage. PostgreSQL with full-disk encryption at rest and managed backups. Redis holds ephemeral queue state only.
- Bundle delivery. The public agent (
t.js) is served from Cloudflare atcdn.usefoil.com, with bundle attestations minted at publish time and synced to the API’s allowlist.
Your integration responsibilities
Foil secures its own surface. A handful of choices on your side are yours to get right:- Keep secret keys server-side only. Never ship a
sk_*to the browser, a mobile app binary, or a client-side SPA bundle. - Restrict publishable key origins. In the dashboard, limit each
pk_*to the domains you actually load it from. This prevents unauthorized reuse from unrelated origins. - Use separate test and live keys.
pk_test_*andsk_test_*exercise the same API surface without producing production-marked events. - Roll out in report-only mode first. See Going to production. Log verdicts for a week before enforcing, so you understand your traffic before blocking any of it.
- Apply the verdict server-side, not client-side. The browser doesn’t have the verdict — don’t try to round-trip it back to the client for enforcement. Enforce in the handler that writes the row.
- Rotate compromised keys immediately. If you suspect a leak, issue a new key, update your deployment, and revoke the old key once the grace window is clear.
Privacy notice language
If you deploy Foil in production, review your privacy notice and disclose the parts that apply to your implementation. The specifics depend on your jurisdiction, but common content areas to cover:- Purpose. Bot, fraud, or abuse detection on your site or product.
- Data processed. Browser and native device telemetry, interaction telemetry, HTTP request metadata. Specifically list anything you retain in your own systems beyond the session lifetime.
- Persistent identifiers. Whether you store the Foil visitor fingerprint ID, and for how long.
- Sub-processor. Foil (operated by abxy-labs) as a processor for this telemetry.
- User rights. How users can exercise access/deletion/portability rights under GDPR, CCPA, or equivalent — which in practice means: how you respond when a user requests their data, given that some of it is processed through Foil.
Regional considerations
Foil’s public surface is designed with data minimization in mind — observations are purpose-limited to bot and fraud detection, and cross-session correlation is opt-in on your side. That said, regional privacy frameworks impose specific obligations on deployers (you) that a product page can’t fully discharge:- GDPR / UK GDPR. Foil processes data on your behalf and is a sub-processor to your controller relationship with users. A DPA governs this relationship.
- CCPA / CPRA. Automation detection is a security-purpose processing activity, generally compatible with CCPA service-provider exemptions. Confirm with your counsel how your jurisdiction categorizes bot detection.
- Data residency. Foil’s managed infrastructure currently runs in regions selected for latency and uptime rather than residency guarantees. If you have hard residency requirements, get in touch before rolling out.
Reporting a vulnerability
We welcome reports from security researchers. To report a vulnerability:- Email
security@usefoil.comwith a clear description, reproduction steps, and any proof-of-concept you have. - Scope. The public agent (
t.js), the API (api.usefoil.com), the dashboard (dashboard.usefoil.com), and the server SDKs are all in scope. The marketing site, docs, and third-party dependencies are secondary priority. - Safe-harbor. We will not take legal action against researchers who report in good faith, do not access data beyond what’s necessary to demonstrate an issue, and give us reasonable time to remediate before public disclosure.
- Out of scope. Findings that require physical access to a user’s machine, social engineering of Foil staff, or attacks against the customer deployments of third parties are outside the scope of this program.
What’s next
How it works
The detection pipeline end-to-end.
Server verification
Verifying sealed tokens on your backend.
Content Security Policy
CSP directives required to run the browser SDK.
Going to production
Report-only rollout and enforcement checklist.