@agentproto/secrets 0.1.0-alpha.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/bin/agentproto-secrets.mjs +16 -3
- package/dist/cli.d.ts +47 -1
- package/dist/cli.mjs +27 -16
- package/dist/cli.mjs.map +1 -1
- package/dist/exposure/index.d.ts +35 -2
- package/dist/exposure/index.mjs +14 -1
- package/dist/exposure/index.mjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/manifest/index.d.ts +2 -2
- package/dist/{types-Clav8IDA.d.ts → types-CHQpxFPe.d.ts} +1 -1
- package/dist/{types-C2dZDHn7.d.ts → types-DS9Qe9Cv.d.ts} +18 -6
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -18,6 +18,58 @@ const x = defineSecrets({
|
|
|
18
18
|
})
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Exposure surface (`@agentproto/secrets/exposure`)
|
|
22
|
+
|
|
23
|
+
The manifest says *which* secrets exist; an **exposure** says *how* a resolved
|
|
24
|
+
secret reaches a runtime. `SecretExposure` is a discriminated union — one
|
|
25
|
+
`kind` per delivery mechanism, so consumers filter with the `isExposureKind`
|
|
26
|
+
guard and handle each with one switch case:
|
|
27
|
+
|
|
28
|
+
| `kind` | Delivers the secret as… |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `env` | an environment variable on the agent process |
|
|
31
|
+
| `file` | a file written into the workspace |
|
|
32
|
+
| `egress-substitute` | a `$$SECRET[NAME]$$` placeholder substituted on egress |
|
|
33
|
+
| `mcp-header` | an HTTP **auth header** on an MCP server's transport |
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { isExposureKind, type SecretExposure } from "@agentproto/secrets/exposure"
|
|
37
|
+
|
|
38
|
+
const headerExposures = exposures.filter(e => isExposureKind(e, "mcp-header"))
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `mcp-header` — brokered MCP auth
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface McpHeaderExposure {
|
|
45
|
+
kind: "mcp-header"
|
|
46
|
+
credentialPath: string // broker path: "<providerId>" or "<providerId>/<account>"
|
|
47
|
+
server?: string // optional upstream override, forwarded to the broker
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`resolveMcpHeaderExposure(exposure, resolver, { signal })` turns that path into
|
|
52
|
+
a ready-to-use header map. The `resolver` is a **structural**
|
|
53
|
+
`McpHeaderResolver` (`resolveHeaders({ path, server?, signal? })`) — the
|
|
54
|
+
`CredentialBroker` from `@agentproto/auth` satisfies it with no adapter, so
|
|
55
|
+
this package stays **dependency-free of `auth`** while still driving it.
|
|
56
|
+
|
|
57
|
+
Every resolved header **value** is run through `assertSafeSecretValue` before
|
|
58
|
+
return — a value containing CR/LF/NUL is rejected, closing off header-injection
|
|
59
|
+
(a smuggled newline can't append extra header lines onto the transport).
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { resolveMcpHeaderExposure } from "@agentproto/secrets/exposure"
|
|
63
|
+
import { CredentialBroker } from "@agentproto/auth"
|
|
64
|
+
|
|
65
|
+
const headers = await resolveMcpHeaderExposure(exposure, broker, { signal })
|
|
66
|
+
// → { Authorization: "Bearer …" }, guarded
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The same guard (`assertSafeSecretValue`) and the `$$SECRET[NAME]$$` engine
|
|
70
|
+
(`substituteSecrets`, `SECRET_PLACEHOLDER_PATTERN`, `formatPlaceholder`) are
|
|
71
|
+
exported from this subpath for the `egress-substitute` path.
|
|
72
|
+
|
|
21
73
|
## License
|
|
22
74
|
|
|
23
75
|
MIT — see [LICENSE](./LICENSE).
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Thin launcher — the built CLI
|
|
3
|
-
// lives on a checked-in file rather than fighting the bundler's
|
|
4
|
-
import
|
|
2
|
+
// Thin launcher — invokes the built CLI's exported `main`. Kept separate so
|
|
3
|
+
// the shebang lives on a checked-in file rather than fighting the bundler's
|
|
4
|
+
// per-entry output. `main` is exported (not run at import time) so tests can
|
|
5
|
+
// import `cli.ts`'s helpers without triggering a real CLI invocation.
|
|
6
|
+
import { main } from "../dist/cli.mjs"
|
|
7
|
+
|
|
8
|
+
main()
|
|
9
|
+
.then(code => {
|
|
10
|
+
process.exitCode = code
|
|
11
|
+
})
|
|
12
|
+
.catch(err => {
|
|
13
|
+
process.stderr.write(
|
|
14
|
+
`agentproto-secrets: ${err instanceof Error ? err.message : String(err)}\n`
|
|
15
|
+
)
|
|
16
|
+
process.exitCode = 1
|
|
17
|
+
})
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentproto-secrets — a small CLI over the seal primitive + provision flow.
|
|
3
|
+
*
|
|
4
|
+
* agentproto-secrets keygen mint a sealing keypair (JSON)
|
|
5
|
+
* agentproto-secrets seal --pubkey <b64> [src] seal a value -> sealed blob
|
|
6
|
+
* agentproto-secrets unseal --privkey <b64> [blob] open a sealed blob -> plaintext
|
|
7
|
+
* agentproto-secrets provision --provider P --method M \
|
|
8
|
+
* --seal-key-url URL --install-url URL [--header 'K: V']... [src]
|
|
9
|
+
* seal a local credential and
|
|
10
|
+
* install it into a vault,
|
|
11
|
+
* carrying only ciphertext
|
|
12
|
+
*
|
|
13
|
+
* credential source [src]: --from-file <path> [--json-path a.b.c] | --from-env <VAR>
|
|
14
|
+
*
|
|
15
|
+
* Vendor-neutral: `provision` takes the server's URLs + auth headers as flags.
|
|
16
|
+
* The plaintext credential is read, sealed, and sent — it is never printed.
|
|
17
|
+
* When `--header` is omitted and `--provider` names an auth provider
|
|
18
|
+
* registered in `@agentproto/auth`, the Authorization header is instead
|
|
19
|
+
* resolved via `CredentialBroker` (this is the only file in this package
|
|
20
|
+
* that imports `@agentproto/auth` — see `ProvisionAuthDeps` below).
|
|
21
|
+
*/
|
|
22
|
+
/** Structural shape `CredentialBroker` satisfies — kept local (rather than
|
|
23
|
+
* importing the class as a type) so tests can inject a fake without touching
|
|
24
|
+
* Keychain or the network. */
|
|
25
|
+
interface AuthHeaderResolver {
|
|
26
|
+
resolveHeaders(o: {
|
|
27
|
+
path: string;
|
|
28
|
+
audience?: string;
|
|
29
|
+
server?: string;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}): Promise<Record<string, string>>;
|
|
32
|
+
}
|
|
33
|
+
interface ProvisionAuthDeps {
|
|
34
|
+
/** Whether `providerId` is a known `@agentproto/auth` auth provider. */
|
|
35
|
+
isRegistered(providerId: string): boolean;
|
|
36
|
+
resolver: AuthHeaderResolver;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the Authorization (etc.) headers a provision request carries.
|
|
40
|
+
* `--header` is an explicit override — when present, the broker is never
|
|
41
|
+
* consulted. Otherwise, a `--provider` registered in `@agentproto/auth`
|
|
42
|
+
* resolves its headers via the broker; an unregistered provider degrades to
|
|
43
|
+
* `explicitHeaders` (empty when none were passed) rather than failing.
|
|
44
|
+
*/
|
|
45
|
+
declare function resolveProvisionHeaders(provider: string, explicitHeaders: string[], deps?: ProvisionAuthDeps): Promise<Record<string, string>>;
|
|
46
|
+
declare function main(): Promise<number>;
|
|
1
47
|
|
|
2
|
-
export {
|
|
48
|
+
export { type AuthHeaderResolver, type ProvisionAuthDeps, main, resolveProvisionHeaders };
|
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { listRecipeIds, resolveRecipeMethod, resolveSourceSpec } from './chunk-NEGXQWE5.mjs';
|
|
2
2
|
import { httpTarget, provisionSealed, resolveCredential } from './chunk-NLZ5HXGO.mjs';
|
|
3
3
|
import { unseal, seal, generateSealKeyPair, sealKeyId } from './chunk-MVHOJPML.mjs';
|
|
4
|
+
import { CredentialBroker, getAuthProvider, KeychainStore } from '@agentproto/auth';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @agentproto/secrets v0.1.0-alpha
|
|
7
8
|
* AIP-19 SECRETS.md `defineSecrets` reference implementation.
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
// src/cli.ts
|
|
11
10
|
function parse(argv) {
|
|
12
11
|
const positionals = [];
|
|
13
12
|
const flags = {};
|
|
@@ -90,6 +89,23 @@ function parseHeaders(raw) {
|
|
|
90
89
|
}
|
|
91
90
|
return out;
|
|
92
91
|
}
|
|
92
|
+
function defaultProvisionAuthDeps() {
|
|
93
|
+
return {
|
|
94
|
+
isRegistered: (id) => getAuthProvider(id) !== void 0,
|
|
95
|
+
resolver: new CredentialBroker({
|
|
96
|
+
store: new KeychainStore(),
|
|
97
|
+
getProvider: getAuthProvider
|
|
98
|
+
})
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function resolveProvisionHeaders(provider, explicitHeaders, deps) {
|
|
102
|
+
const parsed = parseHeaders(explicitHeaders);
|
|
103
|
+
if (explicitHeaders.length > 0) return parsed;
|
|
104
|
+
const { isRegistered, resolver } = deps ?? defaultProvisionAuthDeps();
|
|
105
|
+
if (!isRegistered(provider)) return parsed;
|
|
106
|
+
const brokered = await resolver.resolveHeaders({ path: provider, audience: "api" });
|
|
107
|
+
return { ...brokered, ...parsed };
|
|
108
|
+
}
|
|
93
109
|
var USAGE = `agentproto-secrets \u2014 seal secrets and install them into a vault
|
|
94
110
|
|
|
95
111
|
keygen mint a sealing keypair (JSON to stdout)
|
|
@@ -150,11 +166,13 @@ async function main() {
|
|
|
150
166
|
} catch (err) {
|
|
151
167
|
return fail(err instanceof Error ? err.message : String(err));
|
|
152
168
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
let headers;
|
|
170
|
+
try {
|
|
171
|
+
headers = await resolveProvisionHeaders(provider, args.headers);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
174
|
+
}
|
|
175
|
+
const target = httpTarget({ sealKeyUrl, installUrl, headers });
|
|
158
176
|
try {
|
|
159
177
|
const out = await provisionSealed({
|
|
160
178
|
target,
|
|
@@ -190,14 +208,7 @@ function fail(message) {
|
|
|
190
208
|
`);
|
|
191
209
|
return 1;
|
|
192
210
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}).catch((err) => {
|
|
196
|
-
process.stderr.write(
|
|
197
|
-
`agentproto-secrets: ${err instanceof Error ? err.message : String(err)}
|
|
198
|
-
`
|
|
199
|
-
);
|
|
200
|
-
process.exitCode = 1;
|
|
201
|
-
});
|
|
211
|
+
|
|
212
|
+
export { main, resolveProvisionHeaders };
|
|
202
213
|
//# sourceMappingURL=cli.mjs.map
|
|
203
214
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["/**\n * agentproto-secrets — a small CLI over the seal primitive + provision flow.\n *\n * agentproto-secrets keygen mint a sealing keypair (JSON)\n * agentproto-secrets seal --pubkey <b64> [src] seal a value -> sealed blob\n * agentproto-secrets unseal --privkey <b64> [blob] open a sealed blob -> plaintext\n * agentproto-secrets provision --provider P --method M \\\n * --seal-key-url URL --install-url URL [--header 'K: V']... [src]\n * seal a local credential and\n * install it into a vault,\n * carrying only ciphertext\n *\n * credential source [src]: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n *\n * Vendor-neutral: `provision` takes the server's URLs + auth headers as flags.\n * The plaintext credential is read, sealed, and sent — it is never printed.\n */\n\nimport {\n generateSealKeyPair,\n sealKeyId,\n seal,\n unseal,\n} from \"./seal/index.js\"\nimport {\n provisionSealed,\n httpTarget,\n resolveCredential,\n type CredentialSource,\n} from \"./provision/index.js\"\nimport {\n resolveRecipeMethod,\n resolveSourceSpec,\n listRecipeIds,\n} from \"./provision/recipe/index.js\"\n\ninterface Args {\n positionals: string[]\n flags: Record<string, string>\n headers: string[]\n}\n\nfunction parse(argv: string[]): Args {\n const positionals: string[] = []\n const flags: Record<string, string> = {}\n const headers: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a.startsWith(\"--\")) {\n const eq = a.indexOf(\"=\")\n let key: string\n let val: string\n if (eq !== -1) {\n key = a.slice(2, eq)\n val = a.slice(eq + 1)\n } else {\n key = a.slice(2)\n const next = argv[i + 1]\n if (next !== undefined && !next.startsWith(\"--\")) {\n val = next\n i++\n } else {\n val = \"true\"\n }\n }\n if (key === \"header\") headers.push(val)\n else flags[key] = val\n } else {\n positionals.push(a)\n }\n }\n return { positionals, flags, headers }\n}\n\nfunction credentialSource(args: Args): CredentialSource {\n return {\n ...(args.flags[\"from-env\"] ? { fromEnv: args.flags[\"from-env\"] } : {}),\n ...(args.flags[\"from-file\"] ? { fromFile: args.flags[\"from-file\"] } : {}),\n ...(args.flags[\"json-path\"] ? { jsonPath: args.flags[\"json-path\"] } : {}),\n }\n}\n\n/** Read a value for seal: a positional, or a credential source. */\nasync function readValue(args: Args): Promise<string> {\n if (args.positionals[0]) return args.positionals[0]\n return resolveCredential(credentialSource(args))\n}\n\n/** Read one line from stdin (for a recipe's `prompt` credential source). */\nasync function promptStdin(prompt: string): Promise<string> {\n const { createInterface } = await import(\"node:readline/promises\")\n const rl = createInterface({ input: process.stdin, output: process.stderr })\n try {\n return (await rl.question(`${prompt}: `)).trim()\n } finally {\n rl.close()\n }\n}\n\n/**\n * Resolve the {methodId, credential, label} a provision installs. A recipe\n * (`--provider <id>`) supplies the auth-method id + where the credential lives,\n * so no source flags are needed; explicit `--from-file/--from-env` override the\n * recipe for ad-hoc / unknown providers (then `--method` is required).\n */\nasync function resolveProvision(\n args: Args,\n): Promise<{ methodId: string; credential: string; label?: string }> {\n const provider = args.flags.provider!\n const explicit = args.flags[\"from-file\"] || args.flags[\"from-env\"]\n if (explicit) {\n const methodId = args.flags.method\n if (!methodId)\n throw new Error(\n \"--method is required when passing an explicit --from-file/--from-env source\",\n )\n return {\n methodId,\n credential: await resolveCredential(credentialSource(args)),\n ...(args.flags.label ? { label: args.flags.label } : {}),\n }\n }\n const { recipe, method } = resolveRecipeMethod(provider, args.flags.method)\n const credential = await resolveSourceSpec(method.source, {\n promptImpl: promptStdin,\n })\n const label = args.flags.label ?? method.label ?? recipe.label\n return { methodId: method.id, credential, ...(label ? { label } : {}) }\n}\n\nfunction parseHeaders(raw: string[]): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of raw) {\n const idx = h.indexOf(\":\")\n if (idx === -1) continue\n out[h.slice(0, idx).trim()] = h.slice(idx + 1).trim()\n }\n return out\n}\n\nconst USAGE = `agentproto-secrets — seal secrets and install them into a vault\n\n keygen mint a sealing keypair (JSON to stdout)\n seal --pubkey <b64> [value|src] seal a value -> sealed blob (stdout)\n unseal --privkey <b64> [blob|src] open a sealed blob -> plaintext (stdout)\n provision --provider P --seal-key-url URL --install-url URL \\\\\n [--method M] [--header 'K: V']... [src]\n seal a local credential and install it,\n carrying only ciphertext. A known\n --provider resolves its method + source\n from a builtin recipe; --method picks a\n flavor; an explicit src overrides.\n\n src: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n builtin providers: ${listRecipeIds().join(\", \")}`\n\nasync function main(): Promise<number> {\n const [cmd, ...rest] = process.argv.slice(2)\n const args = parse(rest)\n\n switch (cmd) {\n case \"keygen\": {\n const kp = generateSealKeyPair()\n process.stdout.write(\n JSON.stringify(\n { keyId: sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },\n null,\n 2\n ) + \"\\n\"\n )\n return 0\n }\n\n case \"seal\": {\n const pubkey = args.flags.pubkey\n if (!pubkey) return fail(\"seal: --pubkey <b64> required\")\n const value = await readValue(args)\n process.stdout.write(seal(value, pubkey))\n return 0\n }\n\n case \"unseal\": {\n const privkey = args.flags.privkey\n if (!privkey) return fail(\"unseal: --privkey <b64> required\")\n const blob =\n args.positionals[0] ?? (await resolveCredential(credentialSource(args)))\n process.stdout.write(unseal(blob, privkey))\n return 0\n }\n\n case \"provision\": {\n const provider = args.flags.provider\n const sealKeyUrl = args.flags[\"seal-key-url\"]\n const installUrl = args.flags[\"install-url\"]\n if (!provider) return fail(\"provision: --provider required\")\n if (!sealKeyUrl || !installUrl)\n return fail(\"provision: --seal-key-url and --install-url required\")\n\n let methodId: string\n let credential: string\n let label: string | undefined\n try {\n ;({ methodId, credential, label } = await resolveProvision(args))\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n const target = httpTarget({\n sealKeyUrl,\n installUrl,\n headers: parseHeaders(args.headers),\n })\n try {\n const out = await provisionSealed({\n target,\n provider,\n methodId,\n credential,\n ...(label ? { label } : {}),\n })\n process.stdout.write(\n `sealed + installed ${provider} (${methodId})` +\n (out.secretId ? ` — secret ${out.secretId.slice(0, 8)}` : \"\") +\n ` [key ${out.keyId.slice(0, 8)}]\\n` +\n `the plaintext never left this machine; the server unsealed it.\\n`\n )\n return 0\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n }\n\n case \"-h\":\n case \"--help\":\n case \"help\":\n case undefined:\n process.stdout.write(USAGE + \"\\n\")\n return cmd ? 0 : 1\n\n default:\n return fail(`unknown command '${cmd}'\\n\\n${USAGE}`)\n }\n}\n\nfunction fail(message: string): number {\n process.stderr.write(`agentproto-secrets: ${message}\\n`)\n return 1\n}\n\nmain()\n .then(code => {\n process.exitCode = code\n })\n .catch(err => {\n process.stderr.write(\n `agentproto-secrets: ${err instanceof Error ? err.message : String(err)}\\n`\n )\n process.exitCode = 1\n })\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.mjs","sourcesContent":["/**\n * agentproto-secrets — a small CLI over the seal primitive + provision flow.\n *\n * agentproto-secrets keygen mint a sealing keypair (JSON)\n * agentproto-secrets seal --pubkey <b64> [src] seal a value -> sealed blob\n * agentproto-secrets unseal --privkey <b64> [blob] open a sealed blob -> plaintext\n * agentproto-secrets provision --provider P --method M \\\n * --seal-key-url URL --install-url URL [--header 'K: V']... [src]\n * seal a local credential and\n * install it into a vault,\n * carrying only ciphertext\n *\n * credential source [src]: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n *\n * Vendor-neutral: `provision` takes the server's URLs + auth headers as flags.\n * The plaintext credential is read, sealed, and sent — it is never printed.\n * When `--header` is omitted and `--provider` names an auth provider\n * registered in `@agentproto/auth`, the Authorization header is instead\n * resolved via `CredentialBroker` (this is the only file in this package\n * that imports `@agentproto/auth` — see `ProvisionAuthDeps` below).\n */\n\nimport { CredentialBroker, KeychainStore, getAuthProvider } from \"@agentproto/auth\"\nimport {\n generateSealKeyPair,\n sealKeyId,\n seal,\n unseal,\n} from \"./seal/index.js\"\nimport {\n provisionSealed,\n httpTarget,\n resolveCredential,\n type CredentialSource,\n} from \"./provision/index.js\"\nimport {\n resolveRecipeMethod,\n resolveSourceSpec,\n listRecipeIds,\n} from \"./provision/recipe/index.js\"\n\ninterface Args {\n positionals: string[]\n flags: Record<string, string>\n headers: string[]\n}\n\nfunction parse(argv: string[]): Args {\n const positionals: string[] = []\n const flags: Record<string, string> = {}\n const headers: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a.startsWith(\"--\")) {\n const eq = a.indexOf(\"=\")\n let key: string\n let val: string\n if (eq !== -1) {\n key = a.slice(2, eq)\n val = a.slice(eq + 1)\n } else {\n key = a.slice(2)\n const next = argv[i + 1]\n if (next !== undefined && !next.startsWith(\"--\")) {\n val = next\n i++\n } else {\n val = \"true\"\n }\n }\n if (key === \"header\") headers.push(val)\n else flags[key] = val\n } else {\n positionals.push(a)\n }\n }\n return { positionals, flags, headers }\n}\n\nfunction credentialSource(args: Args): CredentialSource {\n return {\n ...(args.flags[\"from-env\"] ? { fromEnv: args.flags[\"from-env\"] } : {}),\n ...(args.flags[\"from-file\"] ? { fromFile: args.flags[\"from-file\"] } : {}),\n ...(args.flags[\"json-path\"] ? { jsonPath: args.flags[\"json-path\"] } : {}),\n }\n}\n\n/** Read a value for seal: a positional, or a credential source. */\nasync function readValue(args: Args): Promise<string> {\n if (args.positionals[0]) return args.positionals[0]\n return resolveCredential(credentialSource(args))\n}\n\n/** Read one line from stdin (for a recipe's `prompt` credential source). */\nasync function promptStdin(prompt: string): Promise<string> {\n const { createInterface } = await import(\"node:readline/promises\")\n const rl = createInterface({ input: process.stdin, output: process.stderr })\n try {\n return (await rl.question(`${prompt}: `)).trim()\n } finally {\n rl.close()\n }\n}\n\n/**\n * Resolve the {methodId, credential, label} a provision installs. A recipe\n * (`--provider <id>`) supplies the auth-method id + where the credential lives,\n * so no source flags are needed; explicit `--from-file/--from-env` override the\n * recipe for ad-hoc / unknown providers (then `--method` is required).\n */\nasync function resolveProvision(\n args: Args,\n): Promise<{ methodId: string; credential: string; label?: string }> {\n const provider = args.flags.provider!\n const explicit = args.flags[\"from-file\"] || args.flags[\"from-env\"]\n if (explicit) {\n const methodId = args.flags.method\n if (!methodId)\n throw new Error(\n \"--method is required when passing an explicit --from-file/--from-env source\",\n )\n return {\n methodId,\n credential: await resolveCredential(credentialSource(args)),\n ...(args.flags.label ? { label: args.flags.label } : {}),\n }\n }\n const { recipe, method } = resolveRecipeMethod(provider, args.flags.method)\n const credential = await resolveSourceSpec(method.source, {\n promptImpl: promptStdin,\n })\n const label = args.flags.label ?? method.label ?? recipe.label\n return { methodId: method.id, credential, ...(label ? { label } : {}) }\n}\n\nfunction parseHeaders(raw: string[]): Record<string, string> {\n const out: Record<string, string> = {}\n for (const h of raw) {\n const idx = h.indexOf(\":\")\n if (idx === -1) continue\n out[h.slice(0, idx).trim()] = h.slice(idx + 1).trim()\n }\n return out\n}\n\n/** Structural shape `CredentialBroker` satisfies — kept local (rather than\n * importing the class as a type) so tests can inject a fake without touching\n * Keychain or the network. */\nexport interface AuthHeaderResolver {\n resolveHeaders(o: {\n path: string\n audience?: string\n server?: string\n signal?: AbortSignal\n }): Promise<Record<string, string>>\n}\n\nexport interface ProvisionAuthDeps {\n /** Whether `providerId` is a known `@agentproto/auth` auth provider. */\n isRegistered(providerId: string): boolean\n resolver: AuthHeaderResolver\n}\n\nfunction defaultProvisionAuthDeps(): ProvisionAuthDeps {\n return {\n isRegistered: id => getAuthProvider(id) !== undefined,\n resolver: new CredentialBroker({\n store: new KeychainStore(),\n getProvider: getAuthProvider,\n }),\n }\n}\n\n/**\n * Resolve the Authorization (etc.) headers a provision request carries.\n * `--header` is an explicit override — when present, the broker is never\n * consulted. Otherwise, a `--provider` registered in `@agentproto/auth`\n * resolves its headers via the broker; an unregistered provider degrades to\n * `explicitHeaders` (empty when none were passed) rather than failing.\n */\nexport async function resolveProvisionHeaders(\n provider: string,\n explicitHeaders: string[],\n deps?: ProvisionAuthDeps,\n): Promise<Record<string, string>> {\n const parsed = parseHeaders(explicitHeaders)\n if (explicitHeaders.length > 0) return parsed\n const { isRegistered, resolver } = deps ?? defaultProvisionAuthDeps()\n if (!isRegistered(provider)) return parsed\n const brokered = await resolver.resolveHeaders({ path: provider, audience: \"api\" })\n return { ...brokered, ...parsed }\n}\n\nconst USAGE = `agentproto-secrets — seal secrets and install them into a vault\n\n keygen mint a sealing keypair (JSON to stdout)\n seal --pubkey <b64> [value|src] seal a value -> sealed blob (stdout)\n unseal --privkey <b64> [blob|src] open a sealed blob -> plaintext (stdout)\n provision --provider P --seal-key-url URL --install-url URL \\\\\n [--method M] [--header 'K: V']... [src]\n seal a local credential and install it,\n carrying only ciphertext. A known\n --provider resolves its method + source\n from a builtin recipe; --method picks a\n flavor; an explicit src overrides.\n\n src: --from-file <path> [--json-path a.b.c] | --from-env <VAR>\n builtin providers: ${listRecipeIds().join(\", \")}`\n\nexport async function main(): Promise<number> {\n const [cmd, ...rest] = process.argv.slice(2)\n const args = parse(rest)\n\n switch (cmd) {\n case \"keygen\": {\n const kp = generateSealKeyPair()\n process.stdout.write(\n JSON.stringify(\n { keyId: sealKeyId(kp.publicKey), publicKey: kp.publicKey, privateKey: kp.privateKey },\n null,\n 2\n ) + \"\\n\"\n )\n return 0\n }\n\n case \"seal\": {\n const pubkey = args.flags.pubkey\n if (!pubkey) return fail(\"seal: --pubkey <b64> required\")\n const value = await readValue(args)\n process.stdout.write(seal(value, pubkey))\n return 0\n }\n\n case \"unseal\": {\n const privkey = args.flags.privkey\n if (!privkey) return fail(\"unseal: --privkey <b64> required\")\n const blob =\n args.positionals[0] ?? (await resolveCredential(credentialSource(args)))\n process.stdout.write(unseal(blob, privkey))\n return 0\n }\n\n case \"provision\": {\n const provider = args.flags.provider\n const sealKeyUrl = args.flags[\"seal-key-url\"]\n const installUrl = args.flags[\"install-url\"]\n if (!provider) return fail(\"provision: --provider required\")\n if (!sealKeyUrl || !installUrl)\n return fail(\"provision: --seal-key-url and --install-url required\")\n\n let methodId: string\n let credential: string\n let label: string | undefined\n try {\n ;({ methodId, credential, label } = await resolveProvision(args))\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n let headers: Record<string, string>\n try {\n headers = await resolveProvisionHeaders(provider, args.headers)\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n\n const target = httpTarget({ sealKeyUrl, installUrl, headers })\n try {\n const out = await provisionSealed({\n target,\n provider,\n methodId,\n credential,\n ...(label ? { label } : {}),\n })\n process.stdout.write(\n `sealed + installed ${provider} (${methodId})` +\n (out.secretId ? ` — secret ${out.secretId.slice(0, 8)}` : \"\") +\n ` [key ${out.keyId.slice(0, 8)}]\\n` +\n `the plaintext never left this machine; the server unsealed it.\\n`\n )\n return 0\n } catch (err) {\n return fail(err instanceof Error ? err.message : String(err))\n }\n }\n\n case \"-h\":\n case \"--help\":\n case \"help\":\n case undefined:\n process.stdout.write(USAGE + \"\\n\")\n return cmd ? 0 : 1\n\n default:\n return fail(`unknown command '${cmd}'\\n\\n${USAGE}`)\n }\n}\n\nfunction fail(message: string): number {\n process.stderr.write(`agentproto-secrets: ${message}\\n`)\n return 1\n}\n"]}
|
package/dist/exposure/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { M as McpHeaderExposure } from '../types-DS9Qe9Cv.js';
|
|
2
|
+
export { E as EgressSubstituteExposure, a as EnvExposure, F as FileExposure, S as SecretExposure, b as SecretExposureWrap, c as SecretExposureWrapContext, i as isExposureKind } from '../types-DS9Qe9Cv.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Sigil + substitution engine for `$$SECRET[NAME]$$` placeholders.
|
|
@@ -91,4 +92,36 @@ declare class SecretSubstitutionError extends Error {
|
|
|
91
92
|
constructor(code: string, message: string);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Resolver for `McpHeaderExposure` — turns a broker path into ready-to-use
|
|
97
|
+
* HTTP headers for an MCP server's transport.
|
|
98
|
+
*
|
|
99
|
+
* `@agentproto/secrets` stays dependency-free of `@agentproto/auth`: instead
|
|
100
|
+
* of importing `CredentialBroker` directly, this module declares the
|
|
101
|
+
* structural shape it needs (`McpHeaderResolver`). `CredentialBroker`
|
|
102
|
+
* already satisfies it — no adapter required at call sites.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
/** Structural shape a credential broker must satisfy to back
|
|
106
|
+
* `resolveMcpHeaderExposure`. `CredentialBroker` from `@agentproto/auth`
|
|
107
|
+
* matches this shape without any adapter. */
|
|
108
|
+
interface McpHeaderResolver {
|
|
109
|
+
resolveHeaders(o: {
|
|
110
|
+
path: string;
|
|
111
|
+
server?: string;
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}): Promise<Record<string, string>>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve `exposure` into a header map by delegating to `resolver`, then
|
|
117
|
+
* guard every resolved header VALUE with `assertSafeSecretValue` before
|
|
118
|
+
* returning it. This closes off header-injection: a broker (or whatever
|
|
119
|
+
* sits behind it — a flow engine, a vault) that ever returns a value
|
|
120
|
+
* containing CR/LF/NUL would otherwise let that value smuggle extra
|
|
121
|
+
* header lines onto the MCP transport.
|
|
122
|
+
*/
|
|
123
|
+
declare function resolveMcpHeaderExposure(exposure: McpHeaderExposure, resolver: McpHeaderResolver, opts?: {
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
}): Promise<Record<string, string>>;
|
|
126
|
+
|
|
127
|
+
export { McpHeaderExposure, type McpHeaderResolver, SECRET_PLACEHOLDER_PATTERN, type SecretResolver, SecretSubstitutionError, type SubstituteResult, type SubstitutionRecord, assertSafeSecretValue, formatPlaceholder, resolveMcpHeaderExposure, substituteSecrets };
|
package/dist/exposure/index.mjs
CHANGED
|
@@ -86,6 +86,19 @@ var SecretSubstitutionError = class extends Error {
|
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// src/exposure/mcp-header.ts
|
|
90
|
+
async function resolveMcpHeaderExposure(exposure, resolver, opts) {
|
|
91
|
+
const headers = await resolver.resolveHeaders({
|
|
92
|
+
path: exposure.credentialPath,
|
|
93
|
+
server: exposure.server,
|
|
94
|
+
signal: opts?.signal
|
|
95
|
+
});
|
|
96
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
97
|
+
assertSafeSecretValue(name, value);
|
|
98
|
+
}
|
|
99
|
+
return headers;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { SECRET_PLACEHOLDER_PATTERN, SecretSubstitutionError, assertSafeSecretValue, formatPlaceholder, isExposureKind, resolveMcpHeaderExposure, substituteSecrets };
|
|
90
103
|
//# sourceMappingURL=index.mjs.map
|
|
91
104
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exposure/types.ts","../../src/exposure/substitute.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SecretExposure — describes HOW a secret value reaches the runtime\n * that needs it (env var, config file, egress placeholder, future\n * MCP-header injection, future HTTP-bearer routing, …).\n *\n * Distinct from `SecretEntry` (AIP-19), which describes the secret's\n * *declaration* (slug, kind, access policy). Exposure is a *runtime*\n * concern — same secret might be exposed multiple ways across hosts.\n *\n * Discriminated union by `kind` so each new mechanism slots in as one\n * variant rather than another top-level field on whatever catalog the\n * host uses to map secrets onto agent runtimes.\n *\n * # Why this lives here (not in @agentproto/egress)\n * The substitution sigil + the \"what surfaces does this secret have\"\n * vocabulary are properties of the secret itself — they describe its\n * exposure surface independent of WHO consumes it. Egress is one\n * consumer; future ones (MCP servers, HTTP relays) are others.\n *\n * The egress *gating* (mode registry, proxy core) lives in\n * @agentproto/egress because that's a different concern (network-\n * boundary control). Egress imports from here.\n */\n\n/** Function applied to the seed value before write. Receives the raw\n * secret value (or empty string when `field` is omitted) plus an\n * optional context bag the host populates. Returns the file body. */\nexport type SecretExposureWrap = (\n value: string,\n ctx?: SecretExposureWrapContext\n) => string\n\n/** Generic context passed to `wrap` callbacks. Hosts populate fields\n * they have; wraps that don't care ignore the second arg entirely.\n * Kept open-ended (string-keyed) so hosts can attach app-specific\n * data without forcing a schema change here. */\nexport interface SecretExposureWrapContext {\n [key: string]: unknown\n}\n\n/** Inject the secret value as a process env var inside the runtime. */\nexport interface EnvExposure {\n kind: \"env\"\n /** Env var name as the runtime sees it. */\n name: string\n /** Field on the secret/connector settings whose value seeds the env.\n * Required for env exposures — env vars don't have a `wrap` path. */\n field: string\n}\n\n/** Drop a file inside the runtime's filesystem before any process\n * starts. Path uses `~` expansion against the runtime's exec user\n * home (host-resolved). */\nexport interface FileExposure {\n kind: \"file\"\n /** Absolute path or `~`-relative path inside the runtime sandbox. */\n path: string\n /** Field on the secret/connector settings whose value seeds the file\n * body. Omit when `wrap` produces synthetic content (e.g. claude's\n * `.claude.json` migration flags don't depend on a token). */\n field?: string\n /** POSIX file mode. Default `0o600`. */\n mode?: number\n /** Transform applied before write — see SecretExposureWrap. */\n wrap?: SecretExposureWrap\n}\n\n/**\n * Mark this secret as eligible for substitution at the egress proxy.\n * Agents see only `$$SECRET[<placeholderName>]$$` placeholders; the\n * proxy substitutes the real value at the network boundary.\n *\n * The actual proxy + sigil regex + substitution engine live in\n * `@agentproto/egress` — this exposure variant just declares the\n * intent + the per-secret defaults the host install handler stamps.\n */\nexport interface EgressSubstituteExposure {\n kind: \"egress-substitute\"\n /** The token agents reference in `$$SECRET[NAME]$$`. By convention\n * this matches the env-exposure name on the same secret, but it\n * doesn't have to — placeholders and env vars are independent. */\n placeholderName: string\n /** Default value for the per-secret allowlist flag stamped at install\n * time. UI may offer a per-install override. When false, the secret\n * is declared egress-aware but rejected by the resolver until an\n * admin opts in. */\n allowedByDefault: boolean\n /** Audit / UX hint — the providers this secret is *meant* for. NOT\n * enforced at egress (proxy substitutes any matching placeholder\n * regardless of upstream); the field exists so UIs can surface\n * \"this secret is for OpenAI\" without parsing the slug. */\n intendedProviders?: string[]\n}\n\n/**\n * Discriminated union of all exposure mechanisms. Add new variants here\n * as new exposure surfaces are added (MCP-header, HTTP-bearer, etc.) —\n * each adds one variant, one switch case in consumers.\n */\nexport type SecretExposure =\n | EnvExposure\n | FileExposure\n | EgressSubstituteExposure\n\n/** Type guard — convenient for filtering an exposures array by kind. */\nexport function isExposureKind<K extends SecretExposure[\"kind\"]>(\n exposure: SecretExposure,\n kind: K\n): exposure is Extract<SecretExposure, { kind: K }> {\n return exposure.kind === kind\n}\n","/**\n * Sigil + substitution engine for `$$SECRET[NAME]$$` placeholders.\n *\n * Used by hosts that implement egress-time secret substitution (the\n * narrow case in `@agentproto/egress`'s cooperative mode), and any\n * other consumer that wants to materialize secrets only at the\n * boundary instead of in agent-process env.\n *\n * Sigil shape: `$$SECRET[<NAME>]$$` where `<NAME>` is `[A-Z_][A-Z0-9_]*`\n * (matches typical env-var naming). The double-`$$` boundary on both\n * sides plus the bracketed name means a single regex pass can extract\n * names without false positives on strings that happen to contain `$$`\n * elsewhere.\n *\n * # Security\n * Resolved values get a sanity check before substitution:\n * - Reject CR (`\\r`), LF (`\\n`), NUL (`\\0`) — header injection\n * - Reject empty strings — degenerate, almost always a bug\n *\n * Hosts that need stricter validation (length caps, charset narrowing)\n * wrap the resolver passed in; this layer only enforces the universal\n * \"won't break the wire\" guarantees.\n */\n\n/**\n * Match a single `$$SECRET[NAME]$$` placeholder. Capture group 1 is\n * the name. Use the global flag (`g`) when scanning a longer string;\n * the constant here is non-global so callers can `.exec()` reliably.\n *\n * Exported because some consumers (e.g. the host-side install form)\n * want to validate that a string contains no placeholder before\n * accepting user input — they can re-derive a global variant cheaply.\n */\nexport const SECRET_PLACEHOLDER_PATTERN = /\\$\\$SECRET\\[([A-Z_][A-Z0-9_]*)\\]\\$\\$/\n\n/** Global variant — used internally for scanning + substitution. */\nconst SECRET_PLACEHOLDER_PATTERN_G = new RegExp(\n SECRET_PLACEHOLDER_PATTERN.source,\n \"g\"\n)\n\n/** Resolver function the substitution engine calls per-name. Returns\n * the secret value or `null` when the name doesn't resolve. The\n * engine treats null as \"no substitution\" (placeholder remains in\n * the output). Hosts that prefer hard-fail-on-miss should throw from\n * the resolver; the engine propagates. */\nexport type SecretResolver = (name: string) => Promise<string | null> | string | null\n\n/** Outcome of a single substitution call. `replacements` includes one\n * entry per matched placeholder, in the order they appeared. Useful\n * for audit logs (\"this call substituted OPENAI_API_KEY\"). */\nexport interface SubstituteResult {\n output: string\n replacements: SubstitutionRecord[]\n}\n\nexport interface SubstitutionRecord {\n name: string\n /** True when the resolver returned a non-null value. False when the\n * resolver returned null (placeholder kept verbatim). */\n resolved: boolean\n}\n\n/**\n * Validate a resolved secret value before it's substituted. Throws\n * with a stable error code so hosts can map the failure to a 4xx\n * cleanly. Exported so resolvers that pre-fetch can run the same\n * check before handing values back.\n */\nexport function assertSafeSecretValue(name: string, value: string): void {\n if (value.length === 0) {\n throw new SecretSubstitutionError(\n \"secret_value_empty\",\n `Resolved value for '${name}' is empty.`\n )\n }\n // CR / LF / NUL break header framing and stdio respectively. Other\n // control chars are allowed — some legitimate tokens contain them\n // (rare, but a base64 with `\\t` would be wrongly rejected by a\n // broader filter).\n if (/[\\r\\n\\0]/.test(value)) {\n throw new SecretSubstitutionError(\n \"secret_value_unsafe\",\n `Resolved value for '${name}' contains forbidden control characters (CR/LF/NUL).`\n )\n }\n}\n\n/**\n * Substitute every `$$SECRET[NAME]$$` placeholder in `input` using\n * `resolver`. Returns the rewritten string + the list of names that\n * were touched (for audit).\n *\n * Async because resolvers usually decrypt or hit a vault. Even when\n * the resolver is synchronous, the result is a Promise — callers that\n * want the sync path can `await` it cheaply since the underlying work\n * is microtask-scheduled.\n *\n * Whole-string matches are NOT enforced — the placeholder may appear\n * anywhere inside the input (`Bearer $$SECRET[X]$$` is the common\n * case for HTTP Authorization headers). Hosts that want stricter\n * placement (e.g. body-substitution gated to specific JSON paths)\n * should pre-narrow the input before calling here.\n */\nexport async function substituteSecrets(\n input: string,\n resolver: SecretResolver\n): Promise<SubstituteResult> {\n const replacements: SubstitutionRecord[] = []\n // Two-pass: first find all matches + resolve concurrently, then\n // rewrite. Lets parallel resolvers (multiple secrets) overlap their\n // decrypt cost.\n const matches: Array<{ name: string; start: number; end: number }> = []\n let m: RegExpExecArray | null\n const scan = new RegExp(\n SECRET_PLACEHOLDER_PATTERN_G.source,\n SECRET_PLACEHOLDER_PATTERN_G.flags\n )\n while ((m = scan.exec(input)) !== null) {\n matches.push({\n name: m[1]!,\n start: m.index,\n end: m.index + m[0].length,\n })\n }\n if (matches.length === 0) {\n return { output: input, replacements: [] }\n }\n\n // Dedupe so multiple placeholders for the same name only resolve once.\n const uniqueNames = Array.from(new Set(matches.map((x) => x.name)))\n const resolved = new Map<string, string | null>()\n await Promise.all(\n uniqueNames.map(async (name) => {\n const value = await resolver(name)\n if (value !== null) assertSafeSecretValue(name, value)\n resolved.set(name, value)\n })\n )\n\n // Rewrite right-to-left so earlier substitutions don't shift\n // subsequent indices.\n let output = input\n for (let i = matches.length - 1; i >= 0; i--) {\n const { name, start, end } = matches[i]!\n const value = resolved.get(name) ?? null\n if (value !== null) {\n output = output.slice(0, start) + value + output.slice(end)\n }\n // Order-preserving record for audit (forward order).\n }\n for (const { name } of matches) {\n replacements.push({ name, resolved: resolved.get(name) !== null })\n }\n\n return { output, replacements }\n}\n\n/**\n * Convenience: format a placeholder string for a given name.\n * `formatPlaceholder(\"OPENAI_API_KEY\") === \"$$SECRET[OPENAI_API_KEY]$$\"`\n *\n * Throws on invalid names so a bug in the host can't accidentally\n * inject a placeholder that won't round-trip through the regex.\n */\nexport function formatPlaceholder(name: string): string {\n if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {\n throw new SecretSubstitutionError(\n \"invalid_placeholder_name\",\n `Placeholder name '${name}' must match [A-Z_][A-Z0-9_]*`\n )\n }\n return `$$SECRET[${name}]$$`\n}\n\n/** Stable error class — hosts catch by `.code` to map to user-facing\n * responses (typically 400 / 403 / 500). */\nexport class SecretSubstitutionError extends Error {\n readonly code: string\n constructor(code: string, message: string) {\n super(message)\n this.code = code\n this.name = \"SecretSubstitutionError\"\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/exposure/types.ts","../../src/exposure/substitute.ts","../../src/exposure/mcp-header.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SecretExposure — describes HOW a secret value reaches the runtime\n * that needs it (env var, config file, egress placeholder, MCP-header\n * injection, future HTTP-bearer routing, …).\n *\n * Distinct from `SecretEntry` (AIP-19), which describes the secret's\n * *declaration* (slug, kind, access policy). Exposure is a *runtime*\n * concern — same secret might be exposed multiple ways across hosts.\n *\n * Discriminated union by `kind` so each new mechanism slots in as one\n * variant rather than another top-level field on whatever catalog the\n * host uses to map secrets onto agent runtimes.\n *\n * # Why this lives here (not in @agentproto/egress)\n * The substitution sigil + the \"what surfaces does this secret have\"\n * vocabulary are properties of the secret itself — they describe its\n * exposure surface independent of WHO consumes it. Egress is one\n * consumer; future ones (MCP servers, HTTP relays) are others.\n *\n * The egress *gating* (mode registry, proxy core) lives in\n * @agentproto/egress because that's a different concern (network-\n * boundary control). Egress imports from here.\n */\n\n/** Function applied to the seed value before write. Receives the raw\n * secret value (or empty string when `field` is omitted) plus an\n * optional context bag the host populates. Returns the file body. */\nexport type SecretExposureWrap = (\n value: string,\n ctx?: SecretExposureWrapContext\n) => string\n\n/** Generic context passed to `wrap` callbacks. Hosts populate fields\n * they have; wraps that don't care ignore the second arg entirely.\n * Kept open-ended (string-keyed) so hosts can attach app-specific\n * data without forcing a schema change here. */\nexport interface SecretExposureWrapContext {\n [key: string]: unknown\n}\n\n/** Inject the secret value as a process env var inside the runtime. */\nexport interface EnvExposure {\n kind: \"env\"\n /** Env var name as the runtime sees it. */\n name: string\n /** Field on the secret/connector settings whose value seeds the env.\n * Required for env exposures — env vars don't have a `wrap` path. */\n field: string\n}\n\n/** Drop a file inside the runtime's filesystem before any process\n * starts. Path uses `~` expansion against the runtime's exec user\n * home (host-resolved). */\nexport interface FileExposure {\n kind: \"file\"\n /** Absolute path or `~`-relative path inside the runtime sandbox. */\n path: string\n /** Field on the secret/connector settings whose value seeds the file\n * body. Omit when `wrap` produces synthetic content (e.g. claude's\n * `.claude.json` migration flags don't depend on a token). */\n field?: string\n /** POSIX file mode. Default `0o600`. */\n mode?: number\n /** Transform applied before write — see SecretExposureWrap. */\n wrap?: SecretExposureWrap\n}\n\n/**\n * Mark this secret as eligible for substitution at the egress proxy.\n * Agents see only `$$SECRET[<placeholderName>]$$` placeholders; the\n * proxy substitutes the real value at the network boundary.\n *\n * The actual proxy + sigil regex + substitution engine live in\n * `@agentproto/egress` — this exposure variant just declares the\n * intent + the per-secret defaults the host install handler stamps.\n */\nexport interface EgressSubstituteExposure {\n kind: \"egress-substitute\"\n /** The token agents reference in `$$SECRET[NAME]$$`. By convention\n * this matches the env-exposure name on the same secret, but it\n * doesn't have to — placeholders and env vars are independent. */\n placeholderName: string\n /** Default value for the per-secret allowlist flag stamped at install\n * time. UI may offer a per-install override. When false, the secret\n * is declared egress-aware but rejected by the resolver until an\n * admin opts in. */\n allowedByDefault: boolean\n /** Audit / UX hint — the providers this secret is *meant* for. NOT\n * enforced at egress (proxy substitutes any matching placeholder\n * regardless of upstream); the field exists so UIs can surface\n * \"this secret is for OpenAI\" without parsing the slug. */\n intendedProviders?: string[]\n}\n\n/** Inject the secret as an HTTP header on an MCP server's transport,\n * resolved fresh at connect-time via a credential broker. The broker\n * (not this exposure) owns refresh/rotation — this variant just names\n * WHICH credential and, optionally, the upstream server. */\nexport interface McpHeaderExposure {\n kind: \"mcp-header\"\n /** Broker path: \"<providerId>\" or \"<providerId>/<account>\". */\n credentialPath: string\n /** Optional upstream server / apiBase override forwarded to the\n * broker. Omit to let the broker use the provider's default. */\n server?: string\n}\n\n/**\n * Discriminated union of all exposure mechanisms. Add new variants here\n * as new exposure surfaces are added (HTTP-bearer, etc.) — each adds one\n * variant, one switch case in consumers.\n */\nexport type SecretExposure =\n | EnvExposure\n | FileExposure\n | EgressSubstituteExposure\n | McpHeaderExposure\n\n/** Type guard — convenient for filtering an exposures array by kind. */\nexport function isExposureKind<K extends SecretExposure[\"kind\"]>(\n exposure: SecretExposure,\n kind: K\n): exposure is Extract<SecretExposure, { kind: K }> {\n return exposure.kind === kind\n}\n","/**\n * Sigil + substitution engine for `$$SECRET[NAME]$$` placeholders.\n *\n * Used by hosts that implement egress-time secret substitution (the\n * narrow case in `@agentproto/egress`'s cooperative mode), and any\n * other consumer that wants to materialize secrets only at the\n * boundary instead of in agent-process env.\n *\n * Sigil shape: `$$SECRET[<NAME>]$$` where `<NAME>` is `[A-Z_][A-Z0-9_]*`\n * (matches typical env-var naming). The double-`$$` boundary on both\n * sides plus the bracketed name means a single regex pass can extract\n * names without false positives on strings that happen to contain `$$`\n * elsewhere.\n *\n * # Security\n * Resolved values get a sanity check before substitution:\n * - Reject CR (`\\r`), LF (`\\n`), NUL (`\\0`) — header injection\n * - Reject empty strings — degenerate, almost always a bug\n *\n * Hosts that need stricter validation (length caps, charset narrowing)\n * wrap the resolver passed in; this layer only enforces the universal\n * \"won't break the wire\" guarantees.\n */\n\n/**\n * Match a single `$$SECRET[NAME]$$` placeholder. Capture group 1 is\n * the name. Use the global flag (`g`) when scanning a longer string;\n * the constant here is non-global so callers can `.exec()` reliably.\n *\n * Exported because some consumers (e.g. the host-side install form)\n * want to validate that a string contains no placeholder before\n * accepting user input — they can re-derive a global variant cheaply.\n */\nexport const SECRET_PLACEHOLDER_PATTERN = /\\$\\$SECRET\\[([A-Z_][A-Z0-9_]*)\\]\\$\\$/\n\n/** Global variant — used internally for scanning + substitution. */\nconst SECRET_PLACEHOLDER_PATTERN_G = new RegExp(\n SECRET_PLACEHOLDER_PATTERN.source,\n \"g\"\n)\n\n/** Resolver function the substitution engine calls per-name. Returns\n * the secret value or `null` when the name doesn't resolve. The\n * engine treats null as \"no substitution\" (placeholder remains in\n * the output). Hosts that prefer hard-fail-on-miss should throw from\n * the resolver; the engine propagates. */\nexport type SecretResolver = (name: string) => Promise<string | null> | string | null\n\n/** Outcome of a single substitution call. `replacements` includes one\n * entry per matched placeholder, in the order they appeared. Useful\n * for audit logs (\"this call substituted OPENAI_API_KEY\"). */\nexport interface SubstituteResult {\n output: string\n replacements: SubstitutionRecord[]\n}\n\nexport interface SubstitutionRecord {\n name: string\n /** True when the resolver returned a non-null value. False when the\n * resolver returned null (placeholder kept verbatim). */\n resolved: boolean\n}\n\n/**\n * Validate a resolved secret value before it's substituted. Throws\n * with a stable error code so hosts can map the failure to a 4xx\n * cleanly. Exported so resolvers that pre-fetch can run the same\n * check before handing values back.\n */\nexport function assertSafeSecretValue(name: string, value: string): void {\n if (value.length === 0) {\n throw new SecretSubstitutionError(\n \"secret_value_empty\",\n `Resolved value for '${name}' is empty.`\n )\n }\n // CR / LF / NUL break header framing and stdio respectively. Other\n // control chars are allowed — some legitimate tokens contain them\n // (rare, but a base64 with `\\t` would be wrongly rejected by a\n // broader filter).\n if (/[\\r\\n\\0]/.test(value)) {\n throw new SecretSubstitutionError(\n \"secret_value_unsafe\",\n `Resolved value for '${name}' contains forbidden control characters (CR/LF/NUL).`\n )\n }\n}\n\n/**\n * Substitute every `$$SECRET[NAME]$$` placeholder in `input` using\n * `resolver`. Returns the rewritten string + the list of names that\n * were touched (for audit).\n *\n * Async because resolvers usually decrypt or hit a vault. Even when\n * the resolver is synchronous, the result is a Promise — callers that\n * want the sync path can `await` it cheaply since the underlying work\n * is microtask-scheduled.\n *\n * Whole-string matches are NOT enforced — the placeholder may appear\n * anywhere inside the input (`Bearer $$SECRET[X]$$` is the common\n * case for HTTP Authorization headers). Hosts that want stricter\n * placement (e.g. body-substitution gated to specific JSON paths)\n * should pre-narrow the input before calling here.\n */\nexport async function substituteSecrets(\n input: string,\n resolver: SecretResolver\n): Promise<SubstituteResult> {\n const replacements: SubstitutionRecord[] = []\n // Two-pass: first find all matches + resolve concurrently, then\n // rewrite. Lets parallel resolvers (multiple secrets) overlap their\n // decrypt cost.\n const matches: Array<{ name: string; start: number; end: number }> = []\n let m: RegExpExecArray | null\n const scan = new RegExp(\n SECRET_PLACEHOLDER_PATTERN_G.source,\n SECRET_PLACEHOLDER_PATTERN_G.flags\n )\n while ((m = scan.exec(input)) !== null) {\n matches.push({\n name: m[1]!,\n start: m.index,\n end: m.index + m[0].length,\n })\n }\n if (matches.length === 0) {\n return { output: input, replacements: [] }\n }\n\n // Dedupe so multiple placeholders for the same name only resolve once.\n const uniqueNames = Array.from(new Set(matches.map((x) => x.name)))\n const resolved = new Map<string, string | null>()\n await Promise.all(\n uniqueNames.map(async (name) => {\n const value = await resolver(name)\n if (value !== null) assertSafeSecretValue(name, value)\n resolved.set(name, value)\n })\n )\n\n // Rewrite right-to-left so earlier substitutions don't shift\n // subsequent indices.\n let output = input\n for (let i = matches.length - 1; i >= 0; i--) {\n const { name, start, end } = matches[i]!\n const value = resolved.get(name) ?? null\n if (value !== null) {\n output = output.slice(0, start) + value + output.slice(end)\n }\n // Order-preserving record for audit (forward order).\n }\n for (const { name } of matches) {\n replacements.push({ name, resolved: resolved.get(name) !== null })\n }\n\n return { output, replacements }\n}\n\n/**\n * Convenience: format a placeholder string for a given name.\n * `formatPlaceholder(\"OPENAI_API_KEY\") === \"$$SECRET[OPENAI_API_KEY]$$\"`\n *\n * Throws on invalid names so a bug in the host can't accidentally\n * inject a placeholder that won't round-trip through the regex.\n */\nexport function formatPlaceholder(name: string): string {\n if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {\n throw new SecretSubstitutionError(\n \"invalid_placeholder_name\",\n `Placeholder name '${name}' must match [A-Z_][A-Z0-9_]*`\n )\n }\n return `$$SECRET[${name}]$$`\n}\n\n/** Stable error class — hosts catch by `.code` to map to user-facing\n * responses (typically 400 / 403 / 500). */\nexport class SecretSubstitutionError extends Error {\n readonly code: string\n constructor(code: string, message: string) {\n super(message)\n this.code = code\n this.name = \"SecretSubstitutionError\"\n }\n}\n","/**\n * Resolver for `McpHeaderExposure` — turns a broker path into ready-to-use\n * HTTP headers for an MCP server's transport.\n *\n * `@agentproto/secrets` stays dependency-free of `@agentproto/auth`: instead\n * of importing `CredentialBroker` directly, this module declares the\n * structural shape it needs (`McpHeaderResolver`). `CredentialBroker`\n * already satisfies it — no adapter required at call sites.\n */\n\nimport type { McpHeaderExposure } from \"./types.js\"\nimport { assertSafeSecretValue } from \"./substitute.js\"\n\n/** Structural shape a credential broker must satisfy to back\n * `resolveMcpHeaderExposure`. `CredentialBroker` from `@agentproto/auth`\n * matches this shape without any adapter. */\nexport interface McpHeaderResolver {\n resolveHeaders(o: {\n path: string\n server?: string\n signal?: AbortSignal\n }): Promise<Record<string, string>>\n}\n\n/**\n * Resolve `exposure` into a header map by delegating to `resolver`, then\n * guard every resolved header VALUE with `assertSafeSecretValue` before\n * returning it. This closes off header-injection: a broker (or whatever\n * sits behind it — a flow engine, a vault) that ever returns a value\n * containing CR/LF/NUL would otherwise let that value smuggle extra\n * header lines onto the MCP transport.\n */\nexport async function resolveMcpHeaderExposure(\n exposure: McpHeaderExposure,\n resolver: McpHeaderResolver,\n opts?: { signal?: AbortSignal }\n): Promise<Record<string, string>> {\n const headers = await resolver.resolveHeaders({\n path: exposure.credentialPath,\n server: exposure.server,\n signal: opts?.signal,\n })\n for (const [name, value] of Object.entries(headers)) {\n assertSafeSecretValue(name, value)\n }\n return headers\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as SecretsDefinition } from './types-
|
|
2
|
-
export { a as SecretsHandle } from './types-
|
|
3
|
-
import './types-
|
|
1
|
+
import { S as SecretsDefinition } from './types-CHQpxFPe.js';
|
|
2
|
+
export { a as SecretsHandle } from './types-CHQpxFPe.js';
|
|
3
|
+
import './types-DS9Qe9Cv.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* AIP-19 reference implementation of `defineSecrets`.
|
package/dist/manifest/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { a as SecretsHandle } from '../types-
|
|
3
|
-
import '../types-
|
|
2
|
+
import { a as SecretsHandle } from '../types-CHQpxFPe.js';
|
|
3
|
+
import '../types-DS9Qe9Cv.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* AIP-19 SECRETS.md frontmatter zod schema.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SecretExposure — describes HOW a secret value reaches the runtime
|
|
3
|
-
* that needs it (env var, config file, egress placeholder,
|
|
4
|
-
*
|
|
3
|
+
* that needs it (env var, config file, egress placeholder, MCP-header
|
|
4
|
+
* injection, future HTTP-bearer routing, …).
|
|
5
5
|
*
|
|
6
6
|
* Distinct from `SecretEntry` (AIP-19), which describes the secret's
|
|
7
7
|
* *declaration* (slug, kind, access policy). Exposure is a *runtime*
|
|
@@ -83,15 +83,27 @@ interface EgressSubstituteExposure {
|
|
|
83
83
|
* "this secret is for OpenAI" without parsing the slug. */
|
|
84
84
|
intendedProviders?: string[];
|
|
85
85
|
}
|
|
86
|
+
/** Inject the secret as an HTTP header on an MCP server's transport,
|
|
87
|
+
* resolved fresh at connect-time via a credential broker. The broker
|
|
88
|
+
* (not this exposure) owns refresh/rotation — this variant just names
|
|
89
|
+
* WHICH credential and, optionally, the upstream server. */
|
|
90
|
+
interface McpHeaderExposure {
|
|
91
|
+
kind: "mcp-header";
|
|
92
|
+
/** Broker path: "<providerId>" or "<providerId>/<account>". */
|
|
93
|
+
credentialPath: string;
|
|
94
|
+
/** Optional upstream server / apiBase override forwarded to the
|
|
95
|
+
* broker. Omit to let the broker use the provider's default. */
|
|
96
|
+
server?: string;
|
|
97
|
+
}
|
|
86
98
|
/**
|
|
87
99
|
* Discriminated union of all exposure mechanisms. Add new variants here
|
|
88
|
-
* as new exposure surfaces are added (
|
|
89
|
-
*
|
|
100
|
+
* as new exposure surfaces are added (HTTP-bearer, etc.) — each adds one
|
|
101
|
+
* variant, one switch case in consumers.
|
|
90
102
|
*/
|
|
91
|
-
type SecretExposure = EnvExposure | FileExposure | EgressSubstituteExposure;
|
|
103
|
+
type SecretExposure = EnvExposure | FileExposure | EgressSubstituteExposure | McpHeaderExposure;
|
|
92
104
|
/** Type guard — convenient for filtering an exposures array by kind. */
|
|
93
105
|
declare function isExposureKind<K extends SecretExposure["kind"]>(exposure: SecretExposure, kind: K): exposure is Extract<SecretExposure, {
|
|
94
106
|
kind: K;
|
|
95
107
|
}>;
|
|
96
108
|
|
|
97
|
-
export { type EgressSubstituteExposure as E, type FileExposure as F, type SecretExposure as S, type EnvExposure as a, type SecretExposureWrap as b, type SecretExposureWrapContext as c, isExposureKind as i };
|
|
109
|
+
export { type EgressSubstituteExposure as E, type FileExposure as F, type McpHeaderExposure as M, type SecretExposure as S, type EnvExposure as a, type SecretExposureWrap as b, type SecretExposureWrapContext as c, isExposureKind as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/secrets",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "@agentproto/secrets — AIP-19 SECRETS.md reference implementation. A workspace-level manifest format for declaring secret slugs, their purpose, access grants, and audit metadata — without ever storing the values themselves. Hosts resolve slugs against a real vault at reveal time.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"gray-matter": "^4.0.3",
|
|
74
74
|
"zod": "^4.4.3",
|
|
75
|
+
"@agentproto/auth": "0.1.0",
|
|
75
76
|
"@agentproto/define-doctype": "0.1.0"
|
|
76
77
|
},
|
|
77
78
|
"devDependencies": {
|