analyze: detect stray-apostrophe and unescaped-${VAR} Woodpecker footguns #12

Merged
oleks merged 1 commits from footgun-detection into main 2026-08-03 19:52:46 +03:00
Owner

Closes oleks/pipetree#11.

Adds two static checks to -analyze (internal/analyze/footguns.go), alongside the existing shared-image/broken-depends_on checks:

  • StrayApostrophe: a literal apostrophe inside a -c bash -euxc '...'-style single-quoted shell block — including inside a # comment — closes the quote early and hands the rest of the script to the outer shell to re-tokenize. Live incident: oleks/mempalace pipeline 218 failed exit 127 ("patch: command not found", though patch WAS installed) because a comment contained the word "Woodpecker's" — root-caused and fixed in oleks/mempalace commit 54899ec. The same idiom had already been bitten once before (e409e70).
  • UnescapedWoodpeckerVar: Woodpecker substitutes braced ${VAR} in the raw pipeline text at parse time, before the shell runs — including inside comments. A shell variable meant for the shell needs to be escaped as $${VAR} (oleks/mempalace commit 33b880d). Woodpecker's own ${CI_*} builtins and a pipeline's own matrix: axis names are excluded as legitimate unescaped uses.

Both checks are conservative by design to avoid false positives:

  • StrayApostrophe only fires when it finds the idiom's dedicated closing line (a line that trims to exactly '); commands that don't match this precise shape are left unchecked.
  • UnescapedWoodpeckerVar excludes CI_* builtins and, after a real false positive found during fleet validation (see below), the pipeline's own matrix: axis names too.

Testing

go build ./... and go test ./... (single small binary, no full-repo lint per the build-delegation policy). New tests in internal/analyze/footguns_test.go cover:

  • positive cases using the exact real fixtures: testdata/mempalace-stray-apostrophe.yaml is the actual pipeline-218-broken commit (54899ec^), testdata/mempalace-unescaped-var.yaml is the actual pre-33b880d commit.
  • negative cases: testdata/mempalace-fixed.yaml (today's live, post-fix .woodpecker/test.yaml), testdata/matrix-fixture.yaml (a real matrix: pipeline, guards the false positive below), plus unit tests for $$-escaping, '\''-escaping, and comment-embedded footguns.

Fleet validation (~/projects)

First run found a real false positive: ${TARGET_ARCH} in oleks/ii-researcher, oleks/csi-s3, and oleks/common-chronicle (18 hits) — all three declare matrix: {TARGET_ARCH: [...]} and reference ${TARGET_ARCH} unescaped by design (Woodpecker's matrix feature works by substituting ${AXIS_NAME} before parsing). Excluded a pipeline's own matrix axis names and re-ran:

  • 0 stray apostrophes fleet-wide.
  • 8 genuine unescaped-var findings: oleks/deals-site and oleks/element-web-patched (${BUILDKIT_ADDR}/${HP}, shell vars set via step environment:/local assignment), oleks/terminal-agent (${wait}, a local retry-counter shell var).
  • oleks/oracle-adb-backend (checked out locally) and the live oleks/mempalace .woodpecker/test.yaml: clean, confirming no false positives on the two repos known-good post-fix.

Test plan

  • go build ./...
  • go test ./...
  • gofmt -l . clean
  • Ran pipetree -C ~/projects -analyze against the real fleet and reviewed every finding by hand
Closes oleks/pipetree#11. Adds two static checks to `-analyze` (`internal/analyze/footguns.go`), alongside the existing shared-image/broken-depends_on checks: - **StrayApostrophe**: a literal apostrophe inside a `-c bash -euxc '...'`-style single-quoted shell block — including inside a `#` comment — closes the quote early and hands the rest of the script to the outer shell to re-tokenize. Live incident: oleks/mempalace pipeline 218 failed `exit 127` ("patch: command not found", though patch WAS installed) because a comment contained the word "Woodpecker's" — root-caused and fixed in oleks/mempalace commit `54899ec`. The same idiom had already been bitten once before (`e409e70`). - **UnescapedWoodpeckerVar**: Woodpecker substitutes braced `${VAR}` in the raw pipeline text at parse time, before the shell runs — including inside comments. A shell variable meant for the shell needs to be escaped as `$${VAR}` (oleks/mempalace commit `33b880d`). Woodpecker's own `${CI_*}` builtins and a pipeline's own `matrix:` axis names are excluded as legitimate unescaped uses. Both checks are conservative by design to avoid false positives: - StrayApostrophe only fires when it finds the idiom's dedicated closing line (a line that trims to exactly `'`); commands that don't match this precise shape are left unchecked. - UnescapedWoodpeckerVar excludes `CI_*` builtins and, after a real false positive found during fleet validation (see below), the pipeline's own `matrix:` axis names too. ## Testing `go build ./...` and `go test ./...` (single small binary, no full-repo lint per the build-delegation policy). New tests in `internal/analyze/footguns_test.go` cover: - positive cases using the **exact real fixtures**: `testdata/mempalace-stray-apostrophe.yaml` is the actual pipeline-218-broken commit (`54899ec^`), `testdata/mempalace-unescaped-var.yaml` is the actual pre-`33b880d` commit. - negative cases: `testdata/mempalace-fixed.yaml` (today's live, post-fix `.woodpecker/test.yaml`), `testdata/matrix-fixture.yaml` (a real `matrix:` pipeline, guards the false positive below), plus unit tests for `$$`-escaping, `'\''`-escaping, and comment-embedded footguns. ## Fleet validation (`~/projects`) First run found a real false positive: `${TARGET_ARCH}` in oleks/ii-researcher, oleks/csi-s3, and oleks/common-chronicle (18 hits) — all three declare `matrix: {TARGET_ARCH: [...]}` and reference `${TARGET_ARCH}` unescaped by design (Woodpecker's matrix feature works by substituting `${AXIS_NAME}` before parsing). Excluded a pipeline's own matrix axis names and re-ran: - **0** stray apostrophes fleet-wide. - **8** genuine unescaped-var findings: oleks/deals-site and oleks/element-web-patched (`${BUILDKIT_ADDR}`/`${HP}`, shell vars set via step `environment:`/local assignment), oleks/terminal-agent (`${wait}`, a local retry-counter shell var). - oleks/oracle-adb-backend (checked out locally) and the live oleks/mempalace `.woodpecker/test.yaml`: **clean**, confirming no false positives on the two repos known-good post-fix. ## Test plan - [x] `go build ./...` - [x] `go test ./...` - [x] `gofmt -l .` clean - [x] Ran `pipetree -C ~/projects -analyze` against the real fleet and reviewed every finding by hand
oleks added 1 commit 2026-08-03 19:52:00 +03:00
analyze: detect stray-apostrophe and unescaped-${VAR} Woodpecker footguns
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
cb395f5b15
Adds two static checks to -analyze: a literal apostrophe inside a
-c bash -euxc '...' single-quoted shell block (closes the quote early,
dropping the rest of the script to the outer shell - this broke
oleks/mempalace pipeline 218 via a comment containing "Woodpecker's"),
and an unescaped ${VAR} in step commands (Woodpecker substitutes braced
${VAR} at parse time before the shell runs, so shell-owned vars need
$${VAR} - see oleks/mempalace commit 33b880d). Woodpecker's own ${CI_*}
builtins and a pipeline's own matrix: axis names are excluded as
legitimate unescaped uses (the matrix exclusion was added after a fleet
run initially false-positived on oleks/ii-researcher/csi-s3/
common-chronicle's ${TARGET_ARCH} matrix axis).

Verified against ~/projects: 0 stray apostrophes fleet-wide, 8 genuine
unescaped-var findings (oleks/deals-site, oleks/element-web-patched,
oleks/terminal-agent); oleks/oracle-adb-backend and the fixed
oleks/mempalace test.yaml are clean, confirming no false positives on
the two repos known-good post-fix.
oleks merged commit 8ff6f3207e into main 2026-08-03 19:52:46 +03:00
oleks deleted branch footgun-detection 2026-08-03 19:52:47 +03:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/pipetree#12