v2.17.1 (Patch — security hardening)
Five must-fix findings from a draconian security/logic review of the existing code (no new features), each verified against source and pinned by a regression test.
Security
- Path traversal via client-certificate Common Name.
create_client_certificatebuilt the on-disk identifier straight from the caller-supplied Common Name (only spaces replaced) and ranmkdir(parents=True)before any other check, so a CN such as../../../../tmp/evilcreated — and wrote a CA-signed key, certificate and metadata into — a directory outside the managed cert tree (single and batch issuance, operator role). The pre-existing_validate_identifierguard was only ever applied on retrieval, never to the value that constructs the identifier. The Common Name is now slugified at the construction site (covering every caller) to a filesystem-safe token, with a containment assertion beforemkdir. Benign Common Names slugify to themselves so existing identifiers and paths are unchanged, and the full Common Name is still preserved verbatim in the certificate subject and metadata. - Backup restore downgraded private keys to world-readable.
restore_unified_backupset0600only for the exact filenameprivkey.pemand0644for everything else, but certbot keeps the real key bytes inarchive/<domain>/privkeyN.pem(thelive/privkey.pemsymlink points at them) and the ACME account key inaccounts/.../private_key.json— both retained in the unified backup — so a restore actively rewrote served key material and the account key to0644. Restore now mirrors certbot's own permissions: every private-key filename is locked to0600and public material (cert/chain/fullchain plus metadata json) stays0644. GET /api/metricswas reachable unauthenticated. The endpoint carried no auth decorator while its sibling info endpoints (/api/cache,/api/backups) require theviewerrole. It now requires a viewer credential to match its peers; the Prometheus scrape target remains the separate, intentionally public/metricsroute.- A disabled, demoted, or deleted user kept their live session. The role was snapshotted into the session at login and session validation only checked expiry, so disabling, demoting, or deleting a user left an already-issued session valid — with the old role — for up to the session lifetime (default 8h), with no kill switch for a compromised or just-removed account. Those transitions now invalidate the user's in-memory sessions, forcing re-authentication under the new state; an unrelated edit (e.g. email) does not log the user out.
Fixes
- Webhook secrets clobbered on a generic settings save. v2.15.0 restored masked webhook secrets on the dedicated
/api/notifications/configroute, but the genericPOST /api/settingspath (the full-settings round-trip used by the UI and integration tests) still merged thenotificationssubtree without restoring secrets nested in thechannels.webhookslist —_deep_merge_dictreplaces lists wholesale and_strip_masked_valuesonly walks dicts — writing the mask sentinel over the real HMAC secret / bot token. The corruption was silent: deliveries then signed with********.atomic_updatenow restores masked list secrets generically for every deep-merge key, so both paths behave identically.