sasiru-mindaka/Secret-Gate

A zero-knowledge, ephemeral messaging engine built with pure native PHP & Web Crypto API. No frameworks, no dependencies, no server access to secrets.

0

stars

1

commits

PHP

primary language

Sep 12, 2026

updated

secretgate.site
ephemeral-messaging
php8
security
web-crypto-api
zero-knowledge-architecture

README

πŸ”’ Secret Gate

A zero-knowledge, ephemeral messaging engine that never sees your secrets.

Pure Native PHP. No frameworks. No bloat. No trust required.

PHP MySQL Redis License: AGPL v3

Live Demo Β· Report a Bug Β· Request a Feature


Overview

Secret Gate is a self-hostable, ephemeral messaging engine for people who want an anonymous inbox without handing a server the keys to read it. You generate a link, share it, and anyone can drop an encrypted message into it β€” but the encryption/decryption happens entirely in the visitor's browser using the Web Crypto API. The server only ever stores ciphertext it cannot decrypt.

There's no signup, no email, no phone number, and no tracking. Just a keypair, a password, and a link. When a message or link expires, it's gone β€” enforced at the database and cache layer, not by a "trust us" policy.

It was built deliberately without a framework. No Composer dependency tree, no node_modules, no build step. ~10 PHP files you can read top to bottom in an afternoon, deployable on almost any LAMP-style stack.


✨ Key Features

  • πŸ” True Zero-Knowledge Encryption β€” Hybrid RSA-OAEP (2048-bit) + AES-256-GCM performed client-side via window.crypto.subtle. The server stores only ciphertext and a public key; it is mathematically incapable of reading message contents.
  • πŸ—οΈ Server-Side Private Key, Zero Exposure β€” Each link's private key is wrapped with a password-derived AES-256-GCM key (PBKDF2, 300,000 iterations) and held only in the browser's IndexedDB vault β€” never transmitted or logged.
  • πŸͺΆ Zero Framework Bloat β€” No Laravel, no Symfony, no Composer sprawl. Just clean, native, auditable PHP.
  • ⏳ Ephemeral by Design β€” Per-link message TTLs and inactivity-based account expiry, enforced via MySQL expires_at columns and Redis TTL keys β€” not a soft "please delete this" toggle.
  • πŸ›‘οΈ Hardened Security Baseline β€” Argon2id password hashing, strict same-origin + CSRF token validation on every state-changing request, PDO prepared statements everywhere, and a whitelist-only file execution model via .htaccess.
  • πŸ•΅οΈ Privacy by Default β€” No accounts, no email, no PII collected or stored, ever. What we don't collect, we can't leak.
  • 🚦 Abuse-Resistant β€” Redis-backed sliding-window rate limiting, IP blacklisting/burning, honeypot fields, and Cloudflare Turnstile on public-facing forms.
  • πŸ“¦ Deploy Anywhere β€” Runs on any standard Apache + PHP-FPM + MySQL stack. No containers required (though it'll happily run in one).

πŸ—οΈ Architecture & Encryption Flow

Secret Gate uses hybrid encryption: an RSA-OAEP keypair identifies the inbox, while every individual message gets its own single-use AES-256-GCM key β€” the best of both worlds for a multi-sender, single-recipient model.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         1. INBOX CREATION (Owner)                           β”‚
β”‚                                                                             β”‚
β”‚   Browser                                                                   β”‚
β”‚   β”œβ”€ Generate RSA-OAEP 2048-bit keypair              (window.crypto.subtle) β”‚
β”‚   β”œβ”€ Derive AES-256-GCM wrapping key                 (PBKDF2, password)     β”‚
β”‚   β”œβ”€ Encrypt(private_key, wrapping_key)         ──►  store in IndexedDB     β”‚
β”‚   β”‚                                                  (never leaves device)  β”‚
β”‚   └─ POST public_key (JWK) + Argon2id(password) ──► Server                  β”‚
β”‚                                                                             β”‚
β”‚   Server (api.php)                                                          β”‚
β”‚   └─ INSERT INTO sb_links (public_id, public_key, password_hash)            β”‚
β”‚      (server never sees the private key or the plaintext password)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       2. SENDING A MESSAGE (Sender)                         β”‚
β”‚                                                                             β”‚
β”‚   Browser                             Server                                β”‚
β”‚   β”œβ”€ GET public_key(JWK) for link           ──►  fetch from sb_links        β”‚
β”‚   β”œβ”€ Generate one-time AES-256-GCM key                                      β”‚
β”‚   β”œβ”€ Encrypt(message, AES key)                                              β”‚
β”‚   β”œβ”€ Encrypt(AES key, RSA-OAEP public_key)                                  β”‚
β”‚   └─ POST { encrypted_message, wrapped_key } ──►  INSERT sb_messages        β”‚
β”‚                                                     (ciphertext only)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      3. READING MESSAGES (Owner)                            β”‚
β”‚                                                                             β”‚
β”‚   Browser                                Server                             β”‚
β”‚   β”œβ”€ Unlock local vault with password                                       β”‚
β”‚   β”‚  └─ Derive wrapping key (PBKDF2)   ──►  decrypt private key locally     β”‚
β”‚   β”œβ”€ GET encrypted messages            ──►  SELECT WHERE not expired        β”‚
β”‚   └─ Decrypt(AES key, private_key)     ──►  Decrypt(message, AES key)       β”‚
β”‚      (all decryption happens on-device ──►  server output stays opaque)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why hybrid encryption? RSA-OAEP lets any anonymous sender encrypt a message using only the public key β€” no shared secret, no prior handshake. AES-256-GCM keeps per-message encryption fast and keeps ciphertext size sane, since RSA alone isn't built for encrypting arbitrary-length payloads.


πŸš€ Prerequisites & Quick Start

Prerequisites

  • PHP 8.0+ with pdo_mysql, redis, and openssl extensions enabled
  • MySQL 8.0+ (or MariaDB equivalent)
  • Redis server (optional but recommended) β€” powers rate limiting and IP blacklisting/burning. If Redis isn't installed or reachable, Secret Gate automatically falls back to a file-based store under storage/ with the same behavior, so the app still runs without it
  • Apache with mod_rewrite and mod_headers
  • A Cloudflare Turnstile site/secret key pair (used on the feedback form)
  • A Telegram bot token + chat ID (used to relay feedback submissions β€” optional but required for the feedback form to function)

1. Clone the repository

git clone https://github.com/sasiru-mindaka/Secret-Gate.git
cd Secret-Gate

2. Configure your environment

Create an .env file (outside the web root is strongly recommended) with the following keys:

APP_ENV=production

# Database
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=your_db_user
DB_PASS=your_db_password
DB_NAME=schema

# Redis (optional β€” leave as-is if you're not running Redis;
# the app falls back to file-based rate limiting automatically)
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASS=

# Cloudflare Turnstile
CF_TURNSTILE_SECRET_KEY=your_turnstile_secret

# Telegram feedback relay
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id

By default, config.php loads .env from one directory above your web root (__DIR__ . '/../.env') β€” e.g. if your site lives at /var/www/html, place .env at /var/www/.env. This keeps credentials outside the publicly served directory even if the web server itself is ever misconfigured. Only change the path in loadEnv() if your server layout is different from this.

3. Import the database schema

mysql -u your_db_user -p schema < schema.sql

This creates the two core tables:

TablePurpose
sb_linksStores the inbox's public key, Argon2id password hash, and TTL settings β€” never the private key
sb_messagesStores ciphertext only, with an optional expires_at for self-destructing messages

4. Point your web server at the project root

Apache is expected β€” .htaccess handles routing, PHP file whitelisting, and blocks direct access to everything except the entry-point scripts (index.php, api.php, feedback.php, etc.). Make sure AllowOverride All is set for the vhost.


πŸ” Data Minimization & Security Matrix

LayerProtectionImplementation
Message ContentNever readable by the serverClient-side AES-256-GCM, per-message single-use key
Private KeysNever transmittedRSA-OAEP private key wrapped locally with a password-derived AES-256-GCM key, stored only in browser IndexedDB
PasswordsNever stored in plaintextArgon2id hashing (PASSWORD_ARGON2ID)
Database AccessNo injection surface100% PDO prepared statements, zero raw query concatenation
State-Changing RequestsCSRF-protectedPer-session CSRF tokens with expiry, validated via hash_equals()
Cross-Origin RequestsRejected by defaultStrict Origin/Referer host matching, same-origin-only CORS headers
Bot / Abuse TrafficRate-limited & blocked, no message-linked IP loggingRedis-backed sliding-window rate limiting (auto-falls back to file storage if Redis is unavailable) β€” request counters expire on their own within the rate window (~30–60s); temporary IP blacklist entries auto-expire (default 24h); repeat offenders can be permanently "burned" and are kept blocked until manually cleared. IPs are used only for this abuse-prevention logic β€” never stored alongside messages or link data.
Data ExpiryEnforced, not optionalPer-message TTL and inactivity-based link auto-deletion (MySQL + Redis)
File/Config ExposureLocked down.htaccess denies direct PHP execution outside a strict whitelist, blocks .env, .sql, .log, and other sensitive extensions
Personal DataNot collectedNo accounts, no email, no phone number, no analytics/tracking scripts
SessionsHardened cookiesHttpOnly, Secure, SameSite=Lax, strict mode, read_and_close session handling

πŸ“„ License

Secret Gate is released under the GNU Affero General Public License v3.0 or later.

In short: you're free to use, modify, and self-host this project β€” but if you run a modified version as a network service, you must make your modified source available to your users too. See LICENSE for the full text.


Built by Sasiru Mindaka

If Secret Gate is useful to you, consider dropping a ⭐ β€” it helps the project reach more people who care about privacy.

Contributors

sasiru-mindaka/Secret-Gate

A zero-knowledge, ephemeral messaging engine built with pure native PHP & Web Crypto API. No frameworks, no dependencies, no server access to secrets.

0

stars

1

commits

PHP

primary language

Sep 12, 2026

updated

secretgate.site
ephemeral-messaging
php8
security
web-crypto-api
zero-knowledge-architecture

README

πŸ”’ Secret Gate

A zero-knowledge, ephemeral messaging engine that never sees your secrets.

Pure Native PHP. No frameworks. No bloat. No trust required.

PHP MySQL Redis License: AGPL v3

Live Demo Β· Report a Bug Β· Request a Feature


Overview

Secret Gate is a self-hostable, ephemeral messaging engine for people who want an anonymous inbox without handing a server the keys to read it. You generate a link, share it, and anyone can drop an encrypted message into it β€” but the encryption/decryption happens entirely in the visitor's browser using the Web Crypto API. The server only ever stores ciphertext it cannot decrypt.

There's no signup, no email, no phone number, and no tracking. Just a keypair, a password, and a link. When a message or link expires, it's gone β€” enforced at the database and cache layer, not by a "trust us" policy.

It was built deliberately without a framework. No Composer dependency tree, no node_modules, no build step. ~10 PHP files you can read top to bottom in an afternoon, deployable on almost any LAMP-style stack.


✨ Key Features

  • πŸ” True Zero-Knowledge Encryption β€” Hybrid RSA-OAEP (2048-bit) + AES-256-GCM performed client-side via window.crypto.subtle. The server stores only ciphertext and a public key; it is mathematically incapable of reading message contents.
  • πŸ—οΈ Server-Side Private Key, Zero Exposure β€” Each link's private key is wrapped with a password-derived AES-256-GCM key (PBKDF2, 300,000 iterations) and held only in the browser's IndexedDB vault β€” never transmitted or logged.
  • πŸͺΆ Zero Framework Bloat β€” No Laravel, no Symfony, no Composer sprawl. Just clean, native, auditable PHP.
  • ⏳ Ephemeral by Design β€” Per-link message TTLs and inactivity-based account expiry, enforced via MySQL expires_at columns and Redis TTL keys β€” not a soft "please delete this" toggle.
  • πŸ›‘οΈ Hardened Security Baseline β€” Argon2id password hashing, strict same-origin + CSRF token validation on every state-changing request, PDO prepared statements everywhere, and a whitelist-only file execution model via .htaccess.
  • πŸ•΅οΈ Privacy by Default β€” No accounts, no email, no PII collected or stored, ever. What we don't collect, we can't leak.
  • 🚦 Abuse-Resistant β€” Redis-backed sliding-window rate limiting, IP blacklisting/burning, honeypot fields, and Cloudflare Turnstile on public-facing forms.
  • πŸ“¦ Deploy Anywhere β€” Runs on any standard Apache + PHP-FPM + MySQL stack. No containers required (though it'll happily run in one).

πŸ—οΈ Architecture & Encryption Flow

Secret Gate uses hybrid encryption: an RSA-OAEP keypair identifies the inbox, while every individual message gets its own single-use AES-256-GCM key β€” the best of both worlds for a multi-sender, single-recipient model.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         1. INBOX CREATION (Owner)                           β”‚
β”‚                                                                             β”‚
β”‚   Browser                                                                   β”‚
β”‚   β”œβ”€ Generate RSA-OAEP 2048-bit keypair              (window.crypto.subtle) β”‚
β”‚   β”œβ”€ Derive AES-256-GCM wrapping key                 (PBKDF2, password)     β”‚
β”‚   β”œβ”€ Encrypt(private_key, wrapping_key)         ──►  store in IndexedDB     β”‚
β”‚   β”‚                                                  (never leaves device)  β”‚
β”‚   └─ POST public_key (JWK) + Argon2id(password) ──► Server                  β”‚
β”‚                                                                             β”‚
β”‚   Server (api.php)                                                          β”‚
β”‚   └─ INSERT INTO sb_links (public_id, public_key, password_hash)            β”‚
β”‚      (server never sees the private key or the plaintext password)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       2. SENDING A MESSAGE (Sender)                         β”‚
β”‚                                                                             β”‚
β”‚   Browser                             Server                                β”‚
β”‚   β”œβ”€ GET public_key(JWK) for link           ──►  fetch from sb_links        β”‚
β”‚   β”œβ”€ Generate one-time AES-256-GCM key                                      β”‚
β”‚   β”œβ”€ Encrypt(message, AES key)                                              β”‚
β”‚   β”œβ”€ Encrypt(AES key, RSA-OAEP public_key)                                  β”‚
β”‚   └─ POST { encrypted_message, wrapped_key } ──►  INSERT sb_messages        β”‚
β”‚                                                     (ciphertext only)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      3. READING MESSAGES (Owner)                            β”‚
β”‚                                                                             β”‚
β”‚   Browser                                Server                             β”‚
β”‚   β”œβ”€ Unlock local vault with password                                       β”‚
β”‚   β”‚  └─ Derive wrapping key (PBKDF2)   ──►  decrypt private key locally     β”‚
β”‚   β”œβ”€ GET encrypted messages            ──►  SELECT WHERE not expired        β”‚
β”‚   └─ Decrypt(AES key, private_key)     ──►  Decrypt(message, AES key)       β”‚
β”‚      (all decryption happens on-device ──►  server output stays opaque)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why hybrid encryption? RSA-OAEP lets any anonymous sender encrypt a message using only the public key β€” no shared secret, no prior handshake. AES-256-GCM keeps per-message encryption fast and keeps ciphertext size sane, since RSA alone isn't built for encrypting arbitrary-length payloads.


πŸš€ Prerequisites & Quick Start

Prerequisites

  • PHP 8.0+ with pdo_mysql, redis, and openssl extensions enabled
  • MySQL 8.0+ (or MariaDB equivalent)
  • Redis server (optional but recommended) β€” powers rate limiting and IP blacklisting/burning. If Redis isn't installed or reachable, Secret Gate automatically falls back to a file-based store under storage/ with the same behavior, so the app still runs without it
  • Apache with mod_rewrite and mod_headers
  • A Cloudflare Turnstile site/secret key pair (used on the feedback form)
  • A Telegram bot token + chat ID (used to relay feedback submissions β€” optional but required for the feedback form to function)

1. Clone the repository

git clone https://github.com/sasiru-mindaka/Secret-Gate.git
cd Secret-Gate

2. Configure your environment

Create an .env file (outside the web root is strongly recommended) with the following keys:

APP_ENV=production

# Database
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=your_db_user
DB_PASS=your_db_password
DB_NAME=schema

# Redis (optional β€” leave as-is if you're not running Redis;
# the app falls back to file-based rate limiting automatically)
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASS=

# Cloudflare Turnstile
CF_TURNSTILE_SECRET_KEY=your_turnstile_secret

# Telegram feedback relay
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id

By default, config.php loads .env from one directory above your web root (__DIR__ . '/../.env') β€” e.g. if your site lives at /var/www/html, place .env at /var/www/.env. This keeps credentials outside the publicly served directory even if the web server itself is ever misconfigured. Only change the path in loadEnv() if your server layout is different from this.

3. Import the database schema

mysql -u your_db_user -p schema < schema.sql

This creates the two core tables:

TablePurpose
sb_linksStores the inbox's public key, Argon2id password hash, and TTL settings β€” never the private key
sb_messagesStores ciphertext only, with an optional expires_at for self-destructing messages

4. Point your web server at the project root

Apache is expected β€” .htaccess handles routing, PHP file whitelisting, and blocks direct access to everything except the entry-point scripts (index.php, api.php, feedback.php, etc.). Make sure AllowOverride All is set for the vhost.


πŸ” Data Minimization & Security Matrix

LayerProtectionImplementation
Message ContentNever readable by the serverClient-side AES-256-GCM, per-message single-use key
Private KeysNever transmittedRSA-OAEP private key wrapped locally with a password-derived AES-256-GCM key, stored only in browser IndexedDB
PasswordsNever stored in plaintextArgon2id hashing (PASSWORD_ARGON2ID)
Database AccessNo injection surface100% PDO prepared statements, zero raw query concatenation
State-Changing RequestsCSRF-protectedPer-session CSRF tokens with expiry, validated via hash_equals()
Cross-Origin RequestsRejected by defaultStrict Origin/Referer host matching, same-origin-only CORS headers
Bot / Abuse TrafficRate-limited & blocked, no message-linked IP loggingRedis-backed sliding-window rate limiting (auto-falls back to file storage if Redis is unavailable) β€” request counters expire on their own within the rate window (~30–60s); temporary IP blacklist entries auto-expire (default 24h); repeat offenders can be permanently "burned" and are kept blocked until manually cleared. IPs are used only for this abuse-prevention logic β€” never stored alongside messages or link data.
Data ExpiryEnforced, not optionalPer-message TTL and inactivity-based link auto-deletion (MySQL + Redis)
File/Config ExposureLocked down.htaccess denies direct PHP execution outside a strict whitelist, blocks .env, .sql, .log, and other sensitive extensions
Personal DataNot collectedNo accounts, no email, no phone number, no analytics/tracking scripts
SessionsHardened cookiesHttpOnly, Secure, SameSite=Lax, strict mode, read_and_close session handling

πŸ“„ License

Secret Gate is released under the GNU Affero General Public License v3.0 or later.

In short: you're free to use, modify, and self-host this project β€” but if you run a modified version as a network service, you must make your modified source available to your users too. See LICENSE for the full text.


Built by Sasiru Mindaka

If Secret Gate is useful to you, consider dropping a ⭐ β€” it helps the project reach more people who care about privacy.

Contributors

Languages

PHP

100.0%