Effective date: 28th May 2026 Last updated: 28th May 2026 Operated by: Third Time Lucky Corp Pty Ltd (ABN 94 612 461 80094 612 461 800), Queensland, Australia Contact: support@vanishly.link
Plain English summary
This document explains how Vanishly is secured, what we protect against, and what we don’t.
The short version:
- Your Secrets are encrypted in your browser before they reach us. The encryption key never touches our servers.
- We use industry-standard cryptography (AES-GCM 256, ECDH P-256, Argon2id, PBKDF2-SHA256).
- Even if our database is stolen, the Secrets in it cannot be decrypted.
- Even if our servers are fully compromised, the contents of Secrets remain secret. The attacker would only see metadata.
- The server hosting the JavaScript that performs the encryption is itself a trust point. We mitigate this with strict Content Security Policy and published page integrity hashes you can verify.
- We do not defend against: a compromised browser, a compromised recipient who leaks the secret after reading, or a phishing attack that tricks you into pasting your Secret into a fake page.
- We have no password reset. If you lose access to your account, we cannot help. This is intentional.
- We disclose security issues responsibly. Researchers can report vulnerabilities to support@vanishly.link.
The full policy below describes the architecture, our threat model, and our commitments.
1. Purpose of this document
This Security Policy describes the security architecture and operational practices of Vanishly. It serves three purposes:
- To inform users (Senders, Recipients, prospective customers) about how their data is protected
- To document our threat model honestly, including what we do and do not defend against
- To establish our security commitments and responsible disclosure process
This is a living document. We will update it as the Service evolves and our practices mature.
2. Security architecture overview
Vanishly is a zero-knowledge end-to-end encrypted service. This means:
- End-to-end encrypted: encryption happens at the endpoints (Sender’s browser, Recipient’s browser), not at our server
- Zero-knowledge: we hold the encrypted ciphertext but never the decryption keys
These properties are not marketing claims. They are technical properties of how the system is built. The sections below describe the specific cryptographic primitives, key handling, and atomicity guarantees that produce these properties.
3. Cryptographic primitives
We use established, well-reviewed cryptographic algorithms. We do not roll our own cryptography.
| Purpose | Algorithm | Parameters |
|---|---|---|
| Symmetric encryption | AES-GCM | 256-bit key |
| Asymmetric key agreement (Request flow) | ECDH | NIST P-256 curve |
| Password-based key derivation (optional Secret password) | PBKDF2 | SHA-256, 600,000 iterations |
| Account password hashing | Argon2id | memory=64MB, time=4, parallelism=1 |
| Hash for integrity checks | SHA-256 | Standard |
| Random number generation | crypto.getRandomValues() (browser) and random_bytes() (server) | OS-level CSPRNG |
| Key derivation from ECDH shared secret | HKDF | SHA-256 |
The 600,000 PBKDF2 iteration count follows the OWASP 2023 password storage recommendations. The Argon2id parameters meet OWASP’s recommended floor for interactive logins.
4. The Share flow (push)
When a Sender shares a Secret:
- The Sender’s browser generates a random AES-GCM 256-bit key
- The browser encrypts the Secret using that key
- The browser uploads only the ciphertext (and initialization vector) to our server
- The browser constructs a URL:
https://vanishly.link/s/{id}#{key} - The portion after the
#(the URL fragment) contains the key
Critical property: URL fragments are not transmitted in HTTP requests. When the Recipient opens this URL, their browser sends only the path (/s/{id}) to us. The key stays in their browser, used locally to decrypt the ciphertext we return.
Optional secret password: A Sender can require a password the Recipient must enter. The password is processed through PBKDF2 (600,000 iterations) and XORed with the URL fragment key to form the actual encryption key. Without both the URL and the password, the Secret cannot be decrypted.
5. The Request flow (pull)
When a Sender requests a Secret from a Recipient, asymmetric cryptography is needed because the parties cannot share a symmetric key out of band:
- The Sender’s browser generates an ECDH P-256 keypair (
priv_R,pub_R) - The browser sends
pub_Rto our server along with the Recipient’s email and request metadata - The server emails the Recipient a deposit link
- When the Recipient opens the link, they generate their own ephemeral ECDH keypair (
priv_E,pub_E) - The Recipient’s browser computes the shared secret via
ECDH(priv_E, pub_R), derives an AES-GCM key via HKDF, and encrypts the Secret - The Recipient sends ciphertext, IV, and
pub_Eto our server. Their private keypriv_Eis discarded - The Sender returns later via a retrieval URL with
priv_Rin the fragment, fetches ciphertext andpub_E, computes the same shared secret viaECDH(priv_R, pub_E), and decrypts
Both parties arrive at the same AES key through ECDH’s mathematical property: ECDH(a, B) = ECDH(b, A). Our server sees both public keys but cannot derive the shared secret from two public keys alone (this is the discrete logarithm problem on elliptic curves, the foundation of ECDH’s security).
The Sender’s private key priv_R lives only in the retrieval URL fragment, which never reaches our server. If the Sender loses the retrieval link, the Secret cannot be decrypted by anyone, including us.
6. Atomic burn-after-reading
Secrets are consumed exactly once (or up to a configured limit, default 1). Concurrent attempts to read the same Secret must not both succeed. We enforce this at the database layer:
- The consume endpoint begins a transaction
SELECT ... FOR UPDATEacquires a row-level lock on the Secret- The application checks view count and expiry within the transaction
- If the Secret is still available, the view counter is incremented and ciphertext is returned
- The transaction commits, releasing the lock
- A second concurrent request finds the counter has increased and receives a 410 Gone response
This guarantees that simultaneous attempts cannot both retrieve the ciphertext. The database itself enforces serialisation; the application logic cannot bypass it.
7. Account security
7.1 Password handling
Account passwords are hashed using Argon2id before storage. We never see your password in plaintext. We do not log it during authentication. Password verification is constant-time to prevent timing attacks.
We check new passwords against the Have I Been Pwned database (using k-anonymity, so we never send your password to a third party) and reject passwords known to have appeared in data breaches.
Minimum password length is 12 characters. We do not impose composition rules (no “must contain a symbol”) because these reduce entropy in practice without improving security.
7.2 Two-factor authentication
Two-factor authentication using time-based one-time passwords (TOTP, RFC 6238) is mandatory for all Accounts. We do not offer SMS or email-based 2FA because both are vulnerable to interception and account takeover.
Compatible authenticator apps include Authy, 1Password, Bitwarden, Google Authenticator, and any standard TOTP app.
The TOTP secret is encrypted at rest using a server-side key separate from the database.
7.3 Recovery codes
At 2FA setup, you receive 10 single-use recovery codes. We display them once and store only hashes. You must record them yourself (we recommend printing and storing in a secure physical location).
Recovery codes are your only safety net. There is no password reset. There is no email-based account recovery. Losing both your password and your recovery codes means permanent account loss.
This is a deliberate security design. Password reset via email is the most common vector for SaaS account takeover (an attacker compromises the email and resets the password). Removing this vector strengthens account security at the cost of recoverability. We believe this tradeoff is appropriate for a service handling secrets.
7.4 Session management
Sessions are stored in Redis with sliding 30-minute expiry. Session tokens are 256-bit random values. Session cookies are HttpOnly, Secure, SameSite=Strict, and bound to the user agent and IP /24.
A session is invalidated if the user logs out, if 30 minutes pass without activity, or if the user changes their password from another session.
7.5 Rate limiting and lockout
We enforce per-endpoint rate limits to prevent brute-force attacks:
- 5 failed logins per IP per 15 minutes
- 10 failed logins per Account per hour triggers a 1-hour Account lockout
- Per-tier limits on send creation
- Per-IP limits on consume and deposit attempts
Rate limit data uses HMAC-hashed IPs, not raw IPs.
8. Server-side hardening
8.1 Transport security
- TLS 1.3 only (older TLS versions disabled)
- HSTS with preload, includeSubDomains, max-age=2 years
- Certificate transparency monitoring
- Strict Content Security Policy on all pages:
default-src 'none'script-src 'self'(no inline scripts, no external CDNs)style-src 'self'connect-src 'self'frame-ancestors 'none'require-trusted-types-for 'script'
8.2 Application security
- Prepared statements for all database queries (no string concatenation)
- CSRF tokens on all state-changing requests
- Output encoding for all user-supplied content
- Strict input validation against allow-lists where possible
- No inline JavaScript, no
eval(), no dynamic script construction - Subresource Integrity (SRI) on any served scripts (currently all scripts are first-party, so SRI is enforced by CSP)
- SVG sanitisation for user-uploaded logos using
enshrined/svg-sanitize - Re-encoding of raster image uploads to strip EXIF and metadata
8.3 Infrastructure
- Server hardening via CSF firewall, restricted SSH (key-only, non-standard port, IP-restricted)
- KernelCare or equivalent for live kernel patching
- Imunify360 or equivalent WAF tuned for the application
- No shared hosting; dedicated infrastructure
- Database credentials in a file outside the webroot, mode 0400
- Webroot read-only at the OS level except for a writable tmp directory
8.4 Logging
- Nginx access logs rotated daily, retained 7 days
- IP addresses HMAC-hashed at log-write time using a daily-rotated server-side secret
- No request body logging
- No referer logging
- Application logs with stack traces but ciphertext stripped at handler level
- Application logs retained 30 days then purged
- No application performance monitoring tools that capture request bodies
9. What we defend against
Our threat model assumes a competent attacker with various capabilities. We defend against:
- Database breach: An attacker who obtains a complete database dump cannot decrypt any Secret ciphertext. Account passwords are Argon2id-hashed. TOTP seeds are encrypted at rest with a key not in the database.
- Server compromise: An attacker who gains root access to our servers can monitor incoming HTTPS-terminated traffic and read the database, but cannot decrypt Secret ciphertext because the keys never reach the server. They can disclose metadata.
- Hosting provider misbehaviour: The same protection applies. A malicious hosting provider cannot read Secret content.
- Bot prefetching: URL preview bots (Slack, Teams, email clients, link scanners) cannot consume Secrets. We use a click-to-reveal mechanism and bot detection at the User-Agent and request-pattern level.
- Replay and enumeration: Atomic burn-after-reading prevents replay. Secret IDs are 128-bit random, making enumeration infeasible.
- Brute-force on accounts: Rate limiting and account lockout slow attackers; Argon2id makes offline password cracking expensive.
- Email account compromise alone: Because we have no password reset, an attacker who compromises your email cannot take over your Vanishly Account.
- Password compromise alone: Mandatory 2FA prevents an attacker with only your password from accessing your Account.
- Cross-site scripting (XSS): Strict CSP, output encoding, and no inline scripts prevent XSS vectors. User-supplied branding fields go through strict sanitisation.
- Insider threat: Even with full operator access, we cannot decrypt Secret content. Administrative actions are audit-logged.
10. What we DO NOT defend against
We are explicit about the limits of what we protect. The following are outside our threat model, and you should be aware of them:
10.1 Compromised endpoint browsers
If the Sender’s browser is compromised at the time of encryption, the Secret is exposed before it reaches our service. If the Recipient’s browser is compromised at the time of decryption, the same applies. We have no visibility into endpoint security.
10.2 Recipient misbehaviour
Once a Recipient decrypts a Secret, they have the plaintext. They can save it, screenshot it, share it, or leak it. We provide a one-time delivery mechanism; we do not provide ongoing control over what the Recipient does with the Secret.
10.3 Phishing and social engineering
If an attacker tricks a Sender into pasting a Secret into a fake Vanishly page (a lookalike domain, a typosquatted URL, a malicious browser extension), the Secret is captured by the attacker before our encryption applies. We mitigate via the page integrity hash you can verify and via the recipient-side trust signals, but these require user vigilance.
10.4 Compromised JavaScript delivery
Our zero-knowledge architecture depends on the JavaScript we serve performing the encryption faithfully. An attacker who gains the ability to serve malicious JavaScript to a user (via a server compromise that includes the ability to modify served scripts, or via a man-in-the-middle attack on a misconfigured client) can capture Secrets before they are encrypted.
Mitigations:
- Strict Content Security Policy prevents the JavaScript from being modified at runtime
- Subresource Integrity ensures script bytes match expected hashes
- HSTS preload prevents downgrade attacks
- Page integrity hash, displayed on every recipient page, can be verified against the published hash at vanishly.link/integrity
This is the irreducible trust point of any browser-based zero-knowledge service. Every comparable service (Bitwarden Send, PrivateBin, ProtonMail webmail) has the same property. The page integrity hash mechanism is our most direct mitigation. Users who require stronger guarantees can use a native client (we may offer one in future) or pin a specific verified build.
10.5 Metadata exposure
Vanishly necessarily knows:
- Who has an Account
- Which Account created which Share or Request
- Recipient email addresses on Requests
- Timing of sends, reads, and expiries
- IP address hashes of all interactions
This relationship metadata is visible to us and would be visible to an attacker who compromised our database. The contents of Secrets are protected; the fact that they were exchanged is not.
For users requiring metadata privacy, we recommend an additional anonymisation layer (e.g. sending via Tor, using a privacy-preserving email account, or using a service that does not require Accounts). Vanishly’s account-gated design enables auditability, abuse prevention, and the Request flow, but it does not provide metadata anonymity.
10.6 Quantum attack on captured ciphertext
A future quantum computer of sufficient capability could break ECDH P-256, retroactively decrypting Request flow Secrets if their ciphertext was captured. AES-256 is considered quantum-resistant for confidentiality purposes (Grover’s algorithm halves effective key length, leaving 128-bit security, which remains adequate).
We will migrate to post-quantum cryptography as standards mature and the Web Crypto API supports them. We will publish migration plans when relevant.
10.7 Targeted state-level attack
If you are being specifically targeted by a well-resourced state actor, no commercial SaaS service is sufficient. Use air-gapped systems for the highest-sensitivity material.
11. Operational security
11.1 Personnel
Currently, Vanishly is operated by a small team. Production access is restricted to authorised personnel. All production access is logged and audited.
11.2 Incident response
We have an incident response plan covering:
- Suspected breach or compromise
- Availability outages
- Security vulnerabilities reported externally
- Law enforcement requests
Notable elements:
- Suspected compromise triggers immediate service shutdown (Nginx 503) rather than continued operation under uncertainty
- Affected users are notified within 72 hours of confirmed compromise (per Notifiable Data Breaches scheme requirements)
- A post-incident report is published for non-trivial incidents
11.3 Backups
- The
secretstable andrequeststable content are explicitly excluded from backups. Backing up ephemeral encrypted content would violate the ephemerality property and be operationally pointless (the keys are not in the backup) - Account data, branding, and audit log are backed up
- Backups are encrypted at rest and access-controlled
- Backup retention: 30 days
11.4 Change management
Production changes require:
- Code review (for any team larger than one)
- Automated test suite passing
- Staging environment deployment and validation
- Rollback plan
11.5 Dependencies
We minimise third-party dependencies. The runtime stack (PHP, MySQL, Redis, Nginx) is maintained via the OS package manager with timely security updates. Application dependencies (Composer packages) are reviewed before adoption and updated regularly. We monitor security advisories for all dependencies.
12. Responsible disclosure
We welcome security research and reports of vulnerabilities.
12.1 How to report
Email support@vanishly.link with:
- A clear description of the vulnerability
- Steps to reproduce
- The impact you assess
- Your suggested remediation (if any)
- Whether you would like to be credited publicly
12.2 What we commit to
- Acknowledge receipt within 5 business days
- Provide a substantive response within 14 days
- Keep you informed of progress
- Credit you publicly in our security advisory (with your permission)
- Not pursue legal action against good-faith security research
12.3 What we ask of you
- Do not exploit the vulnerability beyond the minimum needed to demonstrate it
- Do not access data belonging to other users
- Do not disrupt the Service
- Give us reasonable time to fix the issue before public disclosure (typically 90 days, or sooner by mutual agreement)
- Provide enough detail for us to reproduce and fix the issue
12.4 Bug bounty
We do not currently operate a paid bug bounty program. We recognise valuable contributions through public acknowledgment in our security advisories and in our hall of fame at vanishly.link/security/researchers.
12.5 Out of scope
The following are not eligible for disclosure recognition:
- Reports of missing security headers without demonstrated impact
- Self-XSS or vulnerabilities requiring physical access to a user’s device
- Vulnerabilities in third-party services we use (report to those vendors directly)
- Social engineering attacks against our team
- Denial of service via volume (without a novel amplification or efficiency vector)
- Issues already known to us or publicly disclosed
13. Compliance and certifications
At launch, Vanishly operates under Australian law and applies the principles of:
- The Privacy Act 1988 (Cth) and Australian Privacy Principles
- GDPR for EU users (as set out in our Privacy Policy)
- The Notifiable Data Breaches scheme
We do not hold formal certifications (ISO 27001, SOC 2, etc.) at launch. We may pursue formal certification as the business matures.
14. Source code and transparency
We believe our security claims should be verifiable. Our roadmap includes:
- Publishing the client-side cryptographic code for external review
- Publishing third-party security audit results when conducted
- Maintaining a public changelog of security-relevant changes
15. Security policy updates
We update this policy as our practices evolve. The “Last updated” date at the top reflects the most recent revision. Material changes will be summarised at vanishly.link/security/changelog. Previous versions are available on request.
Contact
- Security reports and vulnerability disclosure: support@vanishly.link
- General security questions: support@vanishly.link
- Privacy-related security questions: support@vanishly.link