Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

About:Pharmacopedia.ext: Difference between revisions

From Pharmacopedia
[checked revision][checked revision]
0.9.8.7 close-out: Security & encryption refresh -- M3 perspective-invite hashing subsection added, backups section names 14-day active + up-to-180-day recovery-layer, honest limitations updated, OAuth 2.0 subsection notes iOS-autofill autocomplete attrs
0.9.8.7 Security section: correct live TLS config (HIGH:!aNULL, TLS1.0/1.1 allowed), fix LocalSettings.php owner (root:www-data), AbuseFilter 2/2 enabled, OATHAuth 5 recovery codes, HSTS preload pending
Line 68: Line 68:
TLS terminates at Apache 2 (mod_ssl) on the same host as the application. No CDN, no reverse proxy, no load balancer is in front. Certificate is a Let's Encrypt ECDSA leaf, renewed by <code>certbot.timer</code> (fires twice daily, renews when within 30 days of expiry). Private key on disk is mode 600 root:root in <code>/etc/letsencrypt/archive/pharmacopedia.wiki/</code>.
TLS terminates at Apache 2 (mod_ssl) on the same host as the application. No CDN, no reverse proxy, no load balancer is in front. Certificate is a Let's Encrypt ECDSA leaf, renewed by <code>certbot.timer</code> (fires twice daily, renews when within 30 days of expiry). Private key on disk is mode 600 root:root in <code>/etc/letsencrypt/archive/pharmacopedia.wiki/</code>.


Apache TLS config (from <code>/etc/letsencrypt/options-ssl-apache.conf</code>, loaded by every Pharmacopedia vhost):
Apache TLS config (live from <code>/etc/letsencrypt/options-ssl-apache.conf</code> + <code>/etc/apache2/mods-enabled/ssl.conf</code>):


  SSLProtocol        all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
  SSLProtocol        all -SSLv3
  SSLCipherSuite      ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
  SSLCipherSuite      HIGH:!aNULL
                    ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
                    ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:
                    DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
  SSLSessionTickets  off
  SSLSessionTickets  off


Effective enabled: TLS 1.2 + TLS 1.3 only. TLS 1.0 / 1.1 refused. <code>SSLSessionTickets off</code> preserves forward secrecy across server restarts.
Effective TLS range: TLS 1.0 through 1.3 (SSLv3 explicitly refused). The cipher suite shorthand <code>HIGH:!aNULL</code> covers all ECDHE + DHE forward-secrecy ciphers but does not exclude TLSv1.0 or TLSv1.1 at the protocol layer. Hardening to an explicit modern list and <code>SSLProtocol -TLSv1 -TLSv1.1</code> is queued; see ''Honest limitations'' below. <code>SSLSessionTickets off</code> preserves forward secrecy across server restarts.


HSTS:
HSTS:
Line 84: Line 80:
  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload


Two years, subdomains included, preload-ready.
Two years, subdomains included, preload-ready. The <code>preload</code> directive is present in the header; submission to the Chromium HSTS preload list is pending.


=== HTTP security headers ===
=== HTTP security headers ===
Line 150: Line 146:
The high-trust paths and their modes (no values published):
The high-trust paths and their modes (no values published):


  /var/www/mediawiki/LocalSettings.php        640 www-data:www-data
  /var/www/mediawiki/LocalSettings.php        640 root:www-data
   contains: $wgSecretKey, $wgUpgradeKey, $wgDBpassword,
   contains: $wgSecretKey, $wgUpgradeKey, $wgDBpassword,
             $wgTurnstileSecretKey, $wgSMTP['password'],
             $wgTurnstileSecretKey, $wgSMTP['password'],
Line 181: Line 177:
MediaWiki passwords are stored as PBKDF2 hashes (MW core default). Verified across every <code>user_password</code> row: HMAC-SHA-512 inner hash, 30,000 iterations, 64-byte derived key, 16-byte random salt per user. The <code>$wgPasswordConfig</code> default is unmodified; bcrypt is available in MW core but not enabled.
MediaWiki passwords are stored as PBKDF2 hashes (MW core default). Verified across every <code>user_password</code> row: HMAC-SHA-512 inner hash, 30,000 iterations, 64-byte derived key, 16-byte random salt per user. The <code>$wgPasswordConfig</code> default is unmodified; bcrypt is available in MW core but not enabled.


Two-factor authentication via the OATHAuth extension. Default module is TOTP per RFC 6238: HMAC-SHA-1 inner, 6 digits, 30-second time-step, 80-bit shared secret. Ten recovery codes per user, each a random 10-character string, hashed at rest, consumed on use. WebAuthn / FIDO2 (passkey) is available in the same extension and is the operator's first-choice path.
Two-factor authentication via the OATHAuth extension. Default module is TOTP per RFC 6238: HMAC-SHA-1 inner, 6 digits, 30-second time-step, 80-bit shared secret. Five recovery codes per user, each a random string, hashed at rest, consumed on use. WebAuthn / FIDO2 (passkey) is available in the same extension and is the operator's first-choice path.


==== AdminCrypto ====
==== AdminCrypto ====
Line 234: Line 230:


* Cloudflare Turnstile gates account creation, repeated failed logins, URL-bearing edits, and email-sending. Editors are not challenged on normal edits (a deliberate friction trade-off).
* Cloudflare Turnstile gates account creation, repeated failed logins, URL-bearing edits, and email-sending. Editors are not challenged on normal edits (a deliberate friction trade-off).
* The AbuseFilter extension is installed and configured; the active-rule set is small today and the lane will grow as live-fire patterns emerge.
* The AbuseFilter extension is loaded with 2 rules, both enabled as of 0.9.8.7. Rule 1: spam-keyword block (block + disallow). Rule 2: new-account edit throttle (throttle). The active rule set is small; the lane will grow as live-fire patterns emerge.
* Every file or image upload is scanned by ClamAV before the file moves into the persistent store. The scanner is run via <code>clamdscan</code> (persistent daemon, fast) with a fallback to <code>clamscan</code>. The gate is fail-closed: exit 0 = clean (proceed), exit 1 = infected (reject + unlink), any other exit = error (reject + unlink). A scanner crash never becomes a pass.
* Every file or image upload is scanned by ClamAV before the file moves into the persistent store. The scanner is run via <code>clamdscan</code> (persistent daemon, fast) with a fallback to <code>clamscan</code>. The gate is fail-closed: exit 0 = clean (proceed), exit 1 = infected (reject + unlink), any other exit = error (reject + unlink). A scanner crash never becomes a pass.
* fail2ban (see ''SSH + host'' above) bans abusive IPs at the network layer.
* fail2ban (see ''SSH + host'' above) bans abusive IPs at the network layer.
Line 247: Line 243:
* '''No CDN, no DDoS layer.''' One VM, three open ports, UFW + fail2ban. Hostile traffic can take the site down; it cannot exfiltrate.
* '''No CDN, no DDoS layer.''' One VM, three open ports, UFW + fail2ban. Hostile traffic can take the site down; it cannot exfiltrate.
* '''Some application-layer key rotation is "never" by design.''' The voter-hash secret and the AdminCrypto Mode B master key cannot rotate without breaking either anonymity or existing wrappings respectively. Loss of either key has the obvious one-time consequence; trading that off against the alternative (re-encrypt every historical record) was the deliberate choice.
* '''Some application-layer key rotation is "never" by design.''' The voter-hash secret and the AdminCrypto Mode B master key cannot rotate without breaking either anonymity or existing wrappings respectively. Loss of either key has the obvious one-time consequence; trading that off against the alternative (re-encrypt every historical record) was the deliberate choice.
* '''Two enabled AbuseFilter rules.''' The pipeline is in place; the rule set is small. Honest signal: this is plumbing for future use, not active filtering today.
* '''TLS allows TLSv1.0 and TLSv1.1.''' The live Apache literal is <code>SSLProtocol all -SSLv3</code>, which does not exclude older TLS protocol versions at the protocol layer. Hardening to an explicit modern cipher list with <code>SSLProtocol -TLSv1 -TLSv1.1</code> is queued.
* '''Perspective-invite tokens are half-migrated to hashed storage.''' 0.9.8.7 added the <code>pvi_token_hash</code> column, backfilled it, and switched the lookup to hash-first; the cleartext <code>pvi_token</code> column is still present so the dual-write fallback works during the deploy edge. 0.9.8.8 drops the cleartext column and the fallback branch, at which point a database-read attacker can no longer extract usable invite tokens. Severity in the interim: an attacker with DB read can still submit a perspective under a planted invite identity; not access to medical data.
* '''Perspective-invite tokens are half-migrated to hashed storage.''' 0.9.8.7 added the <code>pvi_token_hash</code> column, backfilled it, and switched the lookup to hash-first; the cleartext <code>pvi_token</code> column is still present so the dual-write fallback works during the deploy edge. 0.9.8.8 drops the cleartext column and the fallback branch, at which point a database-read attacker can no longer extract usable invite tokens. Severity in the interim: an attacker with DB read can still submit a perspective under a planted invite identity; not access to medical data.