OverviewSignetSemaForeCuriousLee
Assurance record Published engineering decision

Hostile file input

A public extract of the accepted engineering decision governing hostile file inputs in Attomus Signet.

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:

  1. Query the declared file size from the OS (SAF OpenableColumns.SIZE / iOS file attributes). If declared size exceeds the cap, reject immediately without reading.
  2. 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