·Security

Security posture, in plain language.

A short, specific account of how Satsignal handles the security-sensitive parts of creating a proof: hashing, third-party code, transport, what’s stored where, and how to report a problem. Where a claim is verifiable, we link to the artifact.

01Defense in depth

Six properties that hold whether or not Satsignal is online.

Hashing happens in your browser

Standard mode never sends your file. The page reads the file locally with the File API, computes SHA-256 (and the format-specific canonical fingerprints) via the browser’s built-in crypto.subtle, and posts only the resulting short hex strings.

Bundles never contain file bytes

Your .mbnt bundle is a small zip with a manifest and a canonical JSON document — no original-file bytes. This is a property of the format, not a server policy: there is no code path that writes file bytes into a bundle.

Third-party code is vendored and pinned

Every JavaScript dependency Satsignal loads is hosted at /static/ on our origin and pinned with a Subresource Integrity (SRI) hash. There is no runtime CDN load. Tampering at the static path or in transit produces a browser-side integrity failure, not a silent compromise.

HTTPS only, with HSTS

Caddy fronts every vhost with TLS from Let’s Encrypt and sets Strict-Transport-Security: max-age=31536000; includeSubDomains. After a first visit, the browser refuses cleartext for the apex and every subdomain.

Content Security Policy

Every page sets a CSP that restricts script-src to our origin (with the SRI-pinned vendored libraries), frame-ancestors to disallow embedding, and connect-src to the public BSV explorers we look transactions up against.

Rate limiting on writeable endpoints

Anchor creation endpoints — POST /notarize (the file-proof form on proof.* / sealed.*) and POST /api/v1/anchors (the bearer-auth programmatic API at app.satsignal.cloud) — are rate-limited per source IP and per API key respectively, to prevent automated abuse and keep operator wallet spend bounded. Magic-link login is also rate-limited per source IP and per recipient address. Verification, receipt fetches, and bundle downloads are not rate-limited; we want anyone to be able to verify any receipt, any time.

Metadata fields (filename, label, memo, matter label) reject the most common AI-tool control sequences (e.g. <system-reminder> and a handful of related tokens, after Unicode and HTML-entity normalisation), reject C0/C1 control characters and Unicode format characters at submit time, and have server-side length caps. This narrows a known prompt-injection vector against any downstream agent that reads receipts later, but it is defense-in-depth, not a wall: novel control-channel formats and prose-form directives will pass. Consumers that feed receipt fields into an LLM should treat those fields as untrusted text (wrap in <untrusted-field> envelopes or sub-agent isolate). None of this affects the file you’re notarizing.

02Third-party code, line by line

Exactly what JavaScript loads, and from where.

Each library is vendored verbatim from its canonical npm-published source, served from the same origin as the page that uses it, and loaded with a integrity="sha384-…" attribute. The browser will refuse to execute a file whose hash doesn’t match.

jspdf.umd.min.js (jsPDF 4.2.1)
Renders the printable PDF receipt browser-side. No server-side PDF code path.
jszip.min.js (JSZip 3.10.1)
Reads .mbnt bundles in the verifier and packages the proof-package zip on the receipt page.
pdf.min.mjs + pdf.worker.min.mjs (PDF.js 5.7.284)
Extracts text from PDF inputs in the browser to compute the content-canonical fingerprint. Runs entirely in a Web Worker; isEvalSupported:false is set explicitly on getDocument() so untrusted PDFs cannot reach the eval-based renderer fallback. The file never leaves the page.
qrcode.js (qrcode-generator 1.4.4)
Encodes the receipt URL as a QR code on page 1 of the printable PDF. Vendored unminified on purpose: the jsdelivr-minified build is dynamically generated and explicitly NOT SRI-stable. The original npm-published source is.

The verifier at /verify is itself a single static HTML file. You can save it, audit it, and run it offline against any saved bundle.

03What’s stored, where, and for how long

Two short lists.

What we keep

  • Your bundle (manifest + canonical doc, never file bytes), at /bundle/<id>.mbnt
  • Sealed-mode bundles submitted with the mirror option: same shape, plus the master salt; auto-deleted after the retention TTL printed on the receipt. Blind sealed submissions are never stored on our server.
  • One HTTP access-log line per request — timestamp, source IP, path, status — rotated automatically
  • The bundle ID itself, so the URL keeps resolving

What we don’t keep

  • Your file’s bytes — in standard mode, they never reach the server
  • Filename or memo on the public chain — never. If you provide either, it lives in the receipt bundle on our server (and in your downloaded copy), but never gets written to chain
  • Per-IP rate-limit counters across service restarts — in-memory only
  • Any index keyed by IP, hash, or filename — bundles are addressable only by their opaque ID

The Bitcoin SV chain entry is permanent regardless. It contains a 20-byte commitment plus a small structural marker; nothing personally identifying. If you need a server-side bundle removed, ask — we’ll delete it (blind sealed submissions never had one). The on-chain anchor will remain; that’s the property that makes the proof tamper-evident.

04Reporting a vulnerability

Tell us before you tell anyone else.

Email hello@satsignal.cloud with a description of the issue, steps to reproduce, and any proof-of-concept artifacts. We aim to acknowledge within a few business days. Use a non-Satsignal address (don’t put a sensitive PoC in our hash-lookup endpoint).

We follow ordinary responsible-disclosure norms: we’d rather fix and ship before public disclosure, and we’ll credit you in the fix commit and a brief note here unless you prefer otherwise. No bounty program at this size.

Triage notes:

  • Theoretical attacks on the Bitcoin SV chain itself (chain reorgs, miner collusion, the chain ceasing to operate — those are not Satsignal’s posture to defend).
  • Issues that require a compromised endpoint (your laptop, your browser extension) — we can’t protect what we don’t see.
  • Operator wallet, signing-key, and server-access issues are handled as infrastructure-security reports rather than client-data privacy reports. Customer files never reach our servers in either mode, but operator key compromise could affect service integrity and spend controls — mail those to the same address and we’ll triage as ordinary security-sensitive issues.
  • Generic findings from automated scanners with no concrete impact — we welcome them but they’re lowest priority.