command injection in compiled workflow via unsanitized `sandbox.mcp.env` exports
Critical9.6Published Aug 7, 2026
## Summary `gh aw compile` writes MCP-gateway environment variables into the generated GitHub Actions workflow (`.lock.yml`) using an unescaped `fmt.Fprintf(... "export %s=%s" ...)`. The values come verbatim from the `sandbox.mcp.env` map in a workflow's frontmatter, and the workflow JSON schema imposes no character constraints on them. A value containing shell metacharacters (`;`, `$(...)`, backticks, or a newline) breaks out of the `export` statement and is emitted as a command in a `run:` shell block. When the compiled workflow runs, those commands execute in the GitHub Actions runner with the job's `GITHUB_TOKEN` and secrets. The dangerous input crosses a real trust boundary: `gh-aw` supports **importing and packaging workflow components from third-party repositories**. A malicious shared component that declares `sandbox.mcp.env` can therefore achieve arbitrary command execution in the CI environment of any repository that imports it, compiles, and runs the workflow. ## Impacted code `pkg/workflow/mcp_setup_generator.go` (lines 718–724, sink at 722): ```go if len(gatewayConfig.Env) > 0 { envVarNames := sliceutil.MapKeys(gatewayConfig.Env) sort.Strings(envVarNames) for _...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| gh-aw npm | < v0.86.0 | v0.86.0 |
Details and references
## Summary `gh aw compile` writes MCP-gateway environment variables into the generated GitHub Actions workflow (`.lock.yml`) using an unescaped `fmt.Fprintf(... "export %s=%s" ...)`. The values come verbatim from the `sandbox.mcp.env` map in a workflow's frontmatter, and the workflow JSON schema imposes no character constraints on them. A value containing shell metacharacters (`;`, `$(...)`, backticks, or a newline) breaks out of the `export` statement and is emitted as a command in a `run:` shell block. When the compiled workflow runs, those commands execute in the GitHub Actions runner with the job's `GITHUB_TOKEN` and secrets. The dangerous input crosses a real trust boundary: `gh-aw` supports **importing and packaging workflow components from third-party repositories**. A malicious shared component that declares `sandbox.mcp.env` can therefore achieve arbitrary command execution in the CI environment of any repository that imports it, compiles, and runs the workflow. ## Impacted code `pkg/workflow/mcp_setup_generator.go` (lines 718–724, sink at 722): ```go if len(gatewayConfig.Env) > 0 { envVarNames := sliceutil.MapKeys(gatewayConfig.Env) sort.Strings(envVarNames) for _, envVarName := range envVarNames { fmt.Fprintf(yaml, " export %s=%s\n", envVarName, gatewayConfig.Env[envVarName]) // <-- no quoting/escaping } } ``` Contrast with the adjacent, correctly-handled export 10 lines above (line 711–712), which already shell-escapes its value: ```go escapedCLIServersJSON := shellEscapeArg(string(cliServersJSON)) yaml.WriteString(" export GH_AW_MCP_CLI_SERVERS=" + escapedCLIServersJSON + "\n") ``` The source is parsed verbatim from frontmatter, with no validation (`pkg/workflow/frontmatter_extraction_security.go:363–372`): ```go if envVal, hasEnv := mcpObj["env"]; hasEnv { if envObj, ok := envVal.(map[string]any); ok { mcpConfig.Env = make(map[string]string) for key, value := range envObj { if valueStr, ok := value.(string); ok { mcpConfig.Env[key] = valueStr // key and value copied directly } } } } ``` Commit: `402c2979bbb494a9ef91e08c031e29eae7983ca2` ## CVSS **Score: 9.6 — Critical** **Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H`** | Metric | Value | Rationale | |--------|-------|-----------| | Attack Vector | Network (N) | Payload is delivered as an imported/packaged workflow component over the GitHub/package ecosystem (supply-chain). | | Attack Complexity | Low (L) | A single frontmatter value; no special conditions. | | Privileges Required | None (N) | Any author of a shared/importable component. | | User Interaction | Required (R) | A victim must import the component, run `gh aw compile`, and run the workflow. | | Scope | Changed (C) | Injected commands escape the "workflow definition" authority into the CI runner's execution environment and its secrets/`GITHUB_TOKEN`. | | Confidentiality / Integrity / Availability | High / High / High | Arbitrary code execution in CI: read secrets, tamper with the repo, disrupt the runner. | ## Steps to reproduce / minimal PoC 1. Create a workflow with a malicious `sandbox.mcp.env` value (this is what a third-party imported component would carry). Save as `.github/workflows/poc-envinject.md`: ```markdown --- on: workflow_dispatch: permissions: contents: read engine: id: claude sandbox: mcp: env: AAA_INJECT: "legit; echo PWNED_$(id) > /tmp/pwned #" BBB_NEWLINE: "ok\n echo PWNED_NEWLINE" CCC_BACKTICK: "`touch /tmp/PWNED_BACKTICK`" tools: github: toolsets: [default] --- # PoC Workflow Body. ``` 2. Compile it (in a sealed, no-network container, as done during validation): ```bash docker run --rm --network none \ -v "$PWD":/work -w /work \ -v /path/to/gh-aw-linux:/usr/local/bin/gh-aw:ro alpine:3.
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-78
More GitHub advisories
All GitHub| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 8 | gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment) | Critical9.1 | v0.86.1 |
| Aug 7 | gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow | Medium6.5 | v0.86.1 |
| Aug 7 | gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel) | Medium6.8 | v0.86.0 |
| Aug 6 | Safe-outputs config emitter: JSON injection via templated values despite env-var indirection | High8.5 | v0.78.0 |
| Aug 5 | GitHub Enterprise Server: denial of service | Medium6.6 | No fix yet |
| Aug 5 | GitHub Enterprise Server: path traversal | High8.8 | No fix yet |