IDVault is built with security as the foundation, not an afterthought. Every document you store, every link you share, and every login you make is protected by industry-standard cryptography and strict access controls.
libsodium / X25519Argon2id key derivationXSalsa20-Poly1305 secretboxper-document keysBefore a document ever leaves your device, it's encrypted in your browser using a key derived from your password β the server only ever receives and stores ciphertext. Every document gets its own random encryption key, which is itself wrapped by your vault's key, which is wrapped by a key only your password can unlock. Your password itself never leaves your device, in any form β not at signup, not at login, not ever. Two independent values are derived from it locally with Argon2id: a vault key that unlocks your documents, and a separate, one-way "login secret" used only to verify you when you sign in, computed with a different domain-separation label so the two can't be connected. The server sees the login secret; it never sees the password, and the login secret can't be worked backward into the vault key.
shown oncenever storedyour only backupBecause your password is the key, a normal "forgot password" reset can't work the way it does on other sites β resetting the password without your original one would permanently lock your own documents. Instead, at setup you're shown a 24-word recovery phrase once. It's a second, independent way to unlock the same key, and it's the only way back in if you forget your password. We never see it, and we cannot show it to you again. If it's lost along with your password, your documents are permanently unrecoverable β including by us. Our support team can help with account-level things (billing, your login email, closing the account), but there is no mechanism, manual or otherwise, that lets anyone recover an encrypted vault without either the password or this phrase. That's not a policy choice we could override if we wanted to β it's what "we cannot open your documents" actually means.
URL fragmentnever sent to serverper-share keyWhen you generate a share link, the decryption key is appended to the URL after a # β a part of the address browsers never transmit to any server. The link itself, stored in our database, is useless without that fragment. Whoever holds the full link holds the key; we only ever hold the token. This means the full link is functionally the document β treat it that way. It can end up in your browser history, in whatever chat app carried it, in a synced clipboard, or in a password manager that auto-saved it. Prefer a one-time link for anything sensitive, and revoke a link once it's served its purpose instead of leaving it live indefinitely.
memory = 256 MiB, iterations = 3, parallelism = 1 (fixed by libsodium's high-level API, which doesn't expose a parallelism knob). This is well above OWASP's minimum-recommended floor (19β46 MiB, depending on iteration count) for Argon2id. It runs in your browser via WebAssembly rather than native code, which is the practical ceiling on how much higher we can push memory cost without unlock taking noticeably longer β the current setting targets roughly 600ms to unlock on a mid-range device.bcryptcost factor 12domain-separated from your vault keyYour password is never stored, and never transmitted anywhere β see "Zero-Knowledge Encryption" above. What IDVault actually stores is bcrypt(cost factor 12) of a one-way login secret derived from your password, not the password itself. bcrypt's cost factor makes each guess against a stolen database expensive on its own β but the honest framing is that cost factor buys time, not safety: a weak or previously-breached password can still fall in minutes regardless of the hash, which is why we check new passwords against known-breach data before accepting them (below). A byproduct of the login secret being a fixed-length derived value: bcrypt's own 72-byte input limit, which matters if a raw password were ever long, no longer applies here at all.
JWT HS25630-minute expiryrevocablesilent renewalAfter you log in, IDVault issues a JSON Web Token (JWT) signed with HMAC-SHA256, valid for 30 minutes. While you're active, the app silently renews it in the background every 20 minutes, so you don't notice the short lifetime day-to-day β but a token that leaks (a browser history entry on a shared machine, a misconfigured log) is only useful for a narrow window instead of a full week. Changing your password, or enabling or disabling two-factor authentication, immediately invalidates every token issued before that moment β including one an attacker might be holding β even though a JWT is normally impossible to revoke early.
Have I Been Pwnedk-anonymitychecked client-sideBecause your password is also your encryption key, a weak or previously-breached one isn't just a login risk β it's a cryptographic weakness. At signup, password change, and password reset, IDVault checks your chosen password against Have I Been Pwned's breach corpus using its k-anonymity range API: only the first 5 characters of a SHA-1 hash of the password ever leave your browser, never the password, and never anything derived from your actual vault key. A password that's appeared in known breaches is rejected before an account is ever created around it.
localStorage, and disappears the moment you close the tab, lock the vault, or reload the page (you'll unlock again with your password). The session token described above is the one thing that is kept in localStorage, so a browser stays "logged in" across a refresh. Being honest about what that does and doesn't buy you: a cross-site scripting bug running live in the page, at the moment your vault happens to be unlocked, can still ride that active session the same way legitimate code could β no key storage choice fully closes that door once arbitrary script is executing on the page. What it does prevent is the quieter risk β a token or key sitting in localStorage getting read and exfiltrated for later, unattended reuse from somewhere else entirely. The vault key can't be lifted that way; at worst, the session token can, and only for the 30 minutes it's valid.RFC 6238HMAC-SHA130-second window8 backup codes (SHA-256)IDVault supports TOTP two-factor authentication, compatible with Google Authenticator, Authy, 1Password, and any RFC 6238-compliant app. After enabling 2FA, every login requires your password plus a 6-digit code generated by your device. The code changes every 30 seconds using HMAC-SHA1 keyed to a secret shared at setup time. IDVault also generates 8 one-time backup codes β each an independently random 32-bit value, hashed with SHA-256 before storage β for account recovery if you lose your device.
client-side XSalsa20-Poly1305per-document keysAWS S3 + AES-256 SSEYour files are encrypted in your browser β with a key derived from your password β before they're ever uploaded. What reaches Amazon S3 is already unreadable ciphertext; S3's own Server-Side Encryption (SSE-S3) then wraps it a second time as an additional layer of defense-in-depth. To be precise about what that second layer is for: AWS holds the SSE-S3 key, not us β it protects against physical media loss or mishandling of raw disks inside AWS, not against AWS itself. The layer that matters against everyone, including AWS and including us, is the first one: your browser's encryption, using a key that only exists on your device.
TLS 1.2 / 1.3HTTPS-onlyHSTS: 2yr, includeSubDomainsAll communication between your browser and IDVault servers uses Transport Layer Security (TLS). HTTP connections are rejected or redirected to HTTPS, and an HTTP Strict Transport Security header (max-age=63072000; includeSubDomains) tells browsers to remember that and refuse to fall back to plain HTTP for two years, across every subdomain β closing the window a network-level attacker would otherwise get on your very first visit. See "Security Headers" below for what the Content-Security-Policy actually restricts.
file-type detectionheader inspectionruns in your browserEvery uploaded file is inspected at the binary level before it's accepted β in your browser, before encryption. IDVault reads the first bytes of the file β known as magic bytes β to confirm the file is actually what it claims to be. A renamed .exe disguised as a PDF is detected and rejected immediately, regardless of what the filename says. Only PDFs, JPEGs, and PNGs are accepted as input β SVG, which can embed executable script, is never an accepted file type in the first place. Photos (JPEGs/PNGs) are re-rendered through an in-browser canvas before they're turned into a PDF page, which strips all EXIF metadata as a side effect β including GPS coordinates and device identifiers that a passport or license photo taken on a phone typically carries. Because the server only ever receives ciphertext, none of this can happen server-side β no server-side malware scanning is possible by design, which is also exactly why these checks have to run here, on your device, before anything is ever encrypted.
pdf-libOpenAction stripJavaScript removalruns in your browserPDFs can embed executable JavaScript, automatic actions (OpenAction), and event handlers (AA) that execute code when a file is opened. Before your PDF is encrypted, IDVault parses it β client-side, on your device β and removes all of these active-content entries from the PDF catalog and Names dictionary. The sanitized file is then re-serialized with a clean object stream and only that version is ever encrypted and uploaded.
crypto.randomBytes256-bit entropyhex-encodedShare link tokens are generated using Node's crypto.randomBytes(32), producing 256 bits of cryptographically secure randomness. This makes the token computationally impossible to guess or enumerate, so it's a reliable way to look up and authorize which share record a request is for. To be precise about what it protects: the token is not the decryption key β that's the fragment described above, which the server never sees. The token stops a stranger from finding or brute-forcing your way to someone else's share record; the fragment is what actually opens the document once you're there.
configurable TTLone-time burninstant revocationEvery share link carries a server-enforced expiry timestamp. After the deadline, the link returns HTTP 410 Gone β the document is no longer accessible. One-time links are burned on first access: the usedAt field is set atomically, preventing replay access. Links can also be revoked at any time before expiry.
diagonal overlayunique per-link referenceapplied after decryptionWhen a shared document is opened, IDVault can apply diagonal watermarks across every page of the PDF, along with a short reference code unique to that share link. Because the server only ever holds ciphertext, watermarking now happens in the recipient's browser β right after their key decrypts the file, and before it's rendered β so the server never needs, or gets, a readable copy just to stamp it. Downloads can also be blocked for sensitive documents. Neither of these is a cryptographic guarantee, and we'd rather say so than let them sound like one: once a recipient's browser has decrypted a file to display it, a determined recipient can always retain a copy β a screenshot, a browser devtools export, print-to-PDF. What watermarking and download-blocking actually do is raise friction and make casual, low-effort redistribution traceable back to a specific link.
append-onlytimestampedper-actionEvery sensitive action β document upload, deletion, share creation, password change, 2FA enable/disable, billing update, account export β is recorded in an append-only audit log with a UTC timestamp and full metadata. We call this "append-only" rather than "immutable" deliberately: it's a database table the application only ever writes new rows to and never edits or deletes from, which is meaningfully different from cryptographic immutability (e.g. hash-chained entries where tampering with one row would be detectable). This trail is used for internal security monitoring and, if requested, can be included in your data export for your own records.
Helmet.jsCSPX-Frame-Options: DENYPermissions-PolicyEvery HTTP response from IDVault includes a hardened set of security headers enforced by Helmet.js and Next.js configuration: X-Frame-Options: DENY to prevent clickjacking; X-Content-Type-Options: nosniff; and a Permissions-Policy that restricts microphone and geolocation access to none. The actual Content-Security-Policy, published here verbatim rather than described in the abstract:default-src 'self'; script-src 'self' 'unsafe-eval' https://js.stripe.com; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-src blob: https://js.stripe.com https://hooks.stripe.com; β¦
It blocks injected <script> tags β the main XSS delivery mechanism β and restricts where scripts, frames, and connections can come from at all. It is not a zero-unsafe-directives policy: 'unsafe-eval' is required because the PDF rendering library we use (pdf.js) calls eval() internally, and 'unsafe-inline' is needed for styles. Removing those without breaking document viewing needs either a different PDF rendering approach or a sandboxed rendering context β real work we haven't done yet, not a header we forgot to set. We'd rather publish the actual gap than a claim that papers over it.
200 req/min globalper-account lockoutIP-based + account-basedA global rate limiter caps all API traffic at 200 requests per minute per IP address, with stricter per-route limits on authentication and upload endpoints. But an IP-based limit alone doesn't stop a slow, distributed credential-stuffing attempt spread across many source addresses β so login attempts are tracked per account as well: after repeated failed attempts, that specific account is temporarily locked, regardless of how many different IPs the attempts came from. Both limits reset automatically after their window passes.
allowlist-basedno wildcardbearer tokens, not cookiesIDVault's API uses a strict CORS allowlist β only requests originating from the official myidvault.io domain get to read a response. No wildcard (*) origins are permitted. To be precise about what actually stops cross-site request forgery, since CORS on its own isn't it β CORS restricts who can read a response, not who can send a request: IDVault authenticates every request with a token your own JavaScript explicitly attaches to an Authorization header, rather than a cookie the browser attaches automatically. A malicious page has no way to make your browser attach that header on its behalf, so it can't forge an authenticated request in the first place β CORS then additionally stops it from reading any response even to an unauthenticated one.
JSON formatmetadata only todayon-demandYou can export a copy of everything IDVault stores about you at any time from the Settings page β a structured JSON file with your profile information, account metadata, share link history, and a complete activity log. Being precise about what this does not yet include: your actual document files. Today's export lists each document's metadata, not its decrypted contents β a genuine gap for a product whose promise is that your documents survive anything. A client-side bulk export that decrypts your documents locally and packages the real files is planned; until it ships, use the per-document download inside the app to get an individual file.
crypto.randomBytes(32)24-hour tokenconfirmed before applyAccount registration requires email verification via a cryptographically random 256-bit token sent to your address, valid for 24 hours. Email address changes follow the same pattern β a confirmation link is sent to both the old and new address, and the change is only applied after the new address is confirmed. This prevents account takeover via email-change attacks.
IDVault has not yet undergone a formal third-party security audit or SOC 2 certification. As a self-funded, early-stage product, an independent audit is a planned investment as revenue grows β not something being skipped indefinitely. We also want to be honest about a narrower claim: production ships a minified JavaScript bundle, which means nobody can currently verify from the running app that it matches any particular source code β "verifiable" today means this page accurately describes what the code does, not that you can independently confirm it yourself. Closing that gap for real means publishing the bundle's integrity hash and, eventually, open-sourcing the client-side encryption code under a public license with reproducible builds β both on the roadmap, neither shipped yet. Vulnerability reports through the process below are taken just as seriously as they would be at an already-audited company regardless.
Every security page reads as a list of things that are protected. Here's what this architecture, honestly, cannot protect against β the same four things are true of essentially every browser-delivered end-to-end encrypted product, not just IDVault:
Every third party that touches any part of your data, and what they can actually see:
IDVault is a small, self-funded team asking you to trust it with the only surviving copy of documents that matter β we take that seriously enough to commit to this publicly, not just describe it as an intention. If IDVault ever ceases operating:
Documents are stored as ciphertext in Amazon S3, with versioning enabled β if a file is ever overwritten or deleted, whether by a bug, an accidental action, or anything else, the previous version is recoverable rather than gone. Old versions are permanently purged after 30 days via an automatic lifecycle rule, matching the deletion window described below rather than accumulating indefinitely. Any backup or recovered version is ciphertext only β nothing readable exists in a backup that doesn't already exist in the primary copy. We do not currently run cross-region replication; everything lives in a single AWS region (US East, Ohio).
If a breach affecting your account is confirmed, we commit to notifying affected users within 72 hours of confirmation β the same window required under GDPR, adopted here as our standard regardless of where you're located. After you delete your account, your data (including audit log entries referencing your account) is retained for up to 30 days before permanent deletion, to allow recovery from an accidental deletion request.
IDVault is, today, a team of one. Production access β the database and the AWS account β belongs to the founder alone; nobody else holds credentials to either. That login is protected by multi-factor authentication, the same principle behind the TOTP 2FA offered on your own account above. Whoever has that access can see account metadata and ciphertext β never a readable document, never a password, because neither exists on our side to see, regardless of who's looking or why.
If you discover a security vulnerability in IDVault, please report it responsibly. Do not publicly disclose the vulnerability until we have had a reasonable opportunity to investigate and respond. We take all reports seriously and will work to resolve confirmed issues as quickly as possible.
This policy is also published machine-readably at /.well-known/security.txt per RFC 9116.