Repository navigation
v1.1.0 — security hardening (full-audit remediation)
Security hardening release. A full-surface security audit (2026-08-22, six parallel passes over crypto, codec, standalone runtime, MCP loader, CLI, editor/supply-chain) found no path to forge provenance, compromise a key, or leak a secret — the findings were availability (DoS) and hardening issues. This release fixes all of them: the one High, all nine Medium, and the selected Low findings. Key findings were independently reproduced and verified by a second, separate review before remediation.
Behavioral changes (read before upgrading)
1. verify now rejects malleated (high-s) ECDSA signatures.
For any ECDSA signature (r, s), the twin (r, n − s) also verifies. Accepting both meant every signed capsule had two valid byte-identities — breaking byte-reproducibility, dedup-by-signature, and the planned provenance chaining (prev), where a third party could fork a chain without holding any key. The signer has always emitted canonical low-s (deterministic RFC 6979), so capsules produced by the editor or CLI are unaffected.
Compat note: capsules signed through the legacy Web Crypto fallback path (non-extractable keys, random-k) are high-s with ~50% probability and will now read INVALID. Re-sign them — or normalize the existing signature to its low-s twin (
s' = n − s), which needs no private key and preserves the signer.
2. base64url decoding is strictly canonical. Foreign characters, padding, and non-zero trailing bits are rejected in the core decoder, the CLI, the MCP loader, and the standalone runtime. Encoded strings act as identities (trust comparison, key pinning, capsule hashing); canonicality makes one capsule own exactly one name. Capsules produced by our tooling were always canonical.
3. Passphrase floor enforced at derivation. deriveIdentityFromPassphrase now throws below the floor (≥ 16 chars, score ≥ 3) instead of silently deriving a dictionary-attackable key. Previously the floor was editor-UX-only.
Hardening
- Decompression bombs bounded in CPU, not just memory (the audit's only High): compressed input is rejected above 17 MiB before any inflate work, then slice-fed so the loop actually stops past the 16 MiB output cap — a 512 MB bomb now aborts in ~120 ms instead of inflating fully. The standalone runtime's
DecompressionStreampath gains the same output cap (it previously had none) — a hostile capsule can no longer freeze or OOM the reader's tab. - Standalone capsule CSP:
script-srcdrops'unsafe-inline'for per-filesha256-hash pins of the two inline scripts. Any future regression that reintroduces a raw script sink fails closed as a console error instead of executing in the recipient's browser. - Envelope size capped before any decoding work; two quadratic (ReDoS-class) regexes in the public markdown paths replaced with linear scans.
- Core
sanitizePagenow covers header/footer fields (logo, tagline, copyright, footer links through the URL allowlist);style.bg/fgre-validated against the hex pattern at the CSSOM sink. - CLI:
~/.altwebcreated0700,identity.jsonwritten with mode0600at creation (no world-readable window); terminal-escape (ANSI/OSC) stripping on human-facing output so capsule content cannot repaint a verify verdict. - MCP SSRF filter now parses IPv6 numerically: uncompressed literals (
0:0:0:0:0:0:0:1), NAT64 (64:ff9b::/96), 6to4 (2002::/16), and reserved::/96are all refused; v4-mapped addresses are judged on the embedded address. - Editor zeroes the signing passphrase from state right after identity unlock.
Verification
137 tests across core/MCP/editor (114 in core, three new suites), including: the actual embedded runtime executed with the real DOMPurify (previously stubbed in every test), CSP hashes recomputed independently from the emitted bytes, malleated-signature and decompression-bomb regressions with machine-relative timing bounds.
npm: altweb@1.1.0 · altweb-context@1.1.0