Security audit hardening & edge anti-spoofing
Beyond the layered protections in the security model,
BOA has run codebase-wide security audits whose outcomes mostly have no knob
— they are patch-and-forget. This page collects those operator-relevant results:
the filesystem permission baseline, the 5.9.5 audit, the 5.10.1 edge
anti-spoofing work, and the edgetest checker that verifies the whole edge.
Filesystem permission baseline
The 5.9.5 audit tightened default file/directory permissions across the board. Notably:
- The
/opt/tmpscratch root is sticky1777(it used to be a recursive777, which let one tenant tamper with or delete another's scratch files). The sticky bit prevents cross-uid deletion, matching the model already used for/var/tmp/fpmand/opt/user/{gems,npm}. /opt/tmp/boais forced to0755directories /0644files.- The
daily.shchown -Lsymlink-following path was closed.
Security audit hardening (5.9.5)
BOA 5.9.5 carried a codebase-wide security audit across all repositories. The operator-relevant outcomes (most have no knob — they are patch-and-forget):
- Credentials off the command line. All MySQL/Percona invocations now use
--defaults-extra-file/--defaults-file=/root/.my.cnfinstead of passing the password as an argument, where it was briefly visible in the process list to any local user. Legacy_SQL_PSWDreads were removed throughout, andxmassno longer exposes the SQL root password. This pairs with the hidepid mount (protection #13 on the security model page). - Shell-injection hardening. Component downloads default to HTTPS with
certificate validation (closing an HTTP-mirror MITM vector);
mysql_cleanupuses a strict SQL-identifier allowlist; new internal helpers_is_safe_ident,_validate_safe_dir,_validate_path_prefixand_chmod_safevalidate values before they reach the shell. Theaegir-shell-injection-auditfixes were applied to Provision and thehosting_civicrm,hosting_git,hosting_tasks_extramodules, closing HTTP-basic-auth-password and special-character injection indrush_shell_exec()paths. - HTTP Basic Auth input validation (UI layer). The
hosting_tasks_extrabundled with 5.10.3 adds the user-visible layer of the same audit: the site node's HTTP Basic Auth username, password and message fields reject shell-unsafe input at form-validation time — control bytes (NUL / LF / CR) plus the shell metacharacters< > | & * ? ; ! ( ) ' " \ ` $ { }— because these values flow into Drush subprocess commands and Nginx config templates. A value containing any of these draws an immediate form error instead of being persisted; if a client reports that error, this character set is why. The IP-whitelist field keeps its stricter allowlist (letters, digits, space,/,.,:). On the backend, the health-check task escapes its site-name-derived arguments withescapeshellarg()beforedrush_shell_exec()interpolation. mybackupqueue-file injection closed. The.ftp → sysuserqueue-file path is now validated;lock.incwas rewritten to brace-expansion auto-fd allocation instead ofeval.tar-symlink privilege escalation closed. A path where NOPASSWD-sudotarhelpers could be coaxed into following a symlink to write into root-owned locations was closed.- Config-template hardening (PHP / Nginx / MySQL / SSH / sysctl). PHP
session.use_strict_mode = 1andexpose_php = Off(cli/FPM templates); MySQLlocal_infile = OFF(my.cnf.txt); extendedsysctl.confhardening; the insecurewgetshell alias removed; Nginx-template hardening. SSH specifics are in SSH + SFTP hardening.
Edge / anti-spoofing hardening (5.10.1)
- Cloudflare-aware real client IP. BOA resolves the real visitor behind
Cloudflare (
real_ip_header CF-Connecting-IPplus a BOA-managed, auto-refreshed Cloudflare edge-range list viacloudflare_realip.sh), so rate-limit keys,REMOTE_ADDR, bans and logs reflect the real visitor, not the CDN edge. PHPREMOTE_ADDRis pinned to the original TCP peer ($realip_remote_addr) so Drupal's reverse-proxy handling cannot be fed a spoofed client address. The IDS scores the realip client — see the Abuse Guard topic. - Hardened external fetches, fail-closed. Insecure
curl -kwas dropped (TLS is now verified) and ad-hoc fetches standardised on_crlGet --fail, so an HTTP error yields an empty body rather than an error page parsed as hostile CIDRs. Strict IPv4/IPv6 octet+prefix validation was added to theip_access,nginx-deny,cloudflare-realipandcsf.allowgenerators: a malformed-but-shape-valid token is skipped fail-closed and can never breakconfigtestfleet-wide (validators reject0.0.0.0/0and any/0).
Verifying the edge stack — edgetest
edgetest is a guided one-command pass/fail checker for the whole security edge:
AI bot policy, Cloudflare realip, CSF→nginx web bans, and per-site AI/IP
controls. It is read-only by default; --full runs state-changing proofs
reverted by an idempotent trap, and --remote does cross-box ip_access checks.
Among its assertions it confirms the evasive Perplexity-User block and the
Google-Agent allow.
Related
- Security model — the layered protections these audits reinforce, and the hidepid mount (protection #13) the credential work pairs with.
- Strict binary permissions — the
_STRICT_BIN_PERMISSIONSgroup-read and shell-switch hardening. - SSH + SFTP hardening — the SSH-side config-template outcomes.
- Whole-site IP allow-list (
ip_access) — one of the generators whose IP validation the 5.10.1 work hardened. - Abuse Guard — the realip-scored IDS layer.