@hasna/skills 0.10.90 → 0.10.92
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 +818 -1809
- package/bin/mcp.js +328 -1376
- package/bin/migrate.js +8 -5
- package/bin/server.js +73819 -12659
- package/bin/worker.js +60586 -7855
- package/dist/index.js +255 -27
- package/dist/lib/agent-discovery.d.ts +14 -1
- package/dist/lib/execution-secrets.d.ts +17 -1
- package/dist/runtime-prerequisites.js +8 -5
- package/dist/sdk/index.js +79215 -19013
- 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.92",
|
|
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;
|
|
@@ -22520,6 +22526,11 @@ var init_agent_opencode = __esm(() => {
|
|
|
22520
22526
|
});
|
|
22521
22527
|
|
|
22522
22528
|
// src/lib/agent-adapters.ts
|
|
22529
|
+
function renderAgentHookCommand(command, agent, profileId, event) {
|
|
22530
|
+
if (typeof profileId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) || profileId.includes(".."))
|
|
22531
|
+
throw new Error("Invalid selection profile id");
|
|
22532
|
+
return `'${command.replace(/'/g, `'\\''`)}' hook user-prompt --agent ${agent} --selection-profile ${profileId} --event ${event}`;
|
|
22533
|
+
}
|
|
22523
22534
|
var INTEGRATION_AGENTS;
|
|
22524
22535
|
var init_agent_adapters = __esm(() => {
|
|
22525
22536
|
init_agent_opencode();
|
|
@@ -30550,6 +30561,17 @@ function parseHermesConfig(text2) {
|
|
|
30550
30561
|
throw new Error(`Expected Hermes ${name} object`);
|
|
30551
30562
|
return value;
|
|
30552
30563
|
}
|
|
30564
|
+
function quoted(value) {
|
|
30565
|
+
if (!value || /[\0\r\n]/.test(value))
|
|
30566
|
+
throw new Error("Invalid Hermes Skills command");
|
|
30567
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
30568
|
+
}
|
|
30569
|
+
function hermesHookDefinitions(supervisor) {
|
|
30570
|
+
if (!isAbsolute7(supervisor.runtime) || !isAbsolute7(supervisor.path))
|
|
30571
|
+
throw new Error("Expected absolute Hermes supervisor paths");
|
|
30572
|
+
const entry = (event) => ({ command: `${quoted(supervisor.runtime)} ${quoted(supervisor.path)} --event ${event}`, timeout: 15, ...event === "pre_tool_call" ? { fail_closed: true } : {} });
|
|
30573
|
+
return { pre_llm_call: entry("pre_llm_call"), pre_tool_call: entry("pre_tool_call") };
|
|
30574
|
+
}
|
|
30553
30575
|
function assertHermesEnvironment(home) {
|
|
30554
30576
|
if (process.env.TERMINAL_CWD && process.env.TERMINAL_CWD !== process.cwd())
|
|
30555
30577
|
throw new NativeBridgeIntegrityRefusal("NATIVE_SKILL_DRIFT: Hermes TERMINAL_CWD must match the checked process working directory; review the effective project before using this adapter");
|
|
@@ -38660,9 +38682,94 @@ function nativeDiscoveryAliasTargets(roots, home) {
|
|
|
38660
38682
|
}
|
|
38661
38683
|
return [...targets];
|
|
38662
38684
|
}
|
|
38685
|
+
function hooksAreManagedBridge(agent, hooks, managed) {
|
|
38686
|
+
const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
38687
|
+
const exactKeys3 = (value, keys2) => Object.keys(value).sort().join("\x00") === [...keys2].sort().join("\x00");
|
|
38688
|
+
if (!table(hooks) || typeof managed.command !== "string" || !isAbsolute22(managed.command))
|
|
38689
|
+
return false;
|
|
38690
|
+
const events3 = agent === "gemini" ? ["BeforeAgent", "SessionStart", "BeforeTool"] : ["beforeSubmitPrompt", "sessionStart"];
|
|
38691
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
38692
|
+
if (!events3.includes(event) || !Array.isArray(entries) || entries.length !== 1)
|
|
38693
|
+
return false;
|
|
38694
|
+
let expected;
|
|
38695
|
+
try {
|
|
38696
|
+
expected = renderAgentHookCommand(managed.command, agent, managed.profile, event);
|
|
38697
|
+
} catch {
|
|
38698
|
+
return false;
|
|
38699
|
+
}
|
|
38700
|
+
const entry = entries[0];
|
|
38701
|
+
if (!table(entry))
|
|
38702
|
+
return false;
|
|
38703
|
+
if (agent === "cursor") {
|
|
38704
|
+
if (!exactKeys3(entry, ["command", "timeout"]) || entry.timeout !== 15 || entry.command !== expected)
|
|
38705
|
+
return false;
|
|
38706
|
+
continue;
|
|
38707
|
+
}
|
|
38708
|
+
if (!exactKeys3(entry, event === "BeforeTool" ? ["hooks", "matcher"] : ["hooks"]) || event === "BeforeTool" && entry.matcher !== "activate_skill" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
|
|
38709
|
+
return false;
|
|
38710
|
+
const hook = entry.hooks[0];
|
|
38711
|
+
if (!table(hook) || !exactKeys3(hook, ["command", "timeout", "type"]) || hook.type !== "command" || hook.timeout !== 15000 || hook.command !== expected)
|
|
38712
|
+
return false;
|
|
38713
|
+
}
|
|
38714
|
+
return true;
|
|
38715
|
+
}
|
|
38716
|
+
function sumiPluginLoadsOnlyBuiltins(text2) {
|
|
38717
|
+
for (const match of text2.matchAll(/\b(?:import|require)\s*\(\s*(.?)/g)) {
|
|
38718
|
+
const rest = text2.slice(match.index + match[0].length - match[1].length);
|
|
38719
|
+
if (!/^(['"])node:[A-Za-z0-9_/.-]+\1/.test(rest))
|
|
38720
|
+
return false;
|
|
38721
|
+
}
|
|
38722
|
+
for (const match of text2.matchAll(/\b(?:from|import)\s*(['"])([^'"]*)\1/g))
|
|
38723
|
+
if (!match[2].startsWith("node:"))
|
|
38724
|
+
return false;
|
|
38725
|
+
return true;
|
|
38726
|
+
}
|
|
38727
|
+
function codexRootFreeControls(config2) {
|
|
38728
|
+
const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date));
|
|
38729
|
+
const adapted = [];
|
|
38730
|
+
const servers = config2.mcp_servers;
|
|
38731
|
+
if (table(servers) && Object.keys(servers).length <= AGENT_POLICY_LIMITS.discoveryRoots && Object.entries(servers).every(([name, row]) => name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row)))
|
|
38732
|
+
adapted.push("mcp_servers");
|
|
38733
|
+
const agents = config2.agents;
|
|
38734
|
+
if (table(agents) && Object.keys(agents).length <= AGENT_POLICY_LIMITS.fields && Object.values(agents).every((value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
|
|
38735
|
+
adapted.push("agents");
|
|
38736
|
+
return adapted;
|
|
38737
|
+
}
|
|
38738
|
+
function codexPluginHookHandlers(value) {
|
|
38739
|
+
const table = (item) => Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
38740
|
+
const identifier5 = (item) => typeof item === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(item);
|
|
38741
|
+
const text2 = (item) => typeof item === "string" && item.length <= AGENT_POLICY_LIMITS.pathCharacters && !item.includes("\x00");
|
|
38742
|
+
if (!table(value) || !Object.keys(value).every((key2) => key2 === "hooks" || key2 === "description") || value.description !== undefined && !text2(value.description) || !table(value.hooks) || Object.keys(value.hooks).length > 32)
|
|
38743
|
+
return null;
|
|
38744
|
+
const result = { commands: 0, servers: new Set };
|
|
38745
|
+
for (const [event, groups] of Object.entries(value.hooks)) {
|
|
38746
|
+
if (!CODEX_HOOK_EVENTS.has(event) || !Array.isArray(groups) || groups.length > 32)
|
|
38747
|
+
return null;
|
|
38748
|
+
for (const group of groups) {
|
|
38749
|
+
if (!table(group) || !Object.keys(group).every((key2) => key2 === "hooks" || key2 === "matcher") || group.matcher !== undefined && !text2(group.matcher) || !Array.isArray(group.hooks) || group.hooks.length > 32)
|
|
38750
|
+
return null;
|
|
38751
|
+
for (const handler of group.hooks) {
|
|
38752
|
+
if (!table(handler) || handler.timeout !== undefined && !(Number.isSafeInteger(handler.timeout) && handler.timeout >= 0) || handler.statusMessage !== undefined && !text2(handler.statusMessage))
|
|
38753
|
+
return null;
|
|
38754
|
+
if (handler.type === "command") {
|
|
38755
|
+
if (!text2(handler.command) || !handler.command || !Object.keys(handler).every((key2) => ["type", "command", "commandWindows", "command_windows", "timeout", "async", "statusMessage", "additionalContextLimit"].includes(key2)))
|
|
38756
|
+
return null;
|
|
38757
|
+
result.commands++;
|
|
38758
|
+
} else if (handler.type === "mcp_tool") {
|
|
38759
|
+
if (!identifier5(handler.server) || !identifier5(handler.tool) || handler.input !== undefined && !table(handler.input) || !Object.keys(handler).every((key2) => ["type", "server", "tool", "input", "timeout", "statusMessage"].includes(key2)))
|
|
38760
|
+
return null;
|
|
38761
|
+
result.servers.add(handler.server);
|
|
38762
|
+
} else
|
|
38763
|
+
return null;
|
|
38764
|
+
}
|
|
38765
|
+
}
|
|
38766
|
+
}
|
|
38767
|
+
return result;
|
|
38768
|
+
}
|
|
38663
38769
|
function resolveDiscovery(options, exclusion) {
|
|
38664
38770
|
const canonical9 = options.canonical ?? resolve23, home = resolve23(options.home), agent = options.agent;
|
|
38665
38771
|
const sources = [], roots = new Set, builtinNames = [];
|
|
38772
|
+
const sumiLocalPluginTexts = new Map;
|
|
38666
38773
|
const budget3 = discoveryByteBudget();
|
|
38667
38774
|
let remainingEntries = AGENT_POLICY_LIMITS.discoveryDirectoryEntries;
|
|
38668
38775
|
const entries = (path) => {
|
|
@@ -38739,12 +38846,38 @@ function resolveDiscovery(options, exclusion) {
|
|
|
38739
38846
|
const controls = agent === "claude" ? claudeDiscoveryControlKeys(configText ?? "{}") : agent === "codex" ? codexDiscoveryControlKeys(config2) : Object.keys(config2).filter((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2));
|
|
38740
38847
|
const fieldKey = (key2) => /^[A-Za-z_$][A-Za-z0-9_$.-]{0,22}$/.test(key2) && !/(?:sk|rk|pk)_(?:live|test)_|whsec_/i.test(key2) ? JSON.stringify(key2) : "[withheld field key]";
|
|
38741
38848
|
const unsupportedFields = (names, label) => unresolved(`unknown discovery controls have no root-exclusion adapter for ${label}${names.slice(0, 8).map(fieldKey).join(", ")}${names.length > 8 ? ", additional keys withheld" : ""} in ${quotedPath(canonical9(configPath))}`);
|
|
38742
|
-
const
|
|
38849
|
+
const adapted = agent === "codex" ? codexRootFreeControls(config2) : agent === "opencode" && Array.isArray(config2.instructions) && config2.instructions.length === 0 ? ["instructions"] : [];
|
|
38850
|
+
const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2) && !adapted.includes(key2));
|
|
38743
38851
|
if (unsupportedControls.length)
|
|
38744
38852
|
unsupportedFields(unsupportedControls, "");
|
|
38745
38853
|
if (config2.hooks !== undefined && !object6(config2.hooks))
|
|
38746
38854
|
unresolved("hooks must be an object");
|
|
38747
|
-
const
|
|
38855
|
+
const managedSupervisorOnly = () => {
|
|
38856
|
+
const supervisor = exclusion.managedHook?.supervisor;
|
|
38857
|
+
if (agent !== "hermes" || !supervisor || typeof supervisor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(supervisor.sha256) || !object6(config2.hooks))
|
|
38858
|
+
return false;
|
|
38859
|
+
let expected;
|
|
38860
|
+
try {
|
|
38861
|
+
expected = hermesHookDefinitions(supervisor);
|
|
38862
|
+
} catch {
|
|
38863
|
+
return false;
|
|
38864
|
+
}
|
|
38865
|
+
for (const [event, entries2] of Object.entries(config2.hooks)) {
|
|
38866
|
+
const want = expected[event];
|
|
38867
|
+
if (!want || !Array.isArray(entries2) || entries2.length !== 1 || !object6(entries2[0]))
|
|
38868
|
+
return false;
|
|
38869
|
+
const entry = entries2[0];
|
|
38870
|
+
if (Object.keys(entry).sort().join("\x00") !== Object.keys(want).sort().join("\x00") || Object.keys(want).some((key2) => entry[key2] !== want[key2]))
|
|
38871
|
+
return false;
|
|
38872
|
+
}
|
|
38873
|
+
try {
|
|
38874
|
+
return hashRawDiscoveryFile(canonical9(supervisor.path), budget3) === supervisor.sha256;
|
|
38875
|
+
} catch {
|
|
38876
|
+
return false;
|
|
38877
|
+
}
|
|
38878
|
+
};
|
|
38879
|
+
const managedBridgeOnly = (agent === "gemini" || agent === "cursor") && exclusion.managedHook !== undefined && hooksAreManagedBridge(agent, config2.hooks, exclusion.managedHook) || managedSupervisorOnly();
|
|
38880
|
+
const hasHooks = controls.includes("hooks") && config2.hooks !== undefined && Object.keys(config2.hooks).length > 0 && !managedBridgeOnly;
|
|
38748
38881
|
let hasPluginConfigs = false, hasFunctionHookEnvironment = false;
|
|
38749
38882
|
const verifyCurrentOpaqueSettings = (label) => {
|
|
38750
38883
|
const retained = exclusion.retained;
|
|
@@ -38849,7 +38982,9 @@ function resolveDiscovery(options, exclusion) {
|
|
|
38849
38982
|
unresolved("malformed Sumi plugins");
|
|
38850
38983
|
}
|
|
38851
38984
|
if (agent === "hermes" && config2.skills !== undefined) {
|
|
38852
|
-
keys2(config2.skills, ["external_dirs"], "Hermes skills");
|
|
38985
|
+
keys2(config2.skills, ["external_dirs", "creation_nudge_interval"], "Hermes skills");
|
|
38986
|
+
if (config2.skills.creation_nudge_interval !== undefined && !(Number.isSafeInteger(config2.skills.creation_nudge_interval) && config2.skills.creation_nudge_interval >= 0))
|
|
38987
|
+
unresolved("malformed Hermes skills creation_nudge_interval");
|
|
38853
38988
|
if (config2.skills.external_dirs !== undefined)
|
|
38854
38989
|
paths(typeof config2.skills.external_dirs === "string" ? [config2.skills.external_dirs] : config2.skills.external_dirs, "Hermes external directories");
|
|
38855
38990
|
}
|
|
@@ -38869,10 +39004,21 @@ function resolveDiscovery(options, exclusion) {
|
|
|
38869
39004
|
unresolved(`malformed Codex ${key2}`);
|
|
38870
39005
|
for (const value of Object.values(config2.plugins ?? {}))
|
|
38871
39006
|
keys2(value, ["enabled"], "Codex plugin registration");
|
|
38872
|
-
for (const value of Object.values(config2.marketplaces ?? {}))
|
|
38873
|
-
keys2(value, ["source_type", "source"], "Codex marketplace registration");
|
|
39007
|
+
for (const value of Object.values(config2.marketplaces ?? {})) {
|
|
39008
|
+
keys2(value, ["source_type", "source", "last_updated"], "Codex marketplace registration");
|
|
39009
|
+
if (value.last_updated !== undefined && (typeof value.last_updated !== "string" || !CODEX_MARKETPLACE_TIMESTAMP.test(value.last_updated)))
|
|
39010
|
+
unresolved("malformed Codex marketplace refresh time");
|
|
39011
|
+
}
|
|
38874
39012
|
if (config2.skills !== undefined)
|
|
38875
|
-
keys2(config2.skills, ["config", "bundled"], "Codex skills");
|
|
39013
|
+
keys2(config2.skills, ["config", "bundled", "native_allowed_paths"], "Codex skills");
|
|
39014
|
+
if (config2.skills?.native_allowed_paths !== undefined) {
|
|
39015
|
+
for (const value of paths(config2.skills.native_allowed_paths, "Codex native allowed skill paths")) {
|
|
39016
|
+
if (!isAbsolute22(value))
|
|
39017
|
+
unresolved("relative Codex native allowed skill path");
|
|
39018
|
+
const path = canonical9(value);
|
|
39019
|
+
roots.add(path.endsWith(`${sep11}SKILL.md`) ? dirname24(path) : path);
|
|
39020
|
+
}
|
|
39021
|
+
}
|
|
38876
39022
|
if (config2.skills?.bundled !== undefined) {
|
|
38877
39023
|
keys2(config2.skills.bundled, ["enabled"], "Codex bundled skills");
|
|
38878
39024
|
if (typeof config2.skills.bundled.enabled !== "boolean")
|
|
@@ -38930,7 +39076,9 @@ function resolveDiscovery(options, exclusion) {
|
|
|
38930
39076
|
const target = join33(path, name);
|
|
38931
39077
|
if (!lstatSync25(target).isFile())
|
|
38932
39078
|
unresolved("local plugin packages need a dedicated discovery adapter");
|
|
38933
|
-
witness(target);
|
|
39079
|
+
const text2 = witness(target);
|
|
39080
|
+
if (exclusion && text2 !== null)
|
|
39081
|
+
sumiLocalPluginTexts.set(canonical9(target), text2);
|
|
38934
39082
|
}
|
|
38935
39083
|
}
|
|
38936
39084
|
for (const root of roots) {
|
|
@@ -39064,16 +39212,88 @@ function resolveDiscovery(options, exclusion) {
|
|
|
39064
39212
|
const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual6(candidate, source)) === index);
|
|
39065
39213
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
39066
39214
|
}
|
|
39067
|
-
|
|
39215
|
+
const reviewedManifestAbsence = (root, manifestPath) => {
|
|
39216
|
+
const retained = exclusion?.retained;
|
|
39217
|
+
if (agent !== "claude" || retained?.method !== "reviewed" || ![join33(root, "skills"), root].some((path) => retained.roots.includes(path)))
|
|
39218
|
+
return false;
|
|
39219
|
+
const saved = retained.sources.filter((source) => source.path === canonical9(manifestPath) && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined);
|
|
39220
|
+
return saved.length > 0 && saved.every((source) => projected(source, undefined, budget3) === source.sha256);
|
|
39221
|
+
};
|
|
39222
|
+
const reviewedInstalledCopy = (root, path, identity4) => {
|
|
39223
|
+
const retained = exclusion?.retained;
|
|
39224
|
+
if (agent !== "codex" || retained?.method !== "reviewed")
|
|
39225
|
+
return false;
|
|
39226
|
+
const inside = relative7(root, canonical9(path));
|
|
39227
|
+
if (!inside || inside === ".." || inside.startsWith(`..${sep11}`) || isAbsolute22(inside))
|
|
39228
|
+
return false;
|
|
39229
|
+
const parent = canonical9(join33(nativeHome, "plugins/cache", identity4.marketplace, identity4.name));
|
|
39230
|
+
const current = hashRawDiscoveryFile(canonical9(path), budget3);
|
|
39231
|
+
if (current === null)
|
|
39232
|
+
return false;
|
|
39233
|
+
return retained.sources.some((source) => {
|
|
39234
|
+
if (source.sha256 !== current || source.format !== undefined || source.fields !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || !source.path.startsWith(parent + sep11))
|
|
39235
|
+
return false;
|
|
39236
|
+
const [version2, ...rest] = source.path.slice(parent.length + 1).split(sep11);
|
|
39237
|
+
return Boolean(version2) && version2 !== "." && version2 !== ".." && rest.join(sep11) === inside;
|
|
39238
|
+
});
|
|
39239
|
+
};
|
|
39240
|
+
const verifyCodexPluginHookClosure = (root, identity4, marketplaceSource, servers, declarations) => {
|
|
39241
|
+
const retained = exclusion.retained;
|
|
39242
|
+
if (retained.codexSemanticCache || retained.codexInstallationInputs || retained.codexRetiredMaterializations)
|
|
39243
|
+
return verifyOpaqueClosure(declarations);
|
|
39244
|
+
const within = (base, path) => path === base || path.startsWith(base + sep11);
|
|
39245
|
+
const executables = [];
|
|
39246
|
+
for (const server of servers) {
|
|
39247
|
+
const row = config2.mcp_servers?.[server];
|
|
39248
|
+
if (server === "codex_apps" || server === "_codex_apps" || !isCodexLocalStdioMcpServer(server, row))
|
|
39249
|
+
unresolved("plugin hook MCP server is not a configured local stdio server");
|
|
39250
|
+
for (const value of [row.command, ...row.args ?? []])
|
|
39251
|
+
for (const candidate of [value, value.slice(value.indexOf("=") + 1)]) {
|
|
39252
|
+
if (!isAbsolute22(candidate) || /[\0$]/.test(candidate) || executables.length >= AGENT_POLICY_LIMITS.discoveryRoots)
|
|
39253
|
+
continue;
|
|
39254
|
+
const path = canonical9(candidate);
|
|
39255
|
+
if (lstatSync25(path, { throwIfNoEntry: false })?.isFile())
|
|
39256
|
+
executables.push(path);
|
|
39257
|
+
}
|
|
39258
|
+
}
|
|
39259
|
+
const cacheRoot = canonical9(join33(nativeHome, "plugins/cache"));
|
|
39260
|
+
const ownCache = canonical9(join33(nativeHome, "plugins/cache", identity4.marketplace, identity4.name));
|
|
39261
|
+
const ownCatalog = canonical9(join33(marketplaceSource, ".agents/plugins/marketplace.json"));
|
|
39262
|
+
const account = canonical9(home);
|
|
39263
|
+
const marketplaceRoots = Object.values(config2.marketplaces ?? {}).filter((market) => market?.source_type === "local" && typeof market.source === "string" && isAbsolute22(market.source)).map((market) => canonical9(market.source)).filter((path) => !within(path, account));
|
|
39264
|
+
const otherPlugin = (path) => !within(root, path) && !within(ownCache, path) && path !== ownCatalog && !executables.includes(path) && (within(cacheRoot, path) || marketplaceRoots.some((base) => within(base, path)));
|
|
39265
|
+
const sources2 = retained.sources.filter((source) => source.path !== canonical9(configPath) && !otherPlugin(source.path));
|
|
39266
|
+
if (sources2.some((source) => source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || ![undefined, "bytes", "path-bytes", "executable-v1"].includes(source.hashMode)))
|
|
39267
|
+
return verifyOpaqueClosure(declarations);
|
|
39268
|
+
const declared = declarations.map((path) => relative7(root, path));
|
|
39269
|
+
const declaration = (path) => declarations.includes(path) || path.startsWith(ownCache + sep11) && declared.includes(path.slice(ownCache.length + 1).split(sep11).slice(1).join(sep11));
|
|
39270
|
+
if (!sources2.some((source) => !declaration(source.path) && source.sha256 !== null))
|
|
39271
|
+
unresolved("opaque executable dependency closure is unavailable");
|
|
39272
|
+
for (const source of sources2)
|
|
39273
|
+
if (projected(source, undefined, budget3) !== source.sha256) {
|
|
39274
|
+
throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
39275
|
+
}
|
|
39276
|
+
verifyDiscoveryDirectories((retained.directories ?? []).filter((directory) => !otherPlugin(directory.path)));
|
|
39277
|
+
};
|
|
39278
|
+
const readCodexHookDeclaration = (text2, path) => {
|
|
39279
|
+
if (text2 === null)
|
|
39280
|
+
return null;
|
|
39281
|
+
try {
|
|
39282
|
+
return codexPluginHookHandlers(parseConfig(text2, path));
|
|
39283
|
+
} catch {
|
|
39284
|
+
return null;
|
|
39285
|
+
}
|
|
39286
|
+
};
|
|
39287
|
+
function plugin(root, codex) {
|
|
39068
39288
|
root = canonical9(root);
|
|
39069
39289
|
safe6(root);
|
|
39070
39290
|
if (exclusion)
|
|
39071
39291
|
roots.add(root);
|
|
39072
39292
|
const manifestPath = join33(root, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
|
|
39073
39293
|
const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
|
|
39074
|
-
if (raw === null)
|
|
39294
|
+
if (raw === null && !reviewedManifestAbsence(root, manifestPath))
|
|
39075
39295
|
unresolved("plugin manifest missing");
|
|
39076
|
-
const manifest = parseConfig(raw, manifestPath);
|
|
39296
|
+
const manifest = raw === null ? {} : parseConfig(raw, manifestPath);
|
|
39077
39297
|
if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
|
|
39078
39298
|
unresolved("unknown plugin discovery controls");
|
|
39079
39299
|
const hooks = witness(join33(root, "hooks/hooks.json"));
|
|
@@ -39081,20 +39301,35 @@ function resolveDiscovery(options, exclusion) {
|
|
|
39081
39301
|
if (!exclusion || exclusion.retained?.method !== "reviewed")
|
|
39082
39302
|
unresolved("plugin hooks require a separate no-skill-injection review");
|
|
39083
39303
|
const required2 = [manifestPath, ...hooks !== null ? [join33(root, "hooks/hooks.json")] : []];
|
|
39304
|
+
const scoped = agent === "codex" && codex !== undefined ? [] : undefined;
|
|
39084
39305
|
if (manifest.hooks !== undefined) {
|
|
39085
|
-
if (typeof manifest.hooks
|
|
39086
|
-
|
|
39087
|
-
|
|
39088
|
-
|
|
39089
|
-
|
|
39090
|
-
|
|
39091
|
-
|
|
39306
|
+
if (scoped && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
|
|
39307
|
+
const inline = codexPluginHookHandlers(manifest.hooks);
|
|
39308
|
+
if (!inline)
|
|
39309
|
+
unresolved("unsupported inline plugin hooks");
|
|
39310
|
+
scoped.push(inline);
|
|
39311
|
+
} else {
|
|
39312
|
+
if (typeof manifest.hooks !== "string")
|
|
39313
|
+
unresolved("unsupported plugin hooks target");
|
|
39314
|
+
const hook = resolve23(root, manifest.hooks);
|
|
39315
|
+
if (!hook.startsWith(root + sep11))
|
|
39316
|
+
unresolved("plugin hooks target escapes its root");
|
|
39317
|
+
required2.push(hook);
|
|
39318
|
+
if (scoped)
|
|
39319
|
+
scoped.push(readCodexHookDeclaration(witness(hook), hook));
|
|
39320
|
+
}
|
|
39321
|
+
}
|
|
39322
|
+
if (scoped && hooks !== null)
|
|
39323
|
+
scoped.push(readCodexHookDeclaration(hooks, join33(root, "hooks/hooks.json")));
|
|
39092
39324
|
for (const path of required2) {
|
|
39093
39325
|
const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
|
|
39094
|
-
if (
|
|
39326
|
+
if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped && reviewedInstalledCopy(root, path, codex)))
|
|
39095
39327
|
unresolved("plugin hooks require current independently reviewed sources");
|
|
39096
39328
|
}
|
|
39097
|
-
|
|
39329
|
+
if (scoped && scoped.every((item) => item !== null && item.commands === 0)) {
|
|
39330
|
+
verifyCodexPluginHookClosure(root, codex, codex.source, new Set(scoped.flatMap((item) => [...item.servers])), required2.map((path) => canonical9(path)));
|
|
39331
|
+
} else
|
|
39332
|
+
verifyOpaqueClosure(required2.map((path) => canonical9(path)));
|
|
39098
39333
|
}
|
|
39099
39334
|
const commands = join33(root, "commands");
|
|
39100
39335
|
safe6(commands);
|
|
@@ -39150,7 +39385,7 @@ function resolveDiscovery(options, exclusion) {
|
|
|
39150
39385
|
const catalog = parseConfig(catalogText, join33(market.source, ".agents/plugins/marketplace.json")), matches = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
|
|
39151
39386
|
if (matches.length !== 1 || matches[0]?.source?.source !== "local" || typeof matches[0]?.source?.path !== "string")
|
|
39152
39387
|
unresolved("plugin catalog source is missing or ambiguous");
|
|
39153
|
-
plugin(resolve23(market.source, matches[0].source.path));
|
|
39388
|
+
plugin(resolve23(market.source, matches[0].source.path), exclusion ? { marketplace, name, source: market.source } : undefined);
|
|
39154
39389
|
}
|
|
39155
39390
|
} else if (agent === "opencode") {
|
|
39156
39391
|
if (witness(join33(home, ".config/opencode/opencode.jsonc")) !== null || witness(join33(home, ".config/opencode/config.json")) !== null)
|
|
@@ -39167,7 +39402,11 @@ function resolveDiscovery(options, exclusion) {
|
|
|
39167
39402
|
if (lstatSync25(localPlugins, { throwIfNoEntry: false }) && entries(localPlugins).some((name) => name !== "skills-cli.js"))
|
|
39168
39403
|
unresolved("additional local plugin hooks require review");
|
|
39169
39404
|
} else if (agent === "sumi") {
|
|
39170
|
-
|
|
39405
|
+
const pinnedLocalPlugin = (source) => {
|
|
39406
|
+
const retained = exclusion?.retained, text2 = sumiLocalPluginTexts.get(source.path);
|
|
39407
|
+
return retained?.method === "reviewed" && text2 !== undefined && sumiPluginLoadsOnlyBuiltins(text2) && retained.sources.some((saved) => saved.path === source.path && saved.sha256 === source.sha256 && saved.sha256 !== null && [undefined, "bytes"].includes(saved.hashMode) && saved.format === undefined && saved.fields === undefined && saved.managedPlugins === undefined && saved.reviewArtifact === undefined);
|
|
39408
|
+
};
|
|
39409
|
+
if ((config2.plugin?.length ?? 0) > 0 || (config2.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join33(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null && !pinnedLocalPlugin(source)))
|
|
39171
39410
|
unresolved("external or foreign local plugins require review");
|
|
39172
39411
|
} else if (agent === "hermes") {
|
|
39173
39412
|
for (const directory of [join33(home, ".hermes/plugins"), join33(home, ".hermes/hermes-agent")]) {
|
|
@@ -39207,11 +39446,12 @@ var DISCOVERY_RUNTIME_COMMANDS, quotedPath = (path) => JSON.stringify(path).repl
|
|
|
39207
39446
|
} catch {
|
|
39208
39447
|
return null;
|
|
39209
39448
|
}
|
|
39210
|
-
}, digest2 = (text2) => createHash26("sha256").update(text2).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS;
|
|
39449
|
+
}, digest2 = (text2) => createHash26("sha256").update(text2).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS, CODEX_MARKETPLACE_TIMESTAMP, CODEX_HOOK_EVENTS;
|
|
39211
39450
|
var init_agent_discovery = __esm(() => {
|
|
39212
39451
|
init_sumi_settings_witness();
|
|
39213
39452
|
init_agent_policy_limits();
|
|
39214
39453
|
init_agent_hermes();
|
|
39454
|
+
init_agent_adapters();
|
|
39215
39455
|
init_agent_discovery_directories();
|
|
39216
39456
|
init_agent_discovery_bytes();
|
|
39217
39457
|
init_plugin_projection_store();
|
|
@@ -39241,6 +39481,8 @@ var init_agent_discovery = __esm(() => {
|
|
|
39241
39481
|
CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
39242
39482
|
ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
|
|
39243
39483
|
CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
|
|
39484
|
+
CODEX_MARKETPLACE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
39485
|
+
CODEX_HOOK_EVENTS = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
|
|
39244
39486
|
});
|
|
39245
39487
|
|
|
39246
39488
|
// src/lib/authoring-path.ts
|
|
@@ -39318,7 +39560,10 @@ function configuredRoots(home, explicitHome) {
|
|
|
39318
39560
|
if (recorded && (!executable || executable.target !== recorded.target || onPath && onPath.target !== recorded.target) || !executable && (binding.builtinNames?.length || binding.agent === "gemini" && binding.sources.some((source) => basename15(source.path) === "package.json"))) {
|
|
39319
39561
|
throw new Error("Native discovery runtime target is unresolved or changed");
|
|
39320
39562
|
}
|
|
39321
|
-
|
|
39563
|
+
const managedCommand = current.commands?.[agent], managedProfile = current.profiles?.[agent];
|
|
39564
|
+
const supervisor = current.supervisors?.[agent];
|
|
39565
|
+
const managedHook = (binding.agent === "gemini" || binding.agent === "cursor") && typeof managedCommand === "string" && typeof managedProfile === "string" ? { command: managedCommand, profile: managedProfile } : binding.agent === "hermes" && typeof managedCommand === "string" && typeof managedProfile === "string" && supervisor && typeof supervisor === "object" && typeof supervisor.path === "string" && typeof supervisor.runtime === "string" && typeof supervisor.sha256 === "string" ? { command: managedCommand, profile: managedProfile, supervisor: { path: supervisor.path, runtime: supervisor.runtime, sha256: supervisor.sha256 } } : undefined;
|
|
39566
|
+
roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable, narrowed, ...managedHook ? { managedHook } : {} }));
|
|
39322
39567
|
}
|
|
39323
39568
|
};
|
|
39324
39569
|
if (homePolicy)
|
|
@@ -46326,1312 +46571,19 @@ var init_cloud_executions = __esm(() => {
|
|
|
46326
46571
|
init_fleet_credentials();
|
|
46327
46572
|
});
|
|
46328
46573
|
|
|
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;
|
|
46574
|
+
// src/lib/execution-secrets.ts
|
|
46575
|
+
import { constants as constants20, closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readSync as readSync15, realpathSync as realpathSync12 } from "fs";
|
|
46576
|
+
import { hostname as stationHostname } from "os";
|
|
46577
|
+
async function defaultSecretsClientFactory(load2 = () => import("@hasna/secrets/sdk")) {
|
|
46578
|
+
let sdk;
|
|
46865
46579
|
try {
|
|
46866
|
-
|
|
46580
|
+
sdk = await load2();
|
|
46867
46581
|
} 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;
|
|
46582
|
+
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
46583
|
}
|
|
46891
|
-
return
|
|
46584
|
+
return () => sdk.createSecretsClientFromEnv();
|
|
46892
46585
|
}
|
|
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) {
|
|
46586
|
+
function selectedSecretBindingsTemplate(scope, names, createClient) {
|
|
47635
46587
|
let secretsAuthority;
|
|
47636
46588
|
try {
|
|
47637
46589
|
secretsAuthority = createClient().baseUrl;
|
|
@@ -47701,12 +46653,13 @@ function validateSelectedSecretBindings(value, scope, names) {
|
|
|
47701
46653
|
}
|
|
47702
46654
|
return structuredClone(value);
|
|
47703
46655
|
}
|
|
47704
|
-
async function resolveSelectedSecrets(bindings, createClient
|
|
46656
|
+
async function resolveSelectedSecrets(bindings, createClient) {
|
|
47705
46657
|
if (!Object.keys(bindings.bindings).length)
|
|
47706
46658
|
return {};
|
|
46659
|
+
const factory = createClient ?? await defaultSecretsClientFactory();
|
|
47707
46660
|
let client;
|
|
47708
46661
|
try {
|
|
47709
|
-
client =
|
|
46662
|
+
client = factory();
|
|
47710
46663
|
} catch {
|
|
47711
46664
|
return refuse5("LOCAL_SECRET_UNAVAILABLE", "The configured Secrets client could not authenticate. No ambient credential fallback was attempted.");
|
|
47712
46665
|
}
|
|
@@ -47739,7 +46692,6 @@ function redactExecutionSecrets(text4, env) {
|
|
|
47739
46692
|
return text4;
|
|
47740
46693
|
}
|
|
47741
46694
|
var init_execution_secrets = __esm(() => {
|
|
47742
|
-
init_sdk();
|
|
47743
46695
|
init_selection_cache();
|
|
47744
46696
|
});
|
|
47745
46697
|
|
|
@@ -47878,7 +46830,7 @@ __export(exports_selected_run, {
|
|
|
47878
46830
|
import { createHash as createHash31 } from "crypto";
|
|
47879
46831
|
import { spawn as spawn2 } from "child_process";
|
|
47880
46832
|
import { mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync12, realpathSync as realpathSync13 } from "fs";
|
|
47881
|
-
import { dirname as dirname30, join as
|
|
46833
|
+
import { dirname as dirname30, join as join40, resolve as resolve27 } from "path";
|
|
47882
46834
|
import { tmpdir, hostname as stationHostname2 } from "os";
|
|
47883
46835
|
async function resolveSelectedRun(spec, profileId, options = {}) {
|
|
47884
46836
|
const context2 = await resolveSelectionContext(profileId, options);
|
|
@@ -47935,7 +46887,7 @@ async function prepareSelectedSecretBindings(selected, cwd = process.cwd()) {
|
|
|
47935
46887
|
const names = declaredSecretNames(manifest.runtime?.env);
|
|
47936
46888
|
if (!names.length)
|
|
47937
46889
|
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);
|
|
46890
|
+
return selectedSecretBindingsTemplate({ selection: selected.selection, profileId: selected.profileId, cwd }, names, await defaultSecretsClientFactory());
|
|
47939
46891
|
}
|
|
47940
46892
|
async function executeSelectedLocal(selected, options = {}) {
|
|
47941
46893
|
const selection2 = structuredClone(selected.selection), profileId = selected.profileId;
|
|
@@ -48001,7 +46953,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48001
46953
|
throw new SkillSelectionError("INVALID_SECRET_BINDINGS", "Explicit environment values must be bounded strings without NUL bytes.");
|
|
48002
46954
|
secretEnv[name] = value;
|
|
48003
46955
|
}
|
|
48004
|
-
const runDirectory = mkdtempSync3(
|
|
46956
|
+
const runDirectory = mkdtempSync3(join40(tmpdir(), "skills-execution-"));
|
|
48005
46957
|
const paths = new SkillEntryPaths;
|
|
48006
46958
|
for (const entry of entries) {
|
|
48007
46959
|
paths.add(entry.path, 100, () => {
|
|
@@ -48009,7 +46961,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48009
46961
|
}, () => {
|
|
48010
46962
|
throw new SkillSelectionError("INVALID_BUNDLE_PATH", "The execution bundle path exceeds its limit.");
|
|
48011
46963
|
});
|
|
48012
|
-
const destination =
|
|
46964
|
+
const destination = join40(runDirectory, entry.path);
|
|
48013
46965
|
mkdirSync13(dirname30(destination), { recursive: true, mode: 448 });
|
|
48014
46966
|
writeFileSync12(destination, entry.bytes, { flag: "wx", mode: entry.mode & 511 });
|
|
48015
46967
|
}
|
|
@@ -48037,7 +46989,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48037
46989
|
}
|
|
48038
46990
|
if (bindings)
|
|
48039
46991
|
secretEnv = await resolveSelectedSecrets(bindings, options.createSecretsClient);
|
|
48040
|
-
const result = await subprocess([executable, ...runtimeName === "bun" ? ["--no-env-file"] : [],
|
|
46992
|
+
const result = await subprocess([executable, ...runtimeName === "bun" ? ["--no-env-file"] : [], join40(runDirectory, declaredEntry), ...args], runDirectory, { ...env, ...secretEnv }, input2, timeoutMs);
|
|
48041
46993
|
result.stdout = redactExecutionSecrets(result.stdout, secretEnv);
|
|
48042
46994
|
result.stderr = redactExecutionSecrets(result.stderr, secretEnv);
|
|
48043
46995
|
const receipt = {
|
|
@@ -48051,7 +47003,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
48051
47003
|
...bindings ? { secretBinding: { schema: bindings.schema, selection: bindings.selection, consumer: bindings.consumer, secretsAuthority: bindings.secretsAuthority, bindings: bindings.bindings } } : {},
|
|
48052
47004
|
...result
|
|
48053
47005
|
};
|
|
48054
|
-
writeFileSync12(
|
|
47006
|
+
writeFileSync12(join40(runDirectory, ".execution-receipt.json"), JSON.stringify({ ...receipt, stdout: undefined, stderr: undefined }), { mode: 384 });
|
|
48055
47007
|
return receipt;
|
|
48056
47008
|
}
|
|
48057
47009
|
async function subprocess(command, cwd, env, input2, timeoutMs) {
|
|
@@ -48241,7 +47193,7 @@ var init_blog_article = __esm(() => {
|
|
|
48241
47193
|
|
|
48242
47194
|
// src/mcp/operation-tools.ts
|
|
48243
47195
|
import { existsSync as existsSync16, readdirSync as readdirSync13, statSync as statSync12 } from "fs";
|
|
48244
|
-
import { join as
|
|
47196
|
+
import { join as join41 } from "path";
|
|
48245
47197
|
function registerOperationTools(server) {
|
|
48246
47198
|
server.registerTool("cloud_skill_eligibility", {
|
|
48247
47199
|
title: "Cloud Skill Eligibility",
|
|
@@ -48723,7 +47675,7 @@ function registerOperationTools(server) {
|
|
|
48723
47675
|
if (exists) {
|
|
48724
47676
|
try {
|
|
48725
47677
|
skillCount = readdirSync13(agentSkillsPath).filter((f) => {
|
|
48726
|
-
const full =
|
|
47678
|
+
const full = join41(agentSkillsPath, f);
|
|
48727
47679
|
return !f.startsWith(".") && statSync12(full).isDirectory();
|
|
48728
47680
|
}).length;
|
|
48729
47681
|
} catch {}
|
|
@@ -48791,10 +47743,10 @@ var init_operation_tools = __esm(() => {
|
|
|
48791
47743
|
|
|
48792
47744
|
// src/lib/feedback.ts
|
|
48793
47745
|
import { appendFileSync, existsSync as existsSync17, mkdirSync as mkdirSync14 } from "fs";
|
|
48794
|
-
import { dirname as dirname31, join as
|
|
47746
|
+
import { dirname as dirname31, join as join42 } from "path";
|
|
48795
47747
|
import { Database } from "bun:sqlite";
|
|
48796
47748
|
function getFeedbackDbPath() {
|
|
48797
|
-
return
|
|
47749
|
+
return join42(getDataDir(), "skills.db");
|
|
48798
47750
|
}
|
|
48799
47751
|
function getFeedbackDb() {
|
|
48800
47752
|
const dbPath = getFeedbackDbPath();
|
|
@@ -48826,7 +47778,7 @@ function saveFeedback(input2) {
|
|
|
48826
47778
|
throw new Error("Feedback message is required");
|
|
48827
47779
|
const category = input2.category ?? "general";
|
|
48828
47780
|
if (isApiMode()) {
|
|
48829
|
-
const path =
|
|
47781
|
+
const path = join42(getDataDir(), "feedback.jsonl");
|
|
48830
47782
|
const dir = dirname31(path);
|
|
48831
47783
|
if (!existsSync17(dir))
|
|
48832
47784
|
mkdirSync14(dir, { recursive: true });
|
|
@@ -48990,23 +47942,23 @@ var init_resource_meta_tools = __esm(() => {
|
|
|
48990
47942
|
});
|
|
48991
47943
|
|
|
48992
47944
|
// src/lib/scheduler.ts
|
|
48993
|
-
import { existsSync as existsSync18, readFileSync as
|
|
48994
|
-
import { join as
|
|
47945
|
+
import { existsSync as existsSync18, readFileSync as readFileSync23, writeFileSync as writeFileSync13, mkdirSync as mkdirSync15 } from "fs";
|
|
47946
|
+
import { join as join43 } from "path";
|
|
48995
47947
|
function getSchedulesPath(targetDir = process.cwd()) {
|
|
48996
|
-
return
|
|
47948
|
+
return join43(targetDir, ".skills", "schedules.json");
|
|
48997
47949
|
}
|
|
48998
47950
|
function loadSchedules(targetDir = process.cwd()) {
|
|
48999
47951
|
const path = getSchedulesPath(targetDir);
|
|
49000
47952
|
if (existsSync18(path)) {
|
|
49001
47953
|
try {
|
|
49002
|
-
return JSON.parse(
|
|
47954
|
+
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
49003
47955
|
} catch {}
|
|
49004
47956
|
}
|
|
49005
47957
|
return { version: 1, schedules: [] };
|
|
49006
47958
|
}
|
|
49007
47959
|
function saveSchedules(data, targetDir = process.cwd()) {
|
|
49008
47960
|
const path = getSchedulesPath(targetDir);
|
|
49009
|
-
const dir =
|
|
47961
|
+
const dir = join43(targetDir, ".skills");
|
|
49010
47962
|
if (!existsSync18(dir))
|
|
49011
47963
|
mkdirSync15(dir, { recursive: true });
|
|
49012
47964
|
writeFileSync13(path, JSON.stringify(data, null, 2));
|
|
@@ -49282,12 +48234,12 @@ import { createHash as createHash32, createHmac } from "crypto";
|
|
|
49282
48234
|
import {
|
|
49283
48235
|
existsSync as existsSync19,
|
|
49284
48236
|
mkdirSync as mkdirSync16,
|
|
49285
|
-
readFileSync as
|
|
48237
|
+
readFileSync as readFileSync24,
|
|
49286
48238
|
readdirSync as readdirSync14,
|
|
49287
48239
|
statSync as statSync13,
|
|
49288
48240
|
writeFileSync as writeFileSync14
|
|
49289
48241
|
} from "fs";
|
|
49290
|
-
import { dirname as dirname32, join as
|
|
48242
|
+
import { dirname as dirname32, join as join44, normalize as normalize5, relative as relative10, sep as sep15 } from "path";
|
|
49291
48243
|
function resolveSkillsNativeStorageConfig(env = process.env) {
|
|
49292
48244
|
assertNoRetiredModeEnvVars(env, {
|
|
49293
48245
|
app: SKILLS_ENV_NAMESPACE,
|
|
@@ -49328,7 +48280,7 @@ function getSkillsNativeStorageStatus(options = {}) {
|
|
|
49328
48280
|
local: {
|
|
49329
48281
|
dataDir: getDataDir(),
|
|
49330
48282
|
projectStateDir: getProjectStateDir(targetDir),
|
|
49331
|
-
feedbackDbPath:
|
|
48283
|
+
feedbackDbPath: join44(getDataDir(), "skills.db")
|
|
49332
48284
|
},
|
|
49333
48285
|
remote: {
|
|
49334
48286
|
databaseConfigured: Boolean(config2.databaseUrl),
|
|
@@ -49350,7 +48302,7 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
|
|
|
49350
48302
|
const files = [];
|
|
49351
48303
|
if (existsSync19(projectStateDir)) {
|
|
49352
48304
|
for (const filePath of walkFiles2(projectStateDir)) {
|
|
49353
|
-
const bytes =
|
|
48305
|
+
const bytes = readFileSync24(filePath);
|
|
49354
48306
|
const relativePath = toPosix(relative10(targetDir, filePath));
|
|
49355
48307
|
files.push({
|
|
49356
48308
|
path: relativePath,
|
|
@@ -49409,7 +48361,7 @@ function parsePositiveInteger(value) {
|
|
|
49409
48361
|
function walkFiles2(dir) {
|
|
49410
48362
|
const files = [];
|
|
49411
48363
|
for (const entry of readdirSync14(dir)) {
|
|
49412
|
-
const full =
|
|
48364
|
+
const full = join44(dir, entry);
|
|
49413
48365
|
const stats = statSync13(full);
|
|
49414
48366
|
if (stats.isDirectory())
|
|
49415
48367
|
files.push(...walkFiles2(full));
|
|
@@ -49678,11 +48630,11 @@ var init_private_publication_customer = __esm(() => {
|
|
|
49678
48630
|
});
|
|
49679
48631
|
|
|
49680
48632
|
// src/lib/private-publication-recovery.ts
|
|
49681
|
-
import { constants as constants21, closeSync as
|
|
49682
|
-
import { dirname as dirname33, isAbsolute as
|
|
48633
|
+
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";
|
|
48634
|
+
import { dirname as dirname33, isAbsolute as isAbsolute28, join as join45, resolve as resolve28 } from "path";
|
|
49683
48635
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
49684
48636
|
function safeDirectory(directory2) {
|
|
49685
|
-
if (!
|
|
48637
|
+
if (!isAbsolute28(directory2) || directory2 !== resolve28(directory2) || realpathSync14(directory2) !== directory2)
|
|
49686
48638
|
return fail2();
|
|
49687
48639
|
for (let path = directory2;; path = dirname33(path)) {
|
|
49688
48640
|
const stat3 = lstatSync29(path);
|
|
@@ -49702,10 +48654,10 @@ function unchangedDirectory(directory2, identity4) {
|
|
|
49702
48654
|
return fail2();
|
|
49703
48655
|
}
|
|
49704
48656
|
function readOwned(directory2, name, max) {
|
|
49705
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
49706
|
-
const fd =
|
|
48657
|
+
const identity4 = safeDirectory(directory2), file2 = join45(directory2, name);
|
|
48658
|
+
const fd = openSync23(file2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49707
48659
|
try {
|
|
49708
|
-
const stat3 =
|
|
48660
|
+
const stat3 = fstatSync23(fd);
|
|
49709
48661
|
if (!stat3.isFile() || stat3.nlink !== 1 || stat3.size > max || stat3.size < 1 || (stat3.mode & 63) !== 0 || process.getuid && stat3.uid !== process.getuid())
|
|
49710
48662
|
return fail2();
|
|
49711
48663
|
const buffer = Buffer.alloc(Math.min(max, stat3.size) + 1);
|
|
@@ -49716,22 +48668,22 @@ function readOwned(directory2, name, max) {
|
|
|
49716
48668
|
break;
|
|
49717
48669
|
length += count;
|
|
49718
48670
|
}
|
|
49719
|
-
const bytes = buffer.subarray(0, length), after =
|
|
48671
|
+
const bytes = buffer.subarray(0, length), after = fstatSync23(fd);
|
|
49720
48672
|
unchangedDirectory(directory2, identity4);
|
|
49721
48673
|
if (bytes.length !== stat3.size || stat3.size !== after.size || stat3.mtimeMs !== after.mtimeMs || stat3.ctimeMs !== after.ctimeMs)
|
|
49722
48674
|
return fail2();
|
|
49723
48675
|
return bytes;
|
|
49724
48676
|
} finally {
|
|
49725
|
-
|
|
48677
|
+
closeSync23(fd);
|
|
49726
48678
|
}
|
|
49727
48679
|
}
|
|
49728
48680
|
function writeOwned(directory2, name, bytes) {
|
|
49729
|
-
const fd =
|
|
48681
|
+
const fd = openSync23(join45(directory2, name), constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
49730
48682
|
try {
|
|
49731
48683
|
writeFileSync15(fd, bytes);
|
|
49732
48684
|
fsyncSync6(fd);
|
|
49733
48685
|
} finally {
|
|
49734
|
-
|
|
48686
|
+
closeSync23(fd);
|
|
49735
48687
|
}
|
|
49736
48688
|
}
|
|
49737
48689
|
function save(directory2, value) {
|
|
@@ -49739,12 +48691,12 @@ function save(directory2, value) {
|
|
|
49739
48691
|
writeOwned(directory2, temporary, JSON.stringify(value) + `
|
|
49740
48692
|
`);
|
|
49741
48693
|
unchangedDirectory(directory2, identity4);
|
|
49742
|
-
renameSync8(
|
|
49743
|
-
const fd =
|
|
48694
|
+
renameSync8(join45(directory2, temporary), join45(directory2, "receipt.json"));
|
|
48695
|
+
const fd = openSync23(directory2, constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49744
48696
|
try {
|
|
49745
48697
|
fsyncSync6(fd);
|
|
49746
48698
|
} finally {
|
|
49747
|
-
|
|
48699
|
+
closeSync23(fd);
|
|
49748
48700
|
}
|
|
49749
48701
|
}
|
|
49750
48702
|
function bind(client, receipt) {
|
|
@@ -49752,21 +48704,21 @@ function bind(client, receipt) {
|
|
|
49752
48704
|
throw new PrivatePublicationError("PUBLICATION_IDENTITY_CHANGED", "The fresh session does not match the recovery directory's server, account and membership.");
|
|
49753
48705
|
}
|
|
49754
48706
|
async function locked(directory2, action) {
|
|
49755
|
-
const identity4 = safeDirectory(directory2), file2 =
|
|
48707
|
+
const identity4 = safeDirectory(directory2), file2 = join45(directory2, "operation.lock");
|
|
49756
48708
|
let fd;
|
|
49757
48709
|
try {
|
|
49758
|
-
fd =
|
|
48710
|
+
fd = openSync23(file2, constants21.O_WRONLY | constants21.O_CREAT | constants21.O_EXCL | constants21.O_NOFOLLOW, 384);
|
|
49759
48711
|
} catch {
|
|
49760
48712
|
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
48713
|
}
|
|
49762
|
-
const lock =
|
|
48714
|
+
const lock = fstatSync23(fd);
|
|
49763
48715
|
try {
|
|
49764
48716
|
writeFileSync15(fd, JSON.stringify({ pid: process.pid }) + `
|
|
49765
48717
|
`);
|
|
49766
48718
|
fsyncSync6(fd);
|
|
49767
48719
|
return await action();
|
|
49768
48720
|
} finally {
|
|
49769
|
-
|
|
48721
|
+
closeSync23(fd);
|
|
49770
48722
|
unchangedDirectory(directory2, identity4);
|
|
49771
48723
|
const now = lstatSync29(file2);
|
|
49772
48724
|
if (now.dev !== lock.dev || now.ino !== lock.ino || !now.isFile() || now.isSymbolicLink())
|
|
@@ -49820,15 +48772,15 @@ async function preparePrivatePublication(client, sourceDirectory, recoveryDirect
|
|
|
49820
48772
|
phase: "prepared",
|
|
49821
48773
|
intent: null
|
|
49822
48774
|
};
|
|
49823
|
-
if (!
|
|
48775
|
+
if (!isAbsolute28(recoveryDirectory) || resolve28(recoveryDirectory) !== recoveryDirectory || realpathSync14(dirname33(recoveryDirectory)) !== dirname33(recoveryDirectory))
|
|
49824
48776
|
return fail2();
|
|
49825
48777
|
mkdirSync17(recoveryDirectory, { mode: 448 });
|
|
49826
48778
|
safeDirectory(recoveryDirectory);
|
|
49827
|
-
const parent =
|
|
48779
|
+
const parent = openSync23(dirname33(recoveryDirectory), constants21.O_RDONLY | constants21.O_NOFOLLOW);
|
|
49828
48780
|
try {
|
|
49829
48781
|
fsyncSync6(parent);
|
|
49830
48782
|
} finally {
|
|
49831
|
-
|
|
48783
|
+
closeSync23(parent);
|
|
49832
48784
|
}
|
|
49833
48785
|
writeOwned(recoveryDirectory, "bundle.tgz", packed.bytes);
|
|
49834
48786
|
save(recoveryDirectory, receipt);
|
|
@@ -49921,10 +48873,10 @@ var init_private_publication_recovery = __esm(() => {
|
|
|
49921
48873
|
});
|
|
49922
48874
|
|
|
49923
48875
|
// src/mcp/private-publication-tools.ts
|
|
49924
|
-
import { isAbsolute as
|
|
48876
|
+
import { isAbsolute as isAbsolute29 } from "path";
|
|
49925
48877
|
function registerPrivatePublicationTools(server) {
|
|
49926
48878
|
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(
|
|
48879
|
+
const directory2 = exports_external.string().max(4096).refine(isAbsolute29, "Use an absolute local directory");
|
|
49928
48880
|
const verification2 = { email: exports_external.string().email().max(254), code: exports_external.string().regex(/^\d{6}$/), userId: uuid9, membershipId: uuid9, recoveryDirectory: directory2 };
|
|
49929
48881
|
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
48882
|
const handler = (action) => async (value) => {
|