Arbitrary File Write via --split-exp Path Traversal
GHSA-9fvq-9qj8-6qhx · CVE assignment pending
Summary
The --split-exp (alias -s) flag evaluates a yq expression against input document content and uses the resulting scalar value directly as an output file path with no sanitization. Because neither .. components nor absolute paths are rejected before the value reaches os.MkdirAll and os.Create, an attacker-controlled YAML/JSON/XML document can direct yq to create or overwrite arbitrary files anywhere the victim process has write permission. A victim who runs yq -s '.name' attacker-payload.yml against untrusted input may have files outside the current working directory silently overwritten — yq exits 0.
Impact
- Arbitrary file write — create/overwrite files with attacker-controlled content (the processed document itself), plus arbitrary directory creation.
- Attack vector: network — untrusted YAML processed in CI/CD (charts, PR-supplied manifests, GitOps configs); no authentication required.
- Escalation: overwriting dotfiles, scripts, or workflow definitions (
~/.bashrc,~/.ssh/authorized_keys,.github/workflows/*.yml, crontab) leads to code execution on the next run; any writable file can be truncated or corrupted.
Root Cause
In pkg/yqlib/printer_writer.go, multiPrintWriter.GetWriter takes the filename from the document itself and passes it to the filesystem unsanitized:
// printer_writer.go:66-67 — name comes from the document
if result.MatchingNodes.Len() > 0 {
name = result.MatchingNodes.Front().Value.(*CandidateNode).Value
}
// printer_writer.go:69-72 — only an extension regex; no path validation
var extensionRegexp = regexp.MustCompile(`\.[a-zA-Z0-9]+$`)
if !extensionRegexp.MatchString(name) {
name = fmt.Sprintf("%v.%v", name, sp.extension)
}
// printer_writer.go:74-78 — unsanitized name reaches the sinks
err = os.MkdirAll(filepath.Dir(name), 0750)
...
f, err := os.Create(name)
There is no filepath.IsAbs check, no rejection of .. segments, and no base-directory confinement. --split-exp-file shares the same writer (only the expression source changes — cmd/utils.go:57-64); the flags are wired in cmd/root.go:199-205.
Proof of Concept
Tested against yq v4.54.1. A sentinel file outside the working directory is overwritten with the attacker document:
# Working directory /tmp/yq-poc/work; victim file outside it
$ echo 'ORIGINAL SENSITIVE DATA - MUST NOT BE OVERWRITTEN' \
> /tmp/yq-poc/victim/sensitive.yml
# Attacker-controlled payload: 'name' contains the traversal path
$ cat > /tmp/yq-poc/work/payload.yml <<'EOF'
name: ../victim/sensitive
message: attacker-controlled write
EOF
$ cd /tmp/yq-poc/work
$ yq -s '.name' payload.yml
[exit 0 — silent; the flag derives file names from the document]
$ cat /tmp/yq-poc/victim/sensitive.yml
name: ../victim/sensitive
message: attacker-controlled write ← ORIGINAL DATA GONE
Confirmed variants: relative one level (../victim/…), relative two levels (escapes the PoC sandbox entirely), and absolute paths (name: /tmp/arbitrary/target creates or overwrites at the literal location). 3 screenshots attached to the advisory.
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-10-09 | Vulnerability discovered; PoC confirmed (relative, two-level, and absolute-path variants) |
| 2026-10-23 | Reported via GitHub Private Vulnerability Reporting as Issue A of the combined advisory; accepted by the maintainer the same day |
| 2026-11-05 | Issue A split into the standalone advisory GHSA-9fvq-9qj8-6qhx |
| 2026-11-09 | Advisory published; fix released in yq v4.54.3 |
| Pending | CVE assignment (request due now that a fix is public) |
Fix
yq v4.54.3 restricts split output paths to the current directory by default:
- Restricted `--split-exp` output paths to the current directory by default.
Use `--security-allow-write-any-path` with trusted input to restore the
previous behaviour (#2965)
The advisory also proposes a defensive sanitizeSplitFilename that rejects absolute paths and .. segments after filepath.Clean before any MkdirAll/Create call. Workaround on vulnerable versions: do not pass untrusted documents to --split-exp / --split-exp-file.
Fixed in yq v4.54.3 (2026-11-09). Advisory published 2026-11-09; CVE assignment pending.