{"record":{"id":"0e6ecc9d46c0f1d8","repo":"vercel-labs/agent-browser","slug":"invalid-environment-variable-name-key","errorCode":null,"errorMessage":"Invalid environment variable name: ${key}","messagePattern":"Invalid environment variable name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@agent-browser/sandbox/src/shared.ts","lineNumber":133,"sourceCode":"  if (result.exitCode !== 0) {\n    throw new AgentBrowserCommandError(result);\n  }\n  return result;\n}\n\nexport function defaultSessionName(prefix: string, id: string): string {\n  const safePrefix = sanitizeSessionPart(prefix) || \"agent-browser\";\n  const safeId = sanitizeSessionPart(id) || \"default\";\n  return truncateSessionName(`${safePrefix}-${safeId}`);\n}\n\nfunction formatShellEnv(env: Readonly<Record<string, string | undefined>> | undefined): string {\n  if (env === undefined) return \"\";\n  return Object.entries(env)\n    .filter((entry): entry is [string, string] => entry[1] !== undefined)\n    .map(([key, value]) => {\n      if (!SAFE_ENV_KEY.test(key)) {\n        throw new Error(`Invalid environment variable name: ${key}`);\n      }\n      return `${key}=${quoteShellArg(value)}`;\n    })\n    .join(\" \");\n}\n\nfunction parseJson<TJson>(value: string): TJson | null {\n  try {\n    return JSON.parse(value) as TJson;\n  } catch {\n    return null;\n  }\n}\n\nfunction sanitizeSessionPart(value: string): string {\n  return value.trim().replaceAll(/[^A-Za-z0-9_-]+/g, \"-\").replaceAll(/^-+|-+$/g, \"\");\n}\n","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/vercel-labs/agent-browser/blob/548b159b30eef119ccf6846c8bc807d0eaa3f6f8/packages/@agent-browser/sandbox/src/shared.ts#L115-L151","documentation":"Thrown by formatShellEnv (shared.ts:133) while buildShellCommand renders the env prefix `KEY=value ...` that is prepended to the agent-browser shell command. Every env key must match SAFE_ENV_KEY (/^[A-Za-z_][A-Za-z0-9_]*$/ in packages/@agent-browser/sandbox/src/shared.ts:41) because the key is interpolated unquoted into a shell command line. Any character outside letters, digits, and underscores, or a key that starts with a digit, is rejected to prevent shell injection. Keys whose value is undefined are silently filtered out before this check runs.","triggerScenarios":"Calling buildShellCommand(args, { env }) (shared.ts:88-94) with an env object whose key contains a hyphen, dot, space, equals sign, or slash (e.g. { \"MY-VAR\": \"1\" }, { \"NEXT_PUBLIC.foo\": \"x\" }), an empty-string key, or a key starting with a digit (\"1KEY\"). The throw happens synchronously, before the sandbox command is ever issued.","commonSituations":"Forwarding arbitrary request headers or cookie names as env keys; copying variable names from YAML/Next.js configs that allow dots or hyphens; building env objects dynamically from user input or from Object.keys(process.env) on platforms exposing unusual names; test fixtures reusing display-case labels as keys.","solutions":["Rename the offending key so it starts with a letter or underscore and contains only [A-Za-z0-9_] (the error message prints the exact key that failed).","If the env map comes from untrusted input, sanitize keys before calling buildShellCommand: strip invalid characters or map them to underscores, and drop keys that end up empty.","Keep an allowlist of the env names you actually intend to pass and construct the env object only from that list instead of forwarding a whole dictionary.","If the value (not the name) is the problem you want to ship, note that values are handled safely by quoteShellArg; only the NAME is validated, so fix the name rather than quoting it."],"exampleFix":"// before\nconst command = buildShellCommand([\"open\", url], {\n  env: { \"MY-VAR\": \"value\", API_TOKEN: token },\n});\n\n// after\nconst command = buildShellCommand([\"open\", url], {\n  env: { MY_VAR: \"value\", API_TOKEN: token },\n});","handlingStrategy":"validation","validationCode":"const SAFE_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction buildSafeEnv(env: Readonly<Record<string, string | undefined>>): Record<string, string> {\n  const safe: Record<string, string> = {};\n  for (const [key, value] of Object.entries(env)) {\n    if (value === undefined) continue;\n    if (!SAFE_ENV_KEY.test(key)) {\n      throw new Error(`Invalid environment variable name: ${key}`);\n    }\n    safe[key] = value;\n  }\n  return safe;\n}\n\n// run BEFORE buildShellCommand\nconst env = buildSafeEnv({ \"MY-VAR\": \"x\", OK_VAR: \"y\" }); // fails fast on MY-VAR","typeGuard":"function isValidEnvKey(key: string): boolean {\n  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);\n}\n\nfunction assertValidEnv(env: Readonly<Record<string, string | undefined>>): void {\n  for (const key of Object.keys(env)) {\n    if (!isValidEnvKey(key)) {\n      throw new Error(`Invalid environment variable name: ${key}`);\n    }\n  }\n}","tryCatchPattern":"try {\n  const command = buildShellCommand(args, { env });\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"Invalid environment variable name:\")) {\n    const badKey = error.message.split(\": \")[1];\n    // report the offending key to the user/config layer instead of retrying\n    throw new Error(`Config error: env key '${badKey}' must match /^[A-Za-z_][A-Za-z0-9_]*$/`);\n  }\n  throw error;\n}","preventionTips":["Derive env objects from a fixed allowlist of variable names instead of forwarding whole dictionaries from user input or headers.","When mapping external names to env, normalize them once (replace invalid characters with _, uppercase) and cache the mapping.","Add a unit test that runs your env construction through /^[A-Za-z_][A-Za-z0-9_]*$/ so regressions fail in CI, not at shell-build time.","Remember undefined values are silently dropped; filter them yourself if you want typo'd keys to be caught."],"tags":["environment","validation","shell-injection","sandbox"],"backgroundTag":null,"analyzedSha":"548b159b30eef119ccf6846c8bc807d0eaa3f6f8","analyzedAt":"2026-08-16T10:12:14.925Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}