Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/actions/setup-php/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ runs:
# runs with access to the job. The trailing comment records which release the hash
# is, since a bare hash says nothing about how old the pin is.
#
# Note there is no Dependabot coverage for GitHub Actions in this repository yet, so
# this pin has to be advanced by hand until there is.
# Dependabot advances the hash and the release comment; see `.github/dependabot.yml`.
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ inputs.php-version }}
Expand All @@ -53,4 +52,11 @@ runs:
tools: ${{ inputs.tools }}
- name: Install Composer dependencies
shell: bash
run: composer install ${{ inputs.composer-flags }}
# Through the environment rather than spliced into the command: an expression is
# expanded before the shell ever sees the line, so it would be executed as code
# rather than read as an argument. Unquoted on purpose — the flags are several
# words (`--no-dev --optimize-autoloader`) and have to split into several
# arguments; word splitting is all that happens to an environment variable.
env:
COMPOSER_FLAGS: ${{ inputs.composer-flags }}
run: composer install $COMPOSER_FLAGS
14 changes: 11 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@

version: 2
updates:
- package-ecosystem: "composer" # See documentation for possible values
# The `dependabot-cooldown` suppressions below are deliberate, not an oversight.
# A cooldown makes Dependabot wait a few days after a version is published before
# proposing it, so a hijacked release that is yanked within that window never reaches
# us — at the price of delaying every routine bump by the same few days. Whether
# that trade is worth making for a repository whose every updated package is dev
# tooling that never reaches a user site is
# https://github.com/pluginkollektiv/antispam-bee/issues/855, not something to
# settle in passing while hardening the workflows.
- package-ecosystem: "composer" # zizmor: ignore[dependabot-cooldown]
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "npm" # See documentation for possible values
- package-ecosystem: "npm" # zizmor: ignore[dependabot-cooldown]
directory: "/" # Location of package manifests
schedule:
interval: "daily"
# The workflows pin every action to a commit hash, which stops a moved tag from
# changing what we run — but it also means a pin stays where it is until someone
# moves it. Dependabot advances the hash and the release comment for us. Weekly,
# because actions release far less often than the Composer and npm dependencies.
- package-ecosystem: "github-actions"
- package-ecosystem: "github-actions" # zizmor: ignore[dependabot-cooldown]
directory: "/"
schedule:
interval: "weekly"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/attach-plugin-zip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
# which is what gives the archive its `antispam-bee/` top-level directory.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
ref: ${{ github.event.release.tag_name || inputs.tag }}

- name: Setup PHP and install production dependencies
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/attach-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0

- name: Find the run that classified this commit
Expand Down Expand Up @@ -76,16 +77,18 @@ jobs:
id: download
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ steps.find.outputs.run-id }}
CORPUS: ${{ matrix.corpus }}
run: |
set -euo pipefail
# Not every release has a snapshot for every corpus — a version prepared
# with only the full corpus has no small-corpus artifact. That is not a
# failure; there is simply nothing to attach.
if gh run download "${{ steps.find.outputs.run-id }}" \
--name "asb-detection-snapshot-${{ matrix.corpus }}" --dir snapshot 2>/dev/null; then
if gh run download "$RUN_ID" \
--name "asb-detection-snapshot-$CORPUS" --dir snapshot 2>/dev/null; then
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::No ${{ matrix.corpus }}-corpus snapshot in that run; nothing to attach."
echo "::notice::No $CORPUS-corpus snapshot in that run; nothing to attach."
echo "found=false" >> "$GITHUB_OUTPUT"
fi

Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ on:
# Allow forcing a full run by hand, for example before cutting a release.
workflow_dispatch:

# The jobs only read the checkout and run the suite, so nothing beyond read access
# is needed. Declared explicitly so it stays restricted even if the repository
# default token permission is widened later.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand Down Expand Up @@ -74,6 +80,8 @@ jobs:
wordpress: '5.6'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ on:
# Allow forcing a full run by hand, for example before cutting a release.
workflow_dispatch:

# The jobs only read the checkout and run the suite, so nothing beyond read access
# is needed. Declared explicitly so it stays restricted even if the repository
# default token permission is widened later.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -50,6 +56,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/spam-detection-comparison.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ on:
required: false
default: '1G'

# No job here runs with the workflow default: `compare` declares the permissions it
# actually needs itself. An empty block means a job added later starts with no token
# access until it asks for what it needs.
permissions: {}

jobs:
compare:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -128,6 +133,7 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0 # full history/tags for baseline resolution

# Sets ASB_CORPUS_READY so a missing URL degrades to the bundled fixture
Expand All @@ -142,7 +148,7 @@ jobs:
run: |
set -euo pipefail
if [ -z "${ENC_URL:-}" ] || [ -z "${ASB_CORPUS_KEY:-}" ]; then
echo "::notice::No '${{ env.ASB_CORPUS }}' corpus configured; using the bundled fixture."
echo "::notice::No '$ASB_CORPUS' corpus configured; using the bundled fixture."
echo "ASB_CORPUS_READY=false" >> "$GITHUB_ENV"
exit 0
fi
Expand Down Expand Up @@ -237,6 +243,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ inputs.comment_pr }}
REPO: ${{ github.repository }}
REPORT: ${{ steps.compare.outputs.report-markdown }}
run: |
set -euo pipefail
Expand All @@ -250,11 +257,11 @@ jobs:
fi

# Reuse our own previous comment when there is one.
existing="$(gh api "repos/${{ github.repository }}/issues/$PR/comments" --paginate \
existing="$(gh api "repos/$REPO/issues/$PR/comments" --paginate \
--jq 'map(select(.body | contains("<!-- asb-detection-compare -->"))) | last | .id // empty')"

if [ -n "$existing" ]; then
gh api --method PATCH "repos/${{ github.repository }}/issues/comments/$existing" \
gh api --method PATCH "repos/$REPO/issues/comments/$existing" \
-F body=@"$REPORT" >/dev/null
echo "Updated comment $existing on PR #$PR."
else
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/spelling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ jobs:

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0
52 changes: 47 additions & 5 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
# `wp-since`, since that is what the status checks on existing pull requests are called.
#
# The jobs share a workflow because they share a trigger: all of them are static checks
# over the source, none starts an environment. They still report as separate status checks.
# over the repository, none starts an environment. They still report as separate status
# checks. `workflows` analyses `.github/` itself rather than the plugin source, but it is
# static analysis over checked-in code all the same, so it belongs here rather than in a
# workflow of its own.
#
# Only `pull_request` is filtered — `push` to a major branch deliberately runs everything.
# A merge carries the same diff the pull request carried, so a filter that is too narrow
Expand Down Expand Up @@ -31,13 +34,20 @@ on:
# `Requires at least` header in `readme.txt`, so lowering it there alone changes
# the outcome of the check.
- 'readme.txt'
- '.github/workflows/static-analysis.yml'
# The shared PHP setup is part of this workflow's setup, so a change to it has to run
# this workflow as well.
- '.github/actions/setup-php/**'
# The shared PHP setup is part of this workflow's setup, and `workflows` analyses
# every workflow and composite action, so any change under `.github/` has to run
# this workflow.
- '.github/workflows/**'
- '.github/actions/**'
# Allow forcing a full run by hand, for example before cutting a release.
workflow_dispatch:

# The jobs only read the checkout and run the suite, so nothing beyond read access
# is needed. Declared explicitly so it stays restricted even if the repository
# default token permission is widened later.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -47,6 +57,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
Expand All @@ -59,6 +71,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
Expand All @@ -73,10 +87,38 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
php-version: '8.5'
composer-flags: '--ignore-platform-req=php'
- name: Check for unavailable WordPress functions
run: ./vendor/bin/wp-since check .

workflows:
name: Workflow hardening
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Audit the workflows with zizmor
# Workflows are code, and the same static analysis the PHP gets. zizmor covers
# what review reliably misses: an action pinned to a movable tag, a job running
# with more token than it needs, event data spliced into a shell script, a
# checkout leaving its credentials in `.git/config`.
#
# Deliberately the plain CLI rather than `zizmorcore/zizmor-action`: one fewer
# third-party action in the very workflow that argues for fewer of them, and the
# action's SARIF upload would need `security-events: write` for a report that is
# just as readable in the log.
#
# `GH_TOKEN` only to lift zizmor out of offline mode. Some of its checks have to
# ask the API — whether a pinned hash is a commit that actually exists in the
# action's repository, most of all, which is how an impostor pin is caught. The
# read-only job token is enough for that.
env:
GH_TOKEN: ${{ github.token }}
run: pipx run zizmor --persona=regular --format=plain .github/
8 changes: 8 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ on:
# Allow forcing a full run by hand, for example before cutting a release.
workflow_dispatch:

# The jobs only read the checkout and run the suite, so nothing beyond read access
# is needed. Declared explicitly so it stays restricted even if the repository
# default token permission is widened later.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand All @@ -44,6 +50,8 @@ jobs:
php-versions: ['8.5', '7.4']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/wordpress-plugin-asset-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ on:
push:
branches:
- master

# The upload authenticates against the wordpress.org SVN repository with its own
# credentials and never touches the GitHub API, so the job token needs nothing
# beyond reading the checkout. Declared explicitly because this is one of the two
# jobs that carries real credentials in its environment.
permissions:
contents: read

jobs:
master:
name: Push to master
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/wordpress-plugin-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ permissions:
jobs:
check:
runs-on: ubuntu-latest
# `wp dist-archive` names the zip and its top-level directory after the checkout
# directory, which is the repository name. Bound once here so the shell steps below
# read it from the environment instead of having it spliced into the script.
env:
PLUGIN_SLUG: ${{ github.event.repository.name }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
Expand Down Expand Up @@ -58,11 +65,11 @@ jobs:

- name: Build the plugin archive
run: |
wp dist-archive . ./${{ github.event.repository.name }}.zip
wp dist-archive . "./$PLUGIN_SLUG.zip"
mkdir tmp-build
unzip -q ${{ github.event.repository.name }}.zip -d tmp-build
unzip -q "$PLUGIN_SLUG.zip" -d tmp-build

- name: Run plugin check
uses: wordpress/plugin-check-action@10857da14b6c2246d15402b3e69f777edcf8c12e # v1.1.9
with:
build-dir: ./tmp-build/${{ github.event.repository.name }}
build-dir: ./tmp-build/${{ env.PLUGIN_SLUG }}
10 changes: 10 additions & 0 deletions .github/workflows/wordpress-plugin-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ on:
tags:
- "*"
- "!*-*"

# The upload authenticates against the wordpress.org SVN repository with its own
# credentials and never touches the GitHub API, so the job token needs nothing
# beyond reading the checkout. Declared explicitly because this is one of the two
# jobs that carries real credentials in its environment.
permissions:
contents: read

jobs:
tag:
name: New tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP and install dependencies
uses: ./.github/actions/setup-php
with:
Expand Down