Skip to content

Powered by Grav

BOA security model — multi-Ægir architecture

BOA security model — multi-Ægir architecture

BOA's foundational threat model: multiple Octopus tenants share one host but cannot affect each other or escape their boundaries. This page lays out the model and the built-in protections that implement it. The deep configuration knobs those protections expose — PHP disable_functions, strict binary permissions, the Node/NPM gate, and the codebase-wide audit hardening — now live on their own focused pages, gathered under Where the hardening knobs live at the end.

The trust model

A BOA host has three trust levels:

TXT
1. root (operator)        ← full host control
       ↓ provisions
2. aegir (system user)    ← runs Master Ægir; near-root over all
                            site DBs across all instances
       ↓ provisions
3. oN + oN.ftp (tenant)   ← lshell-restricted; can touch ONLY
                            their own Octopus instance's data

The architectural invariants:

  • Tenants cannot reach the aegir system user. An lshell escape equals catastrophic compromise of every site on the host.
  • Tenants cannot see other tenants' files / DBs. Per-Octopus separation is enforced via Unix permissions plus the lshell chroot.
  • The web server has no write access to Drupal codebases. Even a compromised PHP application cannot modify its own code.
  • Only Ægir-recognised PHP files are served. The Nginx vhost whitelists Drupal entry points.

Never grant anyone access to the aegir system user. Only ever hand out restricted lshell SSH accounts (oN.ftp per tenant plus extra client subaccounts) and non-admin Ægir control-panel accounts. The per-tenant restriction layer is documented in lshell + manage_ltd_users.

The built-in protections

1. Encrypted connections only

Account access is restricted to three encrypted channels:

  • SSH — ed25519 key auth preferred; password auth with 90-day expiry as fallback.
  • SFTP — FTP over SSH.
  • FTPS — FTP over SSL.

No plain FTP, no telnet, no HTTP-only admin pages.

2. Database credentials kept out of reach of site PHP

A site's settings.php has to be readable by the web user, because one PHP-FPM pool per Octopus instance serves every site in that instance — and that instance's Ægir panel too. So the credentials are not kept in it: the file names them, and the values arrive from the vhost per request. The files that do hold the literal values carry the users group, which the web pool user is not in. Full detail on DB credentials.

3. Restricted PHP scripts

The Nginx vhost only allows Drupal's known PHP entry points (index.php, update.php when enabled, cron.php, the hostmaster install scripts). Random PHP files dropped into the webroot do not execute. Combined with #4, this blocks the most common Drupal exploit chain ("vulnerable site takes a file upload → uploaded PHP runs").

4. Web-server-readonly Drupal codebases

The Drupal platform directories under ~/static/, ~/distro/, ~/platforms/ (the tenant tree at /data/disk/oN/) are owned by the per-Octopus instance user oN — each tenant owns its own codebase; the master aegir user does not own tenant codebases.

The web server (www-data) has read-only access, so even a compromised PHP application cannot modify its own code.

The files/ and private/ directories are the exception — they are writable for legitimate Drupal use, using the sticky-bit pattern.

5. Web-server monitoring (Nginx Abuse Guard)

IPs exhibiting DoS-like access patterns get temporarily blocked for 15 minutes, escalating to a permanent block on repeat offence. Operators and tenants can whitelist their own IP by keeping an active SSH connection (the SSH-client set is harvested into every per-site allow-list — see ip_access).

Tunables in /root/.barracuda.cnf:

  • _NGINX_DOS_LIMIT — per-IP page-view threshold within the scan window (default 399).
  • _NGINX_DOS_LINES — number of access-log lines tailed per scan pass (the scan window; default 1999). This is a separate variable from _NGINX_DOS_LIMIT — do not conflate the two.
  • _NGINX_DOS_IGNORE — substring keyword filter (default doccomment).
  • _NGINX_DOS_IGNORE_PATHS — space-separated webhook/API endpoint URI prefixes the IDS never scores (parsed with a laundering-proof URI normaliser, not a substring match). Default: /shopify/webhook /quickbooks/webhook /stripe/webhook /paypal/webhook /github/webhook /gitlab/webhook /graphql /public-api /oauth2.
  • _NGINX_DOS_MODE — the per-IP scoring algorithm (default 2). Mode 1 is the more aggressive setting, adding a second tier of +5 increments on login, registration, node-add and search requests. This is the real crawl-aggressiveness knob on a BOA host.

_NGINX_DOS_MODE is the aggressiveness dial — set it to 1 in /root/.barracuda.cnf on a host under spambot or fake-UA crawler pressure. Re-check it after a BOA upgrade: autoupboa seeds the whole _NGINX_DOS_* block into /root/.barracuda.cnf from its own defaults and rewrites any line it finds, so a hand-set 1 is reset to the default 2 whenever a later release bumps the serial that gates the seeding block. The historical /root/.hr.monitor.cnf marker is not that dial: no code in BOA reads it, so creating it has no effect, and it is scheduled for retirement.

The deep scoring and ban mechanics — and these knobs' full defaults and rationale — live in the Abuse Guard topic: the complete tunable table is on Abuse Guard configuration, and the per-IP scoring detail on scan_nginx scoring.

6. Firewall monitoring (CSF + LFD)

Repeated failed SSH/SFTP/FTPS login attempts escalate temporary block → permanent block. Whitelisted IPs are not exempt from LFD login-failure tracking if abuse is detected. CSF lifecycle: CSF + LFD firewall.

7. Load management

When the host's load exceeds its configured ratio, the web server is temporarily disabled; normal service resumes once load has stayed below the resume threshold for several consecutive passes — resume is latched, never instant. This protects against undetected DoS where the Abuse Guard did not catch the pattern but real load damage is happening. Driven by second.sh (see Load control).

8. Port-scan + flood protection

Detected port scans and SYN floods escalate to a temporary 1-hour block then permanent. SYNPROXY-based; see SYNPROXY DDoS protection.

9. Perfect Forward Secrecy + HTTP/2 + HTTP/3

All HTTPS uses PFS cipher suites with HTTP/2 (or HTTP/3 where supported). Non-PFS-capable browsers fall back to classic TLS, so old browsers stay supported while modern browsers get the security upgrade.

10. PHP errors hidden from browsers

display_errors = Off in PHP-FPM. Errors are only visible via:

  • The server-side per-version FPM error log (/var/log/php/php<NN>-fpm-error.log — one host-wide directory, not per-Octopus).
  • Drupal watchdog (drush watchdog-show).
  • A configured .dev. URL alias (a debug mode that enables in-browser errors; not auto-present — the operator must add the dev.<domain> alias to the site).

11. Password expiration policy

SSH / SFTP / FTPS passwords expire every 90 days. Even with SSH keys in use, the underlying password expires and must be rotated. This is also why BOA sets account passwords with SHA512 (or Bcrypt if migrated — see password hashing).

12. Restricted admin access (uid=1)

The Drupal admin account (uid=1) is unavailable in Ægir to prevent misuse. The Ægir user has privileges equivalent to admin at the Ægir level, but the Drupal-level uid=1 super-user role is intentionally not handed out.

13. Restricted system binaries

BOA modifies access permissions on system binaries that could be abused by web shells (exec, passthru, etc.) so that even sites running older or vulnerable Drupal versions cannot reach the most dangerous binaries directly. Combined with _STRICT_BIN_PERMISSIONS=YES (see Strict binary permissions), the restriction is operator-configurable.

14. Process-list isolation (hidepid)

/proc is mounted hidepid=2 with only the adm group exempt (gid=adm; the aegir user is added to adm so Master Ægir's pgrep of system services still works). One Octopus tenant can no longer inspect another tenant's — or root's — process list, environment, or /proc/<pid>/cmdline.

This closes a cross-tenant credential-disclosure class: secrets that briefly appeared on a process command line (mysql -p<pwd>, mydumper --password=, duplicity b2://<id>:<key>@…) were readable from /proc by any local user. It pairs with the credential-off-the-command-line work in the 5.9.5 security audit.

Applied automatically at install/upgrade, persisted in /etc/fstab, and live-remounted (idempotent). Per-Octopus system users (o1, o2, …) and .ftp accounts are deliberately not in adm, so they keep own-process visibility only.

15. IPv6 disabled at the kernel (sysctl)

BOA ships every box with IPv6 disabled via kernel sysctl — net.ipv6.conf.{all,default,lo}.disable_ipv6 = 1 plus a belt-and-braces set of router-advertisement and autoconf keys (the shipped template's IPv6 block). The disable exists because half-configured IPv6 is a classic source of random-looking slowness: DNS still resolves AAAA records (getaddrinfo does not consult sysctl), so v6-capable clients try the v6 path first, time out against a black-holed address, and only then fall back to v4 — while v4-only clients see nothing wrong. With the stack disabled, every client gets the fast v4 path. The tell on a BOA box: ping6 and ip -6 addr show nothing while AAAA lookups still resolve.

The trap: a hand re-enable does not survive upgrades. BOA's sysctl config is not an edit — it is a whole-file template replace: on upgrade passes _sysctl_update() copies the shipped template over /etc/sysctl.conf (once per release serial) and reapplies it with sysctl -p, so deleting the IPv6 lines by hand quietly reverts on a later upgrade. The supported way to keep a local sysctl change is the refresh opt-out — _NO_SYSCTL_UPDATE=YES in /root/.barracuda.cnf — documented with its caveats on Host OS baseline. Mind that it freezes all of BOA's sysctl and system-limits tuning, not just the IPv6 block, so future kernel-tuning improvements stop arriving too.

Re-enabling IPv6 (at your own risk). Not a supported BOA configuration — the default exists because of consistent operational pain across the fleet — but if a specific host genuinely needs it:

  1. Verify the provider gives you a routed IPv6 prefix, not just SLAAC inside a private bubble.
  2. Publish AAAA records only for addresses the network can actually reach.
  3. Mirror your CSF v4 rules on the v6 side (ip6tables / CSF's IPv6 handling, csf6-style rules) — firewall asymmetry is the classic half-enabled failure, see CSF + LFD firewall.
  4. Remove BOA's IPv6-disable sysctl entries and set _NO_SYSCTL_UPDATE=YES so the template replace does not undo you.
  5. Reboot, then verify from a remote v6-capable host with curl -6 -v https://yoursite.example.com/.

If you cannot complete all five, leave IPv6 off — a partially enabled stack is slower than no stack.

AI bot policy (default-deny scrapers)

A per-class AI bot policy is enforced at the Nginx edge as a default-deny posture against scrapers and abusive agents:

  • AI training scrapers (GPTBot, ClaudeBot, CCBot, Bytespider, Amazonbot, Meta-ExternalAgent, AI2Bot, Diffbot, cohere-ai, …) are blocked by default (444), with a per-site train-allow opt-in. The predecessor $is_ai_crawler handling was already a hard deny (return 444), but keyed on bare vendor-name substrings, so it also caught the vendors' search and assistant fetchers — the over-blocking the per-class model exists to end.
  • Search / user / utility classes are allowed but per-vendor aggregate rate-limited (1–2 r/s).
  • A new evasive class (Perplexity-User) is blocked by default with per-site evasive-allow.
  • Forged opt-out tokens (Google-Extended, Applebot-Extended — robots.txt directives that never appear as real crawlers) are hard-blocked (444) universally.
  • Secret/config path probes (.env, .git, .aws, .ssh, credential *.json, …) are anchored-denied on the normalised URI even for plain-browser UAs.

Per-site control file: /data/disk/<oct>/static/control/ai/policy.txt. The full class table is in the Abuse Guard topic.

Where the hardening knobs live

The deep configuration knobs and audit outcomes that used to sit on this page now have their own focused homes:

  • PHP function restrictions (_PHP_FPM_DENY) — the per-pool disable_functions list, its two-level (host + per-Octopus) control and the shell_exec caveat: FPM capacity sizing.
  • Strict binary permissions (_STRICT_BIN_PERMISSIONS) — group-read stripping and the system-shell switch to dash: Strict binary permissions.
  • Node / NPM security gate — why Node/NPM/npx/scp are stripped from lshell and how to enable them: lshell + manage_ltd_users.
  • Filesystem baseline, the 5.9.5 audit, 5.10.1 edge anti-spoofing and edgetestSecurity audit hardening.

© 2026 BOA Documentation. All rights reserved.