@ianmenethil/zp-devicefp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -103
- package/dist/cdn/zp.dfp.esm.js +294 -84
- package/dist/cdn/zp.dfp.js +287 -84
- package/dist/cdn/zp.dfp.manifest.json +17 -17
- package/dist/cdn/zp.dfp.min.js +1 -1
- package/dist/cdn/zp.dfp.obf.js +1 -1
- package/dist/npm/index.cjs +301 -84
- package/dist/npm/index.d.ts +21 -8
- package/dist/npm/index.mjs +294 -84
- package/docs/api-reference.md +520 -472
- package/docs/architecture.md +171 -40
- package/docs/privacy.md +20 -20
- package/docs/signals.md +425 -383
- package/docs/testing.md +54 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Browser Fingerprint Library
|
|
2
2
|
|
|
3
|
-
Async-first TypeScript browser and device fingerprinting library. Collects
|
|
3
|
+
Async-first TypeScript browser and device fingerprinting library. Collects 21 tiered browser signals, computes per-component SHA-256 hashes, returns a composite thumbprint, and includes an anti-spoof anomaly report with confidence scoring.
|
|
4
4
|
|
|
5
5
|
- **Zero runtime dependencies** — no CDN loads, no npm deps at runtime.
|
|
6
6
|
- **ESM, CJS, and IIFE** — single TypeScript source tree, three distribution formats.
|
|
7
|
-
- **
|
|
7
|
+
- **92 tests across 24 files, 94.9% line coverage, 82.3% function coverage.**
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -28,50 +28,102 @@ const client = await createFingerprintClient({ timeoutMs: 1500 });
|
|
|
28
28
|
// Async collection — all signals, parallel with timeouts
|
|
29
29
|
const result = await client.collect();
|
|
30
30
|
|
|
31
|
-
console.log(result.thumbprint);
|
|
32
|
-
console.log(result.confidence);
|
|
31
|
+
console.log(result.thumbprint); // "a1b2c3d4..." (64-char SHA-256 hex)
|
|
32
|
+
console.log(result.confidence); // 0.87
|
|
33
33
|
console.log(result.antiSpoof.score); // 0.94
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
+
## Named exports
|
|
39
|
+
|
|
40
|
+
### npm / ESM / CJS (`@ianmenethil/zp-devicefp`)
|
|
41
|
+
|
|
42
|
+
**Functions**
|
|
43
|
+
|
|
44
|
+
| Export | Signature | Description |
|
|
45
|
+
| ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
|
|
46
|
+
| `createFingerprintClient` | `(options?: CollectOptions) => Promise<FingerprintClient>` | Factory — creates a configured client instance. |
|
|
47
|
+
|
|
48
|
+
**Constants**
|
|
49
|
+
|
|
50
|
+
| Export | Type | Description |
|
|
51
|
+
| ------------------------- | --------------------------------------------- | ---------------------------------------------------------------- |
|
|
52
|
+
| `LIBRARY_VERSION` | `string` | Semver string of the installed library (e.g. `"0.1.1"`). |
|
|
53
|
+
| `SCHEMA_VERSION` | `number` | Integer schema version; bumped on breaking result-shape changes. |
|
|
54
|
+
| `CORE_SIGNALS` | `SignalName[]` | The 10 signals collected by default. |
|
|
55
|
+
| `EXTENDED_SIGNALS` | `SignalName[]` | The 11 signals requiring `extended: true`. |
|
|
56
|
+
| `SYNC_SIGNALS` | `SignalName[]` | The 13 signals that support `collectSync()`. |
|
|
57
|
+
| `DEFAULT_TIMEOUT_MS` | `number` | Default per-signal timeout: `1500`. |
|
|
58
|
+
| `DEFAULT_FONT_LIST` | `string[]` | The 70 font faces probed during font detection (Fingerprint2 / Cardinal Commerce set). |
|
|
59
|
+
| `DEFAULT_PERMISSION_NAMES`| `readonly [...]` | The 4 permission names queried by the permissions signal. |
|
|
60
|
+
| `DEFAULT_UA_HINTS` | `readonly [...]` | The 8 high-entropy UA-CH hints requested. |
|
|
61
|
+
|
|
62
|
+
**TypeScript types** — all declared in the package root and importable directly:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import type {
|
|
66
|
+
// Client
|
|
67
|
+
FingerprintClient,
|
|
68
|
+
|
|
69
|
+
// Core result
|
|
70
|
+
FingerprintResult,
|
|
71
|
+
SignalResult, // generic: SignalResult<T = unknown>
|
|
72
|
+
SignalName,
|
|
73
|
+
SignalStatus,
|
|
74
|
+
AntiSpoofReport,
|
|
75
|
+
CollectorDiagnostics,
|
|
76
|
+
|
|
77
|
+
// Options
|
|
78
|
+
CollectOptions,
|
|
79
|
+
|
|
80
|
+
// Events
|
|
81
|
+
EventPayloadMap,
|
|
82
|
+
ProgressEvent,
|
|
83
|
+
WarningEvent,
|
|
84
|
+
CompleteEvent,
|
|
85
|
+
} from '@ianmenethil/zp-devicefp';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### CDN / IIFE (`window.DeviceFP`)
|
|
89
|
+
|
|
90
|
+
The IIFE bundle exposes only the three runtime values — TypeScript types are compile-time only and are not present on the global:
|
|
91
|
+
|
|
92
|
+
| Property | Type | Description |
|
|
93
|
+
| ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
|
|
94
|
+
| `createFingerprintClient` | `(options?: CollectOptions) => Promise<FingerprintClient>` | Factory — creates a configured client instance. |
|
|
95
|
+
| `LIBRARY_VERSION` | `string` | Semver string of the running bundle. |
|
|
96
|
+
| `SCHEMA_VERSION` | `number` | Integer schema version of the result shape. |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
38
100
|
## API overview
|
|
39
101
|
|
|
40
|
-
| Method
|
|
41
|
-
|
|
42
|
-
| `client.collect(options?)`
|
|
43
|
-
| `client.collectSync(options?)`
|
|
44
|
-
| `client.
|
|
45
|
-
| `client.on(event, callback)` | `() => void` | Subscribe to `progress`, `warning`, or `complete` events during async collection. |
|
|
102
|
+
| Method | Returns | Description |
|
|
103
|
+
| -------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
|
|
104
|
+
| `client.collect(options?)` | `Promise<FingerprintResult>` | Async collection. All signals run in parallel with per-signal timeouts. Emits events. |
|
|
105
|
+
| `client.collectSync(options?)` | `FingerprintResult` | Sync collection. Only 13 of 21 signals support sync mode. No timeout/abort. |
|
|
106
|
+
| `client.on(event, callback)` | `() => void` | Subscribe to `progress`, `warning`, or `complete` events during async collection. |
|
|
46
107
|
|
|
47
108
|
### Events
|
|
48
109
|
|
|
49
110
|
```ts
|
|
50
|
-
client.on('progress',
|
|
51
|
-
|
|
52
|
-
|
|
111
|
+
client.on('progress', e => {
|
|
112
|
+
// { completed: 3, total: 10, signal: 'canvas', result: SignalResult }
|
|
113
|
+
console.log(`${e.completed}/${e.total}: ${e.signal}`);
|
|
53
114
|
});
|
|
54
115
|
|
|
55
|
-
client.on('warning',
|
|
56
|
-
|
|
57
|
-
|
|
116
|
+
client.on('warning', e => {
|
|
117
|
+
// { signal?: 'webrtc', message: 'RTCPeerConnection not available' }
|
|
118
|
+
console.warn(e.message);
|
|
58
119
|
});
|
|
59
120
|
|
|
60
|
-
client.on('complete',
|
|
61
|
-
|
|
62
|
-
|
|
121
|
+
client.on('complete', e => {
|
|
122
|
+
// { result: FingerprintResult }
|
|
123
|
+
console.log('Done', e.result.thumbprint);
|
|
63
124
|
});
|
|
64
125
|
```
|
|
65
126
|
|
|
66
|
-
### Upload
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
await client.upload(result, {
|
|
70
|
-
endpoint: 'https://api.example.com/fingerprint',
|
|
71
|
-
headers: { 'Authorization': 'Bearer token' },
|
|
72
|
-
bodyExtras: { sessionId: 'abc123' },
|
|
73
|
-
});
|
|
74
|
-
```
|
|
75
127
|
|
|
76
128
|
### Aborting
|
|
77
129
|
|
|
@@ -93,12 +145,12 @@ const result = await resultPromise;
|
|
|
93
145
|
|
|
94
146
|
```ts
|
|
95
147
|
interface CollectOptions {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
148
|
+
include?: SignalName[]; // whitelist: only collect these signals
|
|
149
|
+
exclude?: SignalName[]; // blacklist: skip these signals
|
|
150
|
+
timeoutMs?: number; // per-signal timeout (default: 1500)
|
|
151
|
+
abortSignal?: AbortSignal; // cancel in-flight collection
|
|
152
|
+
extended?: boolean; // include extended-tier signals (default: false)
|
|
153
|
+
debug?: boolean; // enable debug logging (default: false)
|
|
102
154
|
}
|
|
103
155
|
```
|
|
104
156
|
|
|
@@ -108,15 +160,15 @@ interface CollectOptions {
|
|
|
108
160
|
|
|
109
161
|
```ts
|
|
110
162
|
interface FingerprintResult {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
163
|
+
schemaVersion: number; // schema version (integer, bumped on breaking changes)
|
|
164
|
+
libraryVersion: string; // library semver string
|
|
165
|
+
thumbprint: string; // SHA-256 of all component hashes combined
|
|
166
|
+
confidence: number; // 0–1 quality score
|
|
167
|
+
componentHashes: Partial<Record<SignalName, string>>; // per-signal SHA-256 digests
|
|
168
|
+
signals: Partial<Record<SignalName, SignalResult>>; // raw signal results
|
|
169
|
+
antiSpoof: AntiSpoofReport; // coherence analysis and automation hints
|
|
170
|
+
warnings: string[]; // non-fatal collection warnings
|
|
171
|
+
diagnostics: CollectorDiagnostics; // run metadata
|
|
120
172
|
}
|
|
121
173
|
```
|
|
122
174
|
|
|
@@ -124,9 +176,11 @@ interface FingerprintResult {
|
|
|
124
176
|
|
|
125
177
|
```ts
|
|
126
178
|
{
|
|
127
|
-
score: number;
|
|
128
|
-
anomalies: string[];
|
|
179
|
+
score: number; // 0 = automated/bot, 1 = genuine user
|
|
180
|
+
anomalies: string[]; // signal inconsistencies (6 anomaly types)
|
|
129
181
|
automationHints: string[]; // headless/bot indicators (2 hint types)
|
|
182
|
+
fakedOS: boolean; // UA's OS contradicts navigator.platform
|
|
183
|
+
fakedBrowser: boolean; // UA claims Chrome but window.chrome is absent
|
|
130
184
|
}
|
|
131
185
|
```
|
|
132
186
|
|
|
@@ -145,36 +199,38 @@ Full API reference with all types, methods, events, options, confidence scoring,
|
|
|
145
199
|
|
|
146
200
|
---
|
|
147
201
|
|
|
148
|
-
## Signals (
|
|
202
|
+
## Signals (21 total)
|
|
149
203
|
|
|
150
204
|
### Core tier (10 signals, enabled by default)
|
|
151
205
|
|
|
152
|
-
| Signal
|
|
153
|
-
|
|
154
|
-
| `ua`
|
|
155
|
-
| `uaHints`
|
|
156
|
-
| `locale`
|
|
157
|
-
| `screen`
|
|
158
|
-
| `hardware` | yes
|
|
159
|
-
| `storage`
|
|
160
|
-
| `fonts`
|
|
161
|
-
| `canvas`
|
|
162
|
-
| `webgl`
|
|
163
|
-
| `audio`
|
|
164
|
-
|
|
165
|
-
### Extended tier (
|
|
166
|
-
|
|
167
|
-
| Signal
|
|
168
|
-
|
|
169
|
-
| `mediaDevices`
|
|
170
|
-
| `permissions`
|
|
171
|
-
| `webrtc`
|
|
172
|
-
| `frameInfo`
|
|
173
|
-
| `networkInfo`
|
|
174
|
-
| `paymentSupport` | yes
|
|
175
|
-
| `referrerInfo`
|
|
176
|
-
| `navigationInfo` | yes
|
|
177
|
-
| `riskSignals`
|
|
206
|
+
| Signal | Sync | Description |
|
|
207
|
+
| ---------- | ---- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| `ua` | yes | `navigator.userAgent`, platform, webdriver, maxTouchPoints, cookieEnabled, vendor, doNotTrack (normalized), plugins (MIME format) |
|
|
209
|
+
| `uaHints` | no | User-Agent Client Hints: brands, platform, high-entropy values (architecture, bitness, model, etc.) |
|
|
210
|
+
| `locale` | yes | Language, languages, locale, calendar, numbering system, timeZone, formatted samples |
|
|
211
|
+
| `screen` | yes | Screen width/height, colorDepth, orientation, DPR, fakedResolution (FP2 has_lied_resolution), ccaScreenSize (Cardinal bucket), media queries |
|
|
212
|
+
| `hardware` | yes | `navigator.hardwareConcurrency`, `deviceMemory`, platform, maxTouchPoints, touchEventCreationSuccessful, onTouchStartAvailable |
|
|
213
|
+
| `storage` | yes | localStorage, sessionStorage, IndexedDB, Web SQL availability (write-read-delete probe) |
|
|
214
|
+
| `fonts` | yes | Installed font detection via hidden DOM span measurement (70 fonts, 3 generic families) |
|
|
215
|
+
| `canvas` | yes | Canvas 2D rendering pattern → `toDataURL()` (GPU+driver fingerprint) |
|
|
216
|
+
| `webgl` | yes | WebGL renderer/vendor (unmasked), version, extensions, max texture/viewport |
|
|
217
|
+
| `audio` | no | OfflineAudioContext oscillator+compressor numeric signature |
|
|
218
|
+
|
|
219
|
+
### Extended tier (11 signals, requires `extended: true`)
|
|
220
|
+
|
|
221
|
+
| Signal | Sync | Description |
|
|
222
|
+
| ---------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
223
|
+
| `mediaDevices` | no | Media device counts by kind (audioinput, audiooutput, videoinput) — no labels/IDs |
|
|
224
|
+
| `permissions` | no | Permissions API state for geolocation, notifications, camera, microphone |
|
|
225
|
+
| `webrtc` | no | RTCPeerConnection SDP codec/extmap capability hash |
|
|
226
|
+
| `frameInfo` | yes | Iframe count, source domains, top-level window check |
|
|
227
|
+
| `networkInfo` | yes | NetworkInformation API: effectiveType, downlink, RTT, saveData |
|
|
228
|
+
| `paymentSupport` | yes | `window.PaymentRequest` availability |
|
|
229
|
+
| `referrerInfo` | yes | `document.referrer` |
|
|
230
|
+
| `navigationInfo` | yes | PerformanceNavigationTiming.type (navigate/reload/back_forward/prerender) |
|
|
231
|
+
| `riskSignals` | no | Bot/headless detection: webdriver, headless UA, missing chrome object, zero dimensions, empty plugins, empty languages, UA-CH consistency, notification permission, permissions API headless hint, time to capture |
|
|
232
|
+
| `adblock` | no | Ad blocker detection — appends `div.adsbox` to body, checks `offsetHeight === 0` after 100 ms |
|
|
233
|
+
| `geolocation` | no | Geographic coordinates (latitude, longitude, accuracy) via Geolocation API — prompts user permission |
|
|
178
234
|
|
|
179
235
|
Complete per-signal documentation with value shapes, APIs used, and edge cases: **[docs/signals.md](docs/signals.md)**
|
|
180
236
|
|
|
@@ -182,16 +238,30 @@ Complete per-signal documentation with value shapes, APIs used, and edge cases:
|
|
|
182
238
|
|
|
183
239
|
## Anti-spoof heuristics (6 anomalies + 2 automation hints)
|
|
184
240
|
|
|
185
|
-
|
|
186
|
-
|
|
241
|
+
### Anomalies (coherence violations, –0.12 each)
|
|
242
|
+
|
|
243
|
+
| Code | What it detects |
|
|
244
|
+
| ---------------------------------- | ----------------------------------------------- |
|
|
245
|
+
| `ua_platform_mismatch` | `navigator.platform` contradicts UA-CH platform |
|
|
246
|
+
| `touch_claim_without_touch_points` | iPhone UA but `maxTouchPoints` is 0 |
|
|
247
|
+
| `mobile_ua_desktop_screen` | Android UA with screen width >= 1600px |
|
|
248
|
+
| `windows_ua_mac_platform` | Windows UA but platform reports macOS |
|
|
249
|
+
| `implausible_device_memory` | `deviceMemory` < 0.25 or > 64 GB |
|
|
250
|
+
| `tiny_screen_geometry` | Screen width and height both < 200px |
|
|
251
|
+
|
|
252
|
+
### Automation hints (bot/headless indicators, –0.18 each)
|
|
253
|
+
|
|
254
|
+
| Code | What it detects |
|
|
255
|
+
| --------------------- | ------------------------------- |
|
|
187
256
|
| `navigator_webdriver` | `navigator.webdriver` is truthy |
|
|
188
|
-
| `headless_user_agent` | UA contains `"headless"`
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
| `
|
|
257
|
+
| `headless_user_agent` | UA contains `"headless"` |
|
|
258
|
+
|
|
259
|
+
### Named booleans on `AntiSpoofReport`
|
|
260
|
+
|
|
261
|
+
| Field | Derived from | Meaning |
|
|
262
|
+
| ------------- | ----------------------------- | ---------------------------------------------------- |
|
|
263
|
+
| `fakedOS` | `ua_platform_mismatch` anomaly | UA's OS contradicts `navigator.platform` |
|
|
264
|
+
| `fakedBrowser`| `missing-chrome-object` hint | UA claims Chrome but `window.chrome` is absent |
|
|
195
265
|
|
|
196
266
|
---
|
|
197
267
|
|
|
@@ -199,7 +269,7 @@ Complete per-signal documentation with value shapes, APIs used, and edge cases:
|
|
|
199
269
|
|
|
200
270
|
Each signal has a fixed weight (0.2–1.0). Canvas, WebGL, fonts, and audio carry the highest weight. Status affects points earned: `ok` = full weight, `unsupported`/`blocked` = 35%, `timeout` = 15%, `error` = 0.
|
|
201
271
|
|
|
202
|
-
The base ratio is
|
|
272
|
+
The base ratio is scaled by an anti-spoof factor: `base × (0.65 + antiSpoofScore × 0.35)`. A perfect anti-spoof score (1.0) leaves the base unchanged; a zero score reduces it to 65%. Full details in [docs/api-reference.md](docs/api-reference.md).
|
|
203
273
|
|
|
204
274
|
---
|
|
205
275
|
|
|
@@ -207,25 +277,26 @@ The base ratio is blended with the anti-spoof score (65% base / 35% anti-spoof).
|
|
|
207
277
|
|
|
208
278
|
CDN files are available from jsDelivr (automatic from npm publish) and self-hosted.
|
|
209
279
|
|
|
210
|
-
| File
|
|
211
|
-
|
|
212
|
-
| `zp.dfp.js`
|
|
213
|
-
| `zp.dfp.min.js`
|
|
214
|
-
| `zp.dfp.obf.js`
|
|
215
|
-
| `zp.dfp.esm.js`
|
|
216
|
-
| `zp.dfp.manifest.json` | SRI integrity hashes
|
|
280
|
+
| File | Format |
|
|
281
|
+
| ---------------------- | ---------------------------- |
|
|
282
|
+
| `zp.dfp.js` | IIFE (unminified) |
|
|
283
|
+
| `zp.dfp.min.js` | IIFE (minified) |
|
|
284
|
+
| `zp.dfp.obf.js` | IIFE (minified + obfuscated) |
|
|
285
|
+
| `zp.dfp.esm.js` | ESM for `import()` |
|
|
286
|
+
| `zp.dfp.manifest.json` | SRI integrity hashes |
|
|
217
287
|
|
|
218
288
|
```html
|
|
219
289
|
<!-- jsDelivr -->
|
|
220
290
|
<script src="https://cdn.jsdelivr.net/npm/@ianmenethil/zp-devicefp/dist/cdn/zp.dfp.min.js"></script>
|
|
221
291
|
|
|
222
292
|
<!-- Self-hosted -->
|
|
223
|
-
<script src="https://cdn.zenithpayments.support/devicefp/
|
|
293
|
+
<script src="https://cdn.zenithpayments.support/devicefp/zp.dfp.min.js"></script>
|
|
224
294
|
|
|
225
295
|
<script>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
296
|
+
const client = await window.DeviceFP.createFingerprintClient({ timeoutMs: 1500 });
|
|
297
|
+
const result = await client.collect();
|
|
298
|
+
console.log(window.DeviceFP.LIBRARY_VERSION); // "0.1.1"
|
|
299
|
+
console.log(window.DeviceFP.SCHEMA_VERSION); // 2
|
|
229
300
|
</script>
|
|
230
301
|
```
|
|
231
302
|
|
|
@@ -254,7 +325,7 @@ dist/
|
|
|
254
325
|
```bash
|
|
255
326
|
bun install
|
|
256
327
|
bun run build # lint → typecheck → knip → jscpd → tests + coverage → esbuild → CDN deploy
|
|
257
|
-
bun test #
|
|
328
|
+
bun test # 92 tests, 94.9% line coverage
|
|
258
329
|
bun run smoke # validates all dist artifacts
|
|
259
330
|
```
|
|
260
331
|
|
|
@@ -262,13 +333,13 @@ bun run smoke # validates all dist artifacts
|
|
|
262
333
|
|
|
263
334
|
## Docs
|
|
264
335
|
|
|
265
|
-
| Document
|
|
266
|
-
|
|
267
|
-
| [docs/api-reference.md](docs/api-reference.md) | Complete API: types, methods, events, options, constants, confidence, anti-spoof,
|
|
268
|
-
| [docs/signals.md](docs/signals.md)
|
|
269
|
-
| [docs/architecture.md](docs/architecture.md)
|
|
270
|
-
| [docs/privacy.md](docs/privacy.md)
|
|
271
|
-
| [docs/testing.md](docs/testing.md)
|
|
336
|
+
| Document | Covers |
|
|
337
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
338
|
+
| [docs/api-reference.md](docs/api-reference.md) | Complete API: types, methods, events, options, constants, confidence, anti-spoof, CDN |
|
|
339
|
+
| [docs/signals.md](docs/signals.md) | All 21 signals: value shapes, APIs used, sync support, edge cases |
|
|
340
|
+
| [docs/architecture.md](docs/architecture.md) | Architecture, module map, Mermaid diagrams, data flow, confidence scoring |
|
|
341
|
+
| [docs/privacy.md](docs/privacy.md) | Privacy-by-design notes |
|
|
342
|
+
| [docs/testing.md](docs/testing.md) | Test strategy and coverage intent |
|
|
272
343
|
|
|
273
344
|
---
|
|
274
345
|
|
|
@@ -278,12 +349,11 @@ bun run smoke # validates all dist artifacts
|
|
|
278
349
|
- **Explainable output** — every signal includes a status and duration. No silent failures.
|
|
279
350
|
- **No dangerous defaults** — no battery, sensors, arbitrary installed-app probes, or local IP harvesting.
|
|
280
351
|
- **Anti-spoofing** focuses on coherence checks and automation hints, not "secret" client tricks.
|
|
281
|
-
- **Upload is backend-neutral and optional** — you control where (if anywhere) fingerprints are sent.
|
|
282
352
|
|
|
283
353
|
---
|
|
284
354
|
|
|
285
355
|
## Limitations
|
|
286
356
|
|
|
287
357
|
- Browser-side fingerprints are probabilistic, not identity proofs.
|
|
288
|
-
- Font detection
|
|
358
|
+
- Font detection probes 70 common fonts (Fingerprint2 / Cardinal Commerce set) and relies on span-measurement heuristics.
|
|
289
359
|
- The anti-spoof report is heuristic — combine with server-side context for fraud or login-risk decisions.
|