Skip to content

Powered by Grav

Security audit hardening & edge anti-spoofing

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/tmp scratch root is sticky 1777 (it used to be a recursive 777, 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/fpm and /opt/user/{gems,npm}.
  • /opt/tmp/boa is forced to 0755 directories / 0644 files.
  • The daily.sh chown -L symlink-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.cnf instead of passing the password as an argument, where it was briefly visible in the process list to any local user. Legacy _SQL_PSWD reads were removed throughout, and xmass no 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_cleanup uses a strict SQL-identifier allowlist; new internal helpers _is_safe_ident, _validate_safe_dir, _validate_path_prefix and _chmod_safe validate values before they reach the shell. The aegir-shell-injection-audit fixes were applied to Provision and the hosting_civicrm, hosting_git, hosting_tasks_extra modules, closing HTTP-basic-auth-password and special-character injection in drush_shell_exec() paths.
  • HTTP Basic Auth input validation (UI layer). The hosting_tasks_extra bundled 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 with escapeshellarg() before drush_shell_exec() interpolation.
  • mybackup queue-file injection closed. The .ftp → sysuser queue-file path is now validated; lock.inc was rewritten to brace-expansion auto-fd allocation instead of eval.
  • tar-symlink privilege escalation closed. A path where NOPASSWD-sudo tar helpers 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 = 1 and expose_php = Off (cli/FPM templates); MySQL local_infile = OFF (my.cnf.txt); extended sysctl.conf hardening; the insecure wget shell 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-IP plus a BOA-managed, auto-refreshed Cloudflare edge-range list via cloudflare_realip.sh), so rate-limit keys, REMOTE_ADDR, bans and logs reflect the real visitor, not the CDN edge. PHP REMOTE_ADDR is 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 -k was 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 the ip_access, nginx-deny, cloudflare-realip and csf.allow generators: a malformed-but-shape-valid token is skipped fail-closed and can never break configtest fleet-wide (validators reject 0.0.0.0/0 and 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.

© 2026 BOA Documentation. All rights reserved.