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.
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 codeFile:
.github/workflows/ui-e2e-tests-v2.yml(trigger: line 8, secrets: lines 44–79)The workflow triggers on
pull_requestand 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 sourcepnpm run build(line 204).envfile (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_dispatchwith a protected environment, or a post-merge/scheduled workflow. Keep thepull_requestE2E run secretless.Medium —
pr-merged.yml: Three fields interpolated raw into JSON (notoJson)File:
.github/workflows/pr-merged.yml(lines 58–60)The
repository-dispatchpayload is built by string interpolation.title,body,labels, andhtml_urlare correctly wrapped intoJson(), but three fields are not:A branch name containing
"or\(both valid in git) would break the JSON structure sent to the downstream Cloud repo.merged_by.loginis constrained to[a-zA-Z0-9-]and is low risk in practice, butbase.refandhead.refare not.Suggested fix: Wrap all three in
toJson(), matching the existing pattern used fortitle/body.Medium —
sdk-pypi-release.yml/mcp-pypi-release.yml:id-token: writewith audit-only egress during PyPI publishFiles:
.github/workflows/sdk-pypi-release.yml(lines 62, 68–71),.github/workflows/mcp-pypi-release.ymlThe publish jobs use OIDC Trusted Publishing (
id-token: write) to authenticate to PyPI, withegress-policy: auditon 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: blockwith a narrow allowlist (PyPI upload endpoint, GitHub, python-hosting CDN), matching the pattern used inbackport.yml.Low —
pr-conflict-checker.yml:pull_request_target+ checkout +persist-credentials: trueFile:
.github/workflows/pr-conflict-checker.yml(trigger: line 5, permissions: lines 26–27, checkout: lines 36–41)The workflow uses
pull_request_targetwithpull-requests: write+issues: write, checks out the PR head, and keepspersist-credentials: true. The only operation on checked-out content isgrepfor 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 egressFile:
.github/workflows/sdk-refresh-aws-services-regions.yml(lines 23–32, 49–53)This scheduled workflow uses
id-token: writeto assume an AWS role via OIDC, then runsutil/update_aws_services_regions.pyagainst live AWS APIs. The Harden Runner isegress-policy: audit. If the Python script orboto3dependency were tampered with, temporary AWS credentials could be exfiltrated with no egress barrier.Suggested fix: Switch to
egress-policy: blockwith an allowlist limited tosts.amazonaws.comand*.amazonaws.com.Low —
issue-triage.lock.yml: AI triage agent — prompt injection via issue contentFile:
.github/workflows/issue-triage.lock.ymlThis auto-generated workflow runs a GitHub Copilot-powered triage agent when a maintainer applies the
ai-issue-reviewlabel. Theactivationjob hasdiscussions: write,issues: write,pull-requests: writeand Harden Runner inegress-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: blockwith onlyapi.github.comand 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: auditrather thanblock. Affected workflows include:sdk-container-build-push.yml,api-container-build-push.yml,ui-container-build-push.yml,mcp-container-build-push.ymlhelm-chart-release.ymlsdk-bump-version.yml,api-bump-version.yml,ui-bump-version.ymlprepare-release.ymllabeler.ymlAll third-party actions are SHA-pinned, which keeps supply-chain risk low. But
egress-policy: blockwith a narrow allowlist would provide meaningful defense-in-depth.backport.ymlalready demonstrates the right pattern.Suggested fix: Audit each release workflow, enumerate its required outbound endpoints, and switch to
egress-policy: blockwith an explicit allowlist.Notes
permissions: {}at the workflow level with narrow per-job grants is consistently applied — good.backport.ymlegress blocking pattern is the model to follow for other release workflows.