Skip to content

fix(ci): harden against template injection and credential exposure#15

Merged
stevebeattie merged 5 commits into
mainfrom
security/psec-923-wolfi-act
May 5, 2026
Merged

fix(ci): harden against template injection and credential exposure#15
stevebeattie merged 5 commits into
mainfrom
security/psec-923-wolfi-act

Conversation

@stevebeattie

@stevebeattie stevebeattie commented May 5, 2026

Copy link
Copy Markdown
Member

fix(ci): harden against template injection and credential exposure

Refs: PSEC-923
Repo: wolfi-dev/wolfi-act
Scan date: 2026-05-03
Patches: 4

Summary

This patch series addresses security findings from zizmor --persona=auditor across
the workflows and composite action in wolfi-dev/wolfi-act. The four patches are
atomic by change type and apply cleanly in order.


Patch 0001 — fix(ci): resolve template injection findings; fix env file for container

File changed: action.yml

Six template-injection findings identified in the composite action's single run: block.
All six involved ${{ inputs.* }} template expressions evaluated by GitHub Actions before
the shell runs. The affected expressions were:

  • ${{inputs.debug}} — passed as a single-quoted shell value
  • ${{inputs.command}} — used in an empty check and passed to bash for execution
  • ${{inputs.packages}} — used for package list iteration
  • ${{ inputs.apko-image }} — passed as a docker image argument
  • ${{ inputs.command }} (×2) — echoed and passed as the script to bash -c

Fix: Added an env: block (before run: for readability) with four variables:
INPUT_DEBUG, INPUT_COMMAND, INPUT_PACKAGES, INPUT_APKO_IMAGE. Updated all six
in-shell references to use "${VAR}" double-quoted expansions. The single-quote shell
context was intentional to protect against glob expansion, but the GitHub Actions template
is evaluated before the shell sees the script, so single-quoting does not prevent injection
of special characters from input values.

Also in this patch: Changed env > wolfi-act.github.env to a while loop over
env -0 to safely filter INPUT_* variables and any vars with embedded newlines before
passing the env file to any line-oriented consumer. Moving inputs to env: variables
causes INPUT_COMMAND (a multi-line shell script) to appear in the runner's environment.
Plain env outputs multi-line values as raw newlines, so a simple grep -v '^INPUT_'
only removes the first line — continuation lines pass through and break consumers expecting
one NAME=VALUE per line. A grep -z -v $'\n' filter to drop multi-line records also
fails: grep -v returns exit code 1 when nothing is filtered out, which kills the
pipeline under set -o pipefail. The while loop over env -0 (null-terminated
records) avoids both problems: each record is processed atomically regardless of embedded
newlines, and the loop always exits 0.

Note: inputs.command is by design user-supplied executable shell code (the entire purpose
of wolfi-act is to run arbitrary commands in a Wolfi ephemeral container). Moving to an env
var prevents template injection at the Actions layer; the command still executes as bash.
See manual-review.md for a documentation recommendation.


Patch 0002 — fix(ci): add persist-credentials: false and scope workflow permissions

File changed: .github/workflows/ci.yml

Artipacked (2 findings): Both actions/checkout steps in ci.yml (jobs ci and
ci-debug) lacked persist-credentials: false. Neither job performs any git write
operations after checkout, and the action under test (./) uses its own Docker-based
ephemeral runner that does not use the git credential store. persist-credentials: false
is correct.

Excessive permissions (3 findings): ci.yml had no workflow-level permissions: block
(defaulting to broad implicit permissions) and no per-job permissions: blocks. Both jobs
only require contents: read (for checkout). Fixed by:

  • Adding permissions: {} at workflow level (deny-all default)
  • Adding permissions: { contents: read } explicitly to both ci and ci-debug jobs

No changes to build.yml — its existing permissions: blocks are correctly scoped
(contents: read at workflow level; packages: write and id-token: write at build-job
level for GHCR push and cosign keyless signing respectively).


Patch 0003 — fix(ci): add pedantic persona and suppress noisy zizmor rules

Files changed: .github/workflows/zizmor.yaml, .github/zizmor.yml

zizmor persona: Added persona: pedantic to the zizmorcore/zizmor-action step in
zizmor.yaml. This switches from the default regular persona to pedantic, catching
all ${{ }} template expansions in run: blocks (not only attacker-controlled sources).

Paths triggers: Added paths: filters to both pull_request and push triggers in
zizmor.yaml to include action.yml, .github/dependabot.yml, and .github/zizmor.yml.
The action.yml entry is necessary because wolfi-act's composite action definition lives at
the repo root (not under .github/), so changes to it would otherwise not trigger the check.

Rule suppression in .github/zizmor.yml: Added three disabled rules:

  • anonymous-definition — pedantic-only, no security impact (cosmetic)
  • undocumented-permissions — pedantic-only, pure documentation style
  • concurrency-limits — low security value, generates 4 findings in this repo alone

These suppressed the 4 concurrency-limits, 4 anonymous-definition, and 1
undocumented-permissions findings that are noise without actionable remediation.


Patch 0004 — fix(ci): quote $PWD in docker run arguments (SC2086)

File changed: action.yml

Two occurrences of -v ${PWD}:/work \ in docker run commands were unquoted.
Quoted to -v "${PWD}":/work \ to prevent word splitting on paths with spaces
(SC2086). No logic change.


Findings not auto-fixed

1 × stale-action-refsbuild.yml:31

wolfi-dev/wolfi-act@c7bc05c8af23bca710b267e0db3b39c939eb7b02 # main is a self-referential
pin (the workflow references itself). zizmor flags this SHA as not corresponding to a Git
tag. Requires an online check to determine if a tag has since been created for this commit.
Documented in manual-review.md.


Finding counts

Rule Found Fixed Manual review Suppressed
template-injection 6 6 0
artipacked 2 2 0
excessive-permissions 3 3 0
stale-action-refs 1 0 1
concurrency-limits 4 4
anonymous-definition 4 4
undocumented-permissions 1 1
Total 21 11 1 9

Refs: PSEC-923

@stevebeattie stevebeattie requested review from antitree and egibs May 5, 2026 00:15
@stevebeattie stevebeattie marked this pull request as draft May 5, 2026 00:17
@stevebeattie stevebeattie force-pushed the security/psec-923-wolfi-act branch 2 times, most recently from 38eae37 to ef9aa3e Compare May 5, 2026 00:49
stevebeattie and others added 4 commits May 4, 2026 17:54
Move ${{ context }} expressions to env: variables to prevent shell
injection at the GitHub Actions template layer.

Also filter INPUT_* variables from wolfi-act.github.env before passing
it to `docker run --env-file`. INPUT_COMMAND is a multi-line script;
Docker's env file parser rejects values containing whitespace, causing
exit code 125. The INPUT_* vars are consumed by the action script before
the container runs and do not need to be forwarded into the container.

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add persist-credentials: false to both checkout steps in ci.yml, since
no downstream git operations rely on the credential store. Add
permissions: {} at workflow level and explicit contents: read per-job
blocks for ci and ci-debug jobs.

Refs: PSEC-923
Switch zizmor CI workflow to pedantic persona to catch all template
expansions in run: blocks. Add paths: triggers to zizmor.yaml to ensure
changes to .github/zizmor.yml and .github/dependabot.yml are checked.

Disable anonymous-definition, undocumented-permissions, and
concurrency-limits in .github/zizmor.yml — these are pedantic-only
rules with no direct security value that would generate CI noise without
actionable remediation.

Refs: PSEC-923
Quote ${PWD} in two docker run -v mount arguments to prevent word
splitting on paths with spaces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@stevebeattie stevebeattie force-pushed the security/psec-923-wolfi-act branch from ef9aa3e to 91a6dc1 Compare May 5, 2026 00:55
@stevebeattie stevebeattie marked this pull request as ready for review May 5, 2026 01:13
@stevebeattie

Copy link
Copy Markdown
Member Author

The ci failing here is not cuased by this pr, but is because https://raw.githubusercontent.com/chainguard-images/images/main/images/maven/config/template.apko.yaml no longer exists (a raw apko config is no longer used for image config). This is intended to be addressed by #16 which switches to an example apko config from the apko repository.

@stevebeattie stevebeattie merged commit 7f6915e into main May 5, 2026
8 checks passed
@stevebeattie stevebeattie deleted the security/psec-923-wolfi-act branch May 5, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants