# BACKUP369 file format, version 1

This public specification accompanies BACKUP369 web app 0.1.0. Files use the
`.b369` extension. This is a versioned container using Web Crypto primitives,
not a new cryptographic algorithm. It has automated tests, not an independent
security audit. Keep both a separate copy of the archive and its password.

## Encryption

PBKDF2-HMAC-SHA-256, exactly 600,000 iterations, derives a non-exportable
256-bit AES-GCM key from the UTF-8 password and a fresh random 16-byte salt.
The password is used exactly as entered; it is not trimmed or normalized.
New passwords need at least 12 Unicode code points and at most 1,024 UTF-16
code units. A long unique password is necessary against offline guessing.

All integers are unsigned big endian unless specified otherwise. The fixed
48-byte header contains:

| Offset | Length | Value |
| --- | --- | --- |
| 0 | 8 | ASCII `B369BK01` |
| 8 | 4 | PBKDF2 iterations: 600000 |
| 12 | 4 | File chunk size: 1048576 bytes |
| 16 | 16 | Random salt, using crypto.getRandomValues |
| 32 | 4 | Random nonce prefix |
| 36 | 12 | Reserved, all zero |

Each record is a four-byte ciphertext length followed by ciphertext including
the 16-byte GCM authentication tag. The 96-bit IV consists of the four-byte
header prefix and the record index encoded as an eight-byte integer. Each
record index is used exactly once per archive. New archives always generate
a new random salt and prefix. The authenticated additional data consists of
the entire 48-byte header, eight-byte record index and four-byte plaintext
length. The GCM tag length is 128 bits.

Record 0 encrypts the UTF-8 JSON manifest (maximum 1 MiB). Its fields are:
`format: "BACKUP369"`, `version: 1`, ISO `createdAt`, `totalBytes`, and `files`.
Each file has `path`, byte `size`, Unix millisecond `lastModified` and `type`.
File records start at index 1, in manifest order. Every full chunk is 1 MiB;
the last chunk of each file has its exact remaining size. Empty files have no
data record and are represented by their authenticated manifest entry.

## Validation and recovery

Unknown versions or parameters, oversized records, invalid paths, duplicate
paths (case-insensitive), file/directory conflicts, truncated records and
trailing bytes are rejected. Limits are 2 GiB of plaintext and 2,048 files.
The sum of manifest sizes and exact expected archive size must match.
Absolute paths, traversal, control characters, Windows reserved names and
non-portable path characters are rejected. Paths are normalized to NFC.

An unlocked manifest is a preview, not proof of the remaining file contents.
Restore first authenticates every record in the immutable input Blob before
writing restored data. A second pass writes only selected files. Directory
restores create a newly named child directory and never target existing user
files. An unsuccessful directory restore attempts to remove only that newly
created directory; a cleanup failure is explicitly reported. Direct save
streams are aborted on errors. Download fallbacks are produced only after a
complete successful operation. Restore ZIPs use the standard STORE method
with UTF-8 paths and CRC32 and are **not encrypted**.

File sizes and approximate metadata length are not hidden by padding. The
archive's external filename is visible; choose a neutral backup name.
Original file contents are never modified. Filesystem permissions, symbolic
links and empty directories are not backed up. A browser app cannot copy an
operating system or private application data without a user-provided export.

## Browser boundaries

Direct file saving is feature-detected. Other browsers use a strict 96 MiB
output limit to avoid unbounded memory allocation. Backup processing uses
1 MiB chunks. Restore-to-directory is offered only when the browser supplies
the File System Access API. Otherwise restoration produces a standard ZIP.

The interface stores only language, theme and animation preferences in
localStorage. Files, passwords and decrypted manifests are not persisted by
the app. They exist in browser memory during use. JavaScript garbage
collection cannot guarantee secure erasure. Decrypted previews expire after
five minutes of inactivity. Saved archives are outside browser storage.

The service worker caches only a fixed allowlist of interface assets within
`/backup369/`. It never caches selected files, blob downloads or passwords.
The page denies network connections through CSP. Hosting still processes
ordinary requests for the app. No analytics or remote scripts are included.

## Recovery advice

After saving, open the actual downloaded `.b369` file and run the integrity
check. Keep another copy on separate media. No email login, password reset or
studio-held key can recover an archive if the password has been lost.
