Webmention
Version 1.4.3
May 21, 2026
Fixed
- Fixed Bluesky reply threading regression introduced in 1.3.1. Bridgy stores Bluesky webmentions'
hEntryUrlin either DID form (/profile/did:plc:.../post/{rkey}) or handle form (/profile/{handle}/post/{rkey}), but theat://→bsky.appconversion inextractInReplyToUrls()always emits the DID form. Parent-lookup did an exact-string match onhEntryUrl, so replies whose parent was stored in handle form fell back to top-level. Resolution now falls back to matching by the post'srkey(a globally-unique TID), mirroring the existing Mastodon/web/statuses/{id}↔/@user/{id}fallback. Both directions (resolveParentWebmention()andresolveChildWebmentions()) handle the variation.
Added
- Added a backfill migration (
m260521_000000_backfill_parent_ids_bluesky) that re-resolvesparentIdfor existing webmentions with the new rkey matching. The migration only touches rows that still haveparentId = NULL, so prior backfills are preserved. As a side effect, it also picks up any Mastodon stragglers received after the 1.1.0 backfill ran. - Added a
webmention/backfill/blueskyconsole command with a--dry-runflag so the backfill can be previewed before applying. Useful for verifying matches against production data without writing anything.
Version 1.4.2
May 13, 2026
Fixed
- Broadened the
enshrined/svg-sanitizeComposer constraint even more to>=0.19,<1.0to track Craft's own version bumps (Craft 5.6 pinned~0.19.0, Craft 5.9 pinned~0.22.0). The plugin's usage of the Sanitizer API is stable across all 0.x releases, so the wider range introduces no functional changes — only avoids future "satisfiable but conflicts with another require" install errors when Craft moves its constraint forward.
Version 1.4.1
May 13, 2026
Fixed
- Widened the
enshrined/svg-sanitizeComposer constraint to^0.19 || ^0.20 || ^0.21to avoid a dependency conflict on sites running Craft 5.6+ that already pull in a newer svg-sanitize version. The plugin's usage (Sanitizer construction +sanitize()call) is stable across all supported versions.
Version 1.4.0
May 13, 2026
Security
- Critical: Added
safeOutboundRequest()helper enforcing per-hop SSRF protection on all outbound Guzzle requests. Source fetches and avatar downloads now validate resolved IP addresses against private/reserved ranges. - Critical: Response-body size caps of 5 MB for source HTML and avatars enforced via streaming
on_headersguard and byte-counting sink, preventing memory exhaustion from oversized responses. - High: SVG avatars are now sanitized via
enshrined/svg-sanitizebefore being written to the asset volume, removing<script>, event handlers, and<foreignObject>. - High: Avatar file extension is now derived from
Content-Typeheader first, with URL path as fallback, and validated against an allowlist (jpg,jpeg,png,gif,webp,svg). Files with unrecognised MIME types or disallowed extensions are refused. - High: Replaced catastrophic-backtracking regexes in
Sender::_findEndpointInBody()andSender::_findEndpointInHeaders()with DOMDocument/XPath and structured Link-header parsing, eliminating ReDoS risk on attacker-controlled page bodies and headers. Body-parsedrelvalues are now compared case-insensitively, matching the existing header behavior and the HTML living standard. - Medium: Hardened the URL-extraction regex in
extractTargets()against ReDoS. The%character was removed from the path bare character class so percent-encoded sequences (%XY) have exactly one valid parsing instead of two, eliminating the exponential-backtracking root cause. The regex's existing recognition of markdown-link URLs with embedded parens (Wikipedia's_(physicist)style),ftp://URLs, trailing slashes, and percent-encoded paths is preserved unchanged. - Medium: Added a unique database index on
(source, target, targetId, targetSiteId)in thewebmentionstable to prevent duplicate rows from concurrent queue workers. A migration deduplicates existing rows (keeping the most recently processed record, with preference for rows containing thread reply links). AnIntegrityExceptionguard in the queue job handles any remaining races gracefully. - Low: Server filesystem paths are now redacted from
errorTracebefore storage in the database. Stored traces use[plugin]and[craft]placeholders instead of absolute paths visible to anyone with DB access.
Added
- The
trustedSourceHostssetting now also exempts matching sources from the private/reserved-IP check (in addition to the rate limit). This lets self-hosted senders on internal networks, e.g. homelab Mastodon, intranet Micropub servers, deliver Webmentions to public Craft sites without disabling the SSRF guard for everyone. Trust remains opt-in and admin-controlled; unlisted sources are still validated as before.
Version 1.3.1
May 8, 2026
Fixed
- Fixed "Unable to parse URI: at://" failures when processing Bluesky webmentions routed through Bridgy. Bridgy's Bluesky comment pages contain
at://ATProto URIs in their<a href>elements, which crash Guzzle's URL parser. The link iteration invalidateWebmention()now pre-filters non-http(s) hrefs and is wrapped in a try/catch, so a single unparseable link no longer kills the whole validation loop. Affected failure rows clear on retry. - Fixed Bluesky reply threading. mf2
in-reply-tovalues pointing atat://did:.../app.bsky.feed.post/{rkey}are now converted to their canonicalhttps://bsky.app/profile/{did}/post/{rkey}form so they can be matched against existing webmentions'hEntryUrl. Replies to other Bluesky posts now nest correctly under their parent in threaded display.
Version 1.3.0
May 6, 2026
Security
- Fixed a stored XSS vulnerability in author and entry URLs displayed in the control panel and front-end Webmention templates. The plugin now validates URL schemes (only
http(s):allowed) at storage and at render time, sojavascript:,data:, and similar URI schemes from a malicious source page can no longer be rendered as clickable links. URLs containing whitespace, userinfo (user:pass@…), illegal host characters, or longer than 2048 characters are also rejected. Sites running prior versions should upgrade. - Added a per-IP rate limit on the public Webmention endpoint (default: 100 submissions/hour, configurable via the new
rateLimitPerHoursetting). High-traffic non-Bridgy senders that previously had no cap will now receive 429 responses past the limit; raise the limit or add the host totrustedSourceHostsif needed. Set to 0 to disable. - Added a failure-backoff threshold (default: 5, configurable via the new
failureBackoffThresholdsetting). Once a(source, target)pair has been recorded as a failure this many times, further submissions for that pair are no longer queued or fetched until the cleanup command purges the record. The failure row's attempt counter still increments so repeated abuse stays visible in the Failed Webmentions view. - Submissions for the same
(source, target)pair received within a 5-minute window are now deduplicated at the controller, so a flood of identical submissions cannot amplify outbound HTTP fetches.
Added
- Added a
trustedSourceHostssetting (default:['brid.gy']) that lets configured source hosts bypass the per-IP rate limit, so viral traffic routed through Bridgy isn't dropped during a spike. Subdomain matches are included automatically, so the default coversfed.brid.gyandbsky.brid.gy.
Fixed
- Stopped HTML-encoding URL field values during parsing. URL escaping is now handled at the rendering layer, so stored URLs no longer have
&characters corrupted to&.
Version 1.2.0
March 22, 2026
Added
- Added a Failed Webmentions view to the control panel. Webmentions that cannot be processed are now recorded in a new
webmention_failurestable instead of disappearing silently into the logs. - Failed webmentions are shown in a dedicated "Failed" subnav item with a badge displaying the current failure count.
- The failures table shows source, target, error message, attempt count, and last attempted timestamp for each failure.
- Added per-row Retry and Dismiss actions. Retry re-queues the webmention for processing and removes the failure record; a successful retry also clears the record automatically.
- Added bulk Retry All and Dismiss All actions above the failures table.
- If a source+target combination fails repeatedly, the existing failure record is updated (incrementing the attempt count) rather than creating duplicates.
- Added a
failureRetentionDayssetting (default: 30) to automatically prune old failure records. - Added a
webmention/cleanup/failuresconsole command to manually prune failure records older than the retention setting. - Added Bluesky author fallback via the public AT Protocol API. When a webmention from Bridgy Fed (
bsky.brid.gy) contains no author data in its mf2, the plugin now extracts the Bluesky DID from the URL and fetches the author's display name, profile URL, and avatar frompublic.api.bsky.app. The avatar is stored locally via the existing asset saving flow. - Tracking parameters (
utm_*,fbclid,gclid, and similar) are now stripped from URLs during normalization to prevent duplicate webmention records for the same source.
Version 1.1.3
March 22, 2026
- Fixed avatar saving failures in queue context caused by the volume filesystem using a relative Base Path that didn't resolve correctly in CLI.
- Fixed file extension detection for avatar URLs containing query strings (e.g.
avatar.webp?cb=123). - Added validation for empty or truncated avatar responses from CDNs.
- Added error handling for
cleanImage()failures that could destroy the temp file before asset saving. - Fixed a crash when processing Bluesky webmentions with
at://protocol URIs inin-reply-todata.
Version 1.1.2
March 21, 2026
- Fixed a race condition where concurrent queue jobs processing webmentions from the same author would write to the same temporary file path, causing avatar assets to fail with an "Unable to copy stream" error.
- Avatar assets are now reused across webmentions from the same author instead of being re-downloaded and re-saved for each incoming webmention.
Version 1.1.1
March 14, 2026
- Missed to bump up the $schemaVersion…
Version 1.1.0
March 14, 2026
- Added threaded/nested display for webmentions. Replies to other webmentions are now tracked via
in-reply-tofrom mf2 data and displayed as nested threads. - Added
parentIdto the Webmention element, linking replies to the webmention they respond to. - Added reverse resolution: when a new webmention arrives, existing orphan replies are automatically linked to it.
- Added retroactive backfill: existing webmentions with
in-reply-todata are connected to their parents on migration. - Added "Threaded Display" setting in the control panel to toggle between threaded and flat display.
- Added
getThreadedWebmentions(),getThreadedWebmentionsForElement(),getParentWebmention(),getChildWebmentions(), andgetInReplyToUrl()template helpers. - Added
parentId()andhasParent()query params toWebmentionQuery. - Improved accessibility of the default template with semantic list markup and ARIA labels.
- Added retry logic for avatar asset saving to handle transient stream errors.
Version 1.0.7
March 7, 2026
- Fixed a database error caused by oversized
namevalues from senders that populate the h-entrynameproperty with UI chrome and article excerpts due to the mf2 implied name algorithm.
Version 1.0.6
March 5, 2026
- Fixed Bridgy source URL type and site detection to match path segments only, preventing false positives where e.g.
app.bsky.feed.postin Bluesky URLs would incorrectly match as a mention type.
Version 1.0.5
March 4, 2026
- Added alt text support for author photos. (#9)
- Added
loading="lazy"to the webmention Twig template. - Improved reliability: unresolvable URLs (local/test TLDs, localhost, IP addresses) are now detected via DNS lookup and handled gracefully without blocking the queue.
- Improved performance: avatar images are no longer loaded twice, and Guzzle connection/request timeouts are now configured to better handle slow responses.
- Improved URL matching by normalizing URLs for comparison.
- Fixed multiple bugs in webmention parsing and avatar handling, including null checks for representative h-card property access, graceful handling of missing published dates, avatar filename collision prevention (by hashing the full URL), and correct Bridgy type detection.
- Fixed form submission check to use URL parameter.
Version 1.0.4
May 16, 2025
- Added support for eager-loading webmentions
- Added new
element.getTotalWebmentions()andgetTotalWebmentionsByType()methods, which support eager-loading as well.
Version 1.0.3
May 12, 2025
- Improved performance by automatically eager-loading avatars the first time
getAvatar()is called. (#13) - Added the “Host” condition rule type. (#12)
- “Source” and “Target” condition rules no longer have “has a value” or “is empty” operators. (#12)
authorNamevalues now use the h-card’snicknameproperty as a fallback. (#10)- Fixed a bug where webmention validation wasn’t catching
ConnectExceptionerrors. - Fixed a bug where jobs for webmentions whithout a valid backlink to the target got stuck in the queue.
- Fixed a bug where Bluesky source URLs from Brid.gy weren’t always being stored in their entirety.
Version 1.0.2
March 21, 2025
- Fixed a bug where getting the the avatar photo from the parsed representative h-card would fail because the URL was the value inside of an array instead of being a string. Now the plugin supports both cases.
Version 1.0.1
March 15, 2025
- Added
avatarId,host, andpropertiesas optional table attributes - Fixed Bluesky (via Bridgy) avatars: if an avatar image has no extension, the extension is now determined by the respective MIME type
Version 1.0.0
March 9, 2025
- Added Craft 5 compatibility.
- Added the “Avatar Location” setting.
- Avatar assets are now accessible via
webmention.avatar. - Webmentions now store which element they are associated with.
- Added the ability to update webmentions from the control panel.
- Added the
resave/webmentionsCLI command. - Added the
webmention/example-templateCLI command. - Added the
webmention/updateCLI command. - Added the
webmention/update-avatarsCLI command. - Added the
getWebmentions()andgetWebmentionsByType()element behaviors - Added support for the Bridgy site types
mastodon,bluesky,github, andreddit. - Added a new icon based on Paul Robert Lloyd’s IndieWeb icon designs
- Fixed the regex that scans for URLs in entries so that it now correctly handles Markdown links
- Lots of smaller bugfixes and improvements
Version 0.3.1
April 2, 2017
- Changed the retrieval method for links within an entry to fix a bug where a very long article with many links would lead to a PHP execution timeout
- Minor bugfixes and improvements
Version 0.3.0
January 6, 2017
- Webmention sending functionality implemented
- Setting added: Entry Types (for Webmention sending)
- New “Webmention Switch” field type
Version 0.2.0
June 7, 2016
- Webmentions are now stored as Craft elements (ElementType:
Webmention_webmention) - Improved backend functionality: Webmentions are displayed under the tab Webmentions and can be deleted
- The plugin now sets the
typeproperty of an incoming Webmention correctly, based on the Microformats propertiesu-like-of,u-like,u-repost-of, andu-repost.
Version 0.1.0
June 3, 2016
- First version