Skip to content

Secure Files: encrypted .mydt file vault, shared format crate and CLI - #312

Merged
itsmeakhil merged 9 commits into
release-v0.1.16from
features/files
Aug 24, 2026
Merged

Secure Files: encrypted .mydt file vault, shared format crate and CLI#312
itsmeakhil merged 9 commits into
release-v0.1.16from
features/files

Conversation

@itsmeakhil

Copy link
Copy Markdown
Collaborator

Adds Files (Secure Files), an offline encrypted file vault, plus the .mydt format as a standalone crate and CLI.

Import files or folders and they are stored as opaque <32 random hex>.mydt objects in a storage folder you pick. Original names, folder paths, types and contents are encrypted — outside the app the folder is unreadable noise; inside, after unlocking, it looks like a normal file browser.

Format

Each .mydt is self-contained: Argon2id salt/params, a per-file DEK wrapped by the key derived from the master password, and XChaCha20-Poly1305 metadata + payload, with the header bound in as AAD. No index — listing scans the folder and decrypts headers, so a storage folder is portable and self-healing (a copied or corrupt object is reported, never fatal).

Spec: docs/MYDT_FORMAT.md. Implementation lives in crates/mydt — a library plus a mydt CLI (encrypt/decrypt/info/ls) behind --features cli, used unchanged by the desktop app so both sides read and write identical objects.

Desktop

  • /api/v1/secure-files/*: settings, list, import, patch, replace, export, delete, folder rename/delete; a raw-bytes secure_file_read command for previews.
  • After the webview verifies the master password it hands it to Rust once so Argon2id can derive the Secure Files key. That key lives in AppState only and is dropped on lock, including idle auto-lock.
  • Listing keeps a decrypted-metadata cache reconciled by an id-set diff; import encrypts across threads with one directory fsync per batch. Measured at 10k files: import 1.75s, cold list 0.30s, warm list 24ms.

UI

Folder tree, list and grid views (both virtualized), image thumbnails in grid, preview for text/code/images/PDF, export with a plaintext warning, rename/move/replace, and a dismissible warning for unreadable objects. Files up to 20 MB.

Also fixes a latent bug in the shared s3-drive FilePreviewDialog: it used Radix Tooltip without a TooltipProvider, which crashed the dialog subtree for any consumer that did not supply one.

Verification

cargo test (69, including a tamper/truncation/garbage mutation sweep over every byte of the container), mydt crate tests, jest, tsc, i18n:audit, and a manual pass in the desktop app: import, preview, rename, move, replace, export, lock/unlock, and confirming the storage folder holds only masked objects with no plaintext names.

Notes

  • pnpm i18n:sync also backfilled unrelated missing keys across the 26 non-English locales, which is why messages/ is broad.
  • Deferred: drag-and-drop import (Tauri's OS drag-drop would disable the HTML5 drop other tools rely on), multiple storage folders, streaming for files over 20 MB, PDF thumbnails, and a change-master-password flow — which, when it lands, must rewrap every object.
  • The security claims here deserve an independent review before they are advertised.

🤖 Generated with Claude Code

https://claude.ai/code/session_01No7es2PjF9SNQs22RYHcdM

itsmeakhil and others added 3 commits August 22, 2026 22:05
…nd CLI

Adds the Secure Files tool ("Files" in the UI): import files or folders and
store them as opaque `<32 random hex>.mydt` objects in a storage folder the
user picks. Original names, folder paths, types and contents are encrypted;
the app shows them only while the vault is unlocked.

Format (docs/MYDT_FORMAT.md, crates/mydt):
- Self-contained per file: Argon2id salt/params + per-file DEK wrapped by the
  key derived from the master password + XChaCha20-Poly1305 metadata and
  payload, with the header bound in as AAD. No index — listing scans the
  folder and decrypts headers, so a folder is portable and self-healing.
- Extracted into the standalone `mydt` crate: library plus a `mydt` CLI
  (encrypt/decrypt/info/ls) behind `--features cli`, so the desktop app and
  the CLI read and write identical objects. Wired into rust.yml.

Desktop:
- New `/api/v1/secure-files/*` routes: settings, list, import, patch, replace,
  export, delete, folder rename/delete; raw-bytes `secure_file_read` command
  for previews.
- The webview gate hands the verified master password to Rust once per unlock
  so Argon2id can derive the Secure Files key; it lives in AppState only and
  is dropped on lock (idle auto-lock included).
- Listing keeps a decrypted-metadata cache reconciled by an id-set diff, and
  import encrypts across threads with one directory fsync per batch. 10k
  files: import 1.75s, cold list 0.30s, warm list 24ms.

UI:
- Folder tree, list and grid views (both virtualized), preview for text,
  code, images and PDF, export with a plaintext warning, rename/move/replace,
  dismissible warning for unreadable objects. Files up to 20 MB.
- Fixes a latent bug in the shared s3-drive FilePreviewDialog, which used
  Radix Tooltip without a TooltipProvider and crashed the dialog subtree.

`pnpm i18n:sync` also backfilled unrelated missing keys across the 26
non-English locales, which is why messages/ is broad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01No7es2PjF9SNQs22RYHcdM
Grid tiles show the actual image instead of a type icon, Drive-style.

Only the tiles currently on screen are decrypted (the hook is fed the same
visible window the virtualizer computes), at most three at a time, and the
queue re-prioritizes on scroll. Each image is downscaled to 256px and
re-encoded before it is kept, so the plaintext original is dropped right away
and the blob pool stays bounded — 150 thumbnails, oldest off-screen ones
revoked, everything revoked when the tool unmounts on lock.

Cache keys include size and mtime so Replace invalidates a stale thumbnail.
SVG skips the canvas (WKWebView cannot createImageBitmap it) and anything
that fails to decode falls back to its icon without retrying.

Also moves blobMime into lib/secure-files.ts so the preview and thumbnail
paths share one implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01No7es2PjF9SNQs22RYHcdM
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mydevtools-tech Ready Ready Preview Aug 24, 2026 5:04am

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

0 finding(s)

No findings.

itsmeakhil and others added 2 commits August 23, 2026 23:24
Codes were only ever issued at vault setup, so a user who burned all
eight — or set the vault up before codes existed — had no recovery path
left, even though the downloaded code file told them to regenerate from
Settings.

- New Settings card: verifies the master password, generates a fresh set
  and stores it (storing replaces the whole array, which is what
  invalidates the old codes), then shows them once with copy/download.
- GET /api/v1/auth/backup-codes returns {total, remaining} so the card
  can show how many codes are left.
- markBackupCodeUsed now throws on a non-OK response instead of failing
  silently, and the unlock gate burns the code after a successful unlock
  rather than before — a failed burn leaves a retryable code instead of
  spending one on an unlock that never happened.
- Code grid extracted so the setup gate and the settings card render
  codes identically.
- SettingsPage.backupCodes translated across all 27 locales.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XHJKZ77EJdyDQ6gDt7WH6
Adds an Overview entry above the folder tree that swaps the main pane for
storage stats: file and folder counts, content size, true on-disk size with
the encryption overhead called out, a per-type breakdown with counts, sizes
and proportional bars, plus largest and recently added files (each row jumps
to its containing folder). Unreadable objects are surfaced here too.

On-disk size is measured, not estimated. The listing cache now stores each
object's size taken from the handle it already opens to read the header, so
the first scan after unlock gets it for free and later listings cost nothing
extra; write-through keeps it exact across import, rename and replace. Totals
cover readable objects only — unreadable ones stay in `errors` rather than
being folded into the numbers.

Everything else is derived client-side from the file list already in memory
and memoized, so the view stays instant at 100k files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01No7es2PjF9SNQs22RYHcdM
@itsmeakhil
itsmeakhil changed the base branch from main to release-v0.1.16 August 23, 2026 18:43
The Files strings shipped as English placeholders because i18n-sync fills
missing keys by copying en.json — the audit passed on key presence, not on
translation. This replaces all 84 keys per locale with real translations:
navigation label, dashboard card, and the whole SecureFiles namespace.

Plural rules follow each language rather than English: ru/uk/pl/cs carry
few/many, ar carries the full zero/one/two/few/many/other set, and the
languages without a count distinction (ja/ko/zh/tr/vi/id/ms) use a single
other form. Verified by compiling all 1998 messages through
intl-messageformat per locale and formatting them with real arguments, so
every placeholder and plural branch is exercised — "3 файла", "5 plików",
"ملفان", "2 soubory".

Terminology is localised, not transliterated: vault becomes Tresor,
coffre-fort, bóveda, cassaforte, 保管庫, 保险库; storage folder, masked and
overhead follow suit. Quotation marks use each locale's convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01No7es2PjF9SNQs22RYHcdM
@itsmeakhil
itsmeakhil merged commit 9bd7247 into release-v0.1.16 Aug 24, 2026
6 checks passed
@itsmeakhil
itsmeakhil deleted the features/files branch August 26, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant