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:
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 (default399)._NGINX_DOS_LINES— number of access-log lines tailed per scan pass (the scan window; default1999). This is a separate variable from_NGINX_DOS_LIMIT— do not conflate the two._NGINX_DOS_IGNORE— substring keyword filter (defaultdoccomment)._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 (default2). Mode1is the more aggressive setting, adding a second tier of+5increments 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 thedev.<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:
- Verify the provider gives you a routed IPv6 prefix, not just SLAAC inside a private bubble.
- Publish
AAAArecords only for addresses the network can actually reach. - 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. - Remove BOA's IPv6-disable sysctl entries and set
_NO_SYSCTL_UPDATE=YESso the template replace does not undo you. - 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-allowopt-in. The predecessor$is_ai_crawlerhandling 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-pooldisable_functionslist, its two-level (host + per-Octopus) control and theshell_execcaveat: 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
edgetest— Security audit hardening.
Related
- lshell + manage_ltd_users — the per-tenant restriction layer.
- CSF + LFD firewall — the network-edge layer.
- SYNPROXY DDoS protection — the SYN-flood layer.
- AppArmor confinement profiles — the per-binary confinement layer.
- SSH + SFTP hardening — the account-access layer.
/admin*URL protection — the default admin-path guard and its INI opt-out.- Whole-site IP allow-list (
ip_access) anduser_admin_access— the IP-based access controls. - Discontinued features — historical incidents BOA's security model defends against.