Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.
The first scaffold focuses on three defenses:
- Defer automatic plugin updates until the target version is at least
Xdays old. - Run provider-based version approval before plugin package installation/update.
- Verify package hashes and optional author signatures before WordPress unzips an update package.
npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updatesOpen the wp-env site at the URL printed by wp-env. The admin defaults are usually:
- URL:
http://localhost:8888/wp-admin/ - Username:
admin - Password:
password
The plugin screen is under Tools -> Secure Updates Lab.
Useful commands:
npm run lint:php
npm run test:first-seen
npm run env:logs
npm run env:stopnpm install
npm run playgroundThe Playground CLI mounts this repository into:
/wordpress/wp-content/plugins/wp-secure-plugin-updates
The blueprint activates the mounted plugin and opens the lab screen.
Before WordPress installs or unzips a plugin package, the lab asks registered providers whether the specific {slug, version} is approved.
The built-in provider is:
release_age: blocks versions inside the configured defer window.
The release-age provider prefers a local first-seen timestamp when WordPress has detected an available update on this site. The ledger records each exact plugin version once, so daily update checks keep the original timestamp instead of resetting the clock.
This also enables rolling delayed updates. If versions 1.2.1 through 1.2.5 are released over five days and the site uses a 10-day defer window, the plugin can expose the oldest eligible recorded version first instead of only blocking the newest offered version. Records at or below the installed plugin version are purged after updates.
Test the ledger behavior with:
npm run test:first-seenThe smoke test simulates update offers for Hello Dolly and verifies that repeated detections preserve the original first-seen time, a newer blocked offer rolls back to the newest eligible recorded version, release-age checks use the local ledger as their date source, and old ledger records are cleaned up after the installed version advances.
Scanner or reputation providers can tap in with:
add_filter(
'wspu_version_approval_providers',
function (array $providers): array {
$providers[] = function (string $slug, string $version, array $context): array {
return array(
'approved' => true,
'provider' => 'my-provider',
'reason' => 'clear',
);
};
return $providers;
}
);A provider can block install/update by returning:
array(
'approved' => false,
'provider' => 'my-provider',
'reason' => 'too_new',
'message' => 'This version has not passed the provider approval window.',
)It can also fail closed with a WP_Error.
Hash and signature checks use a JSON manifest URL. The current prototype accepts this shape:
{
"plugins": {
"example-plugin": {
"publicKeys": {
"author-2026": "base64-encoded-ed25519-public-key"
},
"versions": {
"1.2.3": {
"sha256": "hex-encoded-package-sha256",
"publicKeyId": "author-2026",
"signature": "base64-ed25519-signature-over-slug|version|sha256"
}
}
}
}
}Hash verification can run without signatures. Signature verification requires PHP sodium support.
This is intentionally a lab plugin. It proves hook points and policy behavior, but WordPress.org does not currently provide first-party package signatures for plugin updates. The manifest model is a proposed trust layer for authors or an external transparency service.