{"record":{"id":"c81cf82cc6836974","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-credential-env-not-allowlisted","errorCode":"cave_sandbox_credential_env_not_allowlisted","errorMessage":"cave_sandbox_credential_env_not_allowlisted","messagePattern":"cave_sandbox_credential_env_not_allowlisted","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":601,"sourceCode":"  \"PATH\",\n  \"PWD\",\n  \"SHELL\",\n  \"DYLD_INSERT_LIBRARIES\",\n]);\n\nexport function validateSandboxCredentialEnv(names: readonly string[]): void {\n  const capabilities = new Set<SandboxCredentialCapability>();\n  for (const name of names) {\n    const denied = typeof name === \"string\" &&\n      (SANDBOX_CREDENTIAL_DENY_NAMES.has(name) ||\n        SANDBOX_CREDENTIAL_DENY_PREFIXES.some((prefix) => name.startsWith(prefix)));\n    const capability = denied || typeof name !== \"string\"\n      ? undefined\n      : SANDBOX_CREDENTIAL_ENV_TO_CAPABILITY.get(name);\n    if (capability === undefined) {\n      // Keep profile-controlled names out of errors. This is a policy result,\n      // not a diagnostic surface, and the name may itself identify a secret.\n      throw new Error(\"cave_sandbox_credential_env_not_allowlisted\");\n    }\n    capabilities.add(capability);\n  }\n  if (capabilities.size > 1) {\n    throw new Error(\"cave_sandbox_credential_capability_ambiguous\");\n  }\n}\n\n/** Build the complete environment for an isolated tool child. No spread of\n * `process.env`: only deterministic runtime baseline plus an exact provider\n * capability selected by the validated live profile. */\nexport function buildSandboxToolEnv(names: readonly string[] = []): NodeJS.ProcessEnv {\n  validateSandboxCredentialEnv(names);\n  const env: NodeJS.ProcessEnv = {\n    LANG: process.env.LANG ?? \"C\",\n    LC_ALL: process.env.LC_ALL ?? \"C\",\n    PATH: process.env.PATH ?? \"\",\n    TZ: process.env.TZ ?? \"UTC\",","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L583-L619","documentation":"Thrown by validateSandboxCredentialEnv() (also called from buildSandboxToolEnv) when an environment variable name is not on the provider allowlist, or is explicitly denied. The sandbox child env is an exact allowlist — not a process.env spread — so only names mapped to a single provider capability in SANDBOX_CREDENTIAL_ENV_TO_CAPABILITY pass; deny-listed families (AWS_, GCP_, GITHUB_, CAVE_, PG, POSTGRES_, ...) and names (PATH, HOME, NODE_OPTIONS, ...) never pass. The offending name is deliberately kept out of the error to avoid leaking secret-identifying names.","triggerScenarios":"Passing credential env names such as 'AWS_SECRET_ACCESS_KEY', 'GITHUB_TOKEN', 'ANTHROPIC_API_KEY'-adjacent CAVE_/provider-reserved names, 'PATH', 'HOME', or any string not present in the provider capability map; also any non-string entry in the names array.","commonSituations":"Trying to hand a sandboxed live tool a cloud credential by env name; copy-pasting a docker-run -e style list of env names into the sandbox profile; assuming any env var present in the parent shell is forwardable to the tool child.","solutions":["Use only the provider credential names the framework allowlists (check the SANDBOX_CREDENTIAL_ENV_BY_CAPABILITY map in packages/agent/src/runtime.ts) — one provider's names per call","If your name is denied because it belongs to a high-impact family (AWS_, GCP_, DATABASE_, ...), do not forward it: restructure the tool to receive credentials via the supported provider capability instead","Remove non-string/empty entries and typos from the names array; validate names against the exported allowlist before calling buildSandboxToolEnv"],"exampleFix":"// before\nbuildSandboxToolEnv(['AWS_SECRET_ACCESS_KEY', 'MY_TYPO_TOKENN']);\n\n// after\nbuildSandboxToolEnv(allowlistedProviderNames); // e.g. the single-provider names from SANDBOX_CREDENTIAL_ENV_BY_CAPABILITY","handlingStrategy":"validation","validationCode":"const ALLOWLISTED = new Set(Object.values(SANDBOX_CREDENTIAL_ENV_BY_CAPABILITY).flat()); // import/replicate the provider map\nfunction validateCredentialNames(names: readonly unknown[]): string[] {\n  return names.map((n) => {\n    if (typeof n !== 'string' || !ALLOWLISTED.has(n)) throw new Error('credential env name is not on the sandbox allowlist');\n    return n;\n  });\n}","typeGuard":"function isAllowlistedCredentialName(value: unknown): value is string { return typeof value === 'string' && ALLOWLISTED.has(value); } // ALLOWLISTED mirrors SANDBOX_CREDENTIAL_ENV_TO_CAPABILITY keys","tryCatchPattern":"try { buildSandboxToolEnv(names); } catch (e) { if (e instanceof Error && e.message === 'cave_sandbox_credential_env_not_allowlisted') throw new SandboxConfigError('one or more env names are denied/not allowlisted; check the provider map', { cause: e }); throw e; }","preventionTips":["Source sandbox env names only from the framework's provider capability map, never from user config","Never forward cloud/infra credential families (AWS_, GCP_, DATABASE_, ...) into tool children","Treat the opaque error as intentional: it hides names that may identify secrets"],"tags":["sandbox","credentials","security","allowlist","fail-closed"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}