Problem
Four env: emission sites in the compiler use fmt.Sprintf/fmt.Fprintf with %s to interpolate frontmatter-derived values into generated YAML .lock.yml files. Depending on the wrapping (none, '...', "..."), specific characters can break out of the YAML scalar and inject sibling environment variables.
One site (GH_AW_LABEL_NAMES) was already fixed in PR #22764 using %q. The remaining four have the same class of bug.
Upstream tracking: https://github.com/github/agentic-workflows/issues/183
Severity
Medium — YAML structure injection. Input is author-controlled (frontmatter), but violates the compiler's "frontmatter values are data, not structure" contract. An injected ${{ secrets.* }} expression reference could expose secrets to steps that shouldn't receive them.
Affected Sites
| # |
Env Var |
File |
Line |
Current Wrapping |
Breaks On |
| 1 |
GH_AW_ALLOWED_BOTS |
compiler_activation_job.go |
L204 |
none (%s) |
\n |
| 2 |
GH_AW_REPOSITORY_IMPORTS |
compiler_yaml_main_job.go |
L109 |
'%s' (single-quoted) |
' |
| 3 |
GH_AW_AGENT_FILE |
compiler_yaml_main_job.go |
L114 |
"%s" (double-quoted) |
" \n |
| 4 |
GH_AW_AGENT_IMPORT_SPEC |
compiler_yaml_main_job.go |
L115 |
"%s" (double-quoted) |
" \n |
Already fixed: GH_AW_LABEL_NAMES (L332 in compiler_activation_job.go) — fixed to %q in PR #22764.
PoC (Site 1 — GH_AW_ALLOWED_BOTS)
---
on:
issues:
types: [opened]
engine: copilot
bots:
- "dependabot[bot]\n INJECTED_VAR: hello"
---
Workflow body.
Compiled output:
env:
GH_AW_ALLOWED_BOTS: dependabot[bot]
INJECTED_VAR: hello # ← injected sibling env var
Solution
Part 1: Fix all four sites with %q
Go's %q format verb produces a valid YAML double-quoted scalar — it escapes ", \, newlines, and control characters:
// compiler_activation_job.go:204 — currently: %s (no quoting)
fmt.Sprintf(" GH_AW_ALLOWED_BOTS: %q\n", strings.Join(data.Bots, ","))
// compiler_yaml_main_job.go:109 — currently: '%s' (single-quoted)
fmt.Fprintf(yaml, " GH_AW_REPOSITORY_IMPORTS: %q\n", string(repoImportsJSON))
// compiler_yaml_main_job.go:114 — currently: "%s" (unescaped double-quoted)
fmt.Fprintf(yaml, " GH_AW_AGENT_FILE: %q\n", data.AgentFile)
// compiler_yaml_main_job.go:115 — currently: "%s" (unescaped double-quoted)
fmt.Fprintf(yaml, " GH_AW_AGENT_IMPORT_SPEC: %q\n", data.AgentImportSpec)
Part 2: Add schema validation for bots items
The bots schema (pkg/parser/schemas/main_workflow_schema.json L8250-8258) currently has no character-set constraint. Add a pattern to restrict bot names to safe characters:
"items": {
"type": "string",
"minLength": 1,
"pattern": "^[A-Za-z0-9._\\[\\]-]+$",
"description": "Bot identifier/name (e.g., 'dependabot[bot]', 'renovate[bot]')"
}
Part 3 (Systemic): Introduce a writeYAMLEnv helper
Create a helper function that makes it impossible to emit unescaped env values:
// writeYAMLEnv emits a single YAML env variable with proper escaping.
// Uses %q to produce a valid YAML double-quoted scalar.
func writeYAMLEnv(w io.Writer, indent, key, value string) {
fmt.Fprintf(w, "%s%s: %q\n", indent, key, value)
}
Then replace all five sites (including the already-fixed GH_AW_LABEL_NAMES) to use this helper. A go vet or golangci-lint custom rule could then flag bare fmt.Fprintf(yaml, ...) patterns for env emission.
Validation
After applying the fix:
- Recompile all workflows:
make recompile
- Verify the PoC no longer injects: compile a workflow with
\n in a bot name and confirm the output contains a properly escaped %q string
- Run existing tests:
make test-unit
- Run
make agent-finish before committing
Notes
Problem
Four
env:emission sites in the compiler usefmt.Sprintf/fmt.Fprintfwith%sto interpolate frontmatter-derived values into generated YAML.lock.ymlfiles. Depending on the wrapping (none,'...',"..."), specific characters can break out of the YAML scalar and inject sibling environment variables.One site (
GH_AW_LABEL_NAMES) was already fixed in PR #22764 using%q. The remaining four have the same class of bug.Upstream tracking: https://github.com/github/agentic-workflows/issues/183
Severity
Medium — YAML structure injection. Input is author-controlled (frontmatter), but violates the compiler's "frontmatter values are data, not structure" contract. An injected
${{ secrets.* }}expression reference could expose secrets to steps that shouldn't receive them.Affected Sites
GH_AW_ALLOWED_BOTScompiler_activation_job.go%s)\nGH_AW_REPOSITORY_IMPORTScompiler_yaml_main_job.go'%s'(single-quoted)'GH_AW_AGENT_FILEcompiler_yaml_main_job.go"%s"(double-quoted)"\nGH_AW_AGENT_IMPORT_SPECcompiler_yaml_main_job.go"%s"(double-quoted)"\nAlready fixed:
GH_AW_LABEL_NAMES(L332 incompiler_activation_job.go) — fixed to%qin PR #22764.PoC (Site 1 —
GH_AW_ALLOWED_BOTS)Compiled output:
Solution
Part 1: Fix all four sites with
%qGo's
%qformat verb produces a valid YAML double-quoted scalar — it escapes",\, newlines, and control characters:Part 2: Add schema validation for
botsitemsThe
botsschema (pkg/parser/schemas/main_workflow_schema.jsonL8250-8258) currently has no character-set constraint. Add apatternto restrict bot names to safe characters:Part 3 (Systemic): Introduce a
writeYAMLEnvhelperCreate a helper function that makes it impossible to emit unescaped env values:
Then replace all five sites (including the already-fixed
GH_AW_LABEL_NAMES) to use this helper. Ago vetorgolangci-lintcustom rule could then flag barefmt.Fprintf(yaml, ...)patterns for env emission.Validation
After applying the fix:
make recompile\nin a bot name and confirm the output contains a properly escaped%qstringmake test-unitmake agent-finishbefore committingNotes
%qis safe for YAML because Go's double-quoted string escaping is a superset of YAML's double-quoted scalar escapingjson.Marshalescapes"→\"but does NOT escape'— so'%s'wrapping of JSON output is unsafe in YAML single-quoted scalars (which use''as escape)