blindseal 0.5.0 → 0.5.1
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/dashboard/assets/blindseal_verifier_bg-BYF7rQG1.wasm +0 -0
- package/dashboard/assets/geist-400-gapTbOY8.woff2 +0 -0
- package/dashboard/assets/geist-500-uokXdC-Q.woff2 +0 -0
- package/dashboard/assets/geist-600-DFOURf8L.woff2 +0 -0
- package/dashboard/assets/geist-mono-400-DTRLJnHl.woff2 +0 -0
- package/dashboard/assets/geist-mono-600-V-KvD_pi.woff2 +0 -0
- package/dashboard/assets/index-ErSVkiAF.css +1 -0
- package/dashboard/assets/index-cOSTL8eT.js +1 -0
- package/dashboard/assets/index-p0eB3k2y.js +70 -0
- package/dashboard/index.html +76 -0
- package/dist/attest-VWYZAOLY.js +346 -0
- package/dist/{audit-C756XFXR.js → audit-G4DDMAXH.js} +1 -1
- package/dist/chunk-6EU4GH4P.js +513 -0
- package/dist/chunk-B44GZXDX.js +136 -0
- package/dist/chunk-CIUQ6WHJ.js +88 -0
- package/dist/{chunk-BTIXDNTV.js → chunk-DJL2E6RF.js} +69 -6
- package/dist/{chunk-PQ457W6T.js → chunk-VSOUJM6X.js} +1 -0
- package/dist/{chunk-JJY62YXJ.js → chunk-ZZTWGFH2.js} +0 -20
- package/dist/dashboard-J6BA7OBL.js +105 -0
- package/dist/decide.js +1 -1
- package/dist/index.js +122 -1307
- package/dist/{init.js → init-ZP3SLHM3.js} +122 -36
- package/dist/run-YQBNEPSD.js +17 -0
- package/dist/{submit-2UALCRX2.js → submit-D6MQTAXT.js} +1 -1
- package/dist/verify-RISAGKXC.js +98 -0
- package/fixtures/attestation-sample.json +25 -0
- package/fixtures/attestation-tampered.json +29 -0
- package/fixtures/attestations.json +174 -0
- package/fixtures/audit.json +1810 -0
- package/fixtures/decisions.json +9899 -0
- package/fixtures/policy.json +216 -0
- package/package.json +8 -8
- package/dist/attest-QLIE2OSQ.js +0 -195
- package/dist/verify-HO4IU7XV.js +0 -45
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isTrustedSource,
|
|
10
10
|
loadConfig,
|
|
11
11
|
mergeDecisions
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-ZZTWGFH2.js";
|
|
13
13
|
import {
|
|
14
14
|
createClientFromEnv,
|
|
15
15
|
runPipeline,
|
|
@@ -19,16 +19,10 @@ import {
|
|
|
19
19
|
rankVerdict
|
|
20
20
|
} from "./chunk-XV57KLEO.js";
|
|
21
21
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "./chunk-PQ457W6T.js";
|
|
22
|
+
packageVersion
|
|
23
|
+
} from "./chunk-CIUQ6WHJ.js";
|
|
25
24
|
import "./chunk-PDX44BCA.js";
|
|
26
25
|
|
|
27
|
-
// src/index.ts
|
|
28
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
29
|
-
import path7 from "path";
|
|
30
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
31
|
-
|
|
32
26
|
// src/transports/stdio.ts
|
|
33
27
|
import { spawn } from "child_process";
|
|
34
28
|
import { createInterface } from "readline";
|
|
@@ -207,116 +201,6 @@ var ManifestChecker = class {
|
|
|
207
201
|
}
|
|
208
202
|
};
|
|
209
203
|
|
|
210
|
-
// src/telemetry/event.ts
|
|
211
|
-
import { createHash as createHash2, randomUUID } from "crypto";
|
|
212
|
-
function sha256Hex(s) {
|
|
213
|
-
return createHash2("sha256").update(s).digest("hex");
|
|
214
|
-
}
|
|
215
|
-
function newId() {
|
|
216
|
-
return randomUUID();
|
|
217
|
-
}
|
|
218
|
-
function resolveInstallId() {
|
|
219
|
-
const explicit = process.env.BLINDSEAL_TELEMETRY_INSTALL_ID;
|
|
220
|
-
if (explicit && explicit.length > 0) return sha256Hex(explicit).slice(0, 16);
|
|
221
|
-
return randomUUID();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// src/telemetry/reporter.ts
|
|
225
|
-
function createReporter(config, fetchImpl = globalThis.fetch) {
|
|
226
|
-
if (!config.enabled || !config.url) {
|
|
227
|
-
return {
|
|
228
|
-
enabled: false,
|
|
229
|
-
send() {
|
|
230
|
-
},
|
|
231
|
-
async flush() {
|
|
232
|
-
},
|
|
233
|
-
async shutdown() {
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
const installId = resolveInstallId();
|
|
238
|
-
const source = process.env.BLINDSEAL_TELEMETRY_SOURCE?.trim() || void 0;
|
|
239
|
-
let buffer = [];
|
|
240
|
-
let timer = null;
|
|
241
|
-
let inFlight = null;
|
|
242
|
-
let errorAnnouncedAt = 0;
|
|
243
|
-
let shutdownCalled = false;
|
|
244
|
-
function scheduleFlush() {
|
|
245
|
-
if (timer || shutdownCalled) return;
|
|
246
|
-
timer = setTimeout(() => {
|
|
247
|
-
timer = null;
|
|
248
|
-
void flushInternal();
|
|
249
|
-
}, config.flushIntervalMs);
|
|
250
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
251
|
-
}
|
|
252
|
-
async function flushInternal() {
|
|
253
|
-
if (buffer.length === 0) return;
|
|
254
|
-
const batch = buffer;
|
|
255
|
-
buffer = [];
|
|
256
|
-
const headers = { "Content-Type": "application/json" };
|
|
257
|
-
if (config.secret) headers.Authorization = `Bearer ${config.secret}`;
|
|
258
|
-
try {
|
|
259
|
-
const resp = await fetchImpl(config.url, {
|
|
260
|
-
method: "POST",
|
|
261
|
-
headers,
|
|
262
|
-
body: JSON.stringify({ events: batch })
|
|
263
|
-
});
|
|
264
|
-
if (!resp.ok) {
|
|
265
|
-
announceError(`HTTP ${resp.status}`);
|
|
266
|
-
}
|
|
267
|
-
} catch (err) {
|
|
268
|
-
announceError(err instanceof Error ? err.message : String(err));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
function announceError(msg) {
|
|
272
|
-
const now = Date.now();
|
|
273
|
-
if (now - errorAnnouncedAt < 6e4) return;
|
|
274
|
-
errorAnnouncedAt = now;
|
|
275
|
-
process.stderr.write(`blindseal: telemetry POST failed (${msg}); dropping events
|
|
276
|
-
`);
|
|
277
|
-
}
|
|
278
|
-
return {
|
|
279
|
-
enabled: true,
|
|
280
|
-
send(partial) {
|
|
281
|
-
if (shutdownCalled) return;
|
|
282
|
-
const event = {
|
|
283
|
-
...partial,
|
|
284
|
-
id: newId(),
|
|
285
|
-
ts: Date.now(),
|
|
286
|
-
installId,
|
|
287
|
-
...source ? { source } : {}
|
|
288
|
-
};
|
|
289
|
-
buffer.push(event);
|
|
290
|
-
if (buffer.length >= config.batchSize) {
|
|
291
|
-
if (timer) {
|
|
292
|
-
clearTimeout(timer);
|
|
293
|
-
timer = null;
|
|
294
|
-
}
|
|
295
|
-
inFlight = (inFlight ?? Promise.resolve()).then(flushInternal);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
scheduleFlush();
|
|
299
|
-
},
|
|
300
|
-
async flush() {
|
|
301
|
-
if (timer) {
|
|
302
|
-
clearTimeout(timer);
|
|
303
|
-
timer = null;
|
|
304
|
-
}
|
|
305
|
-
await inFlight;
|
|
306
|
-
await flushInternal();
|
|
307
|
-
},
|
|
308
|
-
async shutdown() {
|
|
309
|
-
shutdownCalled = true;
|
|
310
|
-
if (timer) {
|
|
311
|
-
clearTimeout(timer);
|
|
312
|
-
timer = null;
|
|
313
|
-
}
|
|
314
|
-
await inFlight;
|
|
315
|
-
await flushInternal();
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
204
|
// src/audit/log.ts
|
|
321
205
|
import { createWriteStream, mkdirSync as mkdirSync2 } from "fs";
|
|
322
206
|
import path2 from "path";
|
|
@@ -367,6 +251,10 @@ function preview(text, max = 200) {
|
|
|
367
251
|
}
|
|
368
252
|
|
|
369
253
|
// src/transports/shared.ts
|
|
254
|
+
import { createHash as createHash2 } from "crypto";
|
|
255
|
+
function sha256Hex(s) {
|
|
256
|
+
return createHash2("sha256").update(s).digest("hex");
|
|
257
|
+
}
|
|
370
258
|
function tryParse(line) {
|
|
371
259
|
try {
|
|
372
260
|
return JSON.parse(line);
|
|
@@ -493,7 +381,6 @@ function startProxy(cmd, args) {
|
|
|
493
381
|
const taint = new TaintStore(config.capability.windowSize);
|
|
494
382
|
const tradeRate = new TradeRateState();
|
|
495
383
|
const manifest = config.manifest.mode !== "off" ? new ManifestChecker(cmd, args, config.manifest) : null;
|
|
496
|
-
const telemetry = createReporter(config.telemetry);
|
|
497
384
|
const audit = createAuditLogger(config.audit);
|
|
498
385
|
const scanStore = createScanStore(config.persistence);
|
|
499
386
|
if (scanStore.enabled) {
|
|
@@ -502,11 +389,6 @@ function startProxy(cmd, args) {
|
|
|
502
389
|
`
|
|
503
390
|
);
|
|
504
391
|
}
|
|
505
|
-
if (config.telemetry.bannerOnStart) {
|
|
506
|
-
process.stderr.write(
|
|
507
|
-
"blindseal: telemetry is enabled (sha256 hashes only, no raw content) \u2014 see PRIVACY.md; set BLINDSEAL_TELEMETRY=0 to disable\n"
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
392
|
const child = spawn(cmd, args, { stdio: ["pipe", "pipe", "inherit"] });
|
|
511
393
|
child.on("error", (err) => {
|
|
512
394
|
process.stderr.write(`blindseal: failed to spawn ${cmd}: ${err.message}
|
|
@@ -538,16 +420,6 @@ function startProxy(cmd, args) {
|
|
|
538
420
|
decideCapability(name, params?.arguments, taint, config.capability),
|
|
539
421
|
decideTradePolicy(name, params?.arguments, taint, config.tradePolicy, tradeRate)
|
|
540
422
|
);
|
|
541
|
-
if (decision.action !== "allow" && telemetry.enabled) {
|
|
542
|
-
telemetry.send({
|
|
543
|
-
type: "capability",
|
|
544
|
-
action: decision.action,
|
|
545
|
-
toolName: name,
|
|
546
|
-
argsHash: sha256Hex(JSON.stringify(params?.arguments ?? null)),
|
|
547
|
-
sourceTools: (decision.taintSources ?? []).map((s) => s.toolName),
|
|
548
|
-
matchedPattern: decision.matchedPattern
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
423
|
if (audit.enabled) {
|
|
552
424
|
audit.log({
|
|
553
425
|
type: "capability",
|
|
@@ -609,15 +481,6 @@ function startProxy(cmd, args) {
|
|
|
609
481
|
verdict,
|
|
610
482
|
config.manifest.mode === "strict" ? "strict" : "on"
|
|
611
483
|
);
|
|
612
|
-
if (telemetry.enabled) {
|
|
613
|
-
telemetry.send({
|
|
614
|
-
type: "manifest",
|
|
615
|
-
status: verdict.status,
|
|
616
|
-
serverKey: verdict.serverKey,
|
|
617
|
-
fingerprint: verdict.fingerprint.fingerprint,
|
|
618
|
-
changes: verdict.changes
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
484
|
if (audit.enabled) {
|
|
622
485
|
audit.log({
|
|
623
486
|
type: "manifest",
|
|
@@ -639,17 +502,6 @@ function startProxy(cmd, args) {
|
|
|
639
502
|
`
|
|
640
503
|
);
|
|
641
504
|
}
|
|
642
|
-
if (telemetry.enabled) {
|
|
643
|
-
telemetry.send({
|
|
644
|
-
type: "detection",
|
|
645
|
-
layer: outcome.layer ?? null,
|
|
646
|
-
verdict: outcome.verdict,
|
|
647
|
-
confidence: outcome.result?.confidence ?? 0,
|
|
648
|
-
toolName,
|
|
649
|
-
contentHash: outcome.contentHash,
|
|
650
|
-
patterns: outcome.result?.detectedPatterns ?? []
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
505
|
if (audit.enabled) {
|
|
654
506
|
const previewText = msg.result.content.map((c) => typeof c.text === "string" ? c.text : "").join("\n");
|
|
655
507
|
audit.log({
|
|
@@ -695,7 +547,6 @@ function startProxy(cmd, args) {
|
|
|
695
547
|
});
|
|
696
548
|
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
697
549
|
process.on(sig, () => {
|
|
698
|
-
void telemetry.shutdown();
|
|
699
550
|
void audit.shutdown();
|
|
700
551
|
try {
|
|
701
552
|
scanStore.close();
|
|
@@ -848,7 +699,6 @@ function startHttpProxy(opts) {
|
|
|
848
699
|
const taint = new TaintStore(config.capability.windowSize);
|
|
849
700
|
const tradeRate = new TradeRateState();
|
|
850
701
|
const manifest = config.manifest.mode !== "off" ? new ManifestChecker("http", [opts.upstream], config.manifest) : null;
|
|
851
|
-
const telemetry = createReporter(config.telemetry);
|
|
852
702
|
const audit = createAuditLogger(config.audit);
|
|
853
703
|
const scanStore = createScanStore(config.persistence);
|
|
854
704
|
if (scanStore.enabled) {
|
|
@@ -857,11 +707,6 @@ function startHttpProxy(opts) {
|
|
|
857
707
|
`
|
|
858
708
|
);
|
|
859
709
|
}
|
|
860
|
-
if (config.telemetry.bannerOnStart) {
|
|
861
|
-
process.stderr.write(
|
|
862
|
-
"blindseal: telemetry is enabled (sha256 hashes only, no raw content) \u2014 see PRIVACY.md; set BLINDSEAL_TELEMETRY=0 to disable\n"
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
710
|
const upstreamUrl = opts.upstream;
|
|
866
711
|
const server = createServer(async (req, res) => {
|
|
867
712
|
if (req.method === "GET" && (req.url === "/health" || req.url === "/")) {
|
|
@@ -892,16 +737,6 @@ function startHttpProxy(opts) {
|
|
|
892
737
|
decideCapability(toolName, toolArgs, taint, config.capability),
|
|
893
738
|
decideTradePolicy(toolName, toolArgs, taint, config.tradePolicy, tradeRate)
|
|
894
739
|
);
|
|
895
|
-
if (decision.action !== "allow" && telemetry.enabled) {
|
|
896
|
-
telemetry.send({
|
|
897
|
-
type: "capability",
|
|
898
|
-
action: decision.action,
|
|
899
|
-
toolName,
|
|
900
|
-
argsHash: sha256Hex(JSON.stringify(toolArgs ?? null)),
|
|
901
|
-
sourceTools: (decision.taintSources ?? []).map((s) => s.toolName),
|
|
902
|
-
matchedPattern: decision.matchedPattern
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
740
|
if (audit.enabled) {
|
|
906
741
|
audit.log({
|
|
907
742
|
type: "capability",
|
|
@@ -972,17 +807,6 @@ function startHttpProxy(opts) {
|
|
|
972
807
|
`
|
|
973
808
|
);
|
|
974
809
|
}
|
|
975
|
-
if (telemetry.enabled) {
|
|
976
|
-
telemetry.send({
|
|
977
|
-
type: "detection",
|
|
978
|
-
layer: outcome.layer ?? null,
|
|
979
|
-
verdict: outcome.verdict,
|
|
980
|
-
confidence: outcome.result?.confidence ?? 0,
|
|
981
|
-
toolName: scannedTool,
|
|
982
|
-
contentHash: outcome.contentHash,
|
|
983
|
-
patterns: outcome.result?.detectedPatterns ?? []
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
810
|
if (audit.enabled) {
|
|
987
811
|
const previewText = scannedMsg.result.content.map((c) => typeof c.text === "string" ? c.text : "").join("\n");
|
|
988
812
|
audit.log({
|
|
@@ -1074,15 +898,6 @@ function startHttpProxy(opts) {
|
|
|
1074
898
|
`);
|
|
1075
899
|
if (config.manifest.mode === "strict") process.exit(3);
|
|
1076
900
|
}
|
|
1077
|
-
if (telemetry.enabled) {
|
|
1078
|
-
telemetry.send({
|
|
1079
|
-
type: "manifest",
|
|
1080
|
-
status: verdict.status,
|
|
1081
|
-
serverKey: verdict.serverKey,
|
|
1082
|
-
fingerprint: verdict.fingerprint.fingerprint,
|
|
1083
|
-
changes: verdict.changes
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
901
|
if (audit.enabled) {
|
|
1087
902
|
audit.log({
|
|
1088
903
|
type: "manifest",
|
|
@@ -1103,17 +918,6 @@ function startHttpProxy(opts) {
|
|
|
1103
918
|
`
|
|
1104
919
|
);
|
|
1105
920
|
}
|
|
1106
|
-
if (telemetry.enabled) {
|
|
1107
|
-
telemetry.send({
|
|
1108
|
-
type: "detection",
|
|
1109
|
-
layer: outcome.layer ?? null,
|
|
1110
|
-
verdict: outcome.verdict,
|
|
1111
|
-
confidence: outcome.result?.confidence ?? 0,
|
|
1112
|
-
toolName,
|
|
1113
|
-
contentHash: outcome.contentHash,
|
|
1114
|
-
patterns: outcome.result?.detectedPatterns ?? []
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
921
|
if (audit.enabled) {
|
|
1118
922
|
const previewText = respMsg.result.content.map((c) => typeof c.text === "string" ? c.text : "").join("\n");
|
|
1119
923
|
audit.log({
|
|
@@ -1174,7 +978,6 @@ function startHttpProxy(opts) {
|
|
|
1174
978
|
});
|
|
1175
979
|
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
1176
980
|
process.on(sig, () => {
|
|
1177
|
-
void telemetry.shutdown();
|
|
1178
981
|
void audit.shutdown();
|
|
1179
982
|
try {
|
|
1180
983
|
scanStore.close();
|
|
@@ -1240,25 +1043,18 @@ function parseRequestBody(raw) {
|
|
|
1240
1043
|
return out;
|
|
1241
1044
|
}
|
|
1242
1045
|
function startScanHttp(opts) {
|
|
1243
|
-
|
|
1244
|
-
const telemetry = createReporter(config.telemetry);
|
|
1245
|
-
if (config.telemetry.bannerOnStart) {
|
|
1246
|
-
process.stderr.write(
|
|
1247
|
-
"blindseal: telemetry is enabled (sha256 hashes only, no raw content) \u2014 see PRIVACY.md; set BLINDSEAL_TELEMETRY=0 to disable\n"
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1046
|
+
loadConfig();
|
|
1250
1047
|
const defaultServerUrl = opts.defaultServerUrl ?? "scan-http:blindseal";
|
|
1251
1048
|
const server = createServer2(async (req, res) => {
|
|
1252
1049
|
const url = req.url ?? "/";
|
|
1253
|
-
const
|
|
1254
|
-
if (req.method === "GET" && (
|
|
1050
|
+
const path5 = url.split("?")[0] ?? "/";
|
|
1051
|
+
if (req.method === "GET" && (path5 === "/health" || path5 === "/")) {
|
|
1255
1052
|
sendJson2(res, 200, {
|
|
1256
|
-
ok: true
|
|
1257
|
-
telemetryEnabled: telemetry.enabled
|
|
1053
|
+
ok: true
|
|
1258
1054
|
});
|
|
1259
1055
|
return;
|
|
1260
1056
|
}
|
|
1261
|
-
if (req.method !== "POST" ||
|
|
1057
|
+
if (req.method !== "POST" || path5 !== "/scan") {
|
|
1262
1058
|
sendJson2(res, 404, { error: "not found" });
|
|
1263
1059
|
return;
|
|
1264
1060
|
}
|
|
@@ -1288,17 +1084,6 @@ function startScanHttp(opts) {
|
|
|
1288
1084
|
sendJson2(res, 500, { error: "pipeline failed" });
|
|
1289
1085
|
return;
|
|
1290
1086
|
}
|
|
1291
|
-
if (telemetry.enabled) {
|
|
1292
|
-
telemetry.send({
|
|
1293
|
-
type: "detection",
|
|
1294
|
-
layer: result.layer ?? null,
|
|
1295
|
-
verdict: result.verdict,
|
|
1296
|
-
confidence: result.confidence,
|
|
1297
|
-
toolName,
|
|
1298
|
-
contentHash,
|
|
1299
|
-
patterns: result.detectedPatterns
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
1087
|
if (result.verdict !== "clean") {
|
|
1303
1088
|
process.stderr.write(
|
|
1304
1089
|
`blindseal[scan-http]: ${result.verdict} (L${result.layer ?? "?"}) tool=${toolName} confidence=${result.confidence.toFixed(2)}
|
|
@@ -1326,7 +1111,6 @@ function startScanHttp(opts) {
|
|
|
1326
1111
|
});
|
|
1327
1112
|
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
1328
1113
|
process.on(sig, () => {
|
|
1329
|
-
void telemetry.shutdown();
|
|
1330
1114
|
server.close(() => process.exit(0));
|
|
1331
1115
|
});
|
|
1332
1116
|
}
|
|
@@ -1773,999 +1557,40 @@ function runHistoryCli(argv) {
|
|
|
1773
1557
|
return runHistory(parsed);
|
|
1774
1558
|
}
|
|
1775
1559
|
|
|
1776
|
-
// src/cli/dashboard.ts
|
|
1777
|
-
import { createServer as createServer3 } from "http";
|
|
1778
|
-
import { existsSync as existsSync4 } from "fs";
|
|
1779
|
-
var HELP2 = `blindseal dashboard \u2014 local HTTP dashboard for scan history
|
|
1780
|
-
|
|
1781
|
-
Usage:
|
|
1782
|
-
blindseal dashboard [flags]
|
|
1783
|
-
|
|
1784
|
-
Flags
|
|
1785
|
-
--port <n> listen port (default 9876)
|
|
1786
|
-
--host <addr> listen host (default 127.0.0.1)
|
|
1787
|
-
--db <path> override DB path (default ~/.blindseal/scans.db)
|
|
1788
|
-
--refresh <sec> auto-refresh interval (default 5)
|
|
1789
|
-
--no-open do not open browser automatically
|
|
1790
|
-
--help, -h this help
|
|
1791
|
-
|
|
1792
|
-
Reads scans recorded by the proxy when run with BLINDSEAL_PERSIST=1. Local-only;
|
|
1793
|
-
never connects to any external service.
|
|
1794
|
-
`;
|
|
1795
|
-
function parseDashboardArgs(argv) {
|
|
1796
|
-
const out = {};
|
|
1797
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1798
|
-
const a = argv[i];
|
|
1799
|
-
if (a === "--help" || a === "-h") {
|
|
1800
|
-
out.help = true;
|
|
1801
|
-
continue;
|
|
1802
|
-
}
|
|
1803
|
-
if (a === "--port") {
|
|
1804
|
-
out.port = Number.parseInt(argv[++i], 10);
|
|
1805
|
-
continue;
|
|
1806
|
-
}
|
|
1807
|
-
if (a === "--host") {
|
|
1808
|
-
out.host = argv[++i];
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
if (a === "--db") {
|
|
1812
|
-
out.dbPath = argv[++i];
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
if (a === "--refresh") {
|
|
1816
|
-
out.refreshSeconds = Number.parseInt(argv[++i], 10);
|
|
1817
|
-
continue;
|
|
1818
|
-
}
|
|
1819
|
-
if (a === "--no-open") {
|
|
1820
|
-
out.openBrowser = false;
|
|
1821
|
-
continue;
|
|
1822
|
-
}
|
|
1823
|
-
if (a && a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
|
|
1824
|
-
}
|
|
1825
|
-
return out;
|
|
1826
|
-
}
|
|
1827
|
-
function renderIndex(refreshSeconds) {
|
|
1828
|
-
const refresh = Math.max(1, refreshSeconds | 0);
|
|
1829
|
-
return `<!doctype html>
|
|
1830
|
-
<html lang="en">
|
|
1831
|
-
<head>
|
|
1832
|
-
<meta charset="utf-8" />
|
|
1833
|
-
<title>blindseal dashboard</title>
|
|
1834
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
1835
|
-
<style>
|
|
1836
|
-
:root { color-scheme: dark; }
|
|
1837
|
-
* { box-sizing: border-box; }
|
|
1838
|
-
body {
|
|
1839
|
-
background: #0a0e0a;
|
|
1840
|
-
color: #d1f5d1;
|
|
1841
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1842
|
-
margin: 0; padding: 24px;
|
|
1843
|
-
font-size: 13px; line-height: 1.5;
|
|
1844
|
-
}
|
|
1845
|
-
h1, h2 { font-weight: 600; color: #5fdc7c; margin: 0 0 12px; }
|
|
1846
|
-
h1 { font-size: 18px; }
|
|
1847
|
-
h2 { font-size: 14px; margin-top: 24px; }
|
|
1848
|
-
header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 20px; border-bottom: 1px solid #1a2a1a; padding-bottom: 12px; }
|
|
1849
|
-
header .status { font-size: 12px; color: #4a7a4a; }
|
|
1850
|
-
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
|
|
1851
|
-
.card { background: #0f150f; border: 1px solid #1a2a1a; border-radius: 4px; padding: 16px; }
|
|
1852
|
-
.card .label { font-size: 11px; color: #4a7a4a; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px; }
|
|
1853
|
-
.card .value { font-size: 24px; font-weight: 600; }
|
|
1854
|
-
.value.malicious { color: #ff6b6b; }
|
|
1855
|
-
.value.suspicious { color: #ffce5c; }
|
|
1856
|
-
.value.clean { color: #5fdc7c; }
|
|
1857
|
-
table { width: 100%; border-collapse: collapse; }
|
|
1858
|
-
th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #1a2a1a; }
|
|
1859
|
-
th { font-weight: 600; color: #5fdc7c; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
1860
|
-
tr:hover td { background: #0f150f; }
|
|
1861
|
-
.verdict { padding: 2px 6px; border-radius: 2px; font-size: 11px; font-weight: 600; }
|
|
1862
|
-
.verdict.malicious { background: rgba(255, 107, 107, 0.15); color: #ff6b6b; }
|
|
1863
|
-
.verdict.suspicious { background: rgba(255, 206, 92, 0.15); color: #ffce5c; }
|
|
1864
|
-
.verdict.clean { background: rgba(95, 220, 124, 0.15); color: #5fdc7c; }
|
|
1865
|
-
.tool { color: #87c5ee; }
|
|
1866
|
-
.preview { color: #6a8a6a; font-size: 11px; max-width: 600px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1867
|
-
svg { display: block; }
|
|
1868
|
-
.bar-clean { fill: #5fdc7c; }
|
|
1869
|
-
.bar-suspicious { fill: #ffce5c; }
|
|
1870
|
-
.bar-malicious { fill: #ff6b6b; }
|
|
1871
|
-
.day-label { fill: #4a7a4a; font-size: 10px; font-family: inherit; }
|
|
1872
|
-
footer { margin-top: 32px; padding-top: 12px; border-top: 1px solid #1a2a1a; font-size: 11px; color: #4a7a4a; }
|
|
1873
|
-
.empty { padding: 24px; text-align: center; color: #4a7a4a; }
|
|
1874
|
-
</style>
|
|
1875
|
-
</head>
|
|
1876
|
-
<body>
|
|
1877
|
-
<header>
|
|
1878
|
-
<h1>blindseal dashboard</h1>
|
|
1879
|
-
<div class="status">auto-refresh: ${refresh}s \xB7 <span id="last-update">\u2014</span></div>
|
|
1880
|
-
</header>
|
|
1881
|
-
<div id="root"><div class="empty">loading\u2026</div></div>
|
|
1882
|
-
<footer>
|
|
1883
|
-
Blindseal is a local prompt-injection scanning proxy. Scans here came from your local DB.
|
|
1884
|
-
Nothing on this page leaves your machine.
|
|
1885
|
-
</footer>
|
|
1886
|
-
<script>
|
|
1887
|
-
const REFRESH_MS = ${refresh * 1e3};
|
|
1888
|
-
const verdictClass = (v) => v === 'malicious' ? 'malicious' : v === 'suspicious' ? 'suspicious' : 'clean';
|
|
1889
|
-
function fmtAge(ts) {
|
|
1890
|
-
const s = Math.floor((Date.now() - ts) / 1000);
|
|
1891
|
-
if (s < 60) return s + 's ago';
|
|
1892
|
-
if (s < 3600) return Math.floor(s/60) + 'm ago';
|
|
1893
|
-
if (s < 86400) return Math.floor(s/3600) + 'h ago';
|
|
1894
|
-
return Math.floor(s/86400) + 'd ago';
|
|
1895
|
-
}
|
|
1896
|
-
function el(tag, attrs = {}, ...children) {
|
|
1897
|
-
const e = document.createElement(tag);
|
|
1898
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
1899
|
-
if (k === 'class') e.className = v;
|
|
1900
|
-
else if (k.startsWith('on')) e.addEventListener(k.slice(2), v);
|
|
1901
|
-
else e.setAttribute(k, v);
|
|
1902
|
-
}
|
|
1903
|
-
for (const c of children) e.append(c instanceof Node ? c : document.createTextNode(String(c)));
|
|
1904
|
-
return e;
|
|
1905
|
-
}
|
|
1906
|
-
function renderBars(byDay) {
|
|
1907
|
-
const w = 600, h = 120, gap = 4;
|
|
1908
|
-
if (byDay.length === 0) return el('div', { class: 'empty' }, 'no scans in window');
|
|
1909
|
-
const barW = Math.max(8, (w - gap * (byDay.length - 1)) / byDay.length);
|
|
1910
|
-
const max = Math.max(1, ...byDay.map((d) => d.clean + d.suspicious + d.malicious));
|
|
1911
|
-
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1912
|
-
svg.setAttribute('width', w); svg.setAttribute('height', h + 20);
|
|
1913
|
-
byDay.forEach((d, i) => {
|
|
1914
|
-
const x = i * (barW + gap);
|
|
1915
|
-
let y = h;
|
|
1916
|
-
for (const [cls, n] of [['clean', d.clean], ['suspicious', d.suspicious], ['malicious', d.malicious]]) {
|
|
1917
|
-
if (n <= 0) continue;
|
|
1918
|
-
const segH = (n / max) * (h - 4);
|
|
1919
|
-
y -= segH;
|
|
1920
|
-
const r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
1921
|
-
r.setAttribute('x', x); r.setAttribute('y', y);
|
|
1922
|
-
r.setAttribute('width', barW); r.setAttribute('height', segH);
|
|
1923
|
-
r.setAttribute('class', 'bar-' + cls);
|
|
1924
|
-
svg.appendChild(r);
|
|
1925
|
-
}
|
|
1926
|
-
if (i % Math.max(1, Math.floor(byDay.length / 6)) === 0) {
|
|
1927
|
-
const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
1928
|
-
t.setAttribute('x', x + barW / 2); t.setAttribute('y', h + 14);
|
|
1929
|
-
t.setAttribute('text-anchor', 'middle'); t.setAttribute('class', 'day-label');
|
|
1930
|
-
t.textContent = d.day.slice(5); // MM-DD
|
|
1931
|
-
svg.appendChild(t);
|
|
1932
|
-
}
|
|
1933
|
-
});
|
|
1934
|
-
return svg;
|
|
1935
|
-
}
|
|
1936
|
-
function render(data) {
|
|
1937
|
-
const root = document.getElementById('root');
|
|
1938
|
-
root.replaceChildren();
|
|
1939
|
-
const v = data.summary;
|
|
1940
|
-
const grid = el('div', { class: 'grid' },
|
|
1941
|
-
el('div', { class: 'card' },
|
|
1942
|
-
el('div', { class: 'label' }, 'malicious'),
|
|
1943
|
-
el('div', { class: 'value malicious' }, v.malicious),
|
|
1944
|
-
),
|
|
1945
|
-
el('div', { class: 'card' },
|
|
1946
|
-
el('div', { class: 'label' }, 'suspicious'),
|
|
1947
|
-
el('div', { class: 'value suspicious' }, v.suspicious),
|
|
1948
|
-
),
|
|
1949
|
-
el('div', { class: 'card' },
|
|
1950
|
-
el('div', { class: 'label' }, 'clean'),
|
|
1951
|
-
el('div', { class: 'value clean' }, v.clean),
|
|
1952
|
-
),
|
|
1953
|
-
);
|
|
1954
|
-
root.append(grid);
|
|
1955
|
-
root.append(el('h2', {}, 'scans by day (last 30)'));
|
|
1956
|
-
root.append(renderBars(data.byDay));
|
|
1957
|
-
root.append(el('h2', {}, 'top tools'));
|
|
1958
|
-
if (data.topTools.length === 0) root.append(el('div', { class: 'empty' }, 'no scans yet'));
|
|
1959
|
-
else {
|
|
1960
|
-
const t = el('table', {});
|
|
1961
|
-
const thead = el('thead', {}, el('tr', {}, el('th', {}, 'tool'), el('th', {}, 'total'), el('th', {}, 'malicious')));
|
|
1962
|
-
const tbody = el('tbody', {}, ...data.topTools.map((r) =>
|
|
1963
|
-
el('tr', {},
|
|
1964
|
-
el('td', { class: 'tool' }, r.toolName),
|
|
1965
|
-
el('td', {}, String(r.total)),
|
|
1966
|
-
el('td', {}, String(r.malicious)),
|
|
1967
|
-
)
|
|
1968
|
-
));
|
|
1969
|
-
t.append(thead, tbody);
|
|
1970
|
-
root.append(t);
|
|
1971
|
-
}
|
|
1972
|
-
root.append(el('h2', {}, 'recent scans (50)'));
|
|
1973
|
-
if (data.recent.length === 0) root.append(el('div', { class: 'empty' }, 'no scans'));
|
|
1974
|
-
else {
|
|
1975
|
-
const t = el('table', {});
|
|
1976
|
-
const thead = el('thead', {}, el('tr', {},
|
|
1977
|
-
el('th', {}, 'when'), el('th', {}, 'verdict'), el('th', {}, 'tool'),
|
|
1978
|
-
el('th', {}, 'layer'), el('th', {}, 'conf'), el('th', {}, 'preview'),
|
|
1979
|
-
));
|
|
1980
|
-
const tbody = el('tbody', {}, ...data.recent.map((r) =>
|
|
1981
|
-
el('tr', {},
|
|
1982
|
-
el('td', {}, fmtAge(r.ts)),
|
|
1983
|
-
el('td', {}, el('span', { class: 'verdict ' + verdictClass(r.verdict) }, r.verdict)),
|
|
1984
|
-
el('td', { class: 'tool' }, r.toolName),
|
|
1985
|
-
el('td', {}, r.layer == null ? '\u2014' : 'L' + r.layer),
|
|
1986
|
-
el('td', {}, r.confidence.toFixed(2)),
|
|
1987
|
-
el('td', { class: 'preview' }, (r.contentPreview || '').replace(/\\n+/g, ' ')),
|
|
1988
|
-
)
|
|
1989
|
-
));
|
|
1990
|
-
t.append(thead, tbody);
|
|
1991
|
-
root.append(t);
|
|
1992
|
-
}
|
|
1993
|
-
document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
|
|
1994
|
-
}
|
|
1995
|
-
async function tick() {
|
|
1996
|
-
try {
|
|
1997
|
-
const r = await fetch('/api/data');
|
|
1998
|
-
const data = await r.json();
|
|
1999
|
-
render(data);
|
|
2000
|
-
} catch (e) {
|
|
2001
|
-
document.getElementById('root').textContent = 'failed to load: ' + e.message;
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
tick();
|
|
2005
|
-
setInterval(tick, REFRESH_MS);
|
|
2006
|
-
</script>
|
|
2007
|
-
</body>
|
|
2008
|
-
</html>`;
|
|
2009
|
-
}
|
|
2010
|
-
function buildDashboardData(store) {
|
|
2011
|
-
const summary = store.countByVerdict();
|
|
2012
|
-
const total = store.total();
|
|
2013
|
-
return {
|
|
2014
|
-
summary: { ...summary, total, dbPath: store.dbPath ?? "" },
|
|
2015
|
-
byDay: store.countByDay(30),
|
|
2016
|
-
topTools: store.topTools(10),
|
|
2017
|
-
recent: store.list({ limit: 50 }).map((r) => ({
|
|
2018
|
-
ts: r.ts,
|
|
2019
|
-
verdict: r.verdict,
|
|
2020
|
-
toolName: r.toolName,
|
|
2021
|
-
layer: r.layer,
|
|
2022
|
-
confidence: r.confidence,
|
|
2023
|
-
contentPreview: r.contentPreview,
|
|
2024
|
-
serverKey: r.serverKey
|
|
2025
|
-
}))
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
function makeDashboardHandler(store, refreshSeconds) {
|
|
2029
|
-
return (req, res) => {
|
|
2030
|
-
const url = req.url ?? "/";
|
|
2031
|
-
if (req.method !== "GET") {
|
|
2032
|
-
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
2033
|
-
res.end("method not allowed");
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
if (url === "/" || url === "/index.html") {
|
|
2037
|
-
const body = renderIndex(refreshSeconds);
|
|
2038
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2039
|
-
res.end(body);
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
if (url === "/api/data" || url.startsWith("/api/data?")) {
|
|
2043
|
-
const data = buildDashboardData(store);
|
|
2044
|
-
const body = JSON.stringify(data);
|
|
2045
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2046
|
-
res.end(body);
|
|
2047
|
-
return;
|
|
2048
|
-
}
|
|
2049
|
-
if (url === "/health") {
|
|
2050
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2051
|
-
res.end(JSON.stringify({ ok: true, dbPath: store.dbPath }));
|
|
2052
|
-
return;
|
|
2053
|
-
}
|
|
2054
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2055
|
-
res.end("not found");
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
function runDashboardCli(argv) {
|
|
2059
|
-
let parsed;
|
|
2060
|
-
try {
|
|
2061
|
-
parsed = parseDashboardArgs(argv);
|
|
2062
|
-
} catch (err) {
|
|
2063
|
-
process.stderr.write(`blindseal dashboard: ${err instanceof Error ? err.message : String(err)}
|
|
2064
|
-
`);
|
|
2065
|
-
process.stderr.write(HELP2);
|
|
2066
|
-
return 2;
|
|
2067
|
-
}
|
|
2068
|
-
if (parsed.help) {
|
|
2069
|
-
process.stdout.write(HELP2);
|
|
2070
|
-
return 0;
|
|
2071
|
-
}
|
|
2072
|
-
const dbPath = parsed.dbPath ?? process.env.BLINDSEAL_PERSIST_PATH ?? defaultDbPath();
|
|
2073
|
-
if (!existsSync4(dbPath)) {
|
|
2074
|
-
process.stderr.write(`blindseal dashboard: no scan database at ${dbPath}
|
|
2075
|
-
`);
|
|
2076
|
-
process.stderr.write(` Run blindseal with BLINDSEAL_PERSIST=1 first.
|
|
2077
|
-
`);
|
|
2078
|
-
return 1;
|
|
2079
|
-
}
|
|
2080
|
-
const store = createScanStore({ enabled: true, dbPath, retentionDays: 0 });
|
|
2081
|
-
if (!store.enabled) {
|
|
2082
|
-
process.stderr.write(`blindseal dashboard: failed to open ${dbPath}
|
|
2083
|
-
`);
|
|
2084
|
-
return 1;
|
|
2085
|
-
}
|
|
2086
|
-
const port = parsed.port ?? 9876;
|
|
2087
|
-
const host = parsed.host ?? "127.0.0.1";
|
|
2088
|
-
const refresh = parsed.refreshSeconds ?? 5;
|
|
2089
|
-
const server = createServer3(makeDashboardHandler(store, refresh));
|
|
2090
|
-
server.listen(port, host, () => {
|
|
2091
|
-
const url = `http://${host}:${port}`;
|
|
2092
|
-
process.stdout.write(`blindseal dashboard: ${url}
|
|
2093
|
-
`);
|
|
2094
|
-
process.stdout.write(` serving ${dbPath} (${store.total()} scans, refresh=${refresh}s)
|
|
2095
|
-
`);
|
|
2096
|
-
process.stdout.write(` Ctrl-C to stop
|
|
2097
|
-
`);
|
|
2098
|
-
});
|
|
2099
|
-
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
2100
|
-
process.on(sig, () => {
|
|
2101
|
-
server.close(() => {
|
|
2102
|
-
try {
|
|
2103
|
-
store.close();
|
|
2104
|
-
} catch {
|
|
2105
|
-
}
|
|
2106
|
-
process.exit(0);
|
|
2107
|
-
});
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
return 0;
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
// src/cli/run.ts
|
|
2114
|
-
import { existsSync as existsSync6 } from "fs";
|
|
2115
|
-
import path6 from "path";
|
|
2116
|
-
|
|
2117
|
-
// src/api/mock-server.ts
|
|
2118
|
-
import { createServer as createServer4 } from "http";
|
|
2119
|
-
import { createHash as createHash4, randomBytes } from "crypto";
|
|
2120
|
-
import { readFileSync as readFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2121
|
-
import os2 from "os";
|
|
2122
|
-
import path5 from "path";
|
|
2123
|
-
|
|
2124
|
-
// ../chain-reads/src/index.ts
|
|
2125
|
-
import { createHash as createHash3 } from "crypto";
|
|
2126
|
-
var VERIFY_PROGRAM_ID = "merZNYKpKwwX4CGgzvma3jPvLfrMTkZcb1cSXUKANcb";
|
|
2127
|
-
var PUBLIC_MAINNET_RPC = "https://api.mainnet-beta.solana.com";
|
|
2128
|
-
var warned = false;
|
|
2129
|
-
function rpcUrl() {
|
|
2130
|
-
const u = process.env.BLINDSEAL_RPC_URL;
|
|
2131
|
-
if (u === "off") return null;
|
|
2132
|
-
if (u) return u;
|
|
2133
|
-
if (!warned) {
|
|
2134
|
-
warned = true;
|
|
2135
|
-
process.stderr.write("blindseal: BLINDSEAL_RPC_URL unset \u2014 using the public mainnet RPC (rate-limited; ~100 req/10s per IP). Set your own endpoint for production.\n");
|
|
2136
|
-
}
|
|
2137
|
-
return PUBLIC_MAINNET_RPC;
|
|
2138
|
-
}
|
|
2139
|
-
async function rpc(method, params) {
|
|
2140
|
-
const url = rpcUrl();
|
|
2141
|
-
if (!url) throw new Error("rpc disabled (BLINDSEAL_RPC_URL=off)");
|
|
2142
|
-
const res = await fetch(url, {
|
|
2143
|
-
method: "POST",
|
|
2144
|
-
headers: { "content-type": "application/json" },
|
|
2145
|
-
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
|
|
2146
|
-
signal: AbortSignal.timeout(1e4)
|
|
2147
|
-
});
|
|
2148
|
-
if (res.status === 429) throw new Error("rpc rate-limited (429) \u2014 set BLINDSEAL_RPC_URL");
|
|
2149
|
-
const j = await res.json();
|
|
2150
|
-
if (j.error) throw new Error(`rpc ${method}: ${j.error.message}`);
|
|
2151
|
-
return j.result;
|
|
2152
|
-
}
|
|
2153
|
-
var disc = (name) => createHash3("sha256").update(`account:${name}`).digest().subarray(0, 8);
|
|
2154
|
-
var b58 = (bytes) => bs58encode(bytes);
|
|
2155
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
2156
|
-
function bs58encode(bytes) {
|
|
2157
|
-
let zeros = 0;
|
|
2158
|
-
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
2159
|
-
const digits = [];
|
|
2160
|
-
for (const byte of bytes) {
|
|
2161
|
-
let carry = byte;
|
|
2162
|
-
for (let j = 0; j < digits.length; j++) {
|
|
2163
|
-
carry += digits[j] << 8;
|
|
2164
|
-
digits[j] = carry % 58;
|
|
2165
|
-
carry = carry / 58 | 0;
|
|
2166
|
-
}
|
|
2167
|
-
while (carry > 0) {
|
|
2168
|
-
digits.push(carry % 58);
|
|
2169
|
-
carry = carry / 58 | 0;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
return "1".repeat(zeros) + digits.reverse().map((d) => ALPHABET[d]).join("");
|
|
2173
|
-
}
|
|
2174
|
-
function bs58decode(s) {
|
|
2175
|
-
const bytes = [];
|
|
2176
|
-
for (const ch of s) {
|
|
2177
|
-
let carry = ALPHABET.indexOf(ch);
|
|
2178
|
-
if (carry < 0) throw new Error("bad base58");
|
|
2179
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
2180
|
-
carry += bytes[j] * 58;
|
|
2181
|
-
bytes[j] = carry & 255;
|
|
2182
|
-
carry >>= 8;
|
|
2183
|
-
}
|
|
2184
|
-
while (carry > 0) {
|
|
2185
|
-
bytes.push(carry & 255);
|
|
2186
|
-
carry >>= 8;
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
let zeros = 0;
|
|
2190
|
-
while (zeros < s.length && s[zeros] === "1") zeros++;
|
|
2191
|
-
return Uint8Array.from([...new Array(zeros).fill(0), ...bytes.reverse()]);
|
|
2192
|
-
}
|
|
2193
|
-
var P = (1n << 255n) - 19n;
|
|
2194
|
-
var D = 37095705934669439343138083508754565189542113879843219016388785533085940283555n;
|
|
2195
|
-
var SQRT_M1 = 19681161376707505956807079304988542015446066515923890162744021073123829784752n;
|
|
2196
|
-
function modPow(b, e, m) {
|
|
2197
|
-
let r = 1n;
|
|
2198
|
-
b %= m;
|
|
2199
|
-
while (e > 0n) {
|
|
2200
|
-
if (e & 1n) r = r * b % m;
|
|
2201
|
-
b = b * b % m;
|
|
2202
|
-
e >>= 1n;
|
|
2203
|
-
}
|
|
2204
|
-
return r;
|
|
2205
|
-
}
|
|
2206
|
-
function isOnCurve(pk) {
|
|
2207
|
-
if (pk.length !== 32) return false;
|
|
2208
|
-
let y = 0n;
|
|
2209
|
-
for (let i = 31; i >= 0; i--) y = y << 8n | BigInt(pk[i]);
|
|
2210
|
-
const sign = y >> 255n & 1n;
|
|
2211
|
-
y &= (1n << 255n) - 1n;
|
|
2212
|
-
if (y >= P) return false;
|
|
2213
|
-
const y2 = y * y % P, u = (y2 - 1n + P) % P, v = (D * y2 + 1n) % P;
|
|
2214
|
-
const v3 = v * v % P * v % P, v7 = v3 * v3 % P * v % P;
|
|
2215
|
-
let x = u * v3 % P * modPow(u * v7 % P, (P - 5n) / 8n, P) % P;
|
|
2216
|
-
const vx2 = v * (x * x % P) % P;
|
|
2217
|
-
if (vx2 === u) {
|
|
2218
|
-
} else if (vx2 === (P - u) % P) x = x * SQRT_M1 % P;
|
|
2219
|
-
else return false;
|
|
2220
|
-
if (x === 0n && sign === 1n) return false;
|
|
2221
|
-
return true;
|
|
2222
|
-
}
|
|
2223
|
-
function findProgramAddress(seeds, programId) {
|
|
2224
|
-
const prog = bs58decode(programId);
|
|
2225
|
-
for (let bump = 255; bump >= 0; bump--) {
|
|
2226
|
-
const h = createHash3("sha256");
|
|
2227
|
-
for (const s of seeds) h.update(s);
|
|
2228
|
-
h.update(Uint8Array.of(bump));
|
|
2229
|
-
h.update(prog);
|
|
2230
|
-
h.update("ProgramDerivedAddress");
|
|
2231
|
-
const cand = h.digest();
|
|
2232
|
-
if (!isOnCurve(cand)) return b58(cand);
|
|
2233
|
-
}
|
|
2234
|
-
throw new Error("no PDA bump");
|
|
2235
|
-
}
|
|
2236
|
-
async function getNullifierStatus(nullifierHex) {
|
|
2237
|
-
const nf = Buffer.from(nullifierHex.replace(/^0x/, ""), "hex");
|
|
2238
|
-
if (nf.length !== 32) throw new Error("nullifier must be 32 bytes");
|
|
2239
|
-
const pda = findProgramAddress([Buffer.from("nullifier"), nf], VERIFY_PROGRAM_ID);
|
|
2240
|
-
const r = await rpc("getAccountInfo", [pda, { encoding: "base64", commitment: "confirmed" }]);
|
|
2241
|
-
const base = { nullifier: nullifierHex, consumed: false, pda, cluster: "mainnet-beta", rpc: process.env.BLINDSEAL_RPC_URL ? "configured" : "public" };
|
|
2242
|
-
if (!r.value) return base;
|
|
2243
|
-
const data = Buffer.from(r.value.data[0], "base64");
|
|
2244
|
-
if (data.length < 81 || !data.subarray(0, 8).equals(disc("Nullifier"))) return base;
|
|
2245
|
-
return {
|
|
2246
|
-
...base,
|
|
2247
|
-
consumed: true,
|
|
2248
|
-
consumedAtSlot: Number(data.readBigUInt64LE(8)),
|
|
2249
|
-
consumerProgram: b58(data.subarray(16, 48)),
|
|
2250
|
-
actionHash: "0x" + data.subarray(48, 80).toString("hex")
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
// src/api/mock-server.ts
|
|
2255
|
-
import { createPrivateKey, createPublicKey } from "crypto";
|
|
2256
|
-
var MOCK_IDENTITY_PKCS8 = "302e020100300506032b657004220420b6c6aa0182de5f17678ed27e9515ed56b438c6bec2ca9d27d653feec4d13da1c";
|
|
2257
|
-
var MOCK_PRIVATE_KEY = createPrivateKey({ key: Buffer.from(MOCK_IDENTITY_PKCS8, "hex"), format: "der", type: "pkcs8" });
|
|
2258
|
-
var MOCK_PUBKEY_HEX = createPublicKey(MOCK_PRIVATE_KEY).export({ type: "spki", format: "der" }).toString("hex");
|
|
2259
|
-
var MIME = {
|
|
2260
|
-
".html": "text/html; charset=utf-8",
|
|
2261
|
-
".js": "text/javascript; charset=utf-8",
|
|
2262
|
-
".css": "text/css; charset=utf-8",
|
|
2263
|
-
".wasm": "application/wasm",
|
|
2264
|
-
".json": "application/json",
|
|
2265
|
-
".svg": "image/svg+xml",
|
|
2266
|
-
".png": "image/png",
|
|
2267
|
-
".woff2": "font/woff2",
|
|
2268
|
-
".woff": "font/woff",
|
|
2269
|
-
".ico": "image/x-icon",
|
|
2270
|
-
".map": "application/json",
|
|
2271
|
-
".txt": "text/plain"
|
|
2272
|
-
};
|
|
2273
|
-
var DASHBOARD_CSP = [
|
|
2274
|
-
"default-src 'self'",
|
|
2275
|
-
"script-src 'self' 'wasm-unsafe-eval'",
|
|
2276
|
-
"style-src 'self' 'unsafe-inline'",
|
|
2277
|
-
"img-src 'self' data:",
|
|
2278
|
-
"font-src 'self'",
|
|
2279
|
-
"connect-src 'self' ws://127.0.0.1:* ws://localhost:* http://127.0.0.1:* http://localhost:*",
|
|
2280
|
-
"object-src 'none'",
|
|
2281
|
-
"base-uri 'self'",
|
|
2282
|
-
"frame-ancestors 'none'"
|
|
2283
|
-
].join("; ");
|
|
2284
|
-
function serveDashboard(res, dist, pathname) {
|
|
2285
|
-
const rel = pathname === "/" ? "/index.html" : decodeURIComponent(pathname);
|
|
2286
|
-
let file = path5.normalize(path5.join(dist, rel));
|
|
2287
|
-
if (!file.startsWith(path5.normalize(dist + path5.sep)) && file !== path5.normalize(dist)) return false;
|
|
2288
|
-
if (!existsSync5(file)) file = path5.join(dist, "index.html");
|
|
2289
|
-
if (!existsSync5(file)) return false;
|
|
2290
|
-
const body = readFileSync3(file);
|
|
2291
|
-
const type = MIME[path5.extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
2292
|
-
res.writeHead(200, {
|
|
2293
|
-
"content-type": type,
|
|
2294
|
-
"content-length": body.length,
|
|
2295
|
-
"content-security-policy": DASHBOARD_CSP,
|
|
2296
|
-
"x-content-type-options": "nosniff",
|
|
2297
|
-
"referrer-policy": "no-referrer",
|
|
2298
|
-
"cache-control": file.endsWith("index.html") ? "no-cache" : "public, max-age=31536000, immutable"
|
|
2299
|
-
});
|
|
2300
|
-
res.end(body);
|
|
2301
|
-
return true;
|
|
2302
|
-
}
|
|
2303
|
-
function loadFixtures(dir) {
|
|
2304
|
-
const read = (f) => JSON.parse(readFileSync3(path5.join(dir, f), "utf8"));
|
|
2305
|
-
return {
|
|
2306
|
-
decisions: read("decisions.json"),
|
|
2307
|
-
policy: read("policy.json"),
|
|
2308
|
-
audit: read("audit.json"),
|
|
2309
|
-
attestations: read("attestations.json"),
|
|
2310
|
-
sample: read("attestation-sample.json")
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
|
-
function resolveToken(explicit) {
|
|
2314
|
-
if (explicit) return explicit;
|
|
2315
|
-
const dir = path5.join(os2.homedir(), ".blindseal");
|
|
2316
|
-
const file = path5.join(dir, "dashboard.token");
|
|
2317
|
-
try {
|
|
2318
|
-
if (existsSync5(file)) return readFileSync3(file, "utf8").trim();
|
|
2319
|
-
mkdirSync3(dir, { recursive: true });
|
|
2320
|
-
const tok = randomBytes(24).toString("base64url");
|
|
2321
|
-
writeFileSync2(file, tok + "\n", { mode: 384 });
|
|
2322
|
-
return tok;
|
|
2323
|
-
} catch {
|
|
2324
|
-
return randomBytes(24).toString("base64url");
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
var sha = (s) => "0x" + createHash4("sha256").update(s).digest("hex");
|
|
2328
|
-
function sendJson3(res, status, body) {
|
|
2329
|
-
const buf = Buffer.from(JSON.stringify(body));
|
|
2330
|
-
res.writeHead(status, { "content-type": "application/json", "content-length": buf.length });
|
|
2331
|
-
res.end(buf);
|
|
2332
|
-
}
|
|
2333
|
-
function sendErr(res, status, code, message) {
|
|
2334
|
-
sendJson3(res, status, { error: { code, message } });
|
|
2335
|
-
}
|
|
2336
|
-
function bearerOk(req, token, url) {
|
|
2337
|
-
const auth = req.headers["authorization"];
|
|
2338
|
-
if (typeof auth === "string" && auth === `Bearer ${token}`) return true;
|
|
2339
|
-
if (url.searchParams.get("token") === token) return true;
|
|
2340
|
-
return false;
|
|
2341
|
-
}
|
|
2342
|
-
async function readBody3(req) {
|
|
2343
|
-
const chunks = [];
|
|
2344
|
-
for await (const c of req) chunks.push(c);
|
|
2345
|
-
if (chunks.length === 0) return {};
|
|
2346
|
-
try {
|
|
2347
|
-
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
2348
|
-
} catch {
|
|
2349
|
-
return void 0;
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
var WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
2353
|
-
function wsAccept(key) {
|
|
2354
|
-
return createHash4("sha1").update(key + WS_GUID).digest("base64");
|
|
2355
|
-
}
|
|
2356
|
-
function wsFrame(text) {
|
|
2357
|
-
const payload = Buffer.from(text);
|
|
2358
|
-
const len = payload.length;
|
|
2359
|
-
let header;
|
|
2360
|
-
if (len < 126) {
|
|
2361
|
-
header = Buffer.from([129, len]);
|
|
2362
|
-
} else if (len < 65536) {
|
|
2363
|
-
header = Buffer.from([129, 126, len >> 8 & 255, len & 255]);
|
|
2364
|
-
} else {
|
|
2365
|
-
header = Buffer.alloc(10);
|
|
2366
|
-
header[0] = 129;
|
|
2367
|
-
header[1] = 127;
|
|
2368
|
-
header.writeBigUInt64BE(BigInt(len), 2);
|
|
2369
|
-
}
|
|
2370
|
-
return Buffer.concat([header, payload]);
|
|
2371
|
-
}
|
|
2372
|
-
function startMockApi(opts) {
|
|
2373
|
-
const port = opts.port ?? 7777;
|
|
2374
|
-
const host = opts.host ?? "127.0.0.1";
|
|
2375
|
-
const token = resolveToken(opts.token);
|
|
2376
|
-
const emitIntervalMs = opts.emitIntervalMs ?? 3e3;
|
|
2377
|
-
const fx = loadFixtures(opts.fixturesDir);
|
|
2378
|
-
const startedAt = Date.now();
|
|
2379
|
-
const envState = process.env.BLINDSEAL_MOCK_STATE ?? "";
|
|
2380
|
-
const stateOf = (url) => url.searchParams.get("state") ?? envState;
|
|
2381
|
-
const TEE_QUOTE = { quote: sha("mock-tee-quote").slice(2), quoteHash: sha("mock-tee-quote-hash") };
|
|
2382
|
-
const status = (st) => ({
|
|
2383
|
-
version: "0.0.0-mock",
|
|
2384
|
-
uptimeSec: Math.floor((Date.now() - startedAt) / 1e3),
|
|
2385
|
-
connection: st === "offline" ? "offline" : st === "reconnecting" ? "reconnecting" : "live",
|
|
2386
|
-
upstreams: [{ name: "mock-upstream", transport: "stdio", healthy: st !== "upstream-down" }],
|
|
2387
|
-
policyRoot: fx.policy.root,
|
|
2388
|
-
detectorVersion: fx.decisions[0]?.detectorVersion ?? "0x",
|
|
2389
|
-
audit: st === "empty" ? { index: -1, hash: fx.audit.genesis, ts: new Date(startedAt).toISOString() } : fx.audit.head,
|
|
2390
|
-
prover: { available: st === "prover-down" ? false : true },
|
|
2391
|
-
l3: { mode: st === "tee" ? "tee" : "local" },
|
|
2392
|
-
mock: true
|
|
2393
|
-
});
|
|
2394
|
-
const wsClients = /* @__PURE__ */ new Set();
|
|
2395
|
-
const broadcast = (obj) => {
|
|
2396
|
-
const frame = wsFrame(JSON.stringify(obj));
|
|
2397
|
-
for (const s of wsClients) {
|
|
2398
|
-
if (!s.destroyed) s.write(frame);
|
|
2399
|
-
}
|
|
2400
|
-
};
|
|
2401
|
-
const createdAttestations = /* @__PURE__ */ new Map();
|
|
2402
|
-
const liveDecisions = /* @__PURE__ */ new Map();
|
|
2403
|
-
const server = createServer4(async (req, res) => {
|
|
2404
|
-
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
2405
|
-
const p = url.pathname;
|
|
2406
|
-
const method = req.method ?? "GET";
|
|
2407
|
-
const origin = req.headers["origin"];
|
|
2408
|
-
if (typeof origin === "string" && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
2409
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
2410
|
-
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type, x-confirm");
|
|
2411
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
|
|
2412
|
-
res.setHeader("Vary", "Origin");
|
|
2413
|
-
}
|
|
2414
|
-
if (method === "OPTIONS") {
|
|
2415
|
-
res.writeHead(204);
|
|
2416
|
-
return res.end();
|
|
2417
|
-
}
|
|
2418
|
-
if (!p.startsWith("/api/")) {
|
|
2419
|
-
if (opts.dashboardDist && method === "GET" && serveDashboard(res, opts.dashboardDist, p)) return;
|
|
2420
|
-
return sendErr(res, 404, "NOT_FOUND", "unknown path");
|
|
2421
|
-
}
|
|
2422
|
-
if (!bearerOk(req, token, url)) return sendErr(res, 401, "UNAUTHORIZED", "missing or invalid bearer token");
|
|
2423
|
-
const seg = p.slice("/api/".length).split("/").filter(Boolean);
|
|
2424
|
-
const st = stateOf(url);
|
|
2425
|
-
if (st === "unauthorized") return sendErr(res, 401, "UNAUTHORIZED", "missing or invalid dashboard token");
|
|
2426
|
-
if (st === "offline" && !(seg[0] === "status")) return sendErr(res, 503, "OFFLINE", "the proxy is unreachable");
|
|
2427
|
-
try {
|
|
2428
|
-
if (method === "GET" && seg[0] === "status" && seg.length === 1) return sendJson3(res, 200, status(st));
|
|
2429
|
-
if (method === "GET" && seg[0] === "decisions" && seg.length === 1) {
|
|
2430
|
-
const q = url.searchParams;
|
|
2431
|
-
let items = st === "empty" ? [] : fx.decisions.slice();
|
|
2432
|
-
const vd = q.get("verdict");
|
|
2433
|
-
if (vd) items = items.filter((d) => d.verdict === vd);
|
|
2434
|
-
const sv = q.get("server");
|
|
2435
|
-
if (sv) items = items.filter((d) => d.server === sv);
|
|
2436
|
-
const tl = q.get("tool");
|
|
2437
|
-
if (tl) items = items.filter((d) => d.tool === tl);
|
|
2438
|
-
const from = q.get("from");
|
|
2439
|
-
if (from) items = items.filter((d) => d.ts >= from);
|
|
2440
|
-
const to = q.get("to");
|
|
2441
|
-
if (to) items = items.filter((d) => d.ts <= to);
|
|
2442
|
-
const limit = Math.min(Math.max(Number.parseInt(q.get("limit") ?? "50", 10) || 50, 1), 200);
|
|
2443
|
-
const cursor = Number.parseInt(q.get("cursor") ?? "0", 10) || 0;
|
|
2444
|
-
const page = items.slice(cursor, cursor + limit);
|
|
2445
|
-
const nextCursor = cursor + limit < items.length ? String(cursor + limit) : void 0;
|
|
2446
|
-
return sendJson3(res, 200, { items: page, ...nextCursor ? { nextCursor } : {} });
|
|
2447
|
-
}
|
|
2448
|
-
if (seg[0] === "decisions" && seg.length >= 2) {
|
|
2449
|
-
const d = fx.decisions.find((x) => x.id === seg[1]) ?? liveDecisions.get(seg[1]);
|
|
2450
|
-
if (!d) return sendErr(res, 404, "NOT_FOUND", "decision not found");
|
|
2451
|
-
if (method === "GET" && seg.length === 2) {
|
|
2452
|
-
return sendJson3(res, 200, { ...d, payloadRedacted: `[mock redacted payload for ${d.id}]` });
|
|
2453
|
-
}
|
|
2454
|
-
if (method === "POST" && seg[2] === "reveal" && seg.length === 3) {
|
|
2455
|
-
if (req.headers["x-confirm"] !== "reveal") return sendErr(res, 400, "VALIDATION", "X-Confirm: reveal required");
|
|
2456
|
-
return sendJson3(res, 200, { payload: `[mock raw payload for ${d.id} \u2014 synthetic]` });
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
if (seg[0] === "audit") {
|
|
2460
|
-
if (method === "GET" && seg[1] === "head") {
|
|
2461
|
-
if (st === "empty") return sendJson3(res, 200, { index: -1, hash: fx.audit.genesis, ts: new Date(startedAt).toISOString() });
|
|
2462
|
-
return sendJson3(res, 200, fx.audit.head);
|
|
2463
|
-
}
|
|
2464
|
-
if (method === "GET" && seg[1] === "entries") {
|
|
2465
|
-
if (st === "empty") return sendJson3(res, 200, []);
|
|
2466
|
-
const from = Number.parseInt(url.searchParams.get("from") ?? "0", 10) || 0;
|
|
2467
|
-
const to = Number.parseInt(url.searchParams.get("to") ?? String(fx.audit.head.index), 10);
|
|
2468
|
-
return sendJson3(res, 200, fx.audit.entries.filter((e) => e.index >= from && e.index <= to));
|
|
2469
|
-
}
|
|
2470
|
-
if (method === "POST" && seg[1] === "verify") {
|
|
2471
|
-
if (st === "chain-broken") return sendJson3(res, 200, { ok: false, checkedFrom: 0, checkedTo: fx.audit.head.index, brokenAt: 143 });
|
|
2472
|
-
let prev = fx.audit.genesis;
|
|
2473
|
-
let brokenAt;
|
|
2474
|
-
for (const e of fx.audit.entries) {
|
|
2475
|
-
const h = hashEntry({ index: e.index, ts: e.ts, decisionId: e.decisionId, verdict: e.verdict, payloadHash: e.payloadHash, prevHash: prev });
|
|
2476
|
-
if (h !== e.hash || e.prevHash !== prev) {
|
|
2477
|
-
brokenAt = e.index;
|
|
2478
|
-
break;
|
|
2479
|
-
}
|
|
2480
|
-
prev = e.hash;
|
|
2481
|
-
}
|
|
2482
|
-
return sendJson3(res, 200, {
|
|
2483
|
-
ok: brokenAt === void 0,
|
|
2484
|
-
checkedFrom: 0,
|
|
2485
|
-
checkedTo: fx.audit.head.index,
|
|
2486
|
-
...brokenAt !== void 0 ? { brokenAt } : {}
|
|
2487
|
-
});
|
|
2488
|
-
}
|
|
2489
|
-
if (method === "POST" && seg[1] === "disclose") {
|
|
2490
|
-
const body = await readBody3(req);
|
|
2491
|
-
if (!body || typeof body.from !== "number" || typeof body.to !== "number") return sendErr(res, 400, "VALIDATION", "from/to required");
|
|
2492
|
-
if (st === "chain-broken" && body.from <= 143 && body.to >= 143)
|
|
2493
|
-
return sendErr(res, 409, "CHAIN_BROKEN", "the chain is broken at #143; cannot disclose a range over it");
|
|
2494
|
-
const entries = fx.audit.entries.filter((e) => e.index >= body.from && e.index <= body.to);
|
|
2495
|
-
return sendJson3(res, 200, discloseBundle({
|
|
2496
|
-
from: body.from,
|
|
2497
|
-
to: body.to,
|
|
2498
|
-
head: fx.audit.head,
|
|
2499
|
-
entries,
|
|
2500
|
-
privateKey: MOCK_PRIVATE_KEY,
|
|
2501
|
-
pubkeyHex: MOCK_PUBKEY_HEX
|
|
2502
|
-
}));
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
if (seg[0] === "policy") {
|
|
2506
|
-
if (method === "GET" && seg.length === 1) return sendJson3(res, 200, fx.policy);
|
|
2507
|
-
if (method === "PUT" && seg.length === 1) {
|
|
2508
|
-
const body = await readBody3(req);
|
|
2509
|
-
if (!body || !Array.isArray(body.rules)) return sendErr(res, 400, "VALIDATION", "rules[] required");
|
|
2510
|
-
const root = sha("policy:" + JSON.stringify(body.rules));
|
|
2511
|
-
return sendJson3(res, 200, { ...fx.policy, rules: body.rules, root, version: (fx.policy.version ?? 1) + 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2512
|
-
}
|
|
2513
|
-
if (method === "POST" && seg[1] === "dry-run") {
|
|
2514
|
-
const body = await readBody3(req);
|
|
2515
|
-
if (body === void 0) return sendErr(res, 400, "VALIDATION", "invalid JSON body");
|
|
2516
|
-
const d = { ...fx.decisions[0], id: "dryrun_" + Date.now(), ts: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2517
|
-
return sendJson3(res, 200, d);
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
if (seg[0] === "attestations") {
|
|
2521
|
-
if (method === "GET" && seg.length === 1) {
|
|
2522
|
-
if (st === "empty") return sendJson3(res, 200, []);
|
|
2523
|
-
const statusFilter = url.searchParams.get("status");
|
|
2524
|
-
const items = statusFilter ? fx.attestations.filter((a) => a.status === statusFilter) : fx.attestations;
|
|
2525
|
-
return sendJson3(res, 200, items);
|
|
2526
|
-
}
|
|
2527
|
-
if (method === "POST" && seg.length === 1) {
|
|
2528
|
-
if (st === "prover-down") return sendErr(res, 503, "PROVER_UNAVAILABLE", "the local prover is not running");
|
|
2529
|
-
const body = await readBody3(req);
|
|
2530
|
-
if (!body || typeof body.decisionId !== "string") return sendErr(res, 400, "VALIDATION", "decisionId required");
|
|
2531
|
-
const d = fx.decisions.find((x) => x.id === body.decisionId);
|
|
2532
|
-
if (!d) return sendErr(res, 404, "NOT_FOUND", "decision not found");
|
|
2533
|
-
if (!d.action) return sendErr(res, 422, "NO_ACTION_FIELDS", "decision has no on-chain action to attest");
|
|
2534
|
-
const id = "att_" + randomBytes(5).toString("hex");
|
|
2535
|
-
createdAttestations.set(id, {
|
|
2536
|
-
...fx.sample,
|
|
2537
|
-
id,
|
|
2538
|
-
decisionId: d.id,
|
|
2539
|
-
status: "generating",
|
|
2540
|
-
progressPct: 50,
|
|
2541
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2542
|
-
});
|
|
2543
|
-
setTimeout(() => broadcast({ type: "attestation", data: { id, status: "generating", progressPct: 50 } }), 50);
|
|
2544
|
-
setTimeout(() => {
|
|
2545
|
-
const a = createdAttestations.get(id);
|
|
2546
|
-
if (a) {
|
|
2547
|
-
a.status = "generated";
|
|
2548
|
-
a.progressPct = 100;
|
|
2549
|
-
}
|
|
2550
|
-
broadcast({ type: "attestation", data: { id, status: "generated", progressPct: 100 } });
|
|
2551
|
-
}, 150);
|
|
2552
|
-
return sendJson3(res, 202, { id });
|
|
2553
|
-
}
|
|
2554
|
-
if (seg.length >= 2) {
|
|
2555
|
-
const a = fx.attestations.find((x) => x.id === seg[1]) ?? createdAttestations.get(seg[1]) ?? (seg[1] === "sample" ? fx.sample : void 0);
|
|
2556
|
-
if (method === "GET" && seg.length === 2) {
|
|
2557
|
-
if (!a) return sendErr(res, 404, "NOT_FOUND", "attestation not found");
|
|
2558
|
-
return sendJson3(res, 200, a);
|
|
2559
|
-
}
|
|
2560
|
-
if (method === "POST" && seg[2] === "submit") {
|
|
2561
|
-
const body = await readBody3(req);
|
|
2562
|
-
if (!body || body.cluster !== "mainnet-beta") return sendErr(res, 400, "VALIDATION", "cluster must be mainnet-beta");
|
|
2563
|
-
return sendJson3(res, 200, { txSig: sha("mock-txsig:" + seg[1]).slice(2, 90) });
|
|
2564
|
-
}
|
|
2565
|
-
if (method === "GET" && seg[2] === "export") {
|
|
2566
|
-
if (!a) return sendErr(res, 404, "NOT_FOUND", "attestation not found");
|
|
2567
|
-
const buf = Buffer.from(JSON.stringify(a, null, 2));
|
|
2568
|
-
res.writeHead(200, { "content-type": "application/json", "content-disposition": `attachment; filename="attestation-${seg[1]}.json"`, "content-length": buf.length });
|
|
2569
|
-
return res.end(buf);
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
if (method === "GET" && seg[0] === "tee" && seg[1] === "quote") {
|
|
2574
|
-
if (st === "tee") return sendJson3(res, 200, { mode: "tee", quote: TEE_QUOTE.quote, quoteHash: TEE_QUOTE.quoteHash });
|
|
2575
|
-
return sendJson3(res, 200, { mode: "local" });
|
|
2576
|
-
}
|
|
2577
|
-
if (seg[0] === "nullifiers" && seg[1] && method === "GET") {
|
|
2578
|
-
try {
|
|
2579
|
-
return sendJson3(res, 200, await getNullifierStatus(seg[1]));
|
|
2580
|
-
} catch (e) {
|
|
2581
|
-
return sendErr(res, 502, "RPC_UNAVAILABLE", e instanceof Error ? e.message : String(e));
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
return sendErr(res, 404, "NOT_FOUND", `no route for ${method} ${p}`);
|
|
2585
|
-
} catch (e) {
|
|
2586
|
-
return sendErr(res, 500, "VALIDATION", e.message);
|
|
2587
|
-
}
|
|
2588
|
-
});
|
|
2589
|
-
server.on("upgrade", (req, socket) => {
|
|
2590
|
-
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
2591
|
-
const isEvents = url.pathname === "/api/events" || url.pathname === "/events";
|
|
2592
|
-
if (!isEvents || !bearerOk(req, token, url)) {
|
|
2593
|
-
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
2594
|
-
socket.destroy();
|
|
2595
|
-
return;
|
|
2596
|
-
}
|
|
2597
|
-
const key = req.headers["sec-websocket-key"];
|
|
2598
|
-
if (typeof key !== "string") {
|
|
2599
|
-
socket.destroy();
|
|
2600
|
-
return;
|
|
2601
|
-
}
|
|
2602
|
-
socket.write(
|
|
2603
|
-
`HTTP/1.1 101 Switching Protocols\r
|
|
2604
|
-
Upgrade: websocket\r
|
|
2605
|
-
Connection: Upgrade\r
|
|
2606
|
-
Sec-WebSocket-Accept: ${wsAccept(key)}\r
|
|
2607
|
-
\r
|
|
2608
|
-
`
|
|
2609
|
-
);
|
|
2610
|
-
wsClients.add(socket);
|
|
2611
|
-
socket.write(wsFrame(JSON.stringify({ type: "status", data: status(envState) })));
|
|
2612
|
-
socket.on("close", () => wsClients.delete(socket));
|
|
2613
|
-
socket.on("error", () => wsClients.delete(socket));
|
|
2614
|
-
});
|
|
2615
|
-
let idx = 0;
|
|
2616
|
-
const timer = setInterval(() => {
|
|
2617
|
-
if (wsClients.size === 0) return;
|
|
2618
|
-
const base = fx.decisions[idx % fx.decisions.length];
|
|
2619
|
-
idx++;
|
|
2620
|
-
const live = { ...base, id: base.id + "_live" + idx, ts: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2621
|
-
liveDecisions.set(live.id, live);
|
|
2622
|
-
if (liveDecisions.size > 100) liveDecisions.delete(liveDecisions.keys().next().value);
|
|
2623
|
-
broadcast({ type: "decision", data: live });
|
|
2624
|
-
}, emitIntervalMs);
|
|
2625
|
-
timer.unref?.();
|
|
2626
|
-
return new Promise((resolve) => {
|
|
2627
|
-
server.listen(port, host, () => {
|
|
2628
|
-
const addr = server.address();
|
|
2629
|
-
const actualPort = typeof addr === "object" && addr ? addr.port : port;
|
|
2630
|
-
resolve({
|
|
2631
|
-
url: `http://${host}:${actualPort}/api`,
|
|
2632
|
-
token,
|
|
2633
|
-
port: actualPort,
|
|
2634
|
-
close: () => new Promise((r) => {
|
|
2635
|
-
clearInterval(timer);
|
|
2636
|
-
for (const s of wsClients) s.destroy();
|
|
2637
|
-
wsClients.clear();
|
|
2638
|
-
server.close(() => r());
|
|
2639
|
-
})
|
|
2640
|
-
});
|
|
2641
|
-
});
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
// src/cli/run.ts
|
|
2646
|
-
var HELP3 = `blindseal run \u2014 start the local API + WS (FRONTEND.md \xA75)
|
|
2647
|
-
|
|
2648
|
-
Usage:
|
|
2649
|
-
BLINDSEAL_MOCK=1 blindseal run [--mock] [flags]
|
|
2650
|
-
|
|
2651
|
-
Flags
|
|
2652
|
-
--mock serve fixtures/ with a fake event stream (M0)
|
|
2653
|
-
--port <n> listen port (default 7777)
|
|
2654
|
-
--host <addr> listen host (default 127.0.0.1)
|
|
2655
|
-
--fixtures <dir> fixtures directory (default: nearest fixtures/)
|
|
2656
|
-
--dashboard <dir> serve the built dashboard at / (default: $BLINDSEAL_DASHBOARD_DIST or
|
|
2657
|
-
packages/dashboard/dist next to fixtures/, if built)
|
|
2658
|
-
--help, -h this help
|
|
2659
|
-
|
|
2660
|
-
M0 supports --mock only. The engine-backed API lands in M1.
|
|
2661
|
-
`;
|
|
2662
|
-
function parseRunArgs(argv) {
|
|
2663
|
-
const out = { mock: process.env.BLINDSEAL_MOCK === "1" };
|
|
2664
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2665
|
-
const a = argv[i];
|
|
2666
|
-
if (a === "--help" || a === "-h") out.help = true;
|
|
2667
|
-
else if (a === "--mock") out.mock = true;
|
|
2668
|
-
else if (a === "--port") out.port = Number.parseInt(argv[++i] ?? "", 10);
|
|
2669
|
-
else if (a === "--host") out.host = argv[++i];
|
|
2670
|
-
else if (a === "--fixtures") out.fixturesDir = argv[++i];
|
|
2671
|
-
else if (a === "--dashboard") out.dashboard = argv[++i];
|
|
2672
|
-
else if (a && a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
|
|
2673
|
-
}
|
|
2674
|
-
return out;
|
|
2675
|
-
}
|
|
2676
|
-
function resolveFixturesDir(explicit) {
|
|
2677
|
-
if (explicit) return explicit;
|
|
2678
|
-
if (process.env.BLINDSEAL_FIXTURES) return process.env.BLINDSEAL_FIXTURES;
|
|
2679
|
-
let dir = process.cwd();
|
|
2680
|
-
for (let i = 0; i < 7; i++) {
|
|
2681
|
-
const candidate = path6.join(dir, "fixtures");
|
|
2682
|
-
if (existsSync6(path6.join(candidate, "decisions.json"))) return candidate;
|
|
2683
|
-
const parent = path6.dirname(dir);
|
|
2684
|
-
if (parent === dir) break;
|
|
2685
|
-
dir = parent;
|
|
2686
|
-
}
|
|
2687
|
-
throw new Error("fixtures/ not found; pass --fixtures <dir> or set BLINDSEAL_FIXTURES");
|
|
2688
|
-
}
|
|
2689
|
-
async function runRun(argv) {
|
|
2690
|
-
let opts;
|
|
2691
|
-
try {
|
|
2692
|
-
opts = parseRunArgs(argv);
|
|
2693
|
-
} catch (e) {
|
|
2694
|
-
process.stderr.write(`blindseal run: ${e.message}
|
|
2695
|
-
`);
|
|
2696
|
-
return 1;
|
|
2697
|
-
}
|
|
2698
|
-
if (opts.help) {
|
|
2699
|
-
process.stdout.write(HELP3);
|
|
2700
|
-
return 0;
|
|
2701
|
-
}
|
|
2702
|
-
if (!opts.mock) {
|
|
2703
|
-
process.stderr.write(
|
|
2704
|
-
"blindseal run: the engine-backed API is not built yet (M1).\nFor now: BLINDSEAL_MOCK=1 blindseal run --mock\n"
|
|
2705
|
-
);
|
|
2706
|
-
return 1;
|
|
2707
|
-
}
|
|
2708
|
-
const fixturesDir = resolveFixturesDir(opts.fixturesDir);
|
|
2709
|
-
const autoDist = path6.join(path6.dirname(fixturesDir), "packages", "dashboard", "dist");
|
|
2710
|
-
const dashboardDist = opts.dashboard ?? process.env.BLINDSEAL_DASHBOARD_DIST ?? (existsSync6(path6.join(autoDist, "index.html")) ? autoDist : void 0);
|
|
2711
|
-
const handle = await startMockApi({ port: opts.port, host: opts.host, fixturesDir, dashboardDist });
|
|
2712
|
-
process.stdout.write(
|
|
2713
|
-
`blindseal (MOCK): local API on ${handle.url}
|
|
2714
|
-
token: ${handle.token}
|
|
2715
|
-
` + (dashboardDist ? ` dashboard: ${handle.url}/#token=${handle.token} (served from ${dashboardDist}; application/wasm + CSP)
|
|
2716
|
-
` : ` dashboard: not served (build packages/dashboard or pass --dashboard <dist>); open the dev app with #token=${handle.token}
|
|
2717
|
-
`) + ` fixtures: ${fixturesDir}
|
|
2718
|
-
WS: ${handle.url.replace(/^http/, "ws")}/events?token=${handle.token}
|
|
2719
|
-
Ctrl-C to stop.
|
|
2720
|
-
`
|
|
2721
|
-
);
|
|
2722
|
-
return new Promise((resolve) => {
|
|
2723
|
-
const shutdown = () => {
|
|
2724
|
-
void handle.close().then(() => resolve(0));
|
|
2725
|
-
};
|
|
2726
|
-
process.on("SIGINT", shutdown);
|
|
2727
|
-
process.on("SIGTERM", shutdown);
|
|
2728
|
-
});
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
1560
|
// src/index.ts
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
const here = path7.dirname(fileURLToPath2(import.meta.url));
|
|
2735
|
-
for (const rel of ["../package.json", "../../package.json"]) {
|
|
2736
|
-
const p = path7.resolve(here, rel);
|
|
2737
|
-
try {
|
|
2738
|
-
const pkg = JSON.parse(readFileSync4(p, "utf8"));
|
|
2739
|
-
if (pkg.name && pkg.version) return String(pkg.version);
|
|
2740
|
-
} catch {
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
} catch {
|
|
2744
|
-
}
|
|
2745
|
-
return "0.0.0";
|
|
2746
|
-
}
|
|
2747
|
-
var HELP4 = `blindseal \u2014 scans MCP tool responses for prompt injection
|
|
1561
|
+
var HELP2 = `blindseal \u2014 a local MCP proxy that screens every tool call, keeps a hash-chained audit
|
|
1562
|
+
log, and produces zero-knowledge attestations verifiable on Solana.
|
|
2748
1563
|
|
|
2749
1564
|
Usage
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
1565
|
+
blindseal init set up ~/.blindseal (identity key, default policy, dashboard token)
|
|
1566
|
+
blindseal init --wrap [--yes --dry-run --config <path>]
|
|
1567
|
+
route the MCP servers in your Claude configs through blindseal
|
|
1568
|
+
blindseal init --unwrap restore those configs from their backups
|
|
1569
|
+
blindseal run --mock local API + dashboard over bundled sample data
|
|
1570
|
+
blindseal dashboard open the dashboard (starts run --mock if nothing is listening)
|
|
1571
|
+
blindseal -- <command> [args...] wrap a stdio MCP server
|
|
1572
|
+
blindseal --transport http --upstream <url> proxy a remote MCP server over HTTP
|
|
1573
|
+
blindseal --transport scan-http [--port <n>] stand-alone POST /scan endpoint
|
|
1574
|
+
blindseal attest <toolcall.json> --target-program <id> [--out <file>] [--expiry <slot> | --no-expiry]
|
|
1575
|
+
blindseal submit <attestation.json> verify on Solana mainnet and consume the nullifier
|
|
1576
|
+
blindseal verify <attestation.json | disclosure.json>
|
|
1577
|
+
blindseal audit verify | disclose --from <i> --to <j>
|
|
1578
|
+
blindseal history [flags] local scan history (needs BLINDSEAL_PERSIST=1)
|
|
1579
|
+
blindseal demo interactive detection demo
|
|
1580
|
+
blindseal <command> --help help for any command
|
|
2756
1581
|
|
|
2757
1582
|
Examples
|
|
2758
|
-
|
|
2759
|
-
|
|
1583
|
+
blindseal -- npx -y @modelcontextprotocol/server-filesystem /tmp
|
|
1584
|
+
blindseal --transport http --upstream https://mcp.example.com/v1 --port 8800
|
|
2760
1585
|
|
|
2761
|
-
|
|
2762
|
-
--transport <stdio|http|scan-http> default: stdio
|
|
1586
|
+
Proxy flags
|
|
1587
|
+
--transport <stdio|http|scan-http> default: stdio (requires \`-- <command>\`)
|
|
2763
1588
|
--upstream <url> required when --transport http
|
|
2764
1589
|
--port, --listen-port <n> default: 8800 (http) / 9787 (scan-http)
|
|
2765
1590
|
--host <addr> default: 127.0.0.1
|
|
2766
1591
|
--server-url <url> logical server identifier for scan-http (default: scan-http:blindseal)
|
|
2767
|
-
--version, -v
|
|
2768
|
-
--help, -h
|
|
1592
|
+
--version, -v print version
|
|
1593
|
+
--help, -h this help
|
|
2769
1594
|
|
|
2770
1595
|
Detection (env)
|
|
2771
1596
|
BLINDSEAL_MODE block (default) | warn | log
|
|
@@ -2774,10 +1599,10 @@ Detection (env)
|
|
|
2774
1599
|
BLINDSEAL_LAYER3_MODEL model id override
|
|
2775
1600
|
BLINDSEAL_LAYER3_BASE_URL base URL override (ollama or custom endpoint)
|
|
2776
1601
|
BLINDSEAL_LAYER3_TIMEOUT_MS judge call hard timeout (default 15000)
|
|
2777
|
-
BLINDSEAL_L3_FP_SAFE 1 = demote suspicious\u2192clean when L3 unavailable
|
|
2778
|
-
ANTHROPIC_API_KEY
|
|
2779
|
-
OPENAI_API_KEY
|
|
2780
|
-
OLLAMA_HOST
|
|
1602
|
+
BLINDSEAL_L3_FP_SAFE 1 = demote suspicious\u2192clean when L3 unavailable
|
|
1603
|
+
ANTHROPIC_API_KEY BYO key for Layer 3 (Anthropic) \u2190 strongly recommended
|
|
1604
|
+
OPENAI_API_KEY BYO key for Layer 3 (OpenAI / compat)
|
|
1605
|
+
OLLAMA_HOST Ollama base URL (default: http://localhost:11434); set to enable offline L3
|
|
2781
1606
|
|
|
2782
1607
|
Capability firewall (env, default off)
|
|
2783
1608
|
BLINDSEAL_CAPABILITY 1 to enable taint-tracking + gate
|
|
@@ -2790,38 +1615,25 @@ Manifest verification (env)
|
|
|
2790
1615
|
BLINDSEAL_MANIFEST_CHECK on (default) | off | strict
|
|
2791
1616
|
BLINDSEAL_MANIFEST_CACHE_DIR override $XDG_CACHE_HOME/blindseal/manifests
|
|
2792
1617
|
|
|
2793
|
-
Telemetry (env, default on when URL configured)
|
|
2794
|
-
BLINDSEAL_TELEMETRY 0 to disable
|
|
2795
|
-
BLINDSEAL_TELEMETRY_URL collector ingest URL
|
|
2796
|
-
BLINDSEAL_TELEMETRY_SECRET optional Bearer token
|
|
2797
|
-
BLINDSEAL_TELEMETRY_BATCH batch size (default 100)
|
|
2798
|
-
BLINDSEAL_TELEMETRY_FLUSH_MS flush interval (default 5000)
|
|
2799
|
-
BLINDSEAL_TELEMETRY_INSTALL_ID stable install id (hashed before send)
|
|
2800
|
-
|
|
2801
1618
|
Audit log (env, default off)
|
|
2802
1619
|
BLINDSEAL_AUDIT_LOG path to append-only JSONL of every decision
|
|
2803
1620
|
|
|
2804
|
-
Local scan history (env, default off
|
|
1621
|
+
Local scan history (env, default off)
|
|
2805
1622
|
BLINDSEAL_PERSIST 1 to record every scan to ~/.blindseal/scans.db
|
|
2806
1623
|
BLINDSEAL_PERSIST_PATH override DB path (default ~/.blindseal/scans.db)
|
|
2807
1624
|
BLINDSEAL_RETENTION_DAYS rolling retention (default 30, purged on open)
|
|
2808
1625
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
1626
|
+
Chain + prover (env)
|
|
1627
|
+
BLINDSEAL_RPC_URL Solana RPC (default: the public mainnet endpoint)
|
|
1628
|
+
BLINDSEAL_PROVER path to a prover binary (otherwise ~/.blindseal/bin or a release download)
|
|
1629
|
+
BLINDSEAL_DASHBOARD_DIST override the dashboard directory served by \`blindseal run\`
|
|
1630
|
+
BLINDSEAL_FIXTURES override the sample data directory for \`run --mock\`
|
|
1631
|
+
|
|
1632
|
+
Nothing leaves this machine except the RPC calls you ask for (attest/submit/verify on chain).
|
|
2814
1633
|
`;
|
|
1634
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["init", "run", "dashboard", "demo", "attest", "submit", "verify", "audit", "history"]);
|
|
1635
|
+
var PROXY_FLAGS = /* @__PURE__ */ new Set(["--", "--transport", "--upstream", "--port", "--listen-port", "--host", "--server-url"]);
|
|
2815
1636
|
function parseArgs(argv) {
|
|
2816
|
-
if (argv.length === 0) return { transport: "stdio", showHelp: true, noArgs: true };
|
|
2817
|
-
if (argv.includes("--version") || argv.includes("-v")) {
|
|
2818
|
-
process.stdout.write(`blindseal ${readVersion2()}
|
|
2819
|
-
`);
|
|
2820
|
-
process.exit(0);
|
|
2821
|
-
}
|
|
2822
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
2823
|
-
return { transport: "stdio", showHelp: true, noArgs: false };
|
|
2824
|
-
}
|
|
2825
1637
|
const sepIdx = argv.indexOf("--");
|
|
2826
1638
|
const flagArgs = sepIdx >= 0 ? argv.slice(0, sepIdx) : argv;
|
|
2827
1639
|
const afterSep = sepIdx >= 0 ? argv.slice(sepIdx + 1) : [];
|
|
@@ -2849,80 +1661,88 @@ function parseArgs(argv) {
|
|
|
2849
1661
|
serverUrl = flagArgs[++i];
|
|
2850
1662
|
} else if (a && a.startsWith("--")) {
|
|
2851
1663
|
throw new Error(`unknown flag: ${a}`);
|
|
1664
|
+
} else if (a) {
|
|
1665
|
+
throw new Error(`unexpected argument "${a}" \u2014 to wrap an MCP server use: blindseal -- ${a} ...`);
|
|
2852
1666
|
}
|
|
2853
1667
|
}
|
|
2854
1668
|
if (transport === "http") {
|
|
2855
1669
|
if (!upstream) throw new Error("--transport http requires --upstream <url>");
|
|
2856
|
-
return { transport, upstream, port: port ?? 8800, host: host ?? "127.0.0.1"
|
|
1670
|
+
return { transport, upstream, port: port ?? 8800, host: host ?? "127.0.0.1" };
|
|
2857
1671
|
}
|
|
2858
1672
|
if (transport === "scan-http") {
|
|
2859
|
-
return {
|
|
2860
|
-
transport,
|
|
2861
|
-
port: port ?? 9787,
|
|
2862
|
-
host: host ?? "127.0.0.1",
|
|
2863
|
-
serverUrl,
|
|
2864
|
-
showHelp: false,
|
|
2865
|
-
noArgs: false
|
|
2866
|
-
};
|
|
1673
|
+
return { transport, port: port ?? 9787, host: host ?? "127.0.0.1", serverUrl };
|
|
2867
1674
|
}
|
|
2868
|
-
|
|
2869
|
-
const cmd =
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
1675
|
+
if (sepIdx < 0) throw new Error('wrapping an MCP server requires "--": blindseal -- <command> [args...]');
|
|
1676
|
+
const cmd = afterSep[0];
|
|
1677
|
+
if (!cmd) throw new Error('no command after "--": blindseal -- <command> [args...]');
|
|
1678
|
+
return { transport: "stdio", stdioTarget: { cmd, args: afterSep.slice(1) } };
|
|
1679
|
+
}
|
|
1680
|
+
function fail(prefix, err) {
|
|
1681
|
+
process.stderr.write(`${prefix}: ${err instanceof Error ? err.message : String(err)}
|
|
1682
|
+
`);
|
|
1683
|
+
process.exit(1);
|
|
2873
1684
|
}
|
|
2874
1685
|
function main() {
|
|
2875
1686
|
const argv = process.argv.slice(2);
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
process.exit(1);
|
|
2881
|
-
});
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
if (argv[0] === "run") {
|
|
2885
|
-
runRun(argv.slice(1)).then((code) => {
|
|
2886
|
-
if (code !== 0) process.exit(code);
|
|
2887
|
-
}).catch((err) => {
|
|
2888
|
-
process.stderr.write(`blindseal run: ${err instanceof Error ? err.message : String(err)}
|
|
2889
|
-
`);
|
|
2890
|
-
process.exit(1);
|
|
2891
|
-
});
|
|
2892
|
-
return;
|
|
2893
|
-
}
|
|
2894
|
-
if (argv[0] === "attest") {
|
|
2895
|
-
import("./attest-QLIE2OSQ.js").then(({ runAttestCli }) => runAttestCli(argv.slice(1))).then((code) => process.exit(code)).catch((err) => {
|
|
2896
|
-
process.stderr.write(`blindseal attest: ${err instanceof Error ? err.message : String(err)}
|
|
2897
|
-
`);
|
|
2898
|
-
process.exit(1);
|
|
2899
|
-
});
|
|
2900
|
-
return;
|
|
1687
|
+
const first = argv[0];
|
|
1688
|
+
if (first === void 0) {
|
|
1689
|
+
process.stdout.write(HELP2);
|
|
1690
|
+
process.exit(2);
|
|
2901
1691
|
}
|
|
2902
|
-
if (
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
`);
|
|
2906
|
-
process.exit(1);
|
|
2907
|
-
});
|
|
2908
|
-
return;
|
|
1692
|
+
if (first === "--help" || first === "-h") {
|
|
1693
|
+
process.stdout.write(HELP2);
|
|
1694
|
+
process.exit(0);
|
|
2909
1695
|
}
|
|
2910
|
-
if (
|
|
2911
|
-
|
|
2912
|
-
(which === "audit" ? import("./audit-C756XFXR.js").then((m) => m.runAuditCli(argv.slice(1))) : import("./verify-HO4IU7XV.js").then((m) => m.runVerifyCli(argv.slice(1)))).then((code) => process.exit(code)).catch((err) => {
|
|
2913
|
-
process.stderr.write(`blindseal ${which}: ${err instanceof Error ? err.message : String(err)}
|
|
1696
|
+
if (first === "--version" || first === "-v") {
|
|
1697
|
+
process.stdout.write(`blindseal ${packageVersion()}
|
|
2914
1698
|
`);
|
|
2915
|
-
|
|
2916
|
-
});
|
|
2917
|
-
return;
|
|
1699
|
+
process.exit(0);
|
|
2918
1700
|
}
|
|
2919
|
-
if (
|
|
2920
|
-
|
|
1701
|
+
if (SUBCOMMANDS.has(first)) {
|
|
1702
|
+
const rest = argv.slice(1);
|
|
1703
|
+
switch (first) {
|
|
1704
|
+
case "init":
|
|
1705
|
+
import("./init-ZP3SLHM3.js").then((m) => m.runInit(rest)).then((code) => process.exit(code)).catch((e) => fail("blindseal init", e));
|
|
1706
|
+
return;
|
|
1707
|
+
case "run":
|
|
1708
|
+
import("./run-YQBNEPSD.js").then((m) => m.runRun(rest)).then((code) => {
|
|
1709
|
+
if (code !== 0) process.exit(code);
|
|
1710
|
+
}).catch((e) => fail("blindseal run", e));
|
|
1711
|
+
return;
|
|
1712
|
+
// the server keeps the process alive
|
|
1713
|
+
case "dashboard":
|
|
1714
|
+
import("./dashboard-J6BA7OBL.js").then((m) => m.runDashboardCli(rest)).then((code) => {
|
|
1715
|
+
if (code !== 0) process.exit(code);
|
|
1716
|
+
}).catch((e) => fail("blindseal dashboard", e));
|
|
1717
|
+
return;
|
|
1718
|
+
// may keep a `run --mock` alive
|
|
1719
|
+
case "demo":
|
|
1720
|
+
runDemo().then(() => process.exit(0)).catch((e) => fail("blindseal demo", e));
|
|
1721
|
+
return;
|
|
1722
|
+
case "attest":
|
|
1723
|
+
import("./attest-VWYZAOLY.js").then((m) => m.runAttestCli(rest)).then((code) => process.exit(code)).catch((e) => fail("blindseal attest", e));
|
|
1724
|
+
return;
|
|
1725
|
+
case "submit":
|
|
1726
|
+
import("./submit-D6MQTAXT.js").then((m) => m.runSubmitCli(rest)).then((code) => process.exit(code)).catch((e) => fail("blindseal submit", e));
|
|
1727
|
+
return;
|
|
1728
|
+
case "verify":
|
|
1729
|
+
import("./verify-RISAGKXC.js").then((m) => m.runVerifyCli(rest)).then((code) => process.exit(code)).catch((e) => fail("blindseal verify", e));
|
|
1730
|
+
return;
|
|
1731
|
+
case "audit":
|
|
1732
|
+
import("./audit-G4DDMAXH.js").then((m) => m.runAuditCli(rest)).then((code) => process.exit(code)).catch((e) => fail("blindseal audit", e));
|
|
1733
|
+
return;
|
|
1734
|
+
case "history":
|
|
1735
|
+
process.exit(runHistoryCli(rest));
|
|
1736
|
+
}
|
|
2921
1737
|
}
|
|
2922
|
-
if (
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
1738
|
+
if (!PROXY_FLAGS.has(first) && !(first.startsWith("-") && argv.includes("--"))) {
|
|
1739
|
+
process.stderr.write(
|
|
1740
|
+
first.startsWith("-") ? `blindseal: unknown flag "${first}"
|
|
1741
|
+
` : `blindseal: unknown command "${first}"
|
|
1742
|
+
`
|
|
1743
|
+
);
|
|
1744
|
+
process.stderr.write(HELP2);
|
|
1745
|
+
process.exit(2);
|
|
2926
1746
|
}
|
|
2927
1747
|
let parsed;
|
|
2928
1748
|
try {
|
|
@@ -2930,26 +1750,21 @@ function main() {
|
|
|
2930
1750
|
} catch (err) {
|
|
2931
1751
|
process.stderr.write(`blindseal: ${err instanceof Error ? err.message : String(err)}
|
|
2932
1752
|
`);
|
|
2933
|
-
process.stderr.write(
|
|
1753
|
+
process.stderr.write(HELP2);
|
|
2934
1754
|
process.exit(2);
|
|
2935
1755
|
}
|
|
2936
|
-
if (parsed.showHelp) {
|
|
2937
|
-
process.stdout.write(HELP4);
|
|
2938
|
-
process.exit(parsed.noArgs ? 2 : 0);
|
|
2939
|
-
}
|
|
2940
1756
|
if (parsed.transport === "http") {
|
|
2941
1757
|
startHttpProxy({ upstream: parsed.upstream, listenPort: parsed.port, listenHost: parsed.host });
|
|
2942
1758
|
return;
|
|
2943
1759
|
}
|
|
2944
1760
|
if (parsed.transport === "scan-http") {
|
|
2945
|
-
startScanHttp({
|
|
2946
|
-
listenPort: parsed.port,
|
|
2947
|
-
listenHost: parsed.host,
|
|
2948
|
-
defaultServerUrl: parsed.serverUrl
|
|
2949
|
-
});
|
|
1761
|
+
startScanHttp({ listenPort: parsed.port, listenHost: parsed.host, defaultServerUrl: parsed.serverUrl });
|
|
2950
1762
|
return;
|
|
2951
1763
|
}
|
|
2952
1764
|
const { cmd, args } = parsed.stdioTarget;
|
|
2953
1765
|
startProxy(cmd, args);
|
|
2954
1766
|
}
|
|
2955
1767
|
main();
|
|
1768
|
+
export {
|
|
1769
|
+
parseArgs
|
|
1770
|
+
};
|