A workflow that checks out and scans pull-request code can execute vulnerable tooling, Composer scripts, custom standards, bootstrap files, or repository code. `pull_request_target` and privileged follow-up workflows are especially risky when they combine secrets with an untrusted checkout.
Use this for WordPress plugin, theme, agency, marketplace, and monorepo teams that accept forked pull requests or scan third-party PHP in GitHub Actions or another CI platform.
Quick answer
Run untrusted pull requests with read-only permissions, no secrets, no deployment credentials, no persistent self-hosted runner, and no writable trusted cache. Pin actions, disable unnecessary Composer scripts, scan only with updated dependencies, and move privileged publishing to a separate workflow that consumes verified immutable artifacts without checking out attacker-controlled code.
What to check first
- Inventory `pull_request`, `pull_request_target`, `workflow_run`, `repository_dispatch`, reusable workflows, environment approvals, and every path that can receive untrusted revisions.
- Print effective token permissions and list secrets, OIDC roles, package credentials, cloud access, cache scopes, artifacts, and runner labels available to each job.
- Search for checkout of a pull-request head SHA inside a privileged event and for scripts, standards, bootstrap files, or Composer plugins loaded from that checkout.
- Separate untrusted lint output from trusted release artifacts, pin third-party actions to reviewed commits, and prevent fork jobs from writing caches consumed by protected branches.
- Test a harmless adversarial pull request that tries to read environment variables, alter cache keys, upload misleading artifacts, and reach protected network endpoints.
Diagnostic table
Use this table to keep the work practical. It connects the symptom to evidence and a verification step.
| Action | Evidence to collect | How to verify |
| Map every workflow trust boundary | Inventory `pull_request`, `pull_request_target`, `workflow_run`, `repository_dispatch`, reusable workflows, environment approvals, and every path that can receive untrusted revisions. | Fork pull requests receive no sensitive secret, token scope, OIDC role, or protected environment. |
| Remove secrets and write permissions | Print effective token permissions and list secrets, OIDC roles, package credentials, cloud access, cache scopes, artifacts, and runner labels available to each job. | The lint job runs on an ephemeral isolated runner with updated WPCS. |
| Pin and minimize executable tooling | Search for checkout of a pull-request head SHA inside a privileged event and for scripts, standards, bootstrap files, or Composer plugins loaded from that checkout. | Trusted caches and release artifacts cannot be replaced by an untrusted job. |
| Separate privileged release work | Separate untrusted lint output from trusted release artifacts, pin third-party actions to reviewed commits, and prevent fork jobs from writing caches consumed by protected branches. | The adversarial fixture fails to read, write, deploy, or persist outside its job. |
Why this usually happens
- Workflow event names can change token and secret behavior.
- Static-analysis tools may load project-defined executable code.
- Shared caches and self-hosted workspaces can cross trust boundaries.
- A privileged workflow may trust artifacts or metadata produced by an untrusted job.
Useful command or data shape
Adapt paths, IDs, and privacy handling to the site before running commands or storing data on production.
name: wpcs-untrusted
on: pull_request
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<reviewed-commit>
- run: composer install --no-interaction --no-scripts
- run: vendor/bin/phpcs --standard=WordPress-Core .
Safe fix order
Do the work in a sequence that makes each result easy to prove. Stop if a step produces new evidence that changes the incident scope.
- Map every workflow trust boundary
- Remove secrets and write permissions
- Pin and minimize executable tooling
- Separate privileged release work
- Run an adversarial verification
Decision rule
Pass when untrusted code can produce only review output, cannot reach secrets or protected resources, cannot poison trusted caches or artifacts, and privileged jobs never execute its checkout.
What to tell the client or owner
Give the owner the affected versions, exact workflow, observed result, business impact, evidence location, temporary control, named owner, and next review time. Remove credentials and personal data from shared screenshots and logs.
Production verification checklist
- Fork pull requests receive no sensitive secret, token scope, OIDC role, or protected environment.
- The lint job runs on an ephemeral isolated runner with updated WPCS.
- Trusted caches and release artifacts cannot be replaced by an untrusted job.
- The adversarial fixture fails to read, write, deploy, or persist outside its job.
Mistakes to avoid
- Do not change several plugins, cache rules, or infrastructure settings before preserving a baseline.
- Do not treat one successful test as proof for retries, alternate clients, background work, or mixed-version fleets.
- Do not paste secrets, personal data, complete production payloads, or customer files into tickets or screenshots.
- Do not close the test until the final user-visible state and the server-side evidence agree.
Questions teams ask during testing
Is `pull_request_target` always unsafe?
No, but it becomes dangerous when a privileged job checks out or executes untrusted pull-request content. Use it only with a clearly reviewed trust design.
Can a linter execute repository code?
Yes. Custom standards, bootstrap files, Composer plugins, scripts, and vulnerable analysis code can execute. Treat the job as a code-execution environment.
When HandL WP should help
Bring in HandL WP when a production checkout, form, email, media pipeline, code-quality gate, or paid lead workflow is at risk. We can preserve evidence, isolate the failing layer, make the smallest corrective change, and verify the result across WordPress, connected services, logs, and the user journey.
If this is active on a production site, harden WordPress CI and code review.
Related HandL WP guides
Use these related guides when the same issue touches tracking, security, checkout, or crawler visibility.
Helpful references