@svrnsec/pulse 0.6.0 → 0.8.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.
Files changed (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +883 -622
  3. package/SECURITY.md +86 -86
  4. package/bin/svrnsec-pulse.js +7 -7
  5. package/dist/{pulse.cjs.js → pulse.cjs} +6379 -6420
  6. package/dist/pulse.cjs.map +1 -0
  7. package/dist/pulse.esm.js +6380 -6421
  8. package/dist/pulse.esm.js.map +1 -1
  9. package/index.d.ts +895 -846
  10. package/package.json +185 -165
  11. package/pkg/pulse_core.js +174 -173
  12. package/src/analysis/audio.js +213 -213
  13. package/src/analysis/authenticityAudit.js +408 -390
  14. package/src/analysis/coherence.js +502 -502
  15. package/src/analysis/coordinatedBehavior.js +825 -0
  16. package/src/analysis/heuristic.js +428 -428
  17. package/src/analysis/jitter.js +446 -446
  18. package/src/analysis/llm.js +473 -472
  19. package/src/analysis/populationEntropy.js +404 -403
  20. package/src/analysis/provider.js +248 -248
  21. package/src/analysis/refraction.js +392 -0
  22. package/src/analysis/trustScore.js +356 -356
  23. package/src/cli/args.js +36 -36
  24. package/src/cli/commands/scan.js +192 -192
  25. package/src/cli/runner.js +157 -157
  26. package/src/collector/adaptive.js +200 -200
  27. package/src/collector/bio.js +297 -287
  28. package/src/collector/canvas.js +247 -239
  29. package/src/collector/dram.js +203 -203
  30. package/src/collector/enf.js +311 -311
  31. package/src/collector/entropy.js +195 -195
  32. package/src/collector/gpu.js +248 -245
  33. package/src/collector/idleAttestation.js +480 -480
  34. package/src/collector/sabTimer.js +189 -191
  35. package/src/fingerprint.js +475 -475
  36. package/src/index.js +342 -342
  37. package/src/integrations/react-native.js +462 -459
  38. package/src/integrations/react.js +184 -185
  39. package/src/middleware/express.js +155 -155
  40. package/src/middleware/next.js +174 -175
  41. package/src/proof/challenge.js +249 -249
  42. package/src/proof/engagementToken.js +426 -394
  43. package/src/proof/fingerprint.js +268 -268
  44. package/src/proof/validator.js +83 -143
  45. package/src/registry/serializer.js +349 -349
  46. package/src/terminal.js +263 -263
  47. package/src/update-notifier.js +259 -264
  48. package/dist/pulse.cjs.js.map +0 -1
@@ -1,175 +1,174 @@
1
- /**
2
- * @sovereign/pulse — Next.js App Router Middleware
3
- *
4
- * Works with Next.js App Router (13+) and Edge Runtime.
5
- *
6
- * ── Route Handler wrapper ──────────────────────────────────────────────────
7
- *
8
- * // app/api/checkout/route.js
9
- * import { withPulse } from '@sovereign/pulse/middleware/next';
10
- *
11
- * export const POST = withPulse({ threshold: 0.6 })(
12
- * async (req) => {
13
- * const { score, provider } = req.pulse;
14
- * return Response.json({ ok: true, score });
15
- * }
16
- * );
17
- *
18
- * ── Challenge endpoint (copy-paste ready) ─────────────────────────────────
19
- *
20
- * // app/api/pulse/challenge/route.js
21
- * import { pulseChallenge } from '@sovereign/pulse/middleware/next';
22
- * export const GET = pulseChallenge();
23
- *
24
- * ── Edge-compatible nonce store ────────────────────────────────────────────
25
- *
26
- * // Uses in-memory by default. For multi-instance deployments, provide
27
- * // a KV store (Vercel KV, Cloudflare KV, Redis via fetch):
28
- *
29
- * import { kv } from '@vercel/kv';
30
- * export const POST = withPulse({
31
- * threshold: 0.6,
32
- * store: {
33
- * set: (k, ttl) => kv.set(k, '1', { ex: ttl }),
34
- * consume: (k) => kv.del(k).then(n => n === 1),
35
- * },
36
- * })(handler);
37
- */
38
-
39
- import { validateProof, generateNonce } from '../proof/validator.js';
40
-
41
- // ---------------------------------------------------------------------------
42
- // Shared in-memory nonce store (single instance / dev only)
43
- // ---------------------------------------------------------------------------
44
-
45
- const _memStore = new Map();
46
-
47
- function memoryStore(ttlSec) {
48
- return {
49
- set(key) {
50
- _memStore.set(key, Date.now() + ttlSec * 1000);
51
- },
52
- consume(key) {
53
- const exp = _memStore.get(key);
54
- if (!exp || Date.now() > exp) return false;
55
- _memStore.delete(key);
56
- return true;
57
- },
58
- };
59
- }
60
-
61
- // ---------------------------------------------------------------------------
62
- // pulseChallenge — GET /api/pulse/challenge
63
- // ---------------------------------------------------------------------------
64
-
65
- /**
66
- * @param {object} [opts]
67
- * @param {number} [opts.ttl=300] - nonce TTL in seconds
68
- * @param {object} [opts.store] - custom nonce store
69
- */
70
- export function pulseChallenge(opts = {}) {
71
- const { ttl = 300, store } = opts;
72
- const _store = store ?? memoryStore(ttl);
73
-
74
- return async function GET() {
75
- const nonce = generateNonce();
76
- await _store.set(`pulse:${nonce}`);
77
- return Response.json({ nonce, expiresIn: ttl });
78
- };
79
- }
80
-
81
- // ---------------------------------------------------------------------------
82
- // withPulse — wraps a Next.js route handler
83
- // ---------------------------------------------------------------------------
84
-
85
- /**
86
- * @param {object} opts
87
- * @param {number} [opts.threshold=0.55]
88
- * @param {number} [opts.ttl=300] - nonce TTL
89
- * @param {boolean} [opts.requireBio=false]
90
- * @param {boolean} [opts.blockSoftwareRenderer=true]
91
- * @param {object} [opts.store] - custom nonce store
92
- * @returns {(handler: Function) => Function} - HOC
93
- */
94
- export function withPulse(opts = {}) {
95
- const {
96
- threshold = 0.55,
97
- ttl = 300,
98
- requireBio = false,
99
- blockSoftwareRenderer = true,
100
- store,
101
- } = opts;
102
-
103
- const _store = store ?? memoryStore(ttl);
104
-
105
- return function wrap(handler) {
106
- return async function wrappedHandler(req, ...args) {
107
- // ── Read proof from headers (preferred) or body ──────────────────────
108
- const proofHeader = req.headers.get('x-pulse-proof');
109
- const hashHeader = req.headers.get('x-pulse-hash');
110
-
111
- let payload, hash;
112
-
113
- if (proofHeader) {
114
- try { payload = JSON.parse(proofHeader); }
115
- catch { return _err(400, 'MALFORMED_PROOF', 'Could not parse x-pulse-proof header'); }
116
- hash = hashHeader;
117
- } else {
118
- // Attempt to read from JSON body (non-streaming)
119
- try {
120
- const body = await req.clone().json();
121
- payload = body.pulsePayload;
122
- hash = body.pulseHash;
123
- } catch {}
124
- }
125
-
126
- if (!payload || !hash) {
127
- return _err(401, 'MISSING_PROOF',
128
- 'Provide pulse proof via x-pulse-proof + x-pulse-hash headers, ' +
129
- 'or pulsePayload + pulseHash in the request body.'
130
- );
131
- }
132
-
133
- // ── Validate ───────────────────────────────────────────────────────────
134
- let result;
135
- try {
136
- result = await validateProof(payload, hash, {
137
- minJitterScore: threshold,
138
- requireBio,
139
- blockSoftwareRenderer,
140
- checkNonce: async (n) => _store.consume(`pulse:${n}`),
141
- });
142
- } catch (err) {
143
- console.error('[pulse] validateProof error:', err);
144
- return _err(500, 'VALIDATION_ERROR', 'Internal error during proof validation');
145
- }
146
-
147
- if (!result.valid) {
148
- return Response.json(
149
- { error: 'PULSE_REJECTED', reasons: result.reasons, riskFlags: result.riskFlags },
150
- { status: 403 }
151
- );
152
- }
153
-
154
- // ── Attach pulse result and call the real handler ──────────────────────
155
- // Next.js Request is immutable so we inject via a lightweight proxy
156
- const enriched = new Proxy(req, {
157
- get(target, prop) {
158
- if (prop === 'pulse') return result;
159
- const val = target[prop];
160
- return typeof val === 'function' ? val.bind(target) : val;
161
- },
162
- });
163
-
164
- return handler(enriched, ...args);
165
- };
166
- };
167
- }
168
-
169
- // ---------------------------------------------------------------------------
170
- // Internal helpers
171
- // ---------------------------------------------------------------------------
172
-
173
- function _err(status, code, message) {
174
- return Response.json({ error: code, message }, { status });
175
- }
1
+ /**
2
+ * @svrnsec/pulse — Next.js App Router Middleware
3
+ *
4
+ * Works with Next.js App Router (13+) and Edge Runtime.
5
+ *
6
+ * ── Route Handler wrapper ──────────────────────────────────────────────────
7
+ *
8
+ * // app/api/checkout/route.js
9
+ * import { withPulse } from '@svrnsec/pulse/middleware/next';
10
+ *
11
+ * export const POST = withPulse({ threshold: 0.6 })(
12
+ * async (req) => {
13
+ * const { score, provider } = req.pulse;
14
+ * return Response.json({ ok: true, score });
15
+ * }
16
+ * );
17
+ *
18
+ * ── Challenge endpoint (copy-paste ready) ─────────────────────────────────
19
+ *
20
+ * // app/api/pulse/challenge/route.js
21
+ * import { pulseChallenge } from '@svrnsec/pulse/middleware/next';
22
+ * export const GET = pulseChallenge();
23
+ *
24
+ * ── Edge-compatible nonce store ────────────────────────────────────────────
25
+ *
26
+ * // Uses in-memory by default. For multi-instance deployments, provide
27
+ * // a KV store (Vercel KV, Cloudflare KV, Redis via fetch):
28
+ *
29
+ * import { kv } from '@vercel/kv';
30
+ * export const POST = withPulse({
31
+ * threshold: 0.6,
32
+ * store: {
33
+ * set: (k, ttl) => kv.set(k, '1', { ex: ttl }),
34
+ * consume: (k) => kv.del(k).then(n => n === 1),
35
+ * },
36
+ * })(handler);
37
+ */
38
+
39
+ import { validateProof, generateNonce } from '../proof/validator.js';
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Shared in-memory nonce store (single instance / dev only)
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function memoryStore(ttlSec) {
46
+ const _memStore = new Map();
47
+ return {
48
+ set(key) {
49
+ _memStore.set(key, Date.now() + ttlSec * 1000);
50
+ },
51
+ consume(key) {
52
+ const exp = _memStore.get(key);
53
+ if (!exp || Date.now() > exp) return false;
54
+ _memStore.delete(key);
55
+ return true;
56
+ },
57
+ };
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // pulseChallenge — GET /api/pulse/challenge
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /**
65
+ * @param {object} [opts]
66
+ * @param {number} [opts.ttl=300] - nonce TTL in seconds
67
+ * @param {object} [opts.store] - custom nonce store
68
+ */
69
+ export function pulseChallenge(opts = {}) {
70
+ const { ttl = 300, store } = opts;
71
+ const _store = store ?? memoryStore(ttl);
72
+
73
+ return async function GET() {
74
+ const nonce = generateNonce();
75
+ await _store.set(`pulse:${nonce}`);
76
+ return Response.json({ nonce, expiresIn: ttl });
77
+ };
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // withPulse — wraps a Next.js route handler
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /**
85
+ * @param {object} opts
86
+ * @param {number} [opts.threshold=0.55]
87
+ * @param {number} [opts.ttl=300] - nonce TTL
88
+ * @param {boolean} [opts.requireBio=false]
89
+ * @param {boolean} [opts.blockSoftwareRenderer=true]
90
+ * @param {object} [opts.store] - custom nonce store
91
+ * @returns {(handler: Function) => Function} - HOC
92
+ */
93
+ export function withPulse(opts = {}) {
94
+ const {
95
+ threshold = 0.55,
96
+ ttl = 300,
97
+ requireBio = false,
98
+ blockSoftwareRenderer = true,
99
+ store,
100
+ } = opts;
101
+
102
+ const _store = store ?? memoryStore(ttl);
103
+
104
+ return function wrap(handler) {
105
+ return async function wrappedHandler(req, ...args) {
106
+ // ── Read proof from headers (preferred) or body ──────────────────────
107
+ const proofHeader = req.headers.get('x-pulse-proof');
108
+ const hashHeader = req.headers.get('x-pulse-hash');
109
+
110
+ let payload, hash;
111
+
112
+ if (proofHeader) {
113
+ try { payload = JSON.parse(proofHeader); }
114
+ catch { return _err(400, 'MALFORMED_PROOF', 'Could not parse x-pulse-proof header'); }
115
+ hash = hashHeader;
116
+ } else {
117
+ // Attempt to read from JSON body (non-streaming)
118
+ try {
119
+ const body = await req.clone().json();
120
+ payload = body.pulsePayload;
121
+ hash = body.pulseHash;
122
+ } catch {}
123
+ }
124
+
125
+ if (!payload || !hash) {
126
+ return _err(401, 'MISSING_PROOF',
127
+ 'Provide pulse proof via x-pulse-proof + x-pulse-hash headers, ' +
128
+ 'or pulsePayload + pulseHash in the request body.'
129
+ );
130
+ }
131
+
132
+ // ── Validate ───────────────────────────────────────────────────────────
133
+ let result;
134
+ try {
135
+ result = await validateProof(payload, hash, {
136
+ minJitterScore: threshold,
137
+ requireBio,
138
+ blockSoftwareRenderer,
139
+ checkNonce: async (n) => _store.consume(`pulse:${n}`),
140
+ });
141
+ } catch (err) {
142
+ console.error('[pulse] validateProof error:', err);
143
+ return _err(500, 'VALIDATION_ERROR', 'Internal error during proof validation');
144
+ }
145
+
146
+ if (!result.valid) {
147
+ return Response.json(
148
+ { error: 'PULSE_REJECTED', reasons: result.reasons, riskFlags: result.riskFlags },
149
+ { status: 403 }
150
+ );
151
+ }
152
+
153
+ // ── Attach pulse result and call the real handler ──────────────────────
154
+ // Next.js Request is immutable so we inject via a lightweight proxy
155
+ const enriched = new Proxy(req, {
156
+ get(target, prop) {
157
+ if (prop === 'pulse') return result;
158
+ const val = target[prop];
159
+ return typeof val === 'function' ? val.bind(target) : val;
160
+ },
161
+ });
162
+
163
+ return handler(enriched, ...args);
164
+ };
165
+ };
166
+ }
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Internal helpers
170
+ // ---------------------------------------------------------------------------
171
+
172
+ function _err(status, code, message) {
173
+ return Response.json({ error: code, message }, { status });
174
+ }