@hasna/skills 0.10.90 → 0.10.91
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/README.md +12 -3
- package/THIRD_PARTY_NOTICES +5926 -0
- package/bin/index.js +572 -1786
- package/bin/mcp.js +66 -1353
- package/bin/migrate.js +8 -5
- package/bin/server.js +73819 -12659
- package/bin/worker.js +60586 -7855
- package/dist/index.js +11 -5
- package/dist/lib/execution-secrets.d.ts +17 -1
- package/dist/runtime-prerequisites.js +8 -5
- package/dist/sdk/index.js +78725 -18745
- package/package.json +8 -5
package/bin/mcp.js
CHANGED
|
@@ -38,7 +38,7 @@ var package_default;
|
|
|
38
38
|
var init_package = __esm(() => {
|
|
39
39
|
package_default = {
|
|
40
40
|
name: "@hasna/skills",
|
|
41
|
-
version: "0.10.
|
|
41
|
+
version: "0.10.91",
|
|
42
42
|
description: "Skills library for AI coding agents",
|
|
43
43
|
type: "module",
|
|
44
44
|
bin: {
|
|
@@ -82,6 +82,7 @@ var init_package = __esm(() => {
|
|
|
82
82
|
"docs/runtime-activation.md",
|
|
83
83
|
"schemas/",
|
|
84
84
|
"LICENSE",
|
|
85
|
+
"THIRD_PARTY_NOTICES",
|
|
85
86
|
"README.md",
|
|
86
87
|
"!dist/**/*test-utils.d.ts",
|
|
87
88
|
"!dist/**/*fixtures.d.ts"
|
|
@@ -89,9 +90,9 @@ var init_package = __esm(() => {
|
|
|
89
90
|
main: "./dist/index.js",
|
|
90
91
|
types: "./dist/index.d.ts",
|
|
91
92
|
scripts: {
|
|
92
|
-
clean: "rm -rf bin/ dist/",
|
|
93
|
-
build: "bun run clean && bun build --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
|
|
94
|
-
"build:js": "rm -rf dist && bun build ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
|
|
93
|
+
clean: "rm -rf bin/ dist/ THIRD_PARTY_NOTICES",
|
|
94
|
+
build: "bun run clean && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/cli/index.tsx --outdir ./bin --target bun && bun build --external @hasna/secrets --external @modelcontextprotocol/sdk ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/index.ts --outfile ./bin/server.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/worker.ts --outfile ./bin/worker.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/migrate.ts --outfile ./bin/migrate.js --target bun && bun build --external @hasna/secrets --external @hasna/contracts/kernel-lock ./src/server/maintenance.ts --outfile ./bin/maintenance.js --target bun && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts && bun scripts/third-party-notices.ts",
|
|
95
|
+
"build:js": "rm -rf dist && bun build --external @hasna/secrets ./src/index.ts ./src/storage.ts ./src/sdk/index.ts ./src/admin-contract.ts ./src/runtime-prerequisites.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --declaration --outDir dist && bun scripts/stage-kernel-lock-assets.ts",
|
|
95
96
|
test: "bun test --timeout 30000",
|
|
96
97
|
dev: "bun run ./src/cli/index.tsx",
|
|
97
98
|
"dev:watch": "bun --watch run ./src/cli/index.tsx",
|
|
@@ -142,7 +143,6 @@ var init_package = __esm(() => {
|
|
|
142
143
|
"@aws-sdk/client-ecs": "^3.1079.0",
|
|
143
144
|
"@aws-sdk/client-s3": "^3.1079.0",
|
|
144
145
|
"@hasna/events": "0.1.18",
|
|
145
|
-
"@hasna/secrets": "0.4.2",
|
|
146
146
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
147
147
|
chalk: "^5.3.0",
|
|
148
148
|
commander: "^12.1.0",
|
|
@@ -154,6 +154,9 @@ var init_package = __esm(() => {
|
|
|
154
154
|
zod: "^4.3.6",
|
|
155
155
|
"@hasna/contracts": "1.3.5"
|
|
156
156
|
},
|
|
157
|
+
optionalDependencies: {
|
|
158
|
+
"@hasna/secrets": "0.4.2"
|
|
159
|
+
},
|
|
157
160
|
engines: {
|
|
158
161
|
bun: ">=1.0.0"
|
|
159
162
|
},
|
|
@@ -1432,6 +1435,9 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
1432
1435
|
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
1433
1436
|
} catch (error) {
|
|
1434
1437
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
1438
|
+
if (translated?.message.includes("(@hasna/secrets) is not installed")) {
|
|
1439
|
+
throw new SkillsFleetCredentialError(`${translated.message} @hasna/secrets is optional and private: install it with access to it.`, "MISSING_API_CREDENTIAL");
|
|
1440
|
+
}
|
|
1435
1441
|
if (translated)
|
|
1436
1442
|
throw translated;
|
|
1437
1443
|
throw error;
|
|
@@ -46326,1312 +46332,19 @@ var init_cloud_executions = __esm(() => {
|
|
|
46326
46332
|
init_fleet_credentials();
|
|
46327
46333
|
});
|
|
46328
46334
|
|
|
46329
|
-
//
|
|
46330
|
-
import {
|
|
46331
|
-
import {
|
|
46332
|
-
|
|
46333
|
-
|
|
46334
|
-
import { createRequire as createRequire2 } from "module";
|
|
46335
|
-
import { hostname as osHostname2 } from "os";
|
|
46336
|
-
import { isAbsolute as isAbsolute28, join as join40 } from "path";
|
|
46337
|
-
import { createRequire as createRequire22 } from "module";
|
|
46338
|
-
function envToken2(name) {
|
|
46339
|
-
return name.toUpperCase().replace(/-/g, "_");
|
|
46340
|
-
}
|
|
46341
|
-
function clientTransportEnvKeys2(name) {
|
|
46342
|
-
const envSegment = envToken2(name);
|
|
46343
|
-
return {
|
|
46344
|
-
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
46345
|
-
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
46346
|
-
};
|
|
46347
|
-
}
|
|
46348
|
-
function credentialOverrideEnvKey2(name) {
|
|
46349
|
-
return `HASNA_${envToken2(name)}_API_KEY_OVERRIDE`;
|
|
46350
|
-
}
|
|
46351
|
-
function credentialPointerEnvKey2(name) {
|
|
46352
|
-
return `HASNA_${envToken2(name)}_API_KEY_REF`;
|
|
46353
|
-
}
|
|
46354
|
-
function homeDir2(env) {
|
|
46355
|
-
const home = env.HOME?.trim();
|
|
46356
|
-
return home ? home : null;
|
|
46357
|
-
}
|
|
46358
|
-
function absoluteOverride2(env, key2) {
|
|
46359
|
-
const value = env[key2]?.trim();
|
|
46360
|
-
return value && isAbsolute28(value) ? value : null;
|
|
46361
|
-
}
|
|
46362
|
-
function hasnaHomeDir2(env) {
|
|
46363
|
-
const override = absoluteOverride2(env, HASNA_HOME_ENV_KEY2);
|
|
46364
|
-
if (override)
|
|
46365
|
-
return override;
|
|
46366
|
-
const home = homeDir2(env);
|
|
46367
|
-
return home ? join40(home, HASNA_HOME_DIR2) : null;
|
|
46368
|
-
}
|
|
46369
|
-
function appConfigDir2(name, env) {
|
|
46370
|
-
const configRoot = absoluteOverride2(env, HASNA_CONFIG_HOME_ENV_KEY2);
|
|
46371
|
-
if (configRoot)
|
|
46372
|
-
return join40(configRoot, name);
|
|
46373
|
-
const root = hasnaHomeDir2(env);
|
|
46374
|
-
return root ? join40(root, name, CONFIG_SUBDIR2) : null;
|
|
46375
|
-
}
|
|
46376
|
-
function credentialDiskSourceList2(name, env, profile = null) {
|
|
46377
|
-
if (!SAFE_APP_SLUG2.test(name))
|
|
46378
|
-
return [];
|
|
46379
|
-
const directory2 = appConfigDir2(name, env);
|
|
46380
|
-
if (!directory2)
|
|
46381
|
-
return [];
|
|
46382
|
-
const file2 = profile ? `${CREDENTIALS_FILE2}-${profile}` : CREDENTIALS_FILE2;
|
|
46383
|
-
return [{ path: join40(directory2, file2), tier: "disk" }];
|
|
46384
|
-
}
|
|
46385
|
-
function credentialDiskSources2(name, env) {
|
|
46386
|
-
return credentialDiskSourceList2(name, env, null).map((s) => s.path);
|
|
46387
|
-
}
|
|
46388
|
-
function profileDiskSources2(name, env, profile) {
|
|
46389
|
-
return credentialDiskSourceList2(name, env, profile).map((s) => s.path);
|
|
46390
|
-
}
|
|
46391
|
-
function parseEnvFile2(text4) {
|
|
46392
|
-
const values = new Map;
|
|
46393
|
-
const unusable = new Set;
|
|
46394
|
-
for (const rawLine of text4.split(/\r?\n/)) {
|
|
46395
|
-
const line = rawLine.trim();
|
|
46396
|
-
if (line.length === 0 || line.startsWith("#"))
|
|
46397
|
-
continue;
|
|
46398
|
-
const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
46399
|
-
const equals = withoutExport.indexOf("=");
|
|
46400
|
-
if (equals <= 0)
|
|
46401
|
-
continue;
|
|
46402
|
-
const key2 = withoutExport.slice(0, equals).trim();
|
|
46403
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key2))
|
|
46404
|
-
continue;
|
|
46405
|
-
let value = withoutExport.slice(equals + 1).trim();
|
|
46406
|
-
const quote3 = value[0];
|
|
46407
|
-
if (quote3 === '"' || quote3 === "'") {
|
|
46408
|
-
if (value.length < 2 || !value.endsWith(quote3)) {
|
|
46409
|
-
unusable.add(key2);
|
|
46410
|
-
continue;
|
|
46411
|
-
}
|
|
46412
|
-
value = value.slice(1, -1);
|
|
46413
|
-
}
|
|
46414
|
-
if (value.trim().length === 0) {
|
|
46415
|
-
unusable.add(key2);
|
|
46416
|
-
continue;
|
|
46417
|
-
}
|
|
46418
|
-
if (values.has(key2) && values.get(key2) !== value)
|
|
46419
|
-
unusable.add(key2);
|
|
46420
|
-
values.set(key2, value);
|
|
46421
|
-
}
|
|
46422
|
-
return { values, unusable };
|
|
46423
|
-
}
|
|
46424
|
-
function configFileModeAllowed2(mode) {
|
|
46425
|
-
const permissions = mode & 4095;
|
|
46426
|
-
return permissions === 256 || permissions === 384;
|
|
46427
|
-
}
|
|
46428
|
-
function configFileReadsCoherent2(before, after) {
|
|
46429
|
-
return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
|
|
46430
|
-
}
|
|
46431
|
-
function readAppConfigFile2(path) {
|
|
46432
|
-
const unsafe = (reason2) => {
|
|
46433
|
-
throw new CredentialFileUnsafeError2(path, reason2);
|
|
46434
|
-
};
|
|
46435
|
-
let fd = -1;
|
|
46436
|
-
try {
|
|
46437
|
-
fd = openSync22(path, O_RDONLY3 | O_NOFOLLOW3 | O_NONBLOCK3);
|
|
46438
|
-
} catch (error61) {
|
|
46439
|
-
const code2 = error61.code;
|
|
46440
|
-
if (code2 === "ENOENT" || code2 === "ENOTDIR")
|
|
46441
|
-
return null;
|
|
46442
|
-
if (code2 === "ELOOP")
|
|
46443
|
-
unsafe("the path is a symlink");
|
|
46444
|
-
unsafe(`the path could not be opened (${code2 ?? "unknown error"})`);
|
|
46445
|
-
}
|
|
46446
|
-
try {
|
|
46447
|
-
const before = fstatSync22(fd);
|
|
46448
|
-
if (!before.isFile())
|
|
46449
|
-
unsafe("the path is not a regular file");
|
|
46450
|
-
if (!configFileModeAllowed2(before.mode)) {
|
|
46451
|
-
unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
|
|
46452
|
-
}
|
|
46453
|
-
const uid2 = process.getuid?.() ?? process.geteuid?.();
|
|
46454
|
-
if (uid2 !== undefined && before.uid !== uid2)
|
|
46455
|
-
unsafe("the file is not owned by the current user");
|
|
46456
|
-
if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
46457
|
-
unsafe("the file exceeds the size limit");
|
|
46458
|
-
const bytes = readFileSync23(fd);
|
|
46459
|
-
const after = fstatSync22(fd);
|
|
46460
|
-
if (!configFileReadsCoherent2(before, after)) {
|
|
46461
|
-
unsafe("the file changed while being read");
|
|
46462
|
-
}
|
|
46463
|
-
return parseEnvFile2(bytes.toString("utf8"));
|
|
46464
|
-
} finally {
|
|
46465
|
-
if (fd !== -1)
|
|
46466
|
-
closeSync22(fd);
|
|
46467
|
-
}
|
|
46468
|
-
}
|
|
46469
|
-
function readCredentialFile2(path, apiKeyKeys) {
|
|
46470
|
-
const parsed = readAppConfigFile2(path);
|
|
46471
|
-
if (!parsed)
|
|
46472
|
-
return null;
|
|
46473
|
-
for (const key2 of apiKeyKeys) {
|
|
46474
|
-
if (parsed.unusable.has(key2)) {
|
|
46475
|
-
throw new CredentialFileUnsafeError2(path, `${key2} is declared but blank or malformed`);
|
|
46476
|
-
}
|
|
46477
|
-
}
|
|
46478
|
-
const values = apiKeyKeys.map((key2) => parsed.values.get(key2)?.trim()).filter((value) => Boolean(value));
|
|
46479
|
-
if (new Set(values).size > 1) {
|
|
46480
|
-
throw new CredentialFileUnsafeError2(path, "credential aliases disagree");
|
|
46481
|
-
}
|
|
46482
|
-
return values[0] ?? null;
|
|
46483
|
-
}
|
|
46484
|
-
function appConfigDiskValue2(name, env, keys2) {
|
|
46485
|
-
const wanted = keys2.filter((key2) => !CREDENTIAL_SHAPED_KEY2.test(key2));
|
|
46486
|
-
if (wanted.length === 0)
|
|
46487
|
-
return null;
|
|
46488
|
-
for (const path of credentialDiskSources2(name, env)) {
|
|
46489
|
-
const parsed = readAppConfigFile2(path);
|
|
46490
|
-
if (!parsed)
|
|
46491
|
-
continue;
|
|
46492
|
-
if (wanted.some((key2) => parsed.unusable.has(key2))) {
|
|
46493
|
-
return { key: wanted.find((key2) => parsed.unusable.has(key2)), value: "", path, unusable: true };
|
|
46494
|
-
}
|
|
46495
|
-
const values = wanted.map((key2) => parsed.values.get(key2)?.trim()).filter((value) => Boolean(value));
|
|
46496
|
-
if (new Set(values).size > 1)
|
|
46497
|
-
throw new CredentialFileUnsafeError2(path, "configuration aliases disagree");
|
|
46498
|
-
for (const key2 of wanted) {
|
|
46499
|
-
if (parsed.unusable.has(key2))
|
|
46500
|
-
return { key: key2, value: "", path, unusable: true };
|
|
46501
|
-
const value = parsed.values.get(key2)?.trim();
|
|
46502
|
-
if (value)
|
|
46503
|
-
return { key: key2, value, path };
|
|
46504
|
-
}
|
|
46505
|
-
}
|
|
46506
|
-
return null;
|
|
46507
|
-
}
|
|
46508
|
-
function assertUsableCredential2(appName, source, value) {
|
|
46509
|
-
if (VAULT_POINTER_SHAPE2.test(value)) {
|
|
46510
|
-
throw new CredentialResolutionError2(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey2(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
|
|
46511
|
-
}
|
|
46512
|
-
if (!ILLEGAL_IN_HEADER_VALUE2.test(value))
|
|
46513
|
-
return;
|
|
46514
|
-
throw new CredentialResolutionError2(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
|
|
46515
|
-
}
|
|
46516
|
-
function sealCredential2(fields2) {
|
|
46517
|
-
const { apiKey } = fields2;
|
|
46518
|
-
const visible = {
|
|
46519
|
-
tier: fields2.tier,
|
|
46520
|
-
source: fields2.source,
|
|
46521
|
-
deliberate: fields2.deliberate,
|
|
46522
|
-
diskCandidates: Object.freeze([...fields2.diskCandidates]),
|
|
46523
|
-
warning: fields2.warning
|
|
46524
|
-
};
|
|
46525
|
-
const sealed = { ...visible };
|
|
46526
|
-
Object.defineProperty(sealed, "apiKey", {
|
|
46527
|
-
value: apiKey,
|
|
46528
|
-
enumerable: false,
|
|
46529
|
-
writable: false,
|
|
46530
|
-
configurable: false
|
|
46531
|
-
});
|
|
46532
|
-
if (fields2.pointerVaultKey !== undefined) {
|
|
46533
|
-
Object.defineProperty(sealed, "pointerVaultKey", {
|
|
46534
|
-
value: fields2.pointerVaultKey,
|
|
46535
|
-
enumerable: false,
|
|
46536
|
-
writable: false,
|
|
46537
|
-
configurable: false
|
|
46538
|
-
});
|
|
46539
|
-
}
|
|
46540
|
-
Object.defineProperty(sealed, INSPECT_CUSTOM2, {
|
|
46541
|
-
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
46542
|
-
enumerable: false,
|
|
46543
|
-
writable: false,
|
|
46544
|
-
configurable: false
|
|
46545
|
-
});
|
|
46546
|
-
Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
|
|
46547
|
-
value: true,
|
|
46548
|
-
enumerable: false,
|
|
46549
|
-
writable: false,
|
|
46550
|
-
configurable: false
|
|
46551
|
-
});
|
|
46552
|
-
return Object.freeze(sealed);
|
|
46553
|
-
}
|
|
46554
|
-
function isSealedCredential(credential) {
|
|
46555
|
-
return credential[CREDENTIAL_SEAL2] === true;
|
|
46556
|
-
}
|
|
46557
|
-
function explicitCredential(appName, apiKey) {
|
|
46558
|
-
const source = "explicit apiKey option";
|
|
46559
|
-
assertUsableCredential2(appName, source, apiKey);
|
|
46560
|
-
return sealCredential2({
|
|
46561
|
-
apiKey,
|
|
46562
|
-
tier: "argument",
|
|
46563
|
-
source,
|
|
46564
|
-
deliberate: true,
|
|
46565
|
-
diskCandidates: [],
|
|
46566
|
-
warning: null
|
|
46567
|
-
});
|
|
46568
|
-
}
|
|
46569
|
-
function validateAndSealResolvedCredential(appName, credential) {
|
|
46570
|
-
const apiKey = credential.apiKey;
|
|
46571
|
-
assertUsableCredential2(appName, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE, apiKey);
|
|
46572
|
-
if (!isSealedCredential(credential)) {
|
|
46573
|
-
return sealCredential2({
|
|
46574
|
-
apiKey,
|
|
46575
|
-
tier: "argument",
|
|
46576
|
-
source: CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE,
|
|
46577
|
-
deliberate: true,
|
|
46578
|
-
diskCandidates: [],
|
|
46579
|
-
warning: null
|
|
46580
|
-
});
|
|
46581
|
-
}
|
|
46582
|
-
return sealCredential2({
|
|
46583
|
-
apiKey,
|
|
46584
|
-
tier: credential.tier,
|
|
46585
|
-
source: credential.source,
|
|
46586
|
-
deliberate: credential.deliberate,
|
|
46587
|
-
diskCandidates: credential.diskCandidates,
|
|
46588
|
-
warning: credential.warning,
|
|
46589
|
-
...credential.pointerVaultKey !== undefined ? { pointerVaultKey: credential.pointerVaultKey } : {}
|
|
46590
|
-
});
|
|
46591
|
-
}
|
|
46592
|
-
function firstEnvValue2(env, keys2) {
|
|
46593
|
-
for (const key2 of keys2) {
|
|
46594
|
-
if (!Object.prototype.hasOwnProperty.call(env, key2))
|
|
46595
|
-
continue;
|
|
46596
|
-
const value = env[key2]?.trim();
|
|
46597
|
-
if (value)
|
|
46598
|
-
return { key: key2, value };
|
|
46599
|
-
}
|
|
46600
|
-
return null;
|
|
46601
|
-
}
|
|
46602
|
-
function isAmbientEnvironment2(env) {
|
|
46603
|
-
return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
|
|
46604
|
-
}
|
|
46605
|
-
function defaultKeychainRunner2(argv) {
|
|
46606
|
-
const result = spawnSync5(KEYCHAIN_SECURITY_BIN2, [...argv], {
|
|
46607
|
-
encoding: "utf8",
|
|
46608
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
46609
|
-
timeout: KEYCHAIN_SPAWN_TIMEOUT_MS2
|
|
46610
|
-
});
|
|
46611
|
-
return {
|
|
46612
|
-
status: result.status,
|
|
46613
|
-
stdout: result.stdout ?? "",
|
|
46614
|
-
stderr: result.error ? result.error.message : result.stderr ?? ""
|
|
46615
|
-
};
|
|
46616
|
-
}
|
|
46617
|
-
function keychainTierEnabled2(env, options) {
|
|
46618
|
-
if ((options.platform ?? process.platform) !== "darwin")
|
|
46619
|
-
return false;
|
|
46620
|
-
if (options.enabled !== undefined)
|
|
46621
|
-
return options.enabled;
|
|
46622
|
-
return options.run !== undefined || isAmbientEnvironment2(env);
|
|
46623
|
-
}
|
|
46624
|
-
function keychainAccount2(env, options) {
|
|
46625
|
-
const station = env[KEYCHAIN_STATION_ENV_KEY2]?.trim();
|
|
46626
|
-
if (station)
|
|
46627
|
-
return station;
|
|
46628
|
-
const host = (options.hostname ?? osHostname2)().split(".")[0]?.trim() ?? "";
|
|
46629
|
-
if (host)
|
|
46630
|
-
return host;
|
|
46631
|
-
const user = env.USER?.trim();
|
|
46632
|
-
return user || null;
|
|
46633
|
-
}
|
|
46634
|
-
function keychainFailureHint2(text4) {
|
|
46635
|
-
const line = text4.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
|
|
46636
|
-
const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
|
|
46637
|
-
return clean ? `: ${clean}` : "";
|
|
46638
|
-
}
|
|
46639
|
-
function readKeychainItem2(name, env, kind, options) {
|
|
46640
|
-
if (!SAFE_APP_SLUG2.test(name) || !keychainTierEnabled2(env, options))
|
|
46641
|
-
return null;
|
|
46642
|
-
const account = keychainAccount2(env, options);
|
|
46643
|
-
if (!account)
|
|
46644
|
-
return null;
|
|
46645
|
-
const service = `${KEYCHAIN_SERVICE_PREFIX2}.${name}.${kind}`;
|
|
46646
|
-
const source = `keychain:${service}@${account}`;
|
|
46647
|
-
const run = options.run ?? defaultKeychainRunner2;
|
|
46648
|
-
let result;
|
|
46649
|
-
try {
|
|
46650
|
-
result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
|
|
46651
|
-
} catch (error61) {
|
|
46652
|
-
const reason2 = keychainFailureHint2(error61 instanceof Error ? error61.message : String(error61));
|
|
46653
|
-
throw new CredentialResolutionError2(name, `The Keychain lookup for ${source} could not run${reason2}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
|
|
46654
|
-
}
|
|
46655
|
-
if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS2)
|
|
46656
|
-
return null;
|
|
46657
|
-
if (result.status !== 0) {
|
|
46658
|
-
throw new CredentialResolutionError2(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint2(result.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
|
|
46659
|
-
}
|
|
46660
|
-
const value = result.stdout.trim();
|
|
46661
|
-
if (!value) {
|
|
46662
|
-
throw new CredentialResolutionError2(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
|
|
46663
|
-
}
|
|
46664
|
-
return { value, source };
|
|
46665
|
-
}
|
|
46666
|
-
function keychainConfigValue2(name, env, options = {}) {
|
|
46667
|
-
return readKeychainItem2(name, env, "api-url", options);
|
|
46668
|
-
}
|
|
46669
|
-
function snapshotClientEnvironment2(name, env) {
|
|
46670
|
-
const keys2 = clientTransportEnvKeys2(name);
|
|
46671
|
-
const ambient = isAmbientEnvironment2(env);
|
|
46672
|
-
const snapshot2 = Object.create(null);
|
|
46673
|
-
for (const key2 of [
|
|
46674
|
-
...keys2.apiUrlKeys,
|
|
46675
|
-
...keys2.apiKeyKeys,
|
|
46676
|
-
credentialOverrideEnvKey2(name),
|
|
46677
|
-
credentialPointerEnvKey2(name),
|
|
46678
|
-
CREDENTIAL_PROFILE_ENV_KEY2,
|
|
46679
|
-
"HOME",
|
|
46680
|
-
HASNA_HOME_ENV_KEY2,
|
|
46681
|
-
HASNA_CONFIG_HOME_ENV_KEY2,
|
|
46682
|
-
KEYCHAIN_STATION_ENV_KEY2,
|
|
46683
|
-
"USER"
|
|
46684
|
-
]) {
|
|
46685
|
-
const descriptor = Object.getOwnPropertyDescriptor(env, key2);
|
|
46686
|
-
if (!descriptor)
|
|
46687
|
-
continue;
|
|
46688
|
-
if (!("value" in descriptor)) {
|
|
46689
|
-
throw new CredentialResolutionError2(name, `${key2} is accessor-backed; client configuration requires own data properties.`, [key2]);
|
|
46690
|
-
}
|
|
46691
|
-
if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
|
|
46692
|
-
throw new CredentialResolutionError2(name, `${key2} must be a string data property.`, [key2]);
|
|
46693
|
-
}
|
|
46694
|
-
snapshot2[key2] = descriptor.value;
|
|
46695
|
-
}
|
|
46696
|
-
if (ambient) {
|
|
46697
|
-
Object.defineProperty(snapshot2, AMBIENT_ENVIRONMENT2, {
|
|
46698
|
-
value: true,
|
|
46699
|
-
enumerable: false,
|
|
46700
|
-
writable: false,
|
|
46701
|
-
configurable: false
|
|
46702
|
-
});
|
|
46703
|
-
}
|
|
46704
|
-
return Object.freeze(snapshot2);
|
|
46705
|
-
}
|
|
46706
|
-
function resolveCredential2(name, env, options = {}) {
|
|
46707
|
-
env = snapshotClientEnvironment2(name, env);
|
|
46708
|
-
const { apiKeyKeys } = clientTransportEnvKeys2(name);
|
|
46709
|
-
const diskPaths = credentialDiskSources2(name, env);
|
|
46710
|
-
if (options.apiKey !== undefined) {
|
|
46711
|
-
const explicitKey = options.apiKey.trim();
|
|
46712
|
-
if (!explicitKey) {
|
|
46713
|
-
throw new CredentialResolutionError2(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
|
|
46714
|
-
}
|
|
46715
|
-
assertUsableCredential2(name, "the explicit apiKey argument", explicitKey);
|
|
46716
|
-
return sealCredential2({
|
|
46717
|
-
apiKey: explicitKey,
|
|
46718
|
-
tier: "argument",
|
|
46719
|
-
source: "explicit apiKey argument",
|
|
46720
|
-
deliberate: true,
|
|
46721
|
-
diskCandidates: diskPaths,
|
|
46722
|
-
warning: null
|
|
46723
|
-
});
|
|
46724
|
-
}
|
|
46725
|
-
const overrideKeyName = credentialOverrideEnvKey2(name);
|
|
46726
|
-
const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
|
|
46727
|
-
if (overrideRaw !== undefined) {
|
|
46728
|
-
const override = overrideRaw.trim();
|
|
46729
|
-
if (!override) {
|
|
46730
|
-
throw new CredentialResolutionError2(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
|
|
46731
|
-
}
|
|
46732
|
-
assertUsableCredential2(name, overrideKeyName, override);
|
|
46733
|
-
return sealCredential2({
|
|
46734
|
-
apiKey: override,
|
|
46735
|
-
tier: "override",
|
|
46736
|
-
source: overrideKeyName,
|
|
46737
|
-
deliberate: true,
|
|
46738
|
-
diskCandidates: diskPaths,
|
|
46739
|
-
warning: null
|
|
46740
|
-
});
|
|
46741
|
-
}
|
|
46742
|
-
const pointerKeyName = credentialPointerEnvKey2(name);
|
|
46743
|
-
const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
|
|
46744
|
-
if (pointerRaw !== undefined) {
|
|
46745
|
-
const pointer = pointerRaw.trim();
|
|
46746
|
-
if (!pointer) {
|
|
46747
|
-
throw new CredentialResolutionError2(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
|
|
46748
|
-
}
|
|
46749
|
-
if (!VAULT_POINTER_SHAPE2.test(pointer)) {
|
|
46750
|
-
throw new CredentialResolutionError2(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
|
|
46751
|
-
}
|
|
46752
|
-
return sealCredential2({
|
|
46753
|
-
apiKey: "",
|
|
46754
|
-
pointerVaultKey: pointer,
|
|
46755
|
-
tier: "pointer",
|
|
46756
|
-
source: pointerKeyName,
|
|
46757
|
-
deliberate: true,
|
|
46758
|
-
diskCandidates: diskPaths,
|
|
46759
|
-
warning: null
|
|
46760
|
-
});
|
|
46761
|
-
}
|
|
46762
|
-
if (options.profile !== undefined && !options.profile.trim()) {
|
|
46763
|
-
throw new CredentialResolutionError2(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
|
|
46764
|
-
}
|
|
46765
|
-
const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY2) ? env[CREDENTIAL_PROFILE_ENV_KEY2] : undefined;
|
|
46766
|
-
if (profileRaw !== undefined && !profileRaw.trim()) {
|
|
46767
|
-
throw new CredentialResolutionError2(name, `${CREDENTIAL_PROFILE_ENV_KEY2} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY2]);
|
|
46768
|
-
}
|
|
46769
|
-
const profile = options.profile?.trim() || profileRaw?.trim();
|
|
46770
|
-
if (profile) {
|
|
46771
|
-
const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY2;
|
|
46772
|
-
if (!SAFE_PROFILE2.test(profile)) {
|
|
46773
|
-
throw new CredentialResolutionError2(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
|
|
46774
|
-
}
|
|
46775
|
-
const paths = profileDiskSources2(name, env, profile);
|
|
46776
|
-
for (const path of paths) {
|
|
46777
|
-
const value = readCredentialFile2(path, apiKeyKeys);
|
|
46778
|
-
if (value) {
|
|
46779
|
-
assertUsableCredential2(name, path, value);
|
|
46780
|
-
return sealCredential2({
|
|
46781
|
-
apiKey: value,
|
|
46782
|
-
tier: "profile",
|
|
46783
|
-
source: path,
|
|
46784
|
-
deliberate: true,
|
|
46785
|
-
diskCandidates: paths,
|
|
46786
|
-
warning: null
|
|
46787
|
-
});
|
|
46788
|
-
}
|
|
46789
|
-
}
|
|
46790
|
-
throw new CredentialResolutionError2(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY2}.`, paths);
|
|
46791
|
-
}
|
|
46792
|
-
const definedEnvEntries = apiKeyKeys.filter((key2) => Object.prototype.hasOwnProperty.call(env, key2) && env[key2] !== undefined).map((key2) => ({ key: key2, value: String(env[key2]).trim() }));
|
|
46793
|
-
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
46794
|
-
if (blankEnv) {
|
|
46795
|
-
throw new CredentialResolutionError2(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
|
|
46796
|
-
}
|
|
46797
|
-
if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
|
|
46798
|
-
throw new CredentialResolutionError2(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
|
|
46799
|
-
}
|
|
46800
|
-
const envHit = firstEnvValue2(env, apiKeyKeys);
|
|
46801
|
-
const keychainHit = readKeychainItem2(name, env, "api-key", options.keychain ?? {});
|
|
46802
|
-
if (keychainHit) {
|
|
46803
|
-
assertUsableCredential2(name, keychainHit.source, keychainHit.value);
|
|
46804
|
-
const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
|
|
46805
|
-
return sealCredential2({
|
|
46806
|
-
apiKey: keychainHit.value,
|
|
46807
|
-
tier: "keychain",
|
|
46808
|
-
source: keychainHit.source,
|
|
46809
|
-
deliberate: false,
|
|
46810
|
-
diskCandidates: diskPaths,
|
|
46811
|
-
warning
|
|
46812
|
-
});
|
|
46813
|
-
}
|
|
46814
|
-
const diskSourceList = credentialDiskSourceList2(name, env, null);
|
|
46815
|
-
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile2(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
46816
|
-
if (diskHits.length > 0) {
|
|
46817
|
-
const winner = diskHits[0];
|
|
46818
|
-
assertUsableCredential2(name, winner.src.path, winner.value);
|
|
46819
|
-
const divergentSources = [
|
|
46820
|
-
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
|
|
46821
|
-
...envHit && envHit.value !== winner.value ? [envHit.key] : []
|
|
46822
|
-
];
|
|
46823
|
-
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
|
|
46824
|
-
return sealCredential2({
|
|
46825
|
-
apiKey: winner.value,
|
|
46826
|
-
tier: winner.src.tier,
|
|
46827
|
-
source: winner.src.path,
|
|
46828
|
-
deliberate: false,
|
|
46829
|
-
diskCandidates: diskPaths,
|
|
46830
|
-
warning
|
|
46831
|
-
});
|
|
46832
|
-
}
|
|
46833
|
-
if (envHit) {
|
|
46834
|
-
assertUsableCredential2(name, envHit.key, envHit.value);
|
|
46835
|
-
return sealCredential2({
|
|
46836
|
-
apiKey: envHit.value,
|
|
46837
|
-
tier: "env",
|
|
46838
|
-
source: envHit.key,
|
|
46839
|
-
deliberate: false,
|
|
46840
|
-
diskCandidates: diskPaths,
|
|
46841
|
-
warning: null
|
|
46842
|
-
});
|
|
46843
|
-
}
|
|
46844
|
-
return null;
|
|
46845
|
-
}
|
|
46846
|
-
async function completePointerCredential2(name, pointerResolution, env = process.env) {
|
|
46847
|
-
const vaultKey = pointerResolution.pointerVaultKey;
|
|
46848
|
-
const pointerEnvKey = pointerResolution.source;
|
|
46849
|
-
if (!vaultKey) {
|
|
46850
|
-
throw new CredentialResolutionError2(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
|
|
46851
|
-
}
|
|
46852
|
-
let secretsSdk;
|
|
46853
|
-
try {
|
|
46854
|
-
secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER);
|
|
46855
|
-
} catch {
|
|
46856
|
-
throw new CredentialResolutionError2(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
46857
|
-
}
|
|
46858
|
-
let client;
|
|
46859
|
-
try {
|
|
46860
|
-
client = secretsSdk.createSecretsClientFromEnv(env);
|
|
46861
|
-
} catch {
|
|
46862
|
-
throw new CredentialResolutionError2(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
46863
|
-
}
|
|
46864
|
-
let secret;
|
|
46335
|
+
// src/lib/execution-secrets.ts
|
|
46336
|
+
import { constants as constants20, closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readSync as readSync15, realpathSync as realpathSync12 } from "fs";
|
|
46337
|
+
import { hostname as stationHostname } from "os";
|
|
46338
|
+
async function defaultSecretsClientFactory(load2 = () => import("@hasna/secrets/sdk")) {
|
|
46339
|
+
let sdk;
|
|
46865
46340
|
try {
|
|
46866
|
-
|
|
46341
|
+
sdk = await load2();
|
|
46867
46342
|
} catch {
|
|
46868
|
-
|
|
46869
|
-
}
|
|
46870
|
-
const value = secret.value;
|
|
46871
|
-
if (!value) {
|
|
46872
|
-
throw new CredentialResolutionError2(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
|
|
46873
|
-
}
|
|
46874
|
-
assertUsableCredential2(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
|
|
46875
|
-
return sealCredential2({
|
|
46876
|
-
apiKey: value,
|
|
46877
|
-
tier: "pointer",
|
|
46878
|
-
source: `${pointerEnvKey} -> vault:${vaultKey}`,
|
|
46879
|
-
deliberate: true,
|
|
46880
|
-
diskCandidates: pointerResolution.diskCandidates,
|
|
46881
|
-
warning: null
|
|
46882
|
-
});
|
|
46883
|
-
}
|
|
46884
|
-
function defaultFleetGatewayBaseUrl2(name) {
|
|
46885
|
-
return `${DEFAULT_FLEET_GATEWAY_ORIGIN2}/${validateAppSlug2(name)}`;
|
|
46886
|
-
}
|
|
46887
|
-
function isValidDnsDomain2(value) {
|
|
46888
|
-
if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN2.test(value) || /[^\x00-\x7f]/.test(value)) {
|
|
46889
|
-
return false;
|
|
46343
|
+
return refuse5("LOCAL_SECRET_UNAVAILABLE", "Vault secret bindings need the optional @hasna/secrets package, which this install could not load. Install @hasna/secrets with access to it, then run again.");
|
|
46890
46344
|
}
|
|
46891
|
-
return
|
|
46345
|
+
return () => sdk.createSecretsClientFromEnv();
|
|
46892
46346
|
}
|
|
46893
|
-
function
|
|
46894
|
-
if (name.length > 63 || !DNS_LABEL_PATTERN2.test(name)) {
|
|
46895
|
-
throw new Error("App name must be one lowercase DNS label.");
|
|
46896
|
-
}
|
|
46897
|
-
return name;
|
|
46898
|
-
}
|
|
46899
|
-
function rawAuthority2(value) {
|
|
46900
|
-
const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
|
|
46901
|
-
if (!match)
|
|
46902
|
-
throw new Error("API URL must be absolute.");
|
|
46903
|
-
const afterScheme = value.slice(match[0].length);
|
|
46904
|
-
const boundary = afterScheme.search(/[/?#]/);
|
|
46905
|
-
const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
|
|
46906
|
-
if (!authority)
|
|
46907
|
-
throw new Error("API URL must include a hostname.");
|
|
46908
|
-
return authority;
|
|
46909
|
-
}
|
|
46910
|
-
function assertCanonicalPort2(port) {
|
|
46911
|
-
if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
|
|
46912
|
-
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
|
|
46913
|
-
}
|
|
46914
|
-
const numericPort = Number(port);
|
|
46915
|
-
if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
|
|
46916
|
-
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
|
|
46917
|
-
}
|
|
46918
|
-
}
|
|
46919
|
-
function canonicalAuthorityHostname2(authority) {
|
|
46920
|
-
let rawHostname;
|
|
46921
|
-
if (authority.startsWith("[")) {
|
|
46922
|
-
const closingBracket = authority.indexOf("]");
|
|
46923
|
-
if (closingBracket === -1) {
|
|
46924
|
-
throw new Error("API URL authority must contain a canonical hostname.");
|
|
46925
|
-
}
|
|
46926
|
-
rawHostname = authority.slice(0, closingBracket + 1);
|
|
46927
|
-
const portSuffix = authority.slice(closingBracket + 1);
|
|
46928
|
-
if (portSuffix) {
|
|
46929
|
-
if (!portSuffix.startsWith(":")) {
|
|
46930
|
-
throw new Error("API URL authority must contain a canonical hostname and port.");
|
|
46931
|
-
}
|
|
46932
|
-
assertCanonicalPort2(portSuffix.slice(1));
|
|
46933
|
-
}
|
|
46934
|
-
if (isIP2(rawHostname.slice(1, -1)) !== 6) {
|
|
46935
|
-
throw new Error("API URL authority must contain a canonical IPv6 literal.");
|
|
46936
|
-
}
|
|
46937
|
-
} else {
|
|
46938
|
-
const firstColon = authority.indexOf(":");
|
|
46939
|
-
const lastColon = authority.lastIndexOf(":");
|
|
46940
|
-
if (firstColon !== lastColon) {
|
|
46941
|
-
throw new Error("IPv6 API URL authorities must use brackets.");
|
|
46942
|
-
}
|
|
46943
|
-
if (lastColon !== -1) {
|
|
46944
|
-
const port = authority.slice(lastColon + 1);
|
|
46945
|
-
assertCanonicalPort2(port);
|
|
46946
|
-
rawHostname = authority.slice(0, lastColon);
|
|
46947
|
-
} else {
|
|
46948
|
-
rawHostname = authority;
|
|
46949
|
-
}
|
|
46950
|
-
const ipVersion = isIP2(rawHostname);
|
|
46951
|
-
const numericAddressParts = rawHostname.split(".");
|
|
46952
|
-
const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
|
|
46953
|
-
if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain2(rawHostname.toLowerCase())) {
|
|
46954
|
-
throw new Error("API URL authority must contain a canonical ASCII hostname.");
|
|
46955
|
-
}
|
|
46956
|
-
}
|
|
46957
|
-
return rawHostname.toLowerCase();
|
|
46958
|
-
}
|
|
46959
|
-
function isDeliberateLoopbackHttpAuthority2(authority) {
|
|
46960
|
-
return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
|
|
46961
|
-
}
|
|
46962
|
-
function toV1BaseUrl2(apiUrl) {
|
|
46963
|
-
if (ASCII_CONTROL_PATTERN2.test(apiUrl)) {
|
|
46964
|
-
throw new Error("API URL must not contain ASCII control characters.");
|
|
46965
|
-
}
|
|
46966
|
-
const input2 = apiUrl.trim();
|
|
46967
|
-
const authority = rawAuthority2(input2);
|
|
46968
|
-
if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
|
|
46969
|
-
throw new Error("API URL authority must be canonical ASCII without credentials.");
|
|
46970
|
-
}
|
|
46971
|
-
const canonicalHostname = canonicalAuthorityHostname2(authority);
|
|
46972
|
-
const url2 = new URL(input2);
|
|
46973
|
-
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
46974
|
-
throw new Error("API URL must use http or https.");
|
|
46975
|
-
}
|
|
46976
|
-
if (url2.username || url2.password) {
|
|
46977
|
-
throw new Error("API URL must not include credentials.");
|
|
46978
|
-
}
|
|
46979
|
-
if (!url2.hostname || url2.hostname.endsWith(".")) {
|
|
46980
|
-
throw new Error("API URL must include a canonical hostname.");
|
|
46981
|
-
}
|
|
46982
|
-
if (url2.hostname.toLowerCase() !== canonicalHostname) {
|
|
46983
|
-
throw new Error("API URL authority must not rely on parser hostname normalization.");
|
|
46984
|
-
}
|
|
46985
|
-
if (url2.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
|
|
46986
|
-
throw new Error("API URL must not use IDN or punycode hostnames.");
|
|
46987
|
-
}
|
|
46988
|
-
if (url2.protocol === "http:" && !isDeliberateLoopbackHttpAuthority2(authority)) {
|
|
46989
|
-
throw new Error("API URL may use http only for an exact loopback authority.");
|
|
46990
|
-
}
|
|
46991
|
-
if (url2.search || url2.hash) {
|
|
46992
|
-
throw new Error("API URL must not include a query string or fragment.");
|
|
46993
|
-
}
|
|
46994
|
-
let path = url2.pathname.replace(/\/+$/, "");
|
|
46995
|
-
if (path.endsWith("/v1"))
|
|
46996
|
-
path = path.slice(0, -"/v1".length);
|
|
46997
|
-
url2.pathname = `${path}/v1`;
|
|
46998
|
-
return url2.toString().replace(/\/+$/, "");
|
|
46999
|
-
}
|
|
47000
|
-
function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
|
|
47001
|
-
env = snapshotClientEnvironment2(name, env);
|
|
47002
|
-
const keys2 = clientTransportEnvKeys2(name);
|
|
47003
|
-
const definedUrlEntries = keys2.apiUrlKeys.filter((key2) => Object.prototype.hasOwnProperty.call(env, key2) && env[key2] !== undefined).map((key2) => ({ key: key2, raw: String(env[key2]) }));
|
|
47004
|
-
const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
|
|
47005
|
-
if (blankUrl) {
|
|
47006
|
-
throw new ClientTransportConfigurationError2(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
|
|
47007
|
-
}
|
|
47008
|
-
const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN2.test(entry.raw));
|
|
47009
|
-
if (controlledUrl) {
|
|
47010
|
-
throw new ClientTransportConfigurationError2(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
|
|
47011
|
-
}
|
|
47012
|
-
const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
|
|
47013
|
-
if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
|
|
47014
|
-
throw new ClientTransportConfigurationError2(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must be identical or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
|
|
47015
|
-
}
|
|
47016
|
-
const envUrlHit = usableUrlEntries[0] ?? null;
|
|
47017
|
-
const keychainUrlHit = keychainConfigValue2(name, env, options.credentials?.keychain);
|
|
47018
|
-
const diskConfigUrlHit = appConfigDiskValue2(name, env, keys2.apiUrlKeys);
|
|
47019
|
-
if (diskConfigUrlHit?.unusable) {
|
|
47020
|
-
throw new ClientTransportConfigurationError2(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
|
|
47021
|
-
}
|
|
47022
|
-
const urlCandidates = [
|
|
47023
|
-
...envUrlHit ? [envUrlHit] : [],
|
|
47024
|
-
...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
|
|
47025
|
-
...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
|
|
47026
|
-
];
|
|
47027
|
-
const configuredUrl = urlCandidates[0] ?? null;
|
|
47028
|
-
const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
|
|
47029
|
-
if (configuredUrl && divergentUrls.length > 0) {
|
|
47030
|
-
throw new ClientTransportConfigurationError2(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
|
|
47031
|
-
}
|
|
47032
|
-
const warnings = [];
|
|
47033
|
-
if (configuredUrl && !envUrlHit) {
|
|
47034
|
-
warnings.push(`No ${keys2.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
|
|
47035
|
-
}
|
|
47036
|
-
const credential = resolveCredential2(name, env, options.credentials);
|
|
47037
|
-
if (!credential) {
|
|
47038
|
-
const diskHint = credentialDiskSourcesForMessage(name, env);
|
|
47039
|
-
const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys2.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
|
|
47040
|
-
warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys2.apiKeyKeys[0]} in the environment.`);
|
|
47041
|
-
throw new ClientTransportConfigurationError2(name, warnings.join(" "), [configuredUrl?.key ?? keys2.apiUrlKeys[0]]);
|
|
47042
|
-
}
|
|
47043
|
-
if (credential.warning)
|
|
47044
|
-
warnings.push(credential.warning);
|
|
47045
|
-
let urlHit;
|
|
47046
|
-
if (configuredUrl) {
|
|
47047
|
-
urlHit = configuredUrl;
|
|
47048
|
-
} else {
|
|
47049
|
-
try {
|
|
47050
|
-
urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl2(name) };
|
|
47051
|
-
} catch (error61) {
|
|
47052
|
-
const message = error61 instanceof Error ? error61.message : String(error61);
|
|
47053
|
-
throw new ClientTransportConfigurationError2(name, `No ${keys2.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys2.apiUrlKeys[0]]);
|
|
47054
|
-
}
|
|
47055
|
-
}
|
|
47056
|
-
const apiUrlSource = urlHit.key;
|
|
47057
|
-
let baseUrl;
|
|
47058
|
-
try {
|
|
47059
|
-
baseUrl = toV1BaseUrl2(urlHit.value);
|
|
47060
|
-
} catch (error61) {
|
|
47061
|
-
const message = error61 instanceof Error ? error61.message : String(error61);
|
|
47062
|
-
throw new ClientTransportConfigurationError2(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
|
|
47063
|
-
}
|
|
47064
|
-
return {
|
|
47065
|
-
resolution: {
|
|
47066
|
-
transport: "http",
|
|
47067
|
-
transportSource: urlHit.key,
|
|
47068
|
-
baseUrl,
|
|
47069
|
-
apiUrlSource,
|
|
47070
|
-
apiKeyPresent: true,
|
|
47071
|
-
apiKeySource: credential.source,
|
|
47072
|
-
apiKeyTier: credential.tier,
|
|
47073
|
-
misconfigured: false,
|
|
47074
|
-
warning: warnings.length > 0 ? warnings.join(" ") : null
|
|
47075
|
-
},
|
|
47076
|
-
credential
|
|
47077
|
-
};
|
|
47078
|
-
}
|
|
47079
|
-
function resolveClientTransport(name, env = process.env, options = {}) {
|
|
47080
|
-
return resolveClientTransportSnapshot(name, env, options).resolution;
|
|
47081
|
-
}
|
|
47082
|
-
function credentialDiskSourcesForMessage(name, env) {
|
|
47083
|
-
const paths = credentialDiskSources2(name, env);
|
|
47084
|
-
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
|
|
47085
|
-
}
|
|
47086
|
-
function currentCredential(name, apiKey) {
|
|
47087
|
-
if (typeof apiKey === "function") {
|
|
47088
|
-
return validateAndSealResolvedCredential(name, apiKey());
|
|
47089
|
-
}
|
|
47090
|
-
return explicitCredential(name, apiKey);
|
|
47091
|
-
}
|
|
47092
|
-
async function resolveRequestCredential(name, apiKey, env = process.env) {
|
|
47093
|
-
const resolved = currentCredential(name, apiKey);
|
|
47094
|
-
if (resolved.tier === "pointer") {
|
|
47095
|
-
return completePointerCredential2(name, resolved, env);
|
|
47096
|
-
}
|
|
47097
|
-
return resolved;
|
|
47098
|
-
}
|
|
47099
|
-
function authFailureGuidance(credential) {
|
|
47100
|
-
const origin = `The API key for this request came from ${credential.source}`;
|
|
47101
|
-
if (credential.deliberate) {
|
|
47102
|
-
const remedy = credential.source === CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE ? `Fix that provider so it returns the current key, or replace it with resolveCredential() ` + `so diagnostics can name the original source.` : `Rotate that key, or unset the override to use the credential on disk.`;
|
|
47103
|
-
return `${origin} \u2014 a credential you selected deliberately. It was NOT substituted with any other key: ` + `falling back here would authenticate as a different principal than the one you named, which is ` + `exactly the failure an override exists to prevent. ${remedy}`;
|
|
47104
|
-
}
|
|
47105
|
-
if (credential.tier === "env") {
|
|
47106
|
-
const target = credential.diskCandidates[0];
|
|
47107
|
-
const remedy = target ? `Store the CURRENT key in the Keychain or write it to ${target} \u2014 both are re-read on every call, so ` + `rotations take effect immediately and in every shell. Do not simply unset ${credential.source}: ` + `nothing was found in the Keychain or on disk, so that would leave this client with no credential at all.` : `This environment has no HOME or HASNA_HOME, so no credential file could be consulted; the disk tier is ` + `unavailable here and there is nothing to fall back to. Set HOME, or supply the key explicitly.`;
|
|
47108
|
-
return `${origin}, a variable in this process's environment. If a wrapper injected it for this one process, the ` + `wrapper re-reads its store on every invocation and the stored key itself is being rejected \u2014 rotate it. ` + `If this SHELL exported it, the export is a snapshot taken when the shell started: a STALE SHELL that ` + `exported the key before it was rotated keeps sending the old one until it exits. ${remedy}`;
|
|
47109
|
-
}
|
|
47110
|
-
if (credential.tier === "keychain") {
|
|
47111
|
-
return `${origin}, which was re-read from the Keychain on this very call \u2014 so a stale shell is NOT the cause ` + `here. The stored item is genuinely being rejected: update it with the current key, or re-run the fleet ` + `key distribution so this machine gets the current key.`;
|
|
47112
|
-
}
|
|
47113
|
-
return `${origin}, which was re-read from disk on this very call \u2014 so a stale shell is NOT the cause here. ` + `The stored credential is genuinely being rejected: rotate it, or re-run the fleet key distribution ` + `so this machine gets the current key.`;
|
|
47114
|
-
}
|
|
47115
|
-
function assertNoAuthorityOverrideHeaders(headers, source) {
|
|
47116
|
-
if (!headers)
|
|
47117
|
-
return;
|
|
47118
|
-
const forbidden = Object.keys(headers).find((name) => AUTHORITY_OVERRIDE_HEADERS2.has(name.trim().toLowerCase()));
|
|
47119
|
-
if (forbidden) {
|
|
47120
|
-
throw new Error(`Authenticated ${source} headers must not set authority header '${forbidden}'.`);
|
|
47121
|
-
}
|
|
47122
|
-
}
|
|
47123
|
-
function appendQuery(path, query) {
|
|
47124
|
-
if (!query)
|
|
47125
|
-
return path;
|
|
47126
|
-
const params = query instanceof URLSearchParams ? query : new URLSearchParams;
|
|
47127
|
-
if (!(query instanceof URLSearchParams)) {
|
|
47128
|
-
for (const [key2, value] of Object.entries(query)) {
|
|
47129
|
-
if (value === null || value === undefined)
|
|
47130
|
-
continue;
|
|
47131
|
-
if (Array.isArray(value)) {
|
|
47132
|
-
for (const v of value)
|
|
47133
|
-
params.append(key2, String(v));
|
|
47134
|
-
} else {
|
|
47135
|
-
params.append(key2, String(value));
|
|
47136
|
-
}
|
|
47137
|
-
}
|
|
47138
|
-
}
|
|
47139
|
-
const qs = params.toString();
|
|
47140
|
-
if (!qs)
|
|
47141
|
-
return path;
|
|
47142
|
-
return `${path}${path.includes("?") ? "&" : "?"}${qs}`;
|
|
47143
|
-
}
|
|
47144
|
-
function createHasnaHttpTransportInternal(options, requestBindingProvider) {
|
|
47145
|
-
const fetchImpl = options.fetchImpl ?? ((input2, init) => fetch(input2, init));
|
|
47146
|
-
const base2 = toV1BaseUrl2(options.baseUrl);
|
|
47147
|
-
const timeoutMs = options.timeoutMs ?? 30000;
|
|
47148
|
-
const sleep = options.sleepImpl ?? defaultSleep;
|
|
47149
|
-
const defaultRetry = options.retry;
|
|
47150
|
-
function resolveRetry(callRetry) {
|
|
47151
|
-
const chosen = callRetry !== undefined ? callRetry : defaultRetry;
|
|
47152
|
-
if (chosen === false)
|
|
47153
|
-
return null;
|
|
47154
|
-
const r2 = chosen ?? {};
|
|
47155
|
-
return {
|
|
47156
|
-
retries: r2.retries ?? 2,
|
|
47157
|
-
baseDelayMs: r2.baseDelayMs ?? 200,
|
|
47158
|
-
maxDelayMs: r2.maxDelayMs ?? 2000,
|
|
47159
|
-
retryStatuses: r2.retryStatuses ?? [...DEFAULT_RETRY_STATUSES]
|
|
47160
|
-
};
|
|
47161
|
-
}
|
|
47162
|
-
async function once(method, rel, url2, body, opts, credential) {
|
|
47163
|
-
assertNoAuthorityOverrideHeaders(options.headers, "transport");
|
|
47164
|
-
assertNoAuthorityOverrideHeaders(opts.headers, "request");
|
|
47165
|
-
const headers = {
|
|
47166
|
-
"x-api-key": credential.apiKey,
|
|
47167
|
-
Authorization: `Bearer ${credential.apiKey}`,
|
|
47168
|
-
Accept: "application/json",
|
|
47169
|
-
...options.headers ?? {},
|
|
47170
|
-
...opts.headers ?? {}
|
|
47171
|
-
};
|
|
47172
|
-
if (opts.idempotencyKey)
|
|
47173
|
-
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
47174
|
-
const init = {
|
|
47175
|
-
method,
|
|
47176
|
-
headers,
|
|
47177
|
-
redirect: "manual"
|
|
47178
|
-
};
|
|
47179
|
-
if (body !== undefined) {
|
|
47180
|
-
headers["Content-Type"] = "application/json";
|
|
47181
|
-
init.body = JSON.stringify(body);
|
|
47182
|
-
}
|
|
47183
|
-
const controller = new AbortController;
|
|
47184
|
-
const onAbort = () => controller.abort();
|
|
47185
|
-
if (opts.signal) {
|
|
47186
|
-
if (opts.signal.aborted)
|
|
47187
|
-
controller.abort();
|
|
47188
|
-
else
|
|
47189
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
47190
|
-
}
|
|
47191
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
|
|
47192
|
-
init.signal = controller.signal;
|
|
47193
|
-
let response;
|
|
47194
|
-
try {
|
|
47195
|
-
response = await fetchImpl(url2, init);
|
|
47196
|
-
} catch (error61) {
|
|
47197
|
-
const err = error61 instanceof Error ? error61 : new Error(String(error61));
|
|
47198
|
-
if (opts.signal?.aborted)
|
|
47199
|
-
return { ok: false, retryable: false, error: err };
|
|
47200
|
-
return { ok: false, retryable: true, error: err };
|
|
47201
|
-
} finally {
|
|
47202
|
-
clearTimeout(timer);
|
|
47203
|
-
if (opts.signal)
|
|
47204
|
-
opts.signal.removeEventListener("abort", onAbort);
|
|
47205
|
-
}
|
|
47206
|
-
const authenticationFailure = response.status === 401 || response.status === 403;
|
|
47207
|
-
let parsed = undefined;
|
|
47208
|
-
if (authenticationFailure) {
|
|
47209
|
-
try {
|
|
47210
|
-
await response.body?.cancel();
|
|
47211
|
-
} catch {}
|
|
47212
|
-
} else {
|
|
47213
|
-
const text4 = await response.text();
|
|
47214
|
-
if (text4.length > 0) {
|
|
47215
|
-
try {
|
|
47216
|
-
parsed = JSON.parse(text4);
|
|
47217
|
-
} catch {
|
|
47218
|
-
parsed = text4;
|
|
47219
|
-
}
|
|
47220
|
-
}
|
|
47221
|
-
}
|
|
47222
|
-
if (!response.ok) {
|
|
47223
|
-
if (response.status >= 300 && response.status < 400) {
|
|
47224
|
-
return {
|
|
47225
|
-
ok: false,
|
|
47226
|
-
retryable: false,
|
|
47227
|
-
error: new HasnaHttpError(method, rel, response.status, parsed)
|
|
47228
|
-
};
|
|
47229
|
-
}
|
|
47230
|
-
if (authenticationFailure) {
|
|
47231
|
-
return {
|
|
47232
|
-
ok: false,
|
|
47233
|
-
retryable: false,
|
|
47234
|
-
error: new HasnaHttpError(method, rel, response.status, undefined, {
|
|
47235
|
-
source: credential.source,
|
|
47236
|
-
tier: credential.tier,
|
|
47237
|
-
guidance: authFailureGuidance(credential)
|
|
47238
|
-
})
|
|
47239
|
-
};
|
|
47240
|
-
}
|
|
47241
|
-
const retry = resolveRetry(opts.retry);
|
|
47242
|
-
const retryable = retry ? retry.retryStatuses.includes(response.status) : false;
|
|
47243
|
-
return { ok: false, retryable, error: new HasnaHttpError(method, rel, response.status, parsed) };
|
|
47244
|
-
}
|
|
47245
|
-
return { ok: true, value: parsed };
|
|
47246
|
-
}
|
|
47247
|
-
async function request(method, path, body, opts = {}) {
|
|
47248
|
-
const upper = method.toUpperCase();
|
|
47249
|
-
const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
|
|
47250
|
-
const retry = resolveRetry(opts.retry);
|
|
47251
|
-
const methodRetryable = IDEMPOTENT_METHODS2.has(upper) || Boolean(opts.idempotencyKey);
|
|
47252
|
-
const maxAttempts = retry && methodRetryable ? retry.retries + 1 : 1;
|
|
47253
|
-
const binding = requestBindingProvider ? await requestBindingProvider() : {
|
|
47254
|
-
baseUrl: base2,
|
|
47255
|
-
credential: await resolveRequestCredential(options.name, options.apiKey)
|
|
47256
|
-
};
|
|
47257
|
-
const url2 = `${binding.baseUrl}${rel}`;
|
|
47258
|
-
const credential = binding.credential;
|
|
47259
|
-
let last = null;
|
|
47260
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
47261
|
-
const result = await once(upper, rel, url2, body, opts, credential);
|
|
47262
|
-
if (result.ok)
|
|
47263
|
-
return result.value;
|
|
47264
|
-
last = result;
|
|
47265
|
-
const canRetry = retry !== null && methodRetryable && result.retryable && attempt < maxAttempts;
|
|
47266
|
-
if (!canRetry)
|
|
47267
|
-
break;
|
|
47268
|
-
const backoff = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** (attempt - 1));
|
|
47269
|
-
const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
|
|
47270
|
-
await sleep(backoff + jitter);
|
|
47271
|
-
}
|
|
47272
|
-
throw last.error;
|
|
47273
|
-
}
|
|
47274
|
-
return {
|
|
47275
|
-
baseUrl: base2,
|
|
47276
|
-
request,
|
|
47277
|
-
get: (path, opts) => request("GET", path, undefined, opts),
|
|
47278
|
-
post: (path, body, opts) => request("POST", path, body, opts),
|
|
47279
|
-
put: (path, body, opts) => request("PUT", path, body, opts),
|
|
47280
|
-
patch: (path, body, opts) => request("PATCH", path, body, opts),
|
|
47281
|
-
del: (path, body, opts) => request("DELETE", path, body, opts)
|
|
47282
|
-
};
|
|
47283
|
-
}
|
|
47284
|
-
function createHasnaHttpTransport(options) {
|
|
47285
|
-
return createHasnaHttpTransportInternal(options);
|
|
47286
|
-
}
|
|
47287
|
-
|
|
47288
|
-
class SecretsClient {
|
|
47289
|
-
transport;
|
|
47290
|
-
probeRoot;
|
|
47291
|
-
fetchImpl;
|
|
47292
|
-
baseHeaders;
|
|
47293
|
-
constructor(options) {
|
|
47294
|
-
if (!options.baseUrl)
|
|
47295
|
-
throw new Error("SecretsClient requires a baseUrl.");
|
|
47296
|
-
this.transport = createHasnaHttpTransport({
|
|
47297
|
-
name: "secrets",
|
|
47298
|
-
baseUrl: options.baseUrl.replace(/\/+$/, ""),
|
|
47299
|
-
apiKey: options.apiKey ?? "",
|
|
47300
|
-
...options.fetch ? { fetchImpl: (input2, init) => options.fetch(input2, init) } : {},
|
|
47301
|
-
...options.headers ? { headers: options.headers } : {}
|
|
47302
|
-
});
|
|
47303
|
-
this.probeRoot = this.transport.baseUrl.replace(/\/v1$/, "");
|
|
47304
|
-
this.fetchImpl = options.fetch ? (input2, init) => options.fetch(input2, init) : (input2, init) => fetch(input2, init);
|
|
47305
|
-
this.baseHeaders = options.headers ?? {};
|
|
47306
|
-
}
|
|
47307
|
-
get baseUrl() {
|
|
47308
|
-
return this.transport.baseUrl;
|
|
47309
|
-
}
|
|
47310
|
-
async probe(path, init) {
|
|
47311
|
-
const headers = { Accept: "application/json", ...this.baseHeaders };
|
|
47312
|
-
new Headers(init?.headers ?? {}).forEach((value, key2) => {
|
|
47313
|
-
headers[key2] = value;
|
|
47314
|
-
});
|
|
47315
|
-
const response = await this.fetchImpl(`${this.probeRoot}${path}`, {
|
|
47316
|
-
...init,
|
|
47317
|
-
method: "GET",
|
|
47318
|
-
headers,
|
|
47319
|
-
redirect: "manual"
|
|
47320
|
-
});
|
|
47321
|
-
const text4 = await response.text();
|
|
47322
|
-
let body = undefined;
|
|
47323
|
-
if (text4) {
|
|
47324
|
-
try {
|
|
47325
|
-
body = JSON.parse(text4);
|
|
47326
|
-
} catch {
|
|
47327
|
-
body = text4;
|
|
47328
|
-
}
|
|
47329
|
-
}
|
|
47330
|
-
if (!response.ok) {
|
|
47331
|
-
throw new ApiError(response.status, `GET ${path} failed: ${response.status}`, body);
|
|
47332
|
-
}
|
|
47333
|
-
return body;
|
|
47334
|
-
}
|
|
47335
|
-
async request(method, path, opts) {
|
|
47336
|
-
const requestOpts = {};
|
|
47337
|
-
if (opts.query)
|
|
47338
|
-
requestOpts.query = opts.query;
|
|
47339
|
-
const initHeaders = opts.init?.headers;
|
|
47340
|
-
if (initHeaders)
|
|
47341
|
-
requestOpts.headers = initHeaders;
|
|
47342
|
-
if (opts.init?.signal)
|
|
47343
|
-
requestOpts.signal = opts.init.signal;
|
|
47344
|
-
try {
|
|
47345
|
-
return await this.transport.request(method, path, opts.body, requestOpts);
|
|
47346
|
-
} catch (error61) {
|
|
47347
|
-
if (error61 instanceof HasnaHttpError) {
|
|
47348
|
-
throw new ApiError(error61.status, `${method} ${path} failed: ${error61.status}`, error61.body);
|
|
47349
|
-
}
|
|
47350
|
-
throw error61;
|
|
47351
|
-
}
|
|
47352
|
-
}
|
|
47353
|
-
async health(init) {
|
|
47354
|
-
return this.probe(`/health`, init);
|
|
47355
|
-
}
|
|
47356
|
-
async ready(init) {
|
|
47357
|
-
return this.probe(`/ready`, init);
|
|
47358
|
-
}
|
|
47359
|
-
async listAudit(query, init) {
|
|
47360
|
-
return this.request("GET", `/audit`, {
|
|
47361
|
-
body: undefined,
|
|
47362
|
-
query,
|
|
47363
|
-
init
|
|
47364
|
-
});
|
|
47365
|
-
}
|
|
47366
|
-
async listItems(query, init) {
|
|
47367
|
-
return this.request("GET", `/items`, {
|
|
47368
|
-
body: undefined,
|
|
47369
|
-
query,
|
|
47370
|
-
init
|
|
47371
|
-
});
|
|
47372
|
-
}
|
|
47373
|
-
async putItem(body, init) {
|
|
47374
|
-
return this.request("POST", `/items`, {
|
|
47375
|
-
body,
|
|
47376
|
-
query: undefined,
|
|
47377
|
-
init
|
|
47378
|
-
});
|
|
47379
|
-
}
|
|
47380
|
-
async searchItems(query, init) {
|
|
47381
|
-
return this.request("GET", `/items/search`, {
|
|
47382
|
-
body: undefined,
|
|
47383
|
-
query,
|
|
47384
|
-
init
|
|
47385
|
-
});
|
|
47386
|
-
}
|
|
47387
|
-
async getItem(id, init) {
|
|
47388
|
-
return this.request("GET", `/items/${encodeURIComponent(String(id))}`, {
|
|
47389
|
-
body: undefined,
|
|
47390
|
-
query: undefined,
|
|
47391
|
-
init
|
|
47392
|
-
});
|
|
47393
|
-
}
|
|
47394
|
-
async deleteItem(id, init) {
|
|
47395
|
-
return this.request("DELETE", `/items/${encodeURIComponent(String(id))}`, {
|
|
47396
|
-
body: undefined,
|
|
47397
|
-
query: undefined,
|
|
47398
|
-
init
|
|
47399
|
-
});
|
|
47400
|
-
}
|
|
47401
|
-
async listSecrets(query, init) {
|
|
47402
|
-
return this.request("GET", `/secrets`, {
|
|
47403
|
-
body: undefined,
|
|
47404
|
-
query,
|
|
47405
|
-
init
|
|
47406
|
-
});
|
|
47407
|
-
}
|
|
47408
|
-
async putSecret(body, init) {
|
|
47409
|
-
return this.request("POST", `/secrets`, {
|
|
47410
|
-
body,
|
|
47411
|
-
query: undefined,
|
|
47412
|
-
init
|
|
47413
|
-
});
|
|
47414
|
-
}
|
|
47415
|
-
async deleteSecret(query, init) {
|
|
47416
|
-
return this.request("DELETE", `/secrets`, {
|
|
47417
|
-
body: undefined,
|
|
47418
|
-
query,
|
|
47419
|
-
init
|
|
47420
|
-
});
|
|
47421
|
-
}
|
|
47422
|
-
async getSecret(query, init) {
|
|
47423
|
-
return this.request("GET", `/secrets/get`, {
|
|
47424
|
-
body: undefined,
|
|
47425
|
-
query,
|
|
47426
|
-
init
|
|
47427
|
-
});
|
|
47428
|
-
}
|
|
47429
|
-
async searchSecrets(query, init) {
|
|
47430
|
-
return this.request("GET", `/secrets/search`, {
|
|
47431
|
-
body: undefined,
|
|
47432
|
-
query,
|
|
47433
|
-
init
|
|
47434
|
-
});
|
|
47435
|
-
}
|
|
47436
|
-
async listSecretVersions(query, init) {
|
|
47437
|
-
return this.request("GET", `/secrets/versions`, {
|
|
47438
|
-
body: undefined,
|
|
47439
|
-
query,
|
|
47440
|
-
init
|
|
47441
|
-
});
|
|
47442
|
-
}
|
|
47443
|
-
async checkSecretVersion(query, init) {
|
|
47444
|
-
return this.request("GET", `/secrets/versions/check`, {
|
|
47445
|
-
body: undefined,
|
|
47446
|
-
query,
|
|
47447
|
-
init
|
|
47448
|
-
});
|
|
47449
|
-
}
|
|
47450
|
-
async restoreSecretVersion(body, init) {
|
|
47451
|
-
return this.request("POST", `/secrets/restore`, {
|
|
47452
|
-
body,
|
|
47453
|
-
query: undefined,
|
|
47454
|
-
init
|
|
47455
|
-
});
|
|
47456
|
-
}
|
|
47457
|
-
async listUsers(query, init) {
|
|
47458
|
-
return this.request("GET", `/users`, {
|
|
47459
|
-
body: undefined,
|
|
47460
|
-
query,
|
|
47461
|
-
init
|
|
47462
|
-
});
|
|
47463
|
-
}
|
|
47464
|
-
async registerUser(body, init) {
|
|
47465
|
-
return this.request("POST", `/users`, {
|
|
47466
|
-
body,
|
|
47467
|
-
query: undefined,
|
|
47468
|
-
init
|
|
47469
|
-
});
|
|
47470
|
-
}
|
|
47471
|
-
async version(init) {
|
|
47472
|
-
return this.probe(`/version`, init);
|
|
47473
|
-
}
|
|
47474
|
-
}
|
|
47475
|
-
function createSecretsClientFromEnv(env = process.env, overrides = {}) {
|
|
47476
|
-
const { credentials, ...clientOverrides } = overrides;
|
|
47477
|
-
if (clientOverrides.baseUrl !== undefined) {
|
|
47478
|
-
const pinnedKey = clientOverrides.apiKey ?? credentials?.apiKey;
|
|
47479
|
-
if (pinnedKey === undefined || pinnedKey === "") {
|
|
47480
|
-
throw new Error("createSecretsClientFromEnv: an explicit baseUrl requires an explicit apiKey; the ambient " + "credential (Keychain hasna.credentials.secrets.api-key, ~/.hasna/secrets/config/credentials, " + "HASNA_SECRETS_API_KEY) is never sent to a caller-supplied authority (hasna/apps#1794).");
|
|
47481
|
-
}
|
|
47482
|
-
return new SecretsClient({ ...clientOverrides, baseUrl: clientOverrides.baseUrl, apiKey: pinnedKey });
|
|
47483
|
-
}
|
|
47484
|
-
const clientEnv = env;
|
|
47485
|
-
const resolverOptions = credentials ? { credentials } : {};
|
|
47486
|
-
const resolution = resolveClientTransport("secrets", clientEnv, resolverOptions);
|
|
47487
|
-
const baseUrl = resolution.baseUrl;
|
|
47488
|
-
const apiKey = clientOverrides.apiKey ?? (() => {
|
|
47489
|
-
const resolved = resolveCredential2("secrets", clientEnv, credentials);
|
|
47490
|
-
if (!resolved) {
|
|
47491
|
-
throw new Error("createSecretsClientFromEnv: no @hasna/secrets credential resolved (Keychain, " + "~/.hasna/secrets/config/credentials, HASNA_SECRETS_API_KEY).");
|
|
47492
|
-
}
|
|
47493
|
-
return resolved;
|
|
47494
|
-
});
|
|
47495
|
-
return new SecretsClient({ ...clientOverrides, baseUrl, apiKey });
|
|
47496
|
-
}
|
|
47497
|
-
var __esm2 = (fn, res) => () => (fn && (res = fn(fn = 0)), res), CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE", CredentialResolutionError2, CredentialFileUnsafeError2, HASNA_HOME_ENV_KEY2 = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY2 = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY2 = "HASNA_STATION", HASNA_HOME_DIR2 = ".hasna", CONFIG_SUBDIR2 = "config", CREDENTIALS_FILE2 = "credentials", KEYCHAIN_SECURITY_BIN2 = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX2 = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS2 = 44, KEYCHAIN_SPAWN_TIMEOUT_MS2 = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG2, SAFE_PROFILE2, ILLEGAL_IN_HEADER_VALUE2, VAULT_POINTER_SHAPE2, CREDENTIAL_SHAPED_KEY2, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider", AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN2 = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN2, DNS_LABEL_PATTERN2, ClientTransportConfigurationError2, HasnaHttpError, DEFAULT_RETRY_STATUSES, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, defaultSleep = (ms) => new Promise((resolve27) => setTimeout(resolve27, ms)), init_transport2, MAX_CREDENTIAL_FILE_BYTES22, INSPECT_CUSTOM22, CREDENTIAL_SEAL22, AMBIENT_ENVIRONMENT22, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk22, IDEMPOTENT_METHODS22, AUTHORITY_OVERRIDE_HEADERS22, init_storage, PATHS_RESOLVER_APP_SLUG_RE2, SQLITE_SIDECAR_FILES, LEGACY_SERVICE_FILES, init_data_dir, SecretsTestIsolationError, TEST_ENTRYPOINT_RE, init_test_isolation, init_client, ApiError;
|
|
47498
|
-
var init_sdk = __esm(() => {
|
|
47499
|
-
init_transport2 = __esm2(() => {
|
|
47500
|
-
CredentialResolutionError2 = class CredentialResolutionError3 extends Error {
|
|
47501
|
-
appName;
|
|
47502
|
-
attempted;
|
|
47503
|
-
constructor(appName, message, attempted) {
|
|
47504
|
-
super(message);
|
|
47505
|
-
this.name = "CredentialResolutionError";
|
|
47506
|
-
this.appName = appName;
|
|
47507
|
-
this.attempted = attempted;
|
|
47508
|
-
}
|
|
47509
|
-
};
|
|
47510
|
-
CredentialFileUnsafeError2 = class CredentialFileUnsafeError3 extends Error {
|
|
47511
|
-
path;
|
|
47512
|
-
constructor(path, reason2) {
|
|
47513
|
-
super(`Refusing unsafe credential/config file ${path}: ${reason2}.`);
|
|
47514
|
-
this.name = "CredentialFileUnsafeError";
|
|
47515
|
-
this.path = path;
|
|
47516
|
-
}
|
|
47517
|
-
};
|
|
47518
|
-
MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
|
|
47519
|
-
SAFE_APP_SLUG2 = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
47520
|
-
SAFE_PROFILE2 = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
47521
|
-
ILLEGAL_IN_HEADER_VALUE2 = /[^\t\x20-\x7e]/;
|
|
47522
|
-
VAULT_POINTER_SHAPE2 = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
|
|
47523
|
-
CREDENTIAL_SHAPED_KEY2 = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
|
|
47524
|
-
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
47525
|
-
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
47526
|
-
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
47527
|
-
SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
47528
|
-
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
47529
|
-
ASCII_CONTROL_PATTERN2 = /[\u0000-\u001f\u007f]/;
|
|
47530
|
-
DNS_LABEL_PATTERN2 = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
47531
|
-
ClientTransportConfigurationError2 = class ClientTransportConfigurationError3 extends Error {
|
|
47532
|
-
appName;
|
|
47533
|
-
sources;
|
|
47534
|
-
constructor(appName, message, sources = []) {
|
|
47535
|
-
super(message);
|
|
47536
|
-
this.name = "ClientTransportConfigurationError";
|
|
47537
|
-
this.appName = appName;
|
|
47538
|
-
this.sources = Object.freeze([...sources]);
|
|
47539
|
-
}
|
|
47540
|
-
};
|
|
47541
|
-
HasnaHttpError = class HasnaHttpError2 extends Error {
|
|
47542
|
-
status;
|
|
47543
|
-
method;
|
|
47544
|
-
path;
|
|
47545
|
-
credentialSource;
|
|
47546
|
-
credentialTier;
|
|
47547
|
-
constructor(method, path, status, body, credential) {
|
|
47548
|
-
const guidance = credential ? `. ${credential.guidance}` : "";
|
|
47549
|
-
super(`Hasna cloud request failed: ${method} ${path} -> ${status}${guidance}`);
|
|
47550
|
-
this.name = "HasnaHttpError";
|
|
47551
|
-
this.status = status;
|
|
47552
|
-
this.method = method;
|
|
47553
|
-
this.path = path;
|
|
47554
|
-
Object.defineProperty(this, "body", {
|
|
47555
|
-
value: body,
|
|
47556
|
-
enumerable: status !== 401 && status !== 403,
|
|
47557
|
-
writable: false,
|
|
47558
|
-
configurable: false
|
|
47559
|
-
});
|
|
47560
|
-
this.credentialSource = credential?.source ?? null;
|
|
47561
|
-
this.credentialTier = credential?.tier ?? null;
|
|
47562
|
-
}
|
|
47563
|
-
};
|
|
47564
|
-
DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
|
|
47565
|
-
IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
47566
|
-
AUTHORITY_OVERRIDE_HEADERS2 = new Set([
|
|
47567
|
-
"host",
|
|
47568
|
-
":authority",
|
|
47569
|
-
"forwarded",
|
|
47570
|
-
"x-forwarded-host",
|
|
47571
|
-
"x-original-host"
|
|
47572
|
-
]);
|
|
47573
|
-
});
|
|
47574
|
-
init_storage = __esm2(() => {
|
|
47575
|
-
MAX_CREDENTIAL_FILE_BYTES22 = 64 * 1024;
|
|
47576
|
-
INSPECT_CUSTOM22 = Symbol.for("nodejs.util.inspect.custom");
|
|
47577
|
-
CREDENTIAL_SEAL22 = Symbol.for("hasna:contracts:sealedCredential");
|
|
47578
|
-
AMBIENT_ENVIRONMENT22 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
47579
|
-
SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
|
|
47580
|
-
requireSecretsSdk22 = createRequire22(import.meta.url);
|
|
47581
|
-
IDEMPOTENT_METHODS22 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
47582
|
-
AUTHORITY_OVERRIDE_HEADERS22 = new Set([
|
|
47583
|
-
"host",
|
|
47584
|
-
":authority",
|
|
47585
|
-
"forwarded",
|
|
47586
|
-
"x-forwarded-host",
|
|
47587
|
-
"x-original-host"
|
|
47588
|
-
]);
|
|
47589
|
-
});
|
|
47590
|
-
init_data_dir = __esm2(() => {
|
|
47591
|
-
PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
47592
|
-
SQLITE_SIDECAR_FILES = ["vault.db-wal", "vault.db-shm", "vault.db-journal"];
|
|
47593
|
-
LEGACY_SERVICE_FILES = [
|
|
47594
|
-
"vault.key",
|
|
47595
|
-
"vault.key.enc",
|
|
47596
|
-
"kms.json",
|
|
47597
|
-
"aws.json",
|
|
47598
|
-
".serve-token"
|
|
47599
|
-
];
|
|
47600
|
-
});
|
|
47601
|
-
init_test_isolation = __esm2(() => {
|
|
47602
|
-
init_data_dir();
|
|
47603
|
-
SecretsTestIsolationError = class SecretsTestIsolationError2 extends Error {
|
|
47604
|
-
constructor(message) {
|
|
47605
|
-
super(message);
|
|
47606
|
-
this.name = "SecretsTestIsolationError";
|
|
47607
|
-
}
|
|
47608
|
-
};
|
|
47609
|
-
TEST_ENTRYPOINT_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
47610
|
-
});
|
|
47611
|
-
init_client = __esm2(() => {
|
|
47612
|
-
init_transport2();
|
|
47613
|
-
init_storage();
|
|
47614
|
-
init_test_isolation();
|
|
47615
|
-
init_transport2();
|
|
47616
|
-
});
|
|
47617
|
-
init_client();
|
|
47618
|
-
ApiError = class ApiError extends Error {
|
|
47619
|
-
status;
|
|
47620
|
-
body;
|
|
47621
|
-
constructor(status, message, body) {
|
|
47622
|
-
super(message);
|
|
47623
|
-
this.status = status;
|
|
47624
|
-
this.body = body;
|
|
47625
|
-
this.name = "ApiError";
|
|
47626
|
-
}
|
|
47627
|
-
};
|
|
47628
|
-
init_client();
|
|
47629
|
-
});
|
|
47630
|
-
|
|
47631
|
-
// src/lib/execution-secrets.ts
|
|
47632
|
-
import { constants as constants20, closeSync as closeSync23, fstatSync as fstatSync23, openSync as openSync23, readSync as readSync15, realpathSync as realpathSync12 } from "fs";
|
|
47633
|
-
import { hostname as stationHostname } from "os";
|
|
47634
|
-
function selectedSecretBindingsTemplate(scope, names, createClient = createSecretsClientFromEnv) {
|
|
46347
|
+
function selectedSecretBindingsTemplate(scope, names, createClient) {
|
|
47635
46348
|
let secretsAuthority;
|
|
47636
46349
|
try {
|
|
47637
46350
|
secretsAuthority = createClient().baseUrl;
|
|
@@ -47701,12 +46414,13 @@ function validateSelectedSecretBindings(value, scope, names) {
|
|
|
47701
46414
|
}
|
|
47702
46415
|
return structuredClone(value);
|
|
47703
46416
|
}
|
|
47704
|
-
async function resolveSelectedSecrets(bindings, createClient
|
|
46417
|
+
async function resolveSelectedSecrets(bindings, createClient) {
|
|
47705
46418
|
if (!Object.keys(bindings.bindings).length)
|
|
47706
46419
|
return {};
|
|
46420
|
+
const factory = createClient ?? await defaultSecretsClientFactory();
|
|
47707
46421
|
let client;
|
|
47708
46422
|
try {
|
|
47709
|
-
client =
|
|
46423
|
+
client = factory();
|
|
47710
46424
|
} catch {
|
|
47711
46425
|
return refuse5("LOCAL_SECRET_UNAVAILABLE", "The configured Secrets client could not authenticate. No ambient credential fallback was attempted.");
|
|
47712
46426
|
}
|
|
@@ -47739,7 +46453,6 @@ function redactExecutionSecrets(text4, env) {
|
|
|
47739
46453
|
return text4;
|
|
47740
46454
|
}
|
|
47741
46455
|
var init_execution_secrets = __esm(() => {
|
|
47742
|
-
init_sdk();
|
|
47743
46456
|
init_selection_cache();
|
|
47744
46457
|
});
|
|
47745
46458
|
|
|
@@ -47878,7 +46591,7 @@ __export(exports_selected_run, {
|
|
|
47878
46591
|
import { createHash as createHash31 } from "crypto";
|
|
47879
46592
|
import { spawn as spawn2 } from "child_process";
|
|
47880
46593
|
import { mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync12, realpathSync as realpathSync13 } from "fs";
|
|
47881
|
-
import { dirname as dirname30, join as
|
|
46594
|
+
import { dirname as dirname30, join as join40, resolve as resolve27 } from "path";
|
|
47882
46595
|
import { tmpdir, hostname as stationHostname2 } from "os";
|
|
47883
46596
|
async function resolveSelectedRun(spec, profileId, options = {}) {
|
|
47884
46597
|
const context2 = await resolveSelectionContext(profileId, options);
|
|
@@ -47935,7 +46648,7 @@ async function prepareSelectedSecretBindings(selected, cwd = process.cwd()) {
|
|
|
47935
46648
|
const names = declaredSecretNames(manifest.runtime?.env);
|
|
47936
46649
|
if (!names.length)
|
|
47937
46650
|
throw new SkillSelectionError("NO_DECLARED_SECRETS", "This selected executable does not declare any environment references.");
|
|
47938
|
-
return selectedSecretBindingsTemplate({ selection: selected.selection, profileId: selected.profileId, cwd }, names);
|
|
46651
|
+
return selectedSecretBindingsTemplate({ selection: selected.selection, profileId: selected.profileId, cwd }, names, await defaultSecretsClientFactory());
|
|
47939
46652
|
}
|
|
47940
46653
|
async function executeSelectedLocal(selected, options = {}) {
|
|
47941
46654
|
const selection2 = structuredClone(selected.selection), profileId = selected.profileId;
|
|
@@ -48001,7 +46714,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48001
46714
|
throw new SkillSelectionError("INVALID_SECRET_BINDINGS", "Explicit environment values must be bounded strings without NUL bytes.");
|
|
48002
46715
|
secretEnv[name] = value;
|
|
48003
46716
|
}
|
|
48004
|
-
const runDirectory = mkdtempSync3(
|
|
46717
|
+
const runDirectory = mkdtempSync3(join40(tmpdir(), "skills-execution-"));
|
|
48005
46718
|
const paths = new SkillEntryPaths;
|
|
48006
46719
|
for (const entry of entries) {
|
|
48007
46720
|
paths.add(entry.path, 100, () => {
|
|
@@ -48009,7 +46722,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48009
46722
|
}, () => {
|
|
48010
46723
|
throw new SkillSelectionError("INVALID_BUNDLE_PATH", "The execution bundle path exceeds its limit.");
|
|
48011
46724
|
});
|
|
48012
|
-
const destination =
|
|
46725
|
+
const destination = join40(runDirectory, entry.path);
|
|
48013
46726
|
mkdirSync13(dirname30(destination), { recursive: true, mode: 448 });
|
|
48014
46727
|
writeFileSync12(destination, entry.bytes, { flag: "wx", mode: entry.mode & 511 });
|
|
48015
46728
|
}
|
|
@@ -48037,7 +46750,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48037
46750
|
}
|
|
48038
46751
|
if (bindings)
|
|
48039
46752
|
secretEnv = await resolveSelectedSecrets(bindings, options.createSecretsClient);
|
|
48040
|
-
const result = await subprocess([executable, ...runtimeName === "bun" ? ["--no-env-file"] : [],
|
|
46753
|
+
const result = await subprocess([executable, ...runtimeName === "bun" ? ["--no-env-file"] : [], join40(runDirectory, declaredEntry), ...args], runDirectory, { ...env, ...secretEnv }, input2, timeoutMs);
|
|
48041
46754
|
result.stdout = redactExecutionSecrets(result.stdout, secretEnv);
|
|
48042
46755
|
result.stderr = redactExecutionSecrets(result.stderr, secretEnv);
|
|
48043
46756
|
const receipt = {
|
|
@@ -48051,7 +46764,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48051
46764
|
...bindings ? { secretBinding: { schema: bindings.schema, selection: bindings.selection, consumer: bindings.consumer, secretsAuthority: bindings.secretsAuthority, bindings: bindings.bindings } } : {},
|
|
48052
46765
|
...result
|
|
48053
46766
|
};
|
|
48054
|
-
writeFileSync12(
|
|
46767
|
+
writeFileSync12(join40(runDirectory, ".execution-receipt.json"), JSON.stringify({ ...receipt, stdout: undefined, stderr: undefined }), { mode: 384 });
|
|
48055
46768
|
return receipt;
|
|
48056
46769
|
}
|
|
48057
46770
|
async function subprocess(command, cwd, env, input2, timeoutMs) {
|
|
@@ -48241,7 +46954,7 @@ var init_blog_article = __esm(() => {
|
|
|
48241
46954
|
|
|
48242
46955
|
// src/mcp/operation-tools.ts
|
|
48243
46956
|
import { existsSync as existsSync16, readdirSync as readdirSync13, statSync as statSync12 } from "fs";
|
|
48244
|
-
import { join as
|
|
46957
|
+
import { join as join41 } from "path";
|
|
48245
46958
|
function registerOperationTools(server) {
|
|
48246
46959
|
server.registerTool("cloud_skill_eligibility", {
|
|
48247
46960
|
title: "Cloud Skill Eligibility",
|
|
@@ -48723,7 +47436,7 @@ function registerOperationTools(server) {
|
|
|
48723
47436
|
if (exists) {
|
|
48724
47437
|
try {
|
|
48725
47438
|
skillCount = readdirSync13(agentSkillsPath).filter((f) => {
|
|
48726
|
-
const full =
|
|
47439
|
+
const full = join41(agentSkillsPath, f);
|
|
48727
47440
|
return !f.startsWith(".") && statSync12(full).isDirectory();
|
|
48728
47441
|
}).length;
|
|
48729
47442
|
} catch {}
|
|
@@ -48791,10 +47504,10 @@ var init_operation_tools = __esm(() => {
|
|
|
48791
47504
|
|
|
48792
47505
|
// src/lib/feedback.ts
|
|
48793
47506
|
import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync14 } from "fs";
|
|
48794
|
-
import { dirname as dirname31, join as
|
|
47507
|
+
import { dirname as dirname31, join as join42 } from "path";
|
|
48795
47508
|
import { Database } from "bun:sqlite";
|
|
48796
47509
|
function getFeedbackDbPath() {
|
|
48797
|
-
return
|
|
47510
|
+
return join42(getDataDir(), "skills.db");
|
|
48798
47511
|
}
|
|
48799
47512
|
function getFeedbackDb() {
|
|
48800
47513
|
const dbPath = getFeedbackDbPath();
|
|
@@ -48826,7 +47539,7 @@ function saveFeedback(input2) {
|
|
|
48826
47539
|
throw new Error("Feedback message is required");
|
|
48827
47540
|
const category = input2.category ?? "general";
|
|
48828
47541
|
if (isApiMode()) {
|
|
48829
|
-
const path =
|
|
47542
|
+
const path = join42(getDataDir(), "feedback.jsonl");
|
|
48830
47543
|
const dir = dirname31(path);
|
|
48831
47544
|
if (!existsSync17(dir))
|
|
48832
47545
|
mkdirSync14(dir, { recursive: true });
|
|
@@ -48990,23 +47703,23 @@ var init_resource_meta_tools = __esm(() => {
|
|
|
48990
47703
|
});
|
|
48991
47704
|
|
|
48992
47705
|
// src/lib/scheduler.ts
|
|
48993
|
-
import { existsSync as existsSync18, readFileSync as
|
|
48994
|
-
import { join as
|
|
47706
|
+
import { existsSync as existsSync18, readFileSync as readFileSync23, writeFileSync as writeFileSync13, mkdirSync as mkdirSync15 } from "fs";
|
|
47707
|
+
import { join as join43 } from "path";
|
|
48995
47708
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
48996
|
-
return
|
|
47709
|
+
return join43(targetDir, ".skills", "schedules.json");
|
|
48997
47710
|
}
|
|
48998
47711
|
function loadSchedules(targetDir = process.cwd()) {
|
|
48999
47712
|
const path = getSchedulesPath(targetDir);
|
|
49000
47713
|
if (existsSync18(path)) {
|
|
49001
47714
|
try {
|
|
49002
|
-
return JSON.parse(
|
|
47715
|
+
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
49003
47716
|
} catch {}
|
|
49004
47717
|
}
|
|
49005
47718
|
return { version: 1, schedules: [] };
|
|
49006
47719
|
}
|
|
49007
47720
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
49008
47721
|
const path = getSchedulesPath(targetDir);
|
|
49009
|
-
const dir =
|
|
47722
|
+
const dir = join43(targetDir, ".skills");
|
|
49010
47723
|
if (!existsSync18(dir))
|
|
49011
47724
|
mkdirSync15(dir, { recursive: true });
|
|
49012
47725
|
writeFileSync13(path, JSON.stringify(data, null, 2));
|
|
@@ -49282,12 +47995,12 @@ import { createHash as createHash32, createHmac } from "crypto";
|
|
|
49282
47995
|
import {
|
|
49283
47996
|
existsSync as existsSync19,
|
|
49284
47997
|
mkdirSync as mkdirSync16,
|
|
49285
|
-
readFileSync as
|
|
47998
|
+
readFileSync as readFileSync24,
|
|
49286
47999
|
readdirSync as readdirSync14,
|
|
49287
48000
|
statSync as statSync13,
|
|
49288
48001
|
writeFileSync as writeFileSync14
|
|
49289
48002
|
} from "fs";
|
|
49290
|
-
import { dirname as dirname32, join as
|
|
48003
|
+
import { dirname as dirname32, join as join44, normalize as normalize5, relative as relative10, sep as sep15 } from "path";
|
|
49291
48004
|
function resolveSkillsNativeStorageConfig(env = process.env) {
|
|
49292
48005
|
assertNoRetiredModeEnvVars(env, {
|
|
49293
48006
|
app: SKILLS_ENV_NAMESPACE,
|
|
@@ -49328,7 +48041,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
49328
48041
|
local: {
|
|
49329
48042
|
dataDir: getDataDir(),
|
|
49330
48043
|
projectStateDir: getProjectStateDir(targetDir),
|
|
49331
|
-
feedbackDbPath:
|
|
48044
|
+
feedbackDbPath: join44(getDataDir(), "skills.db")
|
|
49332
48045
|
},
|
|
49333
48046
|
remote: {
|
|
49334
48047
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -49350,7 +48063,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
49350
48063
|
const files = [];
|
|
49351
48064
|
if (existsSync19(projectStateDir)) {
|
|
49352
48065
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
49353
|
-
const bytes =
|
|
48066
|
+
const bytes = readFileSync24(filePath);
|
|
49354
48067
|
const relativePath = toPosix(relative10(targetDir, filePath));
|
|
49355
48068
|
files.push({
|
|
49356
48069
|
path: relativePath,
|
|
@@ -49409,7 +48122,7 @@ function parsePositiveInteger(value) {
|
|
|
49409
48122
|
function walkFiles2(dir) {
|
|
49410
48123
|
const files = [];
|
|
49411
48124
|
for (const entry of readdirSync14(dir)) {
|
|
49412
|
-
const full =
|
|
48125
|
+
const full = join44(dir, entry);
|
|
49413
48126
|
const stats = statSync13(full);
|
|
49414
48127
|
if (stats.isDirectory())
|
|
49415
48128
|
files.push(...walkFiles2(full));
|
|
@@ -49678,11 +48391,11 @@ var init_private_publication_customer = __esm(() => {
|
|
|
49678
48391
|
});
|
|
49679
48392
|
|
|
49680
48393
|
// src/lib/private-publication-recovery.ts
|
|
49681
|
-
import { constants as constants21, closeSync as
|
|
49682
|
-
import { dirname as dirname33, isAbsolute as
|
|
48394
|
+
import { constants as constants21, closeSync as closeSync23, fsyncSync as fsyncSync6, fstatSync as fstatSync23, lstatSync as lstatSync29, mkdirSync as mkdirSync17, openSync as openSync23, readSync as readSync16, realpathSync as realpathSync14, renameSync as renameSync8, unlinkSync as unlinkSync7, writeFileSync as writeFileSync15 } from "fs";
|
|
48395
|
+
import { dirname as dirname33, isAbsolute as isAbsolute28, join as join45, resolve as resolve28 } from "path";
|
|
49683
48396
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
49684
48397
|
function safeDirectory(directory2) {
|
|
49685
|
-
if (!
|
|
48398
|
+
if (!isAbsolute28(directory2) || directory2 !== resolve28(directory2) || realpathSync14(directory2) !== directory2)
|
|
49686
48399
|
return fail2();
|
|
49687
48400
|
for (let path = directory2;; path = dirname33(path)) {
|
|
49688
48401
|
const stat3 = lstatSync29(path);
|
|
@@ -49702,10 +48415,10 @@ function unchangedDirectory(directory2, identity4) {
|
|
|
49702
48415
|
return fail2();
|
|
49703
48416
|
}
|
|
49704
48417
|
function readOwned(directory2, name, max) {
|
|
49705
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
49706
|
-
const fd =
|
|
48418
|
+
const identity4 = safeDirectory(directory2), file2 = join45(directory2, name);
|
|
48419
|
+
const fd = openSync23(file2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49707
48420
|
try {
|
|
49708
|
-
const stat3 =
|
|
48421
|
+
const stat3 = fstatSync23(fd);
|
|
49709
48422
|
if (!stat3.isFile() || stat3.nlink !== 1 || stat3.size > max || stat3.size < 1 || (stat3.mode & 63) !== 0 || process.getuid && stat3.uid !== process.getuid())
|
|
49710
48423
|
return fail2();
|
|
49711
48424
|
const buffer = Buffer.alloc(Math.min(max, stat3.size) + 1);
|
|
@@ -49716,22 +48429,22 @@ function readOwned(directory2, name, max) {
|
|
|
49716
48429
|
break;
|
|
49717
48430
|
length += count;
|
|
49718
48431
|
}
|
|
49719
|
-
const bytes = buffer.subarray(0, length), after =
|
|
48432
|
+
const bytes = buffer.subarray(0, length), after = fstatSync23(fd);
|
|
49720
48433
|
unchangedDirectory(directory2, identity4);
|
|
49721
48434
|
if (bytes.length !== stat3.size || stat3.size !== after.size || stat3.mtimeMs !== after.mtimeMs || stat3.ctimeMs !== after.ctimeMs)
|
|
49722
48435
|
return fail2();
|
|
49723
48436
|
return bytes;
|
|
49724
48437
|
} finally {
|
|
49725
|
-
|
|
48438
|
+
closeSync23(fd);
|
|
49726
48439
|
}
|
|
49727
48440
|
}
|
|
49728
48441
|
function writeOwned(directory2, name, bytes) {
|
|
49729
|
-
const fd =
|
|
48442
|
+
const fd = openSync23(join45(directory2, name), constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
49730
48443
|
try {
|
|
49731
48444
|
writeFileSync15(fd, bytes);
|
|
49732
48445
|
fsyncSync6(fd);
|
|
49733
48446
|
} finally {
|
|
49734
|
-
|
|
48447
|
+
closeSync23(fd);
|
|
49735
48448
|
}
|
|
49736
48449
|
}
|
|
49737
48450
|
function save(directory2, value) {
|
|
@@ -49739,12 +48452,12 @@ function save(directory2, value) {
|
|
|
49739
48452
|
writeOwned(directory2, temporary, JSON.stringify(value) + `
|
|
49740
48453
|
`);
|
|
49741
48454
|
unchangedDirectory(directory2, identity4);
|
|
49742
|
-
renameSync8(
|
|
49743
|
-
const fd =
|
|
48455
|
+
renameSync8(join45(directory2, temporary), join45(directory2, "receipt.json"));
|
|
48456
|
+
const fd = openSync23(directory2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49744
48457
|
try {
|
|
49745
48458
|
fsyncSync6(fd);
|
|
49746
48459
|
} finally {
|
|
49747
|
-
|
|
48460
|
+
closeSync23(fd);
|
|
49748
48461
|
}
|
|
49749
48462
|
}
|
|
49750
48463
|
function bind(client, receipt) {
|
|
@@ -49752,21 +48465,21 @@ function bind(client, receipt) {
|
|
|
49752
48465
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
49753
48466
|
}
|
|
49754
48467
|
async function locked(directory2, action) {
|
|
49755
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
48468
|
+
const identity4 = safeDirectory(directory2), file2 = join45(directory2, "operation.lock");
|
|
49756
48469
|
let fd;
|
|
49757
48470
|
try {
|
|
49758
|
-
fd =
|
|
48471
|
+
fd = openSync23(file2, constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
49759
48472
|
} catch {
|
|
49760
48473
|
throw new PrivatePublicationError("PUBLICATION_RECOVERY_BUSY", "Another operation holds this recovery directory. If it crashed, confirm that process has stopped before explicitly removing operation.lock and resuming.");
|
|
49761
48474
|
}
|
|
49762
|
-
const lock =
|
|
48475
|
+
const lock = fstatSync23(fd);
|
|
49763
48476
|
try {
|
|
49764
48477
|
writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
|
|
49765
48478
|
`);
|
|
49766
48479
|
fsyncSync6(fd);
|
|
49767
48480
|
return await action();
|
|
49768
48481
|
} finally {
|
|
49769
|
-
|
|
48482
|
+
closeSync23(fd);
|
|
49770
48483
|
unchangedDirectory(directory2, identity4);
|
|
49771
48484
|
const now = lstatSync29(file2);
|
|
49772
48485
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
@@ -49820,15 +48533,15 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
49820
48533
|
phase: "prepared",
|
|
49821
48534
|
intent: null
|
|
49822
48535
|
};
|
|
49823
|
-
if (!
|
|
48536
|
+
if (!isAbsolute28(recoveryDirectory) || resolve28(recoveryDirectory) !== recoveryDirectory || realpathSync14(dirname33(recoveryDirectory)) !== dirname33(recoveryDirectory))
|
|
49824
48537
|
return fail2();
|
|
49825
48538
|
mkdirSync17(recoveryDirectory, { mode: 448 });
|
|
49826
48539
|
safeDirectory(recoveryDirectory);
|
|
49827
|
-
const parent =
|
|
48540
|
+
const parent = openSync23(dirname33(recoveryDirectory), constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49828
48541
|
try {
|
|
49829
48542
|
fsyncSync6(parent);
|
|
49830
48543
|
} finally {
|
|
49831
|
-
|
|
48544
|
+
closeSync23(parent);
|
|
49832
48545
|
}
|
|
49833
48546
|
writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
|
|
49834
48547
|
save(recoveryDirectory, receipt);
|
|
@@ -49921,10 +48634,10 @@ var init_private_publication_recovery = __esm(() => {
|
|
|
49921
48634
|
});
|
|
49922
48635
|
|
|
49923
48636
|
// src/mcp/private-publication-tools.ts
|
|
49924
|
-
import { isAbsolute as
|
|
48637
|
+
import { isAbsolute as isAbsolute29 } from "path";
|
|
49925
48638
|
function registerPrivatePublicationTools(server) {
|
|
49926
48639
|
const uuid9 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
|
|
49927
|
-
const directory2 = exports_external.string().max(4096).refine(
|
|
48640
|
+
const directory2 = exports_external.string().max(4096).refine(isAbsolute29, "Use an absolute local directory");
|
|
49928
48641
|
const verification2 = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid9, membershipId: uuid9, recoveryDirectory: directory2 };
|
|
49929
48642
|
const description = "Manage a private source publication using fresh workspace-bound verification and a host-local recovery directory. Publishing requires explicit UUID current-version comparison and upload consent. Resume reuses immutable saved bytes and intent; an uncertain PUT is finalized for server verification, never uploaded twice. Sessions and signed URLs are not returned or persisted. Private execution is unavailable. Host request history may contain the supplied verification code.";
|
|
49930
48643
|
const handler = (action) => async (value) => {
|