@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
|
@@ -1,185 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @
|
|
3
|
-
*
|
|
4
|
-
* import { usePulse } from '@
|
|
5
|
-
*
|
|
6
|
-
* const {
|
|
7
|
-
* run, reset,
|
|
8
|
-
* stage, pct, vmConf, hwConf, earlyVerdict,
|
|
9
|
-
* proof, result,
|
|
10
|
-
* isRunning, isReady, error,
|
|
11
|
-
* } = usePulse({ apiKey: 'sk_live_...' });
|
|
12
|
-
*
|
|
13
|
-
* // Or self-hosted:
|
|
14
|
-
* const { run, proof, result } = usePulse({
|
|
15
|
-
* challengeUrl: '/api/pulse/challenge',
|
|
16
|
-
* verifyUrl: '/api/pulse/verify',
|
|
17
|
-
* });
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { useState, useCallback, useRef } from 'react';
|
|
21
|
-
|
|
22
|
-
// Lazy import — only loaded in browser, allows tree-shaking in SSR builds
|
|
23
|
-
let _pulseModule = null;
|
|
24
|
-
async function getPulse() {
|
|
25
|
-
if (!_pulseModule) {
|
|
26
|
-
_pulseModule = await import('../index.js');
|
|
27
|
-
}
|
|
28
|
-
return _pulseModule.pulse;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {object} opts
|
|
33
|
-
* @param {string} [opts.apiKey] - hosted API key (zero-config)
|
|
34
|
-
* @param {string} [opts.apiUrl] - hosted API base URL (default: https://api.sovereign.dev)
|
|
35
|
-
* @param {string} [opts.challengeUrl] - self-hosted challenge endpoint
|
|
36
|
-
* @param {string} [opts.verifyUrl] - self-hosted verify endpoint
|
|
37
|
-
* @param {number} [opts.iterations=200]
|
|
38
|
-
* @param {number} [opts.bioWindowMs=3000]
|
|
39
|
-
* @param {boolean} [opts.adaptive=true]
|
|
40
|
-
* @param {boolean} [opts.autoRun=false] - run immediately on mount
|
|
41
|
-
* @param {Function} [opts.onResult] - callback when result is ready
|
|
42
|
-
* @param {Function} [opts.onError] - callback on error
|
|
43
|
-
*/
|
|
44
|
-
export function usePulse(opts = {}) {
|
|
45
|
-
const {
|
|
46
|
-
apiKey,
|
|
47
|
-
apiUrl = 'https://api.sovereign.dev',
|
|
48
|
-
challengeUrl,
|
|
49
|
-
verifyUrl,
|
|
50
|
-
iterations = 200,
|
|
51
|
-
bioWindowMs = 3000,
|
|
52
|
-
adaptive = true,
|
|
53
|
-
autoRun = false,
|
|
54
|
-
onResult,
|
|
55
|
-
onError,
|
|
56
|
-
} = opts;
|
|
57
|
-
|
|
58
|
-
// ── State ────────────────────────────────────────────────────────────────
|
|
59
|
-
const [stage, setStage] = useState(null);
|
|
60
|
-
const [pct, setPct] = useState(0);
|
|
61
|
-
const [vmConf, setVmConf] = useState(0);
|
|
62
|
-
const [hwConf, setHwConf] = useState(0);
|
|
63
|
-
const [earlyVerdict, setEarlyVerdict]= useState(null);
|
|
64
|
-
const [proof, setProof] = useState(null);
|
|
65
|
-
const [result, setResult] = useState(null);
|
|
66
|
-
const [isRunning, setIsRunning] = useState(false);
|
|
67
|
-
const [error, setError] = useState(null);
|
|
68
|
-
|
|
69
|
-
const abortRef = useRef(null);
|
|
70
|
-
const hasAutoRun = useRef(false);
|
|
71
|
-
|
|
72
|
-
// ── run() ────────────────────────────────────────────────────────────────
|
|
73
|
-
const run = useCallback(async () => {
|
|
74
|
-
if (isRunning) return;
|
|
75
|
-
|
|
76
|
-
// Reset
|
|
77
|
-
setStage(null); setPct(0); setVmConf(0); setHwConf(0);
|
|
78
|
-
setEarlyVerdict(null); setProof(null); setResult(null);
|
|
79
|
-
setError(null); setIsRunning(true);
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
// 1. Resolve nonce
|
|
83
|
-
let nonce;
|
|
84
|
-
if (apiKey) {
|
|
85
|
-
const res = await fetch(`${apiUrl}/v1/challenge`, {
|
|
86
|
-
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
87
|
-
});
|
|
88
|
-
if (!res.ok) throw new Error(`Challenge failed: ${res.status}`);
|
|
89
|
-
({ nonce } = await res.json());
|
|
90
|
-
} else if (challengeUrl) {
|
|
91
|
-
const res = await fetch(challengeUrl);
|
|
92
|
-
if (!res.ok) throw new Error(`Challenge failed: ${res.status}`);
|
|
93
|
-
({ nonce } = await res.json());
|
|
94
|
-
} else {
|
|
95
|
-
throw new Error(
|
|
96
|
-
'usePulse requires either apiKey or challengeUrl. ' +
|
|
97
|
-
'Pass apiKey for the hosted API, or challengeUrl + verifyUrl for self-hosted.'
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 2. Run the probe
|
|
102
|
-
const pulse = await getPulse();
|
|
103
|
-
const commitment = await pulse({
|
|
104
|
-
nonce,
|
|
105
|
-
iterations,
|
|
106
|
-
bioWindowMs,
|
|
107
|
-
adaptive,
|
|
108
|
-
onProgress: (s, meta = {}) => {
|
|
109
|
-
setStage(s);
|
|
110
|
-
if (s === 'entropy_batch' && meta) {
|
|
111
|
-
if (meta.pct != null) setPct(meta.pct);
|
|
112
|
-
if (meta.vmConf != null) setVmConf(meta.vmConf);
|
|
113
|
-
if (meta.hwConf != null) setHwConf(meta.hwConf);
|
|
114
|
-
if (meta.earlyVerdict != null) setEarlyVerdict(meta.earlyVerdict);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
setProof(commitment);
|
|
120
|
-
setPct(100);
|
|
121
|
-
|
|
122
|
-
// 3. Verify (hosted or self-hosted)
|
|
123
|
-
if (apiKey || verifyUrl) {
|
|
124
|
-
const url = apiKey ? `${apiUrl}/v1/verify` : verifyUrl;
|
|
125
|
-
const headers = {
|
|
126
|
-
'Content-Type': 'application/json',
|
|
127
|
-
...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}),
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const res = await fetch(url, {
|
|
131
|
-
method: 'POST',
|
|
132
|
-
headers,
|
|
133
|
-
body: JSON.stringify({ payload: commitment.payload, hash: commitment.hash }),
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
isRunning,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @svrnsec/pulse — React Hook
|
|
3
|
+
*
|
|
4
|
+
* import { usePulse } from '@svrnsec/pulse/react';
|
|
5
|
+
*
|
|
6
|
+
* const {
|
|
7
|
+
* run, reset,
|
|
8
|
+
* stage, pct, vmConf, hwConf, earlyVerdict,
|
|
9
|
+
* proof, result,
|
|
10
|
+
* isRunning, isReady, error,
|
|
11
|
+
* } = usePulse({ apiKey: 'sk_live_...' });
|
|
12
|
+
*
|
|
13
|
+
* // Or self-hosted:
|
|
14
|
+
* const { run, proof, result } = usePulse({
|
|
15
|
+
* challengeUrl: '/api/pulse/challenge',
|
|
16
|
+
* verifyUrl: '/api/pulse/verify',
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
21
|
+
|
|
22
|
+
// Lazy import — only loaded in browser, allows tree-shaking in SSR builds
|
|
23
|
+
let _pulseModule = null;
|
|
24
|
+
async function getPulse() {
|
|
25
|
+
if (!_pulseModule) {
|
|
26
|
+
_pulseModule = await import('../index.js');
|
|
27
|
+
}
|
|
28
|
+
return _pulseModule.pulse;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {object} opts
|
|
33
|
+
* @param {string} [opts.apiKey] - hosted API key (zero-config)
|
|
34
|
+
* @param {string} [opts.apiUrl] - hosted API base URL (default: https://api.sovereign.dev)
|
|
35
|
+
* @param {string} [opts.challengeUrl] - self-hosted challenge endpoint
|
|
36
|
+
* @param {string} [opts.verifyUrl] - self-hosted verify endpoint
|
|
37
|
+
* @param {number} [opts.iterations=200]
|
|
38
|
+
* @param {number} [opts.bioWindowMs=3000]
|
|
39
|
+
* @param {boolean} [opts.adaptive=true]
|
|
40
|
+
* @param {boolean} [opts.autoRun=false] - run immediately on mount
|
|
41
|
+
* @param {Function} [opts.onResult] - callback when result is ready
|
|
42
|
+
* @param {Function} [opts.onError] - callback on error
|
|
43
|
+
*/
|
|
44
|
+
export function usePulse(opts = {}) {
|
|
45
|
+
const {
|
|
46
|
+
apiKey,
|
|
47
|
+
apiUrl = 'https://api.sovereign.dev',
|
|
48
|
+
challengeUrl,
|
|
49
|
+
verifyUrl,
|
|
50
|
+
iterations = 200,
|
|
51
|
+
bioWindowMs = 3000,
|
|
52
|
+
adaptive = true,
|
|
53
|
+
autoRun = false,
|
|
54
|
+
onResult,
|
|
55
|
+
onError,
|
|
56
|
+
} = opts;
|
|
57
|
+
|
|
58
|
+
// ── State ────────────────────────────────────────────────────────────────
|
|
59
|
+
const [stage, setStage] = useState(null);
|
|
60
|
+
const [pct, setPct] = useState(0);
|
|
61
|
+
const [vmConf, setVmConf] = useState(0);
|
|
62
|
+
const [hwConf, setHwConf] = useState(0);
|
|
63
|
+
const [earlyVerdict, setEarlyVerdict]= useState(null);
|
|
64
|
+
const [proof, setProof] = useState(null);
|
|
65
|
+
const [result, setResult] = useState(null);
|
|
66
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
67
|
+
const [error, setError] = useState(null);
|
|
68
|
+
|
|
69
|
+
const abortRef = useRef(null);
|
|
70
|
+
const hasAutoRun = useRef(false);
|
|
71
|
+
|
|
72
|
+
// ── run() ────────────────────────────────────────────────────────────────
|
|
73
|
+
const run = useCallback(async () => {
|
|
74
|
+
if (isRunning) return;
|
|
75
|
+
|
|
76
|
+
// Reset
|
|
77
|
+
setStage(null); setPct(0); setVmConf(0); setHwConf(0);
|
|
78
|
+
setEarlyVerdict(null); setProof(null); setResult(null);
|
|
79
|
+
setError(null); setIsRunning(true);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// 1. Resolve nonce
|
|
83
|
+
let nonce;
|
|
84
|
+
if (apiKey) {
|
|
85
|
+
const res = await fetch(`${apiUrl}/v1/challenge`, {
|
|
86
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) throw new Error(`Challenge failed: ${res.status}`);
|
|
89
|
+
({ nonce } = await res.json());
|
|
90
|
+
} else if (challengeUrl) {
|
|
91
|
+
const res = await fetch(challengeUrl);
|
|
92
|
+
if (!res.ok) throw new Error(`Challenge failed: ${res.status}`);
|
|
93
|
+
({ nonce } = await res.json());
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'usePulse requires either apiKey or challengeUrl. ' +
|
|
97
|
+
'Pass apiKey for the hosted API, or challengeUrl + verifyUrl for self-hosted.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 2. Run the probe
|
|
102
|
+
const pulse = await getPulse();
|
|
103
|
+
const commitment = await pulse({
|
|
104
|
+
nonce,
|
|
105
|
+
iterations,
|
|
106
|
+
bioWindowMs,
|
|
107
|
+
adaptive,
|
|
108
|
+
onProgress: (s, meta = {}) => {
|
|
109
|
+
setStage(s);
|
|
110
|
+
if (s === 'entropy_batch' && meta) {
|
|
111
|
+
if (meta.pct != null) setPct(meta.pct);
|
|
112
|
+
if (meta.vmConf != null) setVmConf(meta.vmConf);
|
|
113
|
+
if (meta.hwConf != null) setHwConf(meta.hwConf);
|
|
114
|
+
if (meta.earlyVerdict != null) setEarlyVerdict(meta.earlyVerdict);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
setProof(commitment);
|
|
120
|
+
setPct(100);
|
|
121
|
+
|
|
122
|
+
// 3. Verify (hosted or self-hosted)
|
|
123
|
+
if (apiKey || verifyUrl) {
|
|
124
|
+
const url = apiKey ? `${apiUrl}/v1/verify` : verifyUrl;
|
|
125
|
+
const headers = {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {}),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const res = await fetch(url, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers,
|
|
133
|
+
body: JSON.stringify({ payload: commitment.payload, hash: commitment.hash }),
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok) throw new Error('Verify failed: ' + res.status);
|
|
136
|
+
const verifyResult = await res.json();
|
|
137
|
+
setResult(verifyResult);
|
|
138
|
+
onResult?.(verifyResult, commitment);
|
|
139
|
+
} else {
|
|
140
|
+
onResult?.(null, commitment);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} catch (err) {
|
|
144
|
+
setError(err);
|
|
145
|
+
onError?.(err);
|
|
146
|
+
} finally {
|
|
147
|
+
setIsRunning(false);
|
|
148
|
+
}
|
|
149
|
+
}, [isRunning, apiKey, apiUrl, challengeUrl, verifyUrl, iterations, bioWindowMs, adaptive, onResult, onError]);
|
|
150
|
+
|
|
151
|
+
// ── reset() ──────────────────────────────────────────────────────────────
|
|
152
|
+
const reset = useCallback(() => {
|
|
153
|
+
setStage(null); setPct(0); setVmConf(0); setHwConf(0);
|
|
154
|
+
setEarlyVerdict(null); setProof(null); setResult(null);
|
|
155
|
+
setIsRunning(false); setError(null);
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
// ── autoRun on mount ──────────────────────────────────────────────────────
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (autoRun && !hasAutoRun.current) {
|
|
161
|
+
hasAutoRun.current = true;
|
|
162
|
+
run();
|
|
163
|
+
}
|
|
164
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
// Actions
|
|
168
|
+
run,
|
|
169
|
+
reset,
|
|
170
|
+
// Live probe state
|
|
171
|
+
stage,
|
|
172
|
+
pct,
|
|
173
|
+
vmConf,
|
|
174
|
+
hwConf,
|
|
175
|
+
earlyVerdict,
|
|
176
|
+
// Results
|
|
177
|
+
proof,
|
|
178
|
+
result,
|
|
179
|
+
// Status
|
|
180
|
+
isRunning,
|
|
181
|
+
isReady: !isRunning && (proof != null || error != null),
|
|
182
|
+
error,
|
|
183
|
+
};
|
|
184
|
+
}
|