Skip to content

ci: harden GitHub Actions workflows (injection, pinning, least-privilege) - #92

Merged
ivanasabi merged 2 commits into
developfrom
security/harden-github-actions-workflows
Aug 26, 2026
Merged

ci: harden GitHub Actions workflows (injection, pinning, least-privilege)#92
ivanasabi merged 2 commits into
developfrom
security/harden-github-actions-workflows

Conversation

@ivanasabi

Copy link
Copy Markdown
Contributor

What & why

Behavior-preserving security hardening of the CI / QA / docs GitHub Actions workflows, from a read-only audit (gha-security-review + zizmor + ci-secure). No functional change: every edited step keeps identical inputs, outputs, and runtime behavior; all pins resolve to the exact SHA/digest already in use. Net line count is negative because superfluous GPG blocks were removed.

Scope is deliberately limited to code-level YAML fixes. It excludes anything requiring GitHub org/repo settings, the docs-release App-token scope (WF-SEC-011), and known-latent correctness items flagged for separate follow-up — those are out of scope for a behavior-preserving PR.

Changes

Template-injection hardening

Move PR-influenced values (config from github_config.yml, step outputs) out of run:/github-script ${{ }} splices into step-level env:, referenced via $VARS / process.env.*. A fork PR editing github_config.yml can no longer break out of the interpolation into the runner shell.

  • code-maven_java-QA_e2e_karate.yml: app-config, start-java-app, mvn-clean-verify, stop-java-app, generate-jacoco-report.
  • actions/test-results-verification/action.yml: run: step + the github-script body.

Command-injection removal

  • test-results-summary.sh: drop eval; call the already-validated parser function directly (RESULTS_TYPE is validated against an existing parser file before use).
  • e2e_karate: replace eval "$APP_HEALTH_CHECK_CMD" with a direct curl invocation.

TLS

  • Remove curl -k from the JaCoCo agent/CLI downloads. A MITM of the downloaded -javaagent JAR would be code execution inside CI.

Supply-chain pinning (third-party actions/images → immutable refs)

  • asdf-vm/actions/install@v4@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4
  • yuzutech/kroki@sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d

GITHUB_ENV heredoc injection

  • Random delimiter (openssl rand -hex 16) so untrusted .tool-versions content cannot forge the terminator and inject arbitrary env vars.

Least-privilege

  • permissions: contents: read on the four QA workflows + docs-verify (each self-validated by this PR's own pull_request CI).
  • persist-credentials: false on their checkouts.

Dead credential setup

  • Remove superfluous GPG / ~/.git-credentials setup from docs jobs that never sign or push (docs-verify, docs-publish, docs-build_snapshot).

Validation

  • All 9 YAML files parse (yaml.safe_load); test-results-summary.sh passes bash -n.
  • actionlint introduces no new errors. The only delta is +8 SC2086 info (word-splitting) notices in e2e_karate — these are shellcheck gaining visibility into pre-existing unquoted usages ($MVN_PROPERTIES, $START_APP_CMD, $JACOCO_CLI_REPORT_OPTIONS are intentional, load-bearing word-splits; $JACOCO_VERSION/$APP_LOG_FILE/$APP_PID are trusted values). No assignment I added is unquoted, so runtime word-splitting is unchanged, and env-binding neutralizes injection regardless of downstream quoting (shell variable expansion does not re-parse ;/$()/|).

Reviewer note

This PR edits workflow files that run under pull_request, so its own CI validates the changed workflows from the PR merge commit — the checks on this PR are the test of these edits. Opened as a draft for review.

…ege)

Behavior-preserving security hardening of the CI/QA/docs workflows. No
functional change: every edited step keeps identical inputs, outputs and
runtime behavior. Pins resolve to the exact SHA/digest already in use.

Template-injection hardening (move config/expression values out of run:/JS
splices into env: and reference $VARS, so PR-editable github_config.yml and
step outputs can no longer break into the runner shell):
- code-maven_java-QA_e2e_karate.yml: app-config, start-java-app,
  mvn-clean-verify, stop-java-app and generate-jacoco-report steps.
- actions/test-results-verification/action.yml: run: step and the
  github-script body (now uses process.env.*).

Command-injection removal:
- test-results-summary.sh: drop eval; dispatch the validated parser
  function directly.
- e2e_karate: replace `eval "$APP_HEALTH_CHECK_CMD"` with a direct curl.

TLS: remove `curl -k` on the JaCoCo agent/CLI downloads (a MITM of the
-javaagent JAR is code execution in CI).

Supply-chain pinning:
- asdf-vm/actions/install@v4 -> @b7bcd026f18772e44fe1026d729e1611cc435d47
- yuzutech/kroki -> @sha256:6980bfb218b48b74ea14b888d9c7e8c032d1cb6325f3292277abdf62483abd9d

GITHUB_ENV heredoc: random delimiter (openssl rand) so untrusted
.tool-versions content cannot forge the terminator and inject env vars.

Least-privilege: add `permissions: contents: read` to the four QA
workflows and docs-verify (all self-validated by this PR's own CI);
add `persist-credentials: false` to their checkouts.

Remove superfluous GPG/git-credential setup from docs jobs that never
sign or push (docs-verify, docs-publish, docs-build_snapshot).

Signed-off-by: Ivan Alvarez Sabin <ivanasabi@ext.inditex.com>
@ivanasabi
ivanasabi force-pushed the security/harden-github-actions-workflows branch from 984b228 to 71ba39b Compare August 26, 2026 11:42
…workflows

Clears the CodeQL 'Workflow does not contain permissions' alerts on the
two docs workflows that still lacked a permissions block. Behavior-preserving:
no step inputs, outputs or runtime behavior change; each job gets only the
scopes it actually uses.

docs-build_snapshot.yml:
- check-triggered-release: contents:read + pull-requests:read (reads the
  commit and its associated PR labels via the API; no writes).
- build-snapshot: contents:read + actions:write (clones with
  persist-credentials:false, then dispatches docs-publish via the default
  token -> workflow-dispatch needs actions:write).

docs-release.yml:
- dispatch: actions:write (only re-dispatches this workflow via the default
  token; no checkout).
- release: contents:read (every git push, tag, PR and dispatch uses the App
  token from steps.app-token; the default token only clones the repo).

docs-publish.yml already ships a top-level permissions block and is
unchanged.

Signed-off-by: Ivan Alvarez Sabin <ivanasabi@ext.inditex.com>
@sonarqubecloud

Copy link
Copy Markdown

@ivanasabi
ivanasabi marked this pull request as ready for review August 26, 2026 12:21
@ivanasabi
ivanasabi merged commit e515759 into develop Aug 26, 2026
22 checks passed
@ivanasabi
ivanasabi deleted the security/harden-github-actions-workflows branch August 26, 2026 13:08
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