Skip to content

Commit 9803fa3

Browse files
committed
chore: release 3.1.3
1 parent 86016aa commit 9803fa3

10 files changed

Lines changed: 91 additions & 36 deletions

File tree

β€Ž.github/workflows/playground-preview.ymlβ€Ž

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,24 @@ jobs:
2727
const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
2828
2929
const blueprint = JSON.parse(fs.readFileSync('blueprint.json', 'utf8'));
30-
const pluginStep = blueprint.steps.find(
31-
step =>
32-
step.step === 'installPlugin' &&
33-
step.pluginData &&
34-
step.pluginData.resource === 'git:directory',
35-
);
30+
const pluginStep = blueprint.steps.find(step => {
31+
if (step.step !== 'installPlugin') {
32+
return false;
33+
}
34+
35+
const resource = step.pluginData || step.pluginZipFile || {};
36+
return (
37+
resource.resource === 'git:directory' ||
38+
(resource.resource === 'url' && resource.url && resource.url.includes(`${context.repo.owner}/${context.repo.repo}`))
39+
);
40+
});
3641
3742
if (!pluginStep) {
38-
core.setFailed('blueprint.json does not contain a git:directory plugin install step.');
43+
core.setFailed('blueprint.json does not contain a first-party plugin install step.');
3944
return;
4045
}
4146
47+
delete pluginStep.pluginZipFile;
4248
pluginStep.pluginData = {
4349
resource: 'git:directory',
4450
url: repoUrl,

β€ŽCHANGELOG.mdβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 3.1.3 - 2026-05-11
4+
5+
- **Release Playground link:** the stable release Blueprint installs the tag ZIP through `pluginData` instead of using Playground's currently brittle `git:directory` tag fetch path.
6+
- **Playground link posture:** README Playground links now distinguish the immutable latest-release demo from the current `main` demo.
7+
- **Blueprint password seeding:** the demo Blueprint now uses WordPress core's `wp_set_password()` API instead of writing the password hash directly through `$wpdb`.
8+
39
## 3.1.2 - 2026-05-11
410

511
### Playground and preview fixes

β€Žblueprint-main.jsonβ€Ž

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
3+
"preferredVersions": {
4+
"php": "8.2",
5+
"wp": "7.0-RC1"
6+
},
7+
"landingPage": "/wp-admin/",
8+
"steps": [
9+
{
10+
"step": "runPHP",
11+
"code": "<?php require_once '/wordpress/wp-load.php'; $user = get_user_by('login', 'admin'); if ($user) { wp_set_password('password', $user->ID); wp_set_current_user($user->ID); }"
12+
},
13+
{
14+
"step": "login",
15+
"username": "admin",
16+
"password": "password"
17+
},
18+
{
19+
"step": "installPlugin",
20+
"pluginData": {
21+
"resource": "url",
22+
"url": "https://github.com/dknauss/Sudo/archive/refs/heads/main.zip"
23+
},
24+
"options": { "activate": true }
25+
},
26+
{
27+
"step": "installPlugin",
28+
"pluginData": {
29+
"resource": "wordpress.org/plugins",
30+
"slug": "two-factor"
31+
},
32+
"options": { "activate": true }
33+
},
34+
{
35+
"step": "runPHP",
36+
"code": "<?php require_once '/wordpress/wp-load.php'; $admin = get_user_by('login', 'admin'); $admin_id = $admin ? (int) $admin->ID : 1; if ($admin) { foreach (array('_wp_sudo_expires', '_wp_sudo_token', '_wp_sudo_failed_attempts', '_wp_sudo_lockout_until', '_wp_sudo_failure_event', '_wp_sudo_throttle_until', '_two_factor_enabled_providers', '_two_factor_provider', '_two_factor_totp_key', '_two_factor_backup_codes') as $key) { delete_user_meta($admin_id, $key); } } $session_lengths = array('janesmith' => 5, 'bobdev' => 7, 'carlosadmin' => 10, 'sarahops' => 12, 'mariadev' => 15); $demo_users = array(array('janesmith', 'jane@example.com', 'Jane', 'Smith', 'editor', true), array('bobdev', 'bob@example.com', 'Bob', 'Developer', 'author', true), array('carlosadmin', 'carlos@example.com', 'Carlos', 'GarcΓ­a', 'administrator', true), array('sarahops', 'sarah@example.com', 'Sarah', 'Nakamura', 'editor', true), array('liwei', 'li@example.com', 'Li', 'Wei', 'contributor', false), array('mariadev', 'maria@example.com', 'Maria', 'Santos', 'administrator', true), array('alexkim', 'alex@example.com', 'Alex', 'Kim', 'editor', false), array('priyapatel', 'priya@example.com', 'Priya', 'Patel', 'author', false)); $user_ids = array(); foreach ($demo_users as $u) { $existing = get_user_by('login', $u[0]); $uid = $existing ? (int) $existing->ID : wp_insert_user(array('user_login' => $u[0], 'user_email' => $u[1], 'user_pass' => 'password', 'first_name' => $u[2], 'last_name' => $u[3], 'display_name' => trim($u[2] . ' ' . $u[3]), 'role' => $u[4])); if (!is_wp_error($uid)) { $uid = (int) $uid; $user_ids[$u[0]] = $uid; wp_update_user(array('ID' => $uid, 'user_email' => $u[1], 'first_name' => $u[2], 'last_name' => $u[3], 'display_name' => trim($u[2] . ' ' . $u[3]), 'role' => $u[4])); if ($u[5]) { $minutes = $session_lengths[$u[0]] ?? 15; update_user_meta($uid, '_wp_sudo_expires', time() + ($minutes * MINUTE_IN_SECONDS)); update_user_meta($uid, '_wp_sudo_token', wp_hash(wp_generate_password(32, true, true))); } else { delete_user_meta($uid, '_wp_sudo_expires'); delete_user_meta($uid, '_wp_sudo_token'); } } } if (class_exists('\\WP_Sudo\\Event_Store')) { \\WP_Sudo\\Event_Store::maybe_create_table(); \\WP_Sudo\\Event_Store::bulk_insert(array(array('user_id' => $admin_id, 'event' => 'action_gated', 'rule_id' => 'options.wp_sudo', 'surface' => 'admin', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 90)), array('user_id' => $admin_id, 'event' => 'action_replayed', 'rule_id' => 'options.wp_sudo', 'surface' => '', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 70)), array('user_id' => $user_ids['mariadev'] ?? $admin_id, 'event' => 'action_passed', 'rule_id' => 'plugin.activate', 'surface' => 'admin', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 240)), array('user_id' => $user_ids['carlosadmin'] ?? $admin_id, 'event' => 'action_gated', 'rule_id' => 'user.delete', 'surface' => 'admin', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 520)), array('user_id' => $user_ids['sarahops'] ?? $admin_id, 'event' => 'action_blocked', 'rule_id' => 'auth.app_password', 'surface' => 'rest_app_password', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 960)), array('user_id' => $user_ids['bobdev'] ?? $admin_id, 'event' => 'action_allowed', 'rule_id' => 'tools.export', 'surface' => 'cli', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 1500)), array('user_id' => $user_ids['janesmith'] ?? $admin_id, 'event' => 'action_blocked', 'rule_id' => 'options.critical', 'surface' => 'xmlrpc', 'ip' => '127.0.0.1', 'context' => array('demo' => true), 'created_at' => gmdate('Y-m-d H:i:s', time() - 2300)), array('user_id' => $user_ids['liwei'] ?? $admin_id, 'event' => 'lockout', 'rule_id' => '', 'surface' => '', 'ip' => '127.0.0.1', 'context' => array('demo' => true, 'attempts' => 5), 'created_at' => gmdate('Y-m-d H:i:s', time() - 3600)))); } delete_transient('wp_sudo_active_sessions_' . (function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0)); update_user_meta($admin_id, 'meta-box-order_dashboard', array('side' => 'wp_sudo_activity,dashboard_quick_press,dashboard_primary', 'normal' => 'dashboard_site_health,dashboard_right_now,dashboard_activity')); update_user_meta($admin_id, 'screen_layout_dashboard', 2); echo 'Prepared WP Sudo demo data. Admin password: password. Sudo challenge password: password. Demo sessions: 5-15 minutes.';"
37+
}
38+
]
39+
}

β€Žblueprint.jsonβ€Ž

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"steps": [
99
{
1010
"step": "runPHP",
11-
"code": "<?php require_once '/wordpress/wp-load.php'; global $wpdb; $user = get_user_by('login', 'admin'); if ($user) { $wpdb->update($wpdb->users, array('user_pass' => wp_hash_password('password')), array('ID' => $user->ID)); clean_user_cache($user); }"
11+
"code": "<?php require_once '/wordpress/wp-load.php'; $user = get_user_by('login', 'admin'); if ($user) { wp_set_password('password', $user->ID); wp_set_current_user($user->ID); }"
1212
},
1313
{
1414
"step": "login",
@@ -18,10 +18,8 @@
1818
{
1919
"step": "installPlugin",
2020
"pluginData": {
21-
"resource": "git:directory",
22-
"url": "https://github.com/dknauss/Sudo",
23-
"ref": "v3.1.2",
24-
"refType": "tag"
21+
"resource": "url",
22+
"url": "https://github.com/dknauss/Sudo/archive/refs/tags/v3.1.3.zip"
2523
},
2624
"options": { "activate": true }
2725
},

β€Ždocs/release-status.mdβ€Ž

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,27 @@ This file is the canonical source for **current release state** in this reposito
1212

1313
## Latest public/tagged release
1414

15-
- **Latest tagged release:** `3.1.2`
16-
- **Latest git tag observed:** `v3.1.2`
15+
- **Latest tagged release:** `3.1.3`
16+
- **Latest git tag observed:** `v3.1.3`
1717

1818
## Current `main` release target
1919

2020
- **Next planned release:** `3.2.0` (planning lane; post-release development bump pending)
21-
- **Current `main` runtime version constant:** `3.1.2`
21+
- **Current `main` runtime version constant:** `3.1.3`
2222
- **Current metadata should match:** `readme.txt` stable tag, `wp-sudo.php`, `tests/bootstrap.php`, `phpstan-bootstrap.php`
23-
- **Current public stable metadata:** `readme.txt` stable tag `3.1.2`
23+
- **Current public stable metadata:** `readme.txt` stable tag `3.1.3`
2424
- **Last completed release checklist:** `docs/release-3.0.0-checklist.md`
2525

2626
## Latest release contents
2727

28-
`3.1.2` includes the post-`v3.1.1` Playground and preview fixes that landed on `main`:
28+
`3.1.3` includes the post-`v3.1.2` Playground release-link fixes that landed on `main`:
2929

30-
- Playground login and sudo challenge reauthentication now use the documented `admin` / `password` credential.
31-
- The Sudo toolbar item cancels active sessions correctly from front-end admin-bar contexts.
32-
- Dashboard widget active-session counts refresh after cancellation.
33-
- Playground demo data now includes recent privilege-action samples and staggered 5-15 minute demo sudo sessions.
34-
- Pull request Playground preview links now use the checked-in Blueprint and Playground's CORS-safe `git:directory` resource.
30+
- Stable release Playground links use the tagged raw Blueprint and install the tag ZIP through `pluginData`.
31+
- Current `main` Playground links use `blueprint-main.json` and install the main branch ZIP.
32+
- Pull request Playground previews continue to use the checked-in Blueprint but pin the plugin install to the PR commit.
33+
- Demo admin password seeding uses WordPress core's `wp_set_password()` API instead of a direct `$wpdb->users` update.
3534

36-
Canonical source for post-tag drift after this release: `git log v3.1.2..main --oneline`
35+
Canonical source for post-tag drift after `3.1.3`: `git log v3.1.3..main --oneline`
3736

3837
## WordPress release posture
3938

β€Žphpstan-bootstrap.phpβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*/
1111

1212
// Plugin constants (defined in wp-sudo.php at runtime).
13-
define( 'WP_SUDO_VERSION', '3.1.2' );
13+
define( 'WP_SUDO_VERSION', '3.1.3' );
1414
define( 'WP_SUDO_PLUGIN_DIR', __DIR__ . '/' );
1515
define( 'WP_SUDO_PLUGIN_URL', 'https://example.com/wp-content/plugins/wp-sudo/' );
1616
define( 'WP_SUDO_PLUGIN_BASENAME', 'wp-sudo/wp-sudo.php' );

β€Žreadme.mdβ€Ž

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@ WP Sudo adds **action-gated reauthentication** to WordPress so high-risk operati
1111
[![CodeQL](https://github.com/dknauss/Sudo/actions/workflows/codeql.yml/badge.svg)](https://github.com/dknauss/Sudo/actions/workflows/codeql.yml)
1212
[![Codecov](https://codecov.io/gh/dknauss/Sudo/graph/badge.svg?branch=main)](https://codecov.io/gh/dknauss/Sudo)
1313
[![Type Coverage](https://shepherd.dev/github/dknauss/Sudo/coverage.svg)](https://shepherd.dev/github/dknauss/Sudo)
14-
[![Try in Playground](https://img.shields.io/badge/Try%20it-Playground-3858e9?logo=wordpress&logoColor=white)](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/dknauss/Sudo/main/blueprint.json)
14+
[![Try latest release in Playground](https://img.shields.io/badge/Try%20release-Playground-3858e9?logo=wordpress&logoColor=white)](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2Fdknauss%2FSudo%2Fv3.1.3%2Fblueprint.json)
15+
[![Try main in Playground](https://img.shields.io/badge/Try%20main-Playground-23282d?logo=wordpress&logoColor=white)](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2Fdknauss%2FSudo%2Fmain%2Fblueprint-main.json)
1516

1617
Playground demo credentials are `admin` / `password`. When WP Sudo asks for reauthentication, enter the same password: `password`.
1718

18-
> **3.1.2 Playground patch:** WP Sudo fixes Playground reauthentication, front-end toolbar session cancellation, dashboard demo data, and PR preview links. See [docs/release-status.md](docs/release-status.md) for current release posture.
19+
> **3.1.3 Playground patch:** WP Sudo fixes Playground reauthentication, front-end toolbar session cancellation, dashboard demo data, and stable/main preview links. See [docs/release-status.md](docs/release-status.md) for current release posture.
1920
20-
## What’s new in 3.1.2
21+
## What’s new in 3.1.3
2122

2223
- **Playground authentication:** the demo explicitly resets `admin` to `password`, so login and sudo reauthentication use the same documented credential
2324
- **Toolbar cancellation:** clicking the Sudo toolbar item cancels an active session from wp-admin or the front end without navigating away unexpectedly
2425
- **Dashboard widget freshness:** active-session counts refresh immediately after session cancellation
2526
- **Demo activity:** Playground seeds recent privilege-action events and active demo users with varied 5-15 minute sudo windows
26-
- **Preview links:** pull request Playground previews now use the checked-in Blueprint with a CORS-safe `git:directory` plugin install
27+
- **Preview links:** release and main Playground demos are separate; pull request previews still pin to the PR commit
2728

2829
## Why WP Sudo exists
2930

β€Žreadme.txtβ€Ž

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
_WP Sudo 3.1.2 is a Playground compatibility patch for action-gated reauthentication, fixing demo login, sudo challenge auth, toolbar cancellation, dashboard demo data, and PR preview links._
1+
_WP Sudo 3.1.3 is a Playground compatibility patch for action-gated reauthentication, fixing demo login, sudo challenge auth, toolbar cancellation, dashboard demo data, and stable/main preview links._
22

3-
[Try it in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/dknauss/Sudo/main/blueprint.json)
3+
[Try the latest release in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2Fdknauss%2FSudo%2Fv3.1.3%2Fblueprint.json)
4+
[Try current main in WordPress Playground](https://playground.wordpress.net/?blueprint-url=https%3A%2F%2Fraw.githubusercontent.com%2Fdknauss%2FSudo%2Fmain%2Fblueprint-main.json)
45

56
Playground demo credentials are `admin` / `password`. When WP Sudo asks for reauthentication, enter the same password: `password`.
67

@@ -11,7 +12,7 @@ Tags: sudo, security, reauthentication, access control, admin prote
1112
Requires at least: 6.2
1213
Tested up to: 6.9
1314
Requires PHP: 8.0
14-
Stable tag: 3.1.2
15+
Stable tag: 3.1.3
1516
License: GPL-2.0-or-later
1617
License URI: https://spdx.org/licenses/GPL-2.0-or-later.html
1718

@@ -23,13 +24,13 @@ WordPress has rich access control β€” roles, capabilities, policies on who can d
2324

2425
This is not role-based escalation. Every logged-in user is treated the same: attempt a gated action without an active sudo session, get challenged. Sessions are time-bounded and non-extendable, enforcing the zero-trust principle that trust must be continuously earned, never assumed. WordPress capability checks still run after the gate, so Sudo adds a security layer without changing the permission model.
2526

26-
= What’s new in 3.1.2? =
27+
= What’s new in 3.1.3? =
2728

2829
* **Playground authentication** β€” the demo explicitly resets `admin` to `password`, so login and sudo reauthentication use the same documented credential
2930
* **Toolbar cancellation** β€” clicking the Sudo toolbar item cancels an active session from wp-admin or the front end without navigating away unexpectedly
3031
* **Dashboard widget freshness** β€” active-session counts refresh immediately after session cancellation
3132
* **Demo activity** β€” Playground seeds recent privilege-action events and active demo users with varied 5-15 minute sudo windows
32-
* **Preview links** β€” pull request Playground previews now use the checked-in Blueprint with a CORS-safe `git:directory` plugin install
33+
* **Preview links** β€” release and main Playground demos are separate; pull request previews still pin to the PR commit
3334

3435
= Why Sudo? =
3536

@@ -185,6 +186,11 @@ Extensibility: the action registry is filterable via wp_sudo_gated_actions. Ten
185186

186187
== Changelog ==
187188

189+
= 3.1.3 =
190+
* **Fix: release Playground link** β€” the stable release Blueprint installs the tag ZIP through `pluginData` instead of using Playground's currently brittle `git:directory` tag fetch path.
191+
* **Playground link posture** β€” README Playground links now distinguish the immutable latest-release demo from the current `main` demo.
192+
* **Blueprint password seeding** β€” the demo Blueprint now uses WordPress core's `wp_set_password()` API instead of writing the password hash directly through `$wpdb`.
193+
188194
= 3.1.2 =
189195
* **Fix: Playground authentication** β€” the demo resets the `admin` user password before login so `admin` / `password` works for both WordPress login and WP Sudo reauthentication.
190196
* **Fix: toolbar session cancellation** β€” the Sudo admin bar item now cancels active sessions reliably from wp-admin and front-end contexts.

β€Žtests/bootstrap.phpβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
define( 'WPMU_PLUGIN_URL', 'https://example.com/wp-content/mu-plugins' );
2020

2121
// ── Plugin constants (normally defined in wp-sudo.php) ───────────────
22-
define( 'WP_SUDO_VERSION', '3.1.2' );
22+
define( 'WP_SUDO_VERSION', '3.1.3' );
2323
define( 'WP_SUDO_PLUGIN_DIR', dirname( __DIR__ ) . '/' );
2424
define( 'WP_SUDO_PLUGIN_URL', 'https://example.com/wp-content/plugins/wp-sudo/' );
2525
define( 'WP_SUDO_PLUGIN_BASENAME', 'wp-sudo/wp-sudo.php' );

β€Žwp-sudo.phpβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: Sudo
44
* Plugin URI: https://github.com/dknauss/Sudo
55
* Description: Action-gated reauthentication for WordPress. Dangerous operations require password confirmation before they proceed β€” regardless of user role.
6-
* Version: 3.1.2
6+
* Version: 3.1.3
77
* Requires at least: 6.2
88
* Requires PHP: 8.0
99
* Author: Dan Knauss
@@ -24,7 +24,7 @@
2424
require_once __DIR__ . '/includes/class-bootstrap.php';
2525

2626
// Plugin version.
27-
define( 'WP_SUDO_VERSION', '3.1.2' );
27+
define( 'WP_SUDO_VERSION', '3.1.3' );
2828

2929
// Plugin directory path.
3030
define( 'WP_SUDO_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );

0 commit comments

Comments
Β (0)