Skip to content

Security

Security you can read.

What Tessera does with your secrets, and the file that does it. Where this page and the code disagree, the code is right; tell us.
tess vault status printing the vault path, file size, the wrap methods in the header, and the keychain state

The vault

A vault is one JSON file. A random 256-bit data encryption key (DEK) seals the account list with XChaCha20-Poly1305 under a fresh 24-byte nonce on every write. The DEK is wrapped once per unlock method and every wrap sits in the file, so one vault can carry a Secure Enclave wrap for the app and a passphrase wrap for the CLI at the same time. Adding or removing a method re-wraps the DEK; the payload is not touched.

Before encryption the account list is canonical JSON: keys in UTF-8 byte order, Go's string escaping with HTML escaping off, no whitespace, integers only. Secrets are raw bytes in that payload; base32 exists only at otpauth import and export. Readers reject duplicate keys, base64url, missing padding, unknown versions and tampered ciphertext.

Vault format, rendered · spec/vault-format.md · go/internal/vault/vault.go · swift/Sources/TesseraCore/Vault.swift

Unlocking

Passphrase: argon2id at 128 MiB, t=3, p=4 with a 16-byte salt derives the wrap key. Go uses golang.org/x/crypto; Swift uses the vendored PHC reference implementation (portable C, no SIMD), and a known-answer test keeps the two equal. Cost parameters read from a file are bounds-checked before the KDF runs.

Secure Enclave, app only: a non-extractable P-256 key created with privateKeyUsage wraps the DEK through HKDF-SHA256 over a self key agreement. Require Touch ID adds biometryCurrentSet, so the wrap stops opening after a fingerprint change. Set a recovery passphrase in Settings and the same file gains an argon2id wrap, which is also how the CLI opens an app-created vault.

Keychain, CLI only and opt in: tess vault remember stores the passphrase in the macOS login keychain after verifying it against the vault; tess vault forget removes it. The entry is protected at the same level as an ssh key on disk. Scripts can set TESSERA_PASSPHRASE instead.

swift/App/Sources/SecureEnclaveWrap.swift · swift/Sources/CArgon2 · go/internal/keychain

What the app is allowed to do

Three entitlements: App Sandbox, user-selected files for the open and save panels, and app-scoped bookmarks so a vault you opened once (for example the CLI's file) reopens without a prompt. There is no network entitlement. On-screen QR scanning uses ScreenCaptureKit and asks macOS for Screen Recording permission at the moment you scan. Auto-launch, when you turn it on, registers a login item.

The privacy manifest declares the required-reason APIs the app uses. The Mac App Store privacy label reads Data Not Collected. The clipboard is marked concealed when the app copies a code, and the window is excluded from screen capture.

Tessera.entitlements · PrivacyInfo.xcprivacy · App Store listing

What the CLI touches

tess reads and writes the vault at $TESSERA_VAULT or ~/.local/share/tessera/vault.json and holds a lock file next to it for the length of a command, so two tess processes cannot interleave writes. The Go module imports golang.org/x/crypto, cobra, the terminal and QR packages; it imports neither net nor net/http.

Commands that print secrets are explicit and say so in their help: tess export --uri, --secret, --qr and tess show --secret, --uri. Colored output turns off when piped or when NO_COLOR is set.

go/go.mod · go/internal/store · CLI reference

Two implementations, checked against each other

The Go CLI and the Swift app are separate codebases. spec/testvectors.json and spec/canonical_edge.json pin envelopes, wraps, codes and escaping edge cases; the Go tests, the swiftc verifier and the XCTest suite all run against them on every push, including a full decrypt of a Go-written vault from Swift with real argon2id.

.github/workflows/ci.yml · spec/testvectors.json · swift/Tools/verify/main.swift

Out of scope

This website, and attacks that need root or physical access to an unlocked machine. An attacker who can read your unlocked session can read your codes, in Tessera and in every other authenticator.

SECURITY.md

Reporting

Report privately through GitHub Security Advisories or security@ibrahemid.com. You get an acknowledgment within 48 hours and a fix or a status update within 7 days, and credit in the release notes unless you prefer otherwise. Do not open a public issue for anything exploitable.