{"record":{"id":"d5a6b0ea50ebed32","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-credential-missing","errorCode":"cave_sandbox_credential_missing","errorMessage":"cave_sandbox_credential_missing","messagePattern":"cave_sandbox_credential_missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/agent/src/runtime.ts","lineNumber":624,"sourceCode":"    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\",\n    CAVE_EVAL_FIXTURE: \"1\",\n  };\n  for (const name of names) {\n    const value = process.env[name];\n    if (value === undefined) throw new Error(\"cave_sandbox_credential_missing\");\n    env[name] = value;\n  }\n  return env;\n}\n\ninterface InternalRunOptions extends RunOptions {\n  lockedBuild?: CaveBuildLock;\n  candidatePlan?: CavePlan;\n  /**\n   * Resolved once per root run and handed to descendants so a nested agent\n   * neither re-probes the gateway nor disagrees with its parent about whether\n   * this run is optimized or observe-only.\n   */\n  caveRoute?: ResolvedCaveRoute;\n}\n\nexport type ResolvedCaveRoute = {\n  readonly useGateway: boolean;","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L606-L642","documentation":"Thrown by buildSandboxToolEnv() when a name passed the allowlist validation but process.env does not currently define it. The sandbox child env is built from an exact, deterministic baseline plus only the requested credential values — no process.env spread — so a validated name with no value in the parent environment fails closed rather than silently producing a child missing its credential.","triggerScenarios":"Calling buildSandboxToolEnv(['<allowlisted-name>']) when that variable is unset in the current process — e.g. the credential was never exported, was exported in a different shell, or the runner (CI, service manager) strips it.","commonSituations":"Local run works (var in .bashrc) but CI fails because the secret is only in CI's masked-secret store under a different name; dotenv not loaded before the agent starts; the credential is provided via the framework's devkey/secret store rather than the raw environment.","solutions":["Export the allowlisted variable in the environment that actually starts the process (CI secret variable, systemd Environment=, dotenv loaded before the run)","Verify presence without printing the value: names.every(n => typeof process.env[n] === 'string') before calling buildSandboxToolEnv","If the secret lives in a secrets manager, fetch it into the child env via the framework's supported credential path instead of relying on inherited shell env"],"exampleFix":"// before\nconst env = buildSandboxToolEnv(names); // ANTHROPIC-style allowlisted name not exported in CI\n\n// after\nconst missing = names.filter((n) => process.env[n] === undefined);\nif (missing.length) throw new Error(`credential env not set: count=${missing.length}`); // fail with context, never print values\nconst env2 = buildSandboxToolEnv(names);","handlingStrategy":"validation","validationCode":"function assertCredentialsPresent(names: readonly string[]): void {\n  const missing = names.filter((n) => process.env[n] === undefined);\n  if (missing.length > 0) throw new Error(`${missing.length} allowlisted credential env var(s) are not set in this environment`); // never log names or values\n}","typeGuard":null,"tryCatchPattern":"try { const env = buildSandboxToolEnv(names); } catch (e) { if (e instanceof Error && e.message === 'cave_sandbox_credential_missing') throw new SandboxEnvError('credential env missing: check CI secrets / dotenv before agent start', { cause: e }); throw e; }","preventionTips":["Check presence (typeof process.env[name] === 'string') before building the sandbox env — never print the value","Load dotenv and CI secrets before the process that runs the agent starts","Give the check a count, not the names: missing names can themselves identify secrets"],"tags":["sandbox","credentials","environment","fail-closed"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}