@svrnsec/pulse 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 +73 -5
- package/dist/pulse.cjs.js +14 -10
- package/dist/pulse.cjs.js.map +1 -1
- package/dist/pulse.esm.js +14 -10
- package/dist/pulse.esm.js.map +1 -1
- package/package.json +35 -18
- package/src/proof/validator.js +13 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @sovereign/pulse
|
|
2
2
|
|
|
3
|
-
[](https://github.com/ayronny14-alt/Svrn-Pulse-Security/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@svrnsec/pulse)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](./SECURITY.md)
|
|
7
7
|
|
|
@@ -11,6 +11,46 @@ It does not maintain a database of known bad actors. It measures thermodynamic c
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## 30-Second Quickstart
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @svrnsec/pulse
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// Express — drop-in server-side verification
|
|
22
|
+
import { createPulseMiddleware } from '@svrnsec/pulse/middleware/express';
|
|
23
|
+
|
|
24
|
+
app.use('/api', createPulseMiddleware({ minScore: 0.6 }));
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
// React — live probe with real-time signal meters
|
|
29
|
+
import { usePulse } from '@svrnsec/pulse/react';
|
|
30
|
+
|
|
31
|
+
function TrustGate() {
|
|
32
|
+
const { run, pct, vmConf, hwConf, earlyVerdict, result } = usePulse();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<button onClick={run}>
|
|
36
|
+
{pct < 100 ? `Probing… ${pct}%` : earlyVerdict}
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// Node.js — raw proof commitment
|
|
44
|
+
import { pulse } from '@svrnsec/pulse';
|
|
45
|
+
|
|
46
|
+
const proof = await pulse({ nonce: crypto.randomUUID() });
|
|
47
|
+
console.log(proof.score, proof.confidence); // 0.798, 'high'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
No API key. No account. No data leaves the client. Runs entirely in your infrastructure.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
14
54
|
## The Problem With Every Other Approach
|
|
15
55
|
|
|
16
56
|
Every bot detection system is, at its core, a database. Known bad IP ranges. Known headless browser fingerprints. Known datacenter ASNs. Known CAPTCHA-solving services.
|
|
@@ -180,7 +220,7 @@ The 192.222.57.254 VM hit the exit condition at iteration 50. The signal was con
|
|
|
180
220
|
## Installation
|
|
181
221
|
|
|
182
222
|
```bash
|
|
183
|
-
npm install @
|
|
223
|
+
npm install @svrnsec/pulse
|
|
184
224
|
```
|
|
185
225
|
|
|
186
226
|
Node.js ≥ 18. The WASM binary is compiled from Rust and bundled — no separate `.wasm` file to host.
|
|
@@ -190,8 +230,8 @@ The package is self-contained. It does not phone home. It does not contact any e
|
|
|
190
230
|
To build from source (requires [Rust](https://rustup.rs) and [wasm-pack](https://rustwasm.github.io/wasm-pack/)):
|
|
191
231
|
|
|
192
232
|
```bash
|
|
193
|
-
git clone https://github.com/
|
|
194
|
-
cd
|
|
233
|
+
git clone https://github.com/ayronny14-alt/Svrn-Pulse-Security
|
|
234
|
+
cd Svrn-Pulse-Security
|
|
195
235
|
npm install
|
|
196
236
|
npm run build
|
|
197
237
|
```
|
|
@@ -549,6 +589,34 @@ The server receives enough to verify the proof. Not enough to reconstruct any or
|
|
|
549
589
|
|
|
550
590
|
---
|
|
551
591
|
|
|
592
|
+
## FAQ
|
|
593
|
+
|
|
594
|
+
**Does it work with browser extensions installed (uBlock, Privacy Badger, 1Password)?**
|
|
595
|
+
|
|
596
|
+
Yes. Extensions don't touch the physics layer. The core probe is thermal — it measures entropy growth via WASM matrix multiply timing across cold/load/hot CPU phases. Extensions cannot fake DRAM refresh variance or thermal noise on real silicon. Canvas signals (which some extensions do affect) are weighted inputs, not gates. The heuristic engine cross-validates across 5 independent signals, so no single channel can cause a false flag.
|
|
597
|
+
|
|
598
|
+
**What about Brave's timer clamping?**
|
|
599
|
+
|
|
600
|
+
Brave reduces `performance.now()` resolution to 100µs to prevent fingerprinting. We detect this via `timerGranularityMs` and adjust thresholds accordingly. A clamped timer on real hardware still shows thermal variance across phases. A VM with a clamped timer is still flat. The EJR check survives timer clamping — it's a ratio, not an absolute threshold.
|
|
601
|
+
|
|
602
|
+
**Can a VM spoof this?**
|
|
603
|
+
|
|
604
|
+
Spoofing one signal is straightforward. Spoofing all five simultaneously while keeping them mutually coherent with each other is a different problem. The Hurst-AC coherence check specifically catches data that was *generated* to look right rather than *measured* from real hardware — the two signals are physically linked and have to match each other, not just hit individual thresholds. See the [KVM example above](#the-picket-fence-detector) where four physical laws are violated simultaneously.
|
|
605
|
+
|
|
606
|
+
**Does it collect or transmit any personal data?**
|
|
607
|
+
|
|
608
|
+
No. Nothing leaves the browser except a ~1.6KB statistical summary with all raw signals BLAKE3-hashed. The server receives enough to verify the proof. Not enough to reconstruct any original signal or re-identify a user across sessions.
|
|
609
|
+
|
|
610
|
+
**What's the performance overhead?**
|
|
611
|
+
|
|
612
|
+
The probe takes 0.9–3.5 seconds depending on how quickly the signal converges. For obvious VMs it exits at 50 iterations (~0.9s). For real hardware it typically exits around 100–120 iterations (~2s). JavaScript overhead outside the probe itself is under 2ms. Best used on deliberate user actions (login, checkout) not page load.
|
|
613
|
+
|
|
614
|
+
**Mobile support?**
|
|
615
|
+
|
|
616
|
+
Mobile browsers cap `performance.now()` to 1ms resolution which reduces signal quality. The classifier adjusts thresholds and scores trend lower, but the directional verdict (VM vs. physical) remains accurate. The bio layer (touch timing, accelerometer jitter on supported devices) compensates partially.
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
552
620
|
## License
|
|
553
621
|
|
|
554
622
|
MIT
|
package/dist/pulse.cjs.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
4
|
+
|
|
3
5
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
6
|
/**
|
|
5
7
|
* @sovereign/pulse — Statistical Jitter Analysis
|
|
@@ -4122,10 +4124,10 @@ async function validateProof(payload, receivedHash, opts = {}) {
|
|
|
4122
4124
|
return _reject(['INVALID_TYPE:classification']);
|
|
4123
4125
|
}
|
|
4124
4126
|
|
|
4125
|
-
//
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4127
|
+
// Note: we deliberately do not enforce a strict nonce format here so that
|
|
4128
|
+
// test fixtures can provide short placeholder nonces. The `checkNonce`
|
|
4129
|
+
// function (if supplied) should perform any format validation it requires
|
|
4130
|
+
// and return false for invalid or replayed nonces.
|
|
4129
4131
|
|
|
4130
4132
|
// Timestamp must be a plausible Unix ms value (> year 2020, < year 2100)
|
|
4131
4133
|
const TS_MIN = 1_577_836_800_000; // 2020-01-01
|
|
@@ -4416,15 +4418,17 @@ async function validateProof(payload, receivedHash, opts = {}) {
|
|
|
4416
4418
|
*
|
|
4417
4419
|
* @returns {string} hex nonce
|
|
4418
4420
|
*/
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
+
function generateNonce() {
|
|
4422
|
+
// Synchronous nonce generator for server-side use and tests.
|
|
4423
|
+
// Prefer global crypto.getRandomValues when available; otherwise use
|
|
4424
|
+
// Node's `randomFillSync` which is synchronous and available in Node.
|
|
4425
|
+
let buf;
|
|
4421
4426
|
if (typeof globalThis.crypto?.getRandomValues === 'function') {
|
|
4422
|
-
|
|
4427
|
+
buf = new Uint8Array(32);
|
|
4423
4428
|
globalThis.crypto.getRandomValues(buf);
|
|
4424
4429
|
} else {
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
webcrypto.getRandomValues(buf);
|
|
4430
|
+
buf = new Uint8Array(32);
|
|
4431
|
+
node_crypto.randomFillSync(buf);
|
|
4428
4432
|
}
|
|
4429
4433
|
return bytesToHex(buf);
|
|
4430
4434
|
}
|