Pretty Secure Secret Transmissions. A WordPress plugin by Linchpin for sharing one-time, expiring secrets. The secret is encrypted in the sender's browser before it is sent, the server stores ciphertext it has no key for, the link carries the only key, and the secret is destroyed the moment it is viewed.
Please see CHANGELOG.md for the latest information on the plugin.
| Workflow | Status |
|---|---|
| Release | |
| PHP | |
| JavaScript | |
| Plugin Check | |
| WP Version Check | |
| WordPress.org Deploy |
Psst is a zero-knowledge design. Encryption and decryption happen only in the browser.
- The sender's browser generates a random 256-bit key and encrypts the text with AES-256-GCM using the Web Crypto API. Only the ciphertext is posted to WordPress.
- The key travels in the share link's
#fragment, which browsers never send in an HTTP request:https://example.com/s/{id}/#{key}. It is not logged, not stored, and never reaches the server. A create request that carries a field namedkey,passphrase,secret,plaintext, ormessageis rejected outright. - The recipient opens the link and sees an interstitial. Nothing is consumed by a
GET, so link previewers, mail scanners, and reloads cannot burn the secret. - Clicking View Secret sends a JSON
POSTthat claims the row with a single conditionalUPDATE, returns the envelope exactly once, and hard-deletes it in the same request. The recipient's browser decrypts locally with the key from the fragment, then scrubs the fragment from the URL. - An optional pass phrase is folded into the key derivation in the browser and checked locally against a stored check value, so a typo does not cost the secret and the pass phrase is never transmitted.
Administrators can see that a secret exists, when it expires, and shred it. They cannot read it, and neither can anyone with the database, because there is no key on the server to read it with. Every claim above is asserted by the checked-in unit and end-to-end suites. The full walkthrough is in How the encryption works.
One opt-in feature trades this away, and only for the secrets it is used on. Emailing a share link means giving the server the key, because the server is what sends the mail — and the key then sits in the recipient's mailbox. It is off by default and the sender is shown the tradeoff before choosing it. See Accounts.
| Feature | Details |
|---|---|
| Browser-side encryption | AES-256-GCM with HKDF-derived keys, PBKDF2 for pass phrases, plain Web Crypto, no dependencies |
| One-shot reveal | Atomic conditional claim; concurrent callers race for one row and the loser gets a 404 |
| Preview-safe links | GET never consumes; the reveal requires Content-Type: application/json and answers 415 to anything else |
| Local pass-phrase retry | Wrong pass phrases are detected in the browser against a check value with no second request |
| Expiry | Eleven choices from five minutes to one week, enforced by Action Scheduler and again at read time |
| Shred | Sender's one-time management token or an administrator can destroy a secret before it is read |
| Gutenberg blocks | psst/secret-form and psst/secret-viewer, plus psst/login-form, psst/register-form and psst/account for the optional account layer |
| Hardening | no-store, noindex, no-referrer, nosniff, X-Frame-Options, DONOTCACHEPAGE, opt-in CSP on secret pages |
| Abuse controls | Per-IP and global rate limits, honeypot, optional Cloudflare Turnstile that fails closed |
| Optional accounts | Front end sign in, registration and an account area; a signed-in sender's record of what they sent (metadata only), an optional wp-admin lockout, and optional email delivery. All off by default |
| Admin screen | Settings, a metadata-only list of live secrets with shred, and a health check; under Settings or Mantle |
| Extensibility | Sixteen actions and thirty-six filters. Only psst_share_email_body, part of the opt-in email feature, ever sees a key; nothing else receives a key, pass phrase, or plaintext |
- WordPress 6.9 or later, with a block theme
- PHP 8.3 or later
- HTTPS. The Web Crypto API runs only in secure contexts.
- Composer (for development)
Psst is on the WordPress.org plugin directory. Install it from Plugins → Add New and activate it.
It is also published to Linchpin's private Composer repository for Composer-managed sites.
composer require linchpin/psstActivating the plugin creates two pages, Share a Secret (/share/) holding the create
pattern and Secret (/s/) holding the viewer block, and registers the /s/{id}/ route.
Make the create page your front page or link to it, then review Settings → Psst
(Mantle → Psst when Mantle is installed).
Release zips are attached to each GitHub release and can be uploaded from the Plugins screen. See Installation for the details, including what to check after a Composer deploy that never fires activation.
In the plugin root directory, you can run:
Installs JavaScript dependencies for the root admin app and the blocks/ project.
Builds the admin app into build/ and the two blocks into blocks/build/. Neither directory
is committed, so a fresh checkout needs this before the editor will load.
Runs the browser crypto vectors under Node.
Runs the Playwright suite against WordPress Playground.
Runs every static gate (syntax lint, PHPCS, php-cs-fixer, PHPStan) and then the PHPUnit suite.
Builds the distributable build/psst.zip.
- Run
composer installto install PHP dependencies, including Action Scheduler - Run
npm run install:allto install JavaScript dependencies - Run
npm run build:all, ornpm run startandnpm run start:blocksfor watch builds - Run
npm run playground:startfor a throwaway site onhttp://localhost:9400
- Controllers, Models, Helpers under
includes/, namespaceLinchpin\Psst, discovered and booted byCore\Bootstraponplugins_loaded - Blocks under
blocks/src/,apiVersion3, server-rendered with Interactivity API stores - Protocol in one file,
blocks/src/shared/crypto.js, validated server-side byModel\Envelope - REST API in the
psst/v1namespace, hidden from the index - Admin app under
src/admin/, React with WordPress components and DataViews, wrapped in the Linchpin admin chrome from@linchpinagency/ui
Full documentation lives in docs/ and is published to the Linchpin
docs site by the sync-docs workflow.
| Section | Contents |
|---|---|
| Getting started | Requirements, installation, configuration, your first secret |
| Guides | How the encryption works, accounts, hardening, upgrading from 1.x |
| Reference | REST API, hooks, blocks and patterns, development |
| Troubleshooting | Symptom-to-check reference |
Version 2 is a rewrite. On first load it removes the 1.x secrets, which were encrypted with a
server-side key this version does not use, along with the old options and cron events, and
answers old /secret/... links with 410. If PSST_CRYPTO_KEY is still defined an admin
notice asks you to remove it. The Foundation-based psst theme is no longer needed. See
Upgrading from 1.x.
- GitHub Issues: Report bugs or request features
- Documentation: See
docs/
GPL-2.0-or-later