# hPanel Security Audit Report

**Product:** hPanel — Hosting Control Panel  
**Version:** 1.88.0 (Build 20260525)  
**Date:** 2026-05-26  
**Auditor:** Independent Security Audit  
**Classification:** CONFIDENTIAL  

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Audit Scope & Methodology](#2-audit-scope--methodology)
3. [Installer Script Audit (latest.sh)](#3-installer-script-audit-latestsh)
4. [Panel Application Code Audit (server.zip)](#4-panel-application-code-audit-serverzip)
5. [Authentication & Middleware](#5-authentication--middleware)
6. [Core Services & Utilities](#6-core-services--utilities)
7. [API Routes](#7-api-routes)
8. [Configuration & Deployment](#8-configuration--deployment)
9. [PHP SSO Components](#9-php-sso-components)
10. [Consolidated Findings Summary](#10-consolidated-findings-summary)
11. [Risk Rating & Overall Assessment](#11-risk-rating--overall-assessment)
12. [Prioritized Remediation Roadmap](#12-prioritized-remediation-roadmap)

---

## 1. Executive Summary

This report presents the findings of a comprehensive security audit of the **hPanel** hosting control panel, covering both the installation script (`latest.sh`, ~2629 lines) and the server-side application code (`server.zip`, 145 files). The audit identified a total of **94 security findings** across the entire codebase, including **19 Critical**, **28 High**, **30 Medium**, and **17 Low** severity issues.

The hPanel application is a Node.js-based hosting control panel that manages web hosting accounts, email, DNS, FTP, databases, SSL certificates, Docker containers, backups, and server provisioning. It uses a PostgreSQL database for state management and integrates with system services including Nginx, Postfix, Dovecot, MySQL, BIND9, Redis, vsftpd, and PHP-FPM.

### Key Risk Areas

The most significant security concerns identified in this audit are:

1. **Systemic root execution** — The panel application appears to run entirely as root, with `execSync`/`spawnSync` calls throughout the codebase for system operations. A single sanitization bypass could lead to full system compromise.

2. **Hardcoded secrets and default credentials** — Multiple hardcoded fallback secrets were found, including a webmail SSO default key, database default credentials (`postgres/postgres`), and a seed admin password (`admin123`).

3. **Code obfuscation prevents meaningful audit** — All core business logic is compiled to bytenode `.jsc` format and further obfuscated with `javascript-obfuscator`, making independent verification of security controls impossible.

4. **Plaintext credential storage** — Database passwords, API tokens, DKIM private keys, and Git deployment tokens are stored unencrypted in the PostgreSQL database.

5. **Unsafe temporary file handling** — Multiple components store sensitive data (SSO tokens, MySQL credentials, lock files) in world-writable `/tmp/` directories, enabling symlink attacks and credential theft.

**Overall Risk Rating: CRITICAL** — The combination of root execution, hardcoded secrets, and unauditable compiled code presents an unacceptable risk for production deployment in its current state.

---

## 2. Audit Scope & Methodology

### 2.1 Scope

| Component | Source | Size |
|-----------|--------|------|
| Installer Script | `https://securedownloads.hpanel.net/latest.sh` | 2,629 lines (Bash) |
| Panel Application | `https://securedownloads.hpanel.net/server.zip` | 145 files (Node.js + PHP) |

### 2.2 Methodology

The audit was performed using the following approach:

- **Static code analysis** of the full installer script (line-by-line review)
- **String extraction** from compiled `.jsc` bytecode files using `strings` utility
- **Source code review** of readable files (`.js` stubs, `.php` scripts, `.sh` scripts, config files, database seed/migration files)
- **Configuration analysis** of `.env.example`, `package.json`, `ecosystem.config.js`
- **Cross-referencing** installer script behavior with panel code expectations

### 2.3 Limitations

The core application logic is compiled to V8 bytecode (`.jsc` files) and obfuscated using `javascript-obfuscator`. This means:

- **No line-by-line source code review** is possible for the majority of the application
- Findings from `.jsc` files are based on **string extraction and inference**, not direct code analysis
- The actual behavior of sanitization functions (`shellEscape`, `sanitize`) cannot be verified
- False negatives are possible — additional vulnerabilities may exist in the compiled code that cannot be detected through string analysis alone

---

## 3. Installer Script Audit (latest.sh)

The installer script (`latest.sh`) is a ~2,629-line Bash script that sets up a complete hosting environment on Ubuntu/Debian systems. It installs and configures Nginx, Postfix, Dovecot, MySQL/PostgreSQL, Redis, BIND9, PHP-FPM, Node.js, vsftpd, phpMyAdmin, Roundcube, ModSecurity WAF, Fail2Ban, and ClamAV.

### 3.1 Critical Findings

| ID | Finding | Location | Description |
|----|---------|----------|-------------|
| INS-C01 | Plaintext Dovecot master password | Line 920 | The Dovecot master user password is generated and stored in plaintext in the `.env` file and passed as a command-line argument to configuration functions. Any local user who can read the `.env` file gains full IMAP access to all mailboxes on the server. |
| INS-C02 | Credentials echoed to terminal | Lines 296-300 | During installation, the script echoes database credentials, admin passwords, and other sensitive values directly to the terminal. If the installation is logged or observed, these credentials are exposed. |
| INS-C03 | SQL injection risk in database setup | Lines 370, 395-398 | SQL statements are constructed using string interpolation with variables like `${DB_USER}` and `${DB_PASS}`. While these values are generated by the script, the pattern is dangerous and could be exploited if the generation logic is modified. |
| INS-C04 | No integrity verification on downloaded ZIP | Lines 199-210 | The panel ZIP archive is downloaded via `wget` and extracted without any checksum, hash, or signature verification. An attacker performing a man-in-the-middle attack could substitute a malicious archive. |
| INS-C05 | curl\|bash anti-pattern | Script invocation | The recommended installation method (`curl ... \| bash`) means users execute unverified code directly. There is no way to inspect the script before it runs. |
| INS-C06 | PHP `curl_exec` disabled | Line 547 | The PHP configuration disables `curl_exec`, which breaks many legitimate PHP applications that rely on HTTP requests (API integrations, payment gateways, etc.). |
| INS-C07 | chmod 777 on phpMyAdmin tmp | Line 769 | The phpMyAdmin temporary directory is set to mode `0777` (world-writable), allowing any local user to write arbitrary files that phpMyAdmin may execute. |
| INS-C08 | Silent error suppression | Throughout | Many critical commands are followed by `2>/dev/null` or run with `set +e`, meaning installation failures are silently ignored. This can result in a partially-installed, insecure system. |

### 3.2 High Findings

| ID | Finding | Location | Description |
|----|---------|----------|-------------|
| INS-H01 | No `set -e` in installer | Script header | The script does not use `set -e` (exit on error), meaning failed commands do not stop the installation. A failed package installation could leave the system in an inconsistent state. |
| INS-H02 | `kill -9` on apt processes | Lines 98-104 | The `safe_service` function forcefully kills `apt`/`dpkg` processes with `kill -9`. This can corrupt the apt database and leave the package manager in a broken state. |
| INS-H03 | Unquoted shell variables | Multiple locations | Variables like `${DOMAIN}`, `${DB_PASS}` are used without quotes in multiple places, potentially causing word-splitting and globbing issues. |
| INS-H04 | Dovecot auth socket mode 0666 | Lines 831-835 | The Dovecot authentication socket is created with mode `0666` (world-writable), allowing any local user to authenticate as any email user. |
| INS-H05 | Self-signed certificate with 10-year expiry | Lines 1540-1543 | The installer creates a self-signed SSL certificate valid for 10 years. Long-lived certificates increase the risk of compromise if private keys are leaked. |
| INS-H06 | No download integrity for packages | Throughout | Packages are installed from standard Ubuntu/Debian repositories without verifying package signatures or using `apt-secure`. |

### 3.3 Medium Findings

| ID | Finding | Location | Description |
|----|---------|----------|-------------|
| INS-M01 | ModSecurity disabled for panel | Line 1817 | ModSecurity WAF is turned off for the panel's own endpoints, removing a layer of protection for the most sensitive part of the server. |
| INS-M02 | MySQL user with ALL PRIVILEGES + GRANT OPTION | Line 397 | The hpanel MySQL user is granted ALL PRIVILEGES with GRANT OPTION, allowing it to create new superusers and access any database. |
| INS-M03 | vsftpd writeable chroot | Line 701 | vsftpd is configured with `allow_writeable_chroot=YES`, which can allow FTP users to escape their chroot jail under certain conditions. |
| INS-M04 | Postfix no TLS enforcement | Lines 790-812 | Postfix is configured to offer TLS but does not enforce it. Incoming and outgoing mail can be sent unencrypted. |
| INS-M05 | Redis without authentication | Lines 448-458 | Redis is configured without a password. Any local user or process can connect to Redis and read/modify cached data. |
| INS-M06 | sshpass installed | Line 316 | The `sshpass` utility is installed, which stores SSH passwords in plaintext on the command line. This is a security anti-pattern. |
| INS-M07 | BIND9 listening on all interfaces | BIND config | BIND9 is configured to listen on all network interfaces, exposing DNS service to potential attacks from the internet. |

### 3.4 Low / Informational Findings

| ID | Finding | Location | Description |
|----|---------|----------|-------------|
| INS-L01 | No firewall configuration | Script | The installer does not configure `ufw` or `iptables`. Firewall rules must be configured manually. |
| INS-L02 | No automatic security updates | Script | The installer does not configure unattended-upgrades for security patches. |
| INS-L03 | No disk encryption | Script | No LUKS or other disk encryption is configured. Data at rest is unencrypted. |
| INS-L04 | Verbose logging may contain sensitive data | Throughout | Installation logs may contain passwords, API keys, and other secrets. |
| INS-L05 | No SELinux/AppArmor profiles | Script | No mandatory access control profiles are configured for panel services. |
| INS-L06 | Default SSH port unchanged | Script | SSH remains on port 22 with no fail2ban configuration specific to SSH. |
| INS-L07 | ClamAV not configured for real-time scanning | Script | ClamAV is installed but not configured for real-time file scanning. |
| INS-L08 | No rate limiting on SSH | Script | No SSH rate limiting or connection throttling is configured. |
| INS-L09 | Version information exposed | Various | Version numbers and software identifiers are exposed in HTTP headers and error pages. |

---

## 4. Panel Application Code Audit (server.zip)

### 4.1 Architecture Overview

The hPanel application follows a standard Node.js Express architecture:

```
server/
├── dist/
│   ├── index.js          # Application entry point
│   ├── config/           # Database configuration
│   ├── database/         # Migrations and seed data
│   ├── keys/             # License public keys
│   ├── middleware/        # Auth, rate limiting, license guard, port access
│   ├── routes/           # API endpoints (25+ route modules)
│   ├── services/         # Business logic (15+ service modules)
│   ├── templates/        # HTML templates
│   └── utils/            # Sanitization, shell escape, archive safety
├── phpmyadmin/           # phpMyAdmin SSO integration
├── scripts/              # Update, fix, and utility scripts
├── .env.example          # Environment variable template
├── package.json          # Dependencies
└── ecosystem.config.js   # PM2 process manager config
```

### 4.2 Key Technology Stack

| Component | Technology |
|-----------|------------|
| Runtime | Node.js (via bytenode compiled `.jsc`) |
| Web Framework | Express.js |
| Database | PostgreSQL (via `pg` library) |
| Process Manager | PM2 |
| Authentication | JWT (jsonwebtoken), PAM, bcrypt |
| Terminal | node-pty |
| Docker | Docker API |
| Obfuscation | javascript-obfuscator + bytenode |

### 4.3 Code Obfuscation Concern

**All core business logic files are compiled to V8 bytecode (`.jsc`) and obfuscated with `javascript-obfuscator`.** The `.js` files in the distribution are merely loader stubs:

```javascript
require('bytenode');
module.exports = require('./auth.jsc');
```

This has several security implications:

- **Independent audit is impossible** — The actual implementation of security-critical functions (authentication, authorization, input sanitization) cannot be verified.
- **Obfuscation is not a security control** — It only delays reverse engineering. An attacker with sufficient motivation can decompile the bytecode.
- **Trust problem** — Users must trust the compiled code without being able to verify it doesn't contain backdoors or security flaws.
- **Patch verification** — It is impossible to verify that a security patch actually fixes the reported vulnerability without source code access.

---

## 5. Authentication & Middleware

### 5.1 Authentication Middleware (auth.js → auth.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C01 | CRITICAL | `execSync` used in authentication routes | The presence of `execSync` in the authentication code path is extremely dangerous. If any user input is interpolated into a shell command without proper escaping, it enables Remote Code Execution (RCE). |
| APP-C02 | CRITICAL | PAM authentication with root login capability | The PAM auth service uses `execFileSync` for authentication. The string "Root login" suggests the system allows direct root login via the web panel. If the panel is compromised, the attacker gains root access to the entire server. |
| APP-H01 | HIGH | JWT read from both `header` and `cookies` | Dual-source token extraction increases attack surface. Cookie-based JWT without enforced `HttpOnly`/`SameSite` flags is vulnerable to XSS token theft. |
| APP-H02 | HIGH | No evidence of `algorithms` restriction in JWT verification | If `jsonwebtoken.verify()` is called without explicitly setting `algorithms: ['HS256']`, an attacker could use the "alg": "none" attack or RS256/HS256 key confusion. |
| APP-M01 | MEDIUM | Role extracted only from JWT payload | If the JWT is not re-validated against the database on every request, a demoted user retains elevated privileges until token expiry (7 days). |
| APP-M02 | MEDIUM | 7-day JWT expiry | `JWT_EXPIRES_IN=7d` is excessively long for a control panel. Stolen tokens remain valid for a week. |

### 5.2 License Guard Middleware (licenseGuard.js → licenseGuard.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C03 | CRITICAL | License bypass potential | If the license guard can be bypassed (by modifying `.license-key` or the license client response), all feature gates, user limits, and restrictions become inert. The license state appears to be cached in-memory without cryptographic binding. |
| APP-H03 | HIGH | License key file stored in `dist/` directory | The updater script preserves `dist/.license-key` during updates. If world-readable, this allows license state manipulation. |
| APP-M03 | MEDIUM | `enforceLimits` is a separate per-route function | If a route forgets to call `enforceLimits`, resource limits are not checked, enabling over-provisioning. |

### 5.3 Port Access Middleware (portAccess.js → portAccess.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H04 | HIGH | Port-based access control is bypassable | The middleware relies on the incoming request port to determine context (user panel vs admin panel vs webmail). If behind a reverse proxy that doesn't preserve the original port, or if all ports are accessible, a user could access the admin panel context. |
| APP-M04 | MEDIUM | `allowedRoles` separate from authentication | Port access enforcement and role-based access appear to be separate middleware. If not chained correctly, a user could bypass role checks by using the right port. |

### 5.4 Rate Limiter Middleware (rateLimiter.js → rateLimiter.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H05 | HIGH | IP address sourced from `X-Forwarded-For` without validation | An attacker can spoof this header to bypass rate limits entirely by sending `X-Forwarded-For: <random-ip>` with each request. |
| APP-M05 | MEDIUM | `skipSuccessfulRequests` may be configured | If enabled on login rate limiters, successful brute-force attempts won't count, allowing unlimited attempts. |
| APP-M06 | MEDIUM | No distributed rate limiting | Rate limits appear in-memory only. In PM2 cluster mode, limits are per-process, multiplying the allowed request count. |
| APP-L01 | LOW | Rate limit messages may leak implementation details | Messages like "Too many 2FA attempts" confirm 2FA existence to unauthenticated attackers. |

### 5.5 Auth Routes (auth.js → auth.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H06 | HIGH | Root login capability | The string "Root login" suggests direct root login is permitted through the web panel, presenting a critical risk if the panel is compromised. |
| APP-H07 | HIGH | JWT token issued with `JWT_SECRET` from environment | If this secret is weak or not rotated, all tokens can be forged. Combined with 7-day expiry, this is a significant risk. |
| APP-M07 | MEDIUM | Password reset token handling | The reset flow stores tokens in the database, but the hash algorithm and expiry window are not visible from compiled code. |
| APP-M08 | MEDIUM | 2FA bypass potential | If the 2FA verification is not properly sequenced (session marked as authenticated before 2FA verification), an attacker could bypass 2FA. |

### 5.6 Admin Routes (admin.js → admin.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C04 | CRITICAL | User impersonation without MFA requirement | The `impersonatedBy` field suggests admin impersonation of users. The impersonation issues a new JWT signed with `JWT_SECRET` — if the impersonation endpoint is not properly restricted, any admin can forge sessions for any user. |
| APP-C05 | CRITICAL | `execSync`/`execFileSync`/`spawn` for system operations | Admin routes execute system commands (systemctl, rndc, postsuper, etc.). Despite sanitization utilities, the attack surface is enormous. A single bypass could lead to RCE. |
| APP-H08 | HIGH | Reseller privilege escalation risk | The `checkResellerOwnership` function exists but if ownership checks are inconsistent, a reseller could access other resellers' accounts or escalate to admin. |
| APP-H09 | HIGH | Updater triggered via API | Routes allow triggering the auto-updater from the admin panel. The updater has a fallback path that proceeds without RSA verification, enabling supply chain attacks. |
| APP-H10 | HIGH | Service management via systemctl | Admin routes can start/stop/restart system services. A compromised admin account could cause denial of service. |
| APP-M09 | MEDIUM | MySQL governor access | Route `/mysql-gov` suggests MySQL server management. If this allows arbitrary SQL execution, it could compromise all hosted databases. |
| APP-M10 | MEDIUM | Permissions stored as JSON array | If permissions are not validated server-side, a user could inject additional permissions. |

---

## 6. Core Services & Utilities

### 6.1 Shell Escape Utility (shellEscape.js → shellEscape.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-M11 | MEDIUM | Core security function is unauditable | The shell escaping module is the single most security-critical utility, yet it is compiled to bytecode and obfuscated. A flaw here undermines every module that depends on it. |
| APP-L02 | LOW | Inconsistent metacharacter blacklists | `shellArg` in sanitize.js strips `"` but `safeHostname` does not. Inconsistent character lists suggest possible gaps. |

### 6.2 Sanitize Utility (sanitize.js → sanitize.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H11 | HIGH | `safePath` uses blacklist approach | Blacklist-based sanitization (`[;&|\`$(){}[\]!#~<>*?\n\r\\']`) is inherently fragile. Path sanitization should use whitelist + `path.resolve()` + base directory check. |
| APP-H12 | HIGH | `safeCronField` allows complex expressions | The regex `[^0-9,\-\*\/]` strips letters but permits complex expressions that could mask malicious intent. No validation of field count or range limits. |
| APP-M12 | MEDIUM | `safeUsername` allows dots | Usernames with dots (e.g., `..`, `.hidden`) can cause path traversal when concatenated with `/home/`. |
| APP-M13 | MEDIUM | `safeIdentifier` allows dots | Database identifiers with dots could be interpreted as `schema.table` references, enabling cross-database access. |
| APP-L03 | LOW | Inconsistent double-quote handling across functions | `shellArg` includes `"` in blacklist but `safeHostname` does not. |

### 6.3 Archive Safety Utility (archiveSafety.js → archiveSafety.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H13 | HIGH | `safeCleanup` uses `rmSync({recursive: true, force: true})` | If path validation has any bypass, this could delete arbitrary directory trees. The `force: true` option means it won't fail on nonexistent paths. |
| APP-M14 | MEDIUM | Symlink detection present but TOCTOU evasion possible | An attacker could replace a validated directory entry with a symlink between validation and extraction. |
| APP-M15 | MEDIUM | Archive validation by extension | Type is validated by `endsWith` check, trivially bypassed by renaming a malicious file. Should validate by magic bytes. |

### 6.4 PAM Authentication Service (pamAuth.js → pamAuth.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C06 | CRITICAL | PAM auth via compiled C helper with shadow password access | A compiled C helper uses `getspnam()` to read `/etc/shadow`. If this binary has setuid root and its arguments are not strictly validated, it could be exploited to brute-force or dump password hashes. |
| APP-C07 | CRITICAL | First-time setup flow without proper disable mechanism | The `isFirstTimeSetup` endpoint likely allows creating an admin account without authentication. If not properly disabled after setup, this is an authentication bypass. |
| APP-H14 | HIGH | Username validation allows dots | The regex `^[a-zA-Z0-9._-]+$` allows usernames like `..` or `.bashrc`, causing filesystem confusion. |
| APP-M16 | MEDIUM | Hardcoded `root` user special case | Special handling of the root user in PAM auth suggests root login may be permitted through the panel. |

### 6.5 Terminal Service (terminal.js → terminal.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C08 | CRITICAL | Terminal sessions run as root with `/bin/bash` | Environment variables `USER=root`, `SHELL=/bin/bash` are set when creating PTY sessions. There is no evidence of privilege dropping. Any user with terminal access gets a root shell. |
| APP-C09 | CRITICAL | No apparent session ownership verification | Session IDs may be predictable or not validated against the requesting user's identity. Any authenticated user could access any other user's terminal session. |
| APP-H15 | HIGH | Sessions only protected by timeout | No explicit session revocation on user logout, password change, or admin action. |
| APP-M17 | MEDIUM | WebSocket terminal without apparent origin validation | Without proper origin checking and authentication on the WebSocket upgrade, CSRF-style attacks could hijack terminal sessions. |

### 6.6 Provisioning Service (provisioning.js → provisioning.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C10 | CRITICAL | `safeRm` with insufficient protected paths | `rm -f` and recursive deletion via `execSync`. Protected path list only checks `/usr`, `/sys` prefixes. A path like `/usr/local/important_data` would pass validation. |
| APP-C11 | CRITICAL | `userdel` command execution | If the username passed to `userdel` is not strictly validated, command injection or deletion of the wrong system user is possible. |
| APP-H16 | HIGH | `chown root`, `chmod 755`, `chmod 775` operations | Multiple ownership/permission changes including world-writable group. If any path is controllable by a user, symlink attacks could grant root-owned files. |
| APP-H17 | HIGH | Nginx vhost configuration injection | `createNginxVhost` writes to `/etc/nginx/sites-enabled/`. If domain names or paths are not properly escaped, attacker-controlled nginx directives could be injected. |
| APP-H18 | HIGH | BIND named.conf manipulation | `_parseNamedZones` and `_writeNamedConf` modify `/etc/bind/`. DNS zone injection could redirect traffic. |
| APP-H19 | HIGH | MySQL command execution | `runMysql` and `getMysqlAuth` execute MySQL commands with potentially root-level credentials. SQL injection through user-provided database names could compromise the entire database server. |

### 6.7 Backup Service (backup.js → backup.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H20 | HIGH | MySQL import executes user-supplied SQL | `safeMysqlImport` can import SQL dumps containing malicious statements (`LOAD_FILE()`, `INTO OUTFILE`, `DEFINER` clauses) that read/write arbitrary files or escalate privileges. |
| APP-H21 | HIGH | Restore to potentially sensitive locations | `restoreBackup` restores files with potentially bypassable path validation. A crafted backup could overwrite `/etc/passwd`, cron jobs, or SSH keys. |
| APP-M18 | MEDIUM | Cloud driver credential exposure | S3/Hetzner credentials stored in database or environment variables could be accessed through SQL injection. |

### 6.8 Docker Service (docker.js → docker.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H22 | HIGH | Missing container security constraints | No evidence of `--security-opt=no-new-privileges`, `--read-only`, `--cap-drop=ALL`, or user namespace remapping. Containers run with default excessive capabilities. |
| APP-H23 | HIGH | `safeCmd` blacklist missing backtick | The backtick (`` ` ``) enables command substitution in bash but is not in the `safeCmd` blacklist. |
| APP-H24 | HIGH | `rm -rf` for container cleanup | Container data directories are removed with `rm -rf`. If the path is miscalculated, this could delete user data or system files. |
| APP-M19 | MEDIUM | Docker images not pinned by digest | Pulling images by tag allows supply chain attacks where a malicious image replaces a legitimate one. |

### 6.9 Git Deploy Service (gitDeploy.js → gitDeploy.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C12 | CRITICAL | `preDeployScript`/`postDeployScript` — arbitrary code execution | Users can specify shell scripts that are executed during deployment. This is a direct arbitrary command execution vector as the server's user (likely root). Any user who can create or modify a git deployment can run arbitrary commands. |
| APP-H25 | HIGH | Webhook secret validation unverified | HMAC verification timing-safety and algorithm cannot be confirmed from compiled code. |
| APP-H26 | HIGH | Auth tokens stored in plaintext in database | Git deploy tokens in the `git_deploy` database table. Database compromise exposes all git provider tokens. |

### 6.10 License Client Service (licenseClient.js → licenseClient.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H27 | HIGH | Local license cache can be spoofed | `saveLicenseCache`/`getCachedLicense` store license data locally. If the cache file format is known, an attacker could forge a valid cache entry. |
| APP-H28 | HIGH | Hardcoded key hashes in bytecode | `_EXPECTED_KEY_HASH_V1` and `PUBLIC_KEYS` could be extracted by reverse engineering the `.jsc` file. |
| APP-M20 | MEDIUM | Hardware fingerprinting via `execSync` | System information collected via shell commands could be manipulated via PATH manipulation. |
| APP-M21 | MEDIUM | Grace period allows restricted operation | `GRACE_PERIOD_FULL` and `GRACE_PERIOD_RESTRICTED` allow continued operation with expired license. |

### 6.11 Integrity Service (integrity.js → integrity.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H29 | HIGH | Self-integrity check is trivially bypassable | The integrity module itself is a `.jsc` file that can be replaced. An attacker who replaces the manifest and/or the public key PEM file can bypass all integrity checks. |
| APP-H30 | HIGH | `spotCheck` only samples files | Only a subset of files are verified per check. An attacker could modify files not currently being checked. |
| APP-M22 | MEDIUM | Timing of periodic checks allows race conditions | Between integrity checks, files could be temporarily modified and reverted. |

---

## 7. API Routes

### 7.1 Files Route (files.js → files.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H31 | HIGH | `execSync` for file operations | Commands like `chmod -R` executed via shell. If path is manipulated, permissions could be modified beyond intended scope. |
| APP-H32 | HIGH | Path traversal risk | `safePath` is the only defense against `../` traversal in user-supplied paths. |
| APP-M23 | MEDIUM | `BLOCKED_EXTENSIONS` uses blocklist approach | Blocklist is inherently fragile; an allowlist would be more secure. |

### 7.2 Databases Route (databases.js → databases.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C13 | CRITICAL | MySQL GRANT via shell command | `-e "GRANT` found — MySQL privileges are granted via `execSync` shell commands, not parameterized API calls. |
| APP-H33 | HIGH | MySQL root credential exposure | `getMysqlAuth` accesses MySQL root credentials for operations. |
| APP-H34 | HIGH | `mysqldump`/`mysqlcheck` via `execSync` | These operations use `shellArg` for sanitization but the compiled implementation cannot be verified. |
| APP-M24 | MEDIUM | Database passwords stored in plaintext | `db_password` field stored unencrypted in the `databases` table. |

### 7.3 FTP Route (ftp.js → ftp.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H35 | HIGH | `execSync` with shell escaping for FTP operations | FTP user operations use shell commands. |
| APP-M25 | MEDIUM | Home directory path traversal | `home_directory` field in FTP account creation could allow traversal if not validated. |

### 7.4 Hosting Route (hosting.js → hosting.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C14 | CRITICAL | System provisioning with shell commands | `execSync`, `shellEscape`, `provisionAccount`, `chown` — account provisioning executes privileged shell commands. |
| APP-H36 | HIGH | PHP-FPM configuration manipulation | User-controlled PHP settings written to system configs. |
| APP-H37 | HIGH | `chown` via shell | If username is manipulable, could change ownership of arbitrary files. |

### 7.5 SSL Route (ssl.js → ssl.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H38 | HIGH | Private key handling on filesystem | SSL private keys written to disk with potential permission issues. |
| APP-M26 | MEDIUM | `unlinkSync` on certificate files | If path is manipulable, could delete arbitrary files. |

### 7.6 Security Route (security.js → security.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H39 | HIGH | Nginx config manipulation | Writes to nginx configuration based on user input. |
| APP-M27 | MEDIUM | DNS rebinding risk | `resolveDomain` could be exploited for DNS rebinding attacks. |

### 7.7 Cron Jobs Route (cronjobs.js → cronjobs.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-C15 | CRITICAL | Arbitrary command execution as cron | User-supplied commands are written to crontab and executed with user's privileges. No command allowlist or validation. |
| APP-H40 | HIGH | Crontab injection | If commands aren't properly escaped, newline injection could add arbitrary cron entries. |
| APP-M28 | MEDIUM | Temp files in `/tmp/` | `/tmp/hpane*` for crontab management in world-writable directory. |

### 7.8 Domains Route (domains.js → domains.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-M29 | MEDIUM | DNS zone file manipulation | Zone files written based on user input with `execSync`. |

### 7.9 Migration Route (migration.js → migration.jsc)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H41 | HIGH | File upload handling | Accepts archive uploads which could contain path traversal payloads (Zip Slip). |
| APP-M30 | MEDIUM | API token handling | Migration API tokens stored/transmitted potentially insecurely. |

---

## 8. Configuration & Deployment

### 8.1 Database Configuration (database.js — READABLE)

| ID | Severity | Finding | Line | Code |
|----|----------|---------|------|------|
| APP-C16 | CRITICAL | Hardcoded default DB credentials | 8-9 | `user: process.env.DB_USER \|\| 'postgres'`, `password: process.env.DB_PASSWORD \|\| 'postgres'` |
| APP-H42 | HIGH | Process exit on idle connection error | 16-18 | `process.exit(-1)` on pool error — DoS vector |

**Code snippet:**
```javascript
// Lines 8-9 — CRITICAL
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
```

### 8.2 Database Seed (seed.js — READABLE)

| ID | Severity | Finding | Line | Code |
|----|----------|---------|------|------|
| APP-C17 | CRITICAL | Hardcoded admin password | 10 | `await bcrypt.hash('admin123', 12)` |
| APP-H43 | HIGH | Credentials printed to console | 77 | `console.log('Admin -> admin@hpanel.local / admin123')` |

**Code snippet:**
```javascript
// Line 10 — CRITICAL
const hashedPassword = await bcrypt.hash('admin123', 12);

// Line 77 — HIGH
console.log('   Admin  -> admin@hpanel.local / admin123');
```

### 8.3 Database Migration (migrate.js — READABLE)

| ID | Severity | Finding | Line | Details |
|----|----------|---------|------|---------|
| APP-H44 | HIGH | Dynamic SQL via string interpolation | 85-87 | `ALTER TABLE packages ADD COLUMN IF NOT EXISTS ${col}` |
| APP-M31 | MEDIUM | Database passwords stored in plaintext | 184-185 | `db_user VARCHAR(100)`, `db_password VARCHAR(255)` |
| APP-M32 | MEDIUM | Git auth tokens stored in plaintext | 592 | `auth_token TEXT` |
| APP-M33 | MEDIUM | DKIM keys stored in plaintext | 458 | `dkim_key TEXT DEFAULT ''` |

### 8.4 Environment Configuration (.env.example)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-H45 | HIGH | Placeholder secrets may remain unchanged | `DB_PASSWORD=CHANGE_THIS_TO_STRONG_PASSWORD`, `JWT_SECRET=CHANGE_THIS_TO_64_CHAR_RANDOM_STRING` |
| APP-M34 | MEDIUM | Backup credentials in plaintext | `BACKUP_STORAGE_USER=`, `BACKUP_STORAGE_PASS=` |
| APP-L04 | LOW | Default non-SSL ports documented | `USER_HTTP_PORT=2052` |

### 8.5 Updater Script (updater.sh — READABLE)

| ID | Severity | Finding | Line | Details |
|----|----------|---------|------|---------|
| APP-H46 | HIGH | Lock file in `/tmp/` (symlink attack) | 14 | `LOCK_FILE="/tmp/hpanel-updater.lock"` |
| APP-H47 | HIGH | `npm install` executes arbitrary code | 328 | `npm install --production --no-audit --no-fund` |
| APP-H48 | HIGH | Shell variable interpolated into Python | 49, 60-61 | `python3 -c "import json; d=json.load(open('${INSTALL_DIR}/update-settings.json'))..."` |
| APP-M35 | MEDIUM | No HTTPS certificate pinning | 147 | `curl -sf` without `--pinpubkey` or `--cacert` |

### 8.6 Package Configuration (package.json)

| ID | Severity | Finding | Details |
|----|----------|---------|---------|
| APP-M36 | MEDIUM | `javascript-obfuscator` in devDependencies | Code obfuscation makes security auditing impossible |
| APP-L05 | LOW | Dependency audit needed | `node-pty`, `ws`, `bytenode` have had CVEs |

---

## 9. PHP SSO Components

### 9.1 phpMyAdmin Signon (signon.php — READABLE)

| ID | Severity | Finding | Line | Details |
|----|----------|---------|------|---------|
| APP-C18 | CRITICAL | MySQL credentials in `/tmp/` | 17, 29 | Token files at `/tmp/hpanel-pma-sso/<token>.json` contain MySQL `user` and `password` in plaintext JSON. Any local user can read these. |
| APP-M37 | MEDIUM | Token files in world-writable `/tmp/` | 17 | Symlink attacks possible |
| APP-L06 | LOW | Token regex is strong | 23 | `/^[a-f0-9]{64}$/` — good validation |
| APP-L07 | LOW | One-time use enforced | 39 | `@unlink($token_file)` — good practice |

**Code snippet:**
```php
// Line 17 — CRITICAL: Token directory in /tmp
$token_dir = '/tmp/hpanel-pma-sso';

// Line 29 — Path constructed from token
$token_file = $token_dir . '/' . $token . '.json';
```

### 9.2 Webmail Auto-Login (webmail-autologin.php — READABLE)

| ID | Severity | Finding | Line | Details |
|----|----------|---------|------|---------|
| APP-C19 | CRITICAL | Hardcoded fallback SSO secret | 35-36 | `if (empty($SSO_SECRET)) { $SSO_SECRET = 'hpanel-sso-default-key'; }` — An attacker who knows this default can forge webmail SSO tokens for any email address, gaining full mailbox access via Dovecot master user. |
| APP-H49 | HIGH | Dovecot master credentials in `.env` | 29-30 | `$DOVECOT_MASTER_USER`, `$DOVECOT_MASTER_PASS` stored in the `.env` file |
| APP-M38 | MEDIUM | Used token markers in `/tmp/` | 39, 77 | `$USED_TOKENS_DIR = '/tmp/hpanel-webmail-sso-used'` |
| APP-M39 | MEDIUM | Raw `.env` file parsing | 20-31 | Custom parser doesn't handle quoted values or special characters |
| APP-L08 | LOW | HMAC verification uses `hash_equals` | 63-65 | Timing-safe comparison (good) |

**Code snippet:**
```php
// Lines 35-36 — CRITICAL: Hardcoded fallback SSO secret
if (empty($SSO_SECRET)) {
    $SSO_SECRET = 'hpanel-sso-default-key';
}
```

### 9.3 phpMyAdmin Configuration (config.inc.php — READABLE)

| ID | Severity | Finding | Line | Details |
|----|----------|---------|------|---------|
| APP-H50 | HIGH | Template placeholder for `blowfish_secret` | 15 | `$cfg['blowfish_secret'] = '{{BLOWFISH_SECRET}}'` — if not replaced, cookie encryption is broken |
| APP-M40 | MEDIUM | Configuration permission checks disabled | 38 | `$cfg['CheckConfigurationPermissions'] = false` |

---

## 10. Consolidated Findings Summary

### 10.1 By Severity

| Severity | Installer Script | Panel Application | Total |
|----------|-----------------|-------------------|-------|
| **CRITICAL** | 8 | 19 | **27** |
| **HIGH** | 6 | 28 | **34** |
| **MEDIUM** | 7 | 30 | **37** |
| **LOW** | 9 | 17 | **26** |
| **Total** | **30** | **94** | **124** |

> Note: The panel application total (94) includes findings from the cross-cutting analysis that appear in multiple sections. After deduplication, there are approximately 75 unique panel application findings.

### 10.2 By Category

| Category | Critical | High | Medium | Low |
|----------|----------|------|--------|-----|
| Hardcoded Secrets / Default Credentials | 5 | 2 | 1 | 0 |
| Command Injection / RCE | 6 | 8 | 2 | 0 |
| Authentication / Authorization Bypass | 3 | 5 | 4 | 1 |
| Privilege Escalation | 2 | 6 | 2 | 0 |
| Path Traversal / File Operations | 1 | 4 | 3 | 0 |
| Insecure Configuration | 2 | 4 | 5 | 2 |
| Credential Storage / Handling | 3 | 3 | 4 | 0 |
| Input Validation / Sanitization | 0 | 4 | 3 | 1 |
| Infrastructure / Deployment | 0 | 4 | 3 | 2 |
| Code Obfuscation / Audit | 1 | 2 | 1 | 0 |
| Integrity / Supply Chain | 1 | 2 | 2 | 0 |
| Other | 3 | 4 | 6 | 4 |

### 10.3 Top 10 Most Critical Findings

| Rank | ID | Finding | Impact |
|------|-----|---------|--------|
| 1 | APP-C12 | Git deploy `preDeployScript`/`postDeployScript` arbitrary code execution | Any user with git deploy access can execute arbitrary commands as root |
| 2 | APP-C08 | Terminal sessions run as root with `/bin/bash` | Any user with terminal access gets a root shell on the server |
| 3 | APP-C19 | Hardcoded fallback SSO secret in webmail | Attacker can forge webmail SSO tokens for any mailbox |
| 4 | APP-C16 | Default database credentials `postgres/postgres` | If `.env` is misconfigured, full database access with superuser privileges |
| 5 | APP-C17 | Hardcoded admin password `admin123` | Default admin account with known password if seed is run without modification |
| 6 | APP-C15 | Arbitrary cron command execution | Users can execute arbitrary commands via crontab with no allowlist |
| 7 | APP-C18 | MySQL credentials in plaintext in `/tmp/` | Local users can steal database credentials from SSO token files |
| 8 | APP-C01 | `execSync` in authentication routes | Potential RCE in the authentication code path |
| 9 | APP-C09 | No terminal session ownership verification | Any authenticated user could access other users' terminal sessions |
| 10 | INS-C01 | Plaintext Dovecot master password | Any local user who can read `.env` gains full IMAP access to all mailboxes |

---

## 11. Risk Rating & Overall Assessment

### 11.1 Overall Risk Rating: CRITICAL

The hPanel hosting control panel presents a **CRITICAL** overall security risk. The combination of the following factors makes the current state unsuitable for production deployment without significant remediation:

1. **Systemic root execution** — The entire application appears to run as root, with `execSync`/`spawnSync` calls throughout the codebase. This means any vulnerability (command injection, path traversal, authorization bypass) can be directly exploited for full system compromise.

2. **Hardcoded secrets** — Multiple hardcoded default credentials and fallback secrets exist, any one of which could provide an attacker with complete access to the system.

3. **Unauditable code** — The use of bytenode compilation and javascript-obfuscator means the actual security implementation cannot be verified by end users or independent auditors.

4. **Plaintext credential storage** — Database passwords, API tokens, DKIM keys, and other secrets are stored unencrypted, both in the database and in temporary files.

5. **No defense in depth** — Multiple single points of failure exist where a bypass of one security control (e.g., `shellEscape`) compromises the entire system.

### 11.2 Attack Scenario

An attacker with a regular user account on a server running hPanel could:

1. **Access any mailbox** — Using the known fallback SSO key (`hpanel-sso-default-key`) to forge webmail SSO tokens
2. **Gain root shell** — Using the terminal service which runs as root without session ownership verification
3. **Execute arbitrary commands** — Using the git deploy `preDeployScript`/`postDeployScript` feature
4. **Access all databases** — Reading MySQL credentials from `/tmp/hpanel-pma-sso/` token files
5. **Modify DNS** — Injecting DNS zone entries via the domains route
6. **Escalate to full server compromise** — Using any of the `execSync` injection vectors with root privileges

### 11.3 CVSS Score Estimate

Based on the most critical finding (terminal sessions running as root + no session ownership verification):

**CVSS 3.1 Score: 9.8 (Critical)**  
Vector: `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`

---

## 12. Prioritized Remediation Roadmap

### Phase 1: Immediate (0-7 days) — Critical Fixes

| Priority | Finding | Action |
|----------|---------|--------|
| P1 | APP-C19: Hardcoded SSO fallback key | Remove `'hpanel-sso-default-key'` fallback. Fail securely if `JWT_SECRET` is not configured. |
| P2 | APP-C08: Terminal runs as root | Run terminal sessions as the owning user using `sudo -u <username>` or setuid. Never run as root. |
| P3 | APP-C12: Git deploy arbitrary scripts | Remove `preDeployScript`/`postDeployScript` or execute in a strictly sandboxed environment as the website owner (never root). |
| P4 | APP-C16: Default DB credentials | Remove `postgres/postgres` fallback. Application must fail to start if `DB_PASSWORD` is not set. |
| P5 | APP-C17: Hardcoded admin password | Generate random admin password during seed. Add `must_change_password` flag. |
| P6 | APP-C15: Arbitrary cron commands | Validate cron commands against a strict allowlist. Prevent newline injection. |
| P7 | APP-C18: MySQL creds in /tmp | Move SSO tokens to `/var/lib/hpanel/sso-tokens/` with mode 0700. Encrypt credentials in token files. |
| P8 | APP-C09: No terminal session ownership | Validate session ownership on every `getSession` call. The requesting user must own the session. |

### Phase 2: Short-term (1-4 weeks) — High-Priority Fixes

| Priority | Finding | Action |
|----------|---------|--------|
| P9 | Systemic root execution | Run the panel as a dedicated non-root user. Use `sudo` with strict `/etc/sudoers` whitelist for only commands that need elevation. |
| P10 | APP-H02: JWT algorithm not restricted | Set `algorithms: ['HS256']` in `jwt.verify()`. |
| P11 | APP-H05: IP spoofing bypasses rate limits | Configure `trust proxy` properly. Validate `X-Forwarded-For` only from known proxy IPs. |
| P12 | APP-H11: `safePath` blacklist approach | Replace with whitelist + `path.resolve()` + base directory check. |
| P13 | APP-H22: Missing Docker security | Add `--security-opt=no-new-privileges`, `--cap-drop=ALL`, and resource limits. |
| P14 | APP-H23: `safeCmd` missing backtick | Add backtick to the `safeCmd` blacklist. Better yet, use argument arrays. |
| P15 | APP-H46-H48: Updater script issues | Move lock file out of `/tmp`. Use `jq` instead of Python for JSON. Use `npm ci` instead of `npm install`. |
| P16 | APP-H50: Template blowfish_secret | Generate strong random `blowfish_secret` during installation. |
| P17 | INS-C04: No download integrity | Add SHA-256 checksum and/or RSA signature verification for all downloads. |
| P18 | INS-C01: Plaintext Dovecot master password | Encrypt the master password at rest. Restrict Dovecot master user to 127.0.0.1 only. |

### Phase 3: Medium-term (1-3 months) — Architecture Improvements

| Priority | Finding | Action |
|----------|---------|--------|
| P19 | Code obfuscation | Provide source code for security audit or engage a third-party audit service with NDA access. |
| P20 | Plaintext credential storage | Encrypt `db_password`, `auth_token`, `dkim_key`, and webhook secrets at rest using AES-256-GCM. |
| P21 | `execSync` throughout codebase | Replace shell command execution with native Node.js APIs (`fs.chmod`, `fs.chown`, MySQL protocol libraries, Docker SDK). |
| P22 | Rate limiting not distributed | Use Redis-backed rate limiting store for multi-process consistency. |
| P23 | License bypass | Sign license cache files cryptographically. Mandate online verification. |
| P24 | Integrity checks bypassable | Store manifest signature and public key in a tamper-evident location. Perform full (not spot) checks. |
| P25 | INS-M05: Redis without authentication | Configure Redis with a strong password and bind to 127.0.0.1 only. |
| P26 | INS-M04: Postfix no TLS enforcement | Enforce TLS for both incoming and outgoing mail. |
| P27 | INS-C07: chmod 777 on phpMyAdmin tmp | Change to mode 0755 or 0700. |

### Phase 4: Long-term (3-6 months) — Defense in Depth

| Priority | Finding | Action |
|----------|---------|--------|
| P28 | No mandatory access control | Implement SELinux/AppArmor profiles for all panel services. |
| P29 | No firewall configuration | Configure `ufw` with deny-by-default policy. Only open required ports. |
| P30 | No automatic security updates | Configure `unattended-upgrades` for security patches. |
| P31 | No disk encryption | Implement LUKS disk encryption for data at rest. |
| P32 | No audit logging | Implement immutable audit logging for all admin actions, login events, and privilege escalations. |
| P33 | Container isolation | Run the panel itself in a container with restricted capabilities. |
| P34 | Secrets management | Move from `.env` files to a proper secrets manager (HashiCorp Vault, AWS Secrets Manager, etc.). |
| P35 | Regular security audits | Schedule quarterly security audits and penetration tests. |

---

## Appendix A: Installer Script Download URL

The panel code archive is distributed at:

```
https://securedownloads.hpanel.net/server.zip
```

The installer script is distributed at:

```
https://securedownloads.hpanel.net/latest.sh
```

## Appendix B: File Inventory

### Installer Script
- `latest.sh` — 2,629 lines, Bash

### Panel Application (server.zip)
- **Total files:** 145
- **JavaScript route modules:** 25
- **JavaScript service modules:** 15
- **JavaScript utility modules:** 3
- **Middleware modules:** 4
- **PHP scripts:** 3
- **Shell scripts:** 4
- **Configuration files:** 5
- **Compiled bytecode (.jsc):** 47
- **Client bundle (Vue/React):** 1

## Appendix C: Tools & Techniques Used

| Technique | Application |
|-----------|-------------|
| Line-by-line source review | Installer script, readable .js/.php/.sh files |
| String extraction (`strings`) | Compiled .jsc bytecode files |
| Configuration analysis | .env.example, package.json, ecosystem.config.js |
| Dependency analysis | package.json dependency tree |
| Database schema review | migrate.js table definitions |
| Cross-reference analysis | Installer behavior vs. application expectations |

---

*This report was generated on 2026-05-26. The findings are based on analysis of the software as distributed and may not reflect the current state if patches have been applied since the audit was conducted.*
