All posts

8 min read

Where CardanoWall Keeps Your Keys in the Browser

In the browser, CardanoWall keeps unlocked keys in session memory and writes only encrypted vault ciphertext to IndexedDB — never plaintext Identity Seeds or private keys.

When you unlock CardanoWall in the browser, the unlocked seed and the private keys derived from it live in session memory — ordinary application memory that disappears when you lock, sign out, or close the tab. Persistent browser storage (IndexedDB, sessionStorage) holds only encrypted vault ciphertext and non-secret metadata. Plaintext Identity Seeds and derived private keys are never written there.

That distinction is the whole point. The browser must hold key material to sign and decrypt locally — that is how the cryptography works at all. The safety question is never "can the browser touch keys?" It has to. The question is where those keys live and what gets written to disk. CardanoWall's web model is built so that what survives a reload is ciphertext, not secrets.

What does the browser need while an identity is unlocked?

It needs the private key material for the work you are actually doing — and nothing more durable than the session itself.

After you unlock an identity, the app may need to:

  • sign a Label 309 record;
  • decrypt a sealed record addressed to you;
  • trial-decrypt incoming sealed records to find the ones meant for your inbox;
  • re-encrypt the vault after you add or remove a passkey;
  • reveal your seed when you explicitly ask to see it;
  • rebuild local encrypted caches.

None of that is possible with public keys alone. Each operation needs private material derived from your Identity Seed. The design keeps that material in session memory and clears it on lock or sign-out — on a best-effort basis, for reasons we get to below.

What is session memory here?

Session memory is temporary, in-process application memory that the app discards when the unlock session ends.

In CardanoWall's browser app, the live seed and the keys derived from it are held outside ordinary UI state, in internal memory maps. The reactive UI state holds only non-secret facts: which identity is unlocked, when it was unlocked, which passkey enrollment metadata is on screen during setup. The secret bytes never enter that reactive state.

This separation matters because ordinary UI state is the part of an app most likely to be inspected, serialized, persisted, or accidentally passed into a component that logs it. Secret material deserves a smaller, deliberately enumerated surface. Every place in the app that can hand out secret bytes — the signing closure, the decrypt keys, the seed-reveal escape hatch, the vault-rebuild path — is listed in one file, and each one returns a defensive copy rather than the live buffer.

The browser still holds the secrets while you are unlocked. They are simply not treated as normal app data.

What gets written to IndexedDB?

Encrypted vault ciphertext — the same bytes the server stores, not the seeds inside them.

IndexedDB is used as a local cache of your encrypted identity vault: one row per account, holding the exact age-encrypted ciphertext the server keeps. Caching it lets the app rehydrate after a reload from a single passkey tap, with no round trip to fetch the vault again.

An attacker who only reads that local database sees encrypted vault bytes addressed to your passkeys — not plaintext seeds. The cache is still sensitive service data and the app wipes it on sign-out, account deletion, and passkey changes. But it is not the identity itself; it is a locked box whose only keys live inside your authenticators. (For how that box is sealed, see how CardanoWall stores your identity and how passkeys protect your identity vault.)

These writes are suppressed entirely in public-computer mode and when you choose not to remember the device.

What goes into sessionStorage?

Only non-secret enrollment metadata — never key material.

During identity creation, the app mirrors passkey enrollment metadata to sessionStorage so an accidental reload mid-setup does not wipe the visible state. That metadata is non-secret by construction: credential IDs, public keys, transports, device-type and backup flags, and similar facts an attacker gains nothing from.

What is explicitly kept out of sessionStorage:

  • Identity Seeds;
  • derived private keys;
  • WebAuthn PRF (pseudo-random function) outputs — the secrets a passkey returns to unlock the vault;
  • vault plaintext;
  • decrypted sealed content.

The line is simple. Interface continuity can persist across a reload; identity secrets should not.

What never belongs in browser storage at all?

Plaintext identity material — in any store.

These are kept out of localStorage, sessionStorage, IndexedDB, cookies, logs, and ordinary app state:

  • the Identity Seed;
  • Ed25519 signing private keys;
  • X25519 receive private keys;
  • hybrid post-quantum receive secrets (the seed behind the optional age1pqc... address);
  • WebAuthn PRF outputs;
  • vault plaintext;
  • decrypted sealed content, unless you explicitly save it to an encrypted local cache with clear control over it.

The reasoning is the same one that runs through the whole design: the more places a secret is written, the harder it becomes to reason about deleting it and the larger the surface a compromise can read.

What is public-computer mode?

It is the explicit shared-device mode: while it is on, nothing identity-related is written to browser storage at all.

Turn it on and the app skips every identity-related persistence path — no PIN vault, no IndexedDB vault cache, no version pin, no sessionStorage enrollment mirror. Unlocked keys live in session memory only, surviving in-app navigation for the tab but dying on tab close or reload. The toggle itself is memory-only on purpose: persisting "I'm on a public computer" would be its own browser-storage write, and a shared machine should always reopen in the safe default of asking again. Use it on a library computer, a borrowed laptop, a conference machine, a newsroom kiosk — anything you do not control.

What it does not do is make an untrusted device safe. If the machine is already compromised while you type a seed or unlock an identity, it can still observe secrets in memory. The mode reduces what is left behind after you leave; it does not defeat active local malware. Public computer mode covers the full workflow.

What does zeroization mean in JavaScript?

It means overwriting the secret byte arrays — filling them with zeros — as soon as the app is finished with them.

CardanoWall zeroizes its Uint8Array key buffers when an identity is locked or you sign out, rather than waiting and hoping the garbage collector reclaims them. That is good hygiene and worth doing.

But JavaScript is not a hardened secret-handling environment, and it is honest to say so. Strings are immutable, so a secret that ever became a string cannot be wiped in place. Garbage-collection timing is not under the app's control. Engines can copy memory internally. Developer tools, extensions, and a compromised origin change the risk model entirely.

So zeroization here is a best-effort measure, not a guaranteed erase. It meaningfully shrinks the window in which a stray copy lingers; it does not promise the bytes are gone everywhere.

What if the page is compromised while it's unlocked?

Then the identity is genuinely at risk — this is the hardest boundary in any browser-based cryptography.

A malicious browser extension with page-content access, a compromised device, or a serious cross-site-scripting (XSS) bug running during an unlocked session may be able to read secrets out of memory, or make the app sign or decrypt things you did not intend. No web app can fully eliminate this: code that arrives over the network and then handles keys is exposed to anything else running in that same origin.

CardanoWall leans on defense in depth to shrink the window: a strict Content Security Policy that confines scripts to the app's own origin — with no inline scripts, no eval, and no third-party scripts loaded at all — plus security headers stamped on every response at the edge, a deliberately small secret surface, no plaintext persistence, and unlock and decryption only on explicit user action — never automatically on tab focus. These reduce the odds and the blast radius. They do not make a malicious script inside an unlocked origin harmless; that remains the highest-impact threat the browser model accepts.

For your most sensitive identities, the right answer is to move the key material off the shared web surface. Keep them on a dedicated, trusted device, and prefer a workflow where the secret never touches a browser at all — the CLI in automation, or CardanoWall Desktop, which keeps your keys in a local Rust core instead of a web origin. (For the broader principle, see why keys never leave the device.)

What should you actually do?

Use the browser model deliberately, and match the precautions to the sensitivity of the work.

For everyday use:

  • save your Identity Seed somewhere safe — it is the real backup;
  • add a passkey for daily unlock;
  • lock or sign out when you finish;
  • use "remember this device" only on machines you trust;
  • keep your browser and OS updated;
  • avoid high-risk browser extensions;
  • switch on public-computer mode on any shared device.

For sensitive work, add:

  • a dedicated browser profile or, better, a dedicated device;
  • no unlocking on borrowed machines;
  • a hardware security key as an unlock factor;
  • separation between high-risk and ordinary identities;
  • care with sealed records — a recipient who can decrypt can also leak the plaintext afterward.

The short version

CardanoWall's browser app needs private keys while an identity is unlocked, so it keeps them in session memory rather than persistent storage. IndexedDB caches only the encrypted vault ciphertext; sessionStorage holds only non-secret setup metadata. The Identity Seed and private keys are never written as ordinary browser data.

The hosted-vault model and the browser-storage model reinforce each other: the server holds ciphertext it cannot decrypt, and the browser tries hard not to leave the seed behind. Neither claim is a guarantee against a device that is already compromised — and being clear about that limit is part of taking it seriously. For the full picture of what the service can and cannot observe, see what CardanoWall can see.

securitybrowser-securityidentity