Skip to content

Harden CI supply chain and add an org-wide security policy - #275

Merged
swissspidy merged 1 commit into
mainfrom
claude/wp-cli-github-security-audit-m0tl72
Aug 7, 2026
Merged

Harden CI supply chain and add an org-wide security policy#275
swissspidy merged 1 commit into
mainfrom
claude/wp-cli-github-security-audit-m0tl72

Conversation

@swissspidy

@swissspidy swissspidy commented Aug 7, 2026

Copy link
Copy Markdown
Member

Addresses findings from a security review of this repository. These workflows
are consumed by ~45 repositories in the organization, so changes here apply
org-wide.

Rebased onto #274, which independently pinned actionlint by digest and added
--fail to the two curl calls in the code quality workflow. Those parts of the
review are covered by that change and are dropped here; #274's actionlint pin
resolves to the linux/amd64 manifest of v1.7.11, which is correct for the
GitHub-hosted runners these jobs use.

Add SECURITY.md
This repository provides the organization's default community health files,
but had no security policy, so no wp-cli repository surfaced a "Report a
vulnerability" path. Points at the WP-CLI handbook and the WordPress
HackerOne program rather than restating policy that lives elsewhere.

Scope actions: write to the job that needs it
It was declared at workflow level, so it also reached triage-new-item,
which runs on pull_request_target and processes pull request titles and
bodies written by anyone who can open a PR. actions: write permits
dispatching workflows and deleting caches and artifacts, a known
lateral-movement path. Only triage-unlabeled-items needs it, and that job
is workflow_dispatch-only.

The grant has to stay in the caller, since a caller can only cap a reusable
workflow's permissions and never raise them; removing it there is what broke
dispatching in #271 and prompted the revert in #272. Comments in both files
record this so the next attempt narrows rather than removes.

Pin gherkin-lint
npx --yes gherkin-lint resolved and executed the newest publish on every
run. The package was last released in December 2023 and has two maintainers,
so a single account compromise would reach CI in every repository.

Correct the actions/setup-node pin comment
The pinned SHA is v7.0.0, but the trailing comment read v6. The pin itself is
immutable and current; the comment is what reviewers read, so a wrong one
quietly defeats the point of the convention.

Fail the WP-CLI download loudly
curl -O without -f writes the error body to the file and still exits 0,
so an outage installed an HTML page as /usr/local/bin/wp and surfaced as a
confusing failure much later.

Reduce blast radius of the workflow sync
SKIP_DELETE stops a pattern change from deleting files across every target
repository. The FILE_PATTERNS regexes now escape their dots so they match
only the intended paths. Both jobs drop to contents: read: the sync
authenticates with ACTIONS_BOT, and the action reads that token from its
with: input and makes no API calls, so GITHUB_TOKEN is never used at all.

Summary by CodeRabbit

  • Security

    • Added organization-wide guidance for reporting vulnerabilities, defining scope, actionable findings, and trust-boundary expectations.
  • Bug Fixes

    • Improved command failure handling so download errors stop workflows correctly.
    • Prevented synchronization jobs from deleting files missing from the source.
  • Chores

    • Restricted workflow permissions to the minimum required for each job.
    • Pinned Gherkin linting to a specific version for consistent results.
    • Improved workflow file matching and authentication for synchronization tasks.

Addresses findings from a security review of this repository. These workflows
are consumed by ~45 repositories in the organization, so changes here apply
org-wide.

Rebased onto #274, which independently pinned actionlint by digest and added
`--fail` to the two curl calls in the code quality workflow. Those parts of the
review are covered by that change and are dropped here; #274's actionlint pin
resolves to the linux/amd64 manifest of v1.7.11, which is correct for the
GitHub-hosted runners these jobs use.

Add SECURITY.md
  This repository provides the organization's default community health files,
  but had no security policy, so no wp-cli repository surfaced a "Report a
  vulnerability" path. Points at the WP-CLI handbook and the WordPress
  HackerOne program rather than restating policy that lives elsewhere.

Scope `actions: write` to the job that needs it
  It was declared at workflow level, so it also reached `triage-new-item`,
  which runs on `pull_request_target` and processes pull request titles and
  bodies written by anyone who can open a PR. `actions: write` permits
  dispatching workflows and deleting caches and artifacts, a known
  lateral-movement path. Only `triage-unlabeled-items` needs it, and that job
  is `workflow_dispatch`-only.

  The grant has to stay in the caller, since a caller can only cap a reusable
  workflow's permissions and never raise them; removing it there is what broke
  dispatching in #271 and prompted the revert in #272. Comments in both files
  record this so the next attempt narrows rather than removes.

Pin gherkin-lint
  `npx --yes gherkin-lint` resolved and executed the newest publish on every
  run. The package was last released in December 2023 and has two maintainers,
  so a single account compromise would reach CI in every repository.

Correct the actions/setup-node pin comment
  The pinned SHA is v7.0.0, but the trailing comment read v6. The pin itself is
  immutable and current; the comment is what reviewers read, so a wrong one
  quietly defeats the point of the convention.

Fail the WP-CLI download loudly
  `curl -O` without `-f` writes the error body to the file and still exits 0,
  so an outage installed an HTML page as /usr/local/bin/wp and surfaced as a
  confusing failure much later.

Reduce blast radius of the workflow sync
  SKIP_DELETE stops a pattern change from deleting files across every target
  repository. The FILE_PATTERNS regexes now escape their dots so they match
  only the intended paths. Both jobs drop to `contents: read`: the sync
  authenticates with ACTIONS_BOT, and the action reads that token from its
  `with:` input and makes no API calls, so GITHUB_TOKEN is never used at all.
@swissspidy
swissspidy requested a review from a team as a code owner August 7, 2026 09:00
Copilot AI lite review requested due to automatic review settings August 7, 2026 09:00
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

GitHub Actions hardening

Layer / File(s) Summary
Workflow permission scoping
.github/workflows/issue-triage.yml, .github/workflows/reusable-issue-triage.yml, .github/workflows/sync-workflows.yml
Permissions are documented and scoped to the workflows or jobs that require them. Sync jobs use read-only contents access.
Workflow inputs and synchronization safety
.github/workflows/reusable-code-quality.yml, .github/workflows/reusable-regenerate-readme.yml, .github/workflows/sync-workflows.yml
Node.js and gherkin-lint versions are pinned. WP-CLI downloads fail on HTTP errors. Sync jobs escape file patterns and skip deletion of absent target files.

Repository security policy

Layer / File(s) Summary
Organization security policy
SECURITY.md
The policy defines vulnerability reporting, disclosure guidance, actionable vulnerability criteria, trust-boundary expectations, and shared CI infrastructure scope.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • wp-cli/.github#270: Both PRs harden GitHub Actions and modify permission scoping in sync-workflows.yml.
  • wp-cli/.github#271: Both PRs modify permissions in reusable-issue-triage.yml.

Suggested labels: scope:testing, scope:meta

Suggested reviewers: schlessera, copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two primary changes: CI supply-chain hardening and the organization-wide security policy.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/wp-cli-github-security-audit-m0tl72

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the organization-wide reusable GitHub Actions workflows in wp-cli/.github and adds a default security policy so downstream wp-cli/* repositories surface a consistent vulnerability reporting path.

Changes:

  • Added an org-wide SECURITY.md that directs reporters to the WordPress HackerOne program and the WP-CLI handbook.
  • Reduced token blast radius by scoping actions: write to only the dispatching triage job and dropping workflow-sync jobs to contents: read.
  • Hardened CI execution by pinning gherkin-lint to a specific version and making the WP-CLI download fail loudly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
SECURITY.md Introduces a default, org-wide security reporting policy and scope statement.
.github/workflows/sync-workflows.yml Escapes regex file patterns, prevents deletions via SKIP_DELETE, and reduces permissions to contents: read.
.github/workflows/reusable-regenerate-readme.yml Makes WP-CLI download fail on HTTP errors to avoid silently installing error pages.
.github/workflows/reusable-issue-triage.yml Removes workflow-level actions: write and scopes it to the triage-unlabeled-items dispatch job only.
.github/workflows/reusable-code-quality.yml Corrects the setup-node pin comment and pins gherkin-lint to a fixed version.
.github/workflows/issue-triage.yml Documents why actions: write must remain granted in the caller while being narrowed in the reusable workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/sync-workflows.yml (1)

110-112: 🗄️ Data Integrity & Integration | 🔵 Trivial

Add a cleanup path for SKIP_DELETE.

SKIP_DELETE: true makes both jobs add or update files only. If a source workflow or Dependabot file is removed or renamed, the old file remains in every target repository. This can retain an obsolete or insecure workflow.

Keep the safe default, but add a drift report or an explicit cleanup allowlist.

Also applies to: 178-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sync-workflows.yml around lines 110 - 112, Update the
workflow synchronization jobs using SKIP_DELETE so they retain the safe
no-delete default while detecting source files removed or renamed in target
repositories. Add either a drift report that identifies stale files or an
explicit cleanup allowlist permitting approved deletions, and apply this
consistently to both workflow and Dependabot synchronization paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/reusable-regenerate-readme.yml:
- Around line 49-52: Update the WP-CLI PHAR download step in the workflow to use
an immutable commit or tag instead of the mutable gh-pages reference, then
verify the downloaded artifact with a trusted out-of-band checksum or signature
before installing it as /usr/local/bin/wp. Ensure verification failure stops the
workflow and prevents installation.

---

Nitpick comments:
In @.github/workflows/sync-workflows.yml:
- Around line 110-112: Update the workflow synchronization jobs using
SKIP_DELETE so they retain the safe no-delete default while detecting source
files removed or renamed in target repositories. Add either a drift report that
identifies stale files or an explicit cleanup allowlist permitting approved
deletions, and apply this consistently to both workflow and Dependabot
synchronization paths.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9996aa0-8086-4bc6-b04d-30d2a56431cf

📥 Commits

Reviewing files that changed from the base of the PR and between d6520f3 and 05643d6.

📒 Files selected for processing (6)
  • .github/workflows/issue-triage.yml
  • .github/workflows/reusable-code-quality.yml
  • .github/workflows/reusable-issue-triage.yml
  • .github/workflows/reusable-regenerate-readme.yml
  • .github/workflows/sync-workflows.yml
  • SECURITY.md

Comment on lines +49 to +52
# `-f` matters most here: without it curl writes the error body to the
# file and still exits 0, so an outage silently installs an HTML page
# as /usr/local/bin/wp and the failure surfaces much later.
curl -fsSL -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file=".github/workflows/reusable-regenerate-readme.yml"

if rg -n 'raw\.githubusercontent\.com/wp-cli/builds/(gh-pages|main|master)/' "$file"; then
  echo "Mutable WP-CLI branch reference remains" >&2
  exit 1
fi

if ! rg -n -q '(sha256|sha512|cosign|gpg)' "$file"; then
  echo "No artifact integrity verification found" >&2
  exit 1
fi

Repository: wp-cli/.github

Length of output: 301


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file=".github/workflows/reusable-regenerate-readme.yml"
echo "== file exists and size =="
wc -l "$file" || true
echo
echo "== relevant workflow sections =="
sed -n '1,140p' "$file" | cat -n
echo
echo "== workflow references to this workflow and inputs =="
rg -n "reusable-regenerate-readme|persist-credentials|pull_request|permissions:|wp:" .github/workflows -S || true

Repository: wp-cli/.github

Length of output: 10430


Pin and verify the WP-CLI PHAR before installation.

This workflow has write permissions, persists checkout credentials, and runs wp directly. The download path still follows mutable gh-pages and does not check a digest or signature before moving the PHAR to /usr/local/bin/wp. Use an immutable commit/tag reference and verify an out-of-band checksum or signature before installation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/reusable-regenerate-readme.yml around lines 49 - 52,
Update the WP-CLI PHAR download step in the workflow to use an immutable commit
or tag instead of the mutable gh-pages reference, then verify the downloaded
artifact with a trusted out-of-band checksum or signature before installing it
as /usr/local/bin/wp. Ensure verification failure stops the workflow and
prevents installation.

@swissspidy
swissspidy merged commit 93855f4 into main Aug 7, 2026
14 of 15 checks passed
@swissspidy
swissspidy deleted the claude/wp-cli-github-security-audit-m0tl72 branch August 7, 2026 09:17
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.

3 participants