Skip to content

security(ci): GitHub Actions security hardening findings #11183

@johannes-engler-mw

Description

@johannes-engler-mw

Summary

A review of .github/workflows/ identified several security gaps. Listed in severity order so maintainers can triage and decide what to fix.


High — ui-e2e-tests-v2.yml: Cloud credentials exposed to PR-controlled code

File: .github/workflows/ui-e2e-tests-v2.yml (trigger: line 8, secrets: lines 44–79)

The workflow triggers on pull_request and exposes 30+ cloud/API secrets as environment variables (AWS, Azure, M365, GCP, GitHub, OCI, Alibaba Cloud). PR-controlled code then runs with those secrets available:

  • docker build ./api (line 137) — builds from PR source
  • pnpm run build (line 204)
  • Playwright test suite (line 220)
  • AWS credentials are also written to a .env file (lines 129–131)

Harden Runner is in egress-policy: audit (line 90), so outbound calls are logged but not blocked.

Fork PRs do not receive repository secrets, so the risk is limited to same-repo PRs — any collaborator with push access, or a compromised write-capable account, can exfiltrate all credentials.

Suggested fix: Move the secret-backed E2E tests behind push, workflow_dispatch with a protected environment, or a post-merge/scheduled workflow. Keep the pull_request E2E run secretless.


Medium — pr-merged.yml: Three fields interpolated raw into JSON (no toJson)

File: .github/workflows/pr-merged.yml (lines 58–60)

The repository-dispatch payload is built by string interpolation. title, body, labels, and html_url are correctly wrapped in toJson(), but three fields are not:

"PROWLER_PR_MERGED_BY": "${{ github.event.pull_request.merged_by.login }}",
"PROWLER_PR_BASE_BRANCH": "${{ github.event.pull_request.base.ref }}",
"PROWLER_PR_HEAD_BRANCH": "${{ github.event.pull_request.head.ref }}"

A branch name containing " or \ (both valid in git) would break the JSON structure sent to the downstream Cloud repo. merged_by.login is constrained to [a-zA-Z0-9-] and is low risk in practice, but base.ref and head.ref are not.

Suggested fix: Wrap all three in toJson(), matching the existing pattern used for title/body.


Medium — sdk-pypi-release.yml / mcp-pypi-release.yml: id-token: write with audit-only egress during PyPI publish

Files: .github/workflows/sdk-pypi-release.yml (lines 62, 68–71), .github/workflows/mcp-pypi-release.yml

The publish jobs use OIDC Trusted Publishing (id-token: write) to authenticate to PyPI, with egress-policy: audit on the Harden Runner. The GitHub environment approval gates (pypi-prowler, pypi-prowler-cloud) are a good control. But once a run is approved and executing, a supply-chain compromise of any action in the build pipeline could push a malicious package or exfiltrate the ephemeral OIDC token — and egress would not be blocked.

Suggested fix: Switch publish jobs to egress-policy: block with a narrow allowlist (PyPI upload endpoint, GitHub, python-hosting CDN), matching the pattern used in backport.yml.


Low — pr-conflict-checker.yml: pull_request_target + checkout + persist-credentials: true

File: .github/workflows/pr-conflict-checker.yml (trigger: line 5, permissions: lines 26–27, checkout: lines 36–41)

The workflow uses pull_request_target with pull-requests: write + issues: write, checks out the PR head, and keeps persist-credentials: true. The only operation on checked-out content is grep for conflict markers — no PR code is executed — so this is not currently exploitable. However, having a write-capable token present in a checkout of untrusted content is fragile: any future step that runs content from the working directory would be immediately exploitable.

The workflow already has # zizmor: ignore[artipacked] and # zizmor: ignore[dangerous-triggers] comments indicating awareness.

Suggested fix (optional hardening): Split into two jobs — a read-only conflict detection job (no write token, persist-credentials: false) and a separate write job that only consumes detection output.


Low — sdk-refresh-aws-services-regions.yml: AWS OIDC credentials with audit-only egress

File: .github/workflows/sdk-refresh-aws-services-regions.yml (lines 23–32, 49–53)

This scheduled workflow uses id-token: write to assume an AWS role via OIDC, then runs util/update_aws_services_regions.py against live AWS APIs. The Harden Runner is egress-policy: audit. If the Python script or boto3 dependency were tampered with, temporary AWS credentials could be exfiltrated with no egress barrier.

Suggested fix: Switch to egress-policy: block with an allowlist limited to sts.amazonaws.com and *.amazonaws.com.


Low — issue-triage.lock.yml: AI triage agent — prompt injection via issue content

File: .github/workflows/issue-triage.lock.yml

This auto-generated workflow runs a GitHub Copilot-powered triage agent when a maintainer applies the ai-issue-review label. The activation job has discussions: write, issues: write, pull-requests: write and Harden Runner in egress-policy: audit. The agent processes the full issue body and title (attacker-controlled) and can make GitHub API write calls via the GitHub MCP server.

A crafted issue body designed to hijack the agent's instructions could cause it to post deceptive comments, modify labels, or interact with PRs. The label-application gate (a maintainer must trigger it) raises the bar significantly, but the blast radius of a successful prompt injection is real.

Suggested fix: Add system-level instruction boundaries telling the model to ignore meta-instructions in issue content; restrict write operations to only what is needed; switch to egress-policy: block with only api.github.com and the Copilot endpoint allowlisted.


Low (pattern) — Audit-only egress across most write-capable release workflows

Many workflows that push to container registries, publish packages, commit version bumps, or create releases use egress-policy: audit rather than block. Affected workflows include:

  • sdk-container-build-push.yml, api-container-build-push.yml, ui-container-build-push.yml, mcp-container-build-push.yml
  • helm-chart-release.yml
  • sdk-bump-version.yml, api-bump-version.yml, ui-bump-version.yml
  • prepare-release.yml
  • labeler.yml

All third-party actions are SHA-pinned, which keeps supply-chain risk low. But egress-policy: block with a narrow allowlist would provide meaningful defense-in-depth. backport.yml already demonstrates the right pattern.

Suggested fix: Audit each release workflow, enumerate its required outbound endpoints, and switch to egress-policy: block with an explicit allowlist.


Notes

  • All third-party actions across the repository are pinned to full commit SHAs — good baseline.
  • permissions: {} at the workflow level with narrow per-job grants is consistently applied — good.
  • The backport.yml egress blocking pattern is the model to follow for other release workflows.

Metadata

Metadata

Assignees

Labels

feature-requestNew feature request for Prowler.github_actionsPull requests that update GitHub Actions code

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions