@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.
- package/LICENSE +21 -21
- package/README.md +883 -622
- package/SECURITY.md +86 -86
- package/bin/svrnsec-pulse.js +7 -7
- package/dist/{pulse.cjs.js → pulse.cjs} +6379 -6420
- package/dist/pulse.cjs.map +1 -0
- package/dist/pulse.esm.js +6380 -6421
- package/dist/pulse.esm.js.map +1 -1
- package/index.d.ts +895 -846
- package/package.json +185 -165
- package/pkg/pulse_core.js +174 -173
- package/src/analysis/audio.js +213 -213
- package/src/analysis/authenticityAudit.js +408 -390
- package/src/analysis/coherence.js +502 -502
- package/src/analysis/coordinatedBehavior.js +825 -0
- package/src/analysis/heuristic.js +428 -428
- package/src/analysis/jitter.js +446 -446
- package/src/analysis/llm.js +473 -472
- package/src/analysis/populationEntropy.js +404 -403
- package/src/analysis/provider.js +248 -248
- package/src/analysis/refraction.js +392 -0
- package/src/analysis/trustScore.js +356 -356
- package/src/cli/args.js +36 -36
- package/src/cli/commands/scan.js +192 -192
- package/src/cli/runner.js +157 -157
- package/src/collector/adaptive.js +200 -200
- package/src/collector/bio.js +297 -287
- package/src/collector/canvas.js +247 -239
- package/src/collector/dram.js +203 -203
- package/src/collector/enf.js +311 -311
- package/src/collector/entropy.js +195 -195
- package/src/collector/gpu.js +248 -245
- package/src/collector/idleAttestation.js +480 -480
- package/src/collector/sabTimer.js +189 -191
- package/src/fingerprint.js +475 -475
- package/src/index.js +342 -342
- package/src/integrations/react-native.js +462 -459
- package/src/integrations/react.js +184 -185
- package/src/middleware/express.js +155 -155
- package/src/middleware/next.js +174 -175
- package/src/proof/challenge.js +249 -249
- package/src/proof/engagementToken.js +426 -394
- package/src/proof/fingerprint.js +268 -268
- package/src/proof/validator.js +83 -143
- package/src/registry/serializer.js +349 -349
- package/src/terminal.js +263 -263
- package/src/update-notifier.js +259 -264
- package/dist/pulse.cjs.js.map +0 -1
package/src/cli/runner.js
CHANGED
|
@@ -1,157 +1,157 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @svrnsec/pulse CLI — main entry point
|
|
3
|
-
*
|
|
4
|
-
* Commands:
|
|
5
|
-
* scan run the full probe locally
|
|
6
|
-
* challenge generate a signed challenge nonce
|
|
7
|
-
* version show version and check for updates
|
|
8
|
-
* help show this help text
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { parseArgs } from './args.js';
|
|
12
|
-
import { printBanner, checkForUpdate, CURRENT_VERSION } from '../update-notifier.js';
|
|
13
|
-
|
|
14
|
-
const isTTY = () => process.stderr.isTTY && !process.env.NO_COLOR;
|
|
15
|
-
const A = { reset:'\x1b[0m', gray:'\x1b[90m', bcyan:'\x1b[96m',
|
|
16
|
-
bwhite:'\x1b[97m', bmagenta:'\x1b[95m', bgreen:'\x1b[92m', byellow:'\x1b[93m' };
|
|
17
|
-
const c = (code, s) => isTTY() ? `${code}${s}${A.reset}` : s;
|
|
18
|
-
const gy = (s) => c(A.gray, s);
|
|
19
|
-
const cy = (s) => c(A.bcyan, s);
|
|
20
|
-
const wh = (s) => c(A.bwhite, s);
|
|
21
|
-
const gr = (s) => c(A.bgreen, s);
|
|
22
|
-
const ye = (s) => c(A.byellow, s);
|
|
23
|
-
const mg = (s) => c(A.bmagenta, s);
|
|
24
|
-
|
|
25
|
-
function help() {
|
|
26
|
-
process.stderr.write(`
|
|
27
|
-
${mg('SVRN')}${wh(':PULSE')} ${gy(`v${CURRENT_VERSION}`)} ${gy('Physical Turing Test')}
|
|
28
|
-
|
|
29
|
-
${wh('Usage')}
|
|
30
|
-
${cy('npx svrnsec-pulse')} ${gy('<command>')} ${gy('[options]')}
|
|
31
|
-
|
|
32
|
-
${wh('Commands')}
|
|
33
|
-
${cy('scan')} Run the full probe locally and show a TrustScore
|
|
34
|
-
${cy('challenge')} Generate a signed HMAC challenge nonce
|
|
35
|
-
${cy('version')} Show version and check for updates
|
|
36
|
-
${cy('help')} Show this help text
|
|
37
|
-
|
|
38
|
-
${wh('Scan options')}
|
|
39
|
-
${gy('--json')} Output raw JSON (pipe-friendly)
|
|
40
|
-
${gy('--iterations')} Override probe iteration count ${gy('(default: 200)')}
|
|
41
|
-
${gy('--no-banner')} Suppress the banner
|
|
42
|
-
|
|
43
|
-
${wh('Challenge options')}
|
|
44
|
-
${gy('--secret')} Server secret for HMAC signing ${gy('(or set PULSE_SECRET env)')}
|
|
45
|
-
${gy('--ttl')} Challenge TTL in seconds ${gy('(default: 300)')}
|
|
46
|
-
${gy('--json')} Output raw JSON
|
|
47
|
-
|
|
48
|
-
${wh('Examples')}
|
|
49
|
-
${gy('$')} ${cy('npx svrnsec-pulse scan')}
|
|
50
|
-
${gy('$')} ${cy('npx svrnsec-pulse scan --json | jq .trustScore.score')}
|
|
51
|
-
${gy('$')} ${cy('npx svrnsec-pulse challenge --secret $PULSE_SECRET')}
|
|
52
|
-
${gy('$')} ${cy('PULSE_SECRET=mysecret npx svrnsec-pulse challenge --json')}
|
|
53
|
-
|
|
54
|
-
${wh('Environment')}
|
|
55
|
-
${gy('PULSE_SECRET')} Default server secret for challenge signing
|
|
56
|
-
${gy('NO_COLOR')} Disable ANSI colors
|
|
57
|
-
${gy('PULSE_NO_UPDATE')} Disable update notifications
|
|
58
|
-
|
|
59
|
-
${gy(' Docs: https://github.com/ayronny14-alt/Svrn-Pulse-Security#readme')}
|
|
60
|
-
`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function cmdChallenge(args) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const { createChallenge } = await import('../proof/challenge.js');
|
|
87
|
-
const ttlMs = (parseInt(args.get('ttl', '300'), 10) || 300) * 1000;
|
|
88
|
-
const challenge = createChallenge(secret, { ttlMs });
|
|
89
|
-
|
|
90
|
-
if (args.has('json')) {
|
|
91
|
-
process.stdout.write(JSON.stringify(challenge, null, 2) + '\n');
|
|
92
|
-
} else {
|
|
93
|
-
process.stderr.write('\n');
|
|
94
|
-
process.stderr.write(gy(' nonce ') + wh(challenge.nonce) + '\n');
|
|
95
|
-
process.stderr.write(gy(' issuedAt ') + gy(new Date(challenge.issuedAt).toISOString()) + '\n');
|
|
96
|
-
process.stderr.write(gy(' expiresAt ') + gy(new Date(challenge.expiresAt).toISOString()) + '\n');
|
|
97
|
-
process.stderr.write(gy(' sig ') + cy(challenge.sig) + '\n\n');
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function cmdVersion(args) {
|
|
102
|
-
if (args.has('json')) {
|
|
103
|
-
const { latest, updateAvailable } = await checkForUpdate({ silent: true });
|
|
104
|
-
process.stdout.write(JSON.stringify({ version: CURRENT_VERSION, latest, updateAvailable }) + '\n');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
process.stderr.write(`\n${mg('SVRN')}${wh(':PULSE')} ${gy('v' + CURRENT_VERSION)}\n\n`);
|
|
109
|
-
const { latest, updateAvailable } = await checkForUpdate({ silent: true });
|
|
110
|
-
if (updateAvailable) {
|
|
111
|
-
process.stderr.write(ye(` Update available: ${latest}\n`));
|
|
112
|
-
process.stderr.write(gy(` Run: `) + cy('npm i @svrnsec/pulse@latest') + '\n');
|
|
113
|
-
} else if (latest) {
|
|
114
|
-
process.stderr.write(gr(' Up to date.\n'));
|
|
115
|
-
}
|
|
116
|
-
process.stderr.write('\n');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function run(argv = process.argv.slice(2)) {
|
|
120
|
-
const args = parseArgs(argv);
|
|
121
|
-
const cmd = args.command ?? 'help';
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
switch (cmd) {
|
|
125
|
-
case 'scan': {
|
|
126
|
-
const { runScan } = await import('./commands/scan.js');
|
|
127
|
-
await runScan(args);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case 'challenge':
|
|
131
|
-
case 'ch':
|
|
132
|
-
await cmdChallenge(args);
|
|
133
|
-
break;
|
|
134
|
-
case 'version':
|
|
135
|
-
case '-v':
|
|
136
|
-
case '--version':
|
|
137
|
-
await cmdVersion(args);
|
|
138
|
-
break;
|
|
139
|
-
case 'help':
|
|
140
|
-
case '-h':
|
|
141
|
-
case '--help':
|
|
142
|
-
help();
|
|
143
|
-
break;
|
|
144
|
-
default:
|
|
145
|
-
process.stderr.write(ye(`Unknown command: ${cmd}\n`));
|
|
146
|
-
help();
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
} catch (err) {
|
|
150
|
-
process.stderr.write(
|
|
151
|
-
c(A.bmagenta + '\x1b[1m', 'SVRN:PULSE error') + '\n' +
|
|
152
|
-
gy(err.message) + '\n'
|
|
153
|
-
);
|
|
154
|
-
if (process.env.DEBUG) process.stderr.write(err.stack + '\n');
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @svrnsec/pulse CLI — main entry point
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* scan run the full probe locally
|
|
6
|
+
* challenge generate a signed challenge nonce
|
|
7
|
+
* version show version and check for updates
|
|
8
|
+
* help show this help text
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parseArgs } from './args.js';
|
|
12
|
+
import { printBanner, checkForUpdate, CURRENT_VERSION } from '../update-notifier.js';
|
|
13
|
+
|
|
14
|
+
const isTTY = () => process.stderr.isTTY && !process.env.NO_COLOR;
|
|
15
|
+
const A = { reset:'\x1b[0m', gray:'\x1b[90m', bcyan:'\x1b[96m',
|
|
16
|
+
bwhite:'\x1b[97m', bmagenta:'\x1b[95m', bgreen:'\x1b[92m', byellow:'\x1b[93m' };
|
|
17
|
+
const c = (code, s) => isTTY() ? `${code}${s}${A.reset}` : s;
|
|
18
|
+
const gy = (s) => c(A.gray, s);
|
|
19
|
+
const cy = (s) => c(A.bcyan, s);
|
|
20
|
+
const wh = (s) => c(A.bwhite, s);
|
|
21
|
+
const gr = (s) => c(A.bgreen, s);
|
|
22
|
+
const ye = (s) => c(A.byellow, s);
|
|
23
|
+
const mg = (s) => c(A.bmagenta, s);
|
|
24
|
+
|
|
25
|
+
function help() {
|
|
26
|
+
process.stderr.write(`
|
|
27
|
+
${mg('SVRN')}${wh(':PULSE')} ${gy(`v${CURRENT_VERSION}`)} ${gy('Physical Turing Test')}
|
|
28
|
+
|
|
29
|
+
${wh('Usage')}
|
|
30
|
+
${cy('npx svrnsec-pulse')} ${gy('<command>')} ${gy('[options]')}
|
|
31
|
+
|
|
32
|
+
${wh('Commands')}
|
|
33
|
+
${cy('scan')} Run the full probe locally and show a TrustScore
|
|
34
|
+
${cy('challenge')} Generate a signed HMAC challenge nonce
|
|
35
|
+
${cy('version')} Show version and check for updates
|
|
36
|
+
${cy('help')} Show this help text
|
|
37
|
+
|
|
38
|
+
${wh('Scan options')}
|
|
39
|
+
${gy('--json')} Output raw JSON (pipe-friendly)
|
|
40
|
+
${gy('--iterations')} Override probe iteration count ${gy('(default: 200)')}
|
|
41
|
+
${gy('--no-banner')} Suppress the banner
|
|
42
|
+
|
|
43
|
+
${wh('Challenge options')}
|
|
44
|
+
${gy('--secret')} Server secret for HMAC signing ${gy('(or set PULSE_SECRET env)')}
|
|
45
|
+
${gy('--ttl')} Challenge TTL in seconds ${gy('(default: 300)')}
|
|
46
|
+
${gy('--json')} Output raw JSON
|
|
47
|
+
|
|
48
|
+
${wh('Examples')}
|
|
49
|
+
${gy('$')} ${cy('npx svrnsec-pulse scan')}
|
|
50
|
+
${gy('$')} ${cy('npx svrnsec-pulse scan --json | jq .trustScore.score')}
|
|
51
|
+
${gy('$')} ${cy('npx svrnsec-pulse challenge --secret $PULSE_SECRET')}
|
|
52
|
+
${gy('$')} ${cy('PULSE_SECRET=mysecret npx svrnsec-pulse challenge --json')}
|
|
53
|
+
|
|
54
|
+
${wh('Environment')}
|
|
55
|
+
${gy('PULSE_SECRET')} Default server secret for challenge signing
|
|
56
|
+
${gy('NO_COLOR')} Disable ANSI colors
|
|
57
|
+
${gy('PULSE_NO_UPDATE')} Disable update notifications
|
|
58
|
+
|
|
59
|
+
${gy(' Docs: https://github.com/ayronny14-alt/Svrn-Pulse-Security#readme')}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function cmdChallenge(args) {
|
|
64
|
+
if (args.has('generate-secret')) {
|
|
65
|
+
const { generateSecret } = await import('../proof/challenge.js');
|
|
66
|
+
const s = generateSecret();
|
|
67
|
+
if (args.has('json')) {
|
|
68
|
+
process.stdout.write(JSON.stringify({ secret: s }) + '\n');
|
|
69
|
+
} else {
|
|
70
|
+
process.stderr.write(gr('Generated secret (store in env):\n'));
|
|
71
|
+
process.stdout.write(s + '\n');
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const secret = args.get('secret') ?? process.env.PULSE_SECRET;
|
|
77
|
+
if (!secret) {
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
ye('⚠ ') + 'No secret provided.\n' +
|
|
80
|
+
gy(' Pass --secret <value> or set PULSE_SECRET env var.\n') +
|
|
81
|
+
gy(' Generate one: ') + cy('npx svrnsec-pulse challenge --generate-secret\n')
|
|
82
|
+
);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { createChallenge } = await import('../proof/challenge.js');
|
|
87
|
+
const ttlMs = (parseInt(args.get('ttl', '300'), 10) || 300) * 1000;
|
|
88
|
+
const challenge = createChallenge(secret, { ttlMs });
|
|
89
|
+
|
|
90
|
+
if (args.has('json')) {
|
|
91
|
+
process.stdout.write(JSON.stringify(challenge, null, 2) + '\n');
|
|
92
|
+
} else {
|
|
93
|
+
process.stderr.write('\n');
|
|
94
|
+
process.stderr.write(gy(' nonce ') + wh(challenge.nonce) + '\n');
|
|
95
|
+
process.stderr.write(gy(' issuedAt ') + gy(new Date(challenge.issuedAt).toISOString()) + '\n');
|
|
96
|
+
process.stderr.write(gy(' expiresAt ') + gy(new Date(challenge.expiresAt).toISOString()) + '\n');
|
|
97
|
+
process.stderr.write(gy(' sig ') + cy(challenge.sig) + '\n\n');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function cmdVersion(args) {
|
|
102
|
+
if (args.has('json')) {
|
|
103
|
+
const { latest, updateAvailable } = await checkForUpdate({ silent: true });
|
|
104
|
+
process.stdout.write(JSON.stringify({ version: CURRENT_VERSION, latest, updateAvailable }) + '\n');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
process.stderr.write(`\n${mg('SVRN')}${wh(':PULSE')} ${gy('v' + CURRENT_VERSION)}\n\n`);
|
|
109
|
+
const { latest, updateAvailable } = await checkForUpdate({ silent: true });
|
|
110
|
+
if (updateAvailable) {
|
|
111
|
+
process.stderr.write(ye(` Update available: ${latest}\n`));
|
|
112
|
+
process.stderr.write(gy(` Run: `) + cy('npm i @svrnsec/pulse@latest') + '\n');
|
|
113
|
+
} else if (latest) {
|
|
114
|
+
process.stderr.write(gr(' Up to date.\n'));
|
|
115
|
+
}
|
|
116
|
+
process.stderr.write('\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
120
|
+
const args = parseArgs(argv);
|
|
121
|
+
const cmd = args.command ?? 'help';
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
switch (cmd) {
|
|
125
|
+
case 'scan': {
|
|
126
|
+
const { runScan } = await import('./commands/scan.js');
|
|
127
|
+
await runScan(args);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case 'challenge':
|
|
131
|
+
case 'ch':
|
|
132
|
+
await cmdChallenge(args);
|
|
133
|
+
break;
|
|
134
|
+
case 'version':
|
|
135
|
+
case '-v':
|
|
136
|
+
case '--version':
|
|
137
|
+
await cmdVersion(args);
|
|
138
|
+
break;
|
|
139
|
+
case 'help':
|
|
140
|
+
case '-h':
|
|
141
|
+
case '--help':
|
|
142
|
+
help();
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
process.stderr.write(ye(`Unknown command: ${cmd}\n`));
|
|
146
|
+
help();
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
process.stderr.write(
|
|
151
|
+
c(A.bmagenta + '\x1b[1m', 'SVRN:PULSE error') + '\n' +
|
|
152
|
+
gy(err.message) + '\n'
|
|
153
|
+
);
|
|
154
|
+
if (process.env.DEBUG) process.stderr.write(err.stack + '\n');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|