Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
10d130c
Add AGENTS.md and CLAUDE.md for AI coding agent onboarding
obenland Mar 24, 2026
bd3c996
Fix inaccuracies in AGENTS.md from audit
obenland Mar 24, 2026
8e5a212
Clarify wp-env and lifecycle script descriptions in AGENTS.md
obenland Mar 24, 2026
768f82a
Add .claude/settings.local.json to .gitignore
obenland Mar 24, 2026
c54a209
Add Copilot agent configuration for autonomous issue resolution
obenland Mar 26, 2026
329b84d
Build: Only trigger copilot-setup-steps on manual dispatch
obenland Mar 26, 2026
fbb93d7
Build: Cache dependencies in copilot-setup-steps
obenland Mar 26, 2026
2a30056
Fix inaccuracies flagged in Copilot code review
obenland Mar 26, 2026
52afc72
Update .nvmrc to Node 24, use node-version-file in copilot setup
obenland Mar 26, 2026
a006129
Use canonical PHP lint command and cross-reference agent instruction …
obenland Mar 26, 2026
77332ff
Update AGENTS.md
obenland Mar 26, 2026
61e7eda
Build: Harden Docker image caching in copilot setup
obenland Mar 26, 2026
2ae40d6
Remove PHPDoc instruction, pin Playwright to 1.58.2
obenland Mar 26, 2026
4771fbb
Build: Remove Playwright install from copilot setup
obenland Mar 26, 2026
7a6d6ae
Add dedicated wp-env config for Copilot agents
obenland Mar 30, 2026
4462ef3
Make E2E verification mandatory for all Copilot PRs
obenland Mar 30, 2026
9af270f
Add explicit WordPress core ref to copilot wp-env config
obenland Mar 30, 2026
c449064
Use GitHub repo refs in main .wp-env.json, remove separate copilot co…
obenland Mar 30, 2026
0de0f0a
Remove invalid comment from .wp-env.json
obenland Mar 30, 2026
720db4b
Fix bbPress GitHub repo ref in wp-env config
obenland Mar 30, 2026
f1fad6c
Keep WebAuthn provider as zip download in wp-env config
obenland Mar 30, 2026
533012b
Use GitHub release zip for WebAuthn provider, drop explicit core ref
obenland Mar 30, 2026
dd72175
Use GitHub archive zip for WordPress core in wp-env config
obenland Mar 30, 2026
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
145 changes: 145 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# WordPress.org Two-Factor Plugin — Copilot Agent Instructions

> See also `AGENTS.md` in the repository root for additional context shared across all AI agents.

## Project Overview

This is a **security-critical** WordPress plugin that customizes the [Two-Factor](https://github.com/WordPress/two-factor) plugin for WordPress.org. It enforces 2FA on privileged accounts (committers, deputies, theme authors, WordCamp organizers), strips capabilities from users who haven't enabled 2FA, and provides a React-based settings UI with REST API endpoints.

Key subsystems: provider management (WebAuthn/TOTP/Backup Codes), capability enforcement, session revalidation ("sudo mode"), encrypted TOTP secrets, and a block-based settings interface.

## Working on Issues

### Before Writing Code

1. **Read the issue thoroughly.** Understand the problem, reproduction steps, and acceptance criteria.
2. **Explore the relevant code.** Read the files involved — don't guess at implementations.
3. **Understand _why_ the code is the way it is.** Check `git log` and `git blame` for the files you'll change. Read commit messages and linked PRs/issues to understand the decisions that led to the current design. Security-related decisions are especially important — do not undo them without understanding the rationale.
4. **Check for related tests.** Look in `tests/` for existing test coverage of the area you're modifying.
5. **Map the dependency chain.** This plugin depends on the Two-Factor core plugin, the WebAuthn provider plugin, bbPress, Gutenberg, and wporg-mu-plugins. Understand which dependencies are involved in your change.

### Writing Code

Follow **WordPress coding standards** strictly:
- PHP: tabs for indentation, Yoda conditions (`'value' === $var`), snake_case functions, braces on same line.
- JS/React: tabs for indentation, follow wp-scripts/eslint conventions.
- When in doubt, match the existing file's style and adhere to the WordPress coding standards above.

**Architecture rules:**
- The main plugin file (`wporg-two-factor.php`) uses the `WordPressdotorg\Two_Factor` namespace.
- Settings UI lives in `settings/` — it's a `@wordpress/scripts` block package with its own `package.json`.
- REST API endpoints are in `settings/rest-api.php`.
- Session revalidation is in `revalidation/index.php`.
- Custom providers: `class-encrypted-totp-provider.php` (TOTP with encryption), `class-wporg-webauthn-provider.php` (WebAuthn with caching).

**Security considerations:**
- This is a security plugin. Every change must be defensively coded.
- Never weaken capability checks, 2FA enforcement, or session validation.
- TOTP secrets are encrypted at rest — maintain this guarantee.
- User input must be sanitized, output must be escaped.
- Do not introduce OWASP Top 10 vulnerabilities.

### Writing Tests

Every PR **must** include tests for the changes. This is a security plugin — untested code is unacceptable.

**PHP unit tests:**
- Location: `tests/` directory, files prefixed with `test-`.
- Framework: PHPUnit 9.6 with WordPress test utilities (`WP_UnitTestCase`).
- Run: `npm test` (runs PHPUnit inside wp-env).
- Coverage target: 100% for meaningful, testable code. Use `@codeCoverageIgnore` pragmatically (as configured in `phpunit.xml.dist`) to exclude non-behavioral glue, unreachable or environment-specific paths, but never to hide untested business logic.
- **NEVER use `remove_all_filters()` or `remove_all_actions()` in tests** — it removes production callbacks. Always add/remove specific callbacks by reference.
- Test classes extend `WP_UnitTestCase`. Use `wpSetUpBeforeClass` for expensive setup, `tear_down` for cleanup.
- The test bootstrap (`tests/bootstrap.php`) mocks WordPress.org-specific functions.

**JavaScript tests:**
- Location: `settings/src/tests/`.
- Framework: Jest via `@wordpress/scripts`.
- Run: `npm run test:js`.
- Uses `@testing-library/react` for component tests.

**End-to-end tests with Playwright:**
- Use the Playwright MCP server to interact with the local WordPress site in the browser.
- wp-env provides two instances:
- **Dev instance:** `http://localhost:8888` — use this for Playwright browser testing.
- **Test instance:** `http://localhost:8889` — used by PHPUnit (no browser testing here).
- **Login credentials:** username `admin`, password `password` (wp-env defaults).
- **Login URL:** `http://localhost:8888/wp-login.php`. Log in before testing authenticated flows.
- The dev environment has a **Dummy 2FA provider** enabled (see `.wp-env/mu-plugins/mu-plugin.php`), which allows completing 2FA login without real authenticator hardware. Use this for Playwright flows that require passing the 2FA prompt.
- The `admin` user is configured as a super admin and "special user" in the mu-plugin, so 2FA enforcement applies to them.
- This is a **multisite** installation with bbPress, Gutenberg, and the Two-Factor plugins active network-wide.
- Test real user flows: enabling 2FA, verifying enforcement, checking the settings UI, revalidation prompts.
- Take screenshots to verify visual state when relevant.
- The user profile / 2FA settings page is at `http://localhost:8888/support/users/admin/edit/account/` (bbPress user edit page).

### Validating Changes

Before opening a PR, verify your changes pass all checks:

1. **PHP tests:** `npm test`
2. **JS tests:** `npm run test:js`
3. **JS linting:** `npm run lint:js`
4. **PHP linting:** `npx wp-env run cli --env-cwd=wp-content/plugins/wporg-two-factor composer lint`
5. **E2E verification (mandatory):** Use Playwright to verify the change works in the browser. This is required for every PR — even if the change seems low-risk, open the affected pages and confirm there are no regressions. For UI changes, take screenshots to document the result.

If any check fails, fix the issue — do not skip or ignore failures.

## Project Layout

```
wporg-two-factor.php # Main plugin entry point (namespace: WordPressdotorg\Two_Factor)
class-encrypted-totp-provider.php # TOTP provider with at-rest encryption
class-wporg-webauthn-provider.php # WebAuthn provider with caching
stats.php # 2FA adoption analytics
settings/
settings.php # Settings page registration, replaces core 2FA UI
rest-api.php # REST endpoints for TOTP setup, provider status, passwords
package.json # @wordpress/scripts block package
src/ # React components for settings UI
components/ # UI components (TOTP, passwords, backup codes, WebAuthn)
tests/ # Jest tests for React components
revalidation/
index.php # Session revalidation / "sudo mode" system
tests/
bootstrap.php # Test bootstrap with WordPress.org mocks
test-wporg-two-factor.php # Main PHP test suite
settings/
test-rest-api.php # REST API endpoint tests
.wp-env.json # wp-env configuration — plugins MUST use GitHub repo refs (not zip downloads) for Copilot agents
.wp-env/
after-start.sh # Lifecycle script: composer install, plugin activation, bbPress config
mu-plugins/ # Mock mu-plugins for local development
```

## Build & Test Commands

| Task | Command |
|---|---|
| Start dev environment | `npx wp-env start` |
| Run PHP tests | `npm test` |
| Run JS tests | `npm run test:js` |
| Lint JS | `npm run lint:js` |
| Lint PHP | `npx wp-env run cli --env-cwd=wp-content/plugins/wporg-two-factor composer lint` |
| Build settings block | `npm run build --workspaces` |
| WP-CLI in dev env | `npx wp-env run cli wp <command>` |

## Commit Message Style

Follow the existing style visible in `git log`. Use a short imperative subject line with a category prefix when appropriate (e.g., "WebAuthN:", "Revalidation:", "Tests:", "Build:"). Keep messages concise and focused on _why_, not _what_.

## Key Dependencies

- **Two-Factor** (`WordPress/two-factor`): Core 2FA framework — provides `Two_Factor_Core`, `Two_Factor_Totp`, `Two_Factor_Backup_Codes`.
- **Two-Factor WebAuthn** (`two-factor-provider-webauthn`): WebAuthn provider — provides `TwoFactor_Provider_WebAuthn`.
- **bbPress**: Forum plugin — user profiles integrate with 2FA settings.
- **wporg-mu-plugins**: WordPress.org shared utilities including encryption functions.
- **Gutenberg**: Block editor — settings UI is a block.

## What NOT to Do

- Do not refactor code you weren't asked to change.
- Do not add features beyond what the issue requests.
- Do not weaken security checks or enforcement for convenience.
- Do not skip or disable pre-commit hooks or linting.
- Do not introduce new dependencies without strong justification.
- Do not change CI/CD workflows unless the issue specifically requires it.
13 changes: 13 additions & 0 deletions .github/instructions/js.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
applyTo: "settings/**/*.{js,jsx,ts,tsx}"
---

JavaScript/React conventions for the settings UI:

- This is a `@wordpress/scripts` block package. Use WordPress JS coding standards.
- Tabs for indentation.
- Import WordPress packages from `@wordpress/*` (e.g., `@wordpress/element`, `@wordpress/api-fetch`, `@wordpress/i18n`).
- Use `apiFetch` for REST API calls, not raw `fetch`.
- Translatable strings use `__()`, `_n()`, `sprintf()` from `@wordpress/i18n`.
- Jest tests live in `settings/src/tests/` and use `@testing-library/react`.
- Run JS tests with `npm run test:js`, lint with `npm run lint:js`.
12 changes: 12 additions & 0 deletions .github/instructions/php.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
applyTo: "**/*.php"
---

Follow WordPress PHP coding standards and WordPress documentation standards:
- Tabs for indentation, never spaces.
- Snake_case for function and variable names.
- Opening braces on the same line as the statement.
- Space inside parentheses: `if ( $condition )`, `function_call( $arg )`.
- Use strict type comparisons (`===`, `!==`) unless there's a specific reason not to.
- Sanitize all input, escape all output. Use `sanitize_*()`, `esc_html()`, `esc_attr()`, `wp_kses()` as appropriate.
- Prefix functions and hooks with the plugin namespace or use the `WordPressdotorg\Two_Factor` namespace.
15 changes: 15 additions & 0 deletions .github/instructions/tests.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
applyTo: "tests/**"
---

Testing rules for this security plugin:

- Every code change requires corresponding test coverage.
- Test classes extend `WP_UnitTestCase`.
- Use `wpSetUpBeforeClass( WP_UnitTest_Factory $factory )` for expensive setup shared across tests.
- Use `tear_down()` to reset globals and state after each test.
- NEVER use `remove_all_filters()` or `remove_all_actions()`. Always save callbacks to a variable and remove the specific callback.
- Use `@covers` annotations on every test method to track coverage.
- Test both positive and negative cases — especially for security enforcement (e.g., verify that capability stripping works AND that it doesn't affect users who have 2FA enabled).
- The bootstrap file (`tests/bootstrap.php`) provides mock WordPress.org functions. Check what's available before creating new mocks.
- Run tests with `npm test` which executes PHPUnit inside the wp-env Docker container.
78 changes: 78 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: "Copilot Setup Steps"

on: workflow_dispatch

jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2

- name: Cache node_modules
uses: actions/cache@v4
id: cache-node-modules
with:
path: |
node_modules
settings/node_modules
key: node-modules-${{ hashFiles('package-lock.json', 'settings/package-lock.json') }}

- name: Install NPM dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm install

- name: Cache Docker images
uses: actions/cache@v4
id: cache-docker
with:
path: /tmp/wp-env-docker
key: docker-wp-env-${{ hashFiles('.wp-env.json') }}

- name: Load cached Docker images
if: steps.cache-docker.outputs.cache-hit == 'true'
run: |
shopt -s nullglob
for f in /tmp/wp-env-docker/*.tar; do
docker load < "$f"
done

# Docker is pre-installed on ubuntu-latest runners.
# wp-env uses Docker to run WordPress + MySQL containers.
- name: Start wp-env
run: npx wp-env start --xdebug=coverage

- name: Save Docker images to cache
if: steps.cache-docker.outputs.cache-hit != 'true'
run: |
mkdir -p /tmp/wp-env-docker
images=$(docker ps -a --format '{{.Image}} {{.Names}}' | awk '$2 ~ /^wp-env/ {print $1}' | sort -u)
for image in $images; do
filename=$(echo "$image" | tr '/:' '_')
docker save "$image" -o "/tmp/wp-env-docker/${filename}.tar"
done

- name: Verify environment is healthy
run: |
# Verify WordPress is responding
curl -sf http://localhost:8888/ > /dev/null
curl -sf http://localhost:8889/ > /dev/null
# Run test suites to confirm everything works
npx wp-env run tests-cli --env-cwd=wp-content/plugins/wporg-two-factor vendor/bin/phpunit
npm run test:js
npm run lint:js
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ tests/.phpunit.result.cache
tests/coverage
vendor
node_modules
.claude/settings.local.json
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18
24
7 changes: 4 additions & 3 deletions .wp-env.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"core": "https://github.com/WordPress/WordPress/archive/refs/heads/master.zip",
"multisite": true,
"plugins": [
"https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip",
"https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip",
"WordPress/gutenberg",
"bbpress/bbPress",
"WordPress/two-factor",
"https://downloads.wordpress.org/plugin/two-factor-provider-webauthn.latest-stable.zip",
"https://github.com/sjinks/wp-two-factor-provider-webauthn/releases/latest/download/two-factor-provider-webauthn.zip",
"."
],
"mappings": {
Expand Down
Loading
Loading