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 close-out: star hold-to-expand model, editor enhancements module, vote removal (boss-claude)
 
(One intermediate revision by the same user not shown)
Line 6: Line 6:


* Structured medicine pages via the <code><nowiki>{{MedTemplate}}</nowiki></code> template
* Structured medicine pages via the <code><nowiki>{{MedTemplate}}</nowiki></code> template
* Per-user rating on effects, problems, titration strategies, anecdotes, and drug-drug interactions (continuous 0–100 sliders, ±100 valence; no 0–5 likert anywhere)
* Per-user rating on effects, problems, titration strategies, anecdotes, and drug-drug interactions (continuous 0–100 sliders, ±100 valence; no 0–5 likert anywhere); ratings use a hold-to-expand star widget (300 ms press, spring animation, drag-commit with pixel-travel + value-delta guards); voters can remove their own committed rating
* Binary AND choice/multi voting on arbitrary content (<code>type="single"</code> / <code>type="multi"</code> with 2-5 options, results-visibility policy per element)
* Binary AND choice/multi voting on arbitrary content (<code>type="single"</code> / <code>type="multi"</code> with 2-5 options, results-visibility policy per element)
* Two-perspective data capture (personal vs. provider) wherever clinically meaningful
* Two-perspective data capture (personal vs. provider) wherever clinically meaningful
Line 39: Line 39:
* '''Backend (PHP):''' <code>includes/</code>, one class per parser tag, store, special page, or API module. Auto-loaded under <code>MediaWiki\Extension\Pharmacopedia\</code>. Assessment classes under <code>includes/Assessments/</code>. API modules under <code>includes/Api/</code>.
* '''Backend (PHP):''' <code>includes/</code>, one class per parser tag, store, special page, or API module. Auto-loaded under <code>MediaWiki\Extension\Pharmacopedia\</code>. Assessment classes under <code>includes/Assessments/</code>. API modules under <code>includes/Api/</code>.
* '''Frontend (JS / CSS):''' multiple ResourceModules per surface area:
* '''Frontend (JS / CSS):''' multiple ResourceModules per surface area:
** <code>ext.pharmacopedia</code>: main IIFE (chip-picker, dx autocomplete, BFI-10 compute, vote logic for both binary and choice/multi)
** <code>ext.pharmacopedia</code>: main IIFE (chip-picker, dx autocomplete, BFI-10 compute, vote logic for both binary and choice/multi, hold-to-expand star rating model with spring animation and drag-commit, vote removal)
** <code>ext.pharmacopedia.styles</code>: base extension stylesheet (self-hosted Geist / Newsreader / Source Serif fonts, core component styling)
** <code>ext.pharmacopedia.styles</code>: base extension stylesheet (self-hosted Geist / Newsreader / Source Serif fonts, core component styling)
** <code>ext.pharmacopedia.blocksave</code>: debounced autosave per block (race-safe)
** <code>ext.pharmacopedia.blocksave</code>: debounced autosave per block (race-safe)
Line 49: Line 49:
** <code>ext.pharmacopedia.perspective</code>: observer-perspective form enhancement (slider readout, progress, consent/delete confirm)
** <code>ext.pharmacopedia.perspective</code>: observer-perspective form enhancement (slider readout, progress, consent/delete confirm)
** <code>ext.pharmacopedia.administer</code>: the administer-to-others surfaces (take-flow slider readout + "Not sure" toggling, owner-hub styling)
** <code>ext.pharmacopedia.administer</code>: the administer-to-others surfaces (take-flow slider readout + "Not sure" toggling, owner-hub styling)
** <code>ext.pharmacopedia.editor</code>: editor enhancements loaded on <code>action=edit/submit</code>; smart paste converts bare PMID or DOI from the clipboard into a formatted <code>&lt;ref&gt;</code> tag (PubMed eutils / CrossRef); house-rules linter flags banned terms and em-dashes on submit with a dismissable warning; quick-ref stub (Ctrl+Alt+R) inserts a journal-article <code>&lt;ref&gt;</code> skeleton
** <code>ext.pharmacopedia.observation</code>: quick-add observation textarea + live preview
** <code>ext.pharmacopedia.observation</code>: quick-add observation textarea + live preview
** <code>ext.pharmacopedia.refupgrade</code>: bulk linker for free-text → structured refs
** <code>ext.pharmacopedia.refupgrade</code>: bulk linker for free-text → structured refs
Line 68: Line 69:
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 81:
  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 147:
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 178:
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 231:


* 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 244:
* '''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.