paperclipai/paperclip · error

Invalid sandbox environment variable key: ${key}

Error message

Invalid sandbox environment variable key: ${key}

What it means

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.

Source

Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:728

// Build the one-shot exec command. Daytona's `executeCommand` runs the script
// in a non-login shell, so it does not source `/etc/profile` on its own. The
// Daytona reference image puts `node`, `claude`, and the other CLIs on the PATH
// through `/etc/profile.d/00-restore-env.sh`, which only `/etc/profile` sources.
// So the wrapper sources the login profiles itself; a non-login shell is then
// enough to resolve the CLIs. The wrapper no longer sources `nvm.sh`; the
// sandbox image supplies `node` on the PATH. See the sandbox runtime
// requirements document.
function buildLoginShellScript(input: {
  command: string;
  args: string[];
  cwd?: string;
  env?: Record<string, string>;
  stdinPath?: string;
}): string {
  const callerEnv = input.env ?? {};
  for (const key of Object.keys(callerEnv)) {
    if (!isValidShellEnvKey(key)) {
      throw new Error(`Invalid sandbox environment variable key: ${key}`);
    }
  }
  // Caller env takes priority over noninteractive git credential defaults
  const env = { ...NONINTERACTIVE_GIT_ENV, ...callerEnv };
  const envArgs = Object.entries(env)
    .filter((entry): entry is [string, string] => typeof entry[1] === "string")
    .map(([key, value]) => `${key}=${shellQuote(value)}`);
  const commandParts = [shellQuote(input.command), ...input.args.map(shellQuote)].join(" ");
  const redirectedCommand = input.stdinPath
    ? `${commandParts} < ${shellQuote(input.stdinPath)}`
    : commandParts;
  // Each `executeCommand` call runs in its own shell, so we don't `exec`-
  // replace it; running the command as the last `&&`-chained line is enough to
  // surface the right exit code.
  const finalLine = envArgs.length > 0
    ? `env ${envArgs.join(" ")} ${redirectedCommand}`
    : redirectedCommand;
  const lines = [

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Normalize env var keys to match ^[A-Za-z_][A-Za-z0-9_]*$ (letters, digits, underscore; not starting with a digit).
  2. Convert hyphens/dots to underscores before passing to the sandbox command env.
  3. Validate keys upstream (in the plugin caller) with the same regex before submission.

Example fix

// before: invalid key
env = { 'MY-VAR': 'x' }
// after: POSIX-safe key
env = { 'MY_VAR': 'x' }
Defensive patterns

Strategy: validation

Validate before calling

const SHELL_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
function allEnvKeysValid(env: Record<string, string>): boolean {
  return Object.keys(env).every((k) => SHELL_ENV_KEY.test(k));
}

Type guard

function isValidShellEnvKey(value: string): boolean {
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/65818980626ad75f. Report an issue: GitHub.