Skip to content

Fix MCP gateway environment injection - #50924

Merged
pelikhan merged 8 commits into
mainfrom
fix/ghsa-j77w-g4jj-hp99
Aug 7, 2026
Merged

Fix MCP gateway environment injection#50924
pelikhan merged 8 commits into
mainfrom
fix/ghsa-j77w-g4jj-hp99

Conversation

@pelikhan

@pelikhan pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Overview

Fixes a security vulnerability (GHSA-j77w-g4jj-hp99) in the MCP gateway's handling of sandbox.mcp.env custom environment variables. Previously, user-supplied values were interpolated directly into the generated GitHub Actions shell script (export NAME=VALUE) and into the serialized Docker command string (-e NAME flags forwarded from the shell environment). Both paths were exploitable: shell metacharacters (;, backticks, newlines) in a value could inject arbitrary commands into the runner shell before the gateway process started, and a value assigned to BASH_ENV would be sourced by Bash on the host before the run script even executed.

Security fix

Custom env values are now routed through compiler-controlled, indexed transport variables instead of being embedded in shell code or the Docker command string:

  • The Go compiler writes each sandbox.mcp.env value under GH_AW_MCP_GATEWAY_ENV_0, GH_AW_MCP_GATEWAY_ENV_1, ... in the step's YAML env: block (safe, non-shell-interpolated) plus a manifest variable GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES (JSON array of the original names).
  • A marker token __GH_AW_MCP_GATEWAY_CUSTOM_ENV__ is placed in the serialized Docker command string instead of literal -e NAME flags.
  • At runtime, start_mcp_gateway.cjs replaces the marker with atomic -e NAME=VALUE Docker arguments built directly from process.env, so values never pass through shell parsing or string splitting.
  • Environment variable names are validated against ^[A-Z_][A-Z0-9_]*$ both at compile time (Go, pkg/workflow/sandbox_validation.go) and at runtime (JS, start_mcp_gateway.cjs).
  • Names in the GH_AW_MCP_GATEWAY_ namespace (including GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES) are reserved for internal transport and rejected during validation to prevent collisions/spoofing.

Key changes

Runtime launcher — actions/setup/js/start_mcp_gateway.cjs
  • Added injectCustomGatewayEnvArgs(args, env): finds the __GH_AW_MCP_GATEWAY_CUSTOM_ENV__ marker in the split Docker args, parses GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES, validates names, and replaces the marker with atomic -e NAME=VALUE pairs sourced from indexed GH_AW_MCP_GATEWAY_ENV_N variables. Missing indexed values become empty strings (deterministic name→slot mapping).
  • Invoked right after the Docker command string is split into args in main().
  • Exported for testing.
Compiler — pkg/workflow/mcp_setup_gateway.go
  • Added constants mcpGatewayCustomEnvNamesVar, mcpGatewayCustomEnvTransportPrefix, mcpGatewayCustomEnvMarker.
  • writeMCPGatewayStepEnv now takes gatewayEnvVars and writes them as sorted, indexed GH_AW_MCP_GATEWAY_ENV_N step-env entries plus the GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES JSON manifest, skipping any other var name that collides with the reserved namespace.
  • Removed the old export NAME=VALUE emission from writeMCPGatewayExports (custom values no longer touch the run script).
  • appendMCPGatewayCustomAndHTTPEnvFlags now emits the __GH_AW_MCP_GATEWAY_CUSTOM_ENV__ marker instead of literal -e NAME flags in the Docker command string.
  • Added isReservedMCPGatewayTransportEnvVar helper.
Validation — pkg/workflow/sandbox_validation.go
  • Added mcpGatewayEnvNamePattern (^[A-Z_][A-Z0-9_]*$) and validation in validateSandboxConfig that rejects sandbox.mcp.env names that are malformed or fall in the reserved GH_AW_MCP_GATEWAY_ transport namespace, with actionable error messages/examples.
Docs — docs/adr/50924-secure-mcp-gateway-env-injection.md
  • New ADR documenting the vulnerability, the compiler-controlled transport-variable decision, two rejected alternatives (shell-escaping values; quoting values in the Docker command string), and consequences (positive/negative/neutral).

Testing

  • actions/setup/js/start_mcp_gateway.test.cjs: new tests for injectCustomGatewayEnvArgs covering hostile values (Docker flag/shell injection attempts), multi-value index mapping with empty values, missing transport metadata, commands without the marker, malformed JSON, and invalid names.
  • pkg/workflow/mcp_gateway_env_security_test.go (new, !integration): verifies custom env values never appear as literal export statements, step-env keys, or in the run script; verifies overriding of generated step env, BASH_ENV and GitHub-expression values are treated as opaque data, reserved-name collision handling, and the marker/transport-variable contract with the JS launcher.
  • pkg/workflow/mcp_gateway_env_security_integration_test.go (new, integration build tag): compiles real workflow markdown and asserts the generated lock file's "Start MCP Gateway" step contains the transport variables (not literal exports) and that a shared-MCP-config name collision with reserved transport names does not leak or duplicate metadata.
  • pkg/workflow/sandbox_validation_test.go: new cases for valid/invalid/reserved sandbox.mcp.env names.

References:> Generated by PR Description Updater for #50924 · auto · 96.7 AIC · ⊞ 6.8K ·

Move custom gateway environment values out of generated shell and Docker command strings. Forward them as atomic spawn arguments and validate names defensively.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4a7b7c8f-8100-4da5-bf42-21cd531c8b0a
@pelikhan
pelikhan marked this pull request as ready for review August 6, 2026 21:38
Copilot AI balanced review requested due to automatic review settings August 6, 2026 21:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Secures MCP gateway environment injection by transporting custom values separately and adding them as atomic Docker arguments.

Changes:

  • Validates custom environment variable names.
  • Prevents shell and Docker argument injection.
  • Adds security regression tests.
Show a summary per file
File Description
pkg/workflow/sandbox_validation.go Validates MCP environment names.
pkg/workflow/sandbox_validation_test.go Tests name validation.
pkg/workflow/mcp_setup_gateway.go Adds secure environment transport.
pkg/workflow/mcp_gateway_env_security_test.go Tests injection protections.
actions/setup/js/start_mcp_gateway.cjs Injects atomic Docker arguments.
actions/setup/js/start_mcp_gateway.test.cjs Tests hostile argument handling.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread pkg/workflow/mcp_setup_gateway.go Outdated
Comment on lines +91 to +94
transportEnvVarNames := make(map[string]struct{}, len(customEnvVarNames))
for i := range customEnvVarNames {
transportEnvVarNames[mcpGatewayCustomEnvTransportName(i)] = struct{}{}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Handled in 0568afe, and reinforced in 4dc11a4 by reserving the entire GH_AW_MCP_GATEWAY_ENV_* namespace during step-env filtering and keeping regression coverage for the collision path.

Add cross-language contract, mapping, malformed metadata, collision, and expression-value regression tests following independent review.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4a7b7c8f-8100-4da5-bf42-21cd531c8b0a
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please refresh this PR branch if needed, review any outstanding feedback, and run the pr-finisher skill before handing back to maintainers. Run: https://github.com/github/gh-aw/actions/runs/31128319337

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 10.3 AIC · ⊞ 5.9K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@copilot Please refresh this PR branch if needed, review any outstanding feedback, and run the pr-finisher skill before handing back to maintainers. Run: https://github.com/github/gh-aw/actions/runs...

Addressed the outstanding MCP gateway feedback in 0568afe by reserving GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES alongside the indexed transport keys and adding a collision regression. Ran the PR finisher validation flow locally (make fmt, make lint, make test-unit, make test, make recompile, plus BASE_REF=origin/main make agent-report-progress).

Copilot AI requested a review from gh-aw-bot August 6, 2026 22:32
@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

/review

@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

/matt

@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot add integration workflow tests

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Hey @pelikhan 👋 — excellent work on this security fix! The MCP gateway environment injection protection is comprehensive and well-tested.

What's particularly strong about this PR:

  • Focused change — isolates the vulnerability to one concern: keeping custom env values out of shell scripts and Docker command strings via atomic marker-based injection
  • Thorough test coverage — both JavaScript (hostile Docker injection patterns, newlines, backticks) and Go (shell metacharacter escaping, BASH_ENV protection, GitHub expression handling)
  • Clear security narrative — the PR explicitly addresses GHSA-j77w-g4jj-hp99 with validation commands included
  • Deterministic metadata transport — the GH_AW_MCP_GATEWAY_ENV_${index} pattern prevents override collisions and preserves environment variable order

This is ready for review and merge. 🚀

Generated by ✅ Contribution Check · auto · 55.1 AIC · ⊞ 8.7K ·

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@copilot add integration workflow tests

Addressed in bfc63e0 by adding workflow integration coverage for MCP gateway custom env transport and the reserved metadata-name collision path.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for PR Code Quality Reviewer. Review the logs for details.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 92/100 Excellent

Analysis: This PR fixes CVE GHSA-j77w-g4jj-hp99 with 17 comprehensive security tests covering hostile payload injection (shell, Docker, BASH_ENV), boundary conditions (empty strings, expressions), and cross-layer integration.

Key metrics:

  • Design tests: 88% (15/17 tests verify user-facing security contracts)
  • Implementation tests: 12% (well below 30% threshold)
  • Coding violations: Zero (proper build tags, no mock libraries, descriptive assertions)
  • Test inflation ratio: 4.3:1 (justified for security-critical CVE)

No violations detected. All build tags present, no forbidden mocks, error paths comprehensively tested.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — commenting with 3 targeted issues.

📋 Key Themes & Highlights

Key Themes

  • Pattern drift risk: The env-name validation regex is duplicated in Go and JS with no cross-language synchronisation test, creating a latent divergence risk.
  • Silent collision drop: MCP env vars that collide with reserved transport names are silently dropped; a warning or compile error would improve debuggability.
  • Test coverage gap: The newline injection scenario is tested for correct array output but not for array length invariance, leaving room for a future split-on-newline regression.

Positive Highlights

  • ✅ The marker-based approach is elegant — hostile values never touch the shell because they bypass the dockerCommand string entirely and land as atomic spawn() arguments.
  • TestMCPGatewayCustomEnvCommandContract cross-checking the JS file from Go tests is a great pattern for enforcing cross-language contracts.
  • ✅ Deterministic index mapping (sorted keys → GH_AW_MCP_GATEWAY_ENV_0..N) makes the transport scheme reproducible and testable.
  • ✅ Excellent regression coverage: shell metacharacters, backticks, BASH_ENV, newlines, and Docker argument injection are all exercised.
  • ✅ Compile-time name validation in sandbox_validation.go closes the attack surface before reaching the runtime.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 54.7 AIC · ⊞ 7.1K
Comment /matt to run again

@pelikhan
pelikhan marked this pull request as draft August 6, 2026 23:35
@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot add workflow integration test that ensures the filtering is happening

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Test Quality Sentinel. Review the logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Score: 93/100 — ✅ Excellent

Executive Summary

PR #50924 demonstrates exceptional test coverage for the MCP gateway environment injection security fix (GHSA-j77w-g4jj-hp99). All 15 new tests are design-level behavioral contracts protecting critical security invariants.

📊 Metrics
Metric Value Status
Design tests 15/15 (100%) ✅ Excellent
Edge-case coverage 14/15 (93%) ✅ Excellent
Implementation tests 0/15 (0%) ✅ Well below 30% threshold
Test inflation Mild (1.9–5.8:1) ✅ Justified by security domain
Violations 0 ✅ PASS

Files analyzed:

  • actions/setup/js/start_mcp_gateway.test.cjs (+6 tests)
  • pkg/workflow/mcp_gateway_env_security_test.go (+7 unit tests, new)
  • pkg/workflow/mcp_gateway_env_security_integration_test.go (+2 integration tests, new)
  • pkg/workflow/sandbox_validation_test.go (+1 test variant)

Test Classification Breakdown

Security & Behavioral Contracts (12 high-value tests)
Test Purpose Coverage Verdict
Go Unit: TestMCPGatewayCustomEnvValuesStayOutOfRunScript Shell injection prevention Injection vectors: semicolons, newlines, backticks ✅ DESIGN_TEST
Go Unit: TestMCPGatewayCustomEnvDoesNotSetBashEnvOnHost BASH_ENV isolation (critical) Host environment boundary ✅ DESIGN_TEST
Go Unit: TestMCPGatewayCustomEnvReservesTransportMetadataName Collision prevention Transport metadata uniqueness ✅ DESIGN_TEST
Go Unit: TestMCPGatewayCustomEnvCommandContract Docker command format + Go/JS sync Cross-component verification ✅ DESIGN_TEST
JS: "passes hostile values as one atomic Docker argument" Atomic packaging of injections Shell metacharacter safety ✅ DESIGN_TEST
JS: "preserves sorted multi-value index mapping and empty values" Index mapping + empty value handling Edge cases (empty strings, multiline) ✅ DESIGN_TEST
Go Unit: TestMCPGatewayCustomEnvOverridesGeneratedStepEnv Override precedence Reserved name collision ✅ DESIGN_TEST
Go Unit: TestMCPGatewayCustomEnvPreservesGitHubExpressionAsData Data integrity Expression literal preservation ✅ DESIGN_TEST
Go Integration: TestMCPGatewayCustomEnvIntegrationUsesTransportVariables Full workflow compilation End-to-end verification ✅ DESIGN_TEST
Go Integration: TestMCPGatewayCustomEnvIntegrationKeepsMetadataNameUnique Multi-file collision handling Import + collision prevention ✅ DESIGN_TEST
Go Unit: TestValidateSandboxConfigMCPEnvironmentVariableNames Name validation (2 sub-tests) Regex enforcement + error messages ✅ DESIGN_TEST
JS: "uses an empty value when transport metadata is missing" Graceful degradation Sparse indexing ✅ DESIGN_TEST
Error Handling Tests (2 medium-value tests)
Test Purpose Coverage Verdict
JS: "rejects malformed JSON metadata" Malformed input detection JSON parsing errors ✅ IMPLEMENTATION_TEST
JS: "rejects malformed or unsafe environment variable names" Name validation errors Invalid patterns (hyphens) ✅ IMPLEMENTATION_TEST
Idempotency & Safety (1 medium-value test)
Test Purpose Coverage Verdict
JS: "leaves commands without the marker unchanged" No-op safety Marker-absent commands ✅ DESIGN_TEST

Compliance & Quality Signals

Build Tags: All new Go test files comply with mandatory //go:build directives

  • mcp_gateway_env_security_test.go: //go:build !integration
  • mcp_gateway_env_security_integration_test.go: //go:build integration

Mock Library Policy: No violations detected

  • No gomock, testify/mock, .EXPECT(), or .On() usage
  • Uses only testify/assert and testify/require (acceptable)
  • JS uses vitest native expect/toThrow (acceptable)

Test Inflation (Security-Justified):

  • JS: 1.91:1 (acceptable, <2:1)
  • Go unit: 5.6:1 (justified by security domain + helper function testing)
  • Go integration: 5.8:1 (justified by full-workflow verification)

Scoring Rationale

Design tests (40 pts):      15/15 = 40.0 points ✓
Edge-case coverage (30 pts): 14/15 = 27.9 points ✓
Duplicate detection (20 pts): 0 clusters = 20.0 points ✓
Test inflation (10 pts):      Mild penalty = 5.0 points ✓

Final Score = 92.9 → 93/100

Failure Conditions: PASS ✅

  • implementation_tests / total_new_tests > 0.30: 0/15 = 0% (PASS)
  • Guideline violations: None (PASS)
  • Missing build tags: None (PASS)
  • Mock library violations: None (PASS)

✅ Recommendation: APPROVE

This PR delivers production-quality security tests with comprehensive edge-case coverage. The test-to-code inflation ratio reflects the security-critical nature of environment variable injection prevention, with multiple injection vectors tested independently and cross-component contracts verified.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 24 AIC · ⊞ 7.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 93/100. All 15 tests are design-level behavioral contracts (100% design tests, 0% implementation tests). Test inflation justified by security-critical injection prevention domain. No violations detected.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Security fix review — MCP gateway environment injection (GHSA-j77w-g4jj-hp99)

Overall this is a well-structured fix. The core approach (compiler-controlled transport names, atomic Docker -e NAME=VALUE injection, marker substitution) is correct and the test coverage is thorough.

Two non-blocking issues flagged:

  1. Transport name reservation gap (sandbox_validation.go line 217): GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES and GH_AW_MCP_GATEWAY_ENV_* satisfy the allowed pattern but are silently dropped instead of triggering a validation error. Should be rejected explicitly.

  2. Test helper panic risk (mcp_gateway_env_security_integration_test.go line 213): strings.Index can return -1; the prefix slice would panic if the invariant is ever broken. Easily guarded with an idx < 0 check.

Neither is a blocker for the security fix itself.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 43.8 AIC · ⊞ 5.3K

Comments that could not be inline-anchored

pkg/workflow/sandbox_validation.go:217

The ^[A-Z_][A-Z0-9_]*$ pattern allows names like GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES and GH_AW_MCP_GATEWAY_ENV_0, which are the transport variable names used internally. A user who sets one of these will have their value silently dropped in writeMCPGatewayStepEnv (filtered by transportEnvVarNames) rather than getting a clear error here.

Consider adding a reservation check:

if name == mcpGatewayCustomEnvNamesVar || strings.HasPrefix(name, &quot;GH_AW_MCP_GATEWAY_ENV_&quot;) {
    return Ne</details>

<details><summary>pkg/workflow/mcp_gateway_env_security_integration_test.go:213</summary>

```go
stepPrefix = line[:strings.Index(line, &quot;- name: &quot;)]

strings.Index returns -1 when the substring is not found, which would cause line[:(-1)] to panic. The outer if trimmed != &quot;- name: &quot;+stepName guard ensures - name: is present, but only for the first match line. If the same step name appears twice in the YAML (unlikely but not impossible), the second occurrence could bypass the guard. A defensive check would be:

idx := strings.Index(line, &quot;- name: &quot;)
if idx &lt; 0 { cont</details>

Documents the decision to route sandbox.mcp.env values through
compiler-controlled transport variables (GH_AW_MCP_GATEWAY_ENV_*) to
prevent shell injection and BASH_ENV exploitation (GHSA-j77w-g4jj-hp99).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate - ADR Required

This PR makes significant changes to core business logic (390 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/50924-secure-mcp-gateway-env-injection.md - review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next:

  1. Review the draft ADR committed to your branch - it was generated from the PR diff
  2. Complete the missing sections - add context not inferrable from the diff, refine the decision rationale, and list real alternatives you considered
  3. Reference the ADR in this PR body by adding a line such as:
    ADR: ADR-50924: Secure MCP Gateway Custom Environment Injection

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 65.7 AIC · ⊞ 9.6K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on two correctness gaps in the transport reservation logic.

📋 Key Themes & Highlights

Key Themes

  • Transport namespace not fully reserved (×2): transportEnvVarNames in writeMCPGatewayStepEnv only reserves the exact N indexed slots in use, so an MCP server env block or a future compiler path could emit a GH_AW_MCP_GATEWAY_ENV_M (M ≥ N) directly into the YAML. The companion gap in sandbox_validation.go is that the name-pattern check allows any GH_AW_MCP_GATEWAY_* name at authoring time.
  • Silent empty fallback: The JS injector silently converts missing indexed transport vars to empty strings; no warning or assertion makes a compiler/transport mismatch observable.

Positive Highlights

  • ✅ Root cause properly addressed — values no longer flow through shell export or Docker command strings
  • ✅ Marker-based placeholder is a clean, injection-safe design
  • ✅ Atomic Docker spawn() argument approach is correct
  • ✅ Excellent regression test coverage: metacharacters, newlines, BASH_ENV, Docker injection, GitHub expressions, override precedence
  • formatYAMLEnv quoting ensures hostile values are inert in YAML before they reach the JS launcher
  • ✅ Integration test validates end-to-end compiler output, not just unit behaviour

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 68.4 AIC · ⊞ 7.1K
Comment /matt to run again

Comment thread pkg/workflow/mcp_setup_gateway.go Outdated
transportEnvVarNames := make(map[string]struct{}, len(customEnvVarNames)+1)
transportEnvVarNames[mcpGatewayCustomEnvNamesVar] = struct{}{}
for i := range customEnvVarNames {
transportEnvVarNames[mcpGatewayCustomEnvTransportName(i)] = struct{}{}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] The existing Copilot comment on line 94 is correct: transportEnvVarNames only reserves the exact GH_AW_MCP_GATEWAY_ENV_${i} slots for the N custom gateway vars. An MCP server's env: block could legitimately expose GH_AW_MCP_GATEWAY_ENV_5 (the name is publicly documented), and if N ≤ 5 that slot would not be reserved and would pass through to the generated YAML, leaking a transport name as a direct env var.

💡 Suggested fix — reserve the whole prefix

Add a prefix-based check alongside the index loop so any name in the GH_AW_MCP_GATEWAY_ENV_* namespace is always blocked:

// Reserve all transport slots, not just the N currently in use
for _, name := range []string{mcpGatewayCustomEnvNamesVar} {
    transportEnvVarNames[name] = struct{}{}
}
for name := range mcpEnvVars {
    if strings.HasPrefix(name, "GH_AW_MCP_GATEWAY_ENV_") {
        transportEnvVarNames[name] = struct{}{}
    }
}

Alternatively, extend validateSandboxConfig to reject env names starting with GH_AW_MCP_GATEWAY_ outright, which gives a clearer error at authoring time.

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in 4dc11a4. writeMCPGatewayStepEnv now filters the full GH_AW_MCP_GATEWAY_ENV_* namespace instead of only the currently indexed slots, and the unit/integration tests now cover a colliding GH_AW_MCP_GATEWAY_ENV_5 case.

name,
fmt.Sprintf("environment variable names should match %s", mcpGatewayEnvNamePattern),
"Use uppercase letters, digits, and underscores, starting with a letter or underscore. Example:\n\nsandbox:\n mcp:\n env:\n API_TOKEN: value",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/diagnosing-bugs] The name-pattern check (^[A-Z_][A-Z0-9_]*$) rejects lowercase names and hyphens, but does not prevent authors from using names in the reserved GH_AW_MCP_GATEWAY_ namespace (e.g. GH_AW_MCP_GATEWAY_ENV_0). Combined with the index-bounded reservation gap in writeMCPGatewayStepEnv, a crafted env name could interfere with the transport mechanism.

💡 Suggested fix

Add an explicit reserved-prefix check to validateSandboxConfig:

if strings.HasPrefix(name, "GH_AW_MCP_GATEWAY_") {
    return NewValidationError(
        "sandbox.mcp.env."+name,
        name,
        "environment variable names starting with GH_AW_MCP_GATEWAY_ are reserved for internal transport",
        "Choose a different name that does not start with GH_AW_MCP_GATEWAY_.",
    )
}

This gives authors a clear error at compile time and fully closes the reservation gap.

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in 4dc11a4. validateSandboxConfig now rejects sandbox.mcp.env names in the reserved GH_AW_MCP_GATEWAY_ namespace, with a targeted validation test for GH_AW_MCP_GATEWAY_ENV_0.

throw new Error("GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES must be an array of valid environment variable names");
}

const customArgs = names.flatMap((name, index) => ["-e", `${name}=${env[`GH_AW_MCP_GATEWAY_ENV_${index}`] || ""}`]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] env[GH_AW_MCP_GATEWAY_ENV_${index}] || "" silently swallows undefined by falling back to the empty string — there's no test covering what happens when GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES and the indexed transport vars are mismatched (e.g. names has 3 entries but only ENV_0 is present). The current test "uses an empty value when transport metadata is missing" covers exactly one missing entry but does not assert that the resulting container env is safe from a Docker perspective (the -e NAME= form sets the var to empty in the container, which may be unexpected).

💡 Suggested improvement

Add a test that verifies the missing-transport case logs or warns, so a misconfigured compiler doesn't silently ship empty secrets:

it("warns when indexed transport var is absent", () => {
  const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
  injectCustomGatewayEnvArgs(["run", marker, "image"], {
    GH_AW_MCP_GATEWAY_CUSTOM_ENV_NAMES: '["SECRET_A","SECRET_B"]',
    GH_AW_MCP_GATEWAY_ENV_0: "present",
    // ENV_1 intentionally absent
  });
  expect(warn).toHaveBeenCalledWith(expect.stringContaining("SECRET_B"));
  warn.mockRestore();
});

Or at minimum document in a comment that an absent transport var is intentionally treated as empty (not an error).

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Addressed in 4dc11a4 by documenting the intentional empty-value fallback in actions/setup/js/start_mcp_gateway.cjs. The runtime contract remains -e NAME= for missing slots, and the existing uses an empty value when transport metadata is missing test continues to cover that behavior.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@copilot add workflow integration test that ensures the filtering is happening

Addressed in 966bffc by extending the workflow integration test to collide with both reserved transport env names and assert those extracted ${{ env.GH_AW_MCP_GATEWAY_* }} values are filtered from the Start MCP Gateway step.

@pelikhan

pelikhan commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@copilot run pr-finisher skill

Ran the PR-finisher pass in 4dc11a4: addressed the remaining in-scope review feedback, ran make fmt, make lint, make test-unit, make test, and make recompile, and pushed the fixes. The dedicated BASE_REF=origin/main make agent-report-progress target still reports a merge-base issue in this agent environment, so CI on the latest head still needs a maintainer re-trigger.

@pelikhan
pelikhan merged commit 9496c5b into main Aug 7, 2026
38 checks passed
@pelikhan
pelikhan deleted the fix/ghsa-j77w-g4jj-hp99 branch August 7, 2026 00:44
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.86.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants