This document records the security architecture of SafeGuard Panel, the penetration testing it has undergone, its resistance to the known exploit classes that have hit competing control panels, and the checklist that must be completed before opening the panel to customers.
Last updated: 2026-06-11. Owner: SafeGuard Hosting Inc.
- JWT (HS256) with the signing method pinned at validation (
alg=none, signature-stripping, and RS256/HS256 confusion all rejected). - Server-side session table: every JWT carries a
jtiand is checked against thesessionstable on every request — a terminated, revoked, or forged-but-unsessioned token is rejected immediately. (This is what makes token theft survivable: revoke the session and the token is dead.) JWT_SECRETis never a hardcoded default — the installer writes a 256-bit random secret; if unset, the panel generates a random per-boot secret rather than using a known string.- Password hashing: bcrypt (cost 12) + a server-side
PASSWORD_PEPPERthat lives only in the environment, so a stolen database cannot be cracked offline. - Constant-time login: the "user not found" path runs a dummy bcrypt at the same cost, closing the username-enumeration timing side-channel.
- 2FA (TOTP) with one-time backup codes; failed second-factor attempts count toward the lockout tiers.
- Brute-force defense: 5/min login rate limit plus layered lockout tiers (5 fails → 60s, 10 → 15min, 20 → 1h), each emitting a security event.
- Concurrent-session cap (10); suspend revokes all sessions.
- Support-access keys (time-boxed, read-only/full, revocable, re-validated every request) so support never needs a customer's password.
- Three roles: Owner > Reseller > User, enforced by middleware and by an
owner_id/parent_idscope on every data query. - Verified against horizontal IDOR (cross-user/cross-tenant read/edit/delete), vertical escalation (self role-change, owner-only endpoints), and reseller subtree boundaries — all rejected.
- Mass-assignment safe:
id,owner_id,parent_id,rolecannot be set by the client; a reseller creating a user has the role force-downgraded.
- CSP (
default-src 'self', no inline script), HSTS (preload), X-Frame-Options/frame-ancestors (clickjacking), X-Content-Type-Options, Permissions-Policy, Referrer-Policy, andno-storeon every API response — including error responses. - CSRF: HttpOnly
sg_sessioncookie + double-submitsg_csrftoken on all state-changing requests (Bearer tokens, which carry no ambient authority, are exempt by design). - CORS pinned to the configured origin — no reflection, no credentials.
- Secure cookie flags:
HttpOnly,Secure(under TLS),SameSite=Strict.
- SQL: 100% parameterized queries; identifier quoting + strict name regexes on the DB-engine layer. No string-built SQL anywhere.
- Command execution: every
exec.Commandis argv-style — no shell — so shell-metacharacter injection is structurally impossible. User-influenced arguments (git branch, fail2ban jail, docker image, DB/domain names) are charset-validated so they can't be parsed as CLI flags. - Path traversal: the per-user file jail rejects
.., encoded variants, backslashes, and absolute paths; archive extraction blocks zip-slip and is size-capped against decompression bombs (2 GiB/file, 5 GiB total, 200k entries). - DNS: record name/value reject control characters (zone-file injection) and are length-bounded; structured types are format-validated.
- XSS: API stores data verbatim; React auto-escapes on render; CSP blocks inline/injected script as defense-in-depth.
- The panel splits into two binaries:
panel-coreruns unprivileged (usersafeguard) and holds all the API/UI/DB/business logic;panel-workerruns as root, contains zero business logic, and performs only a small, immutable, strictly-typed set of system mutations. - They communicate over a local Unix socket (
0660 root:safeguard) gated by the kernel-verified peer UID (SO_PEERCRED). Every privileged file write resolves beneath a hardcoded base directory viaopenat2 RESOLVE_BENEATH, closing the symlink/TOCTOU class; all shelling-out is argv-only (no shell). - This shrinks the root attack surface from "the whole panel" to ~a dozen reviewed operations. (The full internal design document — threat model, protocol, op set — is part of the private source repository; researchers engaged for testing can request it via [email protected].)
- Secrets at rest (billing credentials, Imunify license) are AES-256-GCM
encrypted with a master key file (
0600). - Binary integrity check at startup (
SAFEGUARD_BINARY_HASH) — refuses to run if the binary was tampered with. - Per-user PHP-FPM pool isolation (
open_basedir, exec functions disabled) — the boundary that makes user-uploaded.phpsafe, exactly as on cPanel/Plesk. - Body limits (1 MB JSON / 512 MB multipart) and a global 300/min rate limit bound request-flood and upload DoS.
- Audit log (RBAC-scoped, CSV export) records every meaningful action with
the real client IP (resolved from
X-Real-IPonly when the peer is the local nginx proxy, so it cannot be spoofed). - Standalone repair server on a separate port with its own shared-key auth, so the panel can be recovered even when the main service is down.
govulncheck(Go) reports zero vulnerabilities — both reachable and in required modules. Run on every dependency change.- The backend has 3 direct dependencies (JWT, x/crypto, pure-Go SQLite); the shipped binary links no system libraries (CGO disabled).
The production artifacts are a static Go binary plus pre-built static frontend assets. Build tooling never runs on a production server.
- GHSA-gv7w-rqvm-qjhr — esbuild (via Vite, dev dependency). A build-time tool, not part of the deployed panel. The advisory concerns esbuild's Deno binary-download path; SafeGuard consumes esbuild through npm (integrity- pinned in the lockfile), so the affected code path is not used, and esbuild is never present in production. The patched esbuild (0.28+) is incompatible with the current Vite 6 build pipeline, so we hold at the working version and will adopt it when Vite ships it upstream. No production or runtime impact.
SafeGuard has been subjected to multiple automated and manual assessment passes against the live API. Findings below were all fixed and re-verified.
| Severity | Finding | Status |
|---|---|---|
| Critical | JWT forgery via jti-less token (session check was conditional) | Fixed — session row now required for every JWT |
| High | golang-jwt v5.2.1 DoS (CVE GO-2025-3553) |
Fixed — upgraded to v5.2.3 |
| High | Client-IP proxy-blindness → panel-wide lockout DoS | Fixed — trusted X-Real-IP from loopback only |
| High | Git branch argument injection (--upload-pack) → RCE |
Fixed — charset validation |
| Medium | Logout was client-side only (cookie/session survived) | Fixed — server-side revocation |
| Medium | Support sessions could read the never-expiring repair key | Fixed — 403 for support sessions |
| Medium | DNS TXT zone-file injection via newline | Fixed — control-char guard |
| Medium | Username enumeration via login timing | Fixed — constant-time dummy bcrypt |
| Medium | Archive decompression bomb (uncapped extraction) | Fixed — size/entry caps |
| Low | fail2ban jail-name argument injection | Fixed — charset validation |
| Low | 2FA failures didn't count toward lockout | Fixed |
Tested clean (no findings): SQL injection (login bypass, search, blind timing), path traversal / jail escape, SSRF, stored XSS, CSRF coverage, CORS reflection, HTTP verb tampering, mass assignment, session fixation, open redirect, default credentials, and the TOCTOU quota race (held under 15-way parallel attack on SQLite).
The control panels SafeGuard competes with have well-documented breach histories. SafeGuard was explicitly tested against each major class.
| CyberPanel CVE / technique | SafeGuard result |
|---|---|
CVE-2024-51567/51378 — auth-middleware bypass via path normalization (..;, suffix tricks) |
Immune. 16 crafted-path bypasses tested — none reached a protected route. Go's ServeMux normalizes paths and auth middleware wraps the entire router, not a prefix match. |
Header-based auth bypass (X-Original-URL, X-Rewrite-URL) |
Immune. Middleware reads the real request; 5 header tricks rejected. |
| CVE-2024-51568 — command injection in upgrade/status endpoints | Immune. All exec is argv-style (no shell); shell-metachar payloads into owner upgrade/repair endpoints produced no injection. |
| Unauthenticated access to upgrade/RCE endpoints | Immune. 14 sensitive endpoints all reject anon access. |
| CVE-2023-36163 — open redirect | Not present. No attacker-controlled redirects. |
Default 1234567 admin credential |
Not present. Installer randomizes the admin password; no common defaults accepted. |
- Symlink race / local privilege escalation — the highest-frequency real panel CVE class. Mitigated by per-user jails + isolated PHP-FPM pools, but the production file-operation code paths (vhost/cert/zone writes running as root) must be pentested on the live AlmaLinux host (see §4) — this is the one class that genuinely cannot be exercised on the dev box.
- Authenticated file-manager traversal — tested clean.
- API token / session fixation — tested clean (random
jtiper login, server-side revocation).
- Installer sets
JWT_SECRET,PASSWORD_PEPPER,SAFEGUARD_BINARY_HASH(verify on the real box —systemctl cat safeguard). - Panel store is SQLite in production by design (WAL, single writer, hourly snapshots, boot integrity check + auto-restore, daily encrypted off-site copy). SQLite's write serialization closes the quota-check TOCTOU window. Only if the store is ever moved to PostgreSQL (optional multi-node epic, not planned for launch) must resource-quota checks (domains, databases, FTP, email, subdomains) be wrapped in transactions — concurrent PG writers would reopen that window (low-severity overselling, not a breach).
- TLS configured; redirect HTTP→HTTPS; HSTS preload submitted.
- Database backups scheduled and a restore drill performed.
- Professional penetration test against the production code paths —
certbot, BIND zone writes, vhost generation,
systemctl, CloudLinux integration. This is the single most valuable remaining action; it is the only way to exercise the root-privileged file operations where panels historically get symlink-race and LPE bugs. - Run SSL Labs and Mozilla Observatory against the live HTTPS endpoint — confirm cipher suites + that the security headers survive the nginx hop end-to-end.
- Load / soak test (k6 or vegeta, 500+ concurrent) — surfaces resource leaks and the PG quota race.
- Confirm Imunify360/ImunifyAV + CSF + Fail2Ban are active per the chosen Security Engine; verify the firewall default-denies.
- Wire
govulncheck+npm auditinto CI so new CVEs fail the build. - Stand up a vulnerability-disclosure program using the built-in
security.txtfeature (see §5). - Schedule recurring dependency upgrades and an annual third-party pentest.
SafeGuard Panel ships a per-domain security.txt editor (RFC 9116). Publish a
disclosure policy at /.well-known/security.txt with a security contact and
encryption key so researchers can report responsibly. A bug-bounty program
(HackerOne / Intigriti) is recommended once the panel is publicly hosting
customer data.
Every release attaches a SHA256SUMS file and a detached GPG signature
SHA256SUMS.asc, signed by the SafeGuard release-signing key. The private
key never resides on any download server, so a compromised mirror or CDN cannot
forge a valid signature — it can only get caught.
Release-signing key fingerprint:
6C5B CC89 94E4 529F F969 C946 A4A1 7D29 03CB 009F
SafeGuard Hosting (Release Signing) <[email protected]>
The public key is published with each release as safeguard-release-signing.asc
(and in the repo at docs/keys/). To verify a download:
gpg --import safeguard-release-signing.asc
gpg --verify SHA256SUMS.asc SHA256SUMS # expect: Good signature, matching fingerprint above
sha256sum -c SHA256SUMS --ignore-missing # expect: every artifact OKThe installer performs this same check automatically: it carries the public key
inline, fetches SHA256SUMS.asc, and aborts if the signature is present but
does not verify. It is also wrapped so a truncated curl | bash download parses
to completion before executing — a partial download runs nothing.
Operational note: protect the release-signing private key (offline backup; ideally a passphrase or hardware token for the long-term key). Its compromise would let an attacker sign malicious releases. Rotate via the published revocation certificate if ever exposed.
Third-party components installed at setup. SafeGuard's own binaries are
signature-verified as above. The OS dependency stack (Nginx, PHP, MariaDB,
PostgreSQL, BIND, …) is installed from the distribution + EPEL/Remi repositories
with dnf gpgcheck left enabled, so every package is GPG-verified by the
package manager — the same trust model cPanel/Plesk/Imunify rely on. Three
components are pulled from their vendors at install time over HTTPS and run with
elevated privilege; this is the vendor-recommended install path and an accepted,
industry-standard inherited trust:
- Adminer — pinned to a specific release and checksum-verified by the
installer (no moving
latest.php). - CSF (ConfigServer) and ImunifyAV (CloudLinux) — fetched from the vendors and run as root. CloudLinux ships ImunifyAV's actual agent via a GPG-signed repo; CSF is delivered as a tarball. A future hardening is to pin these to recorded checksums where the vendors publish stable artifacts.
The self-update channel, when implemented, MUST verify the GPG-signed
SHA256SUMS and each new binary before swapping (it is a stub today) — otherwise
it would bypass the install-time supply-chain guarantees above.
At the application layer, SafeGuard Panel implements controls that most web applications never reach and is immune to every CyberPanel exploit class that caused the 2024 mass-compromise. The remaining distance to "fully battle-proven" is not additional code — it is external validation: a live pentest of the root-privileged production code paths, load testing, and a running disclosure/bounty program. Complete §4 on a staging AlmaLinux server and the panel is ready to compete with cPanel, Plesk, and DirectAdmin.