Since v1.5.0 RevoAp ships an in-app Security tab that exposes the
runtime IP firewall and a quick-look hardening checklist. Every change
is persisted to var/config/runtime.json (the same store used by the
Settings tab) and takes effect on the next request without touching
config.php or restarting PHP-FPM.
The tab is off by default. To turn it on, add SecurityTab => true
to config.php:
return [
'app' => [
// ...
'SettingsTab' => true,
'LogsTab' => true,
'SecurityTab' => true,
],
// Top-level alias; same precedence rules as SettingsTab/LogsTab.
'SecurityTab' => true,
];The flag is read with the strict Config::tabFlag() helper — only the
literal boolean true enables the tab. Any other value (including
'true', 1, 'yes') is treated as off.
When SecurityTab=false:
- The Security button never appears in the top nav.
/api/security/save,/api/security/test-ip,/api/security/resetreturn404 security_disabled./api/security/statusreturns the public probe (no IP lists).
- IP whitelist — when enabled, only requests whose client IP
matches one of the listed rules can reach the application. Loopback
(
127.0.0.0/8,::1/128) is always allowed and re-asserted on every save. The current admin's IP is auto-added to the whitelist on every visit to the Security tab AND when whitelist enforcement is being turned on, so a misclick cannot lock the operator out. - IP blacklist — matched IPs are denied with HTTP 403 before any controller resolves. Blacklist takes precedence over whitelist. Any loopback rule pasted into the blacklist is silently stripped — you cannot deny localhost.
- Trusted proxies — a list of IPs / CIDRs whose
X-Forwarded-For/X-Real-IPheaders may be honoured. Until bothtrust_proxy_headers=trueandREMOTE_ADDRmatches one of these rules, onlyREMOTE_ADDRis trusted. Never enable this without filling in trusted_proxies — XFF is otherwise spoofable. - Admin session TTL — overrides the cookie lifetime (in minutes) set when the operator unlocks the Settings/Logs/Security tabs.
- Test an IP — dry-run any IP/CIDR against the current rules without modifying state. Useful before turning on a whitelist on a remote host.
- Runtime checklist — read-only flags surfaced from the running configuration: HTTPS detected, debug mode, var/ writable, security headers, etc. Helpful for first-time operators verifying the hardening surface.
- Reset to safe defaults — turns enforcement off and replaces the whitelist with loopback only.
IpFirewall::decide($ip, $state) returns allow / whitelist /
blacklist in this order:
- If
$ipis loopback →allow(always). - Else if blacklist enforcement is ON and
$ipmatches →blacklist. - Else if whitelist enforcement is ON and
$ipdoes NOT match →whitelist. - Else →
allow.
Denials are logged as ip_firewall.denied with the offending IP, the
reason, the request method and path. The application logger is
sanitized through Logger::sanitizeScalar, so secrets in the request
URI never leak.
Default: REMOTE_ADDR. X-Forwarded-For is never blindly trusted.
When trust_proxy_headers=true AND REMOTE_ADDR matches one of the
configured trusted_proxies rules, the leftmost public IP from
X-Forwarded-For is used. If neither condition holds, REMOTE_ADDR
wins. This is what stops an attacker from spoofing their IP via the
header.
If your deployment sits behind nginx / Cloudflare / a load balancer:
- Add the proxy's egress IP / CIDR to Trusted proxies.
- Toggle Trust X-Forwarded-For from listed proxies ON.
If you don't have a proxy, leave the toggle off.
{
"security": {
"ip_whitelist_enabled": false,
"ip_whitelist": ["127.0.0.0/8", "::1/128"],
"ip_blacklist_enabled": false,
"ip_blacklist": [],
"trusted_proxies": [],
"trust_proxy_headers": false,
"admin_session_ttl_minutes": 120
}
}var/config/runtime.json is written with 0600 permissions and is
git-ignored.
GET /api/security/status— public probe. When admin-authenticated also returns the live state and the detected client IP, and auto-adds that IP to the whitelist (idempotent).POST /api/security/save— replace state. Re-asserts loopback rules and the current admin IP whenever whitelist enforcement is on.POST /api/security/test-ip— dry-run match against the current rules. Body:{"ip":"203.0.113.5"}.POST /api/security/reset— reset to safe defaults (enforcement OFF, loopback-only whitelist, blacklist empty).
All four require the admin token (header X-Admin-Token /
Authorization: Bearer … / cookie oc_admin set after /api/settings/unlock).
- Whitelist lockout — if you enable the whitelist from a public network without your IP listed, you will be locked out from the outside (loopback still works for SSH-tunneled recovery). The tab protects you by auto-adding your IP, but if you save state via the API directly, it is your responsibility.
- NAT / dynamic IP — a residential IP that changes daily makes the whitelist a footgun. Prefer a CIDR for your office gateway, or use the blacklist for known-bad IPs and leave the whitelist off.
- Reverse proxies — without
trust_proxy_headers + trusted_proxies,REMOTE_ADDRwill be the proxy's IP for every request, so neither your whitelist nor your blacklist can target real users. Configure the proxy fields first. - Recovery — if you do lock yourself out, SSH to the host and edit
var/config/runtime.jsondirectly: setip_whitelist_enabledtofalseand reload. Loopback is always allowed, socurl --unix-socket/127.0.0.1access works no matter how badly the lists are set.