Skip to content

feat(plugins): let link previews read a public page's preview fields - #3217

Open
joahg wants to merge 1 commit into
block:mainfrom
joahg:joah/host-public-pages
Open

joahg wants to merge 1 commit into
block:mainfrom
joahg:joah/host-public-pages

Conversation

@joahg

@joahg joahg commented Nov 10, 2026

Copy link
Copy Markdown
Member

Linear: AGNTOPS-773

Why

Buzz should unfurl links by default (Joah's call in the plugin bounty thread). A preview reads a page nobody declared ahead of time. Today the only way to do that is spawning curl, which the marketplace link-previews plugin does. curl resolves the name itself, so it can be pointed at private addresses through DNS rebinding.

What

  • A new manifest grant, host.publicPages, and a new call, host.readPage(url, { timeoutMs? }).
  • The native side reuses public_image's checks. It resolves the name, refuses any non-public address, connects only to the addresses it checked, and allows only port 443, with no proxy and no cookies.
  • It follows no redirect. A 3xx returns { status, location }, and the plugin checks the next hop before asking again, exactly as publicImages works.
  • It reads at most the page head (512 KiB, stopping at </head> or <body>). It returns only title, description, siteName, image, imageAlt and icon, preferring Open Graph, then Twitter cards, then <title>/description. Entities are decoded, whitespace collapsed and lengths capped. image and icon are absolute HTTPS URLs. The HTML never reaches the plugin.
  • Plugin access review discloses the grant ("Contact any public website for link previews") with the same VPN and IP caveats as public images.
  • Docs: plugin-architecture.md and plugin-authoring-guide.md.

public_image::request now takes the Accept header, and its error text says "Public host" rather than "Public image host", since pages share it.

Review notes

  • src/plugins/api.ts is FOUNDATION. This adds one optional manifest field and changes nothing else.
  • The head scanner is small and hand-written, adding no dependency. It skips comments, <script>, <style>, <noscript> and <template>, and stops at </head>/<body>.

Testing

  • cargo test --manifest-path src-tauri/Cargo.toml --lib host_request: 49 passed. That includes 9 new public_page tests: preview precedence, scripts and comments skipped, HTTPS-only URLs, length caps, broken markup, a page answered as fields only through the pinned address, redirect handoff, other types and errors returned bare, and the head limit.
  • cargo test -p buzzodz-plugins --lib: 84 passed, including manifest round-trip and rejection for publicPages.
  • vitest: src/features/host/service.test.ts and src/app/PluginAccessReview.test.tsx (102 passed); src/plugins/author.test.mjs passed.
  • tsc --noEmit is clean.

Part of a stack:

  1. This PR: page reads.
  2. The link preview slot.
  3. Bundled buzz.link-previews, on by default.

🤖 Generated with Claude Code

Adds a host.publicPages grant and host.readPage(url). Native code pins
public addresses as host.publicImages does, follows no redirect, reads at
most the page head and returns only title, description, site name, image
and icon, never the HTML.

Linear: AGNTOPS-773

Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>

This branch has not been deployed

No deployments
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.

1 participant