@cloudglides/veil 0.1.0 → 0.1.1
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/.github/workflows/build-on-tag.yml +2 -0
- package/.github/workflows/deploy-pages.yml +73 -0
- package/README.md +190 -0
- package/example/index.html +16 -22
- package/example/veil_core.d.ts +71 -0
- package/example/veil_core.js +370 -0
- package/example/veil_core_bg.wasm +0 -0
- package/example/veil_core_bg.wasm.d.ts +20 -0
- package/package.json +1 -1
- package/src/entropy/approximate.ts +3 -2
- package/src/entropy/complexity.ts +3 -2
- package/src/entropy/distribution.ts +3 -2
- package/src/entropy/spectral.ts +3 -2
- package/src/index.ts +92 -42
- package/src/stability.ts +405 -0
- package/src/tamper.ts +207 -0
- package/src/types.ts +11 -2
- package/src/wasm-loader.ts +32 -2
- package/tsup.config.ts +20 -14
- package/veil-core/src/lib.rs +3 -0
- package/veil-core/src/similarity.rs +67 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Deploy to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: "pages"
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pages: write
|
|
15
|
+
id-token: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- uses: pnpm/action-setup@v2
|
|
24
|
+
with:
|
|
25
|
+
version: 8
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: '20'
|
|
30
|
+
cache: 'pnpm'
|
|
31
|
+
|
|
32
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
33
|
+
|
|
34
|
+
- name: Install wasm-pack
|
|
35
|
+
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: pnpm install
|
|
39
|
+
|
|
40
|
+
- name: Build WASM
|
|
41
|
+
run: pnpm run build:wasm
|
|
42
|
+
|
|
43
|
+
- name: Build
|
|
44
|
+
run: pnpm run build
|
|
45
|
+
env:
|
|
46
|
+
# Add this if you need base path configuration
|
|
47
|
+
VITE_BASE_PATH: /veil/
|
|
48
|
+
|
|
49
|
+
- name: Copy dist to example
|
|
50
|
+
run: |
|
|
51
|
+
cp -r dist/* example/
|
|
52
|
+
touch example/.nojekyll
|
|
53
|
+
|
|
54
|
+
- name: Setup Pages
|
|
55
|
+
uses: actions/configure-pages@v3
|
|
56
|
+
|
|
57
|
+
- name: Upload artifact
|
|
58
|
+
uses: actions/upload-pages-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
path: 'example'
|
|
61
|
+
|
|
62
|
+
deploy:
|
|
63
|
+
environment:
|
|
64
|
+
name: github-pages
|
|
65
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs: build
|
|
68
|
+
steps:
|
|
69
|
+
- name: Deploy to GitHub Pages
|
|
70
|
+
id: deployment
|
|
71
|
+
uses: actions/deploy-pages@v4
|
|
72
|
+
|
|
73
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Veil
|
|
2
|
+
|
|
3
|
+
A deterministic browser fingerprinting library with mathematical rigor and tamper detection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Deterministic Hashing** - Consistent fingerprints across browser refreshes
|
|
8
|
+
- **Mathematical Rigor** - Information-theoretic entropy sources with Bayesian scoring
|
|
9
|
+
- **Tamper Detection** - Identifies suspicious patterns and anomalies in entropy data
|
|
10
|
+
- **Browser Compatible** - Works in modern browsers via WASM
|
|
11
|
+
- **Minimal Overhead** - Lightweight library with no external dependencies
|
|
12
|
+
- **Detailed Metrics** - Uniqueness, confidence, and per-source entropy analysis
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @cloudglides/veil
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @cloudglides/veil
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { getFingerprint } from "@cloudglides/veil";
|
|
26
|
+
|
|
27
|
+
// Simple hash
|
|
28
|
+
const hash = await getFingerprint();
|
|
29
|
+
console.log(hash); // e.g., "a3f5d8c..."
|
|
30
|
+
|
|
31
|
+
// Detailed analysis
|
|
32
|
+
const fingerprint = await getFingerprint({ detailed: true });
|
|
33
|
+
console.log(fingerprint.hash); // fingerprint hash
|
|
34
|
+
console.log(fingerprint.uniqueness); // 0-1 uniqueness score
|
|
35
|
+
console.log(fingerprint.confidence); // 0-1 confidence score
|
|
36
|
+
console.log(fingerprint.tampering_risk); // 0-1 tampering risk
|
|
37
|
+
console.log(fingerprint.anomalies); // array of detected anomalies
|
|
38
|
+
console.log(fingerprint.sources); // entropy sources and their contributions
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API Reference
|
|
42
|
+
|
|
43
|
+
### `getFingerprint(options?): Promise<string | FingerprintResponse>`
|
|
44
|
+
|
|
45
|
+
Generates a browser fingerprint.
|
|
46
|
+
|
|
47
|
+
**Options:**
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
interface FingerprintOptions {
|
|
51
|
+
entropy?: {
|
|
52
|
+
userAgent?: boolean; // default: true
|
|
53
|
+
canvas?: boolean; // default: true
|
|
54
|
+
webgl?: boolean; // default: true
|
|
55
|
+
fonts?: boolean; // default: true
|
|
56
|
+
storage?: boolean; // default: true
|
|
57
|
+
screen?: boolean; // default: true
|
|
58
|
+
};
|
|
59
|
+
hash?: "sha256" | "sha512"; // default: "sha256"
|
|
60
|
+
detailed?: boolean; // return full response (default: false)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Returns:**
|
|
65
|
+
|
|
66
|
+
- `string` - 64 or 128 character hash (if `detailed: false`)
|
|
67
|
+
- `FingerprintResponse` - Full analysis object (if `detailed: true`)
|
|
68
|
+
|
|
69
|
+
### `compareFingerpints(fp1, fp2): Promise<{ similarity: number; match: boolean }>`
|
|
70
|
+
|
|
71
|
+
Compares two fingerprints using Levenshtein distance.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const fp1 = await getFingerprint();
|
|
75
|
+
const fp2 = await getFingerprint();
|
|
76
|
+
const { similarity, match } = await compareFingerpints(fp1, fp2);
|
|
77
|
+
|
|
78
|
+
console.log(similarity); // 0-1, higher = more similar
|
|
79
|
+
console.log(match); // true if similarity > 0.95
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `matchProbability(stored, current): Promise<{ bestMatch: number; confidence: number }>`
|
|
83
|
+
|
|
84
|
+
Finds best match from a stored fingerprint set.
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
const currentFp = await getFingerprint();
|
|
88
|
+
const storedFps = [fp1, fp2, fp3];
|
|
89
|
+
|
|
90
|
+
const { bestMatch, confidence } = await matchProbability(storedFps, currentFp);
|
|
91
|
+
console.log(bestMatch); // 0-1 highest similarity
|
|
92
|
+
console.log(confidence); // probability estimate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Entropy Sources
|
|
96
|
+
|
|
97
|
+
Veil collects entropy from multiple sources:
|
|
98
|
+
|
|
99
|
+
- **userAgent** - Browser user agent string
|
|
100
|
+
- **canvas** - HTML5 canvas fingerprinting
|
|
101
|
+
- **webgl** - WebGL capabilities and renderer
|
|
102
|
+
- **fonts** - Installed system fonts
|
|
103
|
+
- **storage** - LocalStorage and SessionStorage
|
|
104
|
+
- **screen** - Display resolution and color depth
|
|
105
|
+
- **distribution** - KS-test on seeded random samples
|
|
106
|
+
- **complexity** - Lempel-Ziv complexity
|
|
107
|
+
- **spectral** - Spectral entropy analysis
|
|
108
|
+
- **approximate** - Approximate entropy
|
|
109
|
+
- **os** - Operating system detection
|
|
110
|
+
- **language** - Browser language preferences
|
|
111
|
+
- **timezone** - System timezone
|
|
112
|
+
- **hardware** - CPU cores and device memory
|
|
113
|
+
- **plugins** - Browser plugins/extensions
|
|
114
|
+
- **browser** - Browser vendor and version
|
|
115
|
+
- **adblock** - Adblock detection
|
|
116
|
+
- **webFeatures** - Supported web APIs
|
|
117
|
+
|
|
118
|
+
## Tamper Detection
|
|
119
|
+
|
|
120
|
+
Each fingerprint includes a `tampering_risk` score (0-1) and list of detected `anomalies`:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const fp = await getFingerprint({ detailed: true });
|
|
124
|
+
|
|
125
|
+
if (fp.tampering_risk > 0.5) {
|
|
126
|
+
console.warn("High tampering risk detected:", fp.anomalies);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Detected Anomalies:**
|
|
131
|
+
|
|
132
|
+
- Suspiciously uniform entropy across sources
|
|
133
|
+
- Duplicate values in multiple sources
|
|
134
|
+
- Missing critical entropy sources
|
|
135
|
+
- Cross-source inconsistencies (e.g., canvas/webgl mismatch)
|
|
136
|
+
- Suspicious placeholder values (null, fake, mock)
|
|
137
|
+
|
|
138
|
+
## Example
|
|
139
|
+
|
|
140
|
+
See `/example/index.html` for a working demo with UI controls.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Start local server
|
|
144
|
+
python3 -m http.server 8000
|
|
145
|
+
|
|
146
|
+
# Visit http://localhost:8000/example/
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## How It Works
|
|
150
|
+
|
|
151
|
+
1. **Entropy Collection** - Gathers browser characteristics from 25+ sources
|
|
152
|
+
2. **Hashing** - Combines entropy sources and applies SHA-256/512
|
|
153
|
+
3. **Scoring** - Calculates uniqueness via Bayesian methods
|
|
154
|
+
4. **Anomaly Detection** - Analyzes entropy distribution for tampering signs
|
|
155
|
+
5. **Output** - Returns fingerprint hash with optional detailed metrics
|
|
156
|
+
|
|
157
|
+
The library uses WASM (via Rust) for computationally intensive operations:
|
|
158
|
+
|
|
159
|
+
- Kolmogorov-Smirnov test (distribution analysis)
|
|
160
|
+
- Lempel-Ziv complexity
|
|
161
|
+
- Spectral entropy
|
|
162
|
+
- Approximate entropy
|
|
163
|
+
|
|
164
|
+
## Performance
|
|
165
|
+
|
|
166
|
+
- Generation: ~50-100ms (first call includes WASM init)
|
|
167
|
+
- Subsequent calls: ~20-30ms
|
|
168
|
+
- Bundle size: ~23KB (gzipped)
|
|
169
|
+
|
|
170
|
+
## Browser Support
|
|
171
|
+
|
|
172
|
+
- Chrome/Edge 90+
|
|
173
|
+
- Firefox 88+
|
|
174
|
+
- Safari 14+
|
|
175
|
+
- Any browser supporting:
|
|
176
|
+
- WebAssembly
|
|
177
|
+
- Web Crypto API
|
|
178
|
+
- Canvas API
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT - See LICENSE file
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
Contributions welcome. Please open an issue or PR on GitHub.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
**Disclaimer:** Browser fingerprinting has privacy implications. Use responsibly and ensure compliance with local regulations and user privacy expectations.
|
package/example/index.html
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
+
<!-- base href removed to fix module import paths -->
|
|
5
6
|
<title>Veil Fingerprinting Demo</title>
|
|
6
7
|
<style>
|
|
7
8
|
body {
|
|
@@ -96,14 +97,6 @@
|
|
|
96
97
|
<input type="checkbox" id="detailedCheckbox" checked />
|
|
97
98
|
Detailed output (JSON)
|
|
98
99
|
</label>
|
|
99
|
-
<label>
|
|
100
|
-
<input type="checkbox" id="cpuCheckbox" />
|
|
101
|
-
CPU benchmark
|
|
102
|
-
</label>
|
|
103
|
-
<label>
|
|
104
|
-
<input type="checkbox" id="gpuCheckbox" />
|
|
105
|
-
GPU benchmark
|
|
106
|
-
</label>
|
|
107
100
|
</div>
|
|
108
101
|
|
|
109
102
|
<div class="controls">
|
|
@@ -115,33 +108,26 @@
|
|
|
115
108
|
<div id="result">Click "Generate" to start...</div>
|
|
116
109
|
|
|
117
110
|
<script type="module">
|
|
118
|
-
|
|
111
|
+
let distPath = '../dist/index.js';
|
|
112
|
+
if (window.location.pathname.includes('/veil/')) {
|
|
113
|
+
distPath = '/veil/dist/index.js';
|
|
114
|
+
}
|
|
115
|
+
const { getFingerprint } = await import(distPath);
|
|
119
116
|
|
|
120
117
|
window.generateFingerprint = async () => {
|
|
121
118
|
const result = document.getElementById("result");
|
|
122
119
|
const detailed = document.getElementById("detailedCheckbox").checked;
|
|
123
|
-
const cpuBenchmark = document.getElementById("cpuCheckbox").checked;
|
|
124
|
-
const gpuBenchmark = document.getElementById("gpuCheckbox").checked;
|
|
125
120
|
|
|
126
121
|
result.innerHTML = '<div class="loading">Generating fingerprint...</div>';
|
|
127
122
|
console.log("Starting fingerprint generation");
|
|
128
|
-
console.log("Options:", { detailed
|
|
123
|
+
console.log("Options:", { detailed });
|
|
129
124
|
|
|
130
125
|
try {
|
|
131
126
|
const startTime = performance.now();
|
|
132
127
|
|
|
133
|
-
if (cpuBenchmark) {
|
|
134
|
-
console.log("Running CPU benchmark...");
|
|
135
|
-
}
|
|
136
|
-
if (gpuBenchmark) {
|
|
137
|
-
console.log("Running GPU benchmark...");
|
|
138
|
-
}
|
|
139
|
-
|
|
140
128
|
const fp = await getFingerprint({
|
|
141
129
|
hash: "sha256",
|
|
142
130
|
detailed,
|
|
143
|
-
cpuBenchmark,
|
|
144
|
-
gpuBenchmark,
|
|
145
131
|
});
|
|
146
132
|
|
|
147
133
|
const endTime = performance.now();
|
|
@@ -169,7 +155,15 @@
|
|
|
169
155
|
|
|
170
156
|
<strong>Metrics:</strong><br/>
|
|
171
157
|
<pre>Uniqueness: ${(fp.uniqueness * 100).toFixed(2)}%
|
|
172
|
-
Confidence: ${(fp.confidence * 100).toFixed(2)}
|
|
158
|
+
Confidence: ${(fp.confidence * 100).toFixed(2)}%
|
|
159
|
+
Tampering Risk: ${(fp.tampering_risk * 100).toFixed(2)}%</pre>
|
|
160
|
+
|
|
161
|
+
${fp.anomalies.length > 0 ? `
|
|
162
|
+
<strong>Anomalies Detected (${fp.anomalies.length}):</strong><br/>
|
|
163
|
+
<pre>${fp.anomalies.map(a =>
|
|
164
|
+
`[${a.severity.toUpperCase()}] ${a.id} (${a.category})\n${a.message}\nRisk: ${(a.riskContribution * 100).toFixed(0)}%\n`
|
|
165
|
+
).join("\n")}</pre>
|
|
166
|
+
` : ""}
|
|
173
167
|
|
|
174
168
|
<strong>System:</strong><br/>
|
|
175
169
|
<pre>OS: ${fp.system.os}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* tslint:disable */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
export function approx_entropy(samples: Float64Array, m: number): number;
|
|
5
|
+
|
|
6
|
+
export function cosine_similarity(v1: Float64Array, v2: Float64Array): number;
|
|
7
|
+
|
|
8
|
+
export function fnv_hash(s: string): string;
|
|
9
|
+
|
|
10
|
+
export function kolmogorov_complexity(s: string): number;
|
|
11
|
+
|
|
12
|
+
export function ks_test(values: Float64Array): number;
|
|
13
|
+
|
|
14
|
+
export function levenshtein_distance(s1: string, s2: string): number;
|
|
15
|
+
|
|
16
|
+
export function lz_complexity(data: Uint8Array): number;
|
|
17
|
+
|
|
18
|
+
export function murmur_hash(s: string): string;
|
|
19
|
+
|
|
20
|
+
export function sample_ent(samples: Float64Array, m: number, r: number): number;
|
|
21
|
+
|
|
22
|
+
export function shannon_entropy(s: string): number;
|
|
23
|
+
|
|
24
|
+
export function similarity_score(s1: string, s2: string): number;
|
|
25
|
+
|
|
26
|
+
export function spectral(samples: Float64Array): number;
|
|
27
|
+
|
|
28
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
29
|
+
|
|
30
|
+
export interface InitOutput {
|
|
31
|
+
readonly memory: WebAssembly.Memory;
|
|
32
|
+
readonly cosine_similarity: (a: number, b: number, c: number, d: number) => number;
|
|
33
|
+
readonly levenshtein_distance: (a: number, b: number, c: number, d: number) => number;
|
|
34
|
+
readonly similarity_score: (a: number, b: number, c: number, d: number) => number;
|
|
35
|
+
readonly approx_entropy: (a: number, b: number, c: number) => number;
|
|
36
|
+
readonly fnv_hash: (a: number, b: number) => [number, number];
|
|
37
|
+
readonly kolmogorov_complexity: (a: number, b: number) => number;
|
|
38
|
+
readonly ks_test: (a: number, b: number) => number;
|
|
39
|
+
readonly lz_complexity: (a: number, b: number) => number;
|
|
40
|
+
readonly sample_ent: (a: number, b: number, c: number, d: number) => number;
|
|
41
|
+
readonly shannon_entropy: (a: number, b: number) => number;
|
|
42
|
+
readonly spectral: (a: number, b: number) => number;
|
|
43
|
+
readonly murmur_hash: (a: number, b: number) => [number, number];
|
|
44
|
+
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
45
|
+
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
46
|
+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
47
|
+
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
48
|
+
readonly __wbindgen_start: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
55
|
+
* a precompiled `WebAssembly.Module`.
|
|
56
|
+
*
|
|
57
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
58
|
+
*
|
|
59
|
+
* @returns {InitOutput}
|
|
60
|
+
*/
|
|
61
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
65
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
66
|
+
*
|
|
67
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
68
|
+
*
|
|
69
|
+
* @returns {Promise<InitOutput>}
|
|
70
|
+
*/
|
|
71
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|