Skip to content

MCP server  args  templating corrupts values containing nested  ${VAR:-${VAR2:-$(cmd)}}  shell parameter expansion (silent auth/token corruption) #4239

Description

@roxyland

Describe the bug

Environment:

• GitHub Copilot CLI version: 1.0.74
• OS: macOS (Darwin)
• Config:  ~/.copilot/mcp-config.json , stdio MCP server launched via  sh -c "..." 

Summary:
When an MCP server's  args  array contains a shell command string with nested bash parameter-expansion defaults ( ${A:-${B:-$(cmd)}} ), and the outer variable ( A ) is already present in Copilot CLI's own process environment, Copilot CLI appears to perform its own variable substitution pass over the  args  string before handing it to the shell. This pass does not correctly track nested  { / }  pairs — it matches up to the first  }  it finds, substitutes the outer variable's current env value there, and leaves the remainder of the original nested expression (including its own trailing  } ) behind as a literal string suffix. This silently corrupts the resulting value (e.g. an auth token gains a stray trailing  } ), producing hard-to-diagnose authentication failures with no error at MCP startup.

Repro config ( mcp-config.json  entry):

{
"jira": {
"command": "sh",
"args": [
"-c",
"export JIRA_ACCESS_TOKEN="${JIRA_ACCESS_TOKEN:-${JIRA_PAT:-$(keychain.sh get JIRA_PAT)}}" JIRA_BASE_URL="https://example.atlassian.net"; exec node jira-mcp-server.mjs"
]
}
}

Affected version

GitHub Copilot CLI 1.0.74.

Steps to reproduce the behavior

Repro steps: ┃

  1. Ensure  JIRA_ACCESS_TOKEN  is already exported in the shell/parent environment that launches Copilot CLI (e.g. via a startup script), with some valid value  TOKEN123 . ┃
  2. Configure an MCP server whose  args  includes the nested-default pattern above. ┃
  3. Start Copilot CLI, and let it spawn the MCP server. ┃
  4. Inspect the spawned child process's actual environment (e.g.  ps eww -p   on macOS, or  /proc//environ  on Linux). ┃

    Expected:  JIRA_ACCESS_TOKEN  in the child process env equals  TOKEN123  (the shell resolves  ${JIRA_ACCESS_TOKEN:-...}  itself, since  JIRA_ACCESS_TOKEN  is already set — no substitution ┃
    by Copilot CLI should be needed or should not corrupt the string if performed). ┃

    Actual:  JIRA_ACCESS_TOKEN  in the child process env is  TOKEN123}  — a stray trailing  }  character is appended, corrupting the value. This causes the MCP server's outbound API calls to ┃
    fail authentication (e.g. Jira returns "Field 'assignee' does not exist or cannot be viewed by anonymous users" because the malformed Bearer token is rejected and the request is treated ┃
    as unauthenticated). ┃

    Impact: Any MCP server config relying on the common bash idiom of nested  ${VAR:-default}  fallback chains (a standard pattern for "env var, else secret store, else command substitution") ┃
    will have secrets/tokens silently corrupted whenever the outer variable happens to already be set in Copilot CLI's environment — with no startup error, only a downstream, misleading auth ┃
    failure from the third-party API. This is especially dangerous because: ┃

    • The corruption is silent (no warning/error from Copilot CLI itself). ┃
    • The same MCP config file works correctly in other tools/clients (e.g. Amp) that don't pre-template  args , making it look like a server-side/token/auth issue rather than a CLI ┃
    templating bug. ┃

    Suggested fix: Either (a) don't perform Copilot CLI's own variable substitution on MCP server  args  strings that are passed to a  command /shell launcher — let the shell handle its own ┃
    expansion — or (b) if substitution is intentional (e.g. to support  ${HOME} ), use a proper brace-depth-aware parser (or a stricter regex like  ${(\w+)}  with no  :-  support) rather ┃
    than matching to the first  } .

Expected behavior

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:mcpMCP server configuration, discovery, connectivity, OAuth, policy, and registry

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions