Skip to content

fix(recover): wrap embedded-manifest atob in try/catch for corrupt base64#154

Merged
eljojo merged 1 commit intoeljojo:mainfrom
tmchow:osc/107-corrupt-manifest-try-catch
Apr 17, 2026
Merged

fix(recover): wrap embedded-manifest atob in try/catch for corrupt base64#154
eljojo merged 1 commit intoeljojo:mainfrom
tmchow:osc/107-corrupt-manifest-try-catch

Conversation

@tmchow
Copy link
Copy Markdown
Contributor

@tmchow tmchow commented Apr 16, 2026

Summary

Wrap atob(personalization.manifestB64) in internal/html/assets/src/app.ts in a try/catch so a corrupted embedded manifest shows a toast instead of silently leaving Step 2 asking the user to load MANIFEST.age. Addresses #107.

Why

The whole point of embedding the manifest in recover.html is so the user doesn't need a separate MANIFEST.age file after their archive has been sitting on disk for years. If the base64 payload inside recover.html degrades (bit rot, truncated copy, storage corruption), atob throws a DOMException and the recovery page falls back to asking the user for a file that, by design, they may not have.

The existing flow at app.ts:341-349 had no error handler for that path, so:

  • Step 2 tells the user "load MANIFEST.age," with no indication that the embedded copy is damaged.
  • recover.html looks broken rather than specifically corrupted.

Changes

  • internal/html/assets/src/app.ts: wrap the embedded-manifest decode in try { ... } catch (err) { showError(...) }. The success path is unchanged; on failure the user gets a toast whose guidance tells them to try another copy of recover.html or fall back to loading MANIFEST.age manually in Step 2.
  • internal/translations/recover/en.json: add three keys — error_corrupt_embedded_manifest_title, error_corrupt_embedded_manifest_message, error_corrupt_embedded_manifest_guidance. Other locales fall back to English automatically (translations[currentLang][key] || translations['en'][key] || key in i18n.js:7), so no translator action is required to ship this; translators can add their own strings later via the normal flow.

Testing

  • go test ./internal/translations/... — pass (existing parity test is opt-in via REMEMORY_CHECK_TRANSLATIONS=1).
  • npx esbuild internal/html/assets/src/app.ts --bundle --format=iife --target=es2020 — clean build.
  • npx tsc --noEmit — clean.
  • Manual: with a hand-corrupted manifestB64 (swap a byte to !), the page now shows the "Corrupted embedded archive" toast and Step 2 still works via file upload.

Fixes #107

…se64

atob() throws DOMException when its input isn't valid base64. The
embedded manifest lives in recover.html, which is meant to be archived
long-term; bit rot, truncated copies, or storage degradation can leave
the base64 payload unreadable. Previously the exception propagated,
leaving Step 2 asking the user to load MANIFEST.age manually — but the
whole point of embedding was to avoid needing the separate file.

Catch the decode failure and surface a toast so the user knows their
copy of recover.html is damaged and to try another copy. The i18n
function t() already falls back to English when a locale is missing
translations, so the three new keys work across all languages even
before translators catch up.

Fixes eljojo#107
Copy link
Copy Markdown
Owner

@eljojo eljojo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for submitting this fix!

PS: I made an edit to the PR description so it can fit the contribution guidelines. AI footers are not allowed on this repository.

@eljojo eljojo merged commit 95b97b0 into eljojo:main Apr 17, 2026
4 of 8 checks passed
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.

Corrupt embedded manifest base64 crashes recovery page

2 participants