Skip to content

fix(mcp): preserve ':-' inside ${VAR:-default} default values - #1933

Merged
kevincodex1 merged 1 commit into
Twigpine:mainfrom
0xfandom:fix/mcp-envexpansion-default-split
Jul 13, 2026
Merged

kevincodex1 merged 1 commit into
Twigpine:mainfrom
0xfandom:fix/mcp-envexpansion-default-split

Conversation

@0xfandom

@0xfandom 0xfandom commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

expandEnvVarsInString split the ${VAR:-default} syntax with:

// Split on :- ... (limit to 2 parts to preserve :- in defaults)
const [varName, defaultValue] = varContent.split(':-', 2)

The comment says the limit preserves :- inside the default, but JavaScript's String.split(sep, limit) caps the array length and discards the remainder — it is not a Python-style maxsplit that glues the tail back on. So a default containing :- is truncated at the first occurrence:

  • bash: ${VAR:-a:-b} -> a:-b
  • this code: -> a

Reachability

expandEnvVars (src/services/mcp/config.ts) runs this over user .mcp.json server fields — command, each args[], every env value, url, and every headers value (also via the plugin MCP/LSP integrations). Any ${VAR:-default} whose default contains :- (unset var) gets truncated.

Fix

Slice at the first :- with indexOf so any later :- stays in the default, matching bash.

Test

New suite: :--in-default preserved, set var wins, unset uses default, empty default ${VAR:-} -> '', missing var reported. The first case fails before the fix (a vs a:-b).

Summary by CodeRabbit

  • Bug Fixes

    • Corrected environment-variable expansion so default values containing :- are preserved.
    • Improved handling of unset variables, including empty defaults and missing-variable reporting.
  • Tests

    • Added coverage for variable expansion, defaults, empty values, and missing variables.

expandEnvVarsInString split the ${VAR:-default} syntax with
varContent.split(':-', 2). The code comment says the limit is there to
"preserve :- in defaults", but JavaScript's String.split(sep, limit) caps
the array length and discards the remainder — it is not a maxsplit that
glues the tail back on. So a default that itself contains ':-' is
truncated at the first occurrence: ${VAR:-a:-b} expands to "a" instead of
bash's "a:-b".

Slice at the first ':-' with indexOf so any later ':-' stays in the
default. This runs over user .mcp.json command/args/env/url/headers values.
Add coverage for the ':-'-in-default case plus set/unset/empty-default and
missing-var paths.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec5724bc-f7f3-4f7c-a09f-ccfd131ce9ac

📥 Commits

Reviewing files that changed from the base of the PR and between 4f971a1 and 8200f5b.

📒 Files selected for processing (2)
  • src/services/mcp/envExpansion.test.ts
  • src/services/mcp/envExpansion.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: CodeRabbit / Review
  • GitHub Check: smoke-and-tests (24.11.x)
  • GitHub Check: typecheck
  • GitHub Check: smoke-and-tests (22)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

TypeScript code in this repository must use strict mode and ESM imports.

Files:

  • src/services/mcp/envExpansion.test.ts
  • src/services/mcp/envExpansion.ts
**

⚙️ CodeRabbit configuration file

**: # AGENTS.md - AI Agent Coding Guide

This guide is for AI coding agents working in the OpenClaude repository. Read it before changing code, and also follow CONTRIBUTING.md for contributor policy, PR expectations, review follow-up, and project scope.

Project Snapshot

OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI.

The installed CLI runs on Node.js >=22.0.0. Bun is used for source builds, scripts, dependency management, and tests.

Work Style

  • Keep changes focused on one problem.
  • Prefer existing patterns in the file or nearby module.
  • Avoid unrelated formatting, renames, dependency changes, or broad rewrites.
  • Add or update tests when behavior changes.
  • Update docs when setup, commands, provider behavior, or user-facing behavior changes.
  • For new features, larger refactors, dependencies, or runtime changes, follow the issue-first guidance in CONTRIBUTING.md.

Stack And Conventions

  • TypeScript with strict mode and ESM imports.
  • React + Ink for terminal UI.
  • Bun lockfile and Bun scripts for development workflows.
  • Node runtime for the built CLI.

Common libraries and patterns:

  • chalk for terminal color.
  • commander for CLI argument parsing.
  • execa for child processes.
  • Existing service, provider, settings, permission, and UI patterns over new abstractions.

Repository Map

  • src/commands/ - slash and CLI command implementations.
  • src/components/ - React/Ink UI components.
  • src/services/ - API, MCP, OAuth, wiki, voice, and other service integrations.
  • src/tools/ - tool implementations.
  • src/utils/ - shared utilities.
  • src/integrations/ - provider and model integration metadata.
  • src/entrypoints/ - CLI, MCP, SDK, and generated public types.
  • src/tasks/ - local, remote, workflow, and monitor tas...

Files:

  • src/services/mcp/envExpansion.test.ts
  • src/services/mcp/envExpansion.ts
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/services/mcp/envExpansion.test.ts
  • src/services/mcp/envExpansion.ts
src/{skills,utils/plugins,services/mcp}/**

⚙️ CodeRabbit configuration file

src/{skills,utils/plugins,services/mcp}/**: Review skill/plugin/MCP behavior as a trust boundary. Check registry fetches, local and remote installs, path normalization, hash verification, revocation/trust metadata, tools_required handling, config-home behavior, and startup-time loading. Block on path traversal risk, unverified downloads, silent trust promotion, or unexpected code/tool activation.

Files:

  • src/services/mcp/envExpansion.test.ts
  • src/services/mcp/envExpansion.ts
{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}

⚙️ CodeRabbit configuration file

{src/**/*.test.ts,src/**/*.test.tsx,tests/**,scripts/**/*.test.ts,vscode-extension/**/*.test.js}: Review tests for meaningful coverage of the changed behavior, isolation of global/env/config state, async cleanup, fake timers, provider profile leaks, and Windows-compatible assumptions. Block when risky runtime changes lack focused regression coverage or tests assert implementation details while missing the user-visible behavior.

Files:

  • src/services/mcp/envExpansion.test.ts
🪛 ast-grep (0.44.1)
src/services/mcp/envExpansion.test.ts

[error] 8-8: Recursive/iterative merge copies attacker-controllable keys from a source object into a target via a computed property assignment without rejecting dangerous keys, allowing prototype pollution. Skip or block "proto", "constructor", and "prototype" keys (e.g. if (key === "__proto__" || key === "constructor" || key === "prototype") continue;), use a null-prototype object (Object.create(null)), or use a safe merge utility instead.
Context: for (const k of keys) saved[k] = process.env[k]
Note: [CWE-1321] Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

(prototype-pollution-recursive-merge-typescript)


[error] 13-16: Recursive/iterative merge copies attacker-controllable keys from a source object into a target via a computed property assignment without rejecting dangerous keys, allowing prototype pollution. Skip or block "proto", "constructor", and "prototype" keys (e.g. if (key === "__proto__" || key === "constructor" || key === "prototype") continue;), use a null-prototype object (Object.create(null)), or use a safe merge utility instead.
Context: for (const k of keys) {
if (saved[k] === undefined) delete process.env[k]
else process.env[k] = saved[k]
}
Note: [CWE-1321] Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

(prototype-pollution-recursive-merge-typescript)

🔇 Additional comments (2)
src/services/mcp/envExpansion.ts (1)

17-27: LGTM!

src/services/mcp/envExpansion.test.ts (1)

1-49: LGTM!


📝 Walkthrough

Walkthrough

Changes

Environment expansion

Layer / File(s) Summary
Default parsing and validation
src/services/mcp/envExpansion.ts, src/services/mcp/envExpansion.test.ts
${VAR:-default} parsing now preserves later :- sequences, with tests covering set, unset, empty-default, and missing-variable cases.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, scoped, and accurately summarizes the MCP env expansion fix.
Description check ✅ Passed The description explains the problem, reachability, fix, and tests, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Risk Surface Disclosed ✅ Passed PASS: The description calls out MCP .mcp.json/plugin reachability, and the patch is a narrow parsing fix with tests; no blocker is introduced.
No Hidden Policy Change ✅ Passed Only env-var expansion logic and tests changed; no product, trust, routing, telemetry, network, or permission-policy defaults were altered.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevincodex1
kevincodex1 merged commit 9bf9926 into Twigpine:main Jul 13, 2026
5 checks passed
hotmanxp pushed a commit to hotmanxp/openclaude that referenced this pull request Jul 15, 2026
…ne#1933)

expandEnvVarsInString split the ${VAR:-default} syntax with
varContent.split(':-', 2). The code comment says the limit is there to
"preserve :- in defaults", but JavaScript's String.split(sep, limit) caps
the array length and discards the remainder — it is not a maxsplit that
glues the tail back on. So a default that itself contains ':-' is
truncated at the first occurrence: ${VAR:-a:-b} expands to "a" instead of
bash's "a:-b".

Slice at the first ':-' with indexOf so any later ':-' stays in the
default. This runs over user .mcp.json command/args/env/url/headers values.
Add coverage for the ':-'-in-default case plus set/unset/empty-default and
missing-var paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants