Ledgerly
Security center

Check our security, any day you like

Ledgerly holds nothing of yours, so the only thing worth trusting is the code. Here is the latest full audit, a daily report on whether the protective code moved, and the parts of the wallet you can read for yourself.

Daily code report

No file that handles your keys, your recovery phrase or your transactions changed today. The wallet behaves exactly as it did in the last published audit.

September 23, 2026
Last checked
96
Files watched
0
Changes since last report
0
Open findings
Read today's code report
Full audit

Audit of August 31, 2026

8 of 17 checks passed as written, 6 were fixed during the audit, 3 were reviewed and accepted on purpose, and 0 remain open. One thing stays outstanding on purpose: an independent third-party penetration test. Everything here is our own review, and we say so rather than dressing it up.

What we checked, area by area (6 areas)

Encryption and passwords

Your vault is sealed with AES-GCM and a password stretched with Argon2id. The unlock key is never extractable from the browser.

Key isolation

Unlocked signing keys exist only inside an isolated worker. The page you look at never receives them.

Recovery phrase containment

A browser sweep after revealing a phrase found no copy in storage, cookies, the page, memory or any network request.

Browser hardening

A strict content policy, HSTS, no referrers and a minimal permissions policy, all pinned by automated tests.

Sessions and sign-out

Locking one tab locks the others, and signing out clears the session everywhere before it navigates.

Backend trust boundary

Every database table has row-level security, writes are scoped to your own profile, and the public counters cannot be forged.

Every individual check and its evidence (17 checks)
CheckAreaResultEvidence
F1Table privilegesFIXEDEvery table granted the full privilege set to signed-in people, and two granted it to anonymous visitors. That set includes emptying a table, which no access rule filters. Privileges are now the minimum each table needs.
F2Database web requestsFIXEDThe database's built-in web request helpers were executable by anyone holding a database role, which is the classic path to making a server fetch somewhere it should not. Execution is now backend only. The daily scheduled job is unaffected.
F3Token feed ingestFIXEDValues from the third-party token index went into the database unchecked: unbounded text, any URL scheme in the logo field, and any row shape. Every field is now shape-checked, length-bounded, and a logo must be a plain https URL. The feed response is capped and time limited.
F4Directory searchFIXEDThe search box was cleaned by removing four characters. It is now an allowlist: letters, digits, space, dash and underscore, capped at 40 characters, so nothing left can carry meaning in the filter it is placed into.
F5Number handlingFIXEDThe fuzzer crashed the bounds helper with an exotic value. It now returns the floor instead of throwing.
F6Scheduled job token tableFIXEDThe table holding the job secret had no written access rule, only an absence of privileges. It now carries an explicit deny rule for visitors and signed-in people.
A1Sign-in surfacePASSNo anonymous sign-in, no auto-confirm. Password reset and sign-up redirects are same-origin and land on public pages. No credential or key is committed.
A2Route gatesPASSThe wallet is gated behind the unlock screen and the profile gate; no hidden admin or debug route exists.
A3Server endpointsPASSBoth scheduled endpoints demand a secret compared in constant time and refuse an empty or wrong one. The publishable key is not accepted as a secret.
A4Access rulesPASSEvery table has row level security, an explicit privilege grant and at least one rule. Writes are scoped to the signer. Anonymous writing is limited to a zero-value metrics event that can never be read back.
A5RolesPASSNo role is stored on a profile row.
A6Definer functionsPASSAll pin their search path and are executable only by the backend.
A7HeadersPASSContent security policy, no-sniff, no-referrer, HSTS with subdomains, camera-only permissions, and framing restricted to our own origins. Hostile X-Forwarded-*, Origin, Referer and User-Agent values are neither reflected nor trusted.
A8DependenciesPASSNo high or critical vulnerability in the lockfile.
R1Published reports readable by anyoneACCEPTEDThe daily code report table is public by design: it is the content of the public Security Center and the same text is in the public repository. It holds file names and hashes, never keys or user data.
R2Web request extension installed in the shared schemaACCEPTEDPlaced there by the platform and needed by the daily scheduled job. Execution has been revoked from everyone but the backend, which removes the risk the placement carries.
R3Inline scripts allowed by the policyACCEPTEDThe server-rendered page ships an inline hydration payload, so inline scripts cannot be forbidden without breaking the app. Everything else is closed: no plugins, no external forms, no third-party frames.

Full report: docs/security/2026-08-31-full-security-audit.md

History

Recent daily reports

Open source

Audit our code yourself

The code that holds your keys is published: key generation, encryption, the vault format, transaction building, signing, the isolated worker and the extension shell. Every publish ships a checksum list and is tagged to a released version, so you can compare what you installed against what we published. Our servers, analytics and deployment stay private, and they never receive a key, a recovery phrase or a password.