Skip to content
GitHubGHSA-j77w-g4jj-hp99

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 _...

GitHub advisory

Affected versions

PackageAffectedFixed in
gh-aw
npm
< v0.86.0v0.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

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.

GHSA-j77w-g4jj-hp99: GitHub critical vulnerability
Skip to content
GitHubGHSA-j77w-g4jj-hp99

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 _...

GitHub advisory

Affected versions

PackageAffectedFixed in
gh-aw
npm
< v0.86.0v0.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

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.

)\r\n\r\nfor _, envVarName := range envVarNames {\r\n\tif !envNameRe.MatchString(envVarName) {\r\n\t\treturn fmt.Errorf(\"invalid MCP gateway env var name %q\", envVarName)\r\n\t}\r\n\tyaml.WriteString(\" export \" + envVarName + \"=\" +\r\n\t\tshellEscapeArg(gatewayConfig.Env[envVarName]) + \"\\n\")\r\n}\r\n```\r\n\r\nAdditionally, reject control characters/newlines in env values during frontmatter\r\nextraction (`extractMCPGatewayConfig`) so malformed input fails fast at compile time, and\r\nadd a JSON-schema `propertyNames`/`pattern` constraint on `sandbox.mcp.env` keys.\r\n","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H","cvssVersion":"3.1","severitySource":"github","cwes":["CWE-78"],"refs":["https://github.com/github/gh-aw/security/advisories/GHSA-j77w-g4jj-hp99","https://github.com/github/gh-aw"],"affected":[{"product":"github","ecosystem":"npm","package":"gh-aw","introduced":"","fixed":"v0.86.0","lastAffected":""}],"changes":[]},"related":[{"id":"GHSA-jxrq-hq57-gwwm","cve":"","aliases":[],"summary":"gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment)","title":"gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment)","severity":"critical","score":9.1,"product":"github","productLabel":"","products":["github"],"fixed":"v0.86.1","fixFirst":"v0.86.1","fixedN":1,"published":"2026-08-08","modified":"2026-08-08","withdrawn":"","url":"https://github.com/github/gh-aw/security/advisories/GHSA-jxrq-hq57-gwwm","foundAt":"2026-09-26 01:38:39"},{"id":"GHSA-2cwf-x2h8-mqj5","cve":"","aliases":[],"summary":"gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow","title":"gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow","severity":"medium","score":6.5,"product":"github","productLabel":"","products":["github"],"fixed":"v0.86.1","fixFirst":"v0.86.1","fixedN":1,"published":"2026-08-07","modified":"2026-08-07","withdrawn":"","url":"https://github.com/github/gh-aw/security/advisories/GHSA-2cwf-x2h8-mqj5","foundAt":"2026-09-26 01:38:39"},{"id":"GHSA-73j6-rcxw-76w7","cve":"","aliases":[],"summary":"gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel)","title":"gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel)","severity":"medium","score":6.8,"product":"github","productLabel":"","products":["github"],"fixed":"v0.86.0","fixFirst":"v0.86.0","fixedN":1,"published":"2026-08-07","modified":"2026-08-07","withdrawn":"","url":"https://github.com/github/gh-aw/security/advisories/GHSA-73j6-rcxw-76w7","foundAt":"2026-09-26 01:38:39"},{"id":"GHSA-2wjq-689w-pprh","cve":"","aliases":[],"summary":"Safe-outputs config emitter: JSON injection via templated values despite env-var indirection","title":"Safe-outputs config emitter: JSON injection via templated values despite env-var indirection","severity":"high","score":8.5,"product":"github","productLabel":"","products":["github"],"fixed":"v0.78.0","fixFirst":"v0.78.0","fixedN":1,"published":"2026-08-06","modified":"2026-08-06","withdrawn":"","url":"https://github.com/github/gh-aw/security/advisories/GHSA-2wjq-689w-pprh","foundAt":"2026-09-26 01:38:39"},{"id":"CVE-2026-15996","cve":"CVE-2026-15996","aliases":[],"summary":"A denial of service vulnerability was identified in GitHub Enterprise Server that allowed an unauthenticated attacker to cause excessive CPU consumption and exhaust the pool of request-handling...","title":"GitHub Enterprise Server: denial of service","severity":"medium","score":6.6,"product":"github","productLabel":"Enterprise Server","products":["github"],"fixed":"","fixFirst":"","fixedN":0,"published":"2026-08-05","modified":"2026-08-18","withdrawn":"","url":"https://docs.github.com/en/enterprise-server@3.17/admin/release-notes#3.17.16","foundAt":"2026-09-26 01:50:46"},{"id":"CVE-2026-17556","cve":"CVE-2026-17556","aliases":[],"summary":"A path traversal vulnerability was identified in GitHub Enterprise Server that allowed an unauthenticated attacker to delete arbitrary files and directories on the instance, including the entire user...","title":"GitHub Enterprise Server: path traversal","severity":"high","score":8.8,"product":"github","productLabel":"Enterprise Server","products":["github"],"fixed":"","fixFirst":"","fixedN":0,"published":"2026-08-05","modified":"2026-08-18","withdrawn":"","url":"https://docs.github.com/en/enterprise-server@3.17/admin/release-notes#3.17.19","foundAt":"2026-09-26 01:50:46"}]}}