Hardening formseal-embed: tightening trust boundaries and operator responsibility
Just shipped v3.6.0 and v3.7.0 - two releases focused on hardening the browser runtime.
Runtime freeze layer
The biggest change in v3.6.0 is a new runtime hardening layer. I added fse.freeze.js - a bootstrap script that runs after all modules load and locks down the internal state:
- Deep-freezes the config copy (endpoint, public key, field definitions)
- Freezes all runtime modules (crypto, payload, validation, form)
- Locks the
__fse_internal__namespace as non-writable, non-configurable
After initialization, this fails:
window.__fse_internal__.FSE.publicKey = "attacker-key"
// TypeError: cannot assign to frozen object
[!NOTE] This isn't bulletproof against a fully compromised browser - that's not the threat it's designed for. It prevents accidental mutation and sloppy interference from third-party scripts, analytics, and compromised tag managers, which is the realistic concern.
HTTPS enforcement
formseal-embed now refuses to run on HTTP. The crypto module checks window.isSecureContext and throws if you're not on HTTPS - with a localhost exemption for development:
if (!window.isSecureContext) {
if (!(loc.hostname === "localhost" || loc.hostname === "127.0.0.1")) {
throw new Error("[fse/crypto] Secure context (HTTPS) required.");
}
}
No more accidental plaintext submissions on insecure sites.
Removed honeypot
The built-in honeypot field (_hp) is gone. Spam protection is a separate concern from encryption, and bundling them together meant the crypto layer was making assumptions it had no business making. Developers handle their own spam protection (honeypot, CAPTCHA, whatever) if they want it.
Fixed a lying warning
There was a bug where the validation warning said:
[fse/validate] Field 'name' exceeds maxLength (25 > 10). Value truncated to 10 chars before encryption.
We weren't actually truncating anything - we were blocking the submission entirely. Fixed:
[fse/validate] Field 'name' exceeds maxLength (25 > 10). Submission blocked.
New fse keygen command
v3.7.0 adds a one-liner for key generation:
fse keygen
Output:
{
"publicKey": "jLPrZQgckIRUecjSaFfbfutHDL4D9hzM1JoXU-DliT4",
"privateKey": "yOKv1BmkjnI7WvQNj-ZAwqMOfsrSVBZJ8abkmHN2wiA"
}
Keep this keypair somewhere safe. Never store it on your server. Loss of the private key means permanent loss of decryption ability - there is no recovery path.
Previously the docs had a Python snippet you had to copy-paste and run manually. Now it's a one-liner.
Summary
These releases are about hardening the trust boundary:
- Runtime freeze prevents late-stage mutation
- HTTPS enforcement stops insecure deployments
- Honeypot removal keeps concerns separated
- The validation warning now says what actually happens
fse keygenreplaces the copy-paste setup snippet
The project is at v3.7.0 now. Full changelog on GitHub.