ANTLR's Go code generator hands a grammar-controlled string to `gofmt` and forgets to sanitize it
CVE-2026-13501 lives in tool/src/org/antlr/v4/codegen/target/GoTarget.java of ANTLR4 through 4.13.2. When the Go code generator finishes emitting .go files, it shells out to gofmt and passes path/filename data derived from the grammar file. A crafted grammar can inject shell metacharacters into that invocation, achieving command execution as the user running antlr4 -Dlanguage=Go — typically a developer or a CI build agent. The vector is AV:L because the attacker must convince a victim to compile a malicious .g4 grammar; there is no network surface.
Vendor MEDIUM (5.3) is slightly inflated for the realistic deployment. ANTLR is a build-time tool, not a runtime service, and the impacts (C:L/I:L/A:L) only manifest if you compile attacker-supplied grammars — an unusual workflow outside grammar marketplaces or pull-request CI. For typical enterprise consumers who own their grammars, the practical risk is closer to LOW. The exception is anyone running a hosted grammar-compilation service or accepting third-party .g4 files in CI.
4 steps from start to impact.
Attacker crafts a malicious .g4 grammar
$(...), ;, &&) that will be passed through to the gofmt subprocess invocation in GoTarget.java. Public PoC weaponization is trivial — echo "grammar X; options{...};" > evil.g4 with the injection payload in the offending field.- Knowledge of the
GoTargetcodegen path - Ability to deliver the .g4 file to a victim
- Most enterprises author grammars in-house; they don't ingest third-party .g4 files
Victim invokes ANTLR with Go target
antlr4 -Dlanguage=Go evil.g4 or equivalent Maven/Gradle plugin invocation. Only Go-target builds reach the vulnerable code path — Java, Python, C#, TypeScript targets are unaffected because the bug is GoTarget-specific.- Victim uses
-Dlanguage=Go - ANTLR 4.13.2 or older installed
gofmtpresent on PATH
- Go target is a minority of ANTLR users
- Many shops pin grammars and code-review changes
gofmt child processInjected command executes under build user
gofmt is launched via a non-quoted shell-style invocation; the injected payload runs with whatever privileges the build user holds. On a dev laptop that's the user. On a Jenkins/GitHub Actions runner it's the runner's service account — often with checkout tokens and registry credentials in scope.- Build agent has secrets in environment
- No seccomp/sandbox isolation
- Ephemeral GitHub-hosted runners limit blast radius to one job
- Hardened CI uses OIDC short-lived tokens
Pivot to supply-chain compromise (worst-case)
- Self-hosted/persistent CI runner
- Long-lived secrets present
- Output artifacts published downstream
- Modern CI guidance is ephemeral runners + OIDC
- Code review on third-party PRs
The supporting signals.
| In-the-wild exploitation | None observed. No campaign or incident reporting as of 2026-06-30. |
|---|---|
| Public PoC | Disclosure note says exploit is public; trivial to weaponize given a .g4 grammar and -Dlanguage=Go target. |
| EPSS | 0.00678 (~0.7%) — bottom decile of exploit-likelihood scoring. |
| KEV status | Not listed by CISA. |
| CVSS vector | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L — local attack vector, low privilege, partial CIA impact. CVSS v4.0 is 4.8. |
| Affected versions | ANTLR4 up to and including 4.13.2 when invoked with -Dlanguage=Go. |
| Fixed version | No vendor patch published. Vendor reportedly did not respond to coordinated disclosure (per Vulnerability-Lookup). |
| Vulnerable code path | tool/src/org/antlr/v4/codegen/target/GoTarget.java — the gofmt post-processing call. |
| Exposure data | Not internet-reachable; ANTLR is a build-time CLI. Shodan/GreyNoise N/A. |
| Disclosure | Published 2026-06-28 via VulDB / Vulnerability-Lookup. CWE-77 also assigned alongside CWE-74. |
noisgate verdict.
Downgraded from MEDIUM to LOW because the single most decisive factor is attacker-controlled grammar input is required at build time — a workflow virtually no enterprise has unless they operate a hosted grammar compiler or accept untrusted .g4 files from external contributors. Exploitation cannot reach this code path through any runtime service or network exposure.
Why this verdict
- Local-only attack vector (AV:L): no network exposure. Attacker must place a malicious grammar onto a build host and trigger compilation. Removes any internet-scanning population entirely.
- Go target only: the bug lives in
GoTarget.java. Shops using Java/Python/C#/TS code generation — the majority of ANTLR users — are not on the vulnerable code path at all. - Build-time tool, not a service: ANTLR4 ships zero runtime daemons. Production servers running compiled parsers cannot be hit by this CVE. The blast radius is limited to whoever runs the
antlr4tool itself. - Role multiplier — developer workstation: chain succeeds; outcome is single-user compromise of one laptop. Blast radius: host. Not fleet.
- Role multiplier — ephemeral CI runner (e.g. GitHub-hosted): chain succeeds; blast radius is one job's secrets, contained by runner lifecycle.
- Role multiplier — self-hosted persistent CI runner accepting third-party grammar PRs: chain succeeds and reaches supply-chain pivot. This is the worst case but the installed-base share is well under 1% of ANTLR consumers (typical shops compile their own grammars). Insufficient to floor the verdict above LOW.
- EPSS 0.00678 + no KEV + no observed exploitation corroborates the low real-world urgency.
Why not higher?
MEDIUM would imply the bug can be triggered without attacker-controlled grammar input, or that the tool runs in a service role. Neither is true. CVSS v3.1's local-vector + low-impact scoring already produces 5.3; once you apply the realistic exposure filter (only Go-target users compiling untrusted grammars), the practical risk drops further. HIGH would require either network reachability or a fleet-scale blast radius — this has neither.
Why not lower?
IGNORE is wrong because the chain IS exploitable, public PoC exists, and there is one credible enterprise scenario (self-hosted CI runner ingesting third-party grammar contributions) where it leads to supply-chain compromise. Worth tracking on the backlog and fixing when ANTLR ships a patch.
What to do — in priority order.
- Inventory ANTLR4 usage, filter to Go-target builds — Grep build manifests (
pom.xml,build.gradle,Makefile,package.json) forantlr4invocations with-Dlanguage=Goor the Go runtime dependency. Non-Go users have no exposure. Since verdict is LOW there is no noisgate mitigation SLA — fold this inventory into normal asset hygiene. - Forbid third-party
.g4files in CI without review — Treat grammar files like any other executable code in PRs. Require human review for any.g4change from external contributors; reject grammars with shell metacharacters in options, headers, or filenames. Implement now on any pipeline that does accept external grammars. - Move grammar compilation to ephemeral runners — If you use self-hosted CI agents that compile ANTLR grammars, migrate those specific jobs to ephemeral GitHub-hosted / GitLab SaaS runners or single-use container runners. Limits blast radius of any successful injection to one job's short-lived credentials.
- Strip
gofmtfrom build images you don't need it on — If a build image does not requiregofmtfor legitimate purposes, remove the Go toolchain entirely. ANTLR's vulnerable code path silently no-ops whengofmtis absent. - Track upstream for a fix release — Vendor did not respond to initial disclosure per the published note. Subscribe to
antlr/antlr4GitHub releases and bump within the noisgate remediation SLA window (365 days) once a fixed version ships.
- WAF / network controls — the vector is local file invocation; there is no HTTP request to filter.
- Runtime application self-protection (RASP) — ANTLR is not in your runtime; it's a build tool. Production parsers generated by ANTLR are not affected.
- Patching the generated Go parser code — the bug is in the codegen step itself, not in the emitted parser. Regenerating with a fixed ANTLR is the only true fix.
- Disabling
gofmtautoformat in your IDE — unrelated; the call happens inside theantlr4tool, not your editor.
Crowdsourced verification payload.
Run this on each build host or CI runner that has ANTLR installed. Invoke as ./check_antlr_cve_2026_13501.sh — no privileges required beyond what the build user already has. Checks the ANTLR jar version and reports VULNERABLE if ≤4.13.2.
#!/usr/bin/env bash
# noisgate verification: CVE-2026-13501 (ANTLR4 GoTarget command injection)
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -u
find_antlr() {
# Common locations + classpath search
local candidates=()
while IFS= read -r p; do candidates+=("$p"); done < <(
{ command -v antlr4 antlr 2>/dev/null;
find / -name 'antlr-*-complete.jar' -o -name 'antlr4-*.jar' 2>/dev/null;
} | sort -u
)
printf '%s\n' "${candidates[@]}"
}
compare_version() {
# returns 0 if $1 <= $2
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]
}
status=UNKNOWN
found_any=0
for item in $(find_antlr); do
found_any=1
ver=""
if [ -f "$item" ]; then
ver=$(echo "$item" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
else
ver=$("$item" 2>&1 | grep -oE 'Version [0-9]+\.[0-9]+\.[0-9]+' | awk '{print $2}' | head -n1)
fi
[ -z "$ver" ] && continue
echo "Found ANTLR at $item — version $ver"
if compare_version "$ver" "4.13.2"; then
echo " -> VULNERABLE (≤ 4.13.2)"
status=VULNERABLE
else
echo " -> PATCHED"
[ "$status" != "VULNERABLE" ] && status=PATCHED
fi
done
if [ $found_any -eq 0 ]; then
echo "UNKNOWN: no ANTLR installation located on this host"
exit 2
fi
echo "Result: $status"
case "$status" in
PATCHED) exit 0 ;;
VULNERABLE) exit 1 ;;
*) exit 2 ;;
esac
If you remember one thing.
-Dlanguage=Go ANTLR invocations, then escalate to HIGH only if you operate a self-hosted CI runner that compiles third-party .g4 grammars — in that narrow case, gate external grammar contributions through human review immediately and migrate the job to an ephemeral runner.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.