trackslash
VAULT-82 P1

Security review: check the app upholds its security guarantees

0
All issues

Description

Why

Vault makes strong promises:

  • duress resistance (MANIFESTO.md C1–C10);
  • on-device encryption with the App Lock Password, duress vaults and erasing after failed passwords (docs/on-device-encryption.md);
  • encrypted backups;
  • it's fully offline.

A lot of this shipped quickly: VAULT-22/23/34, VAULT-70–75, Spotlight (VAULT-72), clipboard and drag (VAULT-64), More Apps (VAULT-76). Since then, nobody has checked the whole app against the whole set of promises. This ticket is that review.

Disclosure: read this first

This project and badbundle/vault-app are both public.

  • Until a fix has shipped, keep exploitable details out of this ticket, its comments, commit messages and PR descriptions. Give PRs neutral titles, as TRACK-88 did.
  • vault-app has no SECURITY.md, and GitHub's private vulnerability reporting is off for the repo. Before starting, decide where exploitable findings are kept and how outside people report one. One option is to turn on private vulnerability reporting and add a SECURITY.md that points to it. That's Bradley's call.

1. Write down the guarantees

Collect every security promise the app makes, from:

  • MANIFESTO.md (C1–C10).
  • docs/on-device-encryption.md: "What's readable at rest", "Residual limits", "Consequences to accept", and the duress vault and erasing sections.
  • The README: the features and tenets, such as "Fully offline, no servers at all", "no binary dependencies" and "old backups should always be able to be restored".
  • The in-app FAQ (VaultSettings/Resources/en.lproj/FAQ-*.md) and the wording on the security screens.
  • Standing decisions that don't come from any doc, such as:
    • App Lock is off by default.
    • Duress setup exists only inside the App Lock Password screen, and nothing shows that a duress vault exists.
    • There's no attempts-left display.

Make one list. Give each entry where it's enforced and the test that pins it, or "none". This could live in docs/security-model.md, like track-slash-app's SECURITY_MODEL.md, or in a section of the encryption doc.

2. Check each one

Cover at least these areas. For TRACK-88, parallel read-only review agents (one per area) worked well.

  • Duress features (C1–C9): killphrases, search passphrases, hidden and locked items, duress vaults, and erasing after failed passwords. Check for:
    • bulk operations;
    • oracles, through errors, timing, logs or differences in the UI;
    • telemetry or logging;
    • enumeration, including through Spotlight, widgets, AutoFill, QuickType and any counters;
    • undo or audit trails, including SwiftData history, undo managers and backup event logs;
    • device authentication treated as a gate, which C4 rules out.
  • Data at rest: inspect a real app container, on the simulator and on a device, with the password on and off. Compare it with the "What's readable at rest" table. Look at:
    • store files and SQLite -wal/-shm;
    • UserDefaults, for the app and the shared group;
    • keychain items and their accessibility classes;
    • caches, temporary files (see VAULT-62 for PDF export) and app switcher snapshots;
    • logs;
    • file protection classes.
  • App Lock and the App Lock Password: the lock and unlock paths, and the Require Unlock delay. Locking when the app goes to the background and when protected data becomes unavailable. How erasing after failed passwords counts attempts, and the waits between them. Whether a duress vault can be told apart from others by payload shape, size or timing. The claims in the encryption doc, such as the slot format and key zeroing where it's claimed.
  • Backups, exports and device transfer (C10):
    • What someone who holds a PDF, the QR codes, an auto-backup file or its folder can learn without the password: sizes (VAULT-75), file names, dates, the hint.
    • How the backup password is stored and derived.
    • Imports of untrusted PDFs and QR codes: malformed or hostile input must fail safely.
    • Per-vault backup settings (VAULT-70) staying within their own vault.
  • System surfaces: anything that reads the vault without going through the lock, such as:
    • the AutoFill extension, widgets and QuickType;
    • Spotlight (VAULT-72);
    • the clipboard: expiry and Universal Clipboard (VAULT-38/64);
    • drag and drop;
    • screen recording and mirroring (VAULT-32), and screenshots;
    • keyboard learning (VAULT-54);
    • App Intents, URL schemes, Handoff and NSUserActivity.
  • Offline and supply chain:
    • No network access anywhere, including the bad-bundle-apps package from VAULT-76 and every other dependency.
    • No analytics or crash-reporting SDKs.
    • No binary dependencies: check Package.resolved and the vendored Argon2 in CArgon2.
    • Entitlements for the app and its extensions no wider than they need.
  • Cryptography: CryptoEngine, VaultKeygen and VaultBackup. Check the algorithms and parameters, randomness, nonce handling, authenticated encryption, constant-time comparisons, and versioning so that old backups still restore.
  • Docs against reality: for example, the README still says "There is purposely no automatic or online backup", but Auto-Backup exists. Correct every claim in the docs or the FAQ that no longer holds.

3. Handle what's found

  • Fix it: one PR per finding, or per small group of findings, with a neutral title and a test that would have caught it.
  • Accept it: add it to "Consequences to accept" or "Residual limits", or to the security model doc, with the reason.
  • If the manifesto itself should change: that's a separate MANIFESTO: PR, following the manifesto's rule for amendments.
  • If it's bigger work: file a follow-up VAULT issue, described neutrally.

Done when

  • The list of guarantees is in the repo. Each entry says where it's enforced and how it's tested.
  • Every area above has been reviewed. A summary comment here says what was checked and what happened to each finding: fixed (with the PR), accepted (with the doc) or followed up (with the issue). It gives no exploitable detail until the fixes have shipped.
  • The README and FAQ match what the app does.
  • The disclosure path has been decided and documented.

Related: VAULT-26 (the on-device encryption design), and TRACK-88 (the same kind of review for Trackslash).

GitHub

0

No branches or pull requests linked.

Comments

1
Bradley

The review is done. Detailed notes are kept outside the repo and the tracker, as the disclosure rule asks.

What was checked. Every area listed in the ticket:

  • the duress features (C1–C9);
  • data at rest;
  • App Lock and the App Lock Password;
  • backups, exports and transfer (C10);
  • system surfaces;
  • offline and supply chain;
  • cryptography;
  • the docs against the code.

Four parallel read-only reviews covered these, and a fifth took the docs. The new tests (VAULT-83) re-checked a good part of it.

Guarantees. docs/security-model.md (#700) lists every promise (G1–G83), with where it's enforced, the test that pins it (or "none") and whether it holds as stated or with a limit. The README and both AGENTS.md link to it.

Fixed, in neutral PRs, each with tests:

  • #673, #674, #675, #677, #678, #680, #681, #682, #683, #685;
  • #686, #687, #688, #689, #690, #693, #698, and one fix within #699.

Accepted and documented (the design doc's "Consequences to accept" and "Residual limits", and the security model's "Accepted limits"):

  • Without an App Lock Password the store is readable and goes into iPhone backups.
  • Killphrases match on the whole search as it's typed, only in the app's own search.
  • Backups made with one backup password share a salt.
  • The per-item key derivation is lighter than the App Lock Password's.
  • Some build tools are prebuilt binaries.
  • The duress nesting limit.
  • Backups now carry the killphrase and passphrase keys.
  • Guessing on the device is limited by the waits.

Follow-ups. None filed. Three MANIFESTO wordings don't quite match the code (C2, C6 and an example in C7). Changing the manifesto needs its own MANIFESTO: PR, so that's left to Bradley.

Disclosure. SECURITY.md asks for reports through GitHub's private vulnerability reporting, which is now switched on for the repo, and never in an issue, a PR or this tracker.

Not done hands-on. Data at rest was reviewed from the code: every file, defaults key and keychain item the app writes, with their protection classes. Inspecting a real app container on a device is still worth doing before the next release. So are the device checks in RELEASE.md (Face ID and the passcode, and the AutoFill sheet locking with the device).