Skip to content

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

Merged
stevebeattie merged 10 commits intochainguard-dev:mainfrom
stevebeattie:security/psec-923-actions
May 6, 2026
Merged

fix(ci): harden against template injection and credential exposure#901
stevebeattie merged 10 commits intochainguard-dev:mainfrom
stevebeattie:security/psec-923-actions

Conversation

@stevebeattie
Copy link
Copy Markdown
Member

Summary

This patch series addresses GitHub Actions security findings from the PSEC-923 sweep of chainguard-dev/actions. Ten commits are included.

Patches

0001 — fix(ci): resolve template injection findings

Fixes template-injection findings in three CI workflow files by moving
${{ context }} expressions into env: variables and referencing them via
$VAR_NAME in run: blocks.

Files changed:

  • .github/workflows/test-bump-go-version.yaml — 10 injection points across 5 run: steps
  • .github/workflows/test-matrix-extra-inputs.yaml — 1 injection point; the expression was in a single-quoted string, so echo '...' was converted to printf '%s\n' "$MATRIX_JSON" to allow env var expansion; the receiving step was also given id: check-matrix-output and a blank line separator to distinguish it visually from the preceding step's env: block
  • .github/workflows/test-setup-kind.yaml — 1 injection point in check domain step

All env: blocks are placed before run:, and all generated shell references use
double-quoted "$VAR_NAME" form.

0002 — fix(ci): resolve template injection findings in setup-* composite actions

Fixes template-injection findings across 13 setup-* composite action definitions.
All ${{ inputs.X }} expressions are moved to step-level env: blocks as
INPUTS_X: ${{ inputs.X }} and referenced as ${INPUTS_X} in shell.

Standard runner env vars ($RUNNER_OS, $RUNNER_ARCH, $GITHUB_TOKEN, etc.) are
used directly without env: entries since the runner sets them unconditionally.

Files changed:

  • setup-argo-workflows/action.yaml
  • setup-chainguard-terraform/action.yaml
  • setup-gcsfuse/action.yaml
  • setup-hakn/action.yaml
  • setup-k3d/action.yaml
  • setup-kind/action.yaml
  • setup-knative-eventing/action.yaml
  • setup-knative/action.yaml
  • setup-melange/action.yaml
  • setup-mink/action.yaml
  • setup-mirror/action.yamljq invocation converted to use --arg instead of
    single-quote-break alternation ('"${VAR}"'); --arg handles values containing
    ", \, or ] safely and avoids quoting complexity
  • setup-monitoring/action.yaml
  • setup-spdx/action.yaml

0003 — fix(ci): resolve template injection findings in build/utility composite actions

Fixes template-injection findings across 19 build and utility composite action
definitions, using the same env:/INPUTS_* pattern as 0002.

Files changed:

  • boilerplate/action.yaml
  • eof-newline/action.yaml
  • git-tag/action.yml
  • gofmt/action.yaml
  • goimports/action.yaml
  • inky-build-pkg/action.yaml
  • k8s-diag/action.yaml
  • kind-diag/action.yaml
  • matrix-extra-inputs/action.yml
  • melange-build-pkg/action.yaml
  • melange-keygen/action.yaml
  • nodiff/action.yaml
  • release-notes/action.yml
  • setup-gitsign/action.yml
  • setup-terraform-docs/action.yml
  • setup-terraform/action.yml
  • shellcheck/action.yaml
  • terraform-diag/action.yaml
  • trailing-space/action.yaml
  • wgetsum/action.yaml

0004 — fix(ci): resolve template injection in steps.outputs context

Fixes template-injection findings where ${{ steps.X.outputs.Y }} expressions
appear in run: blocks. These are moved to env: variables using a
STEP_X_Y: ${{ steps.X.outputs.Y }} naming convention.

Files changed:

  • bump-go-version-file/action.ymlSTEP_BUMP_OLD_VERSION, STEP_BUMP_NEW_VERSION, STEP_CREATE_PR_UPDATE_DIFF

0005 — fix(ci): address github-env findings in composite actions

Fixes github-env findings where user-controlled values are written to $GITHUB_ENV
or $GITHUB_PATH without stripping newlines, enabling potential newline-injection
attacks. Fixes applied: strip newlines with tr -d '\n' before writing to
$GITHUB_ENV; use printf '%s\n' instead of echo for $GITHUB_PATH writes.

Files changed:

  • chainguard-install/action.yml
  • hugo2confluence/action.yaml
  • setup-argo-workflows/action.yaml
  • setup-gitsign/action.yml
  • setup-kind/action.yaml
  • setup-terraform-docs/action.yml

0006 — fix(ci): add persist-credentials: false to checkout steps

Fixes 4 artipacked findings:

  • .github/workflows/release.yamlpersist-credentials: false added; git fetch --tags does not need credentials since the repo is public, and subsequent git-tag/goreleaser steps receive the token via their own inputs
  • .github/workflows/test-apt-faster.yamlpersist-credentials: false (test workflow, no push ops)
  • .github/workflows/test-setup-eksctl.yamlpersist-credentials: false (test workflow, no push ops)
  • .github/workflows/test-shellcheck.yamlpersist-credentials: false (test workflow, no push ops)

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

  • .github/zizmor.yml — appends suppressions for concurrency-limits (15 findings),
    anonymous-definition (9 findings), and undocumented-permissions (1 finding);
    none of these have exploitable security impact
  • .github/workflows/zizmor.yaml — adds persona: pedantic to the zizmor-action
    step to ensure template-injection findings at the pedantic threshold are also caught
    in CI

0008 — fix(ci): resolve shellcheck findings in composite action files

Fixes shellcheck findings at --severity=info across 7 composite action definition
files (setup-terraform is handled in 0003 where its env: block was already added):

  • bump-go-version-file: quote $GITHUB_STEP_SUMMARY in all 13 redirect targets (SC2086)
  • chainguard-install: add # shellcheck disable=SC2086 comment for intentional word splitting when expanding $PACKAGES (a space-separated package list passed to run_apk)
  • git-tag: quote inner expansion in ${latest_tag#"$git_tag_prefix"} parameter substitution (SC2295)
  • matrix-extra-inputs: quote $k and $v in jq argument construction; quote $GITHUB_OUTPUT redirect (SC2086)
  • release-notes: split export VAR=$(...) into separate declaration and export for ORG and START_SHA (SC2155); quote $LAST_BRANCH and $BRANCH in git merge-base call (SC2086); quote $GITHUB_OUTPUT redirect
  • setup-gitsign: quote $HOME path and $1 argument in shaprog function (SC2086); quote RHS of != comparison in [[ to prevent glob matching (SC2053); quote $GITHUB_PATH redirect
  • setup-terraform-docs: quote $HOME path and $1 argument in shaprog function (SC2086); quote RHS of != comparison (SC2053); quote $GITHUB_PATH redirect

0009 — fix(ci): resolve shellcheck findings in remaining composite action files

Fixes shellcheck findings at --severity=info across 21 composite action files
touched by the template-injection and github-env patches but not yet addressed:

  • SC2164 (cd/pushd without || exit): boilerplate, gofmt, goimports, hugo2confluence, melange-build-pkg
  • SC2242 (exit -1 invalid exit code): setup-hakn, setup-knative, setup-melange
  • SC2259 (redirect overrides piped input in cat | cmd <<EOF): setup-knative, setup-knative-eventing
  • SC2166 (compound -a in [ ]): setup-knative
  • SC2155 (export VAR=$(...) masks return value): setup-hakn, setup-knative, terraform-diag
  • SC1083 (literal {} in jsonpath expression): setup-hakn, setup-knative
  • SC2068 (unquoted array expansion ${arr[@]}): hugo2confluence
  • SC2034 (unused variable): setup-gcsfuse
  • SC2086 (unquoted variables): eof-newline, gofmt, goimports, hugo2confluence, k8s-diag, kind-diag, melange-build-pkg, nodiff, setup-argo-workflows, setup-chainguard-terraform, setup-gcsfuse, setup-hakn, setup-k3d, setup-kind, setup-knative, setup-knative-eventing, setup-melange, shellcheck, wgetsum
  • SC2046 (unquoted $(find ...) for intentional word-splitting): gofmt, goimports; suppressed with explanation comment
  • SC2154 (variable set via env: block not visible to shellcheck): shellcheck/action.yaml; suppressed with explanation comment

0010 — fix(ci): resolve actionlint findings in workflow files

  • .github/actionlint.yaml (new) — suppresses a false positive in test-shellcheck.yaml: actionlint cannot parse type: boolean in composite action inputs, but this field is valid per the GitHub Actions schema
  • test-apt-faster.yaml — add # shellcheck disable=SC2317 before stderr() helper function, consistent with the existing suppressions on the other helpers in that script
  • test-bump-go-version.yaml — fix SC2002 (useless cat): cat file | trtr < file (two instances)

Suppressions Not Applied

  • dependabot-cooldown — out of scope for this campaign (config-level policy finding,
    not a workflow security issue; existing zizmor.yml already adjusts the cooldown to 3 days)

References

  • Campaign: PSEC-923
  • zizmor version: 1.24.1
  • Sweep date: 2026-05-02

@stevebeattie stevebeattie requested review from antitree and egibs May 5, 2026 23:33

- if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
run: echo "$HOME/.argo-cli" >> $GITHUB_PATH
run: printf '%s\n' "$HOME/.argo-cli" >> "$GITHUB_PATH"
Comment thread setup-gitsign/action.yml

- if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
run: echo "$HOME/.gitsign" >> $GITHUB_PATH
run: printf '%s\n' "$HOME/.gitsign" >> "$GITHUB_PATH"

- if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
run: echo "$HOME/.terraform-docs" >> $GITHUB_PATH
run: printf '%s\n' "$HOME/.terraform-docs" >> "$GITHUB_PATH"
stevebeattie and others added 10 commits May 5, 2026 16:52
Move ${{ context }} expressions to env: variables to prevent shell injection.

Refs: PSEC-923
…ions

Moves ${{ inputs.* }} expressions from run: blocks into step-level env:
variables across 13 setup-* composite actions, fixing 50+ template-injection
zizmor findings.

- setup-argo-workflows: INPUTS_ARGO_VERSION; fix fragmented URL quoting in
  curl/cosign calls and simplify log_info message
- setup-chainguard-terraform: INPUTS_AUDIENCE, INPUTS_ENVIRONMENT; fix
  fragmented string concatenation (issuer."${VAR}" → "issuer.${VAR}")
- setup-gcsfuse: INPUTS_VERSION; use built-in GITHUB_TOKEN, RUNNER_OS
- setup-hakn: INPUTS_ISTIO_VERSION, INPUTS_CLUSTER_DOMAIN,
  INPUTS_ENABLE_AUTO_INJECTION, INPUTS_VERSION, INPUTS_SERVING_*;
  fix heredoc YAML bugs (tag: and enableNamespacesByDefault: had literal
  double-quotes injected into YAML output, changing type/syntax);
  fix fragmented path/URL quoting
- setup-k3d: INPUTS_K3D_VERSION, INPUTS_WORKER_COUNT, INPUTS_K3S_IMAGE,
  INPUTS_REGISTRY_{HOST,PORT,MIRROR}, INPUTS_MAX_PODS; fix fragmented URL
- setup-kind: INPUTS_KIND_VERSION, INPUTS_K8S_VERSION,
  INPUTS_REGISTRY_AUTHORITY, INPUTS_KIND_WORKER_COUNT,
  INPUTS_SERVICE_ACCOUNT_ISSUER, INPUTS_CLUSTER_SUFFIX, INPUTS_FEATURE_GATES,
  INPUTS_REGISTRY_{USERNAME,PASSWORD,VOLUME}
- setup-knative: INPUTS_{VERSION,SERVING_VERSION,EVENTING_VERSION,
  ISTIO_VERSION,KINGRESS,CLUSTER_DOMAIN,SERVING_*}; fix fragmented istioctl path
- setup-knative-eventing: INPUTS_VERSION; fix fragmented URL in curl
- setup-melange: INPUTS_VERSION; use built-in GITHUB_TOKEN
- setup-mink: INPUTS_VERSION; fix fragmented URL and filename quoting
- setup-mirror: INPUTS_MIRROR; jq converted from single-quote-break to --arg
  to safely handle mirror URLs containing special characters
- setup-monitoring: INPUTS_MONITORING_NAMESPACE, INPUTS_PROMETHEUS_CHART_VERSION
- setup-spdx: INPUTS_SPDX_TOOLS_VERSION, INPUTS_SBOM_PATH, INPUTS_SBOM_FORMAT;
  fix fragmented URL, filename, and jar path quoting

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…te actions

Moves ${{ inputs.* }}, ${{ runner.* }}, and ${{ github.* }} expressions from
run: blocks into step-level env: variables (or replaces with standard runner
env vars) across 20 composite action files.

- boilerplate: INPUTS_{LANGUAGE,BOILERPLATE_DIRECTORY,EXTENSION,EXCLUDE}
- eof-newline: INPUTS_PATH (added to existing env block)
- git-tag: INPUTS_{BUMP_LEVEL,FORCED_VERSION,GIT_TAG_PREFIX,DRY_RUN,
  COMMITTER,AUTHOR} (added to existing env block)
- gofmt: INPUTS_ARGS (added to existing env block)
- goimports: INPUTS_LOCAL (added to existing env block)
- inky-build-pkg: INPUTS_PACKAGE_NAME; github.workspace → $GITHUB_WORKSPACE
- k8s-diag: INPUTS_CLUSTER_RESOURCES, INPUTS_NAMESPACE_RESOURCES,
  INPUTS_ARTIFACT_NAME (across 4 steps)
- kind-diag: INPUTS_CLUSTER_RESOURCES, INPUTS_NAMESPACE_RESOURCES,
  INPUTS_ARTIFACT_NAME, INPUTS_GRAFANA_{NAMESPACE,SVC,DASHBOARDS,
  USERNAME,PASSWORD}, INPUTS_START_TIME (across 5 steps)
- matrix-extra-inputs: INPUTS_MATRIX_JSON; echo → printf to preserve
  JSON content exactly
- melange-build-pkg: 18 INPUTS_* vars across 2 steps
- melange-keygen: INPUTS_SIGNING_KEY_PATH (2 steps)
- nodiff: INPUTS_{PATH,FIXUP_COMMAND}; github.repository → $GITHUB_REPOSITORY
- release-notes: INPUTS_{BRANCH_NAME,START_REV,END_REV,CHANGELOG_FILENAME};
  github.repository → $GITHUB_REPOSITORY (2 steps)
- setup-gitsign: runner.{os,arch} → $RUNNER_{OS,ARCH};
  github.workflow → $GITHUB_WORKFLOW (all standard runner vars, no env: needed)
- setup-terraform: INPUTS_{TERRAFORM_VERSION_FILE,DEFAULT_TERRAFORM_VERSION}
- setup-terraform-docs: runner.{os,arch} → $RUNNER_{OS,ARCH}
  (standard runner vars, no env: needed)
- shellcheck: github.token → $GITHUB_TOKEN (standard runner var)
- terraform-diag: INPUTS_JSON_FILE (2 steps)
- trailing-space: INPUTS_PATH (added to existing env block)
- wgetsum: INPUTS_{URL,OUTPUT,CHECKSUM,WGET_FLAGS}

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes template-injection findings where steps.outputs.* expressions were
interpolated directly into run: blocks.

- bump-go-version-file: STEP_BUMP_{OLD,NEW}_VERSION,
  STEP_CREATE_PR_UPDATE_DIFF (in Dry-run output step)

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hardens writes to GITHUB_ENV and GITHUB_PATH against newline-injection.

setup-kind: Compute REGISTRY_NAME and REGISTRY_PORT into local variables
with explicit newline stripping (tr -d '\n') before writing to GITHUB_ENV,
since the values are derived from user-provided inputs.registry-authority.

chainguard-install: Strip newlines from APK token before constructing
HTTP_AUTH; use printf to write to GITHUB_ENV and GITHUB_PATH.

hugo2confluence, setup-argo-workflows, setup-gitsign, setup-terraform-docs:
Use printf '%s\n' to write to GITHUB_PATH and quote the variable path.
These values are system-controlled paths ($HOME/..., $PWD/bin), but the
printf form makes the intent explicit.

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents GitHub token from persisting in the git config after checkout,
reducing credential exposure window.

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enable pedantic persona to catch all template expansions. Suppress
concurrency-limits, anonymous-definition, and undocumented-permissions
which have no exploitable security impact.

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- bump-go-version-file: quote $GITHUB_STEP_SUMMARY in all redirects (SC2086)
- chainguard-install: disable SC2086 for intentional word splitting in PACKAGES expansion
- git-tag: quote expansion inside parameter substitution (SC2295)
- matrix-extra-inputs: quote $k and $v in jq argument; quote $GITHUB_OUTPUT redirect
- release-notes: split export+assign for ORG, REPO, START_SHA (SC2155);
  quote $LAST_BRANCH and origin/$BRANCH in git merge-base; quote $GITHUB_OUTPUT
- setup-gitsign: quote $HOME paths and $1 in shaprog; quote RHS of != comparison (SC2053)
- setup-terraform-docs: quote $HOME paths and $1 in shaprog; quote RHS of !=

Refs: PSEC-923

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes shellcheck findings at --severity=info across 21 composite action
files that were touched by the template-injection and github-env patches
but whose pre-existing shellcheck issues were not yet addressed.

Changes by finding type:
- SC2164 (cd/pushd without || exit): boilerplate, gofmt, goimports,
  hugo2confluence, melange-build-pkg
- SC2242 (exit -1 invalid): setup-hakn, setup-knative, setup-melange
- SC2259 (redirect overrides piped input): setup-knative, setup-knative-eventing
- SC2166 (compound -a in [ ]): setup-knative
- SC2155 (export with assignment): setup-hakn, setup-knative, terraform-diag
- SC1083 (literal {} in jsonpath): setup-hakn, setup-knative
- SC2068 (unquoted array expansion): hugo2confluence
- SC2034 (unused variable): setup-gcsfuse
- SC2086 (unquoted variables): eof-newline, gofmt, goimports, hugo2confluence,
  k8s-diag, kind-diag, melange-build-pkg, nodiff, setup-argo-workflows,
  setup-chainguard-terraform, setup-gcsfuse, setup-hakn, setup-k3d, setup-kind,
  setup-knative, setup-knative-eventing, setup-melange, shellcheck, wgetsum
- SC2046 (unquoted find expansion, intentional): gofmt, goimports; suppressed
  with explanation comment
- SC2154 (var from env: block unseen by shellcheck): shellcheck/action.yaml;
  suppressed with explanation comment
- Add .github/actionlint.yaml to suppress false positives:
  - test-shellcheck.yaml: actionlint cannot parse 'type: boolean' in
    composite action inputs (valid per GH Actions schema, but unknown
    to actionlint)
- Fix SC2317,SC2329 on helper functions in test-apt-faster.yaml:
  add suppress before stderr() (called indirectly); update all existing
  SC2317 suppresses to SC2317,SC2329 to cover shellcheck >= 0.10.0,
  which uses SC2329 specifically for function-never-invoked findings
- Fix get_yamls find command in actionlint.yaml workflow to exclude
  .github/*.yml and .github/*.yaml config files (zizmor.yml,
  actionlint.yaml) which are not workflow files and should not be
  passed to actionlint for parsing
- Fix SC2002 (useless cat) in test-bump-go-version.yaml:
  cat file | tr -> tr < file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@stevebeattie stevebeattie force-pushed the security/psec-923-actions branch from 88be648 to 1abeaf3 Compare May 5, 2026 23:53
@stevebeattie stevebeattie merged commit 857131c into chainguard-dev:main May 6, 2026
52 of 53 checks passed
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.

4 participants