@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,245 +1,248 @@
1
- /**
2
- * @sovereign/pulse — WebGPU Thermal Variance Probe
3
- *
4
- * Runs a compute shader on the GPU and measures dispatch timing variance.
5
- *
6
- * Why this works
7
- * ──────────────
8
- * Real consumer GPUs (GTX 1650, RX 6600, M2 GPU) have thermal noise in shader
9
- * execution timing that increases under sustained load — the same thermodynamic
10
- * principle as the CPU probe but in silicon designed for parallel throughput.
11
- *
12
- * Cloud VMs with software GPU emulation (SwiftShader, llvmpipe, Mesa's softpipe)
13
- * execute shaders on the CPU and produce near-deterministic timing — flat CV,
14
- * no thermal growth across phases, no dispatch jitter.
15
- *
16
- * VMs with GPU passthrough (rare in practice, requires dedicated hardware) pass
17
- * this check — which is correct, they have real GPU silicon.
18
- *
19
- * Signals
20
- * ───────
21
- * gpuPresent false = WebGPU absent = software renderer = high VM probability
22
- * isSoftware true = SwiftShader/llvmpipe detected by adapter info
23
- * dispatchCV coefficient of variation across dispatch timings
24
- * thermalGrowth (hotDispatchMean - coldDispatchMean) / coldDispatchMean
25
- * vendorString GPU vendor from adapter info (Intel, NVIDIA, AMD, Apple, etc.)
26
- */
27
-
28
- /* ─── WebGPU availability ────────────────────────────────────────────────── */
29
-
30
- function isWebGPUAvailable() {
31
- return typeof navigator !== 'undefined' && 'gpu' in navigator;
32
- }
33
-
34
- /* ─── Software renderer detection ───────────────────────────────────────── */
35
-
36
- const SOFTWARE_RENDERER_PATTERNS = [
37
- /swiftshader/i,
38
- /llvmpipe/i,
39
- /softpipe/i,
40
- /microsoft basic render/i,
41
- /angle \(.*software/i,
42
- /cpu/i,
43
- ];
44
-
45
- function detectSoftwareRenderer(adapterInfo) {
46
- const desc = [
47
- adapterInfo?.vendor ?? '',
48
- adapterInfo?.device ?? '',
49
- adapterInfo?.description ?? '',
50
- adapterInfo?.architecture ?? '',
51
- ].join(' ');
52
-
53
- return SOFTWARE_RENDERER_PATTERNS.some(p => p.test(desc));
54
- }
55
-
56
- /* ─── Compute shader ─────────────────────────────────────────────────────── */
57
-
58
- // A compute workload that is trivially parallelisable but forces the GPU to
59
- // actually execute — matrix-multiply on 64 × 64 tiles across 256 workgroups.
60
- // Light enough that it doesn't block UI; heavy enough to generate thermal signal.
61
- const SHADER_SRC = /* wgsl */ `
62
- struct Matrix {
63
- values: array<f32, 4096>, // 64x64
64
- };
65
-
66
- @group(0) @binding(0) var<storage, read> matA : Matrix;
67
- @group(0) @binding(1) var<storage, read> matB : Matrix;
68
- @group(0) @binding(2) var<storage, read_write> matC : Matrix;
69
-
70
- @compute @workgroup_size(8, 8)
71
- fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
72
- let row = gid.x;
73
- let col = gid.y;
74
- if (row >= 64u || col >= 64u) { return; }
75
-
76
- var acc: f32 = 0.0;
77
- for (var k = 0u; k < 64u; k++) {
78
- acc += matA.values[row * 64u + k] * matB.values[k * 64u + col];
79
- }
80
- matC.values[row * 64u + col] = acc;
81
- }
82
- `;
83
-
84
- /* ─── collectGpuEntropy ─────────────────────────────────────────────────── */
85
-
86
- /**
87
- * @param {object} [opts]
88
- * @param {number} [opts.iterations=60] – dispatch rounds per phase
89
- * @param {boolean} [opts.phased=true] – cold / load / hot phases
90
- * @param {number} [opts.timeoutMs=8000] – hard abort if GPU stalls
91
- * @returns {Promise<GpuEntropyResult>}
92
- */
93
- export async function collectGpuEntropy(opts = {}) {
94
- const { iterations = 60, phased = true, timeoutMs = 8000 } = opts;
95
-
96
- if (!isWebGPUAvailable()) {
97
- return _noGpu('WebGPU not available in this environment');
98
- }
99
-
100
- let adapter, device;
101
- try {
102
- adapter = await Promise.race([
103
- navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }),
104
- _timeout(timeoutMs, 'requestAdapter timed out'),
105
- ]);
106
- if (!adapter) return _noGpu('No WebGPU adapter found');
107
-
108
- device = await Promise.race([
109
- adapter.requestDevice(),
110
- _timeout(timeoutMs, 'requestDevice timed out'),
111
- ]);
112
- } catch (err) {
113
- return _noGpu(`WebGPU init failed: ${err.message}`);
114
- }
115
-
116
- const adapterInfo = adapter.info ?? {};
117
- const isSoftware = detectSoftwareRenderer(adapterInfo);
118
-
119
- // Compile the shader module once
120
- const shaderModule = device.createShaderModule({ code: SHADER_SRC });
121
-
122
- // Create persistent GPU buffers (64×64 float32 = 16 KB each)
123
- const bufSize = 4096 * 4; // 4096 floats × 4 bytes
124
- const bufA = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
125
- const bufB = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
126
- const bufC = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);
127
-
128
- // Seed with random data
129
- const matData = new Float32Array(4096).map(() => Math.random());
130
- device.queue.writeBuffer(bufA, 0, matData);
131
- device.queue.writeBuffer(bufB, 0, matData);
132
-
133
- const pipeline = device.createComputePipeline({
134
- layout: 'auto',
135
- compute: { module: shaderModule, entryPoint: 'main' },
136
- });
137
-
138
- const bindGroup = device.createBindGroup({
139
- layout: pipeline.getBindGroupLayout(0),
140
- entries: [
141
- { binding: 0, resource: { buffer: bufA } },
142
- { binding: 1, resource: { buffer: bufB } },
143
- { binding: 2, resource: { buffer: bufC } },
144
- ],
145
- });
146
-
147
- // ── Probe ──────────────────────────────────────────────────────────────
148
- async function runPhase(n) {
149
- const timings = [];
150
- for (let i = 0; i < n; i++) {
151
- const t0 = performance.now();
152
- const encoder = device.createCommandEncoder();
153
- const pass = encoder.beginComputePass();
154
- pass.setPipeline(pipeline);
155
- pass.setBindGroup(0, bindGroup);
156
- pass.dispatchWorkgroups(8, 8); // 64 workgroups total
157
- pass.end();
158
- device.queue.submit([encoder.finish()]);
159
- await device.queue.onSubmittedWorkDone();
160
- const t1 = performance.now();
161
- timings.push(t1 - t0);
162
- }
163
- return timings;
164
- }
165
-
166
- let coldTimings, loadTimings, hotTimings;
167
-
168
- if (phased) {
169
- coldTimings = await runPhase(Math.floor(iterations * 0.25));
170
- loadTimings = await runPhase(Math.floor(iterations * 0.50));
171
- hotTimings = await runPhase(iterations - coldTimings.length - loadTimings.length);
172
- } else {
173
- coldTimings = await runPhase(iterations);
174
- loadTimings = [];
175
- hotTimings = [];
176
- }
177
-
178
- // Cleanup
179
- bufA.destroy(); bufB.destroy(); bufC.destroy();
180
- device.destroy();
181
-
182
- const allTimings = [...coldTimings, ...loadTimings, ...hotTimings];
183
- const mean = _mean(allTimings);
184
- const cv = mean > 0 ? _std(allTimings) / mean : 0;
185
-
186
- const coldMean = _mean(coldTimings);
187
- const hotMean = _mean(hotTimings.length ? hotTimings : coldTimings);
188
- const thermalGrowth = coldMean > 0 ? (hotMean - coldMean) / coldMean : 0;
189
-
190
- return {
191
- gpuPresent: true,
192
- isSoftware,
193
- vendor: adapterInfo.vendor ?? 'unknown',
194
- architecture: adapterInfo.architecture ?? 'unknown',
195
- timings: allTimings,
196
- dispatchCV: cv,
197
- thermalGrowth,
198
- coldMean,
199
- hotMean,
200
- // Heuristic: real GPU → thermalGrowth > 0.02 and CV > 0.04
201
- // Software renderer → thermalGrowth ≈ 0, CV < 0.02
202
- verdict: isSoftware ? 'software_renderer'
203
- : thermalGrowth > 0.02 && cv > 0.04 ? 'real_gpu'
204
- : thermalGrowth < 0 && cv < 0.02 ? 'virtual_gpu'
205
- : 'ambiguous',
206
- };
207
- }
208
-
209
- /* ─── helpers ────────────────────────────────────────────────────────────── */
210
-
211
- function _noGpu(reason) {
212
- return { gpuPresent: false, isSoftware: false, vendor: null,
213
- architecture: null, timings: [], dispatchCV: 0,
214
- thermalGrowth: 0, coldMean: 0, hotMean: 0,
215
- verdict: 'no_gpu', reason };
216
- }
217
-
218
- function _createBuffer(device, size, usage) {
219
- return device.createBuffer({ size, usage });
220
- }
221
-
222
- function _mean(arr) {
223
- return arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
224
- }
225
-
226
- function _std(arr) {
227
- const m = _mean(arr);
228
- return Math.sqrt(arr.reduce((s, v) => s + (v - m) ** 2, 0) / arr.length);
229
- }
230
-
231
- function _timeout(ms, msg) {
232
- return new Promise((_, reject) => setTimeout(() => reject(new Error(msg)), ms));
233
- }
234
-
235
- /**
236
- * @typedef {object} GpuEntropyResult
237
- * @property {boolean} gpuPresent
238
- * @property {boolean} isSoftware
239
- * @property {string|null} vendor
240
- * @property {string|null} architecture
241
- * @property {number[]} timings
242
- * @property {number} dispatchCV
243
- * @property {number} thermalGrowth
244
- * @property {string} verdict 'real_gpu' | 'virtual_gpu' | 'software_renderer' | 'no_gpu' | 'ambiguous'
245
- */
1
+ /**
2
+ * @svrnsec/pulse — WebGPU Thermal Variance Probe
3
+ *
4
+ * Runs a compute shader on the GPU and measures dispatch timing variance.
5
+ *
6
+ * Why this works
7
+ * ──────────────
8
+ * Real consumer GPUs (GTX 1650, RX 6600, M2 GPU) have thermal noise in shader
9
+ * execution timing that increases under sustained load — the same thermodynamic
10
+ * principle as the CPU probe but in silicon designed for parallel throughput.
11
+ *
12
+ * Cloud VMs with software GPU emulation (SwiftShader, llvmpipe, Mesa's softpipe)
13
+ * execute shaders on the CPU and produce near-deterministic timing — flat CV,
14
+ * no thermal growth across phases, no dispatch jitter.
15
+ *
16
+ * VMs with GPU passthrough (rare in practice, requires dedicated hardware) pass
17
+ * this check — which is correct, they have real GPU silicon.
18
+ *
19
+ * Signals
20
+ * ───────
21
+ * gpuPresent false = WebGPU absent = software renderer = high VM probability
22
+ * isSoftware true = SwiftShader/llvmpipe detected by adapter info
23
+ * dispatchCV coefficient of variation across dispatch timings
24
+ * thermalGrowth (hotDispatchMean - coldDispatchMean) / coldDispatchMean
25
+ * vendorString GPU vendor from adapter info (Intel, NVIDIA, AMD, Apple, etc.)
26
+ */
27
+
28
+ /* ─── WebGPU availability ────────────────────────────────────────────────── */
29
+
30
+ function isWebGPUAvailable() {
31
+ return typeof navigator !== 'undefined' && 'gpu' in navigator;
32
+ }
33
+
34
+ /* ─── Software renderer detection ───────────────────────────────────────── */
35
+
36
+ const SOFTWARE_RENDERER_PATTERNS = [
37
+ /swiftshader/i,
38
+ /llvmpipe/i,
39
+ /softpipe/i,
40
+ /microsoft basic render/i,
41
+ /angle \(.*software/i,
42
+ /cpu/i,
43
+ ];
44
+
45
+ function detectSoftwareRenderer(adapterInfo) {
46
+ const desc = [
47
+ adapterInfo?.vendor ?? '',
48
+ adapterInfo?.device ?? '',
49
+ adapterInfo?.description ?? '',
50
+ adapterInfo?.architecture ?? '',
51
+ ].join(' ');
52
+
53
+ return SOFTWARE_RENDERER_PATTERNS.some(p => p.test(desc));
54
+ }
55
+
56
+ /* ─── Compute shader ─────────────────────────────────────────────────────── */
57
+
58
+ // A compute workload that is trivially parallelisable but forces the GPU to
59
+ // actually execute — matrix-multiply on 64 × 64 tiles across 256 workgroups.
60
+ // Light enough that it doesn't block UI; heavy enough to generate thermal signal.
61
+ const SHADER_SRC = /* wgsl */ `
62
+ struct Matrix {
63
+ values: array<f32, 4096>, // 64x64
64
+ };
65
+
66
+ @group(0) @binding(0) var<storage, read> matA : Matrix;
67
+ @group(0) @binding(1) var<storage, read> matB : Matrix;
68
+ @group(0) @binding(2) var<storage, read_write> matC : Matrix;
69
+
70
+ @compute @workgroup_size(8, 8)
71
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
72
+ let row = gid.x;
73
+ let col = gid.y;
74
+ if (row >= 64u || col >= 64u) { return; }
75
+
76
+ var acc: f32 = 0.0;
77
+ for (var k = 0u; k < 64u; k++) {
78
+ acc += matA.values[row * 64u + k] * matB.values[k * 64u + col];
79
+ }
80
+ matC.values[row * 64u + col] = acc;
81
+ }
82
+ `;
83
+
84
+ /* ─── collectGpuEntropy ─────────────────────────────────────────────────── */
85
+
86
+ /**
87
+ * @param {object} [opts]
88
+ * @param {number} [opts.iterations=60] – dispatch rounds per phase
89
+ * @param {boolean} [opts.phased=true] – cold / load / hot phases
90
+ * @param {number} [opts.timeoutMs=8000] – hard abort if GPU stalls
91
+ * @returns {Promise<GpuEntropyResult>}
92
+ */
93
+ export async function collectGpuEntropy(opts = {}) {
94
+ const { iterations = 60, phased = true, timeoutMs = 8000 } = opts;
95
+
96
+ if (!isWebGPUAvailable()) {
97
+ return _noGpu('WebGPU not available in this environment');
98
+ }
99
+
100
+ let adapter, device;
101
+ try {
102
+ adapter = await Promise.race([
103
+ navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }),
104
+ _timeout(timeoutMs, 'requestAdapter timed out'),
105
+ ]);
106
+ if (!adapter) return _noGpu('No WebGPU adapter found');
107
+
108
+ device = await Promise.race([
109
+ adapter.requestDevice(),
110
+ _timeout(timeoutMs, 'requestDevice timed out'),
111
+ ]);
112
+ } catch (err) {
113
+ return _noGpu(`WebGPU init failed: ${err.message}`);
114
+ }
115
+
116
+ const adapterInfo = adapter.info ?? {};
117
+ const isSoftware = detectSoftwareRenderer(adapterInfo);
118
+
119
+ device.lost.then(info => console.warn('[pulse] GPU device lost:', info.message));
120
+
121
+ // Compile the shader module once
122
+ const shaderModule = device.createShaderModule({ code: SHADER_SRC });
123
+
124
+ // Create persistent GPU buffers (64×64 float32 = 16 KB each)
125
+ const bufSize = 4096 * 4; // 4096 floats × 4 bytes
126
+ const bufA = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
127
+ const bufB = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
128
+ const bufC = _createBuffer(device, bufSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC);
129
+
130
+ try {
131
+ // Seed with random data
132
+ const matData = new Float32Array(4096).map(() => Math.random());
133
+ device.queue.writeBuffer(bufA, 0, matData);
134
+ device.queue.writeBuffer(bufB, 0, matData);
135
+
136
+ const pipeline = device.createComputePipeline({
137
+ layout: 'auto',
138
+ compute: { module: shaderModule, entryPoint: 'main' },
139
+ });
140
+
141
+ const bindGroup = device.createBindGroup({
142
+ layout: pipeline.getBindGroupLayout(0),
143
+ entries: [
144
+ { binding: 0, resource: { buffer: bufA } },
145
+ { binding: 1, resource: { buffer: bufB } },
146
+ { binding: 2, resource: { buffer: bufC } },
147
+ ],
148
+ });
149
+
150
+ // ── Probe ──────────────────────────────────────────────────────────────
151
+ async function runPhase(n) {
152
+ const timings = [];
153
+ for (let i = 0; i < n; i++) {
154
+ const t0 = performance.now();
155
+ const encoder = device.createCommandEncoder();
156
+ const pass = encoder.beginComputePass();
157
+ pass.setPipeline(pipeline);
158
+ pass.setBindGroup(0, bindGroup);
159
+ pass.dispatchWorkgroups(8, 8); // 64 workgroups total
160
+ pass.end();
161
+ device.queue.submit([encoder.finish()]);
162
+ await device.queue.onSubmittedWorkDone();
163
+ const t1 = performance.now();
164
+ timings.push(t1 - t0);
165
+ }
166
+ return timings;
167
+ }
168
+
169
+ let coldTimings, loadTimings, hotTimings;
170
+
171
+ if (phased) {
172
+ coldTimings = await runPhase(Math.floor(iterations * 0.25));
173
+ loadTimings = await runPhase(Math.floor(iterations * 0.50));
174
+ hotTimings = await runPhase(iterations - coldTimings.length - loadTimings.length);
175
+ } else {
176
+ coldTimings = await runPhase(iterations);
177
+ loadTimings = [];
178
+ hotTimings = [];
179
+ }
180
+
181
+ const allTimings = [...coldTimings, ...loadTimings, ...hotTimings];
182
+ const mean = _mean(allTimings);
183
+ const cv = mean > 0 ? _std(allTimings) / mean : 0;
184
+
185
+ const coldMean = _mean(coldTimings);
186
+ const hotMean = _mean(hotTimings.length ? hotTimings : coldTimings);
187
+ const thermalGrowth = coldMean > 0 ? (hotMean - coldMean) / coldMean : 0;
188
+
189
+ return {
190
+ gpuPresent: true,
191
+ isSoftware,
192
+ vendor: adapterInfo.vendor ?? 'unknown',
193
+ architecture: adapterInfo.architecture ?? 'unknown',
194
+ timings: allTimings,
195
+ dispatchCV: cv,
196
+ thermalGrowth,
197
+ coldMean,
198
+ hotMean,
199
+ // Heuristic: real GPU → thermalGrowth > 0.02 and CV > 0.04
200
+ // Software renderer → thermalGrowth 0, CV < 0.02
201
+ verdict: isSoftware ? 'software_renderer'
202
+ : thermalGrowth > 0.02 && cv > 0.04 ? 'real_gpu'
203
+ : thermalGrowth < 0 && cv < 0.02 ? 'virtual_gpu'
204
+ : 'ambiguous',
205
+ };
206
+ } finally {
207
+ bufA.destroy(); bufB.destroy(); bufC.destroy();
208
+ device.destroy();
209
+ }
210
+ }
211
+
212
+ /* ─── helpers ────────────────────────────────────────────────────────────── */
213
+
214
+ function _noGpu(reason) {
215
+ return { gpuPresent: false, isSoftware: false, vendor: null,
216
+ architecture: null, timings: [], dispatchCV: 0,
217
+ thermalGrowth: 0, coldMean: 0, hotMean: 0,
218
+ verdict: 'no_gpu', reason };
219
+ }
220
+
221
+ function _createBuffer(device, size, usage) {
222
+ return device.createBuffer({ size, usage });
223
+ }
224
+
225
+ function _mean(arr) {
226
+ return arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
227
+ }
228
+
229
+ function _std(arr) {
230
+ const m = _mean(arr);
231
+ return Math.sqrt(arr.reduce((s, v) => s + (v - m) ** 2, 0) / arr.length);
232
+ }
233
+
234
+ function _timeout(ms, msg) {
235
+ return new Promise((_, reject) => setTimeout(() => reject(new Error(msg)), ms));
236
+ }
237
+
238
+ /**
239
+ * @typedef {object} GpuEntropyResult
240
+ * @property {boolean} gpuPresent
241
+ * @property {boolean} isSoftware
242
+ * @property {string|null} vendor
243
+ * @property {string|null} architecture
244
+ * @property {number[]} timings
245
+ * @property {number} dispatchCV
246
+ * @property {number} thermalGrowth
247
+ * @property {string} verdict 'real_gpu' | 'virtual_gpu' | 'software_renderer' | 'no_gpu' | 'ambiguous'
248
+ */