{"record":{"id":"65818980626ad75f","repo":"paperclipai/paperclip","slug":"invalid-sandbox-environment-variable-key-key-658189","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/daytona/src/plugin.ts","lineNumber":728,"sourceCode":"// Build the one-shot exec command. Daytona's `executeCommand` runs the script\n// in a non-login shell, so it does not source `/etc/profile` on its own. The\n// Daytona reference image puts `node`, `claude`, and the other CLIs on the PATH\n// through `/etc/profile.d/00-restore-env.sh`, which only `/etc/profile` sources.\n// So the wrapper sources the login profiles itself; a non-login shell is then\n// enough to resolve the CLIs. The wrapper no longer sources `nvm.sh`; the\n// sandbox image supplies `node` on the PATH. See the sandbox runtime\n// requirements document.\nfunction buildLoginShellScript(input: {\n  command: string;\n  args: string[];\n  cwd?: string;\n  env?: Record<string, string>;\n  stdinPath?: string;\n}): string {\n  const callerEnv = input.env ?? {};\n  for (const key of Object.keys(callerEnv)) {\n    if (!isValidShellEnvKey(key)) {\n      throw new Error(`Invalid sandbox environment variable key: ${key}`);\n    }\n  }\n  // Caller env takes priority over noninteractive git credential defaults\n  const env = { ...NONINTERACTIVE_GIT_ENV, ...callerEnv };\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 redirectedCommand = input.stdinPath\n    ? `${commandParts} < ${shellQuote(input.stdinPath)}`\n    : commandParts;\n  // Each `executeCommand` call runs in its own shell, so we don't `exec`-\n  // replace it; running the command as the last `&&`-chained line is enough to\n  // surface the right exit code.\n  const finalLine = envArgs.length > 0\n    ? `env ${envArgs.join(\" \")} ${redirectedCommand}`\n    : redirectedCommand;\n  const lines = [","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L710-L746","documentation":"Thrown by buildLoginShellScript when an environment variable key passed in the caller env does not match /^[A-Za-z_][A-Za-z0-9_]*$/ (the POSIX shell identifier rule). Keys are validated before being injected into the login shell script to prevent shell injection via malformed variable names.","triggerScenarios":"A command executed in the sandbox is given an env Record whose key contains characters outside [A-Za-z0-9_] or starts with a digit — e.g. 'MY-VAR', '1VAR', 'var.name', or an empty string.","commonSituations":"Caller passes a kebab-case or dotted env var name; a secret manager injects keys with hyphens/dots; misconfigured environment metadata with numeric-leading keys; copy-paste of YAML keys into env without normalization.","solutions":["Normalize env var keys to match ^[A-Za-z_][A-Za-z0-9_]*$ (letters, digits, underscore; not starting with a digit).","Convert hyphens/dots to underscores before passing to the sandbox command env.","Validate keys upstream (in the plugin caller) with the same regex before submission."],"exampleFix":"// before: invalid key\nenv = { 'MY-VAR': 'x' }\n// after: POSIX-safe key\nenv = { 'MY_VAR': 'x' }","handlingStrategy":"validation","validationCode":"const SHELL_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;\nfunction allEnvKeysValid(env: Record<string, string>): boolean {\n  return Object.keys(env).every((k) => SHELL_ENV_KEY.test(k));\n}","typeGuard":"function isValidShellEnvKey(value: string): boolean {\n  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);\n}","tryCatchPattern":null,"preventionTips":["Normalize env var keys to POSIX identifiers (letters, digits, underscore; leading non-digit) before passing to sandbox commands.","Convert hyphens/dots to underscores when bridging from config/secret managers."],"tags":["daytona","env","shell","validation","security"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}