Cairn
Security & Architecture Disclosure
Cairn is engineered as a local-first utility for archiving Claude conversations. This disclosure describes the manifest that ships in v0.3.1 verbatim, and clearly marks any v1.0 capability that is not yet in the shipping package. If a claim here disagrees with the installed manifest, the manifest wins and this page is wrong.
1. Manifest scope & permissions held (v0.3.1)
The v0.3.1 manifest holds exactly four permissions, no host_permissions, and no declared content_scripts:
"permissions": ["activeTab", "scripting", "downloads", "sidePanel"]
// no host_permissions key
// no content_scripts key
// "externally_connectable": { "matches": [] }
activeTab— granted on the single tab the user gestured on (toolbar click). No background or pre-load access.scripting— required by the Chrome API surface to callchrome.scripting.executeScript. This is the post-gesture injection mechanism that delivers the one fetch toclaude.ai. Withoutscripting,activeTabgrants the access but the call fails. This pair is the minimum-permission combination that delivers an export at all.sidePanel— renders the export UI in the browser side panel.downloads— writes the formatted bytes (.json,.md, artefact zip) to the user's local Downloads folder.
Why no host_permissions: because the fetch is injected on-gesture into the active tab via chrome.scripting.executeScript, the extension does not need a static host scope and does not auto-load on any page. The v0.3.0 → v0.3.1 change dropped host_permissions: ["https://claude.ai/*"] entirely. The install-time prompt is now "manage your downloads" only; the "Read and change your data on claude.ai" line is gone.
Permissions categorically rejected: nativeMessaging, tabs, cookies, webRequest, declarativeNetRequest, identity, storage (unlimited), any content_scripts declaration, and any broad host_permissions pattern. The build-time check at ops/scripts/cairn-verify.sh rejects any manifest that adds any of them.
2. Network containment
- Native messaging: absent. Cairn contains zero native binary execution capabilities and cannot communicate with host-level processes or daemons. The
nativeMessagingpermission is not requested. - External connections:
"externally_connectable": { "matches": [] }. Cairn silently drops all inbound messages from web pages outside the extension's own components. - The single authenticated fetch: when the user clicks an Export button, Cairn injects a one-shot
fetch()against the Claude page's own API (/api/share/<id>,/api/shared_conversations/<id>, or/api/organizations/<org>/chat_conversations/<id>) — the same authenticated, same-origin call the Claude web app already makes. There is no telemetry, no analytics, no remote configuration, no third-party SDK that opens a network handle. The one call toclaude.aiis the entire network surface of the export operation. - Extension-pages CSP: the manifest declares
connect-src 'self'for the extension's own HTML pages (side panel, library tab). This blocks any side-panel JavaScript from contacting any origin other than the extension itself. It does not govern the on-gestureexecuteScript-injected fetch, which runs inside the Claude tab's own credential context and is bound by that page's same-origin policy. Both layers are honest; this is what the manifest actually does. - License backend (arriving in v1.0 paid tier, not in v0.3.1): when the v1.0 KnowledgeBank paid tier ships, license validation will hit a single Blacktrace-operated endpoint at
cairn-license.blacktrace.co(Cloudflare Worker). That endpoint sees only the Stripe customer id and email at checkout and issues a signed RS256 JWT; it never sees any conversation content, any KnowledgeBank index row, or any other user data. The local-first claim about conversation data still holds. v0.3.1 does not contact this endpoint at all — the auth surface arrives with v1.0. The endpoint will be documented at /format when it goes live.
3. Storage (v0.3.1) & cryptography (arriving in v1.0)
v0.3.1 storage, as shipped:
- Exported artefacts: files written via the browser's Downloads API land as plaintext on the user's local disk. This is by design — the user can read them in any text editor, search them with grep, version them in git. Cairn does not encrypt the user's own filesystem.
- No persistent local index in v0.3.1. The shipping v0.3.1 package contains no OPFS write path, no key derivation, no ciphertext envelope, and no
storagepermission beyond the manifest's session/local defaults. Inspectingchrome://extensions→ Cairn → storage after a v0.3.1 export will show nothing persistent because nothing is written.
KnowledgeBank index (arriving in v1.0, not in v0.3.1): the design and reader-format are already public at /format so an auditor can verify the implementation when v1.0 ships. The intended shape:
- Per-row OPFS append into
cairn/library.jsonl.encusing AES-256-GCM authenticated encryption with the AAD literal"cairn/v1/library/row". - Key derived from the user's passphrase via Argon2id (t=3, m=64 MiB, p=4) with a 16-byte salt at
cairn/library.salt.binand KDF parameters atcairn/library.kdf.json. The full envelope spec is documented at /format. - Key cached in
chrome.storage.sessiononly — cleared when the browser session ends and when the user clicks Lock library. - Not in the extension package: the passphrase, the derived key, any decrypted index content. The package contains only the WASM Argon2id binary and the encryption-layer JavaScript.
Threat model for the v1.0 index: designed to resist read access by other local processes (malware, forensic tools, evil-maid attacks on a profile directory, shared-machine scenarios, corporate DLP). It is not designed to resist a user who has shared their device passphrase, nor to resist Chrome itself. This section will read as a v1.0 capability description until v1.0 ships; verify against the changelog for the date the v1.0 manifest goes live.
4. What this disclosure does not promise
- It does not promise zero vulnerabilities. It promises that the four largest attack surfaces named in the threat model are closed by manifest-level construction.
- It does not promise immunity to supply-chain compromise of upstream Chrome, Node.js, or Anthropic systems. Cairn's mitigation is the absence of any cross-vendor dependency at runtime.
- It does not promise compliance with any particular regulatory regime as a function of architecture alone. Local-first reduces collection but does not eliminate the operator's APP, GDPR, or CCPA obligations where they would otherwise apply. Customers operating under those regimes should evaluate Cairn against their own privacy impact assessment.
- It does not promise that an MDM administrator who force-installs Cairn cannot configure it against the end-user's interest. Workspace-managed install paths are not supported in v1.0 specifically because that consent inversion was unresolved.
5. Verification
Independent verification of the claims above. If any of these steps return a result different from what this page predicts, this page is wrong and the manifest is right — please report the finding.
- Manifest audit: the canonical v0.3.1 manifest is reproduced byte-for-byte at /dev with a published SHA-256 hash. Compare against an installed copy via
chrome://extensions→ Cairn → Inspect views: service worker → console →chrome.runtime.getManifest(). Expected output:permissionsis exactly["activeTab", "scripting", "downloads", "sidePanel"]with no other keys, nohost_permissionskey at all, nocontent_scriptskey at all, andexternally_connectableis{ "matches": [] }. - Network isolation: open Chrome DevTools → Network tab on a Claude conversation. Trigger a Cairn export. The only network entry should be a single
fetch()to a path underhttps://claude.ai/api/.... Any other host appearing in the Network tab is a finding worth reporting to trace@blacktrace.co. - Offline behaviour: disconnect the host from the network. Open a previously-loaded Claude conversation tab. Triggering Cairn's export should fail at the fetch step with a clear network error and no fallback to a third-party endpoint.
- Permission surface:
chrome://extensions→ Cairn → Site access should read "On click" or be empty for installed sites — because v0.3.1 holds no static host permission. Site access only grants when the user gestures on the toolbar action. If a static host appears, that is a finding. - Build provenance: the zip at /dev is produced by a deterministic build (
ops/scripts/cairn-build.sh) — re-running the script produces the same SHA-256 byte-for-byte. The SBOM is published in the same package.