@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,264 +1,259 @@
1
- /**
2
- * @svrnsec/pulse — Update Notifier
3
- *
4
- * Checks the npm registry for a newer version and prints a styled terminal
5
- * notice when one is available. Non-blocking — the check runs in the
6
- * background and only displays if a newer version is found before the
7
- * process exits.
8
- *
9
- * Zero dependencies. Pure Node.js https module.
10
- * Silent in browser environments and when stdout is not a TTY.
11
- */
12
-
13
- import { createRequire } from 'module';
14
-
15
- /* ─── version from package.json ─────────────────────────────────────────── */
16
-
17
- let _currentVersion = '0.0.0';
18
- try {
19
- const require = createRequire(import.meta.url);
20
- _currentVersion = require('../package.json').version;
21
- } catch {}
22
-
23
- export const CURRENT_VERSION = _currentVersion;
24
-
25
- /* ─── ANSI helpers ───────────────────────────────────────────────────────── */
26
-
27
- const isTTY = () =>
28
- typeof process !== 'undefined' &&
29
- process.stdout?.isTTY === true &&
30
- process.env?.NO_COLOR == null &&
31
- process.env?.PULSE_NO_UPDATE == null;
32
-
33
- const isNode = () => typeof process !== 'undefined' && typeof window === 'undefined';
34
-
35
- const C = {
36
- reset: '\x1b[0m',
37
- bold: '\x1b[1m',
38
- dim: '\x1b[2m',
39
- // foreground
40
- black: '\x1b[30m',
41
- red: '\x1b[31m',
42
- green: '\x1b[32m',
43
- yellow: '\x1b[33m',
44
- blue: '\x1b[34m',
45
- magenta: '\x1b[35m',
46
- cyan: '\x1b[36m',
47
- white: '\x1b[37m',
48
- // bright foreground
49
- bgray: '\x1b[90m',
50
- bred: '\x1b[91m',
51
- bgreen: '\x1b[92m',
52
- byellow: '\x1b[93m',
53
- bblue: '\x1b[94m',
54
- bmagenta: '\x1b[95m',
55
- bcyan: '\x1b[96m',
56
- bwhite: '\x1b[97m',
57
- // background
58
- bgBlack: '\x1b[40m',
59
- bgYellow: '\x1b[43m',
60
- bgBlue: '\x1b[44m',
61
- bgCyan: '\x1b[46m',
62
- };
63
-
64
- const c = isTTY;
65
- const ft = (code, s) => c() ? `${code}${s}${C.reset}` : s;
66
-
67
- /* ─── box renderer ───────────────────────────────────────────────────────── */
68
-
69
- /**
70
- * Render a bordered notification box to stderr.
71
- * Uses box-drawing characters and ANSI colors when the terminal supports them.
72
- */
73
- function _box(lines, opts = {}) {
74
- const { borderColor = C.yellow, titleColor = C.bwhite } = opts;
75
- const pad = 2;
76
- const width = Math.max(...lines.map(l => _visLen(l))) + pad * 2;
77
- const hr = '─'.repeat(width);
78
- const bc = (s) => c() ? `${borderColor}${s}${C.reset}` : s;
79
-
80
- const out = [
81
- bc(`╭${hr}╮`),
82
- ...lines.map(l => {
83
- const vis = _visLen(l);
84
- const fill = ' '.repeat(Math.max(0, width - vis - pad * 2));
85
- return bc('│') + ' '.repeat(pad) + (c() ? l : _stripAnsi(l)) + fill + ' '.repeat(pad) + bc('│');
86
- }),
87
- bc(`╰${hr}╯`),
88
- ];
89
-
90
- process.stderr.write('\n' + out.join('\n') + '\n\n');
91
- }
92
-
93
- /* ─── version comparison ─────────────────────────────────────────────────── */
94
-
95
- function _semverGt(a, b) {
96
- const pa = a.replace(/[^0-9.]/g, '').split('.').map(Number);
97
- const pb = b.replace(/[^0-9.]/g, '').split('.').map(Number);
98
- for (let i = 0; i < 3; i++) {
99
- const da = pa[i] ?? 0, db = pb[i] ?? 0;
100
- if (da > db) return true;
101
- if (da < db) return false;
102
- }
103
- return false;
104
- }
105
-
106
- /* ─── registry fetch ─────────────────────────────────────────────────────── */
107
-
108
- async function _fetchLatest(pkg) {
109
- return new Promise((resolve) => {
110
- let resolved = false;
111
- const done = (v) => { if (!resolved) { resolved = true; resolve(v); } };
112
-
113
- const timeout = setTimeout(() => done(null), 3_000);
114
-
115
- try {
116
- const https = require('https');
117
- const req = https.get(
118
- `https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`,
119
- { headers: { 'Accept': 'application/json', 'User-Agent': `${pkg}/${_currentVersion}` } },
120
- (res) => {
121
- let body = '';
122
- res.setEncoding('utf8');
123
- res.on('data', d => body += d);
124
- res.on('end', () => {
125
- clearTimeout(timeout);
126
- try { done(JSON.parse(body).version ?? null); } catch { done(null); }
127
- });
128
- }
129
- );
130
- req.on('error', () => { clearTimeout(timeout); done(null); });
131
- req.end();
132
- } catch {
133
- clearTimeout(timeout);
134
- done(null);
135
- }
136
- });
137
- }
138
-
139
- // Lazy require for Node.js https module (avoids bundler issues)
140
- let _httpsReq = null;
141
- function require(m) {
142
- if (typeof globalThis.require === 'function') return globalThis.require(m);
143
- // CJS interop — only used server-side
144
- if (typeof process !== 'undefined') {
145
- const mod = process.mainModule?.require ?? (() => null);
146
- return mod(m);
147
- }
148
- return null;
149
- }
150
-
151
- /* ─── checkForUpdate ─────────────────────────────────────────────────────── */
152
-
153
- /**
154
- * Check npm for a newer version of @svrnsec/pulse.
155
- * Call once at process startup — the result is shown before process exit
156
- * (or immediately if already resolved).
157
- *
158
- * @param {object} [opts]
159
- * @param {boolean} [opts.silent=false] suppress output even when update exists
160
- * @param {string} [opts.pkg='@svrnsec/pulse']
161
- * @returns {Promise<{ current: string, latest: string|null, updateAvailable: boolean }>}
162
- */
163
- export async function checkForUpdate(opts = {}) {
164
- const { silent = false, pkg = '@svrnsec/pulse' } = opts;
165
-
166
- if (!isNode()) return { current: _currentVersion, latest: null, updateAvailable: false };
167
-
168
- const latest = await _fetchLatest(pkg);
169
- const updateAvailable = latest != null && _semverGt(latest, _currentVersion);
170
-
171
- if (updateAvailable && !silent && isTTY()) {
172
- _showUpdateBox(_currentVersion, latest, pkg);
173
- }
174
-
175
- return { current: _currentVersion, latest, updateAvailable };
176
- }
177
-
178
- /* ─── notifyOnExit ───────────────────────────────────────────────────────── */
179
-
180
- let _notifyRegistered = false;
181
-
182
- /**
183
- * Register a one-time process 'exit' listener that prints the update notice
184
- * after your application's own output has finished. This is the least
185
- * intrusive way to show the notification.
186
- *
187
- * Called automatically by the package initialiser — you do not need to call
188
- * this manually unless you want to control the timing.
189
- *
190
- * @param {object} [opts]
191
- * @param {string} [opts.pkg='@svrnsec/pulse']
192
- */
193
- export function notifyOnExit(opts = {}) {
194
- if (!isNode() || _notifyRegistered) return;
195
- _notifyRegistered = true;
196
-
197
- const pkg = opts.pkg ?? '@svrnsec/pulse';
198
- let _latest = null;
199
-
200
- // Start the background check immediately
201
- _fetchLatest(pkg).then(v => { _latest = v; }).catch(() => {});
202
-
203
- // Show the box just before the process exits (after all user output)
204
- process.on('exit', () => {
205
- if (_latest && _semverGt(_latest, _currentVersion) && isTTY()) {
206
- _showUpdateBox(_currentVersion, _latest, pkg);
207
- }
208
- });
209
- }
210
-
211
- /* ─── banner ─────────────────────────────────────────────────────────────── */
212
-
213
- /**
214
- * Print the @svrnsec/pulse ASCII banner to stderr.
215
- * Called once at package initialisation in Node.js environments.
216
- */
217
- export function printBanner() {
218
- if (!isNode() || !isTTY()) return;
219
-
220
- const v = ft(C.bgray, `v${_currentVersion}`);
221
- const tag = ft(C.bmagenta + C.bold, 'SVRN');
222
- const pkg = ft(C.bwhite + C.bold, ':PULSE');
223
-
224
- process.stderr.write(
225
- '\n' +
226
- ft(C.bgray, ' ┌─────────────────────────────────────┐') + '\n' +
227
- ft(C.bgray, ' │') + ` ${tag}${pkg} ` + ft(C.bgray, '─ Physical Turing Test │') + '\n' +
228
- ft(C.bgray, ' │') + ` ${ft(C.bgray, 'Hardware-Biological Symmetry Protocol')} ` + ft(C.bgray, '│') + '\n' +
229
- ft(C.bgray, ' │') + ` ${ft(C.bcyan, 'npm i @svrnsec/pulse')} ${' '.repeat(16)}${v} ` + ft(C.bgray, '│') + '\n' +
230
- ft(C.bgray, ' └─────────────────────────────────────┘') + '\n\n'
231
- );
232
- }
233
-
234
- /* ─── _showUpdateBox ─────────────────────────────────────────────────────── */
235
-
236
- function _showUpdateBox(current, latest, pkg) {
237
- const arrow = ft(C.bgray, '→');
238
- const oldV = ft(C.bred, current);
239
- const newV = ft(C.bgreen + C.bold, latest);
240
- const cmd = ft(C.bcyan + C.bold, `npm i ${pkg}@latest`);
241
- const notice = ft(C.byellow + C.bold, ' UPDATE AVAILABLE ');
242
-
243
- _box([
244
- notice,
245
- '',
246
- ` ${oldV} ${arrow} ${newV}`,
247
- '',
248
- ` Run: ${cmd}`,
249
- '',
250
- ft(C.bgray, ` Changelog: https://github.com/ayronny14-alt/Svrn-Pulse-Security/releases`),
251
- ], { borderColor: C.byellow });
252
- }
253
-
254
- /* ─── ANSI utilities ─────────────────────────────────────────────────────── */
255
-
256
- // Measure visible length of string (strip ANSI escape codes)
257
- function _visLen(s) {
258
- return _stripAnsi(s).length;
259
- }
260
-
261
- function _stripAnsi(s) {
262
- // eslint-disable-next-line no-control-regex
263
- return s.replace(/\x1b\[[0-9;]*m/g, '');
264
- }
1
+ /**
2
+ * @svrnsec/pulse — Update Notifier
3
+ *
4
+ * Checks the npm registry for a newer version and prints a styled terminal
5
+ * notice when one is available. Non-blocking — the check runs in the
6
+ * background and only displays if a newer version is found before the
7
+ * process exits.
8
+ *
9
+ * Zero dependencies. Pure Node.js https module.
10
+ * Silent in browser environments and when stdout is not a TTY.
11
+ */
12
+
13
+ import { createRequire } from 'module';
14
+
15
+ /* ─── version from package.json ─────────────────────────────────────────── */
16
+
17
+ let _currentVersion = '0.0.0';
18
+ try {
19
+ const require = createRequire(import.meta.url);
20
+ _currentVersion = require('../package.json').version;
21
+ } catch {}
22
+
23
+ export const CURRENT_VERSION = _currentVersion;
24
+
25
+ /* ─── ANSI helpers ───────────────────────────────────────────────────────── */
26
+
27
+ const isTTY = () =>
28
+ typeof process !== 'undefined' &&
29
+ process.stdout?.isTTY === true &&
30
+ process.env?.NO_COLOR == null &&
31
+ process.env?.PULSE_NO_UPDATE == null;
32
+
33
+ const isNode = () => typeof process !== 'undefined' && typeof window === 'undefined';
34
+
35
+ const C = {
36
+ reset: '\x1b[0m',
37
+ bold: '\x1b[1m',
38
+ dim: '\x1b[2m',
39
+ // foreground
40
+ black: '\x1b[30m',
41
+ red: '\x1b[31m',
42
+ green: '\x1b[32m',
43
+ yellow: '\x1b[33m',
44
+ blue: '\x1b[34m',
45
+ magenta: '\x1b[35m',
46
+ cyan: '\x1b[36m',
47
+ white: '\x1b[37m',
48
+ // bright foreground
49
+ bgray: '\x1b[90m',
50
+ bred: '\x1b[91m',
51
+ bgreen: '\x1b[92m',
52
+ byellow: '\x1b[93m',
53
+ bblue: '\x1b[94m',
54
+ bmagenta: '\x1b[95m',
55
+ bcyan: '\x1b[96m',
56
+ bwhite: '\x1b[97m',
57
+ // background
58
+ bgBlack: '\x1b[40m',
59
+ bgYellow: '\x1b[43m',
60
+ bgBlue: '\x1b[44m',
61
+ bgCyan: '\x1b[46m',
62
+ };
63
+
64
+ const c = isTTY;
65
+ const ft = (code, s) => c() ? `${code}${s}${C.reset}` : s;
66
+
67
+ /* ─── box renderer ───────────────────────────────────────────────────────── */
68
+
69
+ /**
70
+ * Render a bordered notification box to stderr.
71
+ * Uses box-drawing characters and ANSI colors when the terminal supports them.
72
+ */
73
+ function _box(lines, opts = {}) {
74
+ const { borderColor = C.yellow, titleColor = C.bwhite } = opts;
75
+ const pad = 2;
76
+ const width = Math.max(...lines.map(l => _visLen(l))) + pad * 2;
77
+ const hr = '─'.repeat(width);
78
+ const bc = (s) => c() ? `${borderColor}${s}${C.reset}` : s;
79
+
80
+ const out = [
81
+ bc(`╭${hr}╮`),
82
+ ...lines.map(l => {
83
+ const vis = _visLen(l);
84
+ const fill = ' '.repeat(Math.max(0, width - vis - pad * 2));
85
+ return bc('│') + ' '.repeat(pad) + (c() ? l : _stripAnsi(l)) + fill + ' '.repeat(pad) + bc('│');
86
+ }),
87
+ bc(`╰${hr}╯`),
88
+ ];
89
+
90
+ process.stderr.write('\n' + out.join('\n') + '\n\n');
91
+ }
92
+
93
+ /* ─── version comparison ─────────────────────────────────────────────────── */
94
+
95
+ function _semverGt(a, b) {
96
+ const [aVer] = a.split('-');
97
+ const [bVer] = b.split('-');
98
+ const pa = aVer.split('.').map(Number);
99
+ const pb = bVer.split('.').map(Number);
100
+ for (let i = 0; i < 3; i++) {
101
+ const da = pa[i] ?? 0, db = pb[i] ?? 0;
102
+ if (da > db) return true;
103
+ if (da < db) return false;
104
+ }
105
+ return false;
106
+ }
107
+
108
+ /* ─── registry fetch ─────────────────────────────────────────────────────── */
109
+
110
+ async function _fetchLatest(pkg) {
111
+ return new Promise((resolve) => {
112
+ let resolved = false;
113
+ const done = (v) => { if (!resolved) { resolved = true; resolve(v); } };
114
+
115
+ const timeout = setTimeout(() => done(null), 3_000);
116
+
117
+ try {
118
+ const https = _require('https');
119
+ const req = https.get(
120
+ `https://registry.npmjs.org/${encodeURIComponent(pkg)}/latest`,
121
+ { headers: { 'Accept': 'application/json', 'User-Agent': `${pkg}/${_currentVersion}` } },
122
+ (res) => {
123
+ let body = '';
124
+ res.setEncoding('utf8');
125
+ res.on('data', d => body += d);
126
+ res.on('end', () => {
127
+ clearTimeout(timeout);
128
+ try { done(JSON.parse(body).version ?? null); } catch { done(null); }
129
+ });
130
+ }
131
+ );
132
+ req.on('error', () => { clearTimeout(timeout); done(null); });
133
+ req.end();
134
+ } catch {
135
+ clearTimeout(timeout);
136
+ done(null);
137
+ }
138
+ });
139
+ }
140
+
141
+ // Lazy require for Node.js https module (avoids bundler issues)
142
+ function _require(m) {
143
+ return createRequire(import.meta.url)(m);
144
+ }
145
+
146
+ /* ─── checkForUpdate ─────────────────────────────────────────────────────── */
147
+
148
+ /**
149
+ * Check npm for a newer version of @svrnsec/pulse.
150
+ * Call once at process startup — the result is shown before process exit
151
+ * (or immediately if already resolved).
152
+ *
153
+ * @param {object} [opts]
154
+ * @param {boolean} [opts.silent=false] suppress output even when update exists
155
+ * @param {string} [opts.pkg='@svrnsec/pulse']
156
+ * @returns {Promise<{ current: string, latest: string|null, updateAvailable: boolean }>}
157
+ */
158
+ export async function checkForUpdate(opts = {}) {
159
+ const { silent = false, pkg = '@svrnsec/pulse' } = opts;
160
+
161
+ if (!isNode()) return { current: _currentVersion, latest: null, updateAvailable: false };
162
+
163
+ const latest = await _fetchLatest(pkg);
164
+ const updateAvailable = latest != null && _semverGt(latest, _currentVersion);
165
+
166
+ if (updateAvailable && !silent && isTTY()) {
167
+ _showUpdateBox(_currentVersion, latest, pkg);
168
+ }
169
+
170
+ return { current: _currentVersion, latest, updateAvailable };
171
+ }
172
+
173
+ /* ─── notifyOnExit ───────────────────────────────────────────────────────── */
174
+
175
+ let _notifyRegistered = false;
176
+
177
+ /**
178
+ * Register a one-time process 'exit' listener that prints the update notice
179
+ * after your application's own output has finished. This is the least
180
+ * intrusive way to show the notification.
181
+ *
182
+ * Called automatically by the package initialiser — you do not need to call
183
+ * this manually unless you want to control the timing.
184
+ *
185
+ * @param {object} [opts]
186
+ * @param {string} [opts.pkg='@svrnsec/pulse']
187
+ */
188
+ export function notifyOnExit(opts = {}) {
189
+ if (!isNode() || _notifyRegistered) return;
190
+ _notifyRegistered = true;
191
+
192
+ const pkg = opts.pkg ?? '@svrnsec/pulse';
193
+ let _latest = null;
194
+
195
+ // Start the background check immediately
196
+ _fetchLatest(pkg).then(v => { _latest = v; }).catch(() => {});
197
+
198
+ // Show the box just before the process exits (after all user output)
199
+ process.on('exit', () => {
200
+ if (_latest && _semverGt(_latest, _currentVersion) && isTTY()) {
201
+ _showUpdateBox(_currentVersion, _latest, pkg);
202
+ }
203
+ });
204
+ }
205
+
206
+ /* ─── banner ─────────────────────────────────────────────────────────────── */
207
+
208
+ /**
209
+ * Print the @svrnsec/pulse ASCII banner to stderr.
210
+ * Called once at package initialisation in Node.js environments.
211
+ */
212
+ export function printBanner() {
213
+ if (!isNode() || !isTTY()) return;
214
+
215
+ const v = ft(C.bgray, `v${_currentVersion}`);
216
+ const tag = ft(C.bmagenta + C.bold, 'SVRN');
217
+ const pkg = ft(C.bwhite + C.bold, ':PULSE');
218
+
219
+ process.stderr.write(
220
+ '\n' +
221
+ ft(C.bgray, ' ┌─────────────────────────────────────┐') + '\n' +
222
+ ft(C.bgray, ' │') + ` ${tag}${pkg} ` + ft(C.bgray, '─ Physical Turing Test │') + '\n' +
223
+ ft(C.bgray, ' │') + ` ${ft(C.bgray, 'Hardware-Biological Symmetry Protocol')} ` + ft(C.bgray, '│') + '\n' +
224
+ ft(C.bgray, ' │') + ` ${ft(C.bcyan, 'npm i @svrnsec/pulse')} ${' '.repeat(16)}${v} ` + ft(C.bgray, '│') + '\n' +
225
+ ft(C.bgray, ' └─────────────────────────────────────┘') + '\n\n'
226
+ );
227
+ }
228
+
229
+ /* ─── _showUpdateBox ─────────────────────────────────────────────────────── */
230
+
231
+ function _showUpdateBox(current, latest, pkg) {
232
+ const arrow = ft(C.bgray, '→');
233
+ const oldV = ft(C.bred, current);
234
+ const newV = ft(C.bgreen + C.bold, latest);
235
+ const cmd = ft(C.bcyan + C.bold, `npm i ${pkg}@latest`);
236
+ const notice = ft(C.byellow + C.bold, ' UPDATE AVAILABLE ');
237
+
238
+ _box([
239
+ notice,
240
+ '',
241
+ ` ${oldV} ${arrow} ${newV}`,
242
+ '',
243
+ ` Run: ${cmd}`,
244
+ '',
245
+ ft(C.bgray, ` Changelog: https://github.com/ayronny14-alt/Svrn-Pulse-Security/releases`),
246
+ ], { borderColor: C.byellow });
247
+ }
248
+
249
+ /* ─── ANSI utilities ─────────────────────────────────────────────────────── */
250
+
251
+ // Measure visible length of string (strip ANSI escape codes)
252
+ function _visLen(s) {
253
+ return _stripAnsi(s).length;
254
+ }
255
+
256
+ function _stripAnsi(s) {
257
+ // eslint-disable-next-line no-control-regex
258
+ return s.replace(/\x1b\[[0-9;]*m/g, '');
259
+ }