Hardening a shared-host control plane
In August 2026 the remoteagent control plane went through a full security audit. The plane runs on a shared LiteSpeed host inside a Sngine install — a capable but unforgiving environment. This is what we found, and what we changed.
Finding 1: source-code leaks via backup files
The worst issue was also the simplest. Every edited PHP file was kept as *.bak-<timestamp> — a sensible rollback habit — but the web server served those files as plain text. Old plugin versions containing database credentials, vault setup and API structure were publicly downloadable. Verifying this took one curl:
$ curl -s https://remoteagent.online/mona-plugin/init.php.bak-deepdive-20260813 | head -3
<?php
/**
* remoteagent Plugin for Sngine — Production Bootstrap
Full source, credentials included. The fix: block *.bak*, *.sql, .env, config.local.php, logs and dev probes at the web layer (403), disable directory listing, and keep the rollback copies for ops only. The leak class is gone even if a future edit reintroduces a backup file.
Finding 2: a stolen device key could wipe the account
Device tokens (the Remote Agent key on your laptop) authenticated the device API — but the same token also passed the dashboard API's auth and could reach every user-scoped endpoint, including DELETE /reset, key deletion and token revocation. A leaked key wasn't just a leak; it was a remote wipe button.
The fix is a hard bearer-scope allowlist: device tokens may only reach their own runtime surface (chat, tasks, runs, telemetry, insights). Keys, tokens, devices, reset, settings and 2FA endpoints now return 403 bearer_scope for device auth. Defense in depth: the device API also stopped accepting keys in URLs (?key=), which ended up in server logs, and dropped the wildcard Access-Control-Allow-Origin — irrelevant for a CLI, and one less browser-side blast radius.
Finding 3: no second factor on sensitive actions
The vault is a single point of failure. With session hijack or a compromised admin session, an attacker could add their own provider key, mint device tokens, or wipe everything. We shipped real 2FA — see Why every sensitive action now asks for a second factor.
Finding 4: dead security headers
Sngine defines init_security_headers() (HSTS, frame protection, nosniff, referrer policy) but never calls it. One line in the bootstrap enabled it. The CSRF cookie also lacked SameSite; it's now Lax; Secure; HttpOnly.
Finding 5: environment drift
The web runtime is PHP 8.4.19 (LiteSpeed) while the CLI defaults to 8.3.30 — causing recurring false "requires PHP ≥ 8.4" composer warnings. A platform.php = 8.4.19 pin in composer.json made the tooling agree with reality. Database credentials were also duplicated between files; they now come from a single Sngine config source.
The trade-off we accepted
The shared-host WAF is disabled under /mona-plugin/: agent tool payloads (code snippets, JSON args) trip generic rules and would 403 the product into uselessness. The compensating controls — auth, CSRF, rate limits, scope isolation, 2FA — are enforced at the application layer, which we can verify in tests. It's a documented, deliberate trade, not an oversight.