@svrnsec/pulse 0.8.0 → 0.9.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/SECURITY.md +91 -86
- package/dist/pulse.cjs +56 -0
- package/dist/pulse.cjs.map +1 -1
- package/dist/pulse.esm.js +56 -1
- package/dist/pulse.esm.js.map +1 -1
- package/index.d.ts +54 -0
- package/package.json +5 -1
- package/src/errors.js +54 -0
- package/src/index.js +3 -0
package/SECURITY.md
CHANGED
|
@@ -1,86 +1,91 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
`@svrnsec/pulse` is a hardware-physics fingerprinting library used as a security layer.
|
|
6
|
-
We take vulnerabilities seriously and will respond promptly.
|
|
7
|
-
|
|
8
|
-
## Supported Versions
|
|
9
|
-
|
|
10
|
-
| Version | Supported |
|
|
11
|
-
| ------- | ------------------ |
|
|
12
|
-
| 0.
|
|
13
|
-
| < 0.
|
|
14
|
-
|
|
15
|
-
## Threat Model
|
|
16
|
-
|
|
17
|
-
**What pulse protects against:**
|
|
18
|
-
- Automated bots running in cloud VMs / Docker containers with no real hardware
|
|
19
|
-
- Headless browser automation (Puppeteer, Playwright) on virtual machines
|
|
20
|
-
- Credential-stuffing and account-takeover attacks from datacenter IP ranges
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
##
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- [ ]
|
|
81
|
-
- [ ] Set `
|
|
82
|
-
- [ ]
|
|
83
|
-
- [ ]
|
|
84
|
-
- [ ] Set `
|
|
85
|
-
- [ ]
|
|
86
|
-
- [ ]
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@svrnsec/pulse` is a hardware-physics fingerprinting library used as a security layer.
|
|
6
|
+
We take vulnerabilities seriously and will respond promptly.
|
|
7
|
+
|
|
8
|
+
## Supported Versions
|
|
9
|
+
|
|
10
|
+
| Version | Supported |
|
|
11
|
+
| ------- | ------------------ |
|
|
12
|
+
| 0.8.x | :white_check_mark: Security fixes + active development |
|
|
13
|
+
| < 0.8 | :x: Deprecated — critical security vulnerabilities. Upgrade immediately. |
|
|
14
|
+
|
|
15
|
+
## Threat Model
|
|
16
|
+
|
|
17
|
+
**What pulse protects against:**
|
|
18
|
+
- Automated bots running in cloud VMs / Docker containers with no real hardware
|
|
19
|
+
- Headless browser automation (Puppeteer, Playwright) on virtual machines
|
|
20
|
+
- Credential-stuffing and account-takeover attacks from datacenter IP ranges
|
|
21
|
+
- Click farms (via proof-of-idle thermal cooling analysis)
|
|
22
|
+
- LLM-controlled browser agents (via behavioral biometrics)
|
|
23
|
+
- Coordinated inauthentic behavior (via population-level statistical analysis)
|
|
24
|
+
- Proof replay attacks (via HMAC-signed challenges + atomic nonce consumption)
|
|
25
|
+
- Payload tampering (via BLAKE3 commitment integrity)
|
|
26
|
+
|
|
27
|
+
**What pulse does NOT claim to protect against:**
|
|
28
|
+
- A determined human attacker on real consumer hardware
|
|
29
|
+
- A physical device farm where each device genuinely cools between interactions
|
|
30
|
+
- Kernel-level hooks that spoof `performance.now()` at nanosecond precision
|
|
31
|
+
- Server-side replay attacks when `checkNonce` is not wired (always wire it)
|
|
32
|
+
- GPU passthrough VMs with native hardware clock access
|
|
33
|
+
|
|
34
|
+
## Reporting a Vulnerability
|
|
35
|
+
|
|
36
|
+
**Please do NOT open a public GitHub issue for security vulnerabilities.**
|
|
37
|
+
|
|
38
|
+
Report security issues via email:
|
|
39
|
+
|
|
40
|
+
> **security@sovereign.dev** *(or open a private GitHub Security Advisory)*
|
|
41
|
+
|
|
42
|
+
### What to include
|
|
43
|
+
|
|
44
|
+
1. A description of the vulnerability and the expected vs. actual behavior
|
|
45
|
+
2. Steps to reproduce (PoC code, scripts, or screenshots)
|
|
46
|
+
3. The impact — what can an attacker achieve?
|
|
47
|
+
4. Any suggested mitigation or fix
|
|
48
|
+
|
|
49
|
+
### Response SLA
|
|
50
|
+
|
|
51
|
+
| Severity | Initial response | Target fix |
|
|
52
|
+
|----------|-----------------|------------|
|
|
53
|
+
| Critical | 24 hours | 7 days |
|
|
54
|
+
| High | 48 hours | 14 days |
|
|
55
|
+
| Medium | 5 business days | 30 days |
|
|
56
|
+
| Low | 10 business days| Next minor |
|
|
57
|
+
|
|
58
|
+
We follow [coordinated disclosure](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure).
|
|
59
|
+
You will receive credit in the changelog unless you prefer to remain anonymous.
|
|
60
|
+
|
|
61
|
+
## Cryptographic Primitives
|
|
62
|
+
|
|
63
|
+
- **Hashing**: BLAKE3 via `@noble/hashes` — audited, constant-time implementation
|
|
64
|
+
- **Challenge signing**: HMAC-SHA256 over `nonce|issuedAt|expiresAt` with timing-safe comparison
|
|
65
|
+
- **Engagement tokens**: HMAC-SHA256 over fraud-relevant fields with 30-second TTL
|
|
66
|
+
- **Nonce generation**: `crypto.getRandomValues()` / Node.js `webcrypto` — 256 bits of entropy
|
|
67
|
+
- **Webhook signatures**: HMAC-SHA256 — standard authenticated integrity check
|
|
68
|
+
- **API key comparison**: `crypto.timingSafeEqual` — constant-time to prevent timing attacks
|
|
69
|
+
|
|
70
|
+
## Privacy
|
|
71
|
+
|
|
72
|
+
- Raw timing arrays never leave the device — server receives only a ~1.6 KB statistical summary
|
|
73
|
+
- Mouse coordinates are never stored — only timing deltas between events
|
|
74
|
+
- Keystrokes capture only dwell/flight times — key labels are discarded immediately
|
|
75
|
+
- `hardwareId()` is a 128-bit BLAKE3 hash — stable per device, not reversible, not cross-origin linkable
|
|
76
|
+
- No IP addresses are logged by default — integrators should implement their own IP handling policy
|
|
77
|
+
|
|
78
|
+
## Secure Deployment Checklist
|
|
79
|
+
|
|
80
|
+
- [ ] Set `NODE_ENV=production` to enforce secret validation at startup
|
|
81
|
+
- [ ] Set `PULSE_CHALLENGE_SECRET` to a cryptographically random 64-char hex string
|
|
82
|
+
- [ ] Set `WEBHOOK_SECRET` to a separate cryptographically random string
|
|
83
|
+
- [ ] Wire `checkNonce` to Redis `DEL` (returns 1 on first use) for atomic replay prevention
|
|
84
|
+
- [ ] Set `REDIS_URL` for multi-instance deployments (in-memory store is single-instance only)
|
|
85
|
+
- [ ] Put the API server behind TLS (nginx / Caddy / ALB)
|
|
86
|
+
- [ ] Set `CORS_ORIGINS` to your exact domain — not `*`
|
|
87
|
+
- [ ] Set `minJitterScore` >= 0.65 for high-value endpoints
|
|
88
|
+
- [ ] Monitor `riskFlags` in webhook payloads for anomaly detection
|
|
89
|
+
- [ ] Use `/health/ready` (not `/health`) for load balancer health checks
|
|
90
|
+
- [ ] Rotate `PULSE_API_KEYS` regularly; use different keys per environment
|
|
91
|
+
- [ ] Review `webhook.dead_letter` log events for delivery failures
|
package/dist/pulse.cjs
CHANGED
|
@@ -6313,6 +6313,61 @@ function renderInlineUpdateHint(latest) {
|
|
|
6313
6313
|
);
|
|
6314
6314
|
}
|
|
6315
6315
|
|
|
6316
|
+
/**
|
|
6317
|
+
* @svrnsec/pulse — Structured Error Codes
|
|
6318
|
+
*
|
|
6319
|
+
* Use these constants instead of string matching for type-safe error handling.
|
|
6320
|
+
*/
|
|
6321
|
+
const PulseErrorCode = Object.freeze({
|
|
6322
|
+
// Structural
|
|
6323
|
+
INVALID_PAYLOAD_STRUCTURE: 'INVALID_PAYLOAD_STRUCTURE',
|
|
6324
|
+
PROTOTYPE_POLLUTION_ATTEMPT: 'PROTOTYPE_POLLUTION_ATTEMPT',
|
|
6325
|
+
MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
|
|
6326
|
+
INVALID_TYPE: 'INVALID_TYPE',
|
|
6327
|
+
TIMESTAMP_OUT_OF_RANGE: 'TIMESTAMP_OUT_OF_RANGE',
|
|
6328
|
+
UNSUPPORTED_PROOF_VERSION: 'UNSUPPORTED_PROOF_VERSION',
|
|
6329
|
+
|
|
6330
|
+
// Hash integrity
|
|
6331
|
+
INVALID_HASH_FORMAT: 'INVALID_HASH_FORMAT',
|
|
6332
|
+
HASH_MISMATCH_PAYLOAD_TAMPERED: 'HASH_MISMATCH_PAYLOAD_TAMPERED',
|
|
6333
|
+
|
|
6334
|
+
// Timestamp / freshness
|
|
6335
|
+
PROOF_EXPIRED: 'PROOF_EXPIRED',
|
|
6336
|
+
PROOF_FROM_FUTURE: 'PROOF_FROM_FUTURE',
|
|
6337
|
+
NONCE_INVALID_OR_REPLAYED: 'NONCE_INVALID_OR_REPLAYED',
|
|
6338
|
+
|
|
6339
|
+
// Jitter / scoring
|
|
6340
|
+
JITTER_SCORE_TOO_LOW: 'JITTER_SCORE_TOO_LOW',
|
|
6341
|
+
DYNAMIC_THRESHOLD_NOT_MET: 'DYNAMIC_THRESHOLD_NOT_MET',
|
|
6342
|
+
|
|
6343
|
+
// Heuristic / coherence overrides
|
|
6344
|
+
HEURISTIC_HARD_OVERRIDE: 'HEURISTIC_HARD_OVERRIDE',
|
|
6345
|
+
COHERENCE_HARD_OVERRIDE: 'COHERENCE_HARD_OVERRIDE',
|
|
6346
|
+
|
|
6347
|
+
// Renderer
|
|
6348
|
+
SOFTWARE_RENDERER_DETECTED: 'SOFTWARE_RENDERER_DETECTED',
|
|
6349
|
+
BLOCKLISTED_RENDERER: 'BLOCKLISTED_RENDERER',
|
|
6350
|
+
|
|
6351
|
+
// Bio activity
|
|
6352
|
+
NO_BIO_ACTIVITY_DETECTED: 'NO_BIO_ACTIVITY_DETECTED',
|
|
6353
|
+
|
|
6354
|
+
// Cross-signal forgery detection
|
|
6355
|
+
FORGED_SIGNAL_CV_SCORE_IMPOSSIBLE: 'FORGED_SIGNAL:CV_SCORE_IMPOSSIBLE',
|
|
6356
|
+
FORGED_SIGNAL_AUTOCORR_SCORE_IMPOSSIBLE: 'FORGED_SIGNAL:AUTOCORR_SCORE_IMPOSSIBLE',
|
|
6357
|
+
FORGED_SIGNAL_QE_SCORE_IMPOSSIBLE: 'FORGED_SIGNAL:QE_SCORE_IMPOSSIBLE',
|
|
6358
|
+
|
|
6359
|
+
// Risk flags (informational, not rejection reasons)
|
|
6360
|
+
NONCE_FRESHNESS_NOT_CHECKED: 'NONCE_FRESHNESS_NOT_CHECKED',
|
|
6361
|
+
CANVAS_UNAVAILABLE: 'CANVAS_UNAVAILABLE',
|
|
6362
|
+
ZERO_BIO_SAMPLES: 'ZERO_BIO_SAMPLES',
|
|
6363
|
+
NEGATIVE_INTERFERENCE_COEFFICIENT: 'NEGATIVE_INTERFERENCE_COEFFICIENT',
|
|
6364
|
+
INCONSISTENCY_LOW_CV_BUT_HIGH_SCORE: 'INCONSISTENCY:LOW_CV_BUT_HIGH_SCORE',
|
|
6365
|
+
SUSPICIOUS_ZERO_TIMER_GRANULARITY: 'SUSPICIOUS_ZERO_TIMER_GRANULARITY',
|
|
6366
|
+
INCONSISTENCY_FLAT_THERMAL_BUT_HIGH_SCORE: 'INCONSISTENCY:FLAT_THERMAL_BUT_HIGH_SCORE',
|
|
6367
|
+
EXTREME_HURST: 'EXTREME_HURST',
|
|
6368
|
+
AUDIO_JITTER_TOO_FLAT: 'AUDIO_JITTER_TOO_FLAT',
|
|
6369
|
+
});
|
|
6370
|
+
|
|
6316
6371
|
/**
|
|
6317
6372
|
* @svrnsec/pulse
|
|
6318
6373
|
*
|
|
@@ -6785,6 +6840,7 @@ var pulse_core = /*#__PURE__*/Object.freeze({
|
|
|
6785
6840
|
|
|
6786
6841
|
exports.CURRENT_VERSION = CURRENT_VERSION;
|
|
6787
6842
|
exports.Fingerprint = Fingerprint;
|
|
6843
|
+
exports.PulseErrorCode = PulseErrorCode;
|
|
6788
6844
|
exports.checkForUpdate = checkForUpdate;
|
|
6789
6845
|
exports.collectDramTimings = collectDramTimings;
|
|
6790
6846
|
exports.collectEnfTimings = collectEnfTimings;
|