Skip to content

feat(extension): local verification via browser-client + auto-verify + http dev support#1

Merged
jt55401 merged 2 commits into
mainfrom
feat/use-browser-client
May 13, 2026
Merged

feat(extension): local verification via browser-client + auto-verify + http dev support#1
jt55401 merged 2 commits into
mainfrom
feat/use-browser-client

Conversation

@jt55401
Copy link
Copy Markdown
Contributor

@jt55401 jt55401 commented Apr 29, 2026

Summary

Closes the four P0 browser-extension items from TODO-Cleanup.md:

  • Local verification (spec §3.1). Replace the deprecated trust-server /api/content/verify endpoint with verifySignedSection from @htmltrust/browser-client, which uses SubtleCrypto and runs entirely in the extension. The trust server is no longer contacted for verification.
  • Auto-verify on page load. A new content script walks every <signed-section[signature]> on DOMContentLoaded, verifies it locally, evaluates the trust policy, and injects inline trust badges next to each section. No popup interaction required. Pages without signed-sections are a graceful no-op.
  • Pluggable keyid resolution. Uses defaultResolverChain from the lib (did:web → direct URL → trust directories). The directory list is configurable in extension settings.
  • HTTP host_permissions for dev. Add "http://*/*" alongside "https://*/*" in chromium, firefox, and safari manifests so the e2e simulation harness and local dev servers (which run plain HTTP) work. Production deployments are HTTPS by convention but the protocol is transport-agnostic; the comment fields in each manifest spell this out.

Architecture

  • Layer 1 (crypto)verifySignedSection from @htmltrust/browser-client, called from both:
    • The auto-verify content script (per signed-section, with the live DOM Element).
    • The popup-driven background verifyContent() (with the section's outerHTML, run in the service worker context).
  • Layer 2 (policy)evaluateTrustPolicy from the lib. Inputs come from settings: personalTrustList, trustedDomains, directorySubscriptions (empty for now, matching the e2e harness TODO; the trust server doesn't yet expose the spec's <dir>/keys/<keyid>/reputation shape).
  • Resolver chaindefaultResolverChain({ directories }) built from the user's configured trust-directory list.

Settings changes (back-compat preserved)

  • New: trustDirectoryUrls: string[], personalTrustList: string[], trustedDomains: string[].
  • Legacy: trustDirectoryUrl: string is preserved on read via getTrustDirectoryUrls(settings) which normalizes to a list.

Build / typecheck

  • npm run build (chromium + firefox + safari) compiles clean.
  • npx tsc --noEmit passes.
  • Webpack: added IgnorePlugin for node:crypto so the canonicalization lib's Node-only code path doesn't break browser bundles.
  • Jest: pre-existing — zero test files, missing jest-environment-jsdom. Out of scope (P1 in TODO-Cleanup.md).

Deviations

  • The task spec said "file:../htmltrust-browser-client" for the dep path. Because this work runs in a git worktree at .worktrees/browser-ext/, the actual relative path is "file:../../htmltrust-browser-client". If the PR is rebased back into the main repo's package.json (out of worktree), this should be normalized to ../.

Test plan

  • npm install && npm run build:chromium produces build/chromium/ cleanly.
  • Load build/chromium/ as an unpacked extension in Chrome (chrome://extensions → Developer mode → Load unpacked).
  • Visit a page served by the e2e simulation that contains a <signed-section signature="..."> element. Confirm trust badges appear inline below the signed section without clicking the popup.
  • Visit a page with no signed-section. Confirm the extension is a graceful no-op (no badges, no errors in console).
  • Open the popup and click "Verify Content". Confirm the verification result reflects local crypto (no network call to a /api/content/verify endpoint).
  • Open the options page. Confirm the trust-directory list, personal trust list, and trusted-domain editors render and persist.
  • Add a known-trusted keyid to "Personal Trust List" and reload a page signed by that key. Confirm the trust score gains +40 (visible in the badge tooltip's per-input breakdown).
  • Visit an http:// page with a signed section. Confirm the extension can inspect it (host_permissions).
  • Visit an https:// page. Confirm the extension still works (no regression).
  • Inspect the extension's network tab. Confirm no requests to /api/content/verify are made during normal browsing.

🤖 Generated with Claude Code

jt55401 and others added 2 commits April 28, 2026 23:23
Production HTMLTrust deployments use HTTPS by convention but the
protocol itself is transport-agnostic. The local dev simulation harness
and on-machine test pages run plain HTTP, so the extension currently
can't verify them. Add "http://*/*" alongside the existing
"https://*/*" pattern in chromium, firefox, and safari manifests with
an explanatory comment field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o-run on page load

Replace server-mediated verification (deprecated /api/content/verify
endpoint) with local SubtleCrypto verification using the shared
@htmltrust/browser-client library, per spec §3.1. Verification now
runs automatically on every page load with no popup interaction
required.

Changes:

- Add @htmltrust/browser-client dependency (file: link to local
  reference impl). Pull @htmltrust/canonicalization in via the same
  file: pattern so the peer dep matches versions exactly.

- Content script (content-scripts/index.ts): walks every
  <signed-section[signature]> on DOMContentLoaded, calls
  verifySignedSection (Layer 1) + evaluateTrustPolicy (Layer 2),
  injects inline trust badges next to each section. Visual style
  matches the e2e harness for screenshot consistency. Idempotent
  (skips sections that already have a badge sibling). Pages without
  signed-sections are a graceful no-op.

- Background script (background/index.ts): popup's "Verify Content"
  command now runs verifySignedSection in the service worker context
  (HTML fragment passed via executeScript) instead of POSTing to the
  trust server. Best-effort author name lookup is preserved.

- Content signing API client (core/api/content-signing-client.ts):
  verifyContent() is now a deprecated shim that returns a clear
  failure pointing callers at verifySignedSectionLocal(). The class
  also owns the resolver chain and exposes setTrustDirectories() so
  the directory list can be live-updated when settings change.

- Pluggable keyid resolution: the resolver chain
  (defaultResolverChain → did:web → direct URL → trust directories) is
  built from extension settings and passed through to the lib in both
  the content script and background. Settings UI exposes a list of
  directory URLs (textarea, one per line); the legacy single-URL
  setting still works via getTrustDirectoryUrls() normalization.

- Settings (core/common/types.ts, constants.ts, ui/options): add
  trustDirectoryUrls (list), personalTrustList, and trustedDomains.
  These feed evaluateTrustPolicy directly. The legacy trustDirectoryUrl
  scalar is preserved for back-compat with persisted settings.

- Webpack config: IgnorePlugin for node:crypto so the canonicalization
  lib's Node-only code path doesn't break the browser bundle.

Build: npm run build produces all three browser targets (chromium,
firefox, safari) cleanly. typecheck (tsc --noEmit) passes. The Jest
suite has zero test files and is missing jest-environment-jsdom; both
are pre-existing P1 items in TODO-Cleanup.md and out of scope.

Note on trust policy directorySubscriptions: passed empty for now,
mirroring the e2e harness TODO. The lib's reputation URL shape
(<dir>/keys/<keyid>/reputation) doesn't yet match the trust server's
endpoint; will be wired once the server exposes the spec-compliant
shape.

Note on dependency path: package.json uses
"file:../../htmltrust-browser-client" (two segments up) because this
work happens in a git worktree at .worktrees/browser-ext/. From the
main repo root the equivalent path is "file:../htmltrust-browser-client".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jt55401 jt55401 merged commit 4d66494 into main May 13, 2026
1 check failed
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