Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Codacy — reduce noise on generated / vendor / report trees (PR #15 100-issue cap).
# https://docs.codacy.com/repositories-configure/codacy-configuration-file/
# Advisory detector stays in Semgrep/Opengrep (array walk is `for…of`, not `input[i]`).
---
engines:
remark-lint:
enabled: false
exclude_paths:
- "assets/**"
- "libraries/**"
Expand All @@ -10,3 +14,5 @@ exclude_paths:
- "scripts/**"
- "extension/vendor/**"
- "coverage/**"
- "SECURITY.md"
- "AUDIT.md"
10 changes: 5 additions & 5 deletions AUDIT.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Fabric Hub Security Audit
Living posture notes for **hub.fabric.pub** (`@fabric/hub` **0.1.0-RC1**). Re-run **`npm audit`** after dependency changes; keep this file aligned with the current lockfile.

## Status (2026-08-14)
## Status (2026-08-15)

| Area | Posture |
|------|---------|
| `@fabric/core` | Git pin `FabricLabs/fabric#feature/rsi` (lockfile SHA `1fc616492428ec6e8c731e3afb74fd841407aa0e`) |
| `@fabric/http` | Git pin `FabricLabs/fabric-http#feature/rsi` (lockfile SHA `852520a2bd1070bb974b1a34297811f3c63588eb`) |
| `@fabric/core` | Git pin `FabricLabs/fabric#feature/rsi` (lockfile SHA `4a1ff0a5707143d965a2da61f700eda4be3a24ae`) |
| `@fabric/http` | Git pin `FabricLabs/fabric-http#feature/rsi` (lockfile SHA `cff2ce66a62d358f709b0150d2574d3910931047`) |
| npm `allow-git` | **`.npmrc` `allow-git=all`** — required for nested git-dep preparation (commit-SHA fetches of core/http); `root` is insufficient |
| Node | **`engines.node` = `24.15.0`** (aligned with core / http) |
| WebSocket (`ws`) | **Mitigated** — direct + override **`8.21.2`** |
Expand Down Expand Up @@ -63,9 +63,9 @@ Living posture notes for **hub.fabric.pub** (`@fabric/hub` **0.1.0-RC1**). Re-ru
| Device-link linked GET starving the peer | Fixed upstream in `@fabric/http` (keep until TTL; Hub re-exports) |
| Site-login / device-link Origin redeem | Open — inherited from `@fabric/http` (possession proof). Http device-link also allows thin-client Origins on allowlisted hubs; still not a possession proof. |
| Device-link client-supplied nonce | Open — inherited from `@fabric/http` (prefer always-fresh nonce) |
| Device-link FIFO eviction under create flood | Opennit; per-origin quota |
| Device-link FIFO eviction under create flood | Fixedhttp `MAX_SESSIONS_PER_ORIGIN`; Hub re-exports |
| Identity import / stronger at-rest crypto | Deferred — heavy lift |
| Large WIP split into stacked PRs | Open — process |
| Large WIP split into stacked PRs | Open — process ([#15](https://github.com/FabricLabs/hub.fabric.pub/pull/15) merged; remaining RSI is follow-up PRs) |
| Fabric hallmarks (opt-in OP_RETURN) | In tree — Hub publish/scan + docs; regtest-only |

## Disclosure
Expand Down
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
All notable changes to **hub.fabric.pub** (Fabric Hub) are documented here. RCs are coordinated with **`@fabric/core`** and **`@fabric/http`**.

## [Unreleased]
- **Chat shoutbox:** WebRTC → Fabric TCP `P2P_RELAY` inner chat is UTF-8 text (legacy JSON `{ object.content }` envelopes are unpacked). Registry marks `P2P_CHAT_MESSAGE` as `utf8-text`; author is AMP signature.
- **IdentityCrossSign:** `functions/identityCrossSign.js` / `identityCrossSignVerify.js` re-export `@fabric/core`.
- **GetDocument Buffer JSON:** `Filesystem.readFile` returns a Buffer; Hub now JSON-parses Buffers (same as strings) so held files expose `contentBase64`. The old `typeof raw === 'string'` branch returned the Buffer as the document, which made document-network / fabric-epic e2e wait helpers report `no document`.
- **Shared-mode http fallbacks:** when `@fabric/http/functions/httpSharedMode` omits `isHttpSharedModeEnabled` / `resolveHttpListenHost` / `DEFAULT_HTTP_LISTEN_ENV_KEYS`, Hub uses the local implementations (not only `applySharedModeWebsocketGate`). Local listen-host matches http: constructor `host` beats inherited `INTERFACE`.
- **BIP-69 PSBTs:** unsigned Hub builders sort vin/vout with `@fabric/core/functions/bip69` before the first signature (crowdfund payout, original Payjoin PSBT, Bitcoin `_buildPSBT`). Payjoin ACP still appends the Hub input and does not reorder signed slots.
- **Operator Accept:** `AcceptTrackedApplicationContract` / `RejectTrackedApplicationContract` verify Schnorr `OP_IDENTITY`/`admin` tokens against Hub `_rootKey` **or** the Fabric Peer identity (`agent.key`). Local developer `FABRIC_XPRV` is the production publisher. Missing token → `adminToken required`; bad signature → `adminToken invalid`.
- **Peer / Bitcoin debug flood:** do not `console.debug` every Fabric Peer `debug` event, and do not `console.log` Bitcoin `debug`, unless `settings.debug` / `FABRIC_HUB_DEBUG`. Truncate at 2 KiB. Do **not** force `bitcoin.settings.debug = true` after start (that re-enabled per-RPC debug strings on production). Lightning `debug` is gated the same way.
- **Bulk security-advisory ingest:** drop OpenSSF / GHSA malware dumps (`security_advisory`, `@zalastax/nolb-*`, “Malicious code in @…”) on `CreateDocument`, P2P file receive, inventory resync, and `recordActivity`. JSON arrays of advisory objects recurse (cap 32). Those Internal-message floods OOMed production Hub. Pair with `@fabric/http` Internal-log + commit snapshot cuts.
- **Chat shoutbox:** WebRTC → Fabric TCP `P2P_RELAY` inner chat is UTF-8 text (legacy JSON `{ object.content }` envelopes are unpacked via `@fabric/core/functions/fabricChatText`). Registry marks `P2P_CHAT_MESSAGE` as `utf8-text`; author is AMP signature.
- **Identity paths:** Hub re-exports `fabricIdentityAccountPath` (core helper when the pin exports it). Receive leaf stays `fabricIdentityDerivationPath`.
- **Identity cluster:** ingest keys must be compressed or x-only hex (`pubkeyXOnly`); colon-smashed strings are `invalid pubkey`.
- **Beacon:** Hub ready-round retry tests match core recovered-witness checks (real Schnorr; fake `'00'` is rejected).
- **Device link:** per-origin create quota is in `@fabric/http` (`MAX_SESSIONS_PER_ORIGIN`); Hub re-exports it from `functions/fabricDeviceLink.js`. Browser device-link fetch omits client-set Origin/Referer.
- **Site login:** expired `GET /sessions/:delegationToken` requires matching `Authorization: Bearer` (http pin).
- **Pins:** `@fabric/core` lockfile `1fc616492…` (first-tier RC1 contract, undersize AMP drop, IdentityCrossSign kind/id guards, canonical `host:port` dial keys); `@fabric/http` lockfile `852520a…` (`pubkey@` strip + dedicated-NIC `:7778`→`:7777`, `--wallet -p` is not a path, Bearer path-token). `package.json` stays on `#feature/rsi`. `report:install` wipes the lockfile then `npm i --allow-git=all`.
- **Pins:** `@fabric/core` lockfile `f1b5e147` (MuSig2 `autoAccept` default off, BIP-21 `req-*`, collection cwd-containment, Codacy `Number('…')` literals; plus MuSig2/BIP helpers, IPv6 candidate `[host]:port`, generic-message debug exact-match, exported `fabricIdentityAccountPath`, AMP wire name wins over inventory JSON `type: 98`); `@fabric/http` lockfile `ff781c5` (nested core on GitHub still `9306aba05` until http is pushed; 402 blob-id omit, CLI `fabricHomeEnv` before `Environment.start()`, Internal-log/commit snapshot cut, `fabricChatText` re-export, `pubkey@` strip + dedicated-NIC `:7778`→`:7777`, unicast `FABRIC_INTERFACE` sibling-NIC self-filter, Bearer path-token, device-link DELETE cancel on local tree). `package.json` stays on `#feature/rsi`. `report:install` wipes the lockfile then `npm i --allow-git=all`. `scripts/hub.js` loads `~/.fabric/env` via `fabricHomeEnv` (process env wins). Playnet `loadPeerKeySettings` rethrows unless `MODULE_NOT_FOUND`; fallback keeps raw `FABRIC_SEED` hex as `{ seed }`.
- **Managed Bitcoin:** Hub RPC probe keeps cookie credentials (no longer a port-only stub that 401s an orphan `bitcoind`). Spawn early-exit / datadir lock **attaches** to the live node instead of `pm2` crash-looping. Shutdown kills only a Core this process spawned.
- **Playnet:** `npm run playnet:status -- --production` reports native `fabric-beacon` plus an optional sibling GoonCitizen contract id. Deploy the application namespace from GoonCitizen: `npm run playnet:deploy-gooncitizen -- --production --accept`.
- **SPA assets:** `assets/scripts/assets/manifest.json` loads the same-origin `/bundles/browser.min.js` (no remote `fabric.pub` script URI).
Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ Hub admin capabilities (Beacon accept, generateblock, wallet spend, **regtest fa
- `bitcoinClient` attaches `hubAdminToken` only for Hub `/services/bitcoin` bases; explorer/payments URLs use `apiToken` only.
- **Fabric hallmarks** (`bitcoin.hallmarks` / `FABRIC_HALLMARKS`) spend wallet UTXOs when enabled (regtest); off by default. Commitment digests are public on L1 by design — never enable on mainnet until fee/policy review.

## Outstanding (PR #15 / RSI follow-ups)
## Outstanding (PR #16 / RSI follow-ups)
- **Identity import** — xprv imports should persist through the encrypted identity path (not watch-only `id`/`xpub`) and restore locked/unlocked per password flow (heavy lift; extension sync no longer writes unlocked `xprv`/`masterXprv`).
- ~~**Encrypted backup export**~~ — primary “Download encrypted backup” requires an unlocked signing `xprv` (watch-only disabled + labeled).
- **Large WIP split** — PR #15 still spans far more than review-tool limits; land remaining RSI work as stacked PRs (identity, Bitcoin/HTLC, WebRTC, docs).
- **Large WIP split** — [PR #15](https://github.com/FabricLabs/hub.fabric.pub/pull/15) merged; remaining RSI (identity import/KDF, login redeem) continues on [PR #16](https://github.com/FabricLabs/hub.fabric.pub/pull/16).
- **`GenericMessage` / WS** — see [MESSAGE_TRANSPORT.md](MESSAGE_TRANSPORT.md); prefer named AMP types on public hubs.
- ~~**`@fabric/core` / `@fabric/http` pin hygiene**~~ — pins: core `1fc616492428ec6e8c731e3afb74fd841407aa0e`, http `852520a2bd1070bb974b1a34297811f3c63588eb` (refreshed via `feature/rsi`; `report:install` wipes the lockfile then `npm i --allow-git=all`). Keep `package.json` on moving `feature/rsi` during RSI; re-pin releases to lockfile SHAs.
- ~~**`@fabric/core` / `@fabric/http` pin hygiene**~~ — pins: core `4a1ff0a5707143d965a2da61f700eda4be3a24ae`, http `cff2ce66a62d358f709b0150d2574d3910931047` (refreshed via `feature/rsi`; `report:install` wipes the lockfile then `npm i --allow-git=all`). Keep `package.json` on moving `feature/rsi` during RSI; re-pin releases to lockfile SHAs.
- ~~**Fabric coin types**~~ — `functions/fabricAccountDerivedIdentity.js` uses core `fabricIdentityDerivationPath` (default **7778**; optional `mainnet` / **7777**). Wire Hub UI / bitcoin network into that optional arg where product wants mainnet identity paths.
- **Site-login / device-link Origin gates** — inherited from `@fabric/http` (forgeable Origin/Referer for session/device-link redeem on shared hosts; Hub self-sign is opt-in + loopback-only in http). Site-login uses `clientMayPollDesktopSession`. Device-link uses that plus thin-client Origins on allowlisted hubs (`clientMayAccessDeviceLink`, re-exported from `functions/fabricDeviceLink.js`). Not a possession proof. Prefer possession proofs before treating QR `sessionId` as browser-grade auth; cleartext production hubs are no longer default-allowlisted. Per-origin device-link create quota is in http. Remaining coordinated follow-ups: always-fresh device-link nonce, bind `sessionId` into link messages.
- **Payment test route** — Hub defaults `exposePaymentTestRoute` **off**; set `FABRIC_HTTP_PAYMENTS_EXPOSE_TEST_ROUTE=1` (or legacy `FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE=0`) when needed for local 402 checks.
Expand Down
147 changes: 147 additions & 0 deletions assets/passport-privacy.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<!-- Copied from @fabric/passport store/privacy.html — keep in lockstep. -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Privacy Policy — Fabric Passport</title>
<style>
:root { color-scheme: dark; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
line-height: 1.55;
background: #0b0f1a;
color: #e5e7eb;
}
main { max-width: 44rem; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; }
h1 { font-size: 1.75rem; margin: 0 0 0.35rem; color: #f9fafb; }
h2 { font-size: 1.15rem; margin: 2rem 0 0.6rem; color: #f9fafb; }
p, li { color: #d1d5db; }
.meta { color: #9ca3af; font-size: 0.95rem; }
a { color: #2dd4bf; }
table { border-collapse: collapse; width: 100%; font-size: 0.92rem; margin: 1rem 0; }
th, td { border: 1px solid #1f2937; padding: 0.5rem 0.6rem; vertical-align: top; text-align: left; }
th { background: #111827; color: #f9fafb; }
.limited {
border: 1px solid #134e4a;
background: #042f2e;
padding: 0.9rem 1rem;
border-radius: 8px;
}
</style>
</head>
<body>
<main>
<h1>Privacy Policy — Fabric Passport</h1>
<p class="meta">
Product: Fabric Passport (<code>@fabric/passport</code>), a Chromium extension<br />
Operator: Fabric Labs<br />
Effective: 15 August 2026<br />
Contact: <a href="mailto:security@fabric.pub">security@fabric.pub</a>
</p>
<p>
This policy describes how Fabric Passport handles information on your computer and what, if anything, leaves the extension. It is written to satisfy the Chrome Web Store User Data Policy, including Limited Use.
</p>
<p class="limited">
<strong>The use of information received from Google APIs will adhere to the Chrome Web Store User Data Policy, including the Limited Use requirements.</strong>
</p>

<h2>1. What this extension is</h2>
<p>
Fabric Passport is a local cryptographic identity wallet. It lets you create or restore a Fabric identity, sign in to Fabric websites (client-signed Hub sessions), mutually link devices, and use Bitcoin keys derived from that identity against a Fabric Hub you choose.
</p>
<p>
Fabric Labs does not operate a cloud account for Passport. There is no Passport signup with us. We do not receive your seed phrase, wallet password, or private keys.
</p>

<h2>2. Information handled on this device</h2>
<p>
The following is stored in Chromium extension storage on your machine (encrypted at rest with AES-GCM where the implementation encrypts wallet material):
</p>
<table>
<thead>
<tr>
<th>Category</th>
<th>What</th>
<th>Leaves this device?</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authentication information</td>
<td>Wallet password verifier; encrypted seed / extended keys</td>
<td>No (unless you export a backup yourself)</td>
</tr>
<tr>
<td>Personally identifiable information</td>
<td>Fabric public key / identity id; optional display labels you type</td>
<td>Public key is sent only to Hubs you connect to or sites you approve</td>
</tr>
<tr>
<td>Financial and payment information</td>
<td>Bitcoin addresses and xpub derived from your seed; optional Lightning invoice you choose to pay</td>
<td>Sent only to the active Fabric Hub you configured, when you use Wallet features</td>
</tr>
<tr>
<td>User activity</td>
<td>Last-activity timestamp; coarse request counts and Content-Length sums for trusted Hub origins only</td>
<td>No — kept in memory / local storage for the extension UI</td>
</tr>
<tr>
<td>Web history (narrow)</td>
<td>Origins of Fabric Hubs you Connect &amp; register; origin + session id of a site-login or device-link you are asked to approve</td>
<td>Shown to you in the approval UI; Hub origin list stays local unless you use that Hub</td>
</tr>
</tbody>
</table>
<p>
Passport does not collect health information, location, general browsing history, page HTML, cookies from unrelated sites, or advertising identifiers.
</p>
<p>
The content script runs on HTTPS pages (and listed loopback Hub ports) so a Fabric site can postMessage a login or device-link request. It does not scrape the page. It ignores messages that are not Fabric site-login, device-link, or Hub mesh-bridge messages.
</p>

<h2>3. Information sent to other computers</h2>
<p>Passport talks to the network only when you use a feature that needs it:</p>
<ol>
<li>A Fabric Hub you choose (default public hub <a href="https://hub.fabric.pub">https://hub.fabric.pub</a>, or a local/self-hosted Hub, or another allowlisted HTTPS hub). Examples: network status, WebRTC peer registration, client-signed session signatures, device-link signatures, optional Bitcoin or Lightning calls through that Hub’s HTTP API.</li>
<li>The website that asked you to sign in, only after you click Approve &amp; sign (or Approve &amp; link). The payload is a BIP340 signature plus public identity material — not your seed.</li>
<li>Chrome / Google as part of installing or updating the extension from the Chrome Web Store. Fabric Labs does not receive that telemetry.</li>
</ol>
<p>
Cleartext http:// is allowed only for loopback (localhost / 127.0.0.1) so you can develop against a local Hub. Public hubs must be HTTPS.
</p>
<p>
We do not sell user data. We do not use user data for advertising, credit-worthiness, or lending. We do not transfer user data to data brokers.
</p>
<p>
Hubs you connect to are independent operators. Their own privacy practices apply to what they log. Fabric Labs operates hub.fabric.pub; other hosts are not this extension’s publisher.
</p>

<h2>4. Why we request permissions</h2>
<p>
Permissions exist so Passport can keep keys on-device, show an approval prompt, inject a small content script for Fabric postMessage, keep a WebRTC offscreen document alive for Hub mesh, and (only after Connect &amp; register) attach a public X-Fabric-Identity header on requests to that Hub.
</p>

<h2>5. Retention and deletion</h2>
<p>
Local data remains until you remove the identity in Passport settings, clear extension storage, or uninstall the extension. Uninstalling Passport deletes Chromium extension storage for this item on that profile. We cannot remotely delete keys we never received.
</p>

<h2>6. Children</h2>
<p>Passport is not directed at children under 13 (or the applicable age in your country). Do not use it to store a child’s identity.</p>

<h2>7. Changes</h2>
<p>
Canonical source: the <code>@fabric/passport</code> repository (<code>docs/privacy-policy.md</code> and this page). Hosted copy: <a href="https://hub.fabric.pub/passport-privacy.html">https://hub.fabric.pub/passport-privacy.html</a> after Hub deploy.
</p>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<h2>8. Contact</h2>
<p>
Security and privacy: <a href="mailto:security@fabric.pub">security@fabric.pub</a><br />
Source and issues: <a href="https://github.com/FabricLabs/fabric-browser-extension">github.com/FabricLabs/fabric-browser-extension</a>
</p>
</main>
</body>
</html>
9 changes: 9 additions & 0 deletions components/ActivityStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,15 @@ class ActivityStreamElement extends React.Component {
)}
</div>
<div style={{ marginTop: '0.5em' }}>
{showChatChrome && (
<div style={{ marginBottom: '0.5em', color: '#666', fontSize: '0.85em' }}>
<strong>Public mesh shoutbox</strong>
{' — '}
cleartext <code>P2P_CHAT_MESSAGE</code> flood (relays can read).
Confidential traffic uses onion-sealed <code>SendOnion</code> or sealed GroupChat on contracts;
peering/gossip stays the primary mesh fabric.
</div>
)}
{showChatChrome && (meshStatus || chatDebug) && (
<div style={{ marginBottom: '0.5em', color: '#666', fontSize: '0.85em' }}>
WebRTC Debug: mesh connected {meshStatus && Number.isFinite(meshStatus.connected) ? meshStatus.connected : 0}
Expand Down
Loading
Loading