Skip to content

WP7 hardening: thisismyurl-external-link-control (v1.6148.2110) - #34

Merged
thisismyurl merged 5 commits into
mainfrom
feat/abilities-api-wp7
May 29, 2026
Merged

WP7 hardening: thisismyurl-external-link-control (v1.6148.2110)#34
thisismyurl merged 5 commits into
mainfrom
feat/abilities-api-wp7

Conversation

@thisismyurl

Copy link
Copy Markdown
Owner

WP 7 hardening pass — thisismyurl-external-link-control.

Recommendation: TEST (net-new/mechanism change — activation + feature test before merge/tag).

Changes (4 commits):

  • Add WP 7 Abilities API support
  • Harden ELC: fix authenticated SSRF, swap to hardened updater, soften activation defaults
  • Apply gate-editor readme fixes
  • Stamp release version 1.6148.2110

Audit + scorecard: clients/thisismyurl/plugin-line-hardening/HARDENING-AUDIT.md
All files php -l clean. Version stamped X.6148.2110 (Toronto).

thisismyurl and others added 4 commits May 28, 2026 18:59
Register a read-only ability, thisismyurl-external-link-control/scan-external-links,
that exposes the broken-link checker's most recent scan to AI agents and REST
clients. The ability reads stored scan results only — it never triggers a new
scan, so it returns instantly and makes no outbound HTTP requests of its own.

- New includes/abilities.php registers the ability on wp_abilities_api_init,
  guarded by function_exists( 'wp_register_ability' ) for pre-6.9 WordPress.
  Category 'site'; annotations readonly/idempotent true, destructive false;
  show_in_rest true. permission_callback requires manage_options, matching the
  cap used by the checker's admin notice, dashboard widget, AJAX handlers, and
  the plugin's existing REST inventory route.
- Input schema: optional post_id, status (broken|unverified|all), limit.
- Output schema: links[] of { url, post_id, post_ids, status_code, message,
  is_broken, bucket } plus a summary with broken/unverified/total/returned/
  urls_checked/checked_at/has_run counts.
- Extract ELC_Link_Checker::get_results() as the single shaping path for the
  stored RESULTS_OPTION; the ability calls it. (Follow-up: the dashboard widget
  should be migrated onto the same accessor so there is one read path.)
- readme.txt: changelog entry under 1.6149. Version header left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…activation defaults

Fixes the audited findings on thisismyurl-external-link-control:

- SECURITY (authenticated SSRF in ajax_recheck_link): sanitize the URL with
  esc_url_raw; require it to be a member of the current broken/unverified
  result set before fetching; reject hosts that resolve to non-public
  addresses (loopback, 169.254/16 cloud-metadata, RFC-1918, ::1, fe80::/10,
  fc00::/7) before any wp_remote_*. manage_options + nonce gate preserved.
- Updater: wire the hardened TIMU_GitHub_Release_Updater (guarded
  after_install, timeout + UA, 200-check, 6h transient cache) via
  timu_boot_github_release_updater with the repo slug set; delete the buggy
  FWO_GitHub_Updater (updater.php) and its dead .distignore line.
- P2 activation default surprise: master switch now ships OFF so activating
  no longer silently rewrites every external link sitewide (opt-in); readme
  discloses the weekly outbound crawler cron.
- P2 uninstall incomplete: also delete timu_elc_domain_rules,
  timu_elc_broken_link_results, timu_elc_broken_link_ignored, and clear the
  timu_elc_broken_link_check cron event.
- P1/P2 promise under-disclosure: readme Description now discloses the weekly
  outbound link-crawler and the /timu-elc/v1/inventory REST endpoint.
- P2 perf: collect_external_urls() now queries fields=ids with
  no_found_rows=true (and skips meta/term cache priming), fetching content
  per-ID instead of pulling full post objects while paging.
- P3: changelog entry 1.6150; Version header left unchanged per contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 29, 2026 01:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

WP 7 hardening pass for the External Link Control plugin, including a new WP 7 Abilities API report, security hardening around broken-link rechecks, updater replacement, safer activation defaults, and uninstall cleanup.

Changes:

  • Added WP 7 Abilities API registration to expose the last broken-link scan as a read-only, capability-gated report.
  • Hardened and refactored the broken-link checker (SSRF guards for “Recheck”, result shaping helper, and a paging/query adjustment).
  • Swapped the legacy GitHub updater integration to the hardened shared release updater and updated docs/version stamping.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
updater.php Removes the legacy GitHub updater implementation.
thisismyurl-external-link-control.php Stamps release version, changes activation defaults, adds Abilities include, and wires the new GitHub release updater loader.
includes/class-elc-link-checker.php Adjusts URL collection query strategy, adds get_results() shaping, and adds SSRF guards + request sanitization for recheck.
includes/abilities.php Registers a WP 7 Ability that returns the last broken/unverified links report with optional filters.
uninstall.php Expands uninstall cleanup to remove all plugin options and clear the weekly cron hook.
readme.txt Updates stable tag, adds outbound-network-activity disclosure, and adds changelog entries for the hardening/features.
.distignore Updates distribution ignores to match the new updater file and removal of the legacy one.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +329 to 333
foreach ( $post_ids as $post_id ) {
$post_id = (int) $post_id;
$post_content = (string) get_post_field( 'post_content', $post_id );
if ( '' === $post_content ) {
continue;
Comment thread includes/abilities.php
Comment on lines +157 to +160
// Single source of truth for the last scan's stored results.
$checker = new ELC_Link_Checker();
$report = $checker->get_results();

Comment thread readme.txt
Comment on lines +114 to +118
= 1.6150 =
* Security: hardened the dashboard widget's "Recheck" action against server-side request forgery (SSRF). A recheck now only re-probes a URL the most recent scan already surfaced as broken or unverified, and refuses any host that resolves to a non-public address (loopback, link-local / cloud-metadata `169.254.169.254`, or RFC-1918 private space). The existing capability + nonce gate is unchanged.
* Security: swapped the GitHub update checker to the hardened release updater — the API request now carries a User-Agent and a timeout, checks for an HTTP 200 before trusting the response, caches results for six hours, and scopes its post-install folder move to this plugin only.
* Change: link filtering now ships OFF on activation. Activating the plugin no longer silently rewrites every external link sitewide; turn the master switch on from Tools > Link Control when you are ready. The new-tab / nofollow / UGC defaults are pre-set so the behaviour is in place the moment you enable filtering.
* Fix: uninstall now removes the per-domain rules, broken-link scan results, and ignore-list options, and clears the weekly broken-link cron event, instead of leaving them behind.
Comment on lines +329 to 333
foreach ( $post_ids as $post_id ) {
$post_id = (int) $post_id;
$post_content = (string) get_post_field( 'post_content', $post_id );
if ( '' === $post_content ) {
continue;
Comment thread includes/abilities.php
Comment on lines +157 to +160
// Single source of truth for the last scan's stored results.
$checker = new ELC_Link_Checker();
$report = $checker->get_results();

Comment thread readme.txt
Comment on lines +114 to +118
= 1.6150 =
* Security: hardened the dashboard widget's "Recheck" action against server-side request forgery (SSRF). A recheck now only re-probes a URL the most recent scan already surfaced as broken or unverified, and refuses any host that resolves to a non-public address (loopback, link-local / cloud-metadata `169.254.169.254`, or RFC-1918 private space). The existing capability + nonce gate is unchanged.
* Security: swapped the GitHub update checker to the hardened release updater — the API request now carries a User-Agent and a timeout, checks for an HTTP 200 before trusting the response, caches results for six hours, and scopes its post-install folder move to this plugin only.
* Change: link filtering now ships OFF on activation. Activating the plugin no longer silently rewrites every external link sitewide; turn the master switch on from Tools > Link Control when you are ready. The new-tab / nofollow / UGC defaults are pre-set so the behaviour is in place the moment you enable filtering.
* Fix: uninstall now removes the per-domain rules, broken-link scan results, and ignore-list options, and clears the weekly broken-link cron event, instead of leaving them behind.
@thisismyurl
thisismyurl merged commit a34a719 into main May 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants