browsec/FIREFOX_INTEGRATION.md

269 lines
12 KiB
Markdown

# Firefox integration spike
Status: active design and source reconnaissance
Date: 2026-08-17
## First running prototype
The adjacent Firefox checkout now has branch `codex/browsec-spike` with commit
`5ec348b0f1` (*Add Velvet Hammer certificate investigation prototype*).
The prototype adds a visibly distinct, browser-owned Velvet Hammer frame to
`about:certerror`. Its investigation action reads Firefox's protected
`FailedCertSecurityInfo`, displays the requested host, original NSS error,
failure category, conventional override eligibility, HSTS state, and the number
of handshake certificates, then computes and displays SHA-256 fingerprints from
the original DER certificate strings.
It deliberately remains read-only and displays `NOT TRUSTED`; no prototype UI
action silently changes Firefox trust. The dedicated browser test
`browser_aboutCertError_velvetHammer.js` passes all seven assertions, including
the complete DER-derived fingerprint and unchanged terminal trust state.
This first visible surface uses data already exposed to Firefox's trusted
certificate-error document. It does not yet provide the richer immutable
`TlsFacts`, append-only journal, plugin chain, policy broker, or fresh retry
required by the architecture below.
## Pinned reconnaissance baseline
The first source inspection uses an adjacent, shallow Firefox checkout:
```text
Checkout: /home/sergeych/dev/browsec-firefox
Upstream: https://github.com/mozilla-firefox/firefox.git
Branch: main
Depth: 1
Commit: b462c13f11417e13461f1202d71b14e2784f5db0
Commit date: 2026-08-17T09:27:35Z
Version: 156.0a1
```
This is a reconnaissance baseline, not the release baseline. Browsec will
select an ESR only after the integration seam has been proven. The exact
commit, rather than the moving branch name, defines all source references in
this document.
The Firefox checkout is deliberately not nested in this repository. Git
history can be added only when needed:
```sh
git fetch --deepen=50 origin main
# Or, only if full history becomes necessary:
git fetch --unshallow origin
```
## Architectural boundary
An ordinary WebExtension cannot enforce Browsec trust semantics. Firefox core
must remain the reference monitor. It alone may:
- receive authoritative results and certificate bytes from PSM/NSS;
- pause or terminate a TLS connection;
- validate a plugin verdict against identity, capabilities, scope, lifetime,
policy version, connection identity, and overridable error classes;
- make a stored policy visible to later certificate verification;
- require a genuinely fresh connection after a new decision; and
- host browser chrome that content and ordinary extensions cannot imitate.
Velvet Hammer and other security extensions remain JavaScript/TypeScript. They
receive immutable facts, append attributed journal entries, and return the
strict API's Boolean verdicts. They never receive an NSS handle, arbitrary
XPCOM access, or direct access to another extension's data.
## Confirmed certificate-verification path
At the pinned Firefox revision, the ordinary TLS path is:
1. `AuthCertificateHookInternal` in
`security/manager/ssl/SSLServerCertVerification.cpp` captures the peer DER
chain and dispatches an `SSLServerCertVerificationJob`.
2. `SSLServerCertVerificationJob::Run()` executes on a certificate-verification
thread in the parent process.
3. `AuthCertificate()` calls `CertVerifier::VerifySSLServerCert()` and obtains
the constructed DER chain, validation result, EV state, CT information,
issuer-source information, and related facts.
4. `AuthCertificateParseResults()` categorizes a failed result and consults
Firefox's existing `nsICertOverrideService` for eligible exact-certificate
overrides.
5. Both successful and failed results are moved through
`BaseSSLServerCertVerificationResult::Dispatch()`.
6. `SSLServerCertVerificationResult::Run()` executes on the socket transport
thread, populates `CommonSocketControl`, and finally calls
`SetCertVerificationResult(mFinalError)`.
The result object already carries both the constructed chain and peer-presented
chain as original DER byte arrays. This makes it the narrowest promising seam
for producing `TlsFacts` without reparsing lossy display objects.
HTTP/3 has an additional path in `netwerk/protocol/http/Http3Session.cpp`, which
uses `AuthCertificateHookWithInfo()`. It reaches the same verification
machinery, but must receive explicit integration tests; shared implementation
does not by itself prove equivalent pause, retry, and connection-reuse
semantics.
## Proposed native seam
Add a browser-internal `BrowsecTrustHost` continuation between receipt of the
PSM result and the final call to `SetCertVerificationResult`:
```text
NSS/PSM verification
|
v
SSLServerCertVerificationResult::Run
|
v
freeze Browsec TLS observation
|
+---- conventional success ---> bounded pre-accept chain
|
`---- conventional failure ---> normal failure + investigation record
|
v
protected Velvet Hammer UI
```
This should be an asynchronous native state machine, not a thread-blocking call
from C++ into JavaScript. On conventional success, the socket remains in its
existing certificate-authentication wait state while the request is dispatched
to the privileged extension host. Each JavaScript `onBeforeTlsAccept` callback
must return synchronously once invoked, but delivery and continuation across
Firefox threads are naturally asynchronous. A short browser-enforced deadline
fails closed.
Heavy work, network access, and interactive UI are forbidden in that hook.
Extensions perform such work in the background and consult prepared local state
when the hook arrives.
On conventional failure, the first slice need not retain the failed socket.
Core freezes an investigation record, completes the failure normally, and
correlates the resulting navigation with a protected investigation page. If the
user creates a valid exception, Browsec stores the policy and starts a fresh
navigation on a fresh connection.
## Why the existing override service is only a bootstrap adapter
`nsICertOverrideService` already stores host, port, origin attributes, exact
certificate, and session-or-persistent lifetime. PSM consults it only for
errors Firefox categorizes as overridable and rejects overrides where policy
such as HSTS forbids them. This is useful for the first exact-leaf prototype.
It cannot represent Browsec's complete policy model:
- exact DER authority trust restricted to a namespace;
- explicit distrust of otherwise accepted authorities;
- arbitrary timed lifetime;
- plugin identity and decision provenance;
- journal references and the exact set of errors authorized by a verdict;
- independent extension evidence storage; or
- user-inspectable policy history.
Therefore the adapter must be named and documented as provisional. Browsec's
durable policy overlay remains the source of truth even while an exact-leaf
rule is mirrored into the Firefox service for the first demonstration.
## Privileged extension host
The initial host should be a built-in Firefox WebExtension API namespace, not a
legacy external Experiment loaded from an arbitrary path. Current Firefox
implements parent-process extension APIs under
`toolkit/components/extensions/parent/`, with schemas and explicit manifest
permissions controlling exposure.
The Browsec API is available only to packages that pass all of these checks:
1. Browsec security-extension package type;
2. verified package identity;
3. requested capability in the package manifest;
4. local capability grant;
5. permitted execution mode for the current profile/context; and
6. API-version compatibility.
The built-in Velvet Hammer package is compiled into the product, always enabled,
and always ordered last. Its irremovability is enforced by the host, not by an
ordinary extension preference.
## First vertical slice
The spike is complete when an automated browser test demonstrates:
1. Navigate a clean Browsec profile to a local HTTPS server with an unknown
self-signed certificate.
2. Capture original DER, host, port, origin attributes, Firefox error, and both
available chain representations.
3. Display them in a browser-owned Velvet Hammer investigation page.
4. Obtain an explicit exact-certificate, exact-host, exact-port, session-scoped
user decision.
5. Validate and append the verdict to the audit journal.
6. Persist the Browsec policy rule and provisionally mirror it through
`nsICertOverrideService`.
7. Abandon the failed connection and start a fresh navigation.
8. Load the site and display a persistent Browsec-controlled indication that
conventional validation failed and named local policy permitted it.
9. Revoke the rule and prove that another fresh navigation fails again.
No page content, cookie, credential, authorization header, private key, or TLS
session secret is included in the investigation record.
## Patch sequence
### Patch 1: built-in UI and exact-leaf prototype
- add the built-in Velvet Hammer package and protected `about:` surface;
- expose immutable failed-connection facts through a minimal internal API;
- implement the browser-owned **Record security concern** chrome action;
- execute the existing TrustLab failure chain;
- use the provisional exact-leaf override adapter; and
- add browser and xpcshell tests.
This patch can begin in artifact-build mode if it changes only packaged
JavaScript, HTML, CSS, and existing scriptable interfaces.
### Patch 2: native pre-accept continuation
- add the `BrowsecTrustHost` continuation before
`SetCertVerificationResult(0)`;
- bind every observation and verdict to a unique connection attempt;
- enforce deadline, cancellation, and fail-closed behavior;
- ensure that plugin failure never becomes implicit trust; and
- test ordinary TLS and HTTP/3 paths.
This requires a full compiled Firefox build.
### Patch 3: native Browsec policy overlay
- stop depending on Firefox's override store as the source of truth;
- enforce certificate and authority rules with precise error substitution;
- implement explicit distrust on conventional-success paths;
- add timed and persistent policy lifetimes; and
- make policy snapshots auditable and atomically replaceable.
### Patch 4: network-path audit
Cover top-level navigation, redirects, subresources, WebSocket, workers,
service workers, speculative connections, proxy `CONNECT`, TLS resumption,
HTTP/2 and HTTP/3 coalescing, private browsing, containers, and client
certificate interactions. A path is not considered covered merely because it
eventually calls a shared verifier; its connection reuse and enforcement
behavior must be tested.
## Immediate implementation questions
The spike should resolve these from code and tests rather than policy intuition:
- which identifier safely binds the UI investigation to one network attempt;
- where a fresh-retry flag can prohibit reuse and coalescing;
- how much constructed-chain material remains available for every failure;
- which process owns the immutable investigation registry;
- whether the first built-in API can remain JS-only or requires new WebIDL;
- how the protected page proves to the user that it is browser chrome; and
- which Firefox error classes can safely use the provisional override adapter.
## Maintenance rule
Every Firefox rebase must rerun the integration tests and re-audit the call path
from `VerifySSLServerCert` through `SetCertVerificationResult`, including the
HTTP/3 entry point. Silent upstream movement of this boundary is a release
blocker, not an implementation detail.