AUTH-ADR-0032 — External File Inputs Are Treated as Hostile
Publication note: This is the complete decision for the shipped encrypted-backup input path. References to an unreleased migration input have been withheld until that capability ships.
Date: 2026-05-16 Status: Accepted Decision authority: Attomus Security Architecture Triggered by: Security post-mortem (unbounded backup import and hostile-file handling)
Context
Both the iOS and Android backup import paths had strong cryptographic validation — canonical Argon2 parameter enforcement, AEAD authentication, header magic checks — but both read the entire user-selected file into memory before any of that validation ran.
A malicious or accidental .attomusauth file of arbitrary size (hundreds of MB) could exhaust
app memory at file-selection time, before any header parsing or passphrase entry. This was a
local DoS on an attacker-controlled input boundary.
The underlying mistake was treating file size as the parser’s problem. The parser was safe; the allocation that fed it was not.
Decision
Every user-selected file input is treated as hostile. A hard size cap is enforced before any allocation, before any parsing, before any header read.
Rule 1 — Size cap before read
For any file read from user input (SAF on Android, document picker on iOS, file open on any
platform), the size must be checked or bounded before readBytes() / Data(contentsOf:) /
equivalent is called.
Preferred order:
- Query the declared file size from the OS (SAF
OpenableColumns.SIZE/ iOS file attributes). If declared size exceeds the cap, reject immediately without reading. - Read with a hard byte limit. Do not read beyond the cap even if the declared size was unavailable or returned −1.
Both checks should be applied. Declared sizes from SAF providers are not reliable.
Rule 2 — Current size caps
| File type | Cap | Rationale |
|---|---|---|
.attomusauth backup |
1 MiB | Header is 28 bytes; realistic export is < 100 KiB |
| QR code input | in-band | Bounded by camera frame; no file read involved |
These caps are intentionally conservative. A future format revision that legitimately exceeds them requires a new ADR entry, not a silent increase.
Rule 3 — Parser-level caps do not replace read-level caps
Strong header/parameter validation (Argon2 cap enforcement, canonical field checks) must be retained and is the second line of defence. It does not replace the read-level cap — both must be present. The read-level cap protects memory allocation; the parser-level cap protects CPU and key derivation.
Rule 4 — Rejection before parsing
If the size cap is exceeded, the import must be rejected with a user-visible error before any parsing attempt. The file bytes must not be retained in memory. The error message must not confirm whether the file is a valid backup (“This backup file cannot be opened.” — not “Backup file too large” which confirms format knowledge).
Consequences
- Both iOS and Android backup import paths are updated to enforce the 1 MiB cap before reading.
- Any future file input added to either app requires a cap documented in this ADR before
the feature may merge to
main. - Code review checklist: any
readBytes()/Data(contentsOf:)/input.read()on user-supplied input without a preceding size check is a review flag and a merge blocker.
References
- Android security-review finding
3f74521108— unbounded backup import - iOS security-review finding
f23b6a7e— backup import accepts unbounded KDF and file sizes