269 lines
12 KiB
Markdown
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.
|