CherryHQ/cherry-studio · error · Error

Invalid MCP package env: environment variable "${key}" is no

Error message

Invalid MCP package env: environment variable "${key}" is not allowed

What it means

Thrown by buildResolvedEnv when an environment variable key matches the process-affecting denylist. The denylist is NODE_OPTIONS, LD_PRELOAD, LD_LIBRARY_PATH (case-insensitive, compared via canonicalKey.toUpperCase()) plus any key starting with DYLD_. These variables can change code loading in the spawned MCP server (preload a shared library, inject Node flags), so a manifest setting them could run arbitrary code despite command/arg validation.

Source

Thrown at src/main/ai/mcp/McpPackageService.ts:263

 *
 * @throws Error if a key/value contains a null byte or a key is denylisted
 */
export function buildResolvedEnv(
  env: Record<string, string>,
  extractDir: string,
  userConfig?: Record<string, any>
): Record<string, string> {
  const resolvedEnv: Record<string, string> = {}

  for (const [key, value] of Object.entries(env)) {
    if (key.includes('\0')) {
      throw new Error('Invalid MCP package env: null byte detected in environment variable name')
    }

    // Denylist process-affecting variables (DYLD_* on macOS, plus exact matches above).
    const canonicalKey = key.toUpperCase()
    if (DXT_ENV_DENYLIST.includes(canonicalKey) || canonicalKey.startsWith('DYLD_')) {
      throw new Error(`Invalid MCP package env: environment variable "${key}" is not allowed`)
    }

    const substituted = performVariableSubstitution(value, extractDir, userConfig)
    if (substituted.includes('\0')) {
      throw new Error(`Invalid MCP package env: null byte detected in value of environment variable "${key}"`)
    }

    resolvedEnv[key] = substituted
  }

  return resolvedEnv
}

export function validatePackageUploadPayload(
  fileBuffer: ArrayBuffer | NodeJS.ArrayBufferView,
  fileName: string,
  packageFormat: McpPackageFormat
): Buffer {

View on GitHub (pinned to 726446b54c)

Solutions

  1. Remove the denylisted key from the manifest's env (and platform_overrides.env) and find an alternative that does not alter process code-loading (e.g. set memory limits via a wrapper script rather than NODE_OPTIONS).
  2. If the variable is required for the server to run, bundle the prerequisite into the package itself (the .so / .node file inside the extract dir) and reference it via a relative path the command resolves, not via LD_PRELOAD.
  3. Repackage and re-upload.

Example fix

// manifest.json - before
"env": { "NODE_OPTIONS": "--max-old-space-size=4096" }
// after
"env": {},
"args": ["--max-old-space-size=4096"]  // pass to the runtime via args instead
Defensive patterns

Strategy: validation

Validate before calling

const DXT_ENV_DENYLIST = ['NODE_OPTIONS', 'LD_PRELOAD', 'LD_LIBRARY_PATH']
function isAllowedEnvKey(key: string): boolean {
  const canonical = key.toUpperCase()
  return !DXT_ENV_DENYLIST.includes(canonical) && !canonical.startsWith('DYLD_')
}
function allEnvKeysAllowed(env: Record<string, string>): boolean {
  return Object.keys(env).every(isAllowedEnvKey)
}

Type guard

function isNonDeniedEnvKey(key: string): boolean {
  const canonical = key.toUpperCase()
  return !['NODE_OPTIONS', 'LD_PRELOAD', 'LD_LIBRARY_PATH'].includes(canonical)
    && !canonical.startsWith('DYLD_')
}

Prevention

When it happens

Trigger: A manifest env map contains {"NODE_OPTIONS": "--require /tmp/evil.js"}, {"LD_PRELOAD": "/x.so"}, {"DYLD_INSERT_LIBRARIES": "..."}, or any casing variant like "node_options". Platform overrides that merge an env map with one of these keys also trigger it.

Common situations: A package author legitimately wanted to tune Node memory flags via NODE_OPTIONS; a package copied a shell environment that included LD_LIBRARY_PATH; a macOS-targeted override set a DYLD_ variable for code signing reasons.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/2183af56e31e5dbb. Report an issue: GitHub.