{"record":{"id":"d08dda10505ffb58","repo":"paperclipai/paperclip","slug":"invalid-sandbox-environment-variable-key-key","errorCode":null,"errorMessage":"Invalid sandbox environment variable key: ${key}","messagePattern":"Invalid sandbox environment variable key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/cloudflare/bridge-template/src/exec.ts","lineNumber":39,"sourceCode":"}\n\nfunction randomToken(): string {\n  const uuid = globalThis.crypto?.randomUUID?.();\n  if (typeof uuid === \"string\" && uuid.length > 0) return uuid.replace(/[^a-zA-Z0-9-]/g, \"\");\n  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport function buildLoginShellScript(input: {\n  command: string;\n  args: string[];\n  cwd?: string;\n  env?: Record<string, string>;\n  stdinFile?: string | null;\n}): string {\n  const env = input.env ?? {};\n  for (const key of Object.keys(env)) {\n    if (!isValidShellEnvKey(key)) {\n      throw new Error(`Invalid sandbox environment variable key: ${key}`);\n    }\n  }\n\n  const envArgs = Object.entries(env)\n    .filter((entry): entry is [string, string] => typeof entry[1] === \"string\")\n    .map(([key, value]) => `${key}=${shellQuote(value)}`);\n  const commandParts = [shellQuote(input.command), ...input.args.map(shellQuote)].join(\" \");\n  const stdinRedirect = input.stdinFile ? ` < ${shellQuote(input.stdinFile)}` : \"\";\n  // Source the common login profiles before exec so the command runs with the\n  // interactive-shell PATH. The wrapper sources no `nvm.sh`; the sandbox image\n  // supplies node on the PATH.\n  const lines = [\n    'if [ -f /etc/profile ]; then . /etc/profile >/dev/null 2>&1 || true; fi',\n    'if [ -f \"$HOME/.profile\" ]; then . \"$HOME/.profile\" >/dev/null 2>&1 || true; fi',\n    'if [ -f \"$HOME/.bash_profile\" ]; then . \"$HOME/.bash_profile\" >/dev/null 2>&1 || true; elif [ -f \"$HOME/.bashrc\" ]; then . \"$HOME/.bashrc\" >/dev/null 2>&1 || true; fi',\n    'if [ -f \"$HOME/.zprofile\" ]; then . \"$HOME/.zprofile\" >/dev/null 2>&1 || true; fi',\n  ];\n  if (input.cwd) {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/cloudflare/bridge-template/src/exec.ts#L21-L57","documentation":"Thrown by the Cloudflare sandbox bridge's `buildLoginShellScript` (exec.ts:39) when an environment-variable key fails the POSIX-identifier regex `^[A-Za-z_][A-Za-z0-9_]*$`. Each env key is interpolated bare into the login-shell wrapper as `KEY=value`, so an invalid key would either break the shell parse or inject shell metacharacters; the guard rejects it before script assembly.","triggerScenarios":"Posting an `/exec` (or probe/lease setup) request whose `env` object contains a key with characters outside `[A-Za-z0-9_]`, a leading digit, an empty string, dashes (e.g. `MY-VAR`), dots, or unicode. The check runs before the command exec wrapper is built.","commonSituations":"Forwarding a process.env-style object that includes dotted/kebab keys (npm/package config vars), copying a key with a typo or stray whitespace, or a caller that lets arbitrary user input populate env keys.","solutions":["Rename env keys to match `^[A-Za-z_][A-Za-z0-9_]*$` (uppercase SNAKE_CASE is conventional): `MY-VAR` -> `MY_VAR`.","Filter/transform keys at the call site before sending the exec request: strip or replace disallowed characters.","Reject empty-string keys and keys starting with a digit."],"exampleFix":"// before\nenv: { \"MY-VAR\": \"1\", \"2ND\": \"x\" }\n// after\nenv: { MY_VAR: \"1\", SECOND: \"x\" }","handlingStrategy":"validation","validationCode":"const SHELL_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;\nfunction sanitizeEnvKeys(env) {\n  const out = {};\n  for (const [k, v] of Object.entries(env ?? {})) {\n    if (!SHELL_ENV_KEY.test(k)) throw new Error(`Invalid sandbox env key: ${k}`);\n    if (typeof v === \"string\") out[k] = v;\n  }\n  return out;\n}","typeGuard":"function isShellEnvKey(k: string): boolean {\n  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(k);\n}\nfunction isValidEnvRecord(env: unknown): env is Record<string, string> {\n  if (typeof env !== \"object\" || env === null) return false;\n  return Object.entries(env).every(([k, v]) => isShellEnvKey(k) && typeof v === \"string\");\n}","tryCatchPattern":null,"preventionTips":["Restrict env keys to uppercase SNAKE_CASE identifiers.","Do not forward arbitrary process.env keys that may be kebab/dotted.","Strip disallowed characters or reject the key before sending."],"tags":["cloudflare","sandbox","validation","shell-injection-guard","env"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}