Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Secure Plugin Updates Lab

Experimental WordPress plugin for testing safer plugin update routines in a supply-chain attack model.

The first scaffold focuses on three defenses:

  1. Defer automatic plugin updates until the target version is at least X days old.
  2. Run provider-based version approval before plugin package installation/update.
  3. Verify package hashes and optional author signatures before WordPress unzips an update package.

Local dev with wp-env

npm install
npm run env:start
npm run wp -- plugin activate wp-secure-plugin-updates

Open 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:stop

Local dev with WordPress Playground

npm install
npm run playground

The 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.

Version approval providers

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.

Local first-seen ledger

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-seen

The 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 {
returnarray(
'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.

Integrity manifest prototype

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.

Notes

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages