VanillaGift

Security

How we protect your card.

Card numbers are encrypted before they hit disk. PINs are hashed, not stored. Sessions expire in 15 minutes. Here is the full picture, in plain English.

Last updated: May 25, 2026

What we collect

When you check a balance, VanillaGift receives three things from your browser: the card's 16-digit number, the expiry date, and the CVV from the back. That's it. We never ask for your name, email, phone number, address, or any other personal information.

The first time you check a card, you set a 4 to 6 digit PIN. The PIN protects future access to that card's balance and recent transactions. We do not collect or store the PIN in plaintext — see “PIN security” below.

Encryption at rest

Card number, CVV, and expiry are encrypted with AES-256-GCM (Galois/Counter Mode) the moment they reach our server, before any database write. AES-256 is the encryption standard used by the U.S. government for classified information and by every reputable financial institution worldwide.

GCM is an authenticated encryption mode: each ciphertext is paired with an authentication tag that detects tampering. If anyone modifies a single byte of the stored ciphertext, decryption fails — silently inserting a different card number into a database row is impossible.

A fresh 96-bit initialization vector is generated for every encryption, so submitting the same card twice produces two completely different ciphertexts in the database. Identical inputs do not produce identical outputs — pattern analysis on the ciphertext is useless.

The encryption key is held in a server-side environment variable that is never logged, never returned in any API response, and never embedded in the frontend. It can be rotated without re-uploading any data, provided the previous key remains available to decrypt existing rows during transition.

PIN security

PINs are never stored as text. When you create a PIN, we run it through Argon2id, the winner of the 2015 Password Hashing Competition and the current recommendation from the Internet Engineering Task Force (RFC 9106) for new deployments. Argon2id is designed to be deliberately slow and memory-hungry, which makes large-scale offline cracking economically pointless.

Each PIN is hashed with a unique cryptographic salt, so two users who happen to pick the same PIN have completely different hashes in our database. Even with full database access, an attacker cannot tell which users share a PIN.

Brute force protection

After 5 incorrect PIN attempts on a single card, that card is locked for 15 minutes. During the lockout, even the correct PIN is rejected. After the lockout window expires, attempts reset to zero. There is no way to bypass the lockout from the API side.

We also rate-limit by IP address. A single IP address cannot make more than 60 API calls per minute, regardless of how many different cards it tries. This combination — per-card lockout plus per-IP rate limiting — makes large-scale credential-stuffing attacks impractical.

Session security

When you submit your card details, we issue a short-lived JSON Web Token (JWT) signed with HS256. The token is stored in an httpOnly, sameSite=lax cookie that the browser sends back on subsequent requests. Because it is httpOnly, no JavaScript on the page can read it — protecting against XSS token theft. Because it is sameSite=lax, it cannot be sent on cross-site POST requests, protecting against CSRF.

The token has two stages: pre-pin (you have submitted card details but not yet verified your PIN) and pin-verified (you have proven the PIN). Balance and transaction endpoints require pin-verified. A pre-pin token cannot access balance data, even though it is a valid signed session — the API enforces stage transitions server-side.

Tokens expire 15 minutes after issuance. There is no refresh mechanism. After 15 minutes you re-enter your card details. This bounds the impact of a stolen token: it stops working very quickly.

What we never do

  • We never log your card number, CVV, or PIN. The server's logging layer redacts these fields at the structured-log level — they cannot accidentally end up in operational logs, error reports, or analytics.
  • We never send your card data to third parties. No analytics provider, no advertising network, no email service. Card data goes from your browser to our server and that is the end of its journey.
  • We never sell any data. We do not collect data we could sell — no email, no profile, no behavioral tracking.
  • We never display the full card number after entry. The interface shows only the last 4 digits. The encrypted PAN sits in the database; nothing in the UI ever decrypts and displays it.

Audit logging

Every meaningful event — card check, PIN set, PIN verified, PIN failed, balance viewed — is recorded in an audit log with the timestamp, the action, and a one-way SHA-256 hash of the client IP. We hash the IP rather than storing it directly so that the audit log itself contains no PII at rest. The audit log lets us detect abuse patterns (the same hashed IP hitting many cards, repeated PIN failures, etc.) without compromising user privacy.

Transport security

All traffic between your browser and VanillaGift runs over HTTPS, using TLS 1.2 or newer with strong cipher suites. We send HSTS headers so browsers refuse to downgrade. We send a strict Content Security Policy that blocks inline scripts and untrusted sources.

Our backend sets X-Frame-Options to DENY so the site cannot be embedded in an iframe, which makes UI redress (clickjacking) attacks impossible. X-Content-Type-Options is set to nosniff so the browser cannot reinterpret response types.

Reporting a vulnerability

If you believe you have found a security issue in VanillaGift, please email security@balance.myvanillagift.com with a description, reproduction steps, and your contact details. We respond within 72 hours. Responsible disclosure is appreciated — please give us time to fix issues before publishing.