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
pom.xml, build.gradle, Makefile, package.json) for antlr4 invocations with -Dlanguage=Go or 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..g4 files in CI without review — Treat grammar files like any other executable code in PRs. Require human review for any .g4 change from external contributors; reject grammars with shell metacharacters in options, headers, or filenames. Implement now on any pipeline that does accept external grammars.gofmt from build images you don't need it on — If a build image does not require gofmt for legitimate purposes, remove the Go toolchain entirely. ANTLR's vulnerable code path silently no-ops when gofmt is absent.antlr/antlr4 GitHub 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.
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. |
Sources.
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.
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