Security model โ Omitly
What Omitly's cryptography and redaction assert, and what they do not, after the
2026-06 crypto audit. The full public threat model is in docs/THREAT-MODEL.md; the
audit report is at ../security/crypto-audit-2026-06/.
The machine-readable cryptographic inventory (CBOM) this document's language is
kept in sync with lives in crypto-bom.yaml; the software bill of materials
(SBOM, including vendored assets like fonts and models) is generated in CI
(../harness/innisfallen-ci/.github/workflows/deps.yml).
Reporting a vulnerability
Email [email protected] with what you found and how to reproduce it. PGP is not yet set up for this address โ if your report is sensitive, say so and we'll arrange an alternative channel before you send details.
- Acknowledgement: we aim to confirm receipt within 5 business days.
- Scope: the Omitly desktop app (macOS, Windows),
omitly-mcp, the redaction leak checker, and this security portal (security.omitly.app). Third-party dependencies we bundle are in scope for how we use them, not for upstream bugs โ please also report those upstream. - Safe harbor: good-faith security research conducted under this policy โ testing against your own installs/data, not accessing anyone else's, and reporting promptly rather than exploiting โ will not result in legal action from us. We ask that you give us a reasonable chance to fix an issue before any public disclosure.
- What to expect after acknowledgement: we'll work to understand and reproduce the report, keep you updated as we do, and credit you (if you'd like) once a fix ships. We don't currently run a paid bounty program.
This is a small team, not a security company with a dedicated PSIRT โ the commitments above are what we can actually stand behind, not boilerplate copied from a larger vendor's policy.
The tamper-evidence seal
Asserts: the delivered PDF โ visible content and the embedded audit report alike โ
has not been altered since it was sealed. The signature is Ed25519 over the entire
delivered file (a trailing %%OMITLY-SEAL appendix); verification recomputes the hash
over the bytes the holder presents and fails on any mismatch (verify_strict).
Does NOT assert โ important: the seal is integrity, not identity. The signing key is per-install, so a valid seal means "unchanged since sealed by the holder of this key", not "produced by Omitly". Anyone can generate a key and self-sign. The verifier surfaces the key fingerprint for out-of-band comparison rather than claiming origin. Do not market the seal as proof of Omitly origin. (Vendor-anchored notarization โ a pinned vendor key, co-signed online โ remains a roadmap option. The related "hash leaves the machine" concession now exists in one shipped form: timestamp anchoring (off by default since omitly#1060, with a remembered opt-in), below โ which proves WHEN, still never WHO.)
Limitation: a downstream PDF normaliser that rewrites the file drops the trailing appendix โ verification reports "no valid seal" (a fail, never a false pass). A PAdES ByteRange in-document signature is the future upgrade.
Timestamp anchoring (off by default, opt-in)
Asserts: when anchoring is enabled for a redaction, an RFC 3161 timestamp token from a
Time-Stamp Authority covers the SHA-256 of the delivered (sealed) file, proving "this
exact sealed document existed at time T". The token is stored in a <output>.pdf.anchor.json
sidecar (raw DER included, so third-party tools like openssl ts can verify it
independently); verification recomputes everything against the bytes the holder presents
and validates the TSA chain offline against roots pinned in the binary via real RFC 5280
path validation (basicConstraints, keyUsage's keyCertSign bit, and
pathLenConstraint are all enforced on every certificate in the chain below the pinned
root โ omitly#615; before that fix, an issuer was accepted on signature + validity alone,
so an ordinary certificate chaining to a pinned root was wrongly accepted as a valid
intermediate CA).
Does NOT assert: identity (the seal's integrity-not-identity doctrine is unchanged),
and revocation of the TSA certificate is NOT checked (no OCSP/CRL โ same disclosed gap
as PAdES). A TSA outside the pinned bundle (e.g. an organization's internal TSA) is
reported as internally-valid-but-unpinned, never silently trusted. A pinned root's OWN
pathLenConstraint (if any) is not enforced โ RFC 5280 ยง6.2 treats a trust anchor's own
extensions as advisory, the same posture nearly every X.509 implementation takes.
Network disclosure: this is a disclosed, named exception to the no-phone-home posture: one outbound request carrying only the 32-byte digest โ never document content.
Default OFF since 2026-08 (omitly#1060), opt-in and remembered. It was default OFF when introduced (#107), then default ON from #561 until #1060. A sealed document cannot be retroactively upgraded to a different signature scheme, so declining this option forgoes the only mechanism that can later witness a classical-era seal as pre-dating a future break. Enabling Timestamp proof is an explicit opt-in persisted across launches; leaving it off makes redaction zero-network and byte-identical to before the feature existed.
Anchoring runs strictly after the delivered file is written, so it can never fail,
alter, or prevent a redaction. It is not free of latency, and this is stated precisely
rather than rounded to "never delays": the request is synchronous, so an unreachable or
hung TSA delays the command's completion (and the UI returning to idle) by up to the
bounded 8-second client timeout. The output file on disk is already complete and correct
before that wait begins. A failure (offline, TSA unreachable, TSA error) produces an honest
"anchored": false record and a visible warning stating that the document was NOT
timestamped while the redaction itself is complete, verified and sealed โ it is never
silently skipped.
Redaction completeness
Asserts: text glyphs are removed from page content streams (not just covered), images intersecting a region are painted/dropped, metadata/XMP/markup annotations are scrubbed, and the output is independently re-verified (geometric re-walk + raw-byte survivor scan).
Default-deny boundary: content rendered from annotation/form-field appearance streams
(/AP), inline images, and exotic constructs (Type3, patterns) is NOT removed by the
page-content rewrite. When such a path intersects a region the result is forced to
all_passed = false with a warning โ Omitly refuses to claim success rather than leak
under a box. Flattening AcroForm appearance streams into page content is a tracked
follow-up (O-3 real fix).
Metadata scrub scope: the metadata scrub removes every object declared as metadata
(/Type /Metadata) and everything reachable via the standard /Metadata key at any
depth. It does not content-sniff arbitrary objects reachable only via a nonstandard key โ
a stream that declares no /Type /Metadata and is referenced only that way is
indistinguishable from ordinary document content. metadata_scrubbed = true claims
"declared metadata is removed," not "no XML anywhere in the file survives." See
omitly#592.
FIPS / PQC posture
- Ed25519 (FIPS 186-5) and SHA-256 (FIPS 180-4) are FIPS-approved algorithms, but
ed25519-dalek/sha2/getrandomare not CMVP-validated modules. Claim "approved algorithms, non-validated implementation" โ not "FIPS compliant". - The seal protects long-lived document integrity, so it is where post-quantum matters.
Decided 2026-07-26 (owner, omitly#533) and published as the dated transition plan
(
docs/PQC-TRANSITION-PLAN.md, rendered at security.omitly.app/pqc): seal envelope v2 ships hybrid deterministic ECDSA P-384 + ML-DSA-87 (FIPS 204, hedged) with mint-time cross-verification against two independent implementations; SLH-DSA (FIPS 205) codepoints are reserved in the envelope registry as the hash-based fallback, not shipped (this supersedes the earlier SLH-DSA-as-target note that previously lived here); Ed25519 is retired from newly minted seals at v2, retained forever for verifying existing seals and in licence signing (documented exception โ vendor-rotatable, no customer data). Policy cutover to ML-DSA-87-required: no later than end-2029, ahead of the ISM's 2030 classical retirement.
Tracked follow-ups
O-4/O-7 (key at rest in the OS keystore + zeroize) SHIPPED for macOS Keychain / Secret Service with a 0600-file fallback; the Windows Credential Manager owner-ACL variant is tracked as omitly#85 (Windows port milestone). W-1 WASM provenance/checksum; W-2 pin the published npm entrypoints; W-3 CSP + analytics off the leak-checker page. See audit ยง8.