paperclipai/paperclip · error
Invalid sandbox environment variable key: ${key}
Error message
Invalid sandbox environment variable key: ${key} What it means
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.
Source
Thrown at packages/plugins/sandbox-providers/cloudflare/bridge-template/src/exec.ts:39
}
function randomToken(): string {
const uuid = globalThis.crypto?.randomUUID?.();
if (typeof uuid === "string" && uuid.length > 0) return uuid.replace(/[^a-zA-Z0-9-]/g, "");
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
export function buildLoginShellScript(input: {
command: string;
args: string[];
cwd?: string;
env?: Record<string, string>;
stdinFile?: string | null;
}): string {
const env = input.env ?? {};
for (const key of Object.keys(env)) {
if (!isValidShellEnvKey(key)) {
throw new Error(`Invalid sandbox environment variable key: ${key}`);
}
}
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 stdinRedirect = input.stdinFile ? ` < ${shellQuote(input.stdinFile)}` : "";
// Source the common login profiles before exec so the command runs with the
// interactive-shell PATH. The wrapper sources no `nvm.sh`; the sandbox image
// supplies node on the PATH.
const lines = [
'if [ -f /etc/profile ]; then . /etc/profile >/dev/null 2>&1 || true; fi',
'if [ -f "$HOME/.profile" ]; then . "$HOME/.profile" >/dev/null 2>&1 || true; fi',
'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',
'if [ -f "$HOME/.zprofile" ]; then . "$HOME/.zprofile" >/dev/null 2>&1 || true; fi',
];
if (input.cwd) {View on GitHub (pinned to 67001ec6eb)
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.
Example fix
// before
env: { "MY-VAR": "1", "2ND": "x" }
// after
env: { MY_VAR: "1", SECOND: "x" } Defensive patterns
Strategy: validation
Validate before calling
const SHELL_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
function sanitizeEnvKeys(env) {
const out = {};
for (const [k, v] of Object.entries(env ?? {})) {
if (!SHELL_ENV_KEY.test(k)) throw new Error(`Invalid sandbox env key: ${k}`);
if (typeof v === "string") out[k] = v;
}
return out;
} Type guard
function isShellEnvKey(k: string): boolean {
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(k);
}
function isValidEnvRecord(env: unknown): env is Record<string, string> {
if (typeof env !== "object" || env === null) return false;
return Object.entries(env).every(([k, v]) => isShellEnvKey(k) && typeof v === "string");
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Environment variable ${envName} is empty or not set.
- Environment variable ${opts.apiKeyEnv.trim()} is not set
- Environment variable ${opts.valueEnv} is empty or unset.
- networkAllowlist[${index}] must not be empty.
- networkAllowlist[${index}] must be a hostname, hostname:port
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/d08dda10505ffb58.
Report an issue: GitHub.