Share secrets securely with self-destructing links
If set, the recipient must know this passphrase to decrypt the secret. It is never sent to the server.
โ ๏ธ This link expires in and can be viewed .
๐ Remember: the recipient will need the passphrase you set.
Links expire after viewing or set time
Server never sees plaintext โ encrypted in your browser
No registration required
/api
GET /api/health
Returns system status, feature flags, and configuration.
curl https://your-site.pages.dev/api/health
POST /api/secret
๐ป Bash example โ encrypt then create:
#!/bin/bash
# 1. Generate a random 256-bit key
KEY_B64=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n')
# 2. Generate a random 12-byte IV
IV_HEX=$(openssl rand -hex 12)
# 3. Encrypt with AES-256-GCM
SECRET="My secret message"
ENCRYPTED=$(printf '%s' "$SECRET" \
| openssl enc -aes-256-gcm -nosalt -nopad \
-K $(echo "$KEY_B64" | tr '-_' '+/' | base64 -d | xxd -p -c 256) \
-iv "$IV_HEX" 2>/dev/null | base64 | tr '+/' '-_' | tr -d '=\n')
# 4. Combine IV + ciphertext (matches browser format)
IV_B64=$(echo "$IV_HEX" | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=\n')
PAYLOAD="${IV_B64}${ENCRYPTED}"
# 5. POST to API
RESULT=$(curl -s -X POST https://your-site.pages.dev/api/secret \
-H "Content-Type: application/json" \
-d "{\"encryptedData\":\"$PAYLOAD\",\"ttl\":12,\"maxViews\":1}")
ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
echo "Share: https://your-site.pages.dev/view/$ID#$KEY_B64"
โ ๏ธ Browser-based creation is strongly recommended โ the Web Crypto API handles GCM authentication tags correctly. The bash example above is for automation/testing only.
GET /api/secret/{id}/metadata
Returns: exists, viewed, hasFiles, fileCount, viewsRemaining, maxViews, expiresAt, passphraseProtected
๐ป Bash example:
curl -s https://your-site.pages.dev/api/secret/{id}/metadata | jq .
POST /api/secret/{id}/view
Returns encrypted ciphertext. Decrements view count; deletes on last view.
๐ป Bash example โ fetch and decrypt:
#!/bin/bash
# Full URL: https://your-site.pages.dev/view/{id}#{key}
URL="https://your-site.pages.dev/view/abc-123-def#xY9zK2mP..."
ID=$(echo "$URL" | sed 's|.*/view/||' | cut -d'#' -f1)
KEY_B64=$(echo "$URL" | cut -d'#' -f2)
# Fetch encrypted payload (โ ๏ธ this consumes a view)
RESPONSE=$(curl -s -X POST "https://your-site.pages.dev/api/secret/$ID/view")
PAYLOAD=$(echo "$RESPONSE" | grep -o '"encryptedData":"[^"]*"' | cut -d'"' -f4)
# Split IV (first 16 base64url chars = 12 bytes) from ciphertext
IV_B64=$(echo "$PAYLOAD" | head -c 16)
CT_B64=$(echo "$PAYLOAD" | tail -c +17)
IV_HEX=$(echo "$IV_B64" | tr '-_' '+/' | base64 -d | xxd -p -c 256)
KEY_HEX=$(echo "$KEY_B64" | tr '-_' '+/' | base64 -d | xxd -p -c 256)
echo "$CT_B64" | tr '-_' '+/' | base64 -d \
| openssl enc -d -aes-256-gcm -nosalt -nopad -K "$KEY_HEX" -iv "$IV_HEX"
โ ๏ธ Each POST to /view counts as one view and cannot be undone.
Loading feature status from server...
๐ป Bash example โ encrypt a file and attach to a secret:
#!/bin/bash
# 1. Generate key and IV
KEY_B64=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n')
IV_HEX=$(openssl rand -hex 12)
KEY_HEX=$(echo "$KEY_B64" | tr '-_' '+/' | base64 -d | xxd -p -c 256)
IV_B64=$(echo "$IV_HEX" | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=\n')
# 2. Encrypt the secret text
SECRET="My secret message"
TEXT_CT=$(printf '%s' "$SECRET" \
| openssl enc -aes-256-gcm -nosalt -nopad -K "$KEY_HEX" -iv "$IV_HEX" 2>/dev/null \
| base64 | tr '+/' '-_' | tr -d '=\n')
TEXT_PAYLOAD="${IV_B64}${TEXT_CT}"
# 3. Encrypt the file using the same key
FILE="/path/to/document.pdf"
FILE_IV_HEX=$(openssl rand -hex 12)
FILE_IV_B64=$(echo "$FILE_IV_HEX" | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=\n')
FILE_CT=$(openssl enc -aes-256-gcm -nosalt -nopad -K "$KEY_HEX" -iv "$FILE_IV_HEX" \
-in "$FILE" 2>/dev/null | base64 | tr '+/' '-_' | tr -d '=\n')
FILE_PAYLOAD="${FILE_IV_B64}${FILE_CT}"
FILENAME=$(basename "$FILE")
FILESIZE=$(wc -c < "$FILE" | tr -d ' ')
MIMETYPE="application/pdf"
# 4. POST secret + encrypted file
RESULT=$(curl -s -X POST https://your-site.pages.dev/api/secret \
-H "Content-Type: application/json" \
-d "{
\"encryptedData\": \"$TEXT_PAYLOAD\",
\"ttl\": 12,
\"maxViews\": 1,
\"files\": [{
\"name\": \"$FILENAME\",
\"type\": \"$MIMETYPE\",
\"size\": $FILESIZE,
\"data\": \"$FILE_PAYLOAD\"
}]
}")
ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
echo "Share: https://your-site.pages.dev/view/$ID#$KEY_B64"
โ ๏ธ Browser-based creation is recommended. The bash approach omits the AES-GCM authentication tag that OpenSSL produces โ the recipient's browser will reject files encrypted this way. Use the web UI for real file transfers; this example shows the payload structure only.
GET /api/admin/stats
curl -H "X-Admin-Token: your_token" https://your-site.pages.dev/api/admin/stats
POST /api/admin/purge
curl -X POST -H "X-Admin-Token: your_token" https://your-site.pages.dev/api/admin/purge
DELETE /api/admin/secret/{id}
curl -X DELETE -H "X-Admin-Token: your_token" https://your-site.pages.dev/api/admin/secret/{id}
Create: 10/IP/hour ยท View: 50/IP/hour ยท Admin: 5/hour
Configurable via RATE_LIMIT_CREATE, RATE_LIMIT_VIEW, RATE_LIMIT_ADMIN env vars.
#key) โ never sent to the serverHow to decrypt a secret programmatically in the browser or Node.js:
// Full URL: https://your-site.pages.dev/view/{id}#{key}
const url = window.location.href;
const secretId = url.split('/view/')[1].split('#')[0];
const keyB64url = url.split('#')[1];
// Import AES-256-GCM key from URL fragment
const keyBytes = Uint8Array.from(
atob(keyB64url.replace(/-/g, '+').replace(/_/g, '/')),
c => c.charCodeAt(0)
);
const key = await crypto.subtle.importKey(
'raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt']
);
// Fetch encrypted payload (โ ๏ธ consumes a view)
const res = await fetch(`/api/secret/${secretId}/view`, { method: 'POST' });
const { encryptedData } = await res.json();
// Decode base64url โ bytes, split IV (12 bytes) + ciphertext
const b64 = encryptedData.replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const iv = bytes.slice(0, 12);
const cipher = bytes.slice(12);
// Decrypt
const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, cipher);
const secret = new TextDecoder().decode(plain);
console.log('Secret:', secret);