{"record":{"id":"2183af56e31e5dbb","repo":"CherryHQ/cherry-studio","slug":"invalid-mcp-package-env-environment-variable-k","errorCode":null,"errorMessage":"Invalid MCP package env: environment variable \"${key}\" is not allowed","messagePattern":"Invalid MCP package env: environment variable \"(.+?)\" is not allowed","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/McpPackageService.ts","lineNumber":263,"sourceCode":" *\n * @throws Error if a key/value contains a null byte or a key is denylisted\n */\nexport function buildResolvedEnv(\n  env: Record<string, string>,\n  extractDir: string,\n  userConfig?: Record<string, any>\n): Record<string, string> {\n  const resolvedEnv: Record<string, string> = {}\n\n  for (const [key, value] of Object.entries(env)) {\n    if (key.includes('\\0')) {\n      throw new Error('Invalid MCP package env: null byte detected in environment variable name')\n    }\n\n    // Denylist process-affecting variables (DYLD_* on macOS, plus exact matches above).\n    const canonicalKey = key.toUpperCase()\n    if (DXT_ENV_DENYLIST.includes(canonicalKey) || canonicalKey.startsWith('DYLD_')) {\n      throw new Error(`Invalid MCP package env: environment variable \"${key}\" is not allowed`)\n    }\n\n    const substituted = performVariableSubstitution(value, extractDir, userConfig)\n    if (substituted.includes('\\0')) {\n      throw new Error(`Invalid MCP package env: null byte detected in value of environment variable \"${key}\"`)\n    }\n\n    resolvedEnv[key] = substituted\n  }\n\n  return resolvedEnv\n}\n\nexport function validatePackageUploadPayload(\n  fileBuffer: ArrayBuffer | NodeJS.ArrayBufferView,\n  fileName: string,\n  packageFormat: McpPackageFormat\n): Buffer {","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/McpPackageService.ts#L245-L281","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","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.","Repackage and re-upload."],"exampleFix":"// manifest.json - before\n\"env\": { \"NODE_OPTIONS\": \"--max-old-space-size=4096\" }\n// after\n\"env\": {},\n\"args\": [\"--max-old-space-size=4096\"]  // pass to the runtime via args instead","handlingStrategy":"validation","validationCode":"const DXT_ENV_DENYLIST = ['NODE_OPTIONS', 'LD_PRELOAD', 'LD_LIBRARY_PATH']\nfunction isAllowedEnvKey(key: string): boolean {\n  const canonical = key.toUpperCase()\n  return !DXT_ENV_DENYLIST.includes(canonical) && !canonical.startsWith('DYLD_')\n}\nfunction allEnvKeysAllowed(env: Record<string, string>): boolean {\n  return Object.keys(env).every(isAllowedEnvKey)\n}","typeGuard":"function isNonDeniedEnvKey(key: string): boolean {\n  const canonical = key.toUpperCase()\n  return !['NODE_OPTIONS', 'LD_PRELOAD', 'LD_LIBRARY_PATH'].includes(canonical)\n    && !canonical.startsWith('DYLD_')\n}","tryCatchPattern":null,"preventionTips":["Document the denylist in your package-author guide so authors do not attempt to set NODE_OPTIONS or LD_PRELOAD.","Run a manifest linter in CI that flags denylisted env keys before publishing a package.","If a runtime genuinely needs an env var to tune behavior, pass it via args instead."],"tags":["security","env","denylist","mcp","manifest","validation","code-injection"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}