@fiscalmindset/blindfold 0.4.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.
@@ -0,0 +1,756 @@
1
+ // src/env.ts
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ // src/keychain.ts
8
+ import { spawnSync } from "node:child_process";
9
+ var SERVICE = "blindfold";
10
+ var isWin = process.platform === "win32";
11
+ function has(cmd) {
12
+ const finder = isWin ? "where" : "which";
13
+ const r = spawnSync(finder, [cmd], { stdio: "ignore" });
14
+ return r.status === 0;
15
+ }
16
+ var WIN_CS = `
17
+ using System;
18
+ using System.Runtime.InteropServices;
19
+ public static class BFCred {
20
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
21
+ public struct CREDENTIAL {
22
+ public UInt32 Flags; public UInt32 Type;
23
+ [MarshalAs(UnmanagedType.LPWStr)] public string TargetName;
24
+ [MarshalAs(UnmanagedType.LPWStr)] public string Comment;
25
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
26
+ public UInt32 CredentialBlobSize; public IntPtr CredentialBlob; public UInt32 Persist;
27
+ public UInt32 AttributeCount; public IntPtr Attributes;
28
+ [MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;
29
+ [MarshalAs(UnmanagedType.LPWStr)] public string UserName;
30
+ }
31
+ [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool CredWriteW(ref CREDENTIAL c, UInt32 f);
32
+ [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool CredReadW(string t, UInt32 ty, UInt32 f, out IntPtr c);
33
+ [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool CredDeleteW(string t, UInt32 ty, UInt32 f);
34
+ [DllImport("advapi32.dll")] public static extern void CredFree(IntPtr c);
35
+ }
36
+ `;
37
+ var PS_HEADER = `$ErrorActionPreference='Stop'
38
+ Add-Type -TypeDefinition @"
39
+ ${WIN_CS}
40
+ "@
41
+ `;
42
+ function winPS(op, target, secret) {
43
+ let body;
44
+ if (op === "set") {
45
+ body = `$b=[System.Text.Encoding]::UTF8.GetBytes($env:BF_SECRET);$p=[Runtime.InteropServices.Marshal]::AllocHGlobal($b.Length);[Runtime.InteropServices.Marshal]::Copy($b,0,$p,$b.Length);$c=New-Object BFCred+CREDENTIAL;$c.Type=1;$c.TargetName=$env:BF_TARGET;$c.UserName=$env:BF_TARGET;$c.CredentialBlobSize=$b.Length;$c.CredentialBlob=$p;$c.Persist=2;$ok=[BFCred]::CredWriteW([ref]$c,0);[Runtime.InteropServices.Marshal]::FreeHGlobal($p);if($ok){[Console]::Out.Write('BFOK')}`;
46
+ } else if (op === "get") {
47
+ body = `$ptr=[IntPtr]::Zero;if([BFCred]::CredReadW($env:BF_TARGET,1,0,[ref]$ptr)){$c=[Runtime.InteropServices.Marshal]::PtrToStructure($ptr,[BFCred+CREDENTIAL]);$n=[int]$c.CredentialBlobSize;$b=New-Object byte[] $n;[Runtime.InteropServices.Marshal]::Copy([IntPtr]$c.CredentialBlob,$b,0,$n);[BFCred]::CredFree($ptr);[Console]::Out.Write([System.Text.Encoding]::UTF8.GetString($b))}else{exit 1}`;
48
+ } else {
49
+ body = `if([BFCred]::CredDeleteW($env:BF_TARGET,1,0)){[Console]::Out.Write('BFOK')}`;
50
+ }
51
+ const env = { ...process.env, BF_TARGET: target };
52
+ if (secret !== void 0) env.BF_SECRET = secret;
53
+ const r = spawnSync(
54
+ "powershell",
55
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", "-"],
56
+ { input: PS_HEADER + body, env, encoding: "utf8" }
57
+ );
58
+ return { status: r.status, stdout: typeof r.stdout === "string" ? r.stdout : "" };
59
+ }
60
+ function winTarget(account) {
61
+ return `${SERVICE}:${account}`;
62
+ }
63
+ function keychainAvailable() {
64
+ if (process.platform === "darwin") return has("security");
65
+ if (process.platform === "linux") return has("secret-tool");
66
+ if (isWin) return has("powershell");
67
+ return false;
68
+ }
69
+ function keychainGet(account) {
70
+ if (process.platform === "darwin") {
71
+ const r = spawnSync("security", ["find-generic-password", "-a", account, "-s", SERVICE, "-w"], { encoding: "utf8" });
72
+ if (r.status === 0 && typeof r.stdout === "string") return r.stdout.replace(/\n$/, "");
73
+ return null;
74
+ }
75
+ if (process.platform === "linux") {
76
+ const r = spawnSync("secret-tool", ["lookup", "service", SERVICE, "account", account], { encoding: "utf8" });
77
+ if (r.status === 0 && r.stdout) return r.stdout.replace(/\n$/, "");
78
+ return null;
79
+ }
80
+ if (isWin) {
81
+ const r = winPS("get", winTarget(account));
82
+ if (r.status === 0 && r.stdout) return r.stdout.replace(/\r?\n$/, "");
83
+ return null;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ // src/env.ts
89
+ var HERE = path.dirname(fileURLToPath(import.meta.url));
90
+ var SRC_RELATIVE_ROOT = path.resolve(HERE, "..", "..", "..");
91
+ var _repoRoot = null;
92
+ function repoRoot() {
93
+ if (_repoRoot) return _repoRoot;
94
+ let dir = process.cwd();
95
+ for (let i = 0; i < 12; i++) {
96
+ if (fs.existsSync(path.join(dir, ".env")) || fs.existsSync(path.join(dir, ".blindfold")) || fs.existsSync(path.join(dir, ".git"))) {
97
+ _repoRoot = dir;
98
+ return dir;
99
+ }
100
+ const parent = path.dirname(dir);
101
+ if (parent === dir) break;
102
+ dir = parent;
103
+ }
104
+ _repoRoot = SRC_RELATIVE_ROOT;
105
+ return _repoRoot;
106
+ }
107
+ function homeDir() {
108
+ return path.join(os.homedir(), ".blindfold");
109
+ }
110
+ function configPath() {
111
+ return path.join(homeDir(), "config.json");
112
+ }
113
+ function stateDir() {
114
+ const override = process.env.BLINDFOLD_STATE_DIR;
115
+ if (override && override.trim()) return path.resolve(override.trim());
116
+ const home = homeDir();
117
+ if (!fs.existsSync(home)) {
118
+ try {
119
+ const legacy = path.join(repoRoot(), ".blindfold");
120
+ if (fs.existsSync(legacy) && fs.statSync(legacy).isDirectory()) {
121
+ fs.cpSync(legacy, home, { recursive: true });
122
+ } else {
123
+ fs.mkdirSync(home, { recursive: true });
124
+ }
125
+ } catch {
126
+ try {
127
+ fs.mkdirSync(home, { recursive: true });
128
+ } catch {
129
+ }
130
+ }
131
+ }
132
+ return home;
133
+ }
134
+ function assertSafeOverrideUrl(raw, label) {
135
+ let u;
136
+ try {
137
+ u = new URL(raw);
138
+ } catch {
139
+ throw new Error(`${label} is not a valid URL: ${raw}`);
140
+ }
141
+ const isLocal = ["localhost", "127.0.0.1", "::1", "[::1]"].includes(u.hostname);
142
+ if (u.protocol !== "https:" && !isLocal) {
143
+ throw new Error(`${label} must be https (got ${u.protocol}//${u.hostname}); refusing to trust an insecure endpoint`);
144
+ }
145
+ }
146
+ function withFileLockSync(target, fn) {
147
+ const lockPath = `${target}.lock`;
148
+ const deadline = Date.now() + 1e4;
149
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
150
+ const sleep = (ms) => {
151
+ try {
152
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
153
+ } catch {
154
+ }
155
+ };
156
+ for (; ; ) {
157
+ try {
158
+ const fd = fs.openSync(lockPath, "wx");
159
+ fs.writeSync(fd, String(process.pid));
160
+ fs.closeSync(fd);
161
+ break;
162
+ } catch (e) {
163
+ if (e?.code !== "EEXIST") throw e;
164
+ try {
165
+ const st = fs.statSync(lockPath);
166
+ if (Date.now() - st.mtimeMs > 1e4) {
167
+ fs.rmSync(lockPath, { force: true });
168
+ continue;
169
+ }
170
+ } catch {
171
+ }
172
+ if (Date.now() > deadline) throw new Error(`timed out acquiring lock ${lockPath}`);
173
+ sleep(25);
174
+ }
175
+ }
176
+ try {
177
+ return fn();
178
+ } finally {
179
+ fs.rmSync(lockPath, { force: true });
180
+ }
181
+ }
182
+ function loadEnvFromFile(envPath = path.join(repoRoot(), ".env")) {
183
+ if (!fs.existsSync(envPath)) return;
184
+ const text = fs.readFileSync(envPath, "utf8");
185
+ for (const raw of text.split(/\r?\n/)) {
186
+ const line = raw.trim();
187
+ if (!line || line.startsWith("#")) continue;
188
+ const eq = line.indexOf("=");
189
+ if (eq <= 0) continue;
190
+ const key = line.slice(0, eq).trim();
191
+ let val = line.slice(eq + 1).trim();
192
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
193
+ val = val.slice(1, -1);
194
+ }
195
+ if (process.env[key] === void 0) process.env[key] = val;
196
+ }
197
+ }
198
+ function loadHomeConfig() {
199
+ try {
200
+ const cfg = configPath();
201
+ if (fs.existsSync(cfg)) {
202
+ const obj = JSON.parse(fs.readFileSync(cfg, "utf8"));
203
+ for (const [k, v] of Object.entries(obj)) {
204
+ if (k === "T3N_API_KEY_STORE") continue;
205
+ if (typeof v === "string" && process.env[k] === void 0) process.env[k] = v;
206
+ }
207
+ }
208
+ } catch {
209
+ }
210
+ loadEnvFromFile(path.join(homeDir(), ".env"));
211
+ if (process.env.T3N_API_KEY === void 0 && process.env.DID && keychainAvailable()) {
212
+ const k = keychainGet(process.env.DID);
213
+ if (k) process.env.T3N_API_KEY = k;
214
+ }
215
+ }
216
+ function loadBlindfoldEnv() {
217
+ loadEnvFromFile();
218
+ loadHomeConfig();
219
+ const t3nApiKey = process.env.T3N_API_KEY ?? "";
220
+ const did = process.env.DID ?? "";
221
+ const port = Number.parseInt(process.env.BLINDFOLD_PORT ?? "8787", 10);
222
+ const t3EnvRaw = (process.env.BLINDFOLD_T3_ENV ?? "testnet").toLowerCase();
223
+ const t3Env = t3EnvRaw === "production" ? "production" : "testnet";
224
+ const t3BaseUrl = (process.env.T3_BASE_URL ?? process.env.BLINDFOLD_BASE_URL ?? "").trim();
225
+ if (t3BaseUrl) {
226
+ assertSafeOverrideUrl(t3BaseUrl, process.env.T3_BASE_URL ? "T3_BASE_URL" : "BLINDFOLD_BASE_URL");
227
+ }
228
+ const mock = process.env.BLINDFOLD_MOCK === "1";
229
+ return { t3nApiKey, did, port, t3Env, t3BaseUrl, mock };
230
+ }
231
+ function assertRealReady(env) {
232
+ if (env.mock) return;
233
+ const missing = [];
234
+ if (!env.t3nApiKey) missing.push("T3N_API_KEY");
235
+ if (!env.did) missing.push("DID");
236
+ if (missing.length > 0) {
237
+ throw new Error(
238
+ `Missing required env: ${missing.join(", ")}. Claim them at https://docs.terminal3.io/developers/adk/get-started/prerequisites/request-test-tokens, then put them in .env. Or set BLINDFOLD_MOCK=1 to use mock mode for the demo only.`
239
+ );
240
+ }
241
+ }
242
+ function pluckSecret(envName) {
243
+ const v = process.env[envName];
244
+ if (!v) {
245
+ throw new Error(`environment variable ${envName} is unset or empty`);
246
+ }
247
+ return v;
248
+ }
249
+
250
+ // src/log.ts
251
+ var HEADER_BLOCKLIST = /* @__PURE__ */ new Set([
252
+ "authorization",
253
+ "proxy-authorization",
254
+ "x-api-key",
255
+ "cookie",
256
+ "set-cookie"
257
+ ]);
258
+ function safeLog(level, obj) {
259
+ const safe = typeof obj === "string" ? { msg: obj } : redact(obj);
260
+ const line = JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), level, ...safe });
261
+ process.stderr.write(line + "\n");
262
+ }
263
+ function redact(input) {
264
+ const out = {};
265
+ for (const [k, v] of Object.entries(input)) {
266
+ if (k === "headers" && v && typeof v === "object") {
267
+ out[k] = redactHeaders(v);
268
+ } else if (HEADER_BLOCKLIST.has(k.toLowerCase())) {
269
+ out[k] = "[redacted]";
270
+ } else {
271
+ out[k] = v;
272
+ }
273
+ }
274
+ return out;
275
+ }
276
+ function redactHeaders(h) {
277
+ if (Array.isArray(h)) {
278
+ return h.map(([k, v]) => [k, HEADER_BLOCKLIST.has(k.toLowerCase()) ? "[redacted]" : v]);
279
+ }
280
+ const out = {};
281
+ for (const [k, v] of Object.entries(h)) {
282
+ out[k] = HEADER_BLOCKLIST.has(k.toLowerCase()) ? "[redacted]" : String(v);
283
+ }
284
+ return out;
285
+ }
286
+
287
+ // src/prompt.ts
288
+ async function readSecretLine(prompt) {
289
+ process.stderr.write(prompt);
290
+ const stdin = process.stdin;
291
+ if (!stdin.isTTY) {
292
+ return await readOneLine(stdin);
293
+ }
294
+ return await new Promise((resolve, reject) => {
295
+ let buf = "";
296
+ stdin.setRawMode(true);
297
+ stdin.resume();
298
+ stdin.setEncoding("utf8");
299
+ const cleanup = () => {
300
+ stdin.removeListener("data", onData);
301
+ stdin.setRawMode(false);
302
+ stdin.pause();
303
+ };
304
+ const onData = (chunk) => {
305
+ const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
306
+ for (const ch of s) {
307
+ if (ch === "\n" || ch === "\r") {
308
+ cleanup();
309
+ process.stderr.write("\n");
310
+ resolve(buf);
311
+ return;
312
+ }
313
+ if (ch === "") {
314
+ cleanup();
315
+ process.stderr.write("\n");
316
+ reject(new Error("aborted by user"));
317
+ return;
318
+ }
319
+ if (ch === "\x7F" || ch === "\b") {
320
+ if (buf.length > 0) buf = buf.slice(0, -1);
321
+ continue;
322
+ }
323
+ if (ch >= " " || ch === " ") buf += ch;
324
+ }
325
+ };
326
+ stdin.on("data", onData);
327
+ });
328
+ }
329
+ function readOneLine(stdin) {
330
+ return new Promise((resolve, reject) => {
331
+ let buf = "";
332
+ stdin.setEncoding("utf8");
333
+ stdin.on("data", (d) => {
334
+ buf += d;
335
+ });
336
+ stdin.on("end", () => resolve(buf.replace(/\r?\n$/, "")));
337
+ stdin.on("error", reject);
338
+ });
339
+ }
340
+
341
+ // src/sealed-ledger.ts
342
+ import fs2 from "node:fs";
343
+ import path2 from "node:path";
344
+ import { createHash, createHmac, randomBytes } from "node:crypto";
345
+ function defaultSealedLogPath() {
346
+ return process.env.BLINDFOLD_SEALED_LOG ?? path2.join(stateDir(), "sealed.jsonl");
347
+ }
348
+ function ledgerKey() {
349
+ try {
350
+ const keyPath = path2.join(stateDir(), "ledger.key");
351
+ if (fs2.existsSync(keyPath)) return Buffer.from(fs2.readFileSync(keyPath, "utf8").trim(), "hex");
352
+ fs2.mkdirSync(path2.dirname(keyPath), { recursive: true });
353
+ const key = randomBytes(32);
354
+ fs2.writeFileSync(keyPath, key.toString("hex"), { mode: 384 });
355
+ try {
356
+ fs2.chmodSync(keyPath, 384);
357
+ } catch {
358
+ }
359
+ return key;
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+ function coreString(e) {
365
+ return JSON.stringify({
366
+ t: e.t,
367
+ name: e.name,
368
+ source: e.source,
369
+ length: e.length,
370
+ mode: e.mode,
371
+ tenant_did: e.tenant_did,
372
+ map_name: e.map_name
373
+ });
374
+ }
375
+ function sha(s) {
376
+ return createHash("sha256").update(s).digest("hex");
377
+ }
378
+ function chainHash(key, prev, core) {
379
+ if (key) return { hash: createHmac("sha256", key).update(`${prev}
380
+ ${core}`).digest("hex"), alg: "hmac-sha256" };
381
+ return { hash: sha(`${prev}
382
+ ${core}`) };
383
+ }
384
+ function readLastLine(p) {
385
+ if (!fs2.existsSync(p)) return null;
386
+ const fd = fs2.openSync(p, "r");
387
+ try {
388
+ const size = fs2.fstatSync(fd).size;
389
+ if (size === 0) return null;
390
+ const readLen = Math.min(size, 64 * 1024);
391
+ const buf = Buffer.alloc(readLen);
392
+ fs2.readSync(fd, buf, 0, readLen, size - readLen);
393
+ const lines = buf.toString("utf8").split("\n").filter((l) => l.trim().length > 0);
394
+ return lines.length ? lines[lines.length - 1] : null;
395
+ } finally {
396
+ fs2.closeSync(fd);
397
+ }
398
+ }
399
+ function recordSealed(entry) {
400
+ const p = defaultSealedLogPath();
401
+ try {
402
+ withFileLockSync(p, () => {
403
+ let prevHash = "";
404
+ const last = readLastLine(p);
405
+ if (last) {
406
+ try {
407
+ prevHash = JSON.parse(last).hash ?? "";
408
+ } catch {
409
+ prevHash = "";
410
+ }
411
+ }
412
+ const { hash, alg } = chainHash(ledgerKey(), prevHash, coreString(entry));
413
+ const chained = { ...entry, prev: prevHash, hash, ...alg ? { alg } : {} };
414
+ fs2.mkdirSync(path2.dirname(p), { recursive: true });
415
+ fs2.appendFileSync(p, JSON.stringify(chained) + "\n");
416
+ });
417
+ } catch {
418
+ }
419
+ }
420
+
421
+ // src/t3-client.ts
422
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
423
+ import fs3 from "node:fs";
424
+ import path3 from "node:path";
425
+
426
+ // src/constants.ts
427
+ var SENTINEL = "__BLINDFOLD__";
428
+ var CONTRACT_TAIL = "blindfold-proxy";
429
+ var CONTRACT_VERSION = "0.5.6";
430
+
431
+ // src/t3-client.ts
432
+ var T3_TIMEOUT_MS = Number(process.env.BLINDFOLD_T3_TIMEOUT_MS) || 3e4;
433
+ var T3TimeoutError = class extends Error {
434
+ };
435
+ function withDeadline(promise, label, ms = T3_TIMEOUT_MS) {
436
+ return new Promise((resolve, reject) => {
437
+ const timer = setTimeout(() => reject(new T3TimeoutError(`T3 ${label} timed out after ${ms}ms`)), ms);
438
+ if (typeof timer.unref === "function") timer.unref();
439
+ promise.then(
440
+ (v) => {
441
+ clearTimeout(timer);
442
+ resolve(v);
443
+ },
444
+ (e) => {
445
+ clearTimeout(timer);
446
+ reject(e);
447
+ }
448
+ );
449
+ });
450
+ }
451
+ function egressCachePath() {
452
+ return process.env.BLINDFOLD_EGRESS_CACHE ?? path3.join(stateDir(), "egress-hosts.json");
453
+ }
454
+ function loadEgressHosts(did) {
455
+ try {
456
+ const all = JSON.parse(fs3.readFileSync(egressCachePath(), "utf8"));
457
+ return Array.isArray(all[did]) ? all[did] : [];
458
+ } catch {
459
+ return [];
460
+ }
461
+ }
462
+ function saveEgressHosts(did, hosts) {
463
+ const p = egressCachePath();
464
+ withFileLockSync(p, () => {
465
+ let all = {};
466
+ try {
467
+ all = JSON.parse(fs3.readFileSync(p, "utf8"));
468
+ } catch {
469
+ }
470
+ const existing = Array.isArray(all[did]) ? all[did] : [];
471
+ all[did] = Array.from(/* @__PURE__ */ new Set([...existing, ...hosts])).sort();
472
+ fs3.mkdirSync(path3.dirname(p), { recursive: true });
473
+ fs3.writeFileSync(p, JSON.stringify(all, null, 2));
474
+ });
475
+ }
476
+ var sdkCache = null;
477
+ async function loadSdk() {
478
+ if (sdkCache) return sdkCache;
479
+ try {
480
+ const mod = await import("@terminal3/t3n-sdk");
481
+ sdkCache = mod;
482
+ return mod;
483
+ } catch {
484
+ throw new Error(
485
+ "@terminal3/t3n-sdk not installed. Run `npm install @terminal3/t3n-sdk` for REAL T3 mode, or set BLINDFOLD_MOCK=1 to keep using mock mode."
486
+ );
487
+ }
488
+ }
489
+ async function openT3Client(env) {
490
+ if (env.mock) return openMockClient();
491
+ assertRealReady(env);
492
+ return openRealClient(env);
493
+ }
494
+ async function openRealClient(env) {
495
+ if (!/^0x[0-9a-fA-F]{64}$/.test(env.t3nApiKey)) {
496
+ throw new Error("T3N_API_KEY must be a 0x-prefixed 32-byte hex (secp256k1 private key).");
497
+ }
498
+ if (!/^did:t3n:[0-9a-fA-F]+$/.test(env.did)) {
499
+ throw new Error('DID must look like "did:t3n:<hex>".');
500
+ }
501
+ const sdk = await loadSdk();
502
+ sdk.setEnvironment(env.t3Env);
503
+ const baseUrl = env.t3BaseUrl || sdk.NODE_URLS[env.t3Env];
504
+ const wasmComponent = await sdk.loadWasmComponent();
505
+ const address = sdk.eth_get_address(env.t3nApiKey);
506
+ const t3n = new sdk.T3nClient({
507
+ baseUrl,
508
+ wasmComponent,
509
+ handlers: { EthSign: sdk.metamask_sign(address, void 0, env.t3nApiKey) }
510
+ });
511
+ await t3n.handshake();
512
+ await t3n.authenticate(sdk.createEthAuthInput(address));
513
+ const tenant = new sdk.TenantClient({
514
+ environment: env.t3Env,
515
+ baseUrl,
516
+ tenantDid: env.did,
517
+ t3n
518
+ });
519
+ const seedSecret = async (name, value) => {
520
+ await tenant.executeControl("map-entry-set", {
521
+ map_name: tenant.canonicalName("secrets"),
522
+ key: name,
523
+ value
524
+ });
525
+ safeLog("info", { msg: "seeded", name });
526
+ };
527
+ const registerContract2 = async (wasm) => {
528
+ const r = await tenant.contracts.register({
529
+ tail: CONTRACT_TAIL,
530
+ version: CONTRACT_VERSION,
531
+ wasm
532
+ });
533
+ return {
534
+ contractId: r.contract_id ?? r.contractId ?? "(unknown)"
535
+ };
536
+ };
537
+ const invokeForward = async (req) => {
538
+ const raw = await withDeadline(tenant.contracts.execute(CONTRACT_TAIL, {
539
+ version: CONTRACT_VERSION,
540
+ functionName: "forward",
541
+ input: req
542
+ }), "forward");
543
+ if (typeof raw.status === "number" && Array.isArray(raw.headers)) {
544
+ return { status: raw.status, headers: raw.headers, body: raw.body ?? "" };
545
+ }
546
+ return {
547
+ status: raw.code ?? 502,
548
+ headers: [["content-type", "application/json"]],
549
+ body: raw.body ?? ""
550
+ };
551
+ };
552
+ const decodeSecret = (v) => {
553
+ if (typeof v === "string") return v;
554
+ if (Array.isArray(v)) return Buffer.from(v).toString("utf8");
555
+ if (v && typeof v === "object") {
556
+ const o = v;
557
+ if (typeof o.value === "string") return o.value;
558
+ if (Array.isArray(o.value)) return Buffer.from(o.value).toString("utf8");
559
+ const entry = o.entry;
560
+ if (entry && typeof entry.value === "string") return entry.value;
561
+ }
562
+ return "";
563
+ };
564
+ const releaseSecret = async (name) => {
565
+ try {
566
+ const r = await withDeadline(tenant.contracts.execute(CONTRACT_TAIL, {
567
+ version: CONTRACT_VERSION,
568
+ functionName: "release-to-tenant",
569
+ input: { secret_key: name }
570
+ }), "release-to-tenant");
571
+ if (r && r.ok && r.value) return r.value;
572
+ } catch (e) {
573
+ if (e instanceof T3TimeoutError) throw e;
574
+ }
575
+ const direct = decodeSecret(
576
+ await withDeadline(tenant.executeControl("map-entry-get", {
577
+ map_name: tenant.canonicalName("secrets"),
578
+ key: name
579
+ }), "map-entry-get")
580
+ );
581
+ if (!direct) throw new Error(`secret "${name}" not found in the secrets map`);
582
+ return direct;
583
+ };
584
+ const me = async () => {
585
+ const info = await tenant.tenant.me();
586
+ return { tenant: String(info.tenant ?? ""), status: info.status };
587
+ };
588
+ const agentAuthUpdate = async (agentDid, hosts, functions) => {
589
+ let ucv = "0.1.0";
590
+ if (typeof sdk.getScriptVersion === "function") {
591
+ try {
592
+ const v = await sdk.getScriptVersion(baseUrl, "tee:user/contracts");
593
+ if (typeof v === "string" && /^\d/.test(v)) ucv = v;
594
+ } catch {
595
+ }
596
+ }
597
+ const didHex = env.did.replace(/^did:t3n:/, "");
598
+ const revoking = functions.length === 0 && hosts.length === 0;
599
+ const scripts = [{
600
+ scriptName: `z:${didHex}:${CONTRACT_TAIL}`,
601
+ versionReq: `>=${CONTRACT_VERSION}`,
602
+ functions: revoking ? ["forward"] : functions,
603
+ allowedHosts: revoking ? [] : hosts
604
+ }];
605
+ await t3n.execute({
606
+ script_name: "tee:user/contracts",
607
+ script_version: ucv,
608
+ function_name: "agent-auth-update",
609
+ input: { agents: [{ agentDid, scripts }] }
610
+ });
611
+ };
612
+ const grantEgress = async (hosts, opts) => {
613
+ const prev = opts?.replace ? [] : loadEgressHosts(env.did);
614
+ const merged = Array.from(/* @__PURE__ */ new Set([...prev, ...hosts])).sort();
615
+ await agentAuthUpdate(env.did, merged, ["forward", "release-to-tenant"]);
616
+ saveEgressHosts(env.did, merged);
617
+ return merged;
618
+ };
619
+ const setAgentGrant = (agentDid, hosts, functions) => agentAuthUpdate(agentDid, hosts, functions);
620
+ const verifySecret = async (name) => {
621
+ try {
622
+ const value = await releaseSecret(name);
623
+ return { present: true, length: value.length, fingerprint: createHash2("sha256").update(value).digest("hex").slice(0, 8) };
624
+ } catch {
625
+ return { present: false, length: 0, fingerprint: "" };
626
+ }
627
+ };
628
+ const getBalance = async () => {
629
+ const page = await withDeadline(tenant.token.getUsage(), "token.getUsage");
630
+ const b = page.balance ?? {};
631
+ return {
632
+ available: Number(b.available ?? 0),
633
+ reserved: Number(b.reserved ?? 0),
634
+ creditExhausted: Boolean(b.credit_exhausted),
635
+ storageDeposit: b.storage_deposit != null ? Number(b.storage_deposit) : void 0
636
+ };
637
+ };
638
+ return {
639
+ close: async () => {
640
+ },
641
+ seedSecret,
642
+ invokeForward,
643
+ registerContract: registerContract2,
644
+ releaseSecret,
645
+ me,
646
+ grantEgress,
647
+ setAgentGrant,
648
+ verifySecret,
649
+ getBalance,
650
+ isReal: true
651
+ };
652
+ }
653
+ function openMockClient() {
654
+ return {
655
+ close: async () => {
656
+ },
657
+ async seedSecret(name, value) {
658
+ if (!value || value.length === 0) throw new Error(`secret ${name} is empty`);
659
+ safeLog("info", { msg: "mock-seed (value dropped, length only)", name, length: value.length });
660
+ },
661
+ async registerContract(wasm) {
662
+ safeLog("info", { msg: "mock-register-contract", wasmBytes: wasm.byteLength });
663
+ return { contractId: "mock-contract-1" };
664
+ },
665
+ async invokeForward(req) {
666
+ safeLog("info", {
667
+ msg: "mock-forward",
668
+ method: req.method,
669
+ url: schemeAndHost(req.url),
670
+ secret_key: req.secret_key
671
+ });
672
+ const body = `{"mock":true,"note":"Blindfold mock mode \u2014 no real call made.","echo":{"url":${JSON.stringify(req.url)}}}`;
673
+ return { status: 200, headers: [["content-type", "application/json"]], body };
674
+ },
675
+ async releaseSecret(name) {
676
+ safeLog("info", { msg: "mock-release", name });
677
+ return `mock-released:${name}`;
678
+ },
679
+ async me() {
680
+ return { tenant: "did:t3n:mock", status: "active" };
681
+ },
682
+ async grantEgress(hosts) {
683
+ safeLog("info", { msg: "mock-grant-egress", hosts });
684
+ return hosts;
685
+ },
686
+ async setAgentGrant(agentDid, hosts, functions) {
687
+ safeLog("info", { msg: "mock-set-agent-grant", agentDid, hosts, functions });
688
+ },
689
+ async verifySecret(name) {
690
+ return { present: true, length: 0, fingerprint: `mock-${name}`.slice(0, 8) };
691
+ },
692
+ async getBalance() {
693
+ return { available: 1e9, reserved: 0, creditExhausted: false, mock: true };
694
+ },
695
+ isReal: false
696
+ };
697
+ }
698
+ function schemeAndHost(url) {
699
+ try {
700
+ const u = new URL(url);
701
+ return `${u.protocol}//${u.host}`;
702
+ } catch {
703
+ return "<bad-url>";
704
+ }
705
+ }
706
+
707
+ // src/register.ts
708
+ async function registerSecret(opts) {
709
+ const env = loadBlindfoldEnv();
710
+ const t3 = await openT3Client(env);
711
+ try {
712
+ let source = "stdin";
713
+ let value;
714
+ if (opts.value !== void 0) {
715
+ value = opts.value;
716
+ source = "explicit";
717
+ } else if (opts.fromEnv) {
718
+ value = pluckSecret(opts.fromEnv);
719
+ source = `env:${opts.fromEnv}`;
720
+ } else {
721
+ value = await readSecretLine(` Value for "${opts.name}" (input is hidden): `);
722
+ if (!value) throw new Error("empty value");
723
+ }
724
+ if (value.includes(SENTINEL)) {
725
+ throw new Error(`Secret value contains the Blindfold sentinel string "${SENTINEL}" \u2014 this would cause infinite substitution. Use a different value.`);
726
+ }
727
+ await t3.seedSecret(opts.name, value);
728
+ const length = value.length;
729
+ const mode = env.mock ? "mock" : "real";
730
+ recordSealed({
731
+ t: (/* @__PURE__ */ new Date()).toISOString(),
732
+ name: opts.name,
733
+ source,
734
+ length,
735
+ mode,
736
+ tenant_did: env.did,
737
+ map_name: env.did ? `z:${env.did.replace(/^did:t3n:/, "")}:secrets` : "(mock)"
738
+ });
739
+ safeLog("info", { msg: "registered", name: opts.name, source, length, mode });
740
+ } finally {
741
+ await t3.close();
742
+ }
743
+ }
744
+ async function registerContract(wasm) {
745
+ const env = loadBlindfoldEnv();
746
+ const t3 = await openT3Client(env);
747
+ try {
748
+ return await t3.registerContract(wasm);
749
+ } finally {
750
+ await t3.close();
751
+ }
752
+ }
753
+ export {
754
+ registerContract,
755
+ registerSecret
756
+ };