SkillAgentSearch skills...

Fpscanner

Self-hosted browser fingerprinting and bot detection with real-world constraints in mind.

Install / Use

/learn @antoinevastel/Fpscanner

README

Fingerprint Scanner

News: After more than 7 years without any updates, the first release of the new FPScanner is here. This version combines fingerprinting and bot detection in a single library. Try the live demo at fpscanner.com. Expect new fingerprinting signals and more detection logic over time.

CI

Sponsor

This project is sponsored by <a href="https://castle.io/?utm_source=github&utm_medium=oss&utm_campaign=fpscanner">Castle.</a>

<a href="https://castle.io/?utm_source=github&utm_medium=oss&utm_campaign=fpscanner"><img src="assets/castle-logo.png" alt="Castle" height="48" style="vertical-align: middle;"></a>

This library focuses on self-hosted fingerprinting and bot detection primitives. In real-world fraud and bot prevention, teams often need additional capabilities such as traffic observability, historical analysis, rule iteration, and correlation across device, network, and behavioral signals.

Castle provides a production-grade platform for bot and fraud detection, designed to operate at scale and handle these operational challenges end to end.

For a deeper explanation of what this library intentionally does not cover, see the “Limits and non-goals” section at the end of this README.

FPScanner: description

A lightweight browser fingerprinting library for bot detection.

Scraping has become mainstream. AI and LLM-driven companies now crawl the web at a scale that was previously limited to specialized actors, often without clearly respecting robots.txt or rate limits. At the same time, fraudsters do not need to rely solely on public frameworks like OpenBullet or generic automation stacks anymore. With LLMs, writing a custom bot tailored to a specific website has become significantly easier, faster, and cheaper.

The result is a much broader and more diverse bot ecosystem:

  • More actors scraping content, training models, or extracting data
  • More custom automation, harder to fingerprint with outdated heuristics
  • More abuse at signup, login, and sensitive workflows, not just simple scraping

On the defender side, the situation is much more constrained.

You often have two options:

  • Very basic open source libraries that focus on naive or outdated signals
  • Expensive, black-box bot and fraud solutions that require routing traffic through third-party CDNs or vendors

Not every website can afford enterprise-grade bot management products. And even when cost is not the main issue, you may not want to route all your traffic through a CDN or outsource all detection logic to a third party.

This library exists to fill that gap.

It is a self-hosted, lightweight, and up-to-date browser fingerprinting and bot detection library, designed with real-world constraints in mind. The goal is not to promise perfect detection, but to give you solid building blocks that reflect how bots actually behave today.

This includes practical considerations that are often ignored in toy implementations:

  • Anti-replay protections (timestamp + nonce)
  • Payload encryption to prevent trivial forgery
  • Optional obfuscation to raise the cost of reverse-engineering
  • Focus on strong, low-noise signals rather than brittle tricks

The design and trade-offs behind this library are directly inspired by real production experience and by the ideas discussed in these articles:

Those articles are not documentation for this library, but they reflect the same philosophy: understand what attackers actually do, accept that no single signal is perfect, and build simple, composable primitives that you fully control.

Open Source, Production-Ready

This library is open source, but it is not naive about the implications of being open.

In bot detection, openness cuts both ways. Publishing detection logic makes it easier for attackers to study how they are detected. At the same time, defenders routinely study open and closed automation frameworks, anti-detect browsers, and bot tooling to discover new signals and weaknesses. This asymmetry already exists in the ecosystem, regardless of whether this library is open source or not.

The goal here is not to rely on obscurity. It is to acknowledge that attackers will read the code and still make abuse operationally expensive.

This is why the library combines transparency with pragmatic hardening:

  • Anti-replay mechanisms ensure that a valid fingerprint cannot simply be captured once and reused at scale.
  • Build-time key injection means attackers cannot trivially generate valid encrypted payloads without access to your specific build.
  • Optional obfuscation raises the cost of reverse-engineering and makes automated payload forgery harder without executing the code in a real browser.

These controls are not meant to be perfect or unbreakable. Their purpose is to remove the easy shortcuts. An attacker should not be able to look at the repository, reimplement a serializer, and start sending convincing fingerprints from a headless script.

More importantly, detection does not stop at a single boolean flag.

Even if an attacker focuses on bypassing individual bot detection checks, producing fully consistent fingerprints over time is significantly harder. Fingerprints encode relationships between signals, contexts, and environments. Maintaining that consistency across sessions, IPs, and accounts requires real execution, careful state management, and stable tooling.

In practice, this creates leverage on the server side:

  • Fingerprints can be tracked over time
  • Reuse patterns and drift become visible
  • Inconsistencies surface when attackers partially emulate environments or rotate tooling incorrectly

This is how fingerprinting is used in production systems: not as a one-shot verdict, but as a way to observe structure, reuse, and anomalies at scale.

Open source does not weaken this approach. It makes the trade-offs explicit. Attackers are assumed to be capable and adaptive, not careless. The library is designed accordingly: to force real execution, limit replay, and preserve enough structure in the signals that automation leaves traces once you observe it over time.

Features

| Feature | Description | |---------|-------------| | Fast bot detection | Client-side detection of strong automation signals such as navigator.webdriver, CDP usage, Playwright markers, and other common automation artifacts | | Browser fingerprinting | Short-lived fingerprint designed for attack detection, clustering, and session correlation rather than long-term device tracking | | Encrypted payloads | Optional payload encryption to prevent trivial forgery, with the encryption key injected at build time | | Obfuscation | Optional code obfuscation to increase the cost of reverse-engineering and make it harder to forge valid fingerprints without actually executing the collection code | | Cross-context validation | Detects inconsistencies across different JavaScript execution contexts (main page, iframes, and web workers) |


Quick Start

Installation

npm install fpscanner

Note: Out of the box, fpscanner uses a default placeholder encryption key and no obfuscation. This is fine for development and testing, but for production deployments you should build with your own key and enable obfuscation. See Advanced: Custom Builds for details.

Basic Usage

import FingerprintScanner from 'fpscanner';

const scanner = new FingerprintScanner();
const payload = await scanner.collectFingerprint();

// Send payload to your server
fetch('/api/fingerprint', {
  method: 'POST',
  body: JSON.stringify({ fingerprint: payload }),
  headers: { 'Content-Type': 'application/json' }
});

Server-Side (Node.js)

// Decrypt and validate the fingerprint
// Use the same key you provided when building: npx fpscanner build --key=your-key
const key = 'your-secret-key'; // Your custom key

function decryptFingerprint(ciphertext, key) {
  const encrypted = Buffer.from(ciphertext, 'base64');
  const keyBytes = Buffer.from(key, 'utf8');
  const decrypted = Buffer.alloc(encrypted.length);

  for (let i = 0; i < encrypted.length; i++) {
    decrypted[i] = encrypted[i] ^ keyBytes[i % keyBytes.length];
  }

  return JSON.parse(decrypted.toString('utf8'));
}

app.post('/api/fingerprint', (req, res) => {
  const fingerprint = decryptFingerprint(req.body.fingerprint, key);

  // Check bot detection
  if (fingerprint.fastBotDetection) {
    console.log('🤖 Bot detected!', fingerprint.fastBotDetectionDetails);
    return res.status(403).json({ error: 'Bot detected' });
  }

  // Validate timestamp (prevent replay attacks)
  const ageMs = Date.now() - fingerprint.time;
  if (ageMs > 60000) { // 60 seconds
    return res.status(400).json({ error: 'Fingerprint expired' });
  }

  // Use fingerprint.fsid for session correlation
  console.log('Fingerprint ID:', fingerprint.fsid);
  res.json({ ok: true });
});

That's it! For most use cases, this is all you need.


API Reference

collectFingerprint(options?)

Collects browser signals and returns a fingerprint.

const scanner = new FingerprintScanner();

// Default: returns encrypted base64 string
const encrypted = await scanner.collectFingerprint();

// Explicit encryption
const encrypted = await scanner.collectFingerprint({ encrypt: true });

// Raw object (no library encryption)
const fingerprint = await scanner.collectFingerprint({ encrypt: false });

| Option | Type | Default |

View on GitHub
GitHub Stars682
CategoryDevelopment
Updated2d ago
Forks72

Languages

TypeScript

Security Score

100/100

Audited on Mar 22, 2026

No findings