镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Compiler JSON-encodes && to \u0026\u0026 inside ${{ }} expressions in AWF config printf, breaking workflow parse #30695

Closed

Description

@bryanchen-d

Summary

After the AWF JSON-config migration (#29220), the compiler emits the AWF config as a JSON string inside a printf '%s\n' "<json>" step in the generated .lock.yml. Any & characters in that string get JSON-encoded as \u0026 — including the && operators inside ${{ … }} GitHub Actions expressions that are interpolated into network.allowDomains. The Actions expression parser does not decode \u0026 and aborts with:

(Line: 867, Col: 14): Unexpected symbol: '\u0026\u0026'.
Located at position 26 within expression: env.MCP_ENV == 'staging' \u0026\u0026 env.MCP_URL_STAGING || env.MCP_URL_PROD

This blocks every workflow that uses a GitHub Actions expression with && (or &) inside network.allowed. We hit this on six errors-* workflows in microsoft/vscode-engineering after gh aw compile produced lock files with v0.25.40.

Repro

Minimal .md:

---
on: workflow_dispatch
permissions: { contents: read }
env:
  MCP_URL_PROD: 'https://prod.example.com/mcp'
  MCP_URL_STAGING: 'https://staging.example.com/mcp'
  MCP_ENV: 'prod'
engine: { id: copilot }
mcp-servers:
  demo:
    url: ${{ env.MCP_ENV == 'staging' && env.MCP_URL_STAGING || env.MCP_URL_PROD }}
network:
  allowed:
    - "prod.example.com"
    - "staging.example.com"
---

# demo

Run:

gh aw compile .github/workflows/demo.md

Inspect the generated .lock.yml — the firewall step contains:

run: |
  …
  printf '%s\n' "{\"$schema\":\"…\",\"network\":{\"allowDomains\":[\"${{ env.MCP_ENV == 'staging' \u0026\u0026 env.MCP_URL_STAGING || env.MCP_URL_PROD }}\", …

The \u0026\u0026 lives inside a ${{ … }} block, so GH Actions expression parser rejects the workflow before any step runs.

Root cause (likely)

In the codepath that builds the AWF config (introduced in #29220), the entire config object is json.Marshal'd (or equivalent), which escapes & → \u0026 per Go's default HTMLEscape behavior. ${{ … }} placeholders are interpolated as raw strings before JSON serialization, so the operators inside them get escaped along with everything else.

Suggested fixes (any one works)

  1. Disable HTML escaping when serializing the AWF config string. In Go: use json.Encoder with SetEscapeHTML(false). This is the lowest-risk fix and matches what the Actions runtime / firewall actually consume (raw JSON, not HTML).
  2. Substitute ${{ … }} placeholders after JSON serialization (template the config, then inject expressions). Avoids any future escape-class bugs.
  3. Avoid the dynamic expression inside allowDomains entirely by pre-computing the union of possible hosts at compile time. (Workaround for users today; doesn't fix the underlying bug.)

Workaround

Manually sed -i 's/\\u0026/\&/g' the printf line in each affected .lock.yml. Survives until the next gh aw compile.

Affected versions

Reproduced with gh-aw-firewall schema URL …/v0.25.40/awf-config.schema.json in generated lock files. Likely all gh-aw versions since #29220 landed (closed 2026-04-30).

Impact

All 6 errors-* workflows in microsoft/vscode-engineering that use staging/prod MCP URL switching:

  • errors-triage, errors-stable-scan, errors-fix, errors-regression-scan, errors-dedup-test, errors-investigate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions