CherryHQ/cherry-studio · error · Error

Invalid MCP package env: null byte detected in value of envi

Error message

Invalid MCP package env: null byte detected in value of environment variable "${key}"

What it means

Thrown by buildResolvedEnv after variable substitution: an env value, once ${__dirname}, ${HOME}, ${user_config.*} etc. are expanded, contained a NUL byte. The check runs on the substituted value (performVariableSubstitution output), so the null can come from the manifest literally or be introduced by a user_config value injected via the ${user_config.KEY} template.

Source

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

  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 {
  if (typeof fileName !== 'string') {
    throw new Error('Invalid MCP package upload: file name must be a string')
  }

  const trimmedFileName = fileName.trim()

View on GitHub (pinned to 726446b54c)

Solutions

  1. Identify whether the null originated in the manifest value or in a ${user_config.*} substitution. Inspect the manifest's env values first.
  2. If it came from user_config, sanitize the user-supplied value in the package config form before it reaches the substitution (strip \0 and other control characters).
  3. Repackage the manifest with a clean value or re-enter the user config and retry.
Defensive patterns

Strategy: validation

Validate before calling

function hasNoNullByte(value: unknown): boolean {
  return typeof value === 'string' && !value.includes('\0')
}
function cleanEnvValues(env: Record<string, string>, userConfig?: Record<string, any>): boolean {
  // Mirror performVariableSubstitution only for user_config; __dirname/HOME do not inject nulls.
  return Object.entries(env).every(([, v]) => {
    if (!v.includes('\0')) return true
    if (!userConfig) return false
    const substituted = v.replace(/\$\{user_config\.([^}]+)\}/g, (_m, k) => userConfig[k] ?? _m)
    return !substituted.includes('\0')
  })
}

Type guard

function isNullByteFree(value: unknown): value is string {
  return typeof value === 'string' && !value.includes('\0')
}

Prevention

When it happens

Trigger: Manifest env value contains a literal \0; or a ${user_config.field} placeholder is filled by a user-supplied value that contains \0 and the substitution replaces the placeholder with it.

Common situations: A user-config text field accepted a pasted binary blob; a manifest author copied a value from a terminal that included a control character; a malicious package tried to smuggle a null past key-level validation by putting it in the value.

Related errors


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