← Back to Feed CACHED · 2026-06-30 01:52:38 · CACHE_KEY CVE-2026-13501
CVE-2026-13501 · CWE-74 · Disclosed 2026-06-28

ANTLR4 ≤4.13.2 GoTarget gofmt command injection

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Local command injection in ANTLR's gofmt invocation — affects developer/CI machines, not production. Vendor MEDIUM is generous."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker crafts a malicious .g4 grammar

The attacker authors an ANTLR grammar file whose options or filename embeds shell metacharacters (backticks, $(...), ;, &&) 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.
Conditions required:
  • Knowledge of the GoTarget codegen path
  • Ability to deliver the .g4 file to a victim
Where this breaks in practice:
  • Most enterprises author grammars in-house; they don't ingest third-party .g4 files
Detection/coverage: No scanner rule today; SAST tools won't flag a grammar file
STEP 02

Victim invokes ANTLR with Go target

A developer or CI job runs 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.
Conditions required:
  • Victim uses -Dlanguage=Go
  • ANTLR 4.13.2 or older installed
  • gofmt present on PATH
Where this breaks in practice:
  • Go target is a minority of ANTLR users
  • Many shops pin grammars and code-review changes
Detection/coverage: Source-control PR review catches obvious payloads; EDR sees the gofmt child process
STEP 03

Injected 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.
Conditions required:
  • Build agent has secrets in environment
  • No seccomp/sandbox isolation
Where this breaks in practice:
  • Ephemeral GitHub-hosted runners limit blast radius to one job
  • Hardened CI uses OIDC short-lived tokens
Detection/coverage: EDR on the build host flags unexpected child processes spawned from gofmt; CI audit logs show anomalous shell commands
STEP 04

Pivot to supply-chain compromise (worst-case)

If the compromised host is a shared CI runner with persistent credentials, the attacker steals signing keys, registry tokens, or modifies build artifacts downstream. This is the only path that gets past LOW — and it requires the victim to have already accepted attacker-controlled grammar input in their pipeline.
Conditions required:
  • Self-hosted/persistent CI runner
  • Long-lived secrets present
  • Output artifacts published downstream
Where this breaks in practice:
  • Modern CI guidance is ephemeral runners + OIDC
  • Code review on third-party PRs
Detection/coverage: Sigstore/SLSA provenance mismatches; secret-scanner alerts on exfil
03 · Compensating Control

1
Inventory ANTLR4 usage, filter to Go-target builds — Grep build manifests (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.
2
Forbid third-party .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.
3
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.
4
Strip 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.
5
Track upstream for a fix release — Vendor did not respond to initial disclosure per the published note. Subscribe to antlr/antlr4 GitHub releases and bump within the noisgate remediation SLA window (365 days) once a fixed version ships.
What doesn't work
  • 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 gofmt autoformat in your IDE — unrelated; the call happens inside the antlr4 tool, not your editor.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. No campaign or incident reporting as of 2026-06-30.
Public PoCDisclosure note says exploit is public; trivial to weaponize given a .g4 grammar and -Dlanguage=Go target.
EPSS0.00678 (~0.7%) — bottom decile of exploit-likelihood scoring.
KEV statusNot listed by CISA.
CVSS vectorCVSS: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 versionsANTLR4 up to and including 4.13.2 when invoked with -Dlanguage=Go.
Fixed versionNo vendor patch published. Vendor reportedly did not respond to coordinated disclosure (per Vulnerability-Lookup).
Vulnerable code pathtool/src/org/antlr/v4/codegen/target/GoTarget.java — the gofmt post-processing call.
Exposure dataNot internet-reachable; ANTLR is a build-time CLI. Shodan/GreyNoise N/A.
DisclosurePublished 2026-06-28 via VulDB / Vulnerability-Lookup. CWE-77 also assigned alongside CWE-74.

Sources.

  1. Vulnerability-Lookup: CVE-2026-13501
  2. Vulnerability-Lookup: CVE-2026-13500 (sibling bug, same disclosure batch)
  3. GHSA-Q226-W36V-HHXV (related ANTLR4 advisory)
  4. ANTLR4 GitHub repository
  5. Maven Central: org.antlr:antlr4:4.13.2
  6. CWE-74: Injection
  7. CWE-77: Command Injection
  8. Snyk: antlr4 advisories
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.1/10)

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 antlr4 tool 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.

06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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