@svrnsec/pulse 0.7.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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +883 -782
  3. package/SECURITY.md +27 -22
  4. package/bin/svrnsec-pulse.js +7 -7
  5. package/dist/{pulse.cjs.js → pulse.cjs} +6428 -6413
  6. package/dist/pulse.cjs.map +1 -0
  7. package/dist/pulse.esm.js +6429 -6415
  8. package/dist/pulse.esm.js.map +1 -1
  9. package/index.d.ts +949 -846
  10. package/package.json +189 -184
  11. package/pkg/pulse_core.js +174 -173
  12. package/src/analysis/audio.js +213 -213
  13. package/src/analysis/authenticityAudit.js +408 -393
  14. package/src/analysis/coherence.js +502 -502
  15. package/src/analysis/coordinatedBehavior.js +825 -804
  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 -391
  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/errors.js +54 -0
  36. package/src/fingerprint.js +475 -475
  37. package/src/index.js +345 -342
  38. package/src/integrations/react-native.js +462 -459
  39. package/src/integrations/react.js +184 -185
  40. package/src/middleware/express.js +155 -155
  41. package/src/middleware/next.js +174 -175
  42. package/src/proof/challenge.js +249 -249
  43. package/src/proof/engagementToken.js +426 -394
  44. package/src/proof/fingerprint.js +268 -268
  45. package/src/proof/validator.js +82 -142
  46. package/src/registry/serializer.js +349 -349
  47. package/src/terminal.js +263 -263
  48. package/src/update-notifier.js +259 -264
  49. package/dist/pulse.cjs.js.map +0 -1
@@ -1,195 +1,195 @@
1
- /**
2
- * @svrnsec/pulse — Entropy Collector
3
- *
4
- * Bridges the Rust/WASM matrix-multiply probe into JavaScript.
5
- * The WASM module is lazily initialised once and cached for subsequent calls.
6
- */
7
-
8
- import { collectEntropyAdaptive } from './adaptive.js';
9
-
10
- // ---------------------------------------------------------------------------
11
- // WASM loader (lazy singleton)
12
- // ---------------------------------------------------------------------------
13
- let _wasmModule = null;
14
- let _initPromise = null;
15
-
16
- /**
17
- * Initialise (or return the cached) WASM module.
18
- * Works in browsers (via fetch), in Electron (Node.js context), and in
19
- * Jest/Vitest via a manual WASM path override.
20
- *
21
- * @param {string} [wasmPath] – override path/URL to the .wasm binary
22
- */
23
- async function initWasm(wasmPath) {
24
- if (_wasmModule) return _wasmModule;
25
- if (_initPromise) return _initPromise;
26
-
27
- _initPromise = (async () => {
28
- // Dynamic import so bundlers can tree-shake this for server-only builds.
29
- const { default: init, run_entropy_probe, run_memory_probe, compute_autocorrelation } =
30
- await import('../../pkg/pulse_core.js');
31
-
32
- const url = wasmPath ?? new URL('../../pkg/pulse_core_bg.wasm', import.meta.url).href;
33
- await init(url);
34
-
35
- _wasmModule = { run_entropy_probe, run_memory_probe, compute_autocorrelation };
36
- return _wasmModule;
37
- })();
38
-
39
- return _initPromise;
40
- }
41
-
42
- // ---------------------------------------------------------------------------
43
- // collectEntropy
44
- // ---------------------------------------------------------------------------
45
-
46
- /**
47
- * Run the WASM entropy probe and return raw timing data.
48
- *
49
- * @param {object} opts
50
- * @param {number} [opts.iterations=200] - number of matrix-multiply rounds
51
- * @param {number} [opts.matrixSize=64] - N for the N×N matrices
52
- * @param {number} [opts.memSizeKb=512] - size of the memory bandwidth probe
53
- * @param {number} [opts.memIterations=50]
54
- * @param {boolean} [opts.phased=true] - run cold/load/hot phases for entropy-jitter ratio
55
- * @param {string} [opts.wasmPath] - optional custom WASM binary path
56
- *
57
- * @returns {Promise<EntropyResult>}
58
- */
59
- export async function collectEntropy(opts = {}) {
60
- const {
61
- iterations = 200,
62
- matrixSize = 64,
63
- memSizeKb = 512,
64
- memIterations = 50,
65
- phased = true,
66
- adaptive = false,
67
- adaptiveThreshold = 0.85,
68
- onBatch,
69
- wasmPath,
70
- } = opts;
71
-
72
- const wasm = await initWasm(wasmPath);
73
- const t_start = Date.now();
74
-
75
- let phases = null;
76
- let timings, resolutionProbe, checksum, timerGranularityMs;
77
- let _adaptiveInfo = null;
78
-
79
- // ── Adaptive mode: smart early exit, fastest for obvious VMs ──────────
80
- if (adaptive) {
81
- const r = await collectEntropyAdaptive(wasm, {
82
- minIterations: 50,
83
- maxIterations: iterations,
84
- batchSize: 25,
85
- vmThreshold: adaptiveThreshold,
86
- hwThreshold: 0.80,
87
- hwMinIterations: 75,
88
- matrixSize,
89
- onBatch,
90
- });
91
- timings = r.timings;
92
- resolutionProbe = r.resolutionProbe ?? [];
93
- checksum = r.checksum;
94
- timerGranularityMs = r.timerGranularityMs;
95
- _adaptiveInfo = { earlyExit: r.earlyExit, batches: r.batches, elapsedMs: r.elapsedMs };
96
-
97
- // ── Phased collection: cold → load → hot ──────────────────────────────
98
- // Each phase runs a separate WASM probe. On real hardware, sustained load
99
- // increases thermal noise so Phase 3 (hot) entropy is measurably higher
100
- // than Phase 1 (cold). A VM's hypervisor clock is insensitive to guest
101
- // thermal state, so all three phases return nearly identical entropy.
102
- } else if (phased && iterations >= 60) {
103
- const coldN = Math.floor(iterations * 0.25); // ~25% cold
104
- const loadN = Math.floor(iterations * 0.50); // ~50% sustained load
105
- const hotN = iterations - coldN - loadN; // ~25% hot
106
-
107
- const cold = wasm.run_entropy_probe(coldN, matrixSize);
108
- const load = wasm.run_entropy_probe(loadN, matrixSize);
109
- const hot = wasm.run_entropy_probe(hotN, matrixSize);
110
-
111
- const coldTimings = Array.from(cold.timings);
112
- const loadTimings = Array.from(load.timings);
113
- const hotTimings = Array.from(hot.timings);
114
-
115
- timings = [...coldTimings, ...loadTimings, ...hotTimings];
116
- resolutionProbe = Array.from(cold.resolution_probe);
117
- checksum = (cold.checksum + load.checksum + hot.checksum).toString();
118
-
119
- const { detectQuantizationEntropy } = await import('../analysis/jitter.js');
120
- const coldQE = detectQuantizationEntropy(coldTimings);
121
- const hotQE = detectQuantizationEntropy(hotTimings);
122
-
123
- phases = {
124
- cold: { n: coldN, timings: coldTimings, qe: coldQE, mean: _mean(coldTimings) },
125
- load: { n: loadN, timings: loadTimings, qe: detectQuantizationEntropy(loadTimings), mean: _mean(loadTimings) },
126
- hot: { n: hotN, timings: hotTimings, qe: hotQE, mean: _mean(hotTimings) },
127
- // The key signal: entropy growth under load.
128
- // Real silicon: hotQE / coldQE typically 1.05 – 1.40
129
- // VM: hotQE / coldQE typically 0.95 – 1.05 (flat)
130
- entropyJitterRatio: coldQE > 0 ? hotQE / coldQE : 1.0,
131
- };
132
- } else {
133
- // Single-phase fallback (fewer iterations or phased disabled)
134
- const result = wasm.run_entropy_probe(iterations, matrixSize);
135
- timings = Array.from(result.timings);
136
- resolutionProbe = Array.from(result.resolution_probe);
137
- checksum = result.checksum.toString();
138
- }
139
-
140
- // ── Timer resolution (non-adaptive path only — adaptive computes its own) ─
141
- if (!adaptive) {
142
- const resDeltas = [];
143
- for (let i = 1; i < resolutionProbe.length; i++) {
144
- const d = resolutionProbe[i] - resolutionProbe[i - 1];
145
- if (d > 0) resDeltas.push(d);
146
- }
147
- timerGranularityMs = resDeltas.length
148
- ? resDeltas.reduce((a, b) => Math.min(a, b), Infinity)
149
- : null;
150
- }
151
-
152
- // ── Autocorrelation at diagnostic lags ────────────────────────────────
153
- // Extended lags catch long-period steal-time rhythms (Xen: ~150 iters)
154
- const lags = [1, 2, 3, 5, 10, 25, 50];
155
- const autocorrelations = {};
156
- for (const lag of lags) {
157
- if (lag < timings.length) {
158
- autocorrelations[`lag${lag}`] = wasm.compute_autocorrelation(timings, lag);
159
- }
160
- }
161
-
162
- // ── Secondary probe: memory bandwidth jitter ───────────────────────────
163
- const memTimings = Array.from(wasm.run_memory_probe(memSizeKb, memIterations));
164
-
165
- return {
166
- timings,
167
- resolutionProbe,
168
- timerGranularityMs,
169
- autocorrelations,
170
- memTimings,
171
- phases,
172
- checksum,
173
- collectedAt: t_start,
174
- iterations: timings.length, // actual count (adaptive may differ from requested)
175
- matrixSize,
176
- adaptive: _adaptiveInfo, // null in non-adaptive mode
177
- };
178
- }
179
-
180
- function _mean(arr) {
181
- return arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
182
- }
183
-
184
- /**
185
- * @typedef {object} EntropyResult
186
- * @property {number[]} timings - per-iteration wall-clock deltas (ms)
187
- * @property {number[]} resolutionProbe - raw successive perf.now() readings
188
- * @property {number|null} timerGranularityMs - effective timer resolution
189
- * @property {object} autocorrelations - { lag1, lag2, lag3, lag5, lag10 }
190
- * @property {number[]} memTimings - memory-probe timings (ms)
191
- * @property {string} checksum - proof the computation ran
192
- * @property {number} collectedAt - Date.now() at probe start
193
- * @property {number} iterations
194
- * @property {number} matrixSize
195
- */
1
+ /**
2
+ * @svrnsec/pulse — Entropy Collector
3
+ *
4
+ * Bridges the Rust/WASM matrix-multiply probe into JavaScript.
5
+ * The WASM module is lazily initialised once and cached for subsequent calls.
6
+ */
7
+
8
+ import { collectEntropyAdaptive } from './adaptive.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // WASM loader (lazy singleton)
12
+ // ---------------------------------------------------------------------------
13
+ let _wasmModule = null;
14
+ let _initPromise = null;
15
+
16
+ /**
17
+ * Initialise (or return the cached) WASM module.
18
+ * Works in browsers (via fetch), in Electron (Node.js context), and in
19
+ * Jest/Vitest via a manual WASM path override.
20
+ *
21
+ * @param {string} [wasmPath] – override path/URL to the .wasm binary
22
+ */
23
+ async function initWasm(wasmPath) {
24
+ if (_wasmModule) return _wasmModule;
25
+ if (_initPromise) return _initPromise;
26
+
27
+ _initPromise = (async () => {
28
+ // Dynamic import so bundlers can tree-shake this for server-only builds.
29
+ const { default: init, run_entropy_probe, run_memory_probe, compute_autocorrelation } =
30
+ await import('../../pkg/pulse_core.js');
31
+
32
+ const url = wasmPath ?? new URL('../../pkg/pulse_core_bg.wasm', import.meta.url).href;
33
+ await init(url);
34
+
35
+ _wasmModule = { run_entropy_probe, run_memory_probe, compute_autocorrelation };
36
+ return _wasmModule;
37
+ })();
38
+
39
+ return _initPromise;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // collectEntropy
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /**
47
+ * Run the WASM entropy probe and return raw timing data.
48
+ *
49
+ * @param {object} opts
50
+ * @param {number} [opts.iterations=200] - number of matrix-multiply rounds
51
+ * @param {number} [opts.matrixSize=64] - N for the N×N matrices
52
+ * @param {number} [opts.memSizeKb=512] - size of the memory bandwidth probe
53
+ * @param {number} [opts.memIterations=50]
54
+ * @param {boolean} [opts.phased=true] - run cold/load/hot phases for entropy-jitter ratio
55
+ * @param {string} [opts.wasmPath] - optional custom WASM binary path
56
+ *
57
+ * @returns {Promise<EntropyResult>}
58
+ */
59
+ export async function collectEntropy(opts = {}) {
60
+ const {
61
+ iterations = 200,
62
+ matrixSize = 64,
63
+ memSizeKb = 512,
64
+ memIterations = 50,
65
+ phased = true,
66
+ adaptive = false,
67
+ adaptiveThreshold = 0.85,
68
+ onBatch,
69
+ wasmPath,
70
+ } = opts;
71
+
72
+ const wasm = await initWasm(wasmPath);
73
+ const t_start = Date.now();
74
+
75
+ let phases = null;
76
+ let timings, resolutionProbe, checksum, timerGranularityMs;
77
+ let _adaptiveInfo = null;
78
+
79
+ // ── Adaptive mode: smart early exit, fastest for obvious VMs ──────────
80
+ if (adaptive) {
81
+ const r = await collectEntropyAdaptive(wasm, {
82
+ minIterations: 50,
83
+ maxIterations: iterations,
84
+ batchSize: 25,
85
+ vmThreshold: adaptiveThreshold,
86
+ hwThreshold: 0.80,
87
+ hwMinIterations: 75,
88
+ matrixSize,
89
+ onBatch,
90
+ });
91
+ timings = r.timings;
92
+ resolutionProbe = r.resolutionProbe ?? [];
93
+ checksum = r.checksum;
94
+ timerGranularityMs = r.timerGranularityMs;
95
+ _adaptiveInfo = { earlyExit: r.earlyExit, batches: r.batches, elapsedMs: r.elapsedMs };
96
+
97
+ // ── Phased collection: cold → load → hot ──────────────────────────────
98
+ // Each phase runs a separate WASM probe. On real hardware, sustained load
99
+ // increases thermal noise so Phase 3 (hot) entropy is measurably higher
100
+ // than Phase 1 (cold). A VM's hypervisor clock is insensitive to guest
101
+ // thermal state, so all three phases return nearly identical entropy.
102
+ } else if (phased && iterations >= 60) {
103
+ const coldN = Math.floor(iterations * 0.25); // ~25% cold
104
+ const loadN = Math.floor(iterations * 0.50); // ~50% sustained load
105
+ const hotN = iterations - coldN - loadN; // ~25% hot
106
+
107
+ const cold = wasm.run_entropy_probe(coldN, matrixSize);
108
+ const load = wasm.run_entropy_probe(loadN, matrixSize);
109
+ const hot = wasm.run_entropy_probe(hotN, matrixSize);
110
+
111
+ const coldTimings = Array.from(cold.timings);
112
+ const loadTimings = Array.from(load.timings);
113
+ const hotTimings = Array.from(hot.timings);
114
+
115
+ timings = [...coldTimings, ...loadTimings, ...hotTimings];
116
+ resolutionProbe = Array.from(cold.resolution_probe);
117
+ checksum = (cold.checksum + load.checksum + hot.checksum).toString();
118
+
119
+ const { detectQuantizationEntropy } = await import('../analysis/jitter.js');
120
+ const coldQE = detectQuantizationEntropy(coldTimings);
121
+ const hotQE = detectQuantizationEntropy(hotTimings);
122
+
123
+ phases = {
124
+ cold: { n: coldN, timings: coldTimings, qe: coldQE, mean: _mean(coldTimings) },
125
+ load: { n: loadN, timings: loadTimings, qe: detectQuantizationEntropy(loadTimings), mean: _mean(loadTimings) },
126
+ hot: { n: hotN, timings: hotTimings, qe: hotQE, mean: _mean(hotTimings) },
127
+ // The key signal: entropy growth under load.
128
+ // Real silicon: hotQE / coldQE typically 1.05 – 1.40
129
+ // VM: hotQE / coldQE typically 0.95 – 1.05 (flat)
130
+ entropyJitterRatio: coldQE > 0 ? hotQE / coldQE : 1.0,
131
+ };
132
+ } else {
133
+ // Single-phase fallback (fewer iterations or phased disabled)
134
+ const result = wasm.run_entropy_probe(iterations, matrixSize);
135
+ timings = Array.from(result.timings);
136
+ resolutionProbe = Array.from(result.resolution_probe);
137
+ checksum = result.checksum.toString();
138
+ }
139
+
140
+ // ── Timer resolution (non-adaptive path only — adaptive computes its own) ─
141
+ if (!adaptive) {
142
+ const resDeltas = [];
143
+ for (let i = 1; i < resolutionProbe.length; i++) {
144
+ const d = resolutionProbe[i] - resolutionProbe[i - 1];
145
+ if (d > 0) resDeltas.push(d);
146
+ }
147
+ timerGranularityMs = resDeltas.length
148
+ ? resDeltas.reduce((a, b) => Math.min(a, b), Infinity)
149
+ : null;
150
+ }
151
+
152
+ // ── Autocorrelation at diagnostic lags ────────────────────────────────
153
+ // Extended lags catch long-period steal-time rhythms (Xen: ~150 iters)
154
+ const lags = [1, 2, 3, 5, 10, 25, 50];
155
+ const autocorrelations = {};
156
+ for (const lag of lags) {
157
+ if (lag < timings.length) {
158
+ autocorrelations[`lag${lag}`] = wasm.compute_autocorrelation(timings, lag);
159
+ }
160
+ }
161
+
162
+ // ── Secondary probe: memory bandwidth jitter ───────────────────────────
163
+ const memTimings = Array.from(wasm.run_memory_probe(memSizeKb, memIterations));
164
+
165
+ return {
166
+ timings,
167
+ resolutionProbe,
168
+ timerGranularityMs,
169
+ autocorrelations,
170
+ memTimings,
171
+ phases,
172
+ checksum,
173
+ collectedAt: t_start,
174
+ iterations: timings.length, // actual count (adaptive may differ from requested)
175
+ matrixSize,
176
+ adaptive: _adaptiveInfo, // null in non-adaptive mode
177
+ };
178
+ }
179
+
180
+ function _mean(arr) {
181
+ return arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
182
+ }
183
+
184
+ /**
185
+ * @typedef {object} EntropyResult
186
+ * @property {number[]} timings - per-iteration wall-clock deltas (ms)
187
+ * @property {number[]} resolutionProbe - raw successive perf.now() readings
188
+ * @property {number|null} timerGranularityMs - effective timer resolution
189
+ * @property {object} autocorrelations - { lag1, lag2, lag3, lag5, lag10 }
190
+ * @property {number[]} memTimings - memory-probe timings (ms)
191
+ * @property {string} checksum - proof the computation ran
192
+ * @property {number} collectedAt - Date.now() at probe start
193
+ * @property {number} iterations
194
+ * @property {number} matrixSize
195
+ */