@tangle-network/cli-agent-registry 0.0.0-stage → 0.15.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/LICENSE +11 -0
- package/README.md +175 -2
- package/dist/backend-types-CNo_Zj_K.mjs +36 -0
- package/dist/backend-types-DRmLR1e0.d.mts +47 -0
- package/dist/backend-types.d.mts +2 -0
- package/dist/backend-types.mjs +2 -0
- package/dist/cli-auth-bundle-C6DtK7GX.mjs +95 -0
- package/dist/cli-auth-bundle-CwAaH0Ap.d.mts +51 -0
- package/dist/cli-auth-bundle.d.mts +2 -0
- package/dist/cli-auth-bundle.mjs +2 -0
- package/dist/cli-auth-placeholder.d.mts +54 -0
- package/dist/cli-auth-placeholder.mjs +200 -0
- package/dist/cli-backend-auth-env.d.mts +6 -0
- package/dist/cli-backend-auth-env.mjs +44 -0
- package/dist/default-backend.d.mts +24 -0
- package/dist/default-backend.mjs +31 -0
- package/dist/harness-D1fTxnDD.mjs +199 -0
- package/dist/harness-contracts-BZoECCMc.mjs +1076 -0
- package/dist/host-contracts.d.mts +30 -0
- package/dist/host-contracts.json +422 -0
- package/dist/host-contracts.mjs +63 -0
- package/dist/index.d.mts +1397 -0
- package/dist/index.mjs +151 -0
- package/dist/local-cli-auth.d.mts +14 -0
- package/dist/local-cli-auth.mjs +31 -0
- package/dist/native-session-contracts.d.mts +349 -0
- package/dist/native-session-contracts.mjs +40 -0
- package/dist/servable-models.d.mts +103 -0
- package/dist/servable-models.mjs +131 -0
- package/dist/session-contract-BhzcbPRW.d.mts +97 -0
- package/dist/session-contract.d.mts +2 -0
- package/dist/session-contract.mjs +142 -0
- package/dist/spec-CcvTM7-j.d.mts +346 -0
- package/dist/specs-BBF55TnM.mjs +735 -0
- package/dist/specs.d.mts +72 -0
- package/dist/specs.mjs +3 -0
- package/package.json +87 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { o as resolveCliAuthReference, r as assertCliAuthReferenceSupported, t as AUTH_BUNDLE_PATHS } from "./cli-auth-bundle-C6DtK7GX.mjs";
|
|
2
|
+
import { i as baseProxyToken, r as declaredSecretSlotTokens } from "./harness-D1fTxnDD.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/cli-auth-placeholder.ts
|
|
5
|
+
const identity = z.string().regex(/^[A-Za-z0-9_-]{1,256}$/);
|
|
6
|
+
const token = z.string().min(8).max(64 * 1024).regex(/^[^\s\0]+$/);
|
|
7
|
+
const expiry = z.number().int().positive().max(864e13);
|
|
8
|
+
const codexPlan = z.enum([
|
|
9
|
+
"free",
|
|
10
|
+
"plus",
|
|
11
|
+
"pro",
|
|
12
|
+
"business",
|
|
13
|
+
"enterprise",
|
|
14
|
+
"edu",
|
|
15
|
+
"team"
|
|
16
|
+
]);
|
|
17
|
+
const claudePlan = z.enum([
|
|
18
|
+
"free",
|
|
19
|
+
"pro",
|
|
20
|
+
"max",
|
|
21
|
+
"team",
|
|
22
|
+
"enterprise"
|
|
23
|
+
]);
|
|
24
|
+
const metadataSchema = z.object({
|
|
25
|
+
accountId: identity.optional(),
|
|
26
|
+
planType: z.union([codexPlan, claudePlan]).optional(),
|
|
27
|
+
accessTokenExpiresAt: expiry.optional(),
|
|
28
|
+
hasRefreshToken: z.boolean()
|
|
29
|
+
}).strict();
|
|
30
|
+
const codexBundle = z.object({ tokens: z.object({
|
|
31
|
+
access_token: token,
|
|
32
|
+
refresh_token: z.union([token, z.literal("")]).optional(),
|
|
33
|
+
id_token: token,
|
|
34
|
+
account_id: identity.nullable().optional()
|
|
35
|
+
}) });
|
|
36
|
+
const codexClaims = z.object({ "https://api.openai.com/auth": z.object({
|
|
37
|
+
chatgpt_account_id: identity.optional(),
|
|
38
|
+
chatgpt_plan_type: codexPlan.optional(),
|
|
39
|
+
chatgpt_account_is_fedramp: z.boolean().optional()
|
|
40
|
+
}).optional() });
|
|
41
|
+
const claudeBundle = z.object({ claudeAiOauth: z.object({
|
|
42
|
+
accessToken: token,
|
|
43
|
+
refreshToken: token.nullable().optional(),
|
|
44
|
+
expiresAt: expiry.nullable().optional(),
|
|
45
|
+
subscriptionType: claudePlan.nullable().optional()
|
|
46
|
+
}) });
|
|
47
|
+
function invalidMaterial() {
|
|
48
|
+
throw new Error("Unsupported or invalid native credential material");
|
|
49
|
+
}
|
|
50
|
+
function parseJson(raw) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
} catch {
|
|
54
|
+
return invalidMaterial();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function jwtClaims(raw) {
|
|
58
|
+
const parts = raw.split(".");
|
|
59
|
+
if (parts.length !== 3 || parts.some((part) => !part)) return invalidMaterial();
|
|
60
|
+
try {
|
|
61
|
+
const payload = (parts[1] ?? "").replaceAll("-", "+").replaceAll("_", "/");
|
|
62
|
+
const bytes = Uint8Array.from(atob(payload), (value) => value.charCodeAt(0));
|
|
63
|
+
return parseJson(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
64
|
+
} catch {
|
|
65
|
+
return invalidMaterial();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function accessTokenExpiry(value) {
|
|
69
|
+
if (value.split(".").length !== 3) return void 0;
|
|
70
|
+
const parsed = z.object({ exp: z.number().int().positive().optional() }).safeParse(jwtClaims(value));
|
|
71
|
+
if (!parsed.success) return invalidMaterial();
|
|
72
|
+
if (parsed.data.exp === void 0) return void 0;
|
|
73
|
+
const result = expiry.safeParse(parsed.data.exp * 1e3);
|
|
74
|
+
if (!result.success) return invalidMaterial();
|
|
75
|
+
return result.data;
|
|
76
|
+
}
|
|
77
|
+
/** Build sandbox-safe native material without accepting any provider token. */
|
|
78
|
+
function buildCliAuthPlaceholderMaterial(input) {
|
|
79
|
+
const reference = assertCliAuthReferenceSupported(input.backendType, { cliAuth: input.reference });
|
|
80
|
+
const metadata = metadataSchema.safeParse(input.metadata);
|
|
81
|
+
const declaredTokens = Object.values(declaredSecretSlotTokens());
|
|
82
|
+
if (!reference || !metadata.success || !Number.isFinite(input.now.getTime()) || !declaredTokens.includes(baseProxyToken(input.placeholder)) || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(input.placeholder)) return invalidMaterial();
|
|
83
|
+
if (input.backendType !== "codex" && input.backendType !== "claude-code") return invalidMaterial();
|
|
84
|
+
if (input.backendType === "codex" && reference.format === "token") return invalidMaterial();
|
|
85
|
+
const { accountId, planType, accessTokenExpiresAt, hasRefreshToken } = metadata.data;
|
|
86
|
+
let content;
|
|
87
|
+
if (input.backendType === "codex") {
|
|
88
|
+
if (planType !== void 0 && !codexPlan.safeParse(planType).success) return invalidMaterial();
|
|
89
|
+
const claims = {
|
|
90
|
+
sub: input.placeholder,
|
|
91
|
+
aud: "tangle-sandbox",
|
|
92
|
+
exp: Math.floor(input.now.getTime() / 1e3)
|
|
93
|
+
};
|
|
94
|
+
content = {
|
|
95
|
+
auth_mode: "chatgpt",
|
|
96
|
+
OPENAI_API_KEY: null,
|
|
97
|
+
tokens: {
|
|
98
|
+
id_token: `e30.${btoa(JSON.stringify(claims)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "")}.tangle-placeholder`,
|
|
99
|
+
access_token: input.placeholder,
|
|
100
|
+
refresh_token: hasRefreshToken ? input.placeholder : "",
|
|
101
|
+
...accountId ? { account_id: accountId } : {}
|
|
102
|
+
},
|
|
103
|
+
last_refresh: input.now.toISOString()
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
if (planType !== void 0 && !claudePlan.safeParse(planType).success) return invalidMaterial();
|
|
107
|
+
content = { claudeAiOauth: {
|
|
108
|
+
accessToken: input.placeholder,
|
|
109
|
+
refreshToken: hasRefreshToken ? input.placeholder : null,
|
|
110
|
+
expiresAt: accessTokenExpiresAt ?? null,
|
|
111
|
+
scopes: ["user:inference"],
|
|
112
|
+
subscriptionType: planType ?? null,
|
|
113
|
+
rateLimitTier: null
|
|
114
|
+
} };
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
reference,
|
|
118
|
+
authFiles: [{
|
|
119
|
+
path: AUTH_BUNDLE_PATHS[input.backendType],
|
|
120
|
+
content: JSON.stringify(content),
|
|
121
|
+
mode: 384
|
|
122
|
+
}],
|
|
123
|
+
tokenEnvironment: {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* TRUSTED OWNER ONLY. The explicit proxyAccessToken result must reach only the
|
|
128
|
+
* host's existing egress holder. Only nativeMaterial may enter the sandbox.
|
|
129
|
+
* The uploaded bundle, ID token and refresh token are never returned.
|
|
130
|
+
*/
|
|
131
|
+
function resolveCliAuthPlaceholder(input) {
|
|
132
|
+
if (input.secretValue.length > 1024 * 1024) return invalidMaterial();
|
|
133
|
+
const material = resolveCliAuthReference(input.backendType, { cliAuth: input.reference }, { [input.reference.secretEnv]: input.secretValue });
|
|
134
|
+
if (!material) return invalidMaterial();
|
|
135
|
+
let proxyAccessToken;
|
|
136
|
+
let metadata;
|
|
137
|
+
let credentials;
|
|
138
|
+
if (input.backendType === "claude-code" && material.reference.format === "token") {
|
|
139
|
+
const parsed = token.safeParse(Object.values(material.tokenEnvironment)[0]);
|
|
140
|
+
if (!parsed.success) return invalidMaterial();
|
|
141
|
+
proxyAccessToken = parsed.data;
|
|
142
|
+
credentials = [proxyAccessToken];
|
|
143
|
+
metadata = { hasRefreshToken: false };
|
|
144
|
+
} else {
|
|
145
|
+
const expectedPath = input.backendType === "codex" ? AUTH_BUNDLE_PATHS.codex : AUTH_BUNDLE_PATHS["claude-code"];
|
|
146
|
+
if (material.authFiles.length !== 1 || material.authFiles[0]?.path !== expectedPath) return invalidMaterial();
|
|
147
|
+
const bundle = parseJson(material.authFiles[0].content);
|
|
148
|
+
if (input.backendType === "codex") {
|
|
149
|
+
const parsed = codexBundle.safeParse(bundle);
|
|
150
|
+
if (!parsed.success) return invalidMaterial();
|
|
151
|
+
const tokens = parsed.data.tokens;
|
|
152
|
+
const parsedClaims = codexClaims.safeParse(jwtClaims(tokens.id_token));
|
|
153
|
+
if (!parsedClaims.success) return invalidMaterial();
|
|
154
|
+
const claims = parsedClaims.data["https://api.openai.com/auth"];
|
|
155
|
+
if (claims?.chatgpt_account_is_fedramp) return invalidMaterial();
|
|
156
|
+
if (tokens.account_id && claims?.chatgpt_account_id && tokens.account_id !== claims.chatgpt_account_id) return invalidMaterial();
|
|
157
|
+
proxyAccessToken = tokens.access_token;
|
|
158
|
+
credentials = [
|
|
159
|
+
proxyAccessToken,
|
|
160
|
+
tokens.id_token,
|
|
161
|
+
tokens.refresh_token ?? ""
|
|
162
|
+
];
|
|
163
|
+
metadata = {
|
|
164
|
+
accountId: tokens.account_id ?? claims?.chatgpt_account_id,
|
|
165
|
+
planType: claims?.chatgpt_plan_type,
|
|
166
|
+
accessTokenExpiresAt: accessTokenExpiry(proxyAccessToken),
|
|
167
|
+
hasRefreshToken: Boolean(tokens.refresh_token)
|
|
168
|
+
};
|
|
169
|
+
} else if (input.backendType === "claude-code") {
|
|
170
|
+
const parsed = claudeBundle.safeParse(bundle);
|
|
171
|
+
if (!parsed.success) return invalidMaterial();
|
|
172
|
+
const oauth = parsed.data.claudeAiOauth;
|
|
173
|
+
proxyAccessToken = oauth.accessToken;
|
|
174
|
+
credentials = [proxyAccessToken, oauth.refreshToken ?? ""];
|
|
175
|
+
metadata = {
|
|
176
|
+
planType: oauth.subscriptionType ?? void 0,
|
|
177
|
+
accessTokenExpiresAt: oauth.expiresAt ?? void 0,
|
|
178
|
+
hasRefreshToken: Boolean(oauth.refreshToken)
|
|
179
|
+
};
|
|
180
|
+
} else return invalidMaterial();
|
|
181
|
+
}
|
|
182
|
+
const nativeMaterial = buildCliAuthPlaceholderMaterial({
|
|
183
|
+
...input,
|
|
184
|
+
metadata
|
|
185
|
+
});
|
|
186
|
+
const publicOutput = JSON.stringify({
|
|
187
|
+
nativeMaterial,
|
|
188
|
+
metadata
|
|
189
|
+
});
|
|
190
|
+
if (credentials.some((value) => value && publicOutput.includes(value))) return invalidMaterial();
|
|
191
|
+
return {
|
|
192
|
+
proxyAccessToken,
|
|
193
|
+
nativeMaterial,
|
|
194
|
+
metadata
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { buildCliAuthPlaceholderMaterial, resolveCliAuthPlaceholder };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=cli-auth-placeholder.mjs.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/cli-backend-auth-env.d.ts
|
|
2
|
+
/** The narrow credential leg shared by the host and sidecar rebind boundary. */
|
|
3
|
+
declare function assertCliBackendAuthEnv(backendType: string | undefined, authEnv: Readonly<Record<string, string>>): void;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { assertCliBackendAuthEnv };
|
|
6
|
+
//# sourceMappingURL=cli-backend-auth-env.d.mts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { l as getSpec } from "./specs-BBF55TnM.mjs";
|
|
2
|
+
import { i as cliAuthFilesSchema, n as CLI_AUTH_REFERENCE_ENV_VAR, o as resolveCliAuthReference, s as cliAuthReferenceSchema } from "./cli-auth-bundle-C6DtK7GX.mjs";
|
|
3
|
+
import { n as isSafeEnvVarName, t as isForbiddenEnvVarName } from "./harness-D1fTxnDD.mjs";
|
|
4
|
+
//#region src/cli-backend-auth-env.ts
|
|
5
|
+
/** The narrow credential leg shared by the host and sidecar rebind boundary. */
|
|
6
|
+
function assertCliBackendAuthEnv(backendType, authEnv) {
|
|
7
|
+
if (Object.keys(authEnv).length === 0) return;
|
|
8
|
+
const spec = backendType ? getSpec(backendType) : void 0;
|
|
9
|
+
if (!spec) throw new Error("Credential delivery requires a registered backend");
|
|
10
|
+
const allowed = new Set([
|
|
11
|
+
spec.envVars.apiKey,
|
|
12
|
+
spec.envVars.oauthToken,
|
|
13
|
+
spec.envVars.authFiles
|
|
14
|
+
].filter((name) => name !== void 0));
|
|
15
|
+
const rawReference = authEnv[CLI_AUTH_REFERENCE_ENV_VAR];
|
|
16
|
+
if (rawReference !== void 0) {
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(rawReference);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error("Invalid native credential reference");
|
|
22
|
+
}
|
|
23
|
+
const result = cliAuthReferenceSchema.safeParse(parsed);
|
|
24
|
+
if (!result.success || !isSafeEnvVarName(result.data.secretEnv) || isForbiddenEnvVarName(result.data.secretEnv)) throw new Error("Invalid native credential reference");
|
|
25
|
+
allowed.add(CLI_AUTH_REFERENCE_ENV_VAR);
|
|
26
|
+
allowed.add(result.data.secretEnv);
|
|
27
|
+
resolveCliAuthReference(spec.name, { cliAuth: result.data }, authEnv);
|
|
28
|
+
}
|
|
29
|
+
for (const [name, value] of Object.entries(authEnv)) if (!allowed.has(name) || value.includes("\0")) throw new Error("Invalid backend credential environment");
|
|
30
|
+
const files = spec.envVars.authFiles && authEnv[spec.envVars.authFiles];
|
|
31
|
+
if (files !== void 0 && files !== "") {
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = JSON.parse(files);
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error("Invalid backend auth files");
|
|
37
|
+
}
|
|
38
|
+
if (!(Array.isArray(parsed) && parsed.length === 0) && !cliAuthFilesSchema.safeParse(parsed).success) throw new Error("Invalid backend auth files");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { assertCliBackendAuthEnv };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=cli-backend-auth-env.mjs.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { r as BackendType } from "./backend-types-DRmLR1e0.mjs";
|
|
2
|
+
//#region src/default-backend.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Platform-wide default agent backend, read from `DEFAULT_AGENT_BACKEND`.
|
|
5
|
+
*
|
|
6
|
+
* Every "no backend type was supplied" path resolves
|
|
7
|
+
* through — request bodies, container labels, session records, env-driven
|
|
8
|
+
* boot. Deployment config (env files, Dockerfile ENV, test setup) names the
|
|
9
|
+
* default exactly once; code never carries a vendor literal.
|
|
10
|
+
*
|
|
11
|
+
* Fail-closed: throws when the variable is unset or names an unregistered
|
|
12
|
+
* backend. A silent literal here would weld one vendor backend into the
|
|
13
|
+
* platform and mask misconfigured deployments.
|
|
14
|
+
*
|
|
15
|
+
* Published as its own `./default-backend` entry: it pulls only `./specs.js`
|
|
16
|
+
* (data-only), so Cloudflare Worker / edge bundles can consume it without the
|
|
17
|
+
* Node-only barrel (adapter bindings, local CLI auth). Workers have no
|
|
18
|
+
* `process.env` — they MUST pass their bindings record explicitly; the
|
|
19
|
+
* `process.env` default only fires when the argument is omitted.
|
|
20
|
+
*/
|
|
21
|
+
declare function resolveDefaultBackend(env?: Record<string, string | undefined>): BackendType;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { resolveDefaultBackend };
|
|
24
|
+
//# sourceMappingURL=default-backend.d.mts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { i as isBackendType, t as ALL_BACKEND_TYPES } from "./backend-types-CNo_Zj_K.mjs";
|
|
2
|
+
import "./specs-BBF55TnM.mjs";
|
|
3
|
+
//#region src/default-backend.ts
|
|
4
|
+
/**
|
|
5
|
+
* Platform-wide default agent backend, read from `DEFAULT_AGENT_BACKEND`.
|
|
6
|
+
*
|
|
7
|
+
* Every "no backend type was supplied" path resolves
|
|
8
|
+
* through — request bodies, container labels, session records, env-driven
|
|
9
|
+
* boot. Deployment config (env files, Dockerfile ENV, test setup) names the
|
|
10
|
+
* default exactly once; code never carries a vendor literal.
|
|
11
|
+
*
|
|
12
|
+
* Fail-closed: throws when the variable is unset or names an unregistered
|
|
13
|
+
* backend. A silent literal here would weld one vendor backend into the
|
|
14
|
+
* platform and mask misconfigured deployments.
|
|
15
|
+
*
|
|
16
|
+
* Published as its own `./default-backend` entry: it pulls only `./specs.js`
|
|
17
|
+
* (data-only), so Cloudflare Worker / edge bundles can consume it without the
|
|
18
|
+
* Node-only barrel (adapter bindings, local CLI auth). Workers have no
|
|
19
|
+
* `process.env` — they MUST pass their bindings record explicitly; the
|
|
20
|
+
* `process.env` default only fires when the argument is omitted.
|
|
21
|
+
*/
|
|
22
|
+
function resolveDefaultBackend(env = process.env) {
|
|
23
|
+
const raw = env.DEFAULT_AGENT_BACKEND;
|
|
24
|
+
if (raw === void 0 || raw === "") throw new Error(`DEFAULT_AGENT_BACKEND is not set. A backend type was omitted and no platform default is configured. Set DEFAULT_AGENT_BACKEND to one of: ${ALL_BACKEND_TYPES.join(", ")}`);
|
|
25
|
+
if (!isBackendType(raw)) throw new Error(`DEFAULT_AGENT_BACKEND="${raw}" is not a registered backend. Registered backends: ${ALL_BACKEND_TYPES.join(", ")}`);
|
|
26
|
+
return raw;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { resolveDefaultBackend };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=default-backend.mjs.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { n as HARNESS_WARM_TRANSPORT_ENV } from "./harness-contracts-BZoECCMc.mjs";
|
|
2
|
+
import { ProxySecretBindingSchema, ProxySecretGrantSchema, ProxySecretReceiptSchema, ProxySecretUpdateControlsSchema, ProxySecretsSchema, isProxySecretScopeAttenuation, ownPropertyRecordSchema, proxySecretPresentationsEqual } from "@tangle-network/agent-interface";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
function baseProxyToken(token) {
|
|
5
|
+
const i = token.indexOf(".");
|
|
6
|
+
return i === -1 ? token : token.slice(0, i);
|
|
7
|
+
}
|
|
8
|
+
const MODEL_PROXY_TOKENS = {
|
|
9
|
+
OPENCODE_MODEL_API_KEY: "proxy-model-token",
|
|
10
|
+
OPENAI_API_KEY: "proxy-openai-token",
|
|
11
|
+
ANTHROPIC_API_KEY: "proxy-anthropic-token",
|
|
12
|
+
OPENROUTER_API_KEY: "proxy-openrouter-token",
|
|
13
|
+
TANGLE_INFERENCE_KEY: "proxy-openai-token",
|
|
14
|
+
ZAI_API_KEY: "proxy-zai-token",
|
|
15
|
+
ZHIPU_API_KEY: "proxy-zai-token"
|
|
16
|
+
};
|
|
17
|
+
new Set(Object.values(MODEL_PROXY_TOKENS));
|
|
18
|
+
new Set([
|
|
19
|
+
...Object.keys(MODEL_PROXY_TOKENS),
|
|
20
|
+
"TANGLE_API_KEY",
|
|
21
|
+
"GITHUB_TOKEN",
|
|
22
|
+
"GH_TOKEN"
|
|
23
|
+
]);
|
|
24
|
+
/** Proxy-private slot names shared by legacy and canonical bindings. */
|
|
25
|
+
function declaredSecretSlotEnvVar(index) {
|
|
26
|
+
return `TANGLE_DECLARED_SLOT_${index}`;
|
|
27
|
+
}
|
|
28
|
+
/** True for a slot name, which exists at the proxy and never in a box env. */
|
|
29
|
+
function isDeclaredSecretSlotEnvVar(name) {
|
|
30
|
+
return /^TANGLE_DECLARED_SLOT_\d+$/.test(name);
|
|
31
|
+
}
|
|
32
|
+
/** The base placeholder for the slot at `index`; the box holds it salted. */
|
|
33
|
+
function declaredSecretBaseToken(index) {
|
|
34
|
+
return `proxy-declared-${index}-token`;
|
|
35
|
+
}
|
|
36
|
+
/** Every slot name mapped to its base placeholder. */
|
|
37
|
+
function declaredSecretSlotTokens() {
|
|
38
|
+
const slots = {};
|
|
39
|
+
for (let index = 0; index < 16; index++) slots[declaredSecretSlotEnvVar(index)] = declaredSecretBaseToken(index);
|
|
40
|
+
return slots;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region ../runtime-contracts/dist/proxy-secret-materialization.js
|
|
44
|
+
const syntheticHeader = {
|
|
45
|
+
alg: "HS256",
|
|
46
|
+
typ: "JWT"
|
|
47
|
+
};
|
|
48
|
+
const encode = (value) => btoa(value instanceof Uint8Array ? String.fromCharCode(...value) : JSON.stringify(value)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
49
|
+
function placeholderPrefix(slot, format, expiresAt) {
|
|
50
|
+
return format === "jwt" ? `${encode(syntheticHeader)}.${encode({
|
|
51
|
+
sub: "sandbox-placeholder",
|
|
52
|
+
aud: "proxy-secret-placeholder",
|
|
53
|
+
exp: Math.floor(Date.parse(expiresAt) / 1e3)
|
|
54
|
+
})}.` : `proxy-declared-${slot.slice(21)}-token-`;
|
|
55
|
+
}
|
|
56
|
+
function isSyntheticPlaceholder(material) {
|
|
57
|
+
const prefix = placeholderPrefix(material.slot, material.grant.placeholderFormat, material.issuerExpiresAt);
|
|
58
|
+
return material.placeholder.startsWith(prefix) && /^[A-Za-z0-9_-]{43}$/.test(material.placeholder.slice(prefix.length));
|
|
59
|
+
}
|
|
60
|
+
z.array(z.strictObject({
|
|
61
|
+
binding: ProxySecretBindingSchema,
|
|
62
|
+
grant: ProxySecretGrantSchema,
|
|
63
|
+
placeholder: z.string().min(1),
|
|
64
|
+
slot: z.string().refine(isDeclaredSecretSlotEnvVar),
|
|
65
|
+
issuerExpiresAt: z.iso.datetime(),
|
|
66
|
+
native: z.unknown().optional()
|
|
67
|
+
})).superRefine((materials, ctx) => {
|
|
68
|
+
const bindings = ProxySecretsSchema.safeParse(materials.map((m) => m.binding));
|
|
69
|
+
const unique = (values) => new Set(values).size === values.length;
|
|
70
|
+
if (!bindings.success || !unique(materials.map((m) => m.slot)) || !unique(materials.map((m) => m.placeholder)) || !unique(materials.map((m) => m.grant.grantId)) || new Set(materials.map((m) => m.grant.epoch)).size > 1 || new Set(materials.map((m) => m.grant.sandboxId)).size > 1 || materials.some((material) => !isSyntheticPlaceholder(material)) || materials.some(({ binding, grant, issuerExpiresAt }) => !isProxySecretScopeAttenuation(binding, grant.scope) || !proxySecretPresentationsEqual(binding.presentation, grant.presentation) || (binding.placeholderFormat ?? "opaque") !== grant.placeholderFormat || Date.parse(grant.expiresAt) > Date.parse(issuerExpiresAt))) ctx.addIssue({
|
|
71
|
+
code: "custom",
|
|
72
|
+
message: "Invalid proxy secret materialization"
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
ProxySecretUpdateControlsSchema.extend({
|
|
76
|
+
resolvedSecrets: ownPropertyRecordSchema(z.string().min(1), { maxEntries: Number.MAX_SAFE_INTEGER }).default({}),
|
|
77
|
+
proxySecretGrants: ProxySecretReceiptSchema.options[0].shape.bindings,
|
|
78
|
+
expectedProxySecretGeneration: ProxySecretUpdateControlsSchema.shape.expectedProxySecretGeneration.unwrap()
|
|
79
|
+
}).strict().refine((body) => new Set(body.proxySecretGrants.map((b) => b.envName)).size === body.proxySecretGrants.length);
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region ../runtime-contracts/dist/runtime-environment.js
|
|
82
|
+
const SAFE_ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
83
|
+
function isSafeEnvVarName(name) {
|
|
84
|
+
if (typeof name !== "string") return false;
|
|
85
|
+
if (name.length === 0 || name.length > 256) return false;
|
|
86
|
+
return SAFE_ENV_VAR_NAME_RE.test(name);
|
|
87
|
+
}
|
|
88
|
+
/** Client proxy URLs first, then bypass names. */
|
|
89
|
+
const EGRESS_PROXY_ENV_NAMES = [
|
|
90
|
+
"HTTPS_PROXY",
|
|
91
|
+
"https_proxy",
|
|
92
|
+
"HTTP_PROXY",
|
|
93
|
+
"http_proxy",
|
|
94
|
+
"NO_PROXY",
|
|
95
|
+
"no_proxy"
|
|
96
|
+
];
|
|
97
|
+
const CA_TRUST_ENV_NAMES = [
|
|
98
|
+
"NODE_EXTRA_CA_CERTS",
|
|
99
|
+
"SSL_CERT_FILE",
|
|
100
|
+
"CURL_CA_BUNDLE",
|
|
101
|
+
"GIT_SSL_CAINFO",
|
|
102
|
+
"NIX_SSL_CERT_FILE"
|
|
103
|
+
];
|
|
104
|
+
[...EGRESS_PROXY_ENV_NAMES, ...CA_TRUST_ENV_NAMES];
|
|
105
|
+
const FORBIDDEN_ENV_VAR_NAMES = new Set([
|
|
106
|
+
"LD_PRELOAD",
|
|
107
|
+
"LD_LIBRARY_PATH",
|
|
108
|
+
"LD_AUDIT",
|
|
109
|
+
"DYLD_INSERT_LIBRARIES",
|
|
110
|
+
"DYLD_LIBRARY_PATH",
|
|
111
|
+
"DYLD_FALLBACK_LIBRARY_PATH",
|
|
112
|
+
"NODE_OPTIONS",
|
|
113
|
+
"NODE_PATH",
|
|
114
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
115
|
+
"PYTHONPATH",
|
|
116
|
+
"PYTHONSTARTUP",
|
|
117
|
+
"PERL5OPT",
|
|
118
|
+
"PERL5LIB",
|
|
119
|
+
"RUBYOPT",
|
|
120
|
+
"RUBYLIB",
|
|
121
|
+
"PATH",
|
|
122
|
+
"NIX_BIN_PATH",
|
|
123
|
+
"EGRESS_EDGE_POOL_ENABLED",
|
|
124
|
+
"EDGE_BIND_ID",
|
|
125
|
+
"PRIVACY_EGRESS",
|
|
126
|
+
"LOG_REDACT",
|
|
127
|
+
"IFS",
|
|
128
|
+
"BASH_ENV",
|
|
129
|
+
"ENV",
|
|
130
|
+
"SHELL",
|
|
131
|
+
"SHELLOPTS",
|
|
132
|
+
"PROMPT_COMMAND",
|
|
133
|
+
"HOME",
|
|
134
|
+
"XDG_CONFIG_HOME",
|
|
135
|
+
"XDG_DATA_HOME",
|
|
136
|
+
"XDG_CACHE_HOME",
|
|
137
|
+
"XDG_STATE_HOME",
|
|
138
|
+
"XDG_RUNTIME_DIR",
|
|
139
|
+
"HTTPS_PROXY",
|
|
140
|
+
"HTTP_PROXY",
|
|
141
|
+
"ALL_PROXY",
|
|
142
|
+
"NO_PROXY",
|
|
143
|
+
"https_proxy",
|
|
144
|
+
"http_proxy",
|
|
145
|
+
"all_proxy",
|
|
146
|
+
"no_proxy",
|
|
147
|
+
"SSL_CERT_FILE",
|
|
148
|
+
"SSL_CERT_DIR",
|
|
149
|
+
"REQUESTS_CA_BUNDLE",
|
|
150
|
+
"CURL_CA_BUNDLE",
|
|
151
|
+
"NODE_EXTRA_CA_CERTS",
|
|
152
|
+
"HOSTNAME",
|
|
153
|
+
"CONTAINER_ID",
|
|
154
|
+
"AGENT_WORKSPACE_ROOT",
|
|
155
|
+
"WORKSPACE_PATH",
|
|
156
|
+
"STORAGE_PATH",
|
|
157
|
+
"WORKSPACE_FILE_API_ROOT_ONLY",
|
|
158
|
+
"WORKSPACE_ALLOWED_ABSOLUTE_PATHS",
|
|
159
|
+
"TANGLE_PLAN_CALLBACK_ALLOWED_ORIGINS"
|
|
160
|
+
]);
|
|
161
|
+
const FORBIDDEN_ENV_VAR_PREFIXES = [
|
|
162
|
+
"SIDECAR_",
|
|
163
|
+
"ORCHESTRATOR_",
|
|
164
|
+
"AGENT_INTERNAL_",
|
|
165
|
+
"SANDBOX_DEV_",
|
|
166
|
+
"AGENT_SUBPROCESS_",
|
|
167
|
+
"TEE_ATTESTATION_"
|
|
168
|
+
];
|
|
169
|
+
const RUNTIME_CALLER_ALLOWED_ENV_NAMES = new Set(["SIDECAR_CAPABILITIES", "SIDECAR_PLATFORM_CONTROL_RUNTIME"]);
|
|
170
|
+
new Set(Object.values(HARNESS_WARM_TRANSPORT_ENV).flatMap(({ warmServer, prewarm }) => [warmServer, prewarm]));
|
|
171
|
+
function isForbiddenEnvVarName(name) {
|
|
172
|
+
if (RUNTIME_CALLER_ALLOWED_ENV_NAMES.has(name)) return false;
|
|
173
|
+
if (FORBIDDEN_ENV_VAR_NAMES.has(name)) return true;
|
|
174
|
+
for (const prefix of FORBIDDEN_ENV_VAR_PREFIXES) if (name.startsWith(prefix)) return true;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* POSIX names for caller-owned container env. Runtime process.env merges must additionally enforce
|
|
179
|
+
* isForbiddenEnvVarName, including project provisioning and Sidecar config.
|
|
180
|
+
*/
|
|
181
|
+
const safeEnvRecordSchema = ownPropertyRecordSchema(z.string(), { maxEntries: Number.MAX_SAFE_INTEGER }).refine((value) => Object.keys(value).every(isSafeEnvVarName), { message: "invalid env var name" });
|
|
182
|
+
/** Names only: the caller must keep a separate source and pass values to resume({ env }). */
|
|
183
|
+
const sandboxEnvRecoverySchema = z.object({ callerResupplies: z.array(z.string().max(256).refine(isSafeEnvVarName, { message: "invalid env var name" })).max(128).refine((keys) => new Set(keys).size === keys.length, { message: "callerResupplies must contain unique keys" }) }).strict();
|
|
184
|
+
z.object({
|
|
185
|
+
envRecovery: sandboxEnvRecoverySchema,
|
|
186
|
+
dryRun: z.boolean().optional()
|
|
187
|
+
}).strict();
|
|
188
|
+
z.object({
|
|
189
|
+
envRecovery: sandboxEnvRecoverySchema,
|
|
190
|
+
eligibleCallerResupplyKeys: z.array(z.string()),
|
|
191
|
+
requiredOnColdResumeKeys: z.array(z.string()),
|
|
192
|
+
automaticCredentialRecovery: z.literal(false),
|
|
193
|
+
dryRun: z.boolean()
|
|
194
|
+
}).strict();
|
|
195
|
+
safeEnvRecordSchema.refine((value) => Object.keys(value).every((key) => !isForbiddenEnvVarName(key) && !key.startsWith("SIDECAR_")), { message: "runtime-owned env var name cannot be set by callers" });
|
|
196
|
+
//#endregion
|
|
197
|
+
export { baseProxyToken as i, isSafeEnvVarName as n, declaredSecretSlotTokens as r, isForbiddenEnvVarName as t };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=harness-D1fTxnDD.mjs.map
|