A relay, not a vault: designing an Android app for the moment it is seized

What a forensic examiner finds on a seized phone, and why the design starts from the assumption that the device is the adversary’s.

Companion to the proof-stack writeup, which covers how the cryptography is verified. This one is the concrete Android attack surface under seizure. Not yet audited by an external party; the repository goes public with the 8.2.5 release.

The scene

An activist films an abuse. Then the phone is taken: at a checkpoint, a border, in custody, or snatched mid-recording. What follows is predictable: the device is seized, the PIN is coerced, and the hardware goes to a Cellebrite or GrayKey bench where storage is imaged and local vaults are brute-forced offline.

Most secure-capture apps answer this by encrypting files on the device. That fits the threat poorly, because a safe can be opened: by brute-forcing the code, by coercing the person who knows it, by exploiting the OS. As long as the data and the key that reads it both live on the phone, the phone is a single point of failure, and it is exactly the thing the adversary is holding.

Frappuccino (a fork of Tella FOSS, Apache 2.0) takes the other route: not a better safe, but no safe at all. The architecture (chunked end-to-end upload to a blind relay while filming, a reading key that exists only on paper, a device that can encrypt but never decrypt its own past) is on the positioning page and proven in the companion writeup. This piece is only the forensic surface that design produces, on the phone and on the server, and it is honest about what leaks.

On the seized phone

At rest: nothing readable without the paper

Whole-device File-Based Encryption is the load-bearing at-rest control, and the design leans on it rather than reinventing it: a forensic image without the device key is ciphertext. On top of that, even with the device unlocked there is no content and no identity to read:

The plaintext window, and how it is closed

There is one unavoidable moment of plaintext: MediaMuxer writes each finished chunk as a clear chunk-NNN.mp4 in cacheDir/stream_chunks/ before the Rust layer encrypts it. After the chunk is finalized, the Rust layer reads it, encrypts it (tens to ~100 ms), and secure-deletes it. Under normal load one clear chunk exists at a time; if encryption threads back up, more than one can briefly coexist, with the worst case bounded by the 5 s rotation cadence. The exact maximum window is an open question in our own design review, not a number I will invent. Closing that window cleanly is most of the on-device engineering:

Heap and crash forensics

A forensic examiner does not stop at the filesystem.

On the seized relay

The server is treated as an adversary from the start: legal seizure, intrusion, a disloyal operator. So it is built to have nothing worth taking.

On the wire, briefly

Uploads go over QUIC wrapped in a Salamander obfuscation layer: a stateless per-packet XOR scrambler (an 8-byte salt plus a BLAKE2b keystream under a pre-shared key), byte-compatible with Hysteria2, so signature-based DPI finds no QUIC or TLS markers to classify. When UDP is blocked, the client falls back to a custom rustls verifier pinned to a small SPKI set we control, which rejects a man-in-the-middle holding a valid CA-signed certificate.

The honest limit, treated at length in the metadata analysis: obfuscation buys inclassifiability, not invisibility. The size and timing envelope is unchanged, and on the TLS fallback the relay’s domain name rides in the clear in the SNI field. Frappuccino hides who you are and what you filmed, not necessarily that or when you uploaded. It is a transport, not a network anonymizer; if the metadata itself endangers you, combine it with Tor or a VPN.

What a seized phone still gives up

The device-specific residuals, stated with the same care as the features (the full list, including the paper-phrase single point of failure and the absence of court-grade chain of custody, is on the positioning page):

Come break it

The attack surface is deliberately small and written down: a brief per-chunk plaintext window closed three ways, an at-rest state with no content and no identity, a relay built to hold nothing that decrypts, and a transport honest about the metadata it cannot hide. The residuals file says where I already think the soft spots are (flash-residual secure deletion, the metadata envelope, the JVM-heap token during upload). The auditor’s guide and the scope-and-invariants document are the place to start, and the proof runners replay from a clean clone.

The repository goes public with the 8.2.5 release. If the model is wrong about what a seized phone gives up, the fastest way to find out is for a forensic examiner to take one apart.