Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 97 additions & 100 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,140 +1,142 @@
# HTMLTrust Browser Reference

Reference browser extension for client-side validation of HTMLTrust signed content. Verifies cryptographic signatures embedded in web pages using the `<signed-section>` element protocol.
Reference browser extension for validating HTMLTrust `<signed-section>` elements in a browser.

This is a companion to the [HTMLTrust specification](https://github.com/HTMLTrust/htmltrust-spec).
The extension verifies signatures locally, shows a status marker beside each signed section, and exposes details in the popup. It is a companion to the [HTMLTrust specification](https://github.com/HTMLTrust/htmltrust-spec).

## What It Does
## Start here

When you visit a web page containing signed content, this extension:
Readers: contributors and implementers. The normal workflow is:

- **Detects** `<signed-section>` elements carrying `signature`, `keyid`, `algorithm`, and `content-hash` attributes
- **Verifies** signatures by fetching the author's public key and validating the cryptographic signature
- **Displays** trust indicators (badges, outlines) showing verification status
- **Queries** optional trust directories for author reputation and endorsements
- **Enables** community trust/distrust voting on authors and content
1. Build the sibling browser-client package.
2. Install this repository.
3. Run tests and type checking.
4. Build the extension for the browser you use.

## Architecture

The codebase is split into **shared** (reusable) and **browser-specific** layers:

```
src/
├── core/ # ✅ SHARED — reusable across any browser
│ ├── api/ # REST clients for HTMLTrust trust directory server
│ ├── auth/ # Authentication service (API key management)
│ ├── common/ # Types, constants, utilities
│ ├── content/ # Content processor (DOM canonicalization, hashing, metadata extraction)
│ └── storage/ # Storage abstraction (interface + in-memory implementation)
├── platforms/ # 🔴 BROWSER-SPECIFIC — one adapter per browser
│ ├── common/ # PlatformAdapter interface (storage, messaging, tabs, scripting)
│ ├── chromium/ # Chrome / Edge implementation + Manifest V3
│ ├── firefox/ # Future, Manifest V2 (manifest only, no adapter yet)
│ └── safari/ # Future, Manifest V3 (manifest only, no adapter yet)
├── ui/ # ✅ SHARED — React components for popup, options, and in-page UI
│ ├── components/ # Reusable widgets (Button, MetadataInput, ProfileManager, etc.)
│ ├── popup/ # Extension popup (verification status, signing controls)
│ └── options/ # Extension options page (settings, profiles, server config)
├── background/ # Service worker entry point
├── content-scripts/ # Content script entry point
└── assets/ # Icons, CSS
```
After each page load or same-document navigation, the content script refetches the current HTTPS URL (using the browser HTTP cache when available). It parses that response with the browser's HTML parser and freezes signed-section snapshots. It verifies those snapshots, then compares each one with the current live element. If page code changes a signed element, the extension marks it as stale and re-verifies it. A refetch can differ from the original response on personalized, time-varying, or service-worker-controlled pages. Status markers are siblings of `<signed-section>`, so extension UI cannot become signed content.

### Adding a New Browser

1. Create `src/platforms/<browser>/adapter.ts` implementing the `PlatformAdapter` interface
2. Create `src/platforms/<browser>/manifest.json` for that browser
3. Update `webpack.config.js` to add the new target
4. The shared `core/`, `ui/`, `background/`, and `content-scripts/` code works unchanged

## Tech Stack

- **TypeScript** with strict mode
- **React 19** for UI components
- **Webpack 5** with per-browser build targets
- **Jest** + ts-jest for testing
- **js-sha256** + **simhash-js** for content hashing

## Quick Start
## Quick start

### Prerequisites

- Node.js 22+ and npm
- Chromium, Firefox, or Safari for loading the matching build
- Node.js 22 or newer
- npm
- Chromium, Firefox, or Safari for loading a built extension

The extension consumes the browser-client package from a sibling checkout. Use this layout when developing the two repositories together:
Use this checkout layout. The browser-reference package has a local dependency on the browser-client package during development:

```
workspace/
htmltrust-workspace/
├── htmltrust-browser-client/
└── htmltrust-browser-reference/
```

The canonicalization package is downloaded from its pinned v0.2.2 release archive. The browser-client sibling must be built before installing this package.

### Clean checkout
Create both checkouts and build the client first:

```sh
mkdir htmltrust-workspace
cd htmltrust-workspace
git clone https://github.com/HTMLTrust/htmltrust-browser-client.git
git clone https://github.com/HTMLTrust/htmltrust-browser-reference.git
cd htmltrust-browser-client
git checkout 09e8c7552c8111a2cedd83fa45f4ffe3811bf5ca
npm ci --ignore-scripts
npm run build
cd ../htmltrust-browser-reference
npm ci --ignore-scripts
```

The browser-client commit above is the revision pinned by the reference repository's CI. Keep the checkout at that revision when reproducing CI locally.
The reference repository CI pins the client to commit `09e8c7552c8111a2cedd83fa45f4ffe3811bf5ca`. Check out that revision when reproducing CI exactly.

### Build
### Test and type-check

Build for a specific browser:
```sh
npm test -- --runInBand
npm run typecheck
npm run lint
```

Tests use jsdom for DOM behavior. Run the complete check in a Node 22
container with:

```sh
npm run build:chromium # → build/chromium/
npm run build:firefox # → build/firefox/
npm run build:safari # → build/safari/
./scripts/test-in-docker.sh
```

Or build all:
The script copies both sibling repositories into the container, builds the
browser client, runs 60 extension tests, checks types and lint, then builds the
Chromium, Firefox, and Safari packages. Generated files stay outside the
checkout.

### Build

Build one browser with `npm run build:chromium`, `npm run build:firefox`, or `npm run build:safari`. Build all targets and zip archives with:

```sh
npm run build # Builds all targets + creates zips
npm run build:all
```

The unpacked extension is written to `build/<browser>/`. For Chromium, open `chrome://extensions/`, enable Developer mode, choose **Load unpacked**, and select `build/chromium/`.

### Development

```sh
npm run dev:chromium # Watch mode for Chromium
npm run dev:chromium
```

### Load in Chrome
Use the matching `dev:firefox` or `dev:safari` command for another target. Reload the unpacked extension after a rebuild.

1. Open `chrome://extensions/`
2. Enable "Developer mode"
3. Click "Load unpacked" → select the `build/chromium/` folder
## Verification lifecycle

### Test
`src/core/content/navigation-lifecycle.ts` owns navigation state:

```sh
npm test # Run all tests
npm run typecheck # TypeScript check without emitting files
npm run lint # Lint TypeScript sources
```
- `captureNavigationSnapshot` parses refetched HTML and freezes source sections.
- `mapSnapshotToLiveSections` pairs source sections with live elements by signed attributes, so page reordering does not pair one signature with another.
- `observeSignedSection` watches only the live signed element. Mutations trigger re-verification against the immutable source section. History changes and replacement of signed sections trigger a fresh page refetch.
- The content script inserts markers beside the signed element. The marker, tooltip, and vote controls are outside signed content.

The CI validation sequence runs `npm run lint`, `npm test`, and each browser build:
The popup receives copied result records. It cannot mutate the content script's verification cache.

```sh
npm run lint
npm test
npm run build:chromium
npm run build:firefox
npm run build:safari
## Architecture

The codebase is split into **shared** (reusable) and **browser-specific** layers:

```
src/
├── core/ # Shared code used by every browser
│ ├── api/ # REST clients for HTMLTrust trust directory server
│ ├── auth/ # Authentication service (API key management)
│ ├── common/ # Types, constants, utilities
│ ├── content/ # Content processor (DOM canonicalization, hashing, metadata extraction)
│ └── storage/ # Storage abstraction (interface + in-memory implementation)
├── platforms/ # One adapter per browser
│ ├── common/ # PlatformAdapter interface (storage, messaging, tabs, scripting)
│ ├── chromium/ # Chrome / Edge implementation + Manifest V3
│ ├── firefox/ # Future, Manifest V2 (manifest only, no adapter yet)
│ └── safari/ # Future, Manifest V3 (manifest only, no adapter yet)
├── ui/ # Shared popup, options, and in-page React UI
│ ├── components/ # Reusable widgets (Button, MetadataInput, ProfileManager, etc.)
│ ├── popup/ # Extension popup (verification status, signing controls)
│ └── options/ # Extension options page (settings, profiles, server config)
├── background/ # Service worker entry point
├── content-scripts/ # Content script entry point
└── assets/ # Icons, CSS
```

### Adding a New Browser

1. Create `src/platforms/<browser>/adapter.ts` implementing the `PlatformAdapter` interface
2. Create `src/platforms/<browser>/manifest.json` for that browser
3. Update `webpack.config.js` to add the new target
4. The shared `core/`, `ui/`, `background/`, and `content-scripts/` code works unchanged

## Tech stack

- **TypeScript** with strict mode
- **React 19** for UI components
- **Webpack 5** with per-browser build targets
- **Jest** + ts-jest for testing
- **js-sha256** + **simhash-js** for content hashing

## Project Structure

```
Expand All @@ -150,11 +152,10 @@ npm run build:safari

## Current Status

- ✅ Chromium adapter fully implemented
- ✅ Core content verification pipeline
- ✅ React popup and options UI
- ⬜ Firefox adapter, manifest only, needs a `browser.*` API adapter
- ⬜ Safari adapter, manifest only, needs an adapter
- Complete: Chromium adapter, core verification, popup, and options UI
- Complete: navigation snapshots and mutation re-verification
- Pending: Firefox `browser.*` API adapter; the manifest exists
- Pending: Safari adapter; the manifest exists

## Companion Repositories

Expand All @@ -170,18 +171,14 @@ npm run build:safari

This project is licensed under the [PolyForm Noncommercial License 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0). You may use, modify, and share the software for any noncommercial purpose with attribution. Commercial use requires a separate agreement with the licensor.

## Origin & Contributions

HTMLTrust is an idea I (Jason Grey) have been chewing on since 2024. I'm not an academic — I'm an engineer with a day job and a family — so the spec, the reference implementations, and most of this prose have been written with significant help from AI tools acting as research assistant, technical writer, and pair programmer. I wrote the original architectural sketches and reviewed every line; the assistants filled in the gaps and saved me from re-typing the same explanation for the hundredth time.

**Contributions are welcome — human or AI-assisted, doesn't matter to me.** What matters is whether the code, the spec text, or the conformance vectors move the project forward. Open a PR.

What this project is **not** a forum for:
## Origin and contributions

- Debates about whether AI should be used to write code or specifications.
- Opinions on who is or isn't trustworthy on the web.
- Politics, religion, professional practice, or personal philosophy.
Jason Grey began HTMLTrust in 2024 and reviews the protocol and reference
implementations. AI tools have supported research, drafting, and pair
programming throughout the project.

HTMLTrust is a mechanism — a way for *anyone* to sign content they publish and for *anyone* to decide whom they trust, on their own terms. The project takes no position on what the right answers are; it just provides the tools. If you want to debate the answers, there are entire continents of the internet better suited to it.
Contributions are welcome. Open a pull request with the tests or conformance
vectors that demonstrate the change. Keep repository discussion focused on
the protocol and implementation behavior.

If this work is useful to you and you'd like to support it, see [GitHub Sponsors](https://github.com/sponsors/jt55401) or the other channels in [`.github/FUNDING.yml`](.github/FUNDING.yml).
42 changes: 42 additions & 0 deletions scripts/test-in-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CLIENT_ROOT="$(cd "$REPO_ROOT/../htmltrust-browser-client" 2>/dev/null && pwd || true)"

if ! command -v docker >/dev/null 2>&1; then
echo "Docker is required." >&2
exit 2
fi
if [[ -z "$CLIENT_ROOT" || ! -f "$CLIENT_ROOT/package.json" ]]; then
echo "Expected htmltrust-browser-client beside this checkout." >&2
echo "Clone it at: $(dirname "$REPO_ROOT")/htmltrust-browser-client" >&2
exit 2
fi

CHECKOUT_ID="$(printf '%s\n%s' "$REPO_ROOT" "$CLIENT_ROOT" | cksum | awk '{print $1}')"
NPM_CACHE="htmltrust-browser-${CHECKOUT_ID}-npm"

docker run --rm \
--volume "$REPO_ROOT:/source/browser-reference:ro" \
--volume "$CLIENT_ROOT:/source/browser-client:ro" \
--volume "$NPM_CACHE:/root/.npm" \
node:22-bookworm sh -euc '
mkdir -p /work/htmltrust-browser-reference /work/htmltrust-browser-client
(cd /source/browser-reference && tar --exclude=node_modules --exclude=build -cf - .) \
| (cd /work/htmltrust-browser-reference && tar -xf -)
(cd /source/browser-client && tar --exclude=node_modules --exclude=build -cf - .) \
| (cd /work/htmltrust-browser-client && tar -xf -)

cd /work/htmltrust-browser-client
npm ci --ignore-scripts --no-audit --no-fund
npm run build

cd /work/htmltrust-browser-reference
npm ci --ignore-scripts --no-audit --no-fund
npm test -- --runInBand
npm run typecheck
npm run lint
npm run build:all
'
Loading
Loading