# OpenVAPT Self-Assessment Report

> OpenVAPT is an academic, non-commercial project with source-available code built by AI Cybersecurity Engineers (AI systems, not people or registered engineers). This report was produced by automated, read-only checks run by the user on their own machine, for their own use, 100% at their own risk. It is not a penetration test, an audit, a certification, a service or professional advice, and it may be wrong or incomplete. The developers accept no responsibility or liability for any decision taken from it. OpenVAPT may not be sold or used to provide services. No data from this report was sent anywhere.

| Item | Value |
|---|---|
| Host | lab-vulnerable (simulated) |
| Operating System | Ubuntu 18.04.6 LTS |
| Scan Time (UTC) | 2026-10-16 02:10:05 |
| Run As Root | true |
| Tool | OpenVAPT v0.1.0-dev (unknown, dev channel) |

Vulnerability snapshot: 2021-01-01 (2114 days old, 7 advisories). Warning: older than 90 days; newer vulnerabilities are not in it. This warning is not a failure.

## Executive Summary

Scope: this report covers one machine, lab-vulnerable (simulated), assessed on 2026-10-16 02:10 UTC, with root access. 310 checks ran, read-only and limited to this machine.

Result: 308 checks failed (14 Critical, 73 High, 89 Medium, 123 Low, 9 Info) and 2 passed. Critical weaknesses require immediate attention.

Coverage: 0 Incomplete, 0 Error and 0 Skipped, plus 3 targets inside other checks that could not be fully read. These areas are not shown to be free of weaknesses; see Coverage Gaps.

Vulnerability snapshot: 2021-01-01 (2114 days old, 7 advisories). Warning: older than 90 days; newer vulnerabilities are not in it. This warning is not a failure.

Limitation: this is an automated, read-only self-assessment, not a penetration test. A clean result does not prove the machine is free of weaknesses.

| Critical | High | Medium | Low | Passed | Incomplete | Incomplete Targets | Skipped | Errors |
|---|---|---|---|---|---|---|---|---|
| 14 | 73 | 89 | 123 | 2 | 0 | 3 | 0 | 0 |

### Top 5 Risks

| ID | Risk | Severity | CVSS | Why It Matters |
|---|---|---|---|---|
| HOST-CTR-001 | Docker API Exposed Over TCP Without TLS | critical | 10.0 | The Docker API is equivalent to root on the host. |
| HOST-K8S-001 | Kubelet Allows Anonymous or Unauthorised Access | critical | 10.0 | The kubelet API can run commands in every pod on the node. |
| HOST-CVE-002 | Known Exploited Vulnerabilities Present | critical | 9.8 | Known vulnerable package versions can expose a server to documented attacks. |
| HOST-SSH-003 | SSH Permits Empty Passwords | critical | 9.8 | Any account without a password becomes an open door to anyone on the network. |
| NET-DB-002 | Redis Authentication and Network Binding | critical | 9.8 | An unauthenticated network-reachable Redis instance can disclose or alter data and may permit dangerous administrative operations. |

### Notable Coverage Gaps

- NET-TLS-001 (shop.example.test on port 10000): Modern TLS handshake failed, so legacy protocol rejection could not be verified
- NET-TLS-005 (shop.example.test on port 10000): TLS certificate could not be read
- NET-TLS-006 (shop.example.test on port 10000): TLS certificate could not be read

## Risk Heat Map

| Category | Critical | High | Medium | Low | Info |
|---|---|---|---|---|---|
| Identity and Authentication | 3 | 8 | 13 | 12 | 2 |
| Access Control | 0 | 18 | 7 | 6 | 0 |
| Patch and Vulnerability Management | 1 | 9 | 3 | 4 | 2 |
| Secure Configuration | 2 | 5 | 19 | 54 | 4 |
| Cryptography and TLS | 0 | 7 | 14 | 2 | 0 |
| Logging and Monitoring | 0 | 0 | 1 | 22 | 0 |
| Network Exposure | 2 | 6 | 4 | 1 | 0 |
| Data Services | 4 | 8 | 4 | 1 | 0 |
| Web Application Security | 1 | 1 | 10 | 4 | 1 |
| Information Exposure | 1 | 11 | 14 | 17 | 0 |

## Compliance Coverage

Coverage is a mapping of automated checks, not certification of compliance.

| Framework | Safeguard or Subcategory | Checks Run | Failed | Passed |
|---|---|---|---|---|
| CIS Controls v8 | 2.2 | 5 | 5 | 0 |
| CIS Controls v8 | 2.5 | 1 | 1 | 0 |
| CIS Controls v8 | 3.3 | 51 | 51 | 0 |
| CIS Controls v8 | 3.10 | 23 | 23 | 0 |
| CIS Controls v8 | 4.1 | 75 | 75 | 0 |
| CIS Controls v8 | 4.3 | 1 | 1 | 0 |
| CIS Controls v8 | 4.4 | 6 | 5 | 1 |
| CIS Controls v8 | 4.7 | 1 | 1 | 0 |
| CIS Controls v8 | 4.8 | 24 | 23 | 1 |
| CIS Controls v8 | 5.2 | 23 | 23 | 0 |
| CIS Controls v8 | 5.3 | 1 | 1 | 0 |
| CIS Controls v8 | 5.4 | 5 | 5 | 0 |
| CIS Controls v8 | 7.3 | 8 | 8 | 0 |
| CIS Controls v8 | 7.4 | 5 | 5 | 0 |
| CIS Controls v8 | 8.2 | 11 | 11 | 0 |
| CIS Controls v8 | 8.3 | 4 | 4 | 0 |
| CIS Controls v8 | 8.4 | 1 | 1 | 0 |
| CIS Controls v8 | 8.5 | 5 | 5 | 0 |
| CIS Controls v8 | 8.9 | 1 | 1 | 0 |
| CIS Controls v8 | 16.2 | 1 | 1 | 0 |
| CIS Controls v8 | 16.7 | 58 | 58 | 0 |
| NIST CSF 2.0 | DE.CM-09 | 1 | 1 | 0 |
| NIST CSF 2.0 | ID.RA-01 | 18 | 18 | 0 |
| NIST CSF 2.0 | ID.RA-08 | 1 | 1 | 0 |
| NIST CSF 2.0 | PR.AA-01 | 25 | 25 | 0 |
| NIST CSF 2.0 | PR.AA-05 | 56 | 56 | 0 |
| NIST CSF 2.0 | PR.DS-01 | 51 | 51 | 0 |
| NIST CSF 2.0 | PR.DS-02 | 23 | 23 | 0 |
| NIST CSF 2.0 | PR.IR-01 | 30 | 28 | 2 |
| NIST CSF 2.0 | PR.PS-01 | 158 | 157 | 1 |
| NIST CSF 2.0 | PR.PS-02 | 18 | 18 | 0 |
| NIST CSF 2.0 | PR.PS-04 | 22 | 22 | 0 |
| NIST CSF 2.0 | PR.PS-05 | 1 | 1 | 0 |

## Prioritised Remediation Plan

High-severity, low-effort quick wins appear first.

1. **HOST-CTR-001 - Docker API Exposed Over TCP Without TLS** (critical severity, low effort)
   Remove tcp:\/\/ listeners and use the local Unix socket or SSH (docker context with ssh:\/\/). If remote access is unavoidable, enable tlsverify with client certificates and restrict the port in the firewall.
2. **HOST-SSH-003 - SSH Permits Empty Passwords** (critical severity, low effort)
   Set  PermitEmptyPasswords no  in /etc/ssh/sshd\_config, reload sshd, and give every account a password or lock it (passwd -l \<user\>).
3. **NET-DB-002 - Redis Authentication and Network Binding** (critical severity, low effort)
   Set protected-mode yes, configure ACL users with strong secrets, bind to 127.0.0.1 and ::1, and restrict remote access with a firewall.
4. **NET-DB-003 - MongoDB Authorization and Network Binding** (critical severity, low effort)
   Set security.authorization to enabled and net.bindIp to 127.0.0.1, then allow only approved clients through a controlled network path.
5. **NET-DB-004 - PostgreSQL Trust and Broad Client Access** (critical severity, low effort)
   Bind PostgreSQL to required interfaces only and replace trust or unrestricted pg\_hba.conf rules with narrow CIDRs and scram-sha-256. Caution: users whose passwords are stored as md5 cannot log in with scram-sha-256 until their password is set again with password\_encryption = scram-sha-256, so reset them first and reload, not restart, to test.
6. **NET-DB-007 - Elasticsearch Security and Network Binding** (critical severity, low effort)
   Set xpack.security.enabled true, bind network.host to loopback or an approved private interface, require TLS, and filter access.
7. **WEB-NODE-002 - Node.js Debugger (Inspector) Enabled in a Service** (critical severity, low effort)
   Remove --inspect from the service and from NODE\_OPTIONS, run systemctl daemon-reload and restart. Debug on a development machine, or temporarily over an SSH tunnel to 127.0.0.1:9229.
8. **WEB-PY-004 - Python Interactive Debugger Enabled in a Service** (critical severity, low effort)
   Remove the debug switches, run the application under a production WSGI server (Gunicorn, uWSGI or mod\_wsgi) behind nginx or Apache, run systemctl daemon-reload and restart the service.
9. **WEB-TOM-002 - Tomcat Administrative Users With Default or Weak Passwords** (critical severity, low effort)
   Remove accounts you do not need. Give the remaining ones long random passwords and store them as digests: configure a CredentialHandler (for example SecretKeyCredentialHandler with PBKDF2) in the Realm and generate hashes with bin/digest.sh. Restart Tomcat.
10. **WEB-TOM-004 - Tomcat AJP Connector Unprotected (Ghostcat)** (critical severity, low effort)
   If you do not use AJP (most sites proxy over HTTP), delete or comment out the AJP \<Connector\>. Otherwise set address="127.0.0.1", a long random secret="..." with secretRequired="true", and the same secret in the proxy (mod\_proxy\_ajp: ProxyPass ... secret=...). Upgrade Tomcat to a supported release.
11. **HOST-CRON-001 - Cron Jobs Run Programs Other Users Can Modify** (high severity, low effort)
   chmod o-w \<program\> and its folder; move scripts out of /tmp or user-writable folders into /usr/local/sbin owned by root; have cron run only files owned by root (chown root:root).
12. **HOST-CRON-002 - World-Writable Directory in Cron's PATH** (high severity, low effort)
   Remove the writable folder from PATH in /etc/crontab (or chmod o-w it), delete relative entries, and call programs in cron jobs by absolute path.
13. **HOST-CTR-008 - Docker Socket Accessible to Non-Administrators** (high severity, low effort)
   chmod 660 /var/run/docker.sock && chown root:docker /var/run/docker.sock  (the daemon recreates it with these defaults). Remove users from the docker group (gpasswd -d \<user\> docker) and give administrators sudo docker, or move to rootless Docker.
14. **HOST-FS-001 - Sensitive Account Files Have Unsafe Permissions** (high severity, low effort)
   chown root:root /etc/passwd /etc/group && chmod 644 /etc/passwd /etc/group ; chown root:shadow /etc/shadow /etc/gshadow && chmod 640 /etc/shadow /etc/gshadow  (use root:root and 000 on RHEL-family systems).
15. **HOST-FS-002 - Temporary Directory Missing the Sticky Bit** (high severity, low effort)
   chmod 1777 /tmp /var/tmp
16. **HOST-FS-005 - Cron Files Writable by Non-Root Users** (high severity, low effort)
   chown root:root \<path\> && chmod go-w \<path\>  for each listed path (600 for /etc/crontab is a common hardening choice).
17. **HOST-FS-007 - Private TLS Keys Readable by Others** (high severity, low effort)
   chown root:root \<key\> && chmod 600 \<key\>  (or 640 with a dedicated group the web server runs in). Rotate any key that was exposed.
18. **HOST-FS-022 - Account Database Files or Their Backups Not Owned and Locked by Root** (high severity, low effort)
   chown root:root /etc/passwd /etc/passwd- /etc/group /etc/group- && chmod 644 /etc/passwd /etc/group && chmod 600 /etc/passwd- /etc/group- ; chown root:shadow /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow- && chmod 640 /etc/shadow /etc/gshadow && chmod 600 /etc/shadow- /etc/gshadow-  (use root:root and mode 000 on RHEL family systems).
19. **HOST-K8S-002 - Kubernetes Data Directory Accessible to Others** (high severity, low effort)
   chmod 700 /var/lib/etcd && chown etcd:etcd /var/lib/etcd  (or root:root for kubeadm static pods). Enable encryption at rest for Secrets.
20. **HOST-K8S-003 - Kubernetes Credential Files Readable by Others** (high severity, low effort)
   chmod 600 /etc/kubernetes/\*.conf /etc/kubernetes/pki/\*.key /etc/kubernetes/pki/etcd/\*.key && chown root:root on each.
21. **HOST-PKG-002 - Repository Signature Checks Disabled** (high severity, low effort)
   Remove trusted=yes (or Trusted: yes) from the listed source, install the repository's signing key under /etc/apt/keyrings and reference it with signed-by=; remove AllowUnauthenticated and AllowInsecureRepositories settings. On dnf and yum set gpgcheck=1 (and repo\_gpgcheck=1 where supported) in each listed repository and import its key with rpm --import.
22. **HOST-SSH-001 - SSH Allows Direct Root Login** (high severity, low effort)
   In /etc/ssh/sshd\_config set:  PermitRootLogin no  (or prohibit-password if key-based root login is truly required). Log in as a named user and use sudo. Then run: sshd -t && systemctl reload sshd
23. **HOST-SSH-009 - SSH Host Private Keys Readable by Others** (high severity, low effort)
   chown root:root /etc/ssh/ssh\_host\_\*\_key && chmod 600 /etc/ssh/ssh\_host\_\*\_key  (0640 with group ssh\_keys is acceptable on RHEL family).
24. **HOST-SSH-010 - SSH Server Configuration Writable by Non-Root Users** (high severity, low effort)
   chown root:root /etc/ssh/sshd\_config /etc/ssh/sshd\_config.d/\*.conf && chmod 600 /etc/ssh/sshd\_config /etc/ssh/sshd\_config.d/\*.conf
25. **HOST-SYSD-003 - Service Unit Files Writable by Non-Root Users** (high severity, low effort)
   chown root:root \<file\> && chmod 644 \<file\> for each listed file, then review its contents for changes you did not make (ExecStart=, User=, ExecStartPre=) and run systemctl daemon-reload.
26. **HOST-SYSD-004 - Service Programs in Writable Locations** (high severity, low effort)
   Install the program in a root-owned location (/usr/local/bin or /opt/\<app\>), chown root:root and chmod 755 it, point ExecStart= at it, then systemctl daemon-reload and restart the service.
27. **HOST-USR-002 - Accounts Without a Password** (high severity, low effort)
   For each listed user either set a password (passwd \<user\>) or lock the account (passwd -l \<user\>).
28. **HOST-USR-003 - Sudo Rules Without Password (NOPASSWD)** (high severity, low effort)
   Edit with visudo and remove NOPASSWD, or restrict it to one specific, harmless command for automation accounts.
29. **HOST-USR-007 - Insecure Trust Files in Home Directories** (high severity, low effort)
   Delete .rhosts and .shosts files. Replace .netrc credentials with a secret manager or key-based authentication, then delete the file and rotate the passwords it contained.
30. **NET-AI-001 - AI Model Server or Dashboard Reachable From the Network** (high severity, low effort)
   Bind each service to 127.0.0.1 (Ollama: OLLAMA\_HOST=127.0.0.1:11434 in its service environment; Ray: --dashboard-host=127.0.0.1; Gradio: server\_name="127.0.0.1"), and if others need it, publish it only through a reverse proxy with authentication and TLS.
31. **NET-AI-002 - Jupyter Configuration Weakens Authentication** (high severity, low effort)
   Remove the empty token line so Jupyter generates a random token, or set a password with  jupyter server password . Remove disable\_check\_xsrf = True. Bind to 127.0.0.1 and reach it through an SSH tunnel.
32. **NET-APP-001 - Debugger or Application Control Ports Exposed** (high severity, low effort)
   Bind each to 127.0.0.1 or a Unix socket (node --inspect=127.0.0.1:9229; Tomcat Connector address="127.0.0.1" or remove the AJP connector; PHP-FPM listen = /run/php/php-fpm.sock; jupyter --ip=127.0.0.1) and never expose a debugger.
33. **NET-DB-001 - Database or Cache Service Exposed to the Network** (high severity, low effort)
   Bind the service to 127.0.0.1 (MySQL: bind-address = 127.0.0.1; Redis: bind 127.0.0.1 ::1 and protected-mode yes; MongoDB: net.bindIp: 127.0.0.1) and restart it. Caution: confirm first that no application on another machine connects to it, or that application loses its database. If remote access is needed, allow only specific source IPs in the firewall and require TLS and authentication.
34. **NET-DB-005 - MySQL or MariaDB Unsafe Network Options** (high severity, low effort)
   Disable local\_infile and skip-grant-tables. Bind to 127.0.0.1 or enable skip-networking when TCP access is unnecessary.
35. **NET-DB-006 - Memcached UDP or Broad Listen Address** (high severity, low effort)
   Set UDP\_PORT=0 or -U 0 and bind with -l 127.0.0.1. Use a firewall for any explicitly required clients.
36. **NET-DB-008 - Unsafe NFS Export Options** (high severity, low effort)
   Restrict every export to explicit client CIDRs, use root\_squash, remove insecure, and reload exports.
37. **NET-DB-009 - Unsafe Samba Guest or SMB1 Configuration** (high severity, low effort)
   Set guest ok = no, map to guest = Never, and server min protocol = SMB2\_02 or newer. Review each share ACL.
38. **NET-DB-010 - BIND Open Recursion** (high severity, low effort)
   Disable recursion on authoritative-only servers, or set allow-recursion and allow-query-cache to explicit trusted networks.
39. **NET-DB-011 - BIND Zone Transfers Allowed to Any** (high severity, low effort)
   Set allow-transfer to none or explicit secondary server addresses, preferably protected with TSIG.
40. **NET-DB-012 - Postfix Open Relay Controls** (high severity, low effort)
   Set smtpd\_relay\_restrictions = permit\_mynetworks, permit\_sasl\_authenticated, reject\_unauth\_destination and narrow mynetworks.
41. **NET-FW-003 - Firewall Allows Sensitive Services From Anywhere** (high severity, low effort)
   Delete the rule or restrict it to the addresses that need it, for example: ufw delete allow 3306 && ufw allow from 203.0.113.10 to any port 3306 proto tcp . Better, bind the service to 127.0.0.1 and reach it through an SSH tunnel or VPN.
42. **NET-SVC-003 - Network Time Service Amplification Risk** (high severity, low effort)
   Bind the time daemon to required interfaces, restrict clients to explicit networks, disable monitoring, and use noquery or equivalent controls.
43. **WEB-APA-005 - Apache Allows TLS 1.0 or 1.1** (high severity, low effort)
   Set  SSLProtocol -all +TLSv1.2 +TLSv1.3  (Mozilla intermediate) in mods-enabled/ssl.conf or ssl.conf, then apachectl configtest and reload.
44. **WEB-APA-006 - Apache Cipher Suite Includes Weak Suites** (high severity, low effort)
   Use the Mozilla intermediate list for SSLCipherSuite and set  SSLHonorCipherOrder off , as the Mozilla SSL Configuration Generator produces for Apache.
45. **WEB-APP-001 - Laravel Debug Mode On or Application Key Missing** (high severity, low effort)
   Set APP\_DEBUG=false and APP\_ENV=production in .env, run php artisan key:generate if APP\_KEY is empty, then php artisan config:cache. Keep .env outside the web root (Laravel's public/ folder) and readable only by the application user.
46. **WEB-APP-003 - Symfony Running in the Development Environment** (high severity, low effort)
   Set APP\_ENV=prod and APP\_DEBUG=0 in .env (or .env.local), run composer dump-env prod, clear the cache (php bin/console cache:clear) and remove the web profiler bundle from production.
47. **WEB-APP-004 - Rails Master Key or Secrets File Exposed** (high severity, low effort)
   chmod 600 config/master.key config/credentials/\*.key; keep them out of git (.gitignore) and out of public/; load the key from RAILS\_MASTER\_KEY in the environment on servers. Rotate the credentials if the key was ever exposed.
48. **WEB-APP-006 - Spring Boot Actuator Endpoints Fully Exposed** (high severity, low effort)
   Set management.endpoints.web.exposure.include=health,info (and exclude env,heapdump,configprops), move management.server.port to a port that is not published, and require authentication for every actuator endpoint with Spring Security.
49. **WEB-CMS-004 - CMS Configuration Readable by All Users** (high severity, low effort)
   chmod 640 \<file\> and chown it to the site owner with the web server's group (for example chown deploy:www-data wp-config.php); on PHP-FPM pools that run as the site user, 600 or 400 also works. Rotate the database password if other people have shell access.
50. **WEB-CMS-005 - CMS Configuration Writable by Other Users** (high severity, low effort)
   chmod 640 (or 440) \<file\>. Drupal also expects sites/default to be 555 after installation. Review the file for code you did not add.
51. **WEB-CMS-019 - Drupal update.php Open Without Authentication** (high severity, low effort)
   Set $settings\['update\_free\_access'\] = FALSE; in settings.php (the default) and run updates as user 1 or with drush updatedb.
52. **WEB-DOC-001 - Secrets or Backups Stored Inside a Web Root** (high severity, low effort)
   Move each listed file outside the document root (for example to a 700 folder in the account's home), delete what is not needed, and rotate any secret or key that was exposed. Keep backups in a dedicated location outside every web root.
53. **WEB-DOC-002 - Web Root Files Writable by Other Users** (high severity, low effort)
   chmod o-w \<file\> for each listed file (find \<docroot\> -type f -perm -0002 -exec chmod o-w {} +). Use 644 for files and 755 for folders, owned by the site account, and grant the web server write access only to upload folders.
54. **WEB-EXP-001 - Git Repository Exposed on the Website** (high severity, low effort)
   Block dot-folders in the web server (Apache: RedirectMatch 404 /\\.git ; nginx: location \~ /\\.(?\!well-known) { deny all; }) and deploy without the .git folder in the web root. Rotate any secret that was ever committed.
55. **WEB-EXP-004 - Backup or Database Dump Files Downloadable** (high severity, low effort)
   Move backups outside the web root (for example /home/\<user\>/deploy-backups/ with mode 700), delete the exposed copies and treat their contents as leaked.
56. **WEB-EXP-007 - Other Version Control Metadata Exposed** (high severity, low effort)
   Block dot-folders at the web server and deploy without version control metadata in the web root. Rotate any secret ever committed.
57. **WEB-EXP-008 - Log Files Downloadable From the Website** (high severity, low effort)
   Move logs outside the web root, deny access to \*.log at the web server, disable WP\_DEBUG\_LOG in production, and review the exposed logs for leaked secrets.
58. **WEB-EXP-012 - Framework Debug Pages Exposed** (high severity, low effort)
   Disable debug mode in production (APP\_DEBUG=false, DEBUG = False, APP\_ENV=prod), remove profiler routes, and show generic error pages.
59. **WEB-NGX-003 - nginx Allows TLS 1.0 or 1.1** (high severity, low effort)
   Set  ssl\_protocols TLSv1.2 TLSv1.3;  in the http block (the Mozilla intermediate profile), run nginx -t and reload.
60. **WEB-NGX-004 - nginx Cipher List Includes Weak Suites** (high severity, low effort)
   Use the Mozilla intermediate list, for example  ssl\_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;  and  ssl\_prefer\_server\_ciphers off;
61. **WEB-PHP-003 - PHP Allows Remote File Inclusion** (high severity, low effort)
   In php.ini set  allow\_url\_include = Off  (the default since PHP 5.2) and restart PHP.
62. **WEB-PY-001 - Django Debug Mode Enabled** (high severity, low effort)
   Set DEBUG = False in production, preferably from the environment (DEBUG = os.environ.get('DJANGO\_DEBUG') == '1'), set ALLOWED\_HOSTS, and restart the application. Run python manage.py check --deploy.
63. **WEB-PY-003 - Django Uses the Generated Development Secret Key** (high severity, low effort)
   Generate a new key (python -c 'from django.core.management.utils import get\_random\_secret\_key; print(get\_random\_secret\_key())'), load it from the environment or a root-readable file (SECRET\_KEY = os.environ\['DJANGO\_SECRET\_KEY'\]), and restart. Changing the key logs all users out.
64. **WEB-TOM-001 - Tomcat Manager Reachable Without Address Restriction** (high severity, low effort)
   If you do not use them, delete the manager and host-manager folders (or apt remove tomcatN-admin). Otherwise keep the default valve in META-INF/context.xml: \<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1" /\> and reach the Manager through an SSH tunnel.
65. **HOST-CVE-002 - Known Exploited Vulnerabilities Present** (critical severity, medium effort)
   Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.
66. **HOST-K8S-001 - Kubelet Allows Anonymous or Unauthorised Access** (critical severity, medium effort)
   Set authentication.anonymous.enabled: false, authorization.mode: Webhook and readOnlyPort: 0 in the kubelet configuration, then restart the kubelet.
67. **NET-SVC-002 - Control Plane or Remote Desktop Ports Network-Reachable** (critical severity, medium effort)
   Bind control planes to loopback or a dedicated management address, require mutual TLS or strong authentication, and filter access to approved source networks or a VPN.
68. **WEB-EXP-002 - Environment or Secrets File Exposed** (critical severity, medium effort)
   Move secrets out of the web root, block dot-files at the web server, delete backup copies, then rotate every secret in the file because it must be treated as leaked.
69. **HOST-CTR-003 - Privileged or Host-Namespace Containers** (high severity, medium effort)
   Recreate each container without --privileged, --pid=host, --net=host and --ipc=host; drop all capabilities and add back only the specific ones needed (--cap-drop=ALL --cap-add=...).
70. **HOST-CTR-004 - Containers Mount the Docker Socket or Host Root** (high severity, medium effort)
   Remove these mounts. If a tool genuinely needs the Docker API, use a restricted socket proxy that allows only the required read-only calls.
71. **HOST-CTR-011 - Seccomp or AppArmor Disabled for Containers** (high severity, medium effort)
   Remove --security-opt seccomp=unconfined and apparmor=unconfined from the container definitions and recreate them; if an application needs one extra system call, write a custom seccomp profile that adds only that call. Delete seccomp-profile from daemon.json.
72. **HOST-CTR-014 - containerd Remote API or Podman Unconfined Defaults** (high severity, medium effort)
   containerd: remove tcp\_address or set tcp\_tls\_cert and tcp\_tls\_key, then restart containerd. Podman: delete seccomp\_profile = "unconfined", set label = true and apparmor\_profile = "container-default" in containers.conf.
73. **HOST-CVE-001 - Installed OS Packages With Known Vulnerabilities** (high severity, medium effort)
   Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.
74. **HOST-DKR-001 - Users With Root-Equivalent Docker Access** (high severity, medium effort)
   Remove users who do not need it from the docker group (gpasswd -d \<user\> docker); consider rootless Docker or Podman; keep docker.sock at 660 root:docker.
75. **HOST-FS-003 - World-Writable Files in System Configuration** (high severity, medium effort)
   For each listed file or directory: chmod o-w \<path\>. Then find out how it became writable (a deployment script or a careless chmod 777).
76. **HOST-FS-004 - Unexpected SUID or SGID Programs** (high severity, medium effort)
   Confirm whether each program is needed. Remove the bit with chmod u-s,g-s \<file\>, or delete the file if you did not put it there and investigate how it arrived.
77. **HOST-PKG-001 - Security Updates Waiting to Be Installed** (high severity, medium effort)
   Debian/Ubuntu: apt-get update && apt-get upgrade . RHEL family: dnf upgrade --security . Consider unattended-upgrades or dnf-automatic for security patches.
78. **HOST-SEC-001 - Secrets in World-Readable Files** (high severity, medium effort)
   chmod o-r each listed file (640 with the service's group, or 600). Move secrets into root-only files, systemd credentials or a secrets manager, and rotate every key or password that was readable.
79. **HOST-USR-001 - Extra Accounts With Root Privileges (UID 0)** (high severity, medium effort)
   Investigate who created each account. Remove it (userdel \<name\>) or give it a normal UID (usermod -u \<new-uid\> \<name\>). Grant admin rights through sudo instead.
80. **NET-FW-002 - IPv6 Listeners Without IPv6 Firewall** (high severity, medium effort)
   Apply equivalent default-deny filtering for IPv6 with nftables or ip6tables and explicitly allow required services. Caution: allow SSH and ICMPv6 neighbour discovery and router messages before applying the default deny, or remote IPv6 sessions and IPv6 connectivity are cut.
81. **NET-SVC-001 - Cleartext Legacy Services Listening** (high severity, medium effort)
   Replace with encrypted equivalents: SSH/SFTP instead of Telnet/FTP/rsh, IMAPS (993)/POP3S (995), LDAPS (636), SNMPv3. Disable the old service: systemctl disable --now \<unit\>.
82. **WEB-CVE-001 - Vulnerable Web Application Libraries** (high severity, medium effort)
   Update the affected library or WordPress core and deploy a refreshed lockfile or asset. For a source build, run 'make vulndb' to embed a generated snapshot.
83. **HOST-OS-001 - Operating System Past End of Security Support** (high severity, high effort)
   Plan an upgrade to a supported release (for example do-release-upgrade on Ubuntu, or a fresh install and migration). Paid extended support (Ubuntu Pro, RHEL ELS) is an alternative while migrating.
84. **WEB-CMS-012 - Drupal Core Past End of Life** (high severity, high effort)
   Upgrade to a supported Drupal major with composer (drupal/core-recommended), run drush updatedb and test the site; for Drupal 7, migrate with the Migrate module or commission a rebuild.
85. **WEB-CMS-014 - Joomla Core Past End of Life** (high severity, high effort)
   Update to the current Joomla series through the Joomla Update component (back up first, check extension compatibility, then update), and keep the one-click updater enabled.
86. **WEB-NODE-001 - Node.js Version Past End of Life** (high severity, high effort)
   Move to an Active or Maintenance LTS release (even-numbered majors), for example with the NodeSource or distribution packages, test the application, then restart its service.
87. **WEB-PHP-004 - PHP Version Past End of Security Support** (high severity, high effort)
   Upgrade to a supported PHP branch (see php.net/supported-versions) and test the applications, or confirm your distribution still backports security fixes to this version.
88. **HOST-BOOT-003 - Uncommon Filesystem Modules Not Disabled** (medium severity, low effort)
   Create /etc/modprobe.d/openvapt-filesystems.conf with, for each module:  install \<module\> /bin/false  and  blacklist \<module\>  then unload any loaded one with  modprobe -r \<module\>. Caution: first confirm that no listed module is in use (lsmod, findmnt); blocking a filesystem a mount or package format needs (for example squashfs for snap packages, or vfat for the EFI system partition) breaks those mounts at the next boot.
89. **HOST-BOOT-004 - USB Storage Driver Not Disabled** (medium severity, low effort)
   Create /etc/modprobe.d/openvapt-usb.conf containing  install usb-storage /bin/false  and  blacklist usb-storage  then  modprobe -r usb-storage. Servers that need USB backups should record the exception instead.
90. **HOST-CTR-010 - Docker Daemon Allows Insecure Registries or Debug Features** (medium severity, low effort)
   Remove insecure-registries (serve the registry over TLS with a trusted certificate, or add its CA to /etc/docker/certs.d/\<registry\>/ca.crt), set "debug": false and "experimental": false in daemon.json, and remove any --insecure-registry, --debug (-D) or --experimental flag from the dockerd ExecStart in docker.service drop-ins (then systemctl daemon-reload). Restart Docker in a maintenance window: running containers stop unless live-restore is on.
91. **HOST-FS-006 - Boot Loader Configuration Exposed** (medium severity, low effort)
   chown root:root \<grub.cfg\> && chmod 600 \<grub.cfg\>  (for example /boot/grub/grub.cfg or /boot/grub2/grub.cfg).
92. **HOST-FS-014 - World-Writable Files Outside Temporary Directories** (medium severity, low effort)
   For each listed file: chmod o-w \<path\>. Then find the deployment step or chmod 777 habit that created it.
93. **HOST-FS-017 - World-Writable Directories Without the Sticky Bit** (medium severity, low effort)
   chmod +t \<dir\>  for directories that must stay shared, or  chmod o-w \<dir\>  for those that should not be writable by everyone.
94. **HOST-KRN-001 - Address Space Layout Randomisation Not Fully Enabled** (medium severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.randomize\_va\_space = 2  then apply with: sysctl --system
95. **HOST-KRN-009 - Unprivileged eBPF Allowed** (medium severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.unprivileged\_bpf\_disabled = 1  then apply with: sysctl --system
96. **HOST-PAM-008 - Root PATH Contains Unsafe Entries** (medium severity, low effort)
   Edit the listed file so PATH holds only absolute, root-owned directories, for example  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" , remove any . or trailing colon, and  chmod o-w  any listed directory. Log out and in again.
97. **HOST-PWD-001 - Weak Password Hashing Algorithm Configured** (medium severity, low effort)
   Set  ENCRYPT\_METHOD YESCRYPT  (or SHA512 on older systems) in /etc/login.defs, and make sure pam\_unix uses the same algorithm. Ask users to change passwords so existing hashes are upgraded.
98. **HOST-PWD-002 - Accounts With Obsolete Password Hashes** (medium severity, low effort)
   Configure a modern algorithm (HOST-PWD-001), then force a password change for each listed account (chage -d 0 \<user\>) or lock unused accounts (passwd -l \<user\>).
99. **HOST-SSH-002 - SSH Password Authentication Enabled** (medium severity, low effort)
   Install your public key for each admin user (ssh-copy-id), confirm key login works in a second session, then set  PasswordAuthentication no  and  KbdInteractiveAuthentication no  in /etc/ssh/sshd\_config and reload sshd.
100. **HOST-SSH-004 - SSH Offers Weak Cryptography** (medium severity, low effort)
   Remove the listed algorithms. On OpenSSH 8.x and later the defaults are already strong: delete any custom Ciphers/MACs/KexAlgorithms lines, or set explicit modern lists such as  Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com  then reload sshd.
101. **HOST-SSH-007 - SSH Forwarding Features Enabled** (medium severity, low effort)
   Set AllowTcpForwarding no, AllowAgentForwarding no, PermitTunnel no and GatewayPorts no globally or in every applicable Match block, then reload sshd.
102. **HOST-SSH-011 - SSH Legacy Trust Options Enabled** (medium severity, low effort)
   Set  HostbasedAuthentication no ,  IgnoreRhosts yes  and  PermitUserEnvironment no  in /etc/ssh/sshd\_config, then reload sshd.
103. **HOST-SVC-002 - Telnet Server Installed or Enabled** (medium severity, low effort)
   apt purge telnetd inetutils-telnetd  or  dnf remove telnet-server , and  systemctl disable --now telnet.socket . Use SSH. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
104. **HOST-SVC-003 - rsh, rlogin, rexec or finger Server Installed or Enabled** (medium severity, low effort)
   apt purge rsh-server rsh-redone-server fingerd  or  dnf remove rsh-server finger-server , and  systemctl disable --now rsh.socket rlogin.socket rexec.socket finger.socket . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
105. **HOST-SVC-004 - TFTP Server Installed or Enabled** (medium severity, low effort)
   apt purge tftpd-hpa atftpd  or  dnf remove tftp-server , and  systemctl disable --now tftpd-hpa.service tftp.socket . Where PXE is required, serve only that directory, read-only, from a dedicated host. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
106. **HOST-SVC-005 - FTP Server Installed or Enabled** (medium severity, low effort)
   Move users to SFTP (the OpenSSH internal-sftp subsystem), then  apt purge vsftpd proftpd-core pure-ftpd  or  dnf remove vsftpd proftpd  and  systemctl disable --now vsftpd . If FTP must stay, enforce TLS (FTPS) and restrict it to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
107. **HOST-SVC-006 - NIS Server or Client Installed or Enabled** (medium severity, low effort)
   Replace NIS with a modern directory (SSSD with LDAPS or FreeIPA), then  apt purge nis  or  dnf remove ypserv ypbind  and  systemctl disable --now ypserv ypbind . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
108. **HOST-SYSD-005 - Secrets Written Into Service Unit Files** (medium severity, low effort)
   Move secrets to a root-owned file readable only by root (chmod 600) and load it with EnvironmentFile=, or better use LoadCredential=/SetCredentialEncrypted= so the secret is only visible to the service. Then rotate every secret that was exposed.
109. **HOST-USR-006 - Legacy NIS Entries in Account Files** (medium severity, low effort)
   Remove the '+' lines (with vipw / vigr) and use a modern directory service such as SSSD with LDAPS if central accounts are needed.
110. **HOST-USR-009 - Password and Shadow Files Out of Step** (medium severity, low effort)
   pwconv  moves hashes into /etc/shadow and creates missing entries; userdel or vipw/vigr removes orphan lines; run pwck and grpck to confirm the files are consistent.
111. **NET-APP-002 - Application Server Ports Reachable Without a Reverse Proxy** (medium severity, low effort)
   Bind the application to 127.0.0.1 (Node.js: app.listen(3000, '127.0.0.1'); Gunicorn: --bind 127.0.0.1:8000; Tomcat: address="127.0.0.1" on the Connector) and publish it only through nginx or Apache.
112. **NET-DB-013 - Mail Cleartext Authentication Allowed** (medium severity, low effort)
   Set smtpd\_tls\_auth\_only = yes and disable\_plaintext\_auth = yes. Use implicit TLS on ports 465, 993, and 995. STARTTLS assessment is out of scope.
113. **NET-DB-014 - MySQL Symbolic Links Allowed for Tables** (medium severity, low effort)
   Add  symbolic-links=0  (or  skip-symbolic-links ) under \[mysqld\] and restart the service in a maintenance window.
114. **NET-DB-015 - MySQL secure\_file\_priv Not Restricted** (medium severity, low effort)
   Add  secure\_file\_priv = NULL  (no file operations) or  secure\_file\_priv = /var/lib/mysql-files  under \[mysqld\] and restart in a maintenance window.
115. **NET-DB-016 - MySQL Does Not Require Encrypted Client Connections** (medium severity, low effort)
   Provision a server certificate (MySQL creates one by default), then add  require\_secure\_transport = ON  under \[mysqld\] and restart in a maintenance window. Test every application connection string first.
116. **NET-DB-019 - PostgreSQL Uses md5 or password Authentication** (medium severity, low effort)
   Set  password\_encryption = scram-sha-256  in postgresql.conf, have every role reset its password so a SCRAM verifier is stored, then change the pg\_hba.conf methods to scram-sha-256 and reload.
117. **NET-DB-020 - PostgreSQL SSL Not Enabled** (medium severity, low effort)
   Install a certificate and key readable only by the postgres user, set  ssl = on , ssl\_cert\_file and ssl\_key\_file, reload, then change remote pg\_hba.conf rules from host to hostssl.
118. **NET-DB-022 - PostgreSQL Password Encryption Not SCRAM** (medium severity, low effort)
   Set  password\_encryption = scram-sha-256 , reload, and ask every role to reset its password so the verifier is regenerated. Then move pg\_hba.conf to scram-sha-256 (NET-DB-019).
119. **NET-DB-023 - Redis Dangerous Commands Not Restricted** (medium severity, low effort)
   Prefer ACLs on Redis 6 and newer:  user app on \>secret \~app:\* -@all +@read +@write -@dangerous . Otherwise add  rename-command FLUSHALL ""  (and the same for FLUSHDB, CONFIG, DEBUG, KEYS) and restart.
120. **NET-DNS-002 - BIND DNSSEC Validation Disabled** (medium severity, low effort)
   Set  dnssec-validation auto;  in options (remove any  no ), run named-checkconf and rndc reconfig. Authoritative-only servers with recursion off may leave it unset.
121. **NET-MAIL-001 - Postfix SMTP TLS Not Offered or Has No Certificate** (medium severity, low effort)
   Set  smtpd\_tls\_security\_level = may ,  smtpd\_tls\_chain\_files = /etc/letsencrypt/live/\<host\>/privkey.pem, /etc/letsencrypt/live/\<host\>/fullchain.pem  (or smtpd\_tls\_cert\_file and smtpd\_tls\_key\_file), then postfix reload.
122. **NET-MAIL-002 - Postfix Allows TLS 1.0 or 1.1** (medium severity, low effort)
   Add  smtpd\_tls\_protocols = \>=TLSv1.2  and  smtpd\_tls\_mandatory\_protocols = \>=TLSv1.2  (Postfix 3.6 and newer; on older releases use  \!SSLv2, \!SSLv3, \!TLSv1, \!TLSv1.1 ), then postfix reload.
123. **NET-MAIL-004 - Dovecot Does Not Require TLS** (medium severity, low effort)
   Set  ssl = required  and  ssl\_min\_protocol = TLSv1.2  in conf.d/10-ssl.conf, then doveadm reload. Prefer implicit TLS ports 993 and 995 for clients. Caution: clients set to plaintext stop connecting, so confirm every mail client uses TLS first.
124. **NET-SVC-004 - Management Panel or Monitoring Ports Network-Reachable** (medium severity, low effort)
   Bind the panel or exporter to 127.0.0.1 or a management address, put it behind a reverse proxy with authentication, or restrict the port to monitoring hosts in the firewall.
125. **NET-TLS-001 - Deprecated TLS 1.0 or 1.1 Accepted** (medium severity, low effort)
   Apache: SSLProtocol -all +TLSv1.2 +TLSv1.3 . nginx: ssl\_protocols TLSv1.2 TLSv1.3; . Postfix/Dovecot: set the minimum protocol to TLSv1.2. Reload the service afterwards.
126. **NET-TLS-002 - TLS Certificate Expired or Expiring Soon** (medium severity, low effort)
   Renew the certificate (certbot renew, or your provider's process) and make sure renewal is automated (systemctl list-timers | grep certbot).
127. **NET-TLS-003 - Self-Signed or Mismatched TLS Certificate** (medium severity, low effort)
   Issue a certificate from a public CA covering every site name, for example with certbot --apache or certbot --nginx -d example.com -d www\.example.com.
128. **NET-TLS-004 - Weak TLS 1.2 Cipher Suites Accepted** (medium severity, low effort)
   Allow only modern AEAD suites for TLS 1.2 and prefer TLS 1.3. Remove RC4, 3DES, NULL, EXPORT, anonymous, and CBC with SHA-1 suites. Caution: very old clients and devices that support only those suites stop connecting; check the access logs for them first.
129. **WEB-APA-003 - Apache Directory Indexes Enabled in Configuration** (medium severity, low effort)
   Change the block to  Options -Indexes +FollowSymLinks  (or remove Indexes). Where a listing is wanted, protect that Directory with Require.
130. **WEB-APA-011 - Apache Has No Slow Request Protection** (medium severity, low effort)
   Enable the module (Debian and Ubuntu: a2enmod reqtimeout; other systems: LoadModule reqtimeout\_module modules/mod\_reqtimeout.so) and keep or set  RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500 , then reload.
131. **WEB-APP-002 - Application .env File Readable by All Users** (medium severity, low effort)
   chmod 640 .env && chown \<app-user\>:\<web-group\> .env  (or 600 when the application runs as its own user). Rotate the secrets if other people have had shell access.
132. **WEB-APP-005 - Rails Production Does Not Force HTTPS** (medium severity, low effort)
   Set config.force\_ssl = true in config/environments/production.rb (and config.assume\_ssl = true behind a TLS-terminating proxy), then restart the application.
133. **WEB-APP-009 - Git Repository Stored Inside a Web Root** (medium severity, low effort)
   Deploy from a build step that copies only the needed files (git archive, rsync --exclude .git, or a CI artefact), or point the document root at a public/ sub-folder as Laravel, Symfony and Rails expect. Remove the .git folder from the web root and rotate any secret ever committed.
134. **WEB-APP-010 - Framework Writable Folders Open to All Users** (medium severity, low effort)
   chmod -R o-w storage bootstrap/cache var  and give the web server group write access instead: chgrp -R www-data storage bootstrap/cache && chmod -R g+w storage bootstrap/cache.
135. **WEB-CMS-006 - WordPress Shows Debug Output to Visitors** (medium severity, low effort)
   In production set define('WP\_DEBUG', false);. If you need logs, use define('WP\_DEBUG\_LOG', '/path/outside/webroot/wp-debug.log'); together with define('WP\_DEBUG\_DISPLAY', false);.
136. **WEB-CMS-008 - WordPress Automatic Updates Disabled** (medium severity, low effort)
   Remove those lines, or set define('WP\_AUTO\_UPDATE\_CORE', 'minor'); so at least security releases install automatically. Keep backups and enable plugin auto-updates where practical.
137. **WEB-CMS-009 - Joomla Debug or Verbose Error Reporting Enabled** (medium severity, low effort)
   In System, Global Configuration set Debug System to No and Error Reporting to None (or System Default), or set public $debug = false; and public $error\_reporting = 'none'; in configuration.php.
138. **WEB-CMS-013 - Drupal Trusted Host Patterns Not Set** (medium severity, low effort)
   Add to settings.php:  $settings\['trusted\_host\_patterns'\] = \['^www\\.example\\.com$', '^example\\.com$'\];  then clear caches (drush cr). The status report page should no longer warn about trusted hosts.
139. **WEB-CMS-015 - Magento Not in Production Mode** (medium severity, low effort)
   bin/magento deploy:mode:set production  (which also deploys static content and compiles DI), then clear caches. Keep developer mode for staging only.
140. **WEB-CMS-016 - Ghost Configured With a Plain HTTP Site URL** (medium severity, low effort)
   ghost config url https:\/\/\<your-domain\>  (or edit config.production.json), set up TLS with ghost setup ssl or your reverse proxy, then ghost restart.
141. **WEB-COR-001 - Cross-Origin Resource Sharing Allows Any Origin With Credentials** (medium severity, low effort)
   Return Access-Control-Allow-Origin only for an explicit allow-list of trusted origins, never '\*' or 'null' together with credentials, and add Vary: Origin.
142. **WEB-DOC-003 - Archives or Certificates Inside a Web Root** (medium severity, low effort)
   Confirm each file is meant to be public. Move backups and anything holding a private key outside the document root, rotate any exposed key, and keep published downloads in a dedicated folder such as /downloads/.
143. **WEB-EXP-003 - PHP Information Page Exposed** (medium severity, low effort)
   Delete the file. Never leave diagnostic pages on a production server.
144. **WEB-EXP-005 - Directory Listing Enabled** (medium severity, low effort)
   Apache: Options -Indexes . nginx: autoindex off; (the default). Add an empty index.html to folders that must stay browsable-free.
145. **WEB-EXP-006 - Web Server Status Page Exposed** (medium severity, low effort)
   Restrict to administrators: \<Location /server-status\> Require local \</Location\> (Apache), or allow 127.0.0.1; deny all; (nginx). Remove the module if unused.
146. **WEB-EXP-011 - Database Administration Tool Exposed** (medium severity, low effort)
   Remove the tool, or restrict it to administrators by IP allow-list, VPN or an extra authentication layer, and keep it updated.
147. **WEB-HDR-001 - Missing HTTP Strict Transport Security (HSTS)** (medium severity, low effort)
   Apache: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" . nginx: add\_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; . Only add 'preload' once every subdomain supports HTTPS.
148. **WEB-HDR-003 - Site Can Be Framed (Clickjacking)** (medium severity, low effort)
   Send  Content-Security-Policy: frame-ancestors 'self'  (modern) and  X-Frame-Options: SAMEORIGIN  (older browsers).
149. **WEB-NGX-002 - nginx Directory Listing Enabled in Configuration** (medium severity, low effort)
   Remove  autoindex on;  or set  autoindex off;  in that block. Where a listing is really needed, protect the location with auth\_basic or an allow list.
150. **WEB-NGX-007 - nginx Serves Hidden Files and Folders** (medium severity, low effort)
   Add to every server block:  location \~ /\\. { deny all; access\_log off; log\_not\_found off; }  and keep .well-known reachable with a more specific  location ^\~ /.well-known/  block before it.
151. **WEB-PHP-001 - PHP Displays Errors to Visitors** (medium severity, low effort)
   In php.ini set  display\_errors = Off  and  log\_errors = On , then restart PHP-FPM or the web server.
152. **WEB-PHP-005 - PHP Session Cookies Not Hardened** (medium severity, low effort)
   In php.ini set  session.cookie\_httponly = 1 ,  session.use\_strict\_mode = 1 ,  session.cookie\_samesite = Lax  and, for HTTPS sites,  session.cookie\_secure = 1 , then restart PHP-FPM.
153. **WEB-PY-002 - Django Accepts Any Host Header** (medium severity, low effort)
   List the real domain names: ALLOWED\_HOSTS = \['example.com', 'www\.example.com'\], and restart the application.
154. **WEB-TLS-001 - Site Available Over Plain HTTP Without Redirect** (medium severity, low effort)
   Apache: in the port 80 vhost add  Redirect permanent / https:\/\/\<site\>/ . nginx: return 301 https:\/\/$host$request\_uri; . Get a free certificate with certbot if HTTPS is not set up yet.
155. **WEB-TOM-003 - Tomcat Shutdown Port Uses the Default Command** (medium severity, low effort)
   Disable the port with \<Server port="-1" ...\> and stop Tomcat through systemd, or set shutdown to a long random string. Restart Tomcat.
156. **WEB-TOM-005 - Tomcat Example and Documentation Applications Deployed** (medium severity, low effort)
   Delete the examples and docs folders from webapps (or apt remove tomcatN-examples tomcatN-docs) and restart Tomcat.
157. **HOST-BOOT-001 - Boot Loader Has No Password** (medium severity, medium effort)
   Run  grub-mkpasswd-pbkdf2  (grub2-setpassword on RHEL family), add  set superusers="admin"  and  password\_pbkdf2 admin \<hash\>  to /etc/grub.d/40\_custom (RHEL family writes /boot/grub2/user.cfg), then regenerate with  update-grub  or  grub2-mkconfig -o /boot/grub2/grub.cfg. Add  --unrestricted  to the normal menu entry so unattended reboots still work.
158. **HOST-BOOT-006 - No File Integrity Monitoring** (medium severity, medium effort)
   Install AIDE (apt install aide / dnf install aide), initialise the database with  aideinit  or  aide --init, move the new database into place, and enable the daily check (dailyaidecheck.timer on Debian family, or a cron.daily entry) that mails or logs the differences.
159. **HOST-CTR-002 - Docker Daemon Not Hardened** (medium severity, medium effort)
   Add to /etc/docker/daemon.json:  "no-new-privileges": true, "icc": false, "userns-remap": "default", "live-restore": true  then restart Docker after testing your containers.
160. **HOST-CTR-012 - Containers Run Without Memory or Process Limits** (medium severity, medium effort)
   Recreate each container with --memory (for example 512m) and --pids-limit (for example 256), or set them in the compose file under deploy.resources.limits / pids\_limit.
161. **HOST-CTR-013 - Containers Run as Root With a Writable Root Filesystem** (medium severity, medium effort)
   Add USER \<uid\> to the image or run with --user 1000:1000, and start the container with --read-only plus --tmpfs /tmp for scratch space; recreate the container.
162. **HOST-FS-016 - SUID or SGID Programs Outside the Known Baseline** (medium severity, medium effort)
   For each listed program decide whether the bit is needed. Remove it with  chmod u-s,g-s \<path\>  or uninstall the package. Record the ones you keep so the next review is quick. Caution: programs such as passwd, sudo, su, mount and ping need their bit to work for normal users; removing it from those breaks password changes and administrator access.
163. **HOST-PKG-005 - Running Kernel Older Than Installed Kernel** (medium severity, medium effort)
   Schedule a maintenance window and reboot into the newest kernel. Where downtime is a problem, use the distribution's live patching service and still plan regular reboots.
164. **HOST-PWD-004 - No Account Lockout After Failed Logins** (medium severity, medium effort)
   Enable pam\_faillock (authselect enable-feature with-faillock on RHEL family; pam-auth-update or a faillock profile on Debian/Ubuntu) with settings such as deny=5 unlock\_time=900 in /etc/security/faillock.conf. Keep a root session open and test a login in a second session before closing it: a wrong PAM line can lock every user out, including administrators. Do not lock root (no even\_deny\_root) unless console access is assured.
165. **HOST-SYSD-001 - Custom Services Run as Root** (medium severity, medium effort)
   Create a dedicated account (useradd --system --no-create-home --shell /usr/sbin/nologin \<app\>), give it ownership of only the folders the app writes to, then add User=\<app\> and Group=\<app\> to the \[Service\] section (or DynamicUser=yes). Run systemctl daemon-reload and restart the service. Ports below 1024 can be allowed with AmbientCapabilities=CAP\_NET\_BIND\_SERVICE instead of root.
166. **HOST-UPD-002 - Reboot Required to Apply Updates** (medium severity, medium effort)
   Schedule a maintenance window and reboot (or use live patching where supported).
167. **HOST-USR-004 - Duplicate User IDs** (medium severity, medium effort)
   Give each account a unique UID (usermod -u \<new-uid\> \<user\>) and fix file ownership (find / -xdev -uid \<old-uid\>).
168. **HOST-USR-008 - Duplicate Group IDs or Account Names** (medium severity, medium effort)
   groupmod -g \<new-gid\> \<group\>  for one of each duplicate pair, then fix file ownership (find / -xdev -gid \<old-gid\>); remove or rename the duplicate name entries with vigr and vipw, which lock the files while editing.
169. **NET-FW-001 - No Active Host Firewall** (medium severity, medium effort)
   Ubuntu: ufw default deny incoming && ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable . RHEL family: systemctl enable --now firewalld . Always allow SSH before enabling, or you will lock yourself out.
170. **NET-FW-005 - Container Ports Bypass the Host Firewall** (medium severity, medium effort)
   Publish container ports on loopback only (-p 127.0.0.1:8080:80) and put a reverse proxy in front, or add filtering to DOCKER-USER, for example: iptables -I DOCKER-USER -i eth0 \! -s 203.0.113.0/24 -j DROP (persist it with your firewall tooling). Caution: test from an allowed address first; a wrong interface name or source range cuts access to every published container.
171. **NET-TLS-005 - TLS Certificate Public Key Too Small** (medium severity, medium effort)
   Replace the certificate and private key with RSA 2048 bits or stronger, or an approved EC key of at least 256 bits.
172. **NET-TLS-006 - SHA-1 Signed TLS Leaf Certificate** (medium severity, medium effort)
   Reissue the certificate with SHA-256 or stronger and deploy the complete updated chain.
173. **WEB-CK-001 - Cookies Missing Secure, HttpOnly or SameSite** (medium severity, medium effort)
   PHP: session.cookie\_secure=1, session.cookie\_httponly=1, session.cookie\_samesite=Lax in php.ini . Frameworks have equivalent settings (e.g. Laravel config/session.php). Use HttpOnly for every cookie JavaScript does not need to read.
174. **WEB-HDR-002 - Missing Content Security Policy** (medium severity, medium effort)
   Start with a report-only policy to find what the site loads, then enforce, for example: Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
175. **WEB-PY-005 - Python Development Server Used in a Service** (medium severity, medium effort)
   Serve the application with a production server (Gunicorn or uWSGI for WSGI, Uvicorn without --reload or Hypercorn for ASGI) bound to 127.0.0.1, behind nginx or Apache. Serve static files from the web server.
176. **HOST-BOOT-002 - UEFI Secure Boot Disabled** (medium severity, high effort)
   Enable Secure Boot in the firmware set-up and boot a signed shim and kernel (every mainstream distribution ships them). Custom kernels and out-of-tree modules then need signing with a Machine Owner Key. Where the platform cannot do Secure Boot, record the exception. Caution: unsigned kernels and modules (for example DKMS drivers such as NVIDIA or ZFS) stop loading once it is on, so sign or replace them first and keep console access for the first boot.
177. **HOST-AUD-001 - Audit Daemon Not Enabled at Boot** (low severity, low effort)
   systemctl enable auditd  (on some distributions: systemctl enable --now auditd). Then confirm the rules in /etc/audit/rules.d load at boot with augenrules --check.
178. **HOST-AUD-002 - Identity Files Not Audited** (low severity, low effort)
   Add to /etc/audit/rules.d/50-identity.rules:  -w /etc/passwd -p wa -k identity  and the same for /etc/group, /etc/shadow, /etc/gshadow, /etc/sudoers and /etc/sudoers.d/ , then run augenrules --load.
179. **HOST-AUD-003 - Login and Logout Events Not Audited** (low severity, low effort)
   Add to /etc/audit/rules.d/50-login.rules:  -w /var/log/lastlog -p wa -k logins ,  -w /var/run/faillock -p wa -k logins ,  -w /var/log/wtmp -p wa -k session ,  -w /var/log/btmp -p wa -k session , then augenrules --load.
180. **HOST-AUD-004 - System Time Changes Not Audited** (low severity, low effort)
   Add to /etc/audit/rules.d/50-time.rules:  -a always,exit -F arch=b64 -S adjtimex,settimeofday,clock\_settime -k time-change  (and the same with arch=b32), plus  -w /etc/localtime -p wa -k time-change , then augenrules --load.
181. **HOST-AUD-005 - Kernel Module Loading Not Audited** (low severity, low effort)
   Add to /etc/audit/rules.d/50-modules.rules:  -a always,exit -F arch=b64 -S init\_module,finit\_module,delete\_module -k modules  (and arch=b32), or  -a always,exit -F path=/usr/bin/kmod -F perm=x -F auid\>=1000 -F auid\!=unset -k modules , then augenrules --load.
182. **HOST-AUD-006 - Privileged Command Use Not Audited** (low severity, low effort)
   Generate rules for every setuid and setgid program:  find / -xdev \\( -perm -4000 -o -perm -2000 \\) -type f | awk '{print "-a always,exit -F path=" $1 " -F perm=x -F auid\>=1000 -F auid\!=unset -k privileged"}' \> /etc/audit/rules.d/50-privileged.rules  then augenrules --load.
183. **HOST-AUD-007 - Audit Configuration Not Immutable** (low severity, low effort)
   Add  -e 2  as the last line of the last file in /etc/audit/rules.d (for example 99-finalize.rules), then augenrules --load and reboot. Plan rule changes before enabling it: every later change needs a reboot.
184. **HOST-AUD-008 - Audit Log Files Readable by Others** (low severity, low effort)
   chmod 0750 /var/log/audit && chmod 0600 /var/log/audit/\* && chown -R root:root /var/log/audit , and set log\_group = root in /etc/audit/auditd.conf so new files keep these modes.
185. **HOST-AUD-009 - Audit Daemon Ignores a Full Log Disk** (low severity, low effort)
   In /etc/audit/auditd.conf set  max\_log\_file\_action = keep\_logs  (or rotate with enough num\_logs),  space\_left\_action = email  (or syslog or exec), and  admin\_space\_left\_action = halt  or  single  as policy requires, then systemctl restart auditd. Caution: admin\_space\_left\_action = halt or single stops the server or drops it to single-user mode when the audit disk fills; choose it only where losing audit records is worse than downtime, and give /var/log/audit room and monitoring first.
186. **HOST-AUD-010 - Journal Not Persistent or Not Compressed** (low severity, low effort)
   In /etc/systemd/journald.conf set  Storage=persistent  and  Compress=yes , create the directory with  mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal , then systemctl restart systemd-journald.
187. **HOST-BOOT-007 - Prelink Installed** (low severity, low effort)
   Undo and remove it:  prelink -ua && apt purge prelink  (or  dnf remove prelink). Then re-initialise the integrity database (aide --init).
188. **HOST-BOOT-008 - Core Dumps Not Restricted** (low severity, low effort)
   Add  \* hard core 0  to /etc/security/limits.conf (or a file in limits.d), set  Storage=none  and  ProcessSizeMax=0  in the \[Coredump\] section of /etc/systemd/coredump.conf, and keep  fs.suid\_dumpable = 0.
189. **HOST-CTR-005 - Docker Container Logs Have No Size Limit** (low severity, low effort)
   Add to /etc/docker/daemon.json:  "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}  (or use journald) and restart Docker; recreate containers that set their own LogConfig.
190. **HOST-CTR-006 - Docker Userland Proxy Enabled** (low severity, low effort)
   Add  "userland-proxy": false  to /etc/docker/daemon.json and restart Docker; test that published ports still answer from the host itself.
191. **HOST-CTR-007 - Docker Default Ulimits Not Set** (low severity, low effort)
   Add to /etc/docker/daemon.json:  "default-ulimits": {"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 32768}, "nproc": {"Name": "nproc", "Hard": 4096, "Soft": 2048}}  and restart Docker; tune the numbers to your workloads.
192. **HOST-FS-009 - Default File Creation Mask Too Permissive** (low severity, low effort)
   Set  UMASK 027  in /etc/login.defs (and, if used, in /etc/profile or /etc/bash.bashrc).
193. **HOST-FS-015 - Files With No Known Owner or Group** (low severity, low effort)
   Decide who should own each listed file and apply  chown \<user\>:\<group\> \<path\>, or remove files that belonged to a deleted account after checking their contents.
194. **HOST-FS-018 - Kernel Symbol Map or Initial RAM Disk Readable by Others** (low severity, low effort)
   chmod 600 /boot/System.map-\* /boot/initrd\* /boot/initramfs\*  and, on Debian family systems, set  UMASK=0077  in /etc/initramfs-tools/initramfs.conf so new images are created private.
195. **HOST-FS-019 - Cron and At Not Restricted to Allowed Users** (low severity, low effort)
   Create the allow lists:  touch /etc/cron.allow /etc/at.allow && chmod 600 /etc/cron.allow /etc/at.allow && chown root:root /etc/cron.allow /etc/at.allow  then add one user name per line for the accounts that need scheduling, and remove /etc/cron.deny and /etc/at.deny. Caution: once cron.allow exists, crontabs of users not listed stop running, so list every service and user account that has a crontab (ls /var/spool/cron/crontabs or /var/spool/cron) before creating it.
196. **HOST-FS-020 - Cron Configuration Readable by Other Users** (low severity, low effort)
   chown root:root /etc/crontab /etc/cron.\* && chmod 600 /etc/crontab && chmod 700 /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly
197. **HOST-FS-021 - Shell Profile Sets a Permissive File Creation Mask** (low severity, low effort)
   Change every  umask 022  line in the listed files to  umask 027, or remove the line so the login.defs value applies. Users start a new login shell to pick it up.
198. **HOST-IDS-001 - No Brute-Force Protection Service** (low severity, low effort)
   Install and enable one, for example: apt install fail2ban && systemctl enable --now fail2ban , with an sshd jail enabled.
199. **HOST-KRN-002 - Kernel Pointers Exposed to Unprivileged Users** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.kptr\_restrict = 1  then apply with: sysctl --system
200. **HOST-KRN-003 - Kernel Log Readable by Unprivileged Users** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.dmesg\_restrict = 1  then apply with: sysctl --system
201. **HOST-KRN-004 - Setuid Programs May Dump Core** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.suid\_dumpable = 0  then apply with: sysctl --system
202. **HOST-KRN-005 - IP Forwarding Enabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.ip\_forward = 0  then apply with: sysctl --system
203. **HOST-KRN-006 - ICMP Redirects Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.accept\_redirects = 0  then apply with: sysctl --system
204. **HOST-KRN-007 - Source-Routed Packets Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.accept\_source\_route = 0  then apply with: sysctl --system
205. **HOST-KRN-008 - TCP SYN Cookies Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.tcp\_syncookies = 1  then apply with: sysctl --system
206. **HOST-KRN-010 - Process Tracing Not Restricted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.yama.ptrace\_scope = 1  then apply with: sysctl --system
207. **HOST-KRN-011 - Symlink Protection Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_symlinks = 1  then apply with: sysctl --system
208. **HOST-KRN-012 - Hardlink Protection Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_hardlinks = 1  then apply with: sysctl --system
209. **HOST-KRN-013 - Reverse Path Filtering Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.rp\_filter = 1  then apply with: sysctl --system
210. **HOST-KRN-014 - Martian Packets Not Logged** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.log\_martians = 1  then apply with: sysctl --system
211. **HOST-KRN-015 - Server Sends ICMP Redirects** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.send\_redirects = 0  then apply with: sysctl --system
212. **HOST-KRN-016 - Broadcast ICMP Echo Answered** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.icmp\_echo\_ignore\_broadcasts = 1  then apply with: sysctl --system
213. **HOST-KRN-017 - IPv6 ICMP Redirects Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_redirects = 0  then apply with: sysctl --system
214. **HOST-KRN-018 - Performance Events Open to Unprivileged Users** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.perf\_event\_paranoid = 2  then apply with: sysctl --system
215. **HOST-KRN-019 - Magic SysRq Key Enabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.sysrq = 0  then apply with: sysctl --system
216. **HOST-KRN-020 - FIFO Protection in Shared Directories Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_fifos = 1  then apply with: sysctl --system
217. **HOST-KRN-021 - Regular File Protection in Shared Directories Disabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_regular = 1  then apply with: sysctl --system
218. **HOST-KRN-022 - Unprivileged User Namespaces Allowed** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing  kernel.unprivileged\_userns\_clone = 0  (Debian family) or  user.max\_user\_namespaces = 0  (other kernels), then apply with: sysctl --system. Rootless Docker, Podman, Flatpak and some browsers need user namespaces, so test first.
219. **HOST-KRN-023 - IPv6 Source-Routed Packets Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_source\_route = 0  then apply with: sysctl --system
220. **HOST-KRN-024 - Secure ICMP Redirects Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.secure\_redirects = 0  then apply with: sysctl --system
221. **HOST-KRN-025 - IPv6 Router Advertisements Accepted** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_ra = 0  then apply with: sysctl --system. Caution: on a host that gets its IPv6 address or default route from router advertisements (SLAAC, most cloud dual-stack VMs), accept\_ra = 0 removes the IPv6 default route and cuts IPv6 connectivity. Use it only with static IPv6 addressing; otherwise keep accept\_ra = 1 and rely on RA Guard on the switch or a host firewall rule for RA sources.
222. **HOST-KRN-026 - Bogus ICMP Error Responses Logged** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.icmp\_ignore\_bogus\_error\_responses = 1  then apply with: sysctl --system
223. **HOST-KRN-027 - Kexec Kernel Replacement Allowed** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.kexec\_load\_disabled = 1  then apply with: sysctl --system. Caution: kexec\_load\_disabled = 1 also stops kdump from loading its crash kernel (kdump.service on the RHEL family, kdump-tools on Debian and Ubuntu), so crash dumps are lost. Keep it 0 where kdump is used, or accept that trade-off deliberately. The setting cannot be turned back off without a reboot.
224. **HOST-KRN-028 - Unprivileged userfaultfd Allowed** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  vm.unprivileged\_userfaultfd = 0  then apply with: sysctl --system. Caution: unprivileged live migration tools (for example QEMU post-copy migration or CRIU run as a normal user) need userfaultfd; test those first.
225. **HOST-KRN-029 - TTY Line Discipline Autoloading Enabled** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  dev.tty.ldisc\_autoload = 0  then apply with: sysctl --system
226. **HOST-KRN-030 - BPF JIT Hardening Off** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.core.bpf\_jit\_harden = 2  then apply with: sysctl --system
227. **HOST-KRN-031 - io\_uring Open to Unprivileged Users** (low severity, low effort)
   Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.io\_uring\_disabled = 2  then apply with: sysctl --system. Caution: applications that use io\_uring for fast I/O (some databases, proxies and runtimes) fall back or fail to start when it is disabled; test them first, or use 1 to allow only a chosen group.
228. **HOST-LOG-001 - System Logging Not Persistent** (low severity, low effort)
   Either install and enable rsyslog, or make the journal persistent: mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal && systemctl restart systemd-journald
229. **HOST-LOG-003 - Security Logs Readable by All Users** (low severity, low effort)
   chmod o-rwx /var/log/auth.log /var/log/secure /var/log/syslog /var/log/messages  and set the matching create mode in /etc/logrotate.d.
230. **HOST-LOG-004 - Log Rotation Not Configured** (low severity, low effort)
   Install logrotate (apt install logrotate / dnf install logrotate) and keep its daily timer enabled, or set SystemMaxUse= in journald.conf.
231. **HOST-PAM-001 - Password Quality Module Not Enforced** (low severity, low effort)
   Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family). Make sure the password stack has  password requisite pam\_pwquality.so retry=3  before pam\_unix (pam-auth-update or authselect enable-feature with-pwquality), and in /etc/security/pwquality.conf keep  dictcheck = 1  and  usercheck = 1 . Pair it with a breached-password blocklist where possible. Keep a root session open and test a login and a password change in a second session before closing it: a wrong PAM line can lock every user out.
232. **HOST-PAM-002 - Account Lockout Releases Too Quickly** (low severity, low effort)
   In /etc/security/faillock.conf set  unlock\_time = 900  (or 0 to require an administrator to run faillock --reset), keeping deny between 3 and 10. Keep a root session open and test in a second session before closing the first: a wrong PAM line can lock every user out, and unlock\_time = 0 means locked accounts, administrators included, wait for faillock --reset.
233. **HOST-PAM-003 - su Not Restricted to an Administrative Group** (low severity, low effort)
   In /etc/pam.d/su uncomment or add  auth required pam\_wheel.so use\_uid group=sudo  (group=wheel on the RHEL family), and make sure the group contains only administrators. Caution: add every administrator who uses su to that group first and test from a second session, or they lose su access.
234. **HOST-PAM-004 - Password History Not Enforced** (low severity, low effort)
   Add  password required pam\_pwhistory.so remember=5 use\_authtok  before pam\_unix in the password stack (pam-auth-update on Debian/Ubuntu, authselect enable-feature with-pwhistory on the RHEL family), or set  remember = 5  in /etc/security/pwhistory.conf. Keep a root session open and test a password change in a second session before closing it: a wrong PAM line can block password changes or logins.
235. **HOST-PAM-005 - Minimum Password Age Not Set** (low severity, low effort)
   Set  PASS\_MIN\_DAYS 1  in /etc/login.defs, and for existing accounts  chage --mindays 1 \<user\> .
236. **HOST-PAM-007 - Inactive Accounts Never Locked** (low severity, low effort)
   useradd -D -f 30  (writes INACTIVE=30 to /etc/default/useradd), and for existing accounts  chage --inactive 30 \<user\> . Caution: this locks accounts whose password has expired for 30 days, so exempt break-glass and rarely used administrator accounts or keep their passwords current.
237. **HOST-PAM-009 - No Shell Idle Timeout** (low severity, low effort)
   Create /etc/profile.d/99-tmout.sh with  TMOUT=900 ; readonly TMOUT ; export TMOUT  (mode 644, owned by root). Sessions that must stay open should use a multiplexer such as tmux with its own lock, not a longer TMOUT. Caution: a readonly TMOUT ends idle interactive shells, including foreground jobs left waiting in them; run long jobs under nohup, systemd-run or a multiplexer.
238. **HOST-PKG-004 - Many Old Kernels Retained** (low severity, low effort)
   Debian/Ubuntu: apt-get autoremove --purge  removes kernels outside the keep list. RHEL family: set installonly\_limit=3 in /etc/dnf/dnf.conf and run  dnf remove --oldinstallonly . Never remove the running kernel (uname -r).
239. **HOST-PKG-006 - Package Lists Not Refreshed Automatically** (low severity, low effort)
   Debian/Ubuntu: create /etc/apt/apt.conf.d/20auto-upgrades with  APT::Periodic::Update-Package-Lists "1";  (dpkg-reconfigure unattended-upgrades writes it) and  systemctl enable apt-daily.timer . RHEL family:  systemctl enable --now dnf-makecache.timer .
240. **HOST-PWD-003 - Short Minimum Password Length** (low severity, low effort)
   Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family) and set  minlen = 15  (or more) in /etc/security/pwquality.conf.
241. **HOST-SSH-005 - SSH X11 Forwarding Enabled** (low severity, low effort)
   Set  X11Forwarding no  in /etc/ssh/sshd\_config and reload sshd.
242. **HOST-SSH-006 - SSH Allows Many Authentication Attempts** (low severity, low effort)
   Set  MaxAuthTries 4  (or lower) in /etc/ssh/sshd\_config and reload sshd. Consider fail2ban or sshguard for rate limiting.
243. **HOST-SSH-008 - SSH Authentication Controls Too Permissive** (low severity, low effort)
   Set LoginGraceTime 60 or lower and choose AuthenticationMethods appropriate to the server, such as publickey, then restrict access with AllowUsers or AllowGroups where practical.
244. **HOST-SSH-012 - SSH Logging Level Too Low** (low severity, low effort)
   Set  LogLevel VERBOSE  (or INFO) in /etc/ssh/sshd\_config and reload sshd.
245. **HOST-SSH-013 - SSH Authorized Keys Without Restrictions or Protection** (low severity, low effort)
   Prefix each root key with options, for example  from="203.0.113.0/24",no-port-forwarding,no-agent-forwarding  or  command="/usr/local/bin/backup",restrict ; chmod 600 \~/.ssh/authorized\_keys and 700 \~/.ssh for every account. Prefer sudo from a personal account over direct root keys.
246. **HOST-SUDO-001 - Sudo Commands Not Logged to a Dedicated File** (low severity, low effort)
   With visudo add:  Defaults use\_pty  and  Defaults logfile="/var/log/sudo.log"
247. **HOST-SVC-001 - Unnecessary Legacy Services Running** (low severity, low effort)
   systemctl disable --now \<service\>  for each listed service you do not need, or remove its package. Caution: check first that nothing depends on it (systemctl list-dependencies --reverse \<service\>) and keep console access when disabling network or remote-access services.
248. **HOST-SVC-007 - Avahi or CUPS Installed or Enabled** (low severity, low effort)
   apt purge avahi-daemon cups cups-daemon  or  dnf remove avahi cups , and  systemctl disable --now avahi-daemon.socket avahi-daemon.service cups.socket cups.service . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
249. **HOST-SVC-008 - NFS Server or rpcbind Installed or Enabled** (low severity, low effort)
   apt purge nfs-kernel-server rpcbind  or  dnf remove nfs-utils rpcbind  where NFS is not served, and  systemctl disable --now nfs-server rpcbind.socket rpcbind.service . Where NFS is required, export read-only with root\_squash to named hosts and firewall port 2049 and 111. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
250. **HOST-SVC-009 - DHCP or DNS Server Installed or Enabled** (low severity, low effort)
   apt purge isc-dhcp-server bind9  or  dnf remove dhcp-server bind , and  systemctl disable --now isc-dhcp-server named . If this machine is meant to be a DNS or DHCP server, record that in the suppression file and review NET-SVC findings for its configuration. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
251. **HOST-SVC-010 - Samba File Server Installed or Enabled** (low severity, low effort)
   apt purge samba  or  dnf remove samba , and  systemctl disable --now smbd nmbd  (smb nmb on the RHEL family). Where it is required, disable SMB1, require signing and restrict shares to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
252. **HOST-SVC-011 - SNMP Agent or Squid Proxy Installed or Enabled** (low severity, low effort)
   apt purge snmpd squid  or  dnf remove net-snmp squid , and  systemctl disable --now snmpd squid . If monitoring needs SNMP, use version 3 with authPriv and bind the agent to the management network. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
253. **HOST-SVC-012 - inetd Super-Server Installed or Enabled** (low severity, low effort)
   apt purge xinetd openbsd-inetd  or  dnf remove xinetd , and  systemctl disable --now xinetd . Review /etc/inetd.conf and /etc/xinetd.d first so nothing needed disappears. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.
254. **HOST-TIME-001 - Time Synchronisation Not Active** (low severity, low effort)
   Enable one time service, for example: systemctl enable --now systemd-timesyncd  (or install chrony).
255. **HOST-UPD-001 - Automatic Security Updates Not Configured** (low severity, low effort)
   Debian/Ubuntu: apt install unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades . RHEL family: dnf install dnf-automatic, set apply\_updates = yes and upgrade\_type = security, then systemctl enable --now dnf-automatic.timer .
256. **HOST-USR-005 - System Accounts With Login Shells** (low severity, low effort)
   usermod -s /usr/sbin/nologin \<account\>  for each listed account (use /sbin/nologin on RHEL family).
257. **HOST-USR-010 - Home Directories Accessible to Other Users** (low severity, low effort)
   chmod 750 /home/\<user\>  for each listed account (700 if group access is not needed), and set HOME\_MODE 0750 in /etc/login.defs or DIR\_MODE=0750 in /etc/adduser.conf so new accounts start that way.
258. **NET-DB-017 - MySQL Error Log Not Configured** (low severity, low effort)
   Add  log\_error = /var/log/mysql/error.log  under \[mysqld\], make the folder writable by the mysql user, and add a logrotate rule.
259. **NET-DB-018 - MySQL Resolves Client Host Names** (low severity, low effort)
   Rewrite grants that use host names to IP addresses or '%', then add  skip-name-resolve  under \[mysqld\] and restart in a maintenance window.
260. **NET-DB-021 - PostgreSQL Connection Logging Off** (low severity, low effort)
   Set  log\_connections = on  and  log\_disconnections = on  in postgresql.conf and reload. On PostgreSQL 18 and newer, log\_connections accepts a list such as  receipt,authentication,authorization .
261. **NET-DNS-001 - BIND Version String Disclosed** (low severity, low effort)
   Add  version "not disclosed";  inside the options block and run named-checkconf, then rndc reconfig.
262. **NET-FW-004 - Firewall Rules Open Ports With No Service** (low severity, low effort)
   Remove rules for services that no longer exist (ufw status numbered, then ufw delete \<n\>), or note why a rule must stay. Review the firewall whenever a service is retired.
263. **NET-MAIL-003 - Postfix VRFY Command Enabled** (low severity, low effort)
   Add  disable\_vrfy\_command = yes  to main.cf and postfix reload.
264. **WEB-APA-001 - Apache Reveals Version in Header and Footer** (low severity, low effort)
   Set  ServerTokens Prod  and  ServerSignature Off  in apache2.conf or conf-enabled/security.conf (Debian) or httpd.conf (RHEL), then apachectl configtest and reload.
265. **WEB-APA-002 - Apache TRACE Method Enabled** (low severity, low effort)
   Add  TraceEnable Off  to the server configuration and reload.
266. **WEB-APA-004 - Apache AllowOverride All Permits .htaccess Control** (low severity, low effort)
   Set  AllowOverride None  and move needed rules into the vhost, or allow only what the application needs, for example  AllowOverride FileInfo Indexes Limit  for WordPress permalinks.
267. **WEB-APA-007 - Apache OCSP Stapling Not Enabled** (low severity, low effort)
   Add  SSLUseStapling On  inside the vhost and  SSLStaplingCache "shmcb:logs/ssl\_stapling(32768)"  outside it, as the Mozilla generator produces.
268. **WEB-APA-009 - Apache ETag Reveals Inode Numbers** (low severity, low effort)
   Set  FileETag MTime Size  or  FileETag None  and reload.
269. **WEB-APA-010 - Apache Serves Repository and Environment Files** (low severity, low effort)
   Add to the server configuration:  \<DirectoryMatch "/\\.(git|svn|hg)"\> Require all denied \</DirectoryMatch\>  and  \<FilesMatch "^\\.(env|git|svn)"\> Require all denied \</FilesMatch\> , then reload.
270. **WEB-APP-007 - Flask Debug Mode Enabled in Application Code** (low severity, low effort)
   Remove debug=True from app.run() and read it from the environment for local work only (app.run(debug=os.environ.get('FLASK\_DEBUG') == '1')); serve production with Gunicorn or uWSGI, which never call app.run().
271. **WEB-CMS-001 - WordPress Version Disclosed** (low severity, low effort)
   Keep WordPress updated (automatic minor updates on). Remove the generator tag (remove\_action('wp\_head','wp\_generator')) and delete readme.html after each update, or block it at the web server.
272. **WEB-CMS-002 - WordPress XML-RPC Interface Enabled** (low severity, low effort)
   Block it at the web server (\<Files xmlrpc.php\> Require all denied \</Files\>) or disable it with a security plugin, unless Jetpack or the mobile app needs it.
273. **WEB-CMS-003 - WordPress User Accounts Enumerable** (low severity, low effort)
   Restrict the users endpoint to authenticated requests with a security plugin or a small rest\_endpoints filter, and use login names that differ from public display names.
274. **WEB-CMS-007 - WordPress Dashboard File Editor Enabled** (low severity, low effort)
   Add define('DISALLOW\_FILE\_EDIT', true); to wp-config.php above the line that says to stop editing.
275. **WEB-CMS-010 - Joomla Version Disclosed** (low severity, low effort)
   Block the folder at the web server (Apache: \<Directory "/path/administrator/manifests"\> Require all denied \</Directory\>; nginx: location ^\~ /administrator/manifests/ { deny all; }) and keep Joomla updated.
276. **WEB-CMS-011 - Drupal Version Disclosed by Changelog** (low severity, low effort)
   Delete the changelog files after each update, or deny \*.txt files in the Drupal root and core folders at the web server, and keep Drupal updated.
277. **WEB-CMS-018 - WordPress Author Archive Reveals Login Names** (low severity, low effort)
   Return 404 for author queries by id (a small redirect rule or a security plugin's user enumeration protection), and give administrators a nickname and display name that differ from the login name.
278. **WEB-EXP-009 - Dependency Manifests Exposed** (low severity, low effort)
   Keep manifests outside the public web root (for example serve only a public/ subfolder), or deny them at the web server.
279. **WEB-EXP-010 - Default Web Server Page Served** (low severity, low effort)
   Remove the default site or replace its index page, and disable unused virtual hosts.
280. **WEB-HDR-004 - Missing X-Content-Type-Options** (low severity, low effort)
   Send  X-Content-Type-Options: nosniff  on every response (Apache: Header always set X-Content-Type-Options nosniff; nginx: add\_header X-Content-Type-Options nosniff always;).
281. **WEB-HDR-005 - Missing Referrer-Policy** (low severity, low effort)
   Send  Referrer-Policy: strict-origin-when-cross-origin  (or no-referrer). Never use unsafe-url.
282. **WEB-HDR-006 - Missing Permissions-Policy** (low severity, low effort)
   Send, for example,  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()  adjusted to the features the site really uses.
283. **WEB-HDR-007 - Server Software Version Disclosed** (low severity, low effort)
   Apache: ServerTokens Prod and ServerSignature Off . nginx: server\_tokens off; . PHP: expose\_php = Off in php.ini . Remove X-Powered-By in the application or proxy.
284. **WEB-NGX-001 - nginx Version Shown in Server Header** (low severity, low effort)
   In the http block of nginx.conf add  server\_tokens off;  then run nginx -t and reload.
285. **WEB-NGX-005 - nginx OCSP Stapling Off or Session Tickets On** (low severity, low effort)
   Add  ssl\_stapling on; ssl\_stapling\_verify on; ssl\_session\_tickets off;  and a  resolver  directive, as the Mozilla generator produces.
286. **WEB-NODE-003 - Node.js Service Not Running in Production Mode** (low severity, low effort)
   Add Environment=NODE\_ENV=production to the \[Service\] section (systemctl edit \<unit\>), run systemctl daemon-reload and restart the service.
287. **WEB-PHP-002 - PHP Version Exposed in Headers** (low severity, low effort)
   In php.ini set  expose\_php = Off  and restart PHP.
288. **HOST-AUD-011 - Logs Not Forwarded to a Remote Collector** (low severity, medium effort)
   rsyslog: create /etc/rsyslog.d/90-remote.conf with  \*.\* action(type="omfwd" target="logs.example.internal" port="6514" protocol="tcp" StreamDriver="gtls" StreamDriverMode="1" StreamDriverAuthMode="x509/name")  and restart rsyslog. Or configure systemd-journal-upload with URL= pointing at a journal-remote collector. Prefer TLS so logs are not readable in transit.
289. **HOST-BOOT-005 - Kernel Module Loading Unrestricted** (low severity, medium effort)
   Boot with  module.sig\_enforce=1  (or  lockdown=integrity) on the kernel command line and sign any out-of-tree modules, or set  kernel.modules\_disabled = 1  from a late boot service once every needed module is loaded (it cannot be turned back on without a reboot).
290. **HOST-CTR-015 - Docker Content Trust Not Enabled** (low severity, medium effort)
   Add  DOCKER\_CONTENT\_TRUST=1  to /etc/environment (or a file in /etc/profile.d) and sign your own images with docker trust sign; images without signatures then need an explicit --disable-content-trust for a deliberate exception.
291. **HOST-CVE-003 - Vulnerability Snapshot Outdated or Test Data** (low severity, medium effort)
   Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.
292. **HOST-FS-008 - Temporary Filesystems Allow Executables or Devices** (low severity, medium effort)
   Mount the path as tmpfs or a separate filesystem with options nodev,nosuid (and noexec where software allows), for example via systemd tmp.mount or /etc/fstab, then remount.
293. **HOST-FS-010 - /var/tmp Not a Separate Filesystem** (low severity, medium effort)
   Mount /var/tmp as a separate filesystem, or bind-mount it onto /tmp:  /tmp /var/tmp none bind,nodev,nosuid,noexec 0 0  in /etc/fstab, then reboot or remount. Caution: /var/tmp is meant to survive reboots and some installers and databases run programs from it, so check what uses it before binding it to /tmp with noexec, and keep console access when editing /etc/fstab (a wrong line can stop the boot).
294. **HOST-FS-011 - /home Not a Separate Filesystem With nodev** (low severity, medium effort)
   Place /home on its own partition or logical volume and add nodev,nosuid to its options in /etc/fstab; remount with: mount -o remount /home . Caution: moving /home to a new volume needs a backup, a maintenance window and console access; a wrong /etc/fstab line can stop the boot.
295. **HOST-FS-012 - Log Directories on the Root Filesystem** (low severity, medium effort)
   Place /var/log (and ideally /var/log/audit) on its own partition or logical volume, listed in /etc/fstab with nodev,nosuid,noexec. Caution: moving log directories needs a backup, a maintenance window and console access; stop the logging services while copying, and a wrong /etc/fstab line can stop the boot.
296. **HOST-FS-013 - Temporary Filesystems Allow Executables** (low severity, medium effort)
   Add noexec to the mount options of each temporary filesystem (systemd tmp.mount drop-in or /etc/fstab) and remount. Some package managers need exec on /tmp during installs; set TMPDIR for them rather than removing noexec.
297. **HOST-LOG-002 - Security Audit Daemon Not Running** (low severity, medium effort)
   Install the audit package (apt install auditd, or dnf install audit), enable it (systemctl enable --now auditd) and load a rule set appropriate to your policy.
298. **HOST-SYSD-002 - Custom Services Lack Sandboxing** (low severity, medium effort)
   Add the missing lines with systemctl edit \<unit\> (a drop-in), for example NoNewPrivileges=yes, ProtectSystem=strict, ReadWritePaths=/var/lib/\<app\>, PrivateTmp=yes, ProtectHome=yes. Restart the service and test it; add ReadWritePaths= for every folder the app must write. systemd-analyze security \<unit\> suggests further options.
299. **HOST-MAC-001 - No Mandatory Access Control Enforced** (low severity, high effort)
   Ubuntu/Debian: keep AppArmor enabled (systemctl enable --now apparmor) and its profiles in enforce mode. RHEL family: set SELINUX=enforcing in /etc/selinux/config and relabel. Test applications in permissive or complain mode first.
300. **HOST-PAM-006 - Password Expiry Warning Too Short** (info severity, low effort)
   Set  PASS\_WARN\_AGE 7  in /etc/login.defs, and for existing accounts  chage --warndays 7 \<user\> .
301. **HOST-PWD-005 - Forced Periodic Password Rotation Configured** (info severity, low effort)
   Prefer long passphrases, a breached-password blocklist and multi-factor authentication over forced rotation. If policy allows, set  PASS\_MAX\_DAYS 365  or  99999  in /etc/login.defs and change passwords when compromise is suspected.
302. **WEB-APA-008 - Apache Has No Request Filtering Module** (info severity, low effort)
   Consider ModSecurity with the OWASP Core Rule Set in detection-only mode first, or mod\_evasive for simple rate limiting, and tune before blocking.
303. **WEB-CMS-017 - WordPress Cron Reachable by Visitors** (info severity, low effort)
   Add define('DISABLE\_WP\_CRON', true); to wp-config.php, run  \*/5 \* \* \* \* www-data php /var/www/\<site\>/wp-cron.php  from the system cron (or curl the URL from cron), and deny direct requests to wp-cron.php at the web server.
304. **WEB-NGX-006 - nginx Request Limits Not Configured** (info severity, low effort)
   Set  client\_max\_body\_size  to what the application needs (the default is 1m) and add  limit\_req\_zone  for login and API locations with a sensible rate. See the nginx limit\_req and limit\_conn module documentation.
305. **WEB-SEC-001 - No security.txt Contact File** (info severity, low effort)
   Publish /.well-known/security.txt with at least Contact: and Expires: fields (see securitytxt.org).
306. **HOST-PKG-003 - Third-Party Package Repositories Present** (info severity, medium effort)
   For each listed repository, record who runs it and why it is needed; remove the ones nobody can justify (delete the file under /etc/apt/sources.list.d or /etc/yum.repos.d and apt-get update or dnf clean all). Keep the rest pinned to the packages they are for (apt preferences, dnf includepkgs).
307. **WEB-APP-008 - Dependency Lock Files Older Than a Year** (info severity, medium effort)
   Run composer update / npm update / bundle update / poetry update in a test environment, run the audit command of each ecosystem (composer audit, npm audit, bundle audit, pip-audit) and deploy. Schedule dependency updates at least quarterly.
308. **HOST-CTR-009 - Docker Not Running in Rootless Mode** (info severity, high effort)
   Where the workloads allow it, install rootless Docker (dockerd-rootless-setuptool.sh install) for the service account and disable the system daemon; otherwise keep userns-remap (HOST-CTR-002) as the fallback.
## Findings

### \[CRITICAL\] Docker API Exposed Over TCP Without TLS (HOST-CTR-001)

Category: network. Effort: low. Confidence: high.

Checks whether the Docker daemon is configured to listen on a TCP socket without TLS client verification.

**Why it matters:** The Docker API is equivalent to root on the host. Exposed over TCP without mutual TLS, anyone who can reach the port can start a privileged container and take over the machine; this is a common cause of cryptomining infections.

- **docker:** Docker API listens on TCP without TLS verification
  - `daemon.json hosts: tcp://0.0.0.0:2375`

**Fix:** Remove tcp:\/\/ listeners and use the local Unix socket or SSH (docker context with ssh:\/\/). If remote access is unavoidable, enable tlsverify with client certificates and restrict the port in the firewall.

**Verify:** `ss -tlnp | grep -E ':(2375|2376)'  and  grep -r 'tcp://' /etc/docker /etc/systemd/system/docker.service.d`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-319; OWASP Top 10 A05:2021, A07:2021; NIST SP 800-53 AC-3, SC-8

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[CRITICAL\] Known Exploited Vulnerabilities Present (HOST-CVE-002)

Category: patch. Effort: medium. Confidence: high.

Checks installed packages against an offline vulnerability snapshot.

**Why it matters:** Known vulnerable package versions can expose a server to documented attacks.

- **sudo:** sudo 1.8.21p2-3ubuntu1.4: LAB-CVE-2021-3156 (CVE-2021-3156), fixed in 1.8.21p2-3ubuntu1.5, severity high
  - `matcher: range`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`
- **policykit-1:** policykit-1 0.105-20ubuntu0.18.04.5: LAB-CVE-2021-4034 (CVE-2021-4034), fixed in 0.105-20ubuntu0.18.04.6, severity high
  - `matcher: range`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`

**Fix:** Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.

**Verify:** `Re-run OpenVAPT after patching and confirm the finding is absent.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** P10: Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities, https://openvapt.com/academy/p10.html (offline: `openvapt learn P10`)

### \[CRITICAL\] Kubelet Allows Anonymous or Unauthorised Access (HOST-K8S-001)

Category: identity. Effort: medium. Confidence: high.

Checks the kubelet configuration for anonymous authentication, AlwaysAllow authorisation or the unauthenticated read-only port.

**Why it matters:** The kubelet API can run commands in every pod on the node. With anonymous access or AlwaysAllow, anyone who can reach port 10250 controls the node's workloads; the read-only port 10255 leaks pod and environment details.

- **/var/lib/kubelet/config.yaml:** Kubelet allows unauthenticated or unauthorised access
  - `authentication.anonymous.enabled: true`
  - `authorization.mode: AlwaysAllow`
  - `readOnlyPort: 10255`

**Fix:** Set authentication.anonymous.enabled: false, authorization.mode: Webhook and readOnlyPort: 0 in the kubelet configuration, then restart the kubelet.

**Verify:** `grep -A2 -E 'anonymous|authorization|readOnlyPort' /var/lib/kubelet/config.yaml`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-862; OWASP Top 10 A01:2021, A07:2021; NIST SP 800-53 AC-3, IA-2

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[CRITICAL\] SSH Permits Empty Passwords (HOST-SSH-003)

Category: identity. Effort: low. Confidence: high.

Checks whether SSH lets accounts with an empty password log in.

**Why it matters:** Any account without a password becomes an open door to anyone on the network.

- **sshd:** Empty passwords are accepted
  - `permitemptypasswords yes`

**Fix:** Set  PermitEmptyPasswords no  in /etc/ssh/sshd\_config, reload sshd, and give every account a password or lock it (passwd -l \<user\>).

**Verify:** `sshd -T | grep -i permitemptypasswords  should print 'no'.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-258; NIST SP 800-53 IA-5(1)

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[CRITICAL\] Redis Authentication and Network Binding (NET-DB-002)

Category: data. Effort: low. Confidence: high.

Redis lacks protected mode, authentication, or a loopback-only bind.

**Why it matters:** An unauthenticated network-reachable Redis instance can disclose or alter data and may permit dangerous administrative operations.

- **/etc/redis/redis.conf:** Redis lacks protected mode, authentication, or a loopback-only bind.
  - `/etc/redis/redis.conf: protected-mode = no`
  - `/etc/redis/redis.conf: bind = 0.0.0.0`

**Fix:** Set protected-mode yes, configure ACL users with strong secrets, bind to 127.0.0.1 and ::1, and restrict remote access with a firewall.

**Verify:** `Review redis-server --test-memory only for health, then inspect the effective configuration and listening socket after reload.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[CRITICAL\] MongoDB Authorization and Network Binding (NET-DB-003)

Category: data. Effort: low. Confidence: high.

MongoDB authorization is disabled or it binds beyond loopback.

**Why it matters:** MongoDB without authorization or with broad exposure can permit unauthenticated database access.

- **/etc/mongod.conf:** MongoDB authorization is disabled or it binds beyond loopback.
  - `/etc/mongod.conf: authorization = disabled`
  - `/etc/mongod.conf: bindip = 0.0.0.0`

**Fix:** Set security.authorization to enabled and net.bindIp to 127.0.0.1, then allow only approved clients through a controlled network path.

**Verify:** `Run mongod --config <file> --configExpand none in a maintenance workflow and confirm port 27017 is loopback-only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[CRITICAL\] PostgreSQL Trust and Broad Client Access (NET-DB-004)

Category: data. Effort: low. Confidence: high.

PostgreSQL accepts trust authentication or unrestricted client networks.

**Why it matters:** Trust authentication and all-address client rules can allow database access without a password from untrusted networks.

- **/etc/postgresql/16/main/postgresql.conf:** PostgreSQL accepts trust authentication or unrestricted client networks.
  - `/etc/postgresql/16/main/postgresql.conf: broad or trust rule detected`
  - `/etc/postgresql/16/main/pg_hba.conf: broad or trust rule detected`

**Fix:** Bind PostgreSQL to required interfaces only and replace trust or unrestricted pg\_hba.conf rules with narrow CIDRs and scram-sha-256. Caution: users whose passwords are stored as md5 cannot log in with scram-sha-256 until their password is set again with password\_encryption = scram-sha-256, so reset them first and reload, not restart, to test.

**Verify:** `Run pg_ctl reload through normal operations, then use pg_hba_file_rules and ss -tlnp to verify the effective rules.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[CRITICAL\] Elasticsearch Security and Network Binding (NET-DB-007)

Category: data. Effort: low. Confidence: high.

Elasticsearch security is disabled or network.host is broadly reachable.

**Why it matters:** An exposed Elasticsearch cluster without authentication can disclose, alter, or delete indexed data.

- **/etc/elasticsearch/elasticsearch.yml:** Elasticsearch security is disabled or network.host is broadly reachable.
  - `/etc/elasticsearch/elasticsearch.yml: xpack.security.enabled = false`
  - `/etc/elasticsearch/elasticsearch.yml: network.host = 0.0.0.0`

**Fix:** Set xpack.security.enabled true, bind network.host to loopback or an approved private interface, require TLS, and filter access.

**Verify:** `Use the local node configuration API with approved credentials and confirm port 9200 is not broadly exposed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[CRITICAL\] Control Plane or Remote Desktop Ports Network-Reachable (NET-SVC-002)

Category: network. Effort: medium. Confidence: high.

Docker API, Kubernetes API, kubelet, etcd, VNC or RDP listen on network-reachable addresses.

**Why it matters:** These services hand out administrative control of the host or the cluster. The unauthenticated Docker API and etcd give code execution or every cluster secret to whoever connects; RDP and VNC are the most attacked remote access services.

- **sockets:** Control plane or remote desktop ports are network-reachable
  - `tcp 0.0.0.0:2375 Docker API without TLS, bound to all interfaces`
  - `tcp 0.0.0.0:6443 Kubernetes API, bound to all interfaces`

**Fix:** Bind control planes to loopback or a dedicated management address, require mutual TLS or strong authentication, and filter access to approved source networks or a VPN.

**Verify:** `Review ss -tulpn and the host firewall from a trusted administrative session.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1327, CWE-923; OWASP Top 10 A05:2021; NIST SP 800-53 AC-17, CM-7, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[CRITICAL\] Environment or Secrets File Exposed (WEB-EXP-002)

Category: exposure. Effort: medium. Confidence: high.

Checks whether .env and similar configuration files can be downloaded.

**Why it matters:** Framework .env files hold database passwords, mail credentials, encryption keys and API tokens. Downloading one usually means full compromise of the application and its data.

- **shop.example.test:** Exposed: /.env (HTTP 200)
  - `/.env (HTTP 200)`

**Fix:** Move secrets out of the web root, block dot-files at the web server, delete backup copies, then rotate every secret in the file because it must be treated as leaked.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/.env  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-538, CWE-260; OWASP Top 10 A05:2021; ASVS 14.3.2, 2.10.4; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[CRITICAL\] Node.js Debugger (Inspector) Enabled in a Service (WEB-NODE-002)

Category: config. Effort: low. Confidence: high.

Checks whether a Node.js service is started with --inspect, which opens the V8 debugger.

**Why it matters:** The inspector has no authentication: anyone who can connect to it can run any JavaScript inside the application, as the service's user. Bound to 0.0.0.0 that is remote code execution; even on 127.0.0.1 any local user or server-side request forgery bug can reach it.

- **systemd units:** Node.js services start the debugger
  - `shop-api.service: starts the inspector on a network address (--inspect=0.0.0.0:9229 )`

**Fix:** Remove --inspect from the service and from NODE\_OPTIONS, run systemctl daemon-reload and restart. Debug on a development machine, or temporarily over an SSH tunnel to 127.0.0.1:9229.

**Verify:** `systemctl show -p ExecStart,Environment <unit> | grep -c inspect  prints 0, and nothing listens on 9229.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-306; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-7, CM-6

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[CRITICAL\] Python Interactive Debugger Enabled in a Service (WEB-PY-004)

Category: config. Effort: low. Confidence: high.

Checks whether a service runs Flask or Werkzeug with the interactive debugger enabled.

**Why it matters:** The Werkzeug debugger lets a visitor run Python code from an error page. Its PIN has been bypassed and brute-forced in practice, and with WERKZEUG\_DEBUG\_PIN=off there is no protection at all: this is remote code execution.

- **systemd units:** Services enable the Python interactive debugger
  - `shop-reports.service: enables the debugger (FLASK_DEBUG=1)`

**Fix:** Remove the debug switches, run the application under a production WSGI server (Gunicorn, uWSGI or mod\_wsgi) behind nginx or Apache, run systemctl daemon-reload and restart the service.

**Verify:** `systemctl show -p ExecStart,Environment <unit>  shows no debug switches.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-94; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-7, CM-6

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[CRITICAL\] Tomcat Administrative Users With Default or Weak Passwords (WEB-TOM-002)

Category: identity. Effort: low. Confidence: high.

Checks tomcat-users.xml for manager or admin accounts with well-known default passwords, passwords equal to the user name, or short passwords.

**Why it matters:** Default Tomcat credentials (tomcat/tomcat, admin/admin, tomcat/s3cret) are in every attack tool's word list. A Manager account with such a password lets anyone deploy code on the server.

- **/etc/tomcat9:** Tomcat administrative users have weak credentials
  - `user "tomcat" (roles manager-gui,admin-gui) uses a documented default password`

**Fix:** Remove accounts you do not need. Give the remaining ones long random passwords and store them as digests: configure a CredentialHandler (for example SecretKeyCredentialHandler with PBKDF2) in the Realm and generate hashes with bin/digest.sh. Restart Tomcat.

**Verify:** `Re-run this check; no administrative user should be listed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1392, CWE-521, CWE-256; OWASP Top 10 A07:2021; ASVS 2.1.1, 2.5.4; WSTG-ATHN-02; NIST SP 800-53 IA-5

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[CRITICAL\] Tomcat AJP Connector Unprotected (Ghostcat) (WEB-TOM-004)

Category: webapp. Effort: low. Confidence: high.

Checks for an active AJP connector that has no shared secret and is not bound to loopback, or that disables the secret requirement.

**Why it matters:** AJP is a trusted, unauthenticated protocol between a web server and Tomcat. CVE-2020-1938 (Ghostcat) showed that a reachable AJP port lets an attacker read any file in the web applications and, where uploads exist, execute code. It is listed in the CISA Known Exploited Vulnerabilities catalogue.

- **/etc/tomcat9:** AJP connector is reachable without protection
  - `AJP connector on port 8009 is not bound to loopback and does not require a secret`

**Fix:** If you do not use AJP (most sites proxy over HTTP), delete or comment out the AJP \<Connector\>. Otherwise set address="127.0.0.1", a long random secret="..." with secretRequired="true", and the same secret in the proxy (mod\_proxy\_ajp: ProxyPass ... secret=...). Upgrade Tomcat to a supported release.

**Verify:** `grep -i ajp <conf>/server.xml  and  ss -tlnp | grep 8009  shows nothing or 127.0.0.1 only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1327; OWASP Top 10 A05:2021, A06:2021; NIST SP 800-53 SC-7, AC-3, CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[HIGH\] Cron Jobs Run Programs Other Users Can Modify (HOST-CRON-001)

Category: access. Effort: low. Confidence: high.

Resolves the programs and scripts that system and root cron jobs run and checks whether any is world-writable or sits in a world-writable folder.

**Why it matters:** HOST-FS-005 protects the cron tables themselves, but a root job that runs /usr/local/bin/backup.sh is only as trustworthy as that file. If any user can edit the script, or replace it in a writable folder, their code runs as root at the next scheduled minute.

- **cron:** Cron jobs run programs that other users can modify
  - `0777 /usr/local/bin/backup.sh is world-writable`

**Fix:** chmod o-w \<program\> and its folder; move scripts out of /tmp or user-writable folders into /usr/local/sbin owned by root; have cron run only files owned by root (chown root:root).

**Verify:** `for p in $(awk '/^[^#]/ && NF>6 {print $7}' /etc/crontab /etc/cron.d/*); do stat -c '%a %U %n' $p; done`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-426; NIST SP 800-53 AC-3, AC-6, CM-5

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[HIGH\] World-Writable Directory in Cron's PATH (HOST-CRON-002)

Category: access. Effort: low. Confidence: high.

Checks the PATH that system cron jobs search for commands for world-writable or relative entries.

**Why it matters:** A cron job that calls backup-db rather than /usr/local/sbin/backup-db runs whichever file of that name PATH finds first. If a folder on that PATH is world-writable, or the PATH contains an empty or relative entry, any user can plant a program that root runs on schedule (PATH interception).

- **cron PATH:** Cron's PATH can be hijacked
  - `/etc/crontab: 0777 /opt/scripts is world-writable without the sticky bit`

**Fix:** Remove the writable folder from PATH in /etc/crontab (or chmod o-w it), delete relative entries, and call programs in cron jobs by absolute path.

**Verify:** `grep ^PATH /etc/crontab /etc/cron.d/* ; for d in $(grep ^PATH /etc/crontab | cut -d= -f2 | tr : ' '); do stat -c '%a %n' $d; done`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-426, CWE-427; NIST SP 800-53 CM-6, AC-6, CM-7

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[HIGH\] Privileged or Host-Namespace Containers (HOST-CTR-003)

Category: config. Effort: medium. Confidence: high.

Looks for containers running privileged, sharing the host's PID, network or IPC namespace, or with dangerous added capabilities.

**Why it matters:** A privileged container, or one with CAP\_SYS\_ADMIN or host namespaces, has almost no isolation: a compromise of the application inside is a compromise of the host.

- **containers:** Containers with weak isolation
  - `3f2a9c1d8e7b: privileged, pid=host, network=host, cap-add SYS_ADMIN`

**Fix:** Recreate each container without --privileged, --pid=host, --net=host and --ipc=host; drop all capabilities and add back only the specific ones needed (--cap-drop=ALL --cap-add=...).

**Verify:** `docker inspect --format '{{.Name}} {{.HostConfig.Privileged}} {{.HostConfig.PidMode}} {{.HostConfig.NetworkMode}} {{.HostConfig.CapAdd}}' $(docker ps -aq)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-653; NIST SP 800-53 AC-6, SC-39

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[HIGH\] Containers Mount the Docker Socket or Host Root (HOST-CTR-004)

Category: access. Effort: medium. Confidence: high.

Looks for containers that bind-mount the Docker socket, the host root filesystem or sensitive host paths.

**Why it matters:** A container with /var/run/docker.sock can control Docker and therefore the host. Mounting / or /etc gives direct write access to host configuration.

- **containers:** Containers mount the Docker socket or sensitive host paths
  - `3f2a9c1d8e7b: /var/run/docker.sock:/var/run/docker.sock`
  - `3f2a9c1d8e7b: /:/host`

**Fix:** Remove these mounts. If a tool genuinely needs the Docker API, use a restricted socket proxy that allows only the required read-only calls.

**Verify:** `docker inspect --format '{{.Name}} {{.HostConfig.Binds}}' $(docker ps -aq)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-732; NIST SP 800-53 AC-6, SC-39

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[HIGH\] Docker Socket Accessible to Non-Administrators (HOST-CTR-008)

Category: access. Effort: low. Confidence: high.

Checks the Docker socket's permissions and who belongs to the docker group.

**Why it matters:** Whoever can write to /var/run/docker.sock is root: one command starts a privileged container with the host's disk mounted. Members of the docker group have that power without sudo, logging or a password prompt, so the group should be empty or hold only administrators.

- **/var/run/docker.sock:** Docker socket is reachable by non-administrators
  - `/var/run/docker.sock mode 0666 is accessible to all users`
  - `docker group members (root-equivalent): deploy`

**Fix:** chmod 660 /var/run/docker.sock && chown root:docker /var/run/docker.sock  (the daemon recreates it with these defaults). Remove users from the docker group (gpasswd -d \<user\> docker) and give administrators sudo docker, or move to rootless Docker.

**Verify:** `stat -c '%a %U:%G %n' /var/run/docker.sock ; getent group docker`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-250; NIST SP 800-53 AC-3, AC-6, CM-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[HIGH\] Seccomp or AppArmor Disabled for Containers (HOST-CTR-011)

Category: config. Effort: medium. Confidence: high.

Looks for the daemon or individual containers turning off the default seccomp profile, AppArmor or SELinux labelling.

**Why it matters:** Docker's default seccomp profile blocks around 40 dangerous system calls and AppArmor or SELinux confines what a container's root can touch on the host. Running unconfined removes the main barriers between a compromised container and the kernel.

- **containers:** Containers run without seccomp, AppArmor or SELinux confinement
  - `daemon.json seccomp-profile is unconfined`
  - `3f2a9c1d8e7b: seccomp=unconfined, apparmor=unconfined`

**Fix:** Remove --security-opt seccomp=unconfined and apparmor=unconfined from the container definitions and recreate them; if an application needs one extra system call, write a custom seccomp profile that adds only that call. Delete seccomp-profile from daemon.json.

**Verify:** `docker inspect --format '{{.Name}} {{.HostConfig.SecurityOpt}}' $(docker ps -aq)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-653, CWE-250; NIST SP 800-53 AC-6, SC-39, CM-7

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[HIGH\] containerd Remote API or Podman Unconfined Defaults (HOST-CTR-014)

Category: config. Effort: medium. Confidence: high.

Checks the containerd and Podman configuration files for a TCP API without TLS and for unconfined seccomp or disabled labelling defaults.

**Why it matters:** containerd's gRPC API is root on the host, like the Docker API; a tcp\_address without TLS exposes it to the network. Podman's containers.conf sets the defaults every container inherits: an unconfined seccomp profile or disabled SELinux labelling removes the isolation for all of them.

- **/etc/containerd/config.toml:** containerd listens on TCP without TLS
  - `grpc tcp_address = 0.0.0.0:8888`

**Fix:** containerd: remove tcp\_address or set tcp\_tls\_cert and tcp\_tls\_key, then restart containerd. Podman: delete seccomp\_profile = "unconfined", set label = true and apparmor\_profile = "container-default" in containers.conf.

**Verify:** `grep -E 'tcp_address|tcp_tls' /etc/containerd/config.toml ; grep -E 'seccomp_profile|^label|apparmor_profile' /etc/containers/containers.conf`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; NIST SP 800-53 AC-3, SC-8, CM-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[HIGH\] Installed OS Packages With Known Vulnerabilities (HOST-CVE-001)

Category: patch. Effort: medium. Confidence: high.

Checks installed packages against an offline vulnerability snapshot.

**Why it matters:** Known vulnerable package versions can expose a server to documented attacks.

- **sudo:** sudo 1.8.21p2-3ubuntu1.4: LAB-CVE-2021-3156 (CVE-2021-3156), fixed in 1.8.21p2-3ubuntu1.5, severity high
  - `matcher: range`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`
- **policykit-1:** policykit-1 0.105-20ubuntu0.18.04.5: LAB-CVE-2021-4034 (CVE-2021-4034), fixed in 0.105-20ubuntu0.18.04.6, severity high
  - `matcher: range`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`

**Fix:** Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.

**Verify:** `Re-run OpenVAPT after patching and confirm the finding is absent.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** P10: Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities, https://openvapt.com/academy/p10.html (offline: `openvapt learn P10`)

### \[HIGH\] Users With Root-Equivalent Docker Access (HOST-DKR-001)

Category: identity. Effort: medium. Confidence: high.

Checks whether non-root users can control Docker, which is equivalent to root access.

**Why it matters:** Members of the docker group, or anyone who can write to the Docker socket, can start a container that mounts the host filesystem and become root.

- **docker:** Non-root users can control Docker (root equivalent)
  - `docker group members: deploy`
  - `/var/run/docker.sock is world-writable (0666)`

**Fix:** Remove users who do not need it from the docker group (gpasswd -d \<user\> docker); consider rootless Docker or Podman; keep docker.sock at 660 root:docker.

**Verify:** `getent group docker ; stat -c '%a %U:%G' /var/run/docker.sock`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-266; NIST SP 800-53 AC-6

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[HIGH\] Sensitive Account Files Have Unsafe Permissions (HOST-FS-001)

Category: access. Effort: low. Confidence: high.

Checks that /etc/shadow and /etc/gshadow are not readable by others, and /etc/passwd and /etc/group are not writable by others.

**Why it matters:** Readable shadow files let any local user (or a compromised web app) copy password hashes and crack them offline. Writable passwd/group files let them create a root account.

- **/etc/shadow:** /etc/shadow is readable or writable by others
  - `mode 0644`
- **/etc/passwd:** /etc/passwd is writable by group or others
  - `mode 0666`

**Fix:** chown root:root /etc/passwd /etc/group && chmod 644 /etc/passwd /etc/group ; chown root:shadow /etc/shadow /etc/gshadow && chmod 640 /etc/shadow /etc/gshadow  (use root:root and 000 on RHEL-family systems).

**Verify:** `stat -c '%a %U:%G %n' /etc/passwd /etc/group /etc/shadow /etc/gshadow`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[HIGH\] Temporary Directory Missing the Sticky Bit (HOST-FS-002)

Category: access. Effort: low. Confidence: high.

Checks that world-writable temp directories have the sticky bit set.

**Why it matters:** Without the sticky bit any user can delete or replace other users' files in /tmp, enabling symlink attacks against root-run scripts.

- **/tmp:** /tmp is world-writable without the sticky bit
  - `mode 0777`

**Fix:** chmod 1777 /tmp /var/tmp

**Verify:** `stat -c '%a %n' /tmp /var/tmp  should print 1777 for both.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-377, CWE-732; NIST SP 800-53 AC-3

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] World-Writable Files in System Configuration (HOST-FS-003)

Category: access. Effort: medium. Confidence: high.

Looks for files under /etc and in cron directories that any user can modify.

**Why it matters:** A world-writable config or cron file lets any local user change how a root-owned service behaves, often leading straight to root.

- **/etc:** World-writable files found
  - `0666 /etc/passwd`
  - `0666 /etc/app/db.conf`
  - `0666 /etc/crontab`
  - `0666 /etc/ssh/sshd_config`
  - `0666 /etc/systemd/system/shop-api.service`

**Fix:** For each listed file or directory: chmod o-w \<path\>. Then find out how it became writable (a deployment script or a careless chmod 777).

**Verify:** `find /etc -xdev \( -type f -perm -0002 \) -o \( -type d -perm -0002 ! -perm -1000 \)  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Unexpected SUID or SGID Programs (HOST-FS-004)

Category: access. Effort: medium. Confidence: high.

Looks for set-user-ID or set-group-ID programs in places where packages do not normally install them.

**Why it matters:** A SUID program runs with its owner's rights (usually root). One placed in /tmp, /home, /var/www or /opt is a common persistence and privilege-escalation trick.

- **filesystem:** SUID/SGID programs outside standard system paths
  - `4755 /home/deploy/.cache/sh`

**Fix:** Confirm whether each program is needed. Remove the bit with chmod u-s,g-s \<file\>, or delete the file if you did not put it there and investigate how it arrived.

**Verify:** `find /tmp /home /var/www /opt /usr/local -xdev -perm /6000 -type f  should print nothing unexpected.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 CM-7, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Cron Files Writable by Non-Root Users (HOST-FS-005)

Category: access. Effort: low. Confidence: high.

Checks that system cron files and directories are owned by root and not writable by others.

**Why it matters:** Cron runs these jobs as root. Anyone who can edit them can run any command as root at the next scheduled minute.

- **cron:** Cron paths can be modified by non-root users
  - `0666 uid=0 /etc/crontab`

**Fix:** chown root:root \<path\> && chmod go-w \<path\>  for each listed path (600 for /etc/crontab is a common hardening choice).

**Verify:** `stat -c '%a %U %n' /etc/crontab /etc/cron.*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Private TLS Keys Readable by Others (HOST-FS-007)

Category: crypto. Effort: low. Confidence: high.

Checks that TLS private keys under the standard key directories are not readable by other users.

**Why it matters:** A stolen private key lets an attacker impersonate your websites and, with older key exchange, decrypt recorded traffic.

- **/etc/ssl/private/\*:** 1 file(s) are readable or writable by others
  - `0644 /etc/ssl/private/shop.key`

**Fix:** chown root:root \<key\> && chmod 600 \<key\>  (or 640 with a dedicated group the web server runs in). Rotate any key that was exposed.

**Verify:** `stat -c '%a %U:%G %n' /etc/ssl/private/* /etc/pki/tls/private/* /etc/letsencrypt/archive/*/privkey*.pem`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Account Database Files or Their Backups Not Owned and Locked by Root (HOST-FS-022)

Category: access. Effort: low. Confidence: high.

Checks the owner and permissions of /etc/passwd, /etc/group, /etc/shadow, /etc/gshadow and their dash-suffixed backup copies.

**Why it matters:** The tools that edit accounts leave a backup copy (passwd-, shadow-) next to the live file. A readable shadow- backup leaks the same password hashes as shadow itself, and a file not owned by root can be rewritten by whoever owns it. HOST-FS-001 checks the mode of the live files; this check adds ownership and the backups.

- **/etc/passwd:** 3 account file(s) have an unsafe owner or mode
  - `0666 /etc/passwd (writable by group or others)`
  - `0644 /etc/shadow (accessible by others)`
  - `0644 /etc/shadow- (accessible by others)`

**Fix:** chown root:root /etc/passwd /etc/passwd- /etc/group /etc/group- && chmod 644 /etc/passwd /etc/group && chmod 600 /etc/passwd- /etc/group- ; chown root:shadow /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow- && chmod 640 /etc/shadow /etc/gshadow && chmod 600 /etc/shadow- /etc/gshadow-  (use root:root and mode 000 on RHEL family systems).

**Verify:** `stat -c '%a %U:%G %n' /etc/passwd* /etc/group* /etc/shadow* /etc/gshadow*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-522; NIST SP 800-53 AC-3, AC-6, IA-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[HIGH\] Kubernetes Data Directory Accessible to Others (HOST-K8S-002)

Category: access. Effort: low. Confidence: high.

Checks that the etcd data directory is accessible only by its owner.

**Why it matters:** etcd stores every Kubernetes object, including Secrets, usually unencrypted at rest. Read access to its data directory exposes all cluster credentials.

- **/var/lib/etcd:** etcd data directory is accessible by group or others
  - `mode 0755`

**Fix:** chmod 700 /var/lib/etcd && chown etcd:etcd /var/lib/etcd  (or root:root for kubeadm static pods). Enable encryption at rest for Secrets.

**Verify:** `stat -c '%a %U:%G %n' /var/lib/etcd`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, SC-28

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[HIGH\] Kubernetes Credential Files Readable by Others (HOST-K8S-003)

Category: identity. Effort: low. Confidence: high.

Checks that kubeconfig files and Kubernetes PKI private keys are not readable by other users.

**Why it matters:** admin.conf and the cluster CA key grant full control of the cluster; any local user who can read them becomes a cluster administrator.

- **/etc/kubernetes/admin.conf:** 1 file(s) are readable or writable by group or others
  - `0644 /etc/kubernetes/admin.conf`

**Fix:** chmod 600 /etc/kubernetes/\*.conf /etc/kubernetes/pki/\*.key /etc/kubernetes/pki/etcd/\*.key && chown root:root on each.

**Verify:** `stat -c '%a %n' /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[HIGH\] Operating System Past End of Security Support (HOST-OS-001)

Category: patch. Effort: high. Confidence: high.

Checks whether the Linux distribution release still receives free security updates.

**Why it matters:** Once a release reaches end of life, newly discovered vulnerabilities are never fixed. Every later CVE in the kernel, OpenSSL or the web server stays open forever.

- **Ubuntu 18.04.6 LTS:** Security support ended on 2023-05-31

**Fix:** Plan an upgrade to a supported release (for example do-release-upgrade on Ubuntu, or a fresh install and migration). Paid extended support (Ubuntu Pro, RHEL ELS) is an alternative while migrating.

**Verify:** `cat /etc/os-release  and compare with the vendor lifecycle page.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SA-22, SI-2

**Learn:** F3: Vulnerabilities, CVE, CWE and CVSS, https://openvapt.com/academy/f3.html (offline: `openvapt learn F3`)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Security Updates Waiting to Be Installed (HOST-PKG-001)

Category: patch. Effort: medium. Confidence: high.

Checks the package manager for published security updates that are not installed yet.

**Why it matters:** Most real-world compromises use vulnerabilities for which a fix was already available. Pending security updates are known, public weaknesses.

- **apt:** 2 security updates pending
  - `openssl`
  - `sudo`

**Fix:** Debian/Ubuntu: apt-get update && apt-get upgrade . RHEL family: dnf upgrade --security . Consider unattended-upgrades or dnf-automatic for security patches.

**Verify:** `Re-run openvapt; this check should pass.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** F3: Vulnerabilities, CVE, CWE and CVSS, https://openvapt.com/academy/f3.html (offline: `openvapt learn F3`)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[HIGH\] Repository Signature Checks Disabled (HOST-PKG-002)

Category: patch. Effort: low. Confidence: high.

Checks that the package manager verifies repository signatures, so a tampered mirror or intercepted download cannot install code as root.

**Why it matters:** Package installation runs as root. apt and dnf verify a signature over the repository index before trusting any package in it; an entry marked trusted=yes, AllowUnauthenticated or gpgcheck=0 switches that off, and a mirror operator or anyone on the network path can then deliver a modified package at the next update.

- **package repositories:** Repository signature verification is disabled
  - `/etc/apt/sources.list.d/lab-ppa.list: ppa.launchpadcontent.net marked trusted=yes (signature check off)`

**Fix:** Remove trusted=yes (or Trusted: yes) from the listed source, install the repository's signing key under /etc/apt/keyrings and reference it with signed-by=; remove AllowUnauthenticated and AllowInsecureRepositories settings. On dnf and yum set gpgcheck=1 (and repo\_gpgcheck=1 where supported) in each listed repository and import its key with rpm --import.

**Verify:** `grep -r 'trusted=yes' /etc/apt/ ; grep -r 'gpgcheck=0' /etc/yum.repos.d/ /etc/dnf/dnf.conf 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-494, CWE-347; OWASP Top 10 A08:2021; NIST SP 800-53 SI-7, CM-14, SR-3, SR-11

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[HIGH\] Secrets in World-Readable Files (HOST-SEC-001)

Category: exposure. Effort: medium. Confidence: high.

Looks inside world-readable configuration files under /etc and the web roots for private keys, cloud access keys and password assignments.

**Why it matters:** A password or key in a file every local account can read is available to every website, service account and compromised process on the machine. Secrets found this way are the usual first step from a small web compromise to the database, the cloud account or root.

- **filesystem:** 3 world-readable file(s) contain secrets
  - `/etc/app/db.conf (mode 0666): credential assignment`
  - `/etc/backup/backup-key.pem (mode 0644): private key`
  - `/var/www/shop/.env (mode 0644): AWS access key id, credential assignment`

**Fix:** chmod o-r each listed file (640 with the service's group, or 600). Move secrets into root-only files, systemd credentials or a secrets manager, and rotate every key or password that was readable.

**Verify:** `find /etc /var/www -type f -perm -0004 -name '.env*' -o -type f -perm -0004 -name '*.key'  then re-run this check.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-312, CWE-732, CWE-798; OWASP Top 10 A02:2021, A05:2021; NIST SP 800-53 IA-5, SC-28, AC-3

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[HIGH\] SSH Allows Direct Root Login (HOST-SSH-001)

Category: identity. Effort: low. Confidence: high.

Checks whether the SSH daemon accepts logins as root with a password.

**Why it matters:** Root is the one account name every attacker knows. Allowing it over SSH turns any password guess into full control of the server, and removes the audit trail of which person used root.

- **sshd:** Root may log in over SSH with a password
  - `permitrootlogin yes`

**Fix:** In /etc/ssh/sshd\_config set:  PermitRootLogin no  (or prohibit-password if key-based root login is truly required). Log in as a named user and use sudo. Then run: sshd -t && systemctl reload sshd

**Verify:** `sshd -T | grep -i permitrootlogin  should print 'permitrootlogin no'.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-6(2), IA-2

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

**Learn:** P23: Test Design for Security Checks, https://openvapt.com/academy/p23.html (offline: `openvapt learn P23`)

**Learn:** A2: Remediation and Verification, https://openvapt.com/academy/a2.html (offline: `openvapt learn A2`)

### \[HIGH\] SSH Host Private Keys Readable by Others (HOST-SSH-009)

Category: crypto. Effort: low. Confidence: high.

Checks that SSH host private keys are readable only by root.

**Why it matters:** Anyone who can read a host private key can impersonate the server and intercept SSH sessions from users who trust it.

- **/etc/ssh/ssh\_host\_\*\_key:** 1 file(s) are readable or writable by others
  - `0644 /etc/ssh/ssh_host_ed25519_key`

**Fix:** chown root:root /etc/ssh/ssh\_host\_\*\_key && chmod 600 /etc/ssh/ssh\_host\_\*\_key  (0640 with group ssh\_keys is acceptable on RHEL family).

**Verify:** `stat -c '%a %U:%G %n' /etc/ssh/ssh_host_*_key`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[HIGH\] SSH Server Configuration Writable by Non-Root Users (HOST-SSH-010)

Category: access. Effort: low. Confidence: high.

Checks that the SSH daemon configuration cannot be modified by group or other users.

**Why it matters:** Whoever can edit sshd\_config can enable root login, add their own keys file location or weaken authentication.

- **/etc/ssh/sshd\_config:** 1 file(s) are writable by group or others
  - `0666 /etc/ssh/sshd_config`

**Fix:** chown root:root /etc/ssh/sshd\_config /etc/ssh/sshd\_config.d/\*.conf && chmod 600 /etc/ssh/sshd\_config /etc/ssh/sshd\_config.d/\*.conf

**Verify:** `stat -c '%a %U %n' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[HIGH\] Service Unit Files Writable by Non-Root Users (HOST-SYSD-003)

Category: access. Effort: low. Confidence: high.

Checks whether any systemd service unit or drop-in file can be changed by a user other than root.

**Why it matters:** systemd starts services as root by default. Whoever can edit a unit file can change ExecStart= or User= and have their own program run as root at the next restart or reboot, a well-known privilege escalation and persistence technique.

- **systemd units:** Service unit files can be changed by non-root users
  - `/etc/systemd/system/shop-api.service mode 0666 uid 0`

**Fix:** chown root:root \<file\> && chmod 644 \<file\> for each listed file, then review its contents for changes you did not make (ExecStart=, User=, ExecStartPre=) and run systemctl daemon-reload.

**Verify:** `stat -c '%U %a %n' /etc/systemd/system/*.service  shows root and 644 (or stricter).`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, CM-5

**Learn:** P11: Service Hardening With systemd, https://openvapt.com/academy/p11.html (offline: `openvapt learn P11`)

### \[HIGH\] Service Programs in Writable Locations (HOST-SYSD-004)

Category: access. Effort: low. Confidence: high.

Checks whether a service starts a program from a temporary folder, or a program file that other users can replace.

**Why it matters:** If the program a root service starts lives in /tmp or is writable by other users, any local user (or a compromised web application) can replace it and gain root the next time the service restarts.

- **systemd units:** Services start programs that other users can replace
  - `shop-worker.service: starts /tmp/shop-worker from a temporary folder`

**Fix:** Install the program in a root-owned location (/usr/local/bin or /opt/\<app\>), chown root:root and chmod 755 it, point ExecStart= at it, then systemctl daemon-reload and restart the service.

**Verify:** `stat -c '%U %a %n' $(systemctl show -p ExecStart --value <unit> | awk '{print $2}' | sed 's/path=//;s/;//')`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-427; NIST SP 800-53 AC-3, CM-5

**Learn:** P11: Service Hardening With systemd, https://openvapt.com/academy/p11.html (offline: `openvapt learn P11`)

### \[HIGH\] Extra Accounts With Root Privileges (UID 0) (HOST-USR-001)

Category: identity. Effort: medium. Confidence: high.

Checks /etc/passwd for accounts other than root that have user id 0.

**Why it matters:** Any account with UID 0 is root, whatever its name. Extra UID 0 accounts are a classic backdoor and bypass sudo logging.

- **/etc/passwd:** Accounts other than root have UID 0
  - `toor:x:0:0`

**Fix:** Investigate who created each account. Remove it (userdel \<name\>) or give it a normal UID (usermod -u \<new-uid\> \<name\>). Grant admin rights through sudo instead.

**Verify:** `awk -F: '$3==0' /etc/passwd  should print only the root line.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-266; NIST SP 800-53 AC-2, AC-6

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[HIGH\] Accounts Without a Password (HOST-USR-002)

Category: identity. Effort: low. Confidence: high.

Checks /etc/shadow for accounts whose password field is empty.

**Why it matters:** An empty password field means the account can log in with no password at all on any service that allows password logins. The score uses a local attack vector: any local user can switch to the account (su) without a password, while the SSH daemon refuses empty passwords unless PermitEmptyPasswords is yes. If HOST-SSH-003 also fails, the same account is open from the network and should be treated as Critical.

- **/etc/shadow:** Accounts have an empty password
  - `deploy`

**Fix:** For each listed user either set a password (passwd \<user\>) or lock the account (passwd -l \<user\>).

**Verify:** `awk -F: '$2==""' /etc/shadow  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-258, CWE-521; NIST SP 800-53 IA-5

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[HIGH\] Sudo Rules Without Password (NOPASSWD) (HOST-USR-003)

Category: identity. Effort: low. Confidence: high.

Checks sudoers files for rules that grant root without asking for a password.

**Why it matters:** NOPASSWD means anyone who takes over that user (a stolen SSH key, a hijacked session, a web shell running as that user) becomes root instantly.

- **/etc/sudoers:** Sudo rules grant root without a password
  - `/etc/sudoers: deploy ALL=(ALL) NOPASSWD: ALL`

**Fix:** Edit with visudo and remove NOPASSWD, or restrict it to one specific, harmless command for automation accounts.

**Verify:** `grep -r NOPASSWD /etc/sudoers /etc/sudoers.d  should print nothing, or only narrowly scoped commands.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306; NIST SP 800-53 AC-6, IA-11

**Learn:** F1: Security Basics, https://openvapt.com/academy/f1.html (offline: `openvapt learn F1`)

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[HIGH\] Insecure Trust Files in Home Directories (HOST-USR-007)

Category: identity. Effort: low. Confidence: high.

Looks for .rhosts, .shosts and .netrc files in user home directories.

**Why it matters:** .rhosts and .shosts grant password-less trust to other hosts. .netrc files usually hold plain-text passwords for FTP or other services.

- **home directories:** Insecure trust or credential files found
  - `/home/deploy/.netrc`

**Fix:** Delete .rhosts and .shosts files. Replace .netrc credentials with a secret manager or key-based authentication, then delete the file and rotate the passwords it contained.

**Verify:** `Re-run this check; no files should be listed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-256, CWE-290; NIST SP 800-53 IA-5, CM-6

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[HIGH\] AI Model Server or Dashboard Reachable From the Network (NET-AI-001)

Category: network. Effort: low. Confidence: high.

Checks whether a self-hosted AI model server, AI web interface, AI cluster dashboard or vector database listens on a network-reachable address.

**Why it matters:** Many self-hosted AI tools have no login enabled by default. Reached from the network, anyone can use the models and the computing power, read stored documents and embeddings, pull or delete models, and on some dashboards submit jobs that run code.

- **sockets:** AI services are network-reachable
  - `tcp6 [::]:11434 Ollama model API, bound to all interfaces`
  - `a web server listens on port 80 or 443 on this machine: bind the application to 127.0.0.1 and publish it through that proxy`

**Fix:** Bind each service to 127.0.0.1 (Ollama: OLLAMA\_HOST=127.0.0.1:11434 in its service environment; Ray: --dashboard-host=127.0.0.1; Gradio: server\_name="127.0.0.1"), and if others need it, publish it only through a reverse proxy with authentication and TLS.

**Verify:** `ss -tlnp  shows these ports on 127.0.0.1 or ::1 only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1327; OWASP Top 10 A05:2021, A07:2021; NIST SP 800-53 SC-7, CM-7, AC-3

**Learn:** P24: Securing Self-Hosted AI Services, https://openvapt.com/academy/p24.html (offline: `openvapt learn P24`)

### \[HIGH\] Jupyter Configuration Weakens Authentication (NET-AI-002)

Category: identity. Effort: low. Confidence: high.

Checks whether a Jupyter configuration blanks the login token without setting a password, or turns off cross-site request protection.

**Why it matters:** Jupyter runs any code typed into it with the rights of the account that started it. With the token blanked and no password, anyone who can reach the port, or any local user, gets that account. With XSRF checks off, a web page a user visits can send it commands.

- **/etc/jupyter/jupyter\_server\_config.py:** Jupyter configuration weakens authentication or request forgery protection
  - `token is an empty string and no login password is set, so anyone who reaches the server can run code`
  - `disable_check_xsrf = True: other websites can send requests to the server through a visitor's browser`

**Fix:** Remove the empty token line so Jupyter generates a random token, or set a password with  jupyter server password . Remove disable\_check\_xsrf = True. Bind to 127.0.0.1 and reach it through an SSH tunnel.

**Verify:** `grep -nE 'token|password|disable_check_xsrf' <config file>  shows no empty token and no disabled XSRF check, and the server log prints a URL with a token at start-up.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-352; OWASP Top 10 A07:2021, A05:2021; NIST SP 800-53 IA-2, AC-3

**Learn:** P24: Securing Self-Hosted AI Services, https://openvapt.com/academy/p24.html (offline: `openvapt learn P24`)

### \[HIGH\] Debugger or Application Control Ports Exposed (NET-APP-001)

Category: network. Effort: low. Confidence: high.

Checks whether the Node.js inspector, Tomcat AJP or shutdown port, PHP-FPM FastCGI or Jupyter listen on network-reachable addresses.

**Why it matters:** These ports are not web pages: the Node.js inspector and Jupyter run code for whoever connects, Tomcat AJP has been abused for file read and code execution (Ghostcat), the shutdown port stops the server, and PHP-FPM executes scripts handed to it. None of them is designed to face a network.

- **sockets:** Debugger or application control ports are network-reachable
  - `tcp 0.0.0.0:8009 Tomcat AJP connector, bound to all interfaces`
  - `tcp 0.0.0.0:9229 Node.js inspector (debugger), bound to all interfaces`
  - `a web server listens on port 80 or 443 on this machine: bind the application to 127.0.0.1 and publish it through that proxy`

**Fix:** Bind each to 127.0.0.1 or a Unix socket (node --inspect=127.0.0.1:9229; Tomcat Connector address="127.0.0.1" or remove the AJP connector; PHP-FPM listen = /run/php/php-fpm.sock; jupyter --ip=127.0.0.1) and never expose a debugger.

**Verify:** `ss -tlnp  shows these ports on 127.0.0.1 or ::1 only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-1327; OWASP Top 10 A05:2021; NIST SP 800-53 SC-7, CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[HIGH\] Database or Cache Service Exposed to the Network (NET-DB-001)

Category: data. Effort: low. Confidence: high.

Checks whether databases, caches, search engines or the Docker API listen on a network-reachable address.

**Why it matters:** Exposed databases and caches (Redis, MongoDB, Elasticsearch, Memcached) are among the most common causes of mass data breaches and ransom wipes, often because they ship without authentication.

- **sockets:** Data services listen on network-reachable addresses
  - `tcp 0.0.0.0:3306 MySQL/MariaDB`
  - `tcp 0.0.0.0:6379 Redis`

**Fix:** Bind the service to 127.0.0.1 (MySQL: bind-address = 127.0.0.1; Redis: bind 127.0.0.1 ::1 and protected-mode yes; MongoDB: net.bindIp: 127.0.0.1) and restart it. Caution: confirm first that no application on another machine connects to it, or that application loses its database. If remote access is needed, allow only specific source IPs in the firewall and require TLS and authentication.

**Verify:** `ss -tlnp | grep -E ':(3306|5432|6379|27017|9200|11211)'  should show only 127.0.0.1 or ::1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1327, CWE-306; OWASP Top 10 A05:2021; NIST SP 800-53 SC-7, CM-7

**Learn:** F1: Security Basics, https://openvapt.com/academy/f1.html (offline: `openvapt learn F1`)

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] MySQL or MariaDB Unsafe Network Options (NET-DB-005)

Category: data. Effort: low. Confidence: high.

MySQL or MariaDB enables local file loading, skips grants, or listens broadly with networking enabled.

**Why it matters:** These options can bypass authorization, expand file access, or expose the database to untrusted networks.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** MySQL or MariaDB enables local file loading, skips grants, or listens broadly with networking enabled.
  - `/etc/mysql/mysql.conf.d/mysqld.cnf: local_infile = ON`
  - `/etc/mysql/mysql.conf.d/mysqld.cnf: skip-grant-tables =`
  - `/etc/mysql/mysql.conf.d/mysqld.cnf: skip-networking = OFF`
  - `/etc/mysql/mysql.conf.d/mysqld.cnf: bind-address = 0.0.0.0`

**Fix:** Disable local\_infile and skip-grant-tables. Bind to 127.0.0.1 or enable skip-networking when TCP access is unnecessary.

**Verify:** `Inspect SHOW VARIABLES through an approved administrator session and confirm the listening address locally.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] Memcached UDP or Broad Listen Address (NET-DB-006)

Category: data. Effort: low. Confidence: high.

Memcached enables UDP or listens on a non-loopback address.

**Why it matters:** Unauthenticated Memcached exposure can disclose cached data and UDP mode has been abused for amplification attacks.

- **/etc/memcached.conf:** Memcached enables UDP or listens on a non-loopback address.
  - `/etc/memcached.conf: -l 0.0.0.0`
  - `/etc/memcached.conf: -U 11211`

**Fix:** Set UDP\_PORT=0 or -U 0 and bind with -l 127.0.0.1. Use a firewall for any explicitly required clients.

**Verify:** `Confirm ss -ltnup shows Memcached only on loopback and no UDP port 11211.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-406, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 SC-5, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] Unsafe NFS Export Options (NET-DB-008)

Category: data. Effort: low. Confidence: high.

NFS exports are world-accessible or use no\_root\_squash or insecure.

**Why it matters:** These export options can grant untrusted clients filesystem access or preserve remote root privileges.

- **/etc/exports:** NFS exports are world-accessible or use no\_root\_squash or insecure.
  - `/srv/share *(rw,no_root_squash,insecure)`

**Fix:** Restrict every export to explicit client CIDRs, use root\_squash, remove insecure, and reload exports.

**Verify:** `Run exportfs -v during approved administration and confirm only intended networks and safe options appear.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-306; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] Unsafe Samba Guest or SMB1 Configuration (NET-DB-009)

Category: data. Effort: low. Confidence: high.

Samba permits guest access, maps unknown users to guest, or allows SMB1.

**Why it matters:** Guest shares weaken accountability, while SMB1 lacks modern protocol protections and has a long history of severe flaws.

- **/etc/samba/smb.conf:** Samba permits guest access, maps unknown users to guest, or allows SMB1.
  - `guest ok enabled`
  - `unknown users map to guest`
  - `SMB1 NT1 enabled`

**Fix:** Set guest ok = no, map to guest = Never, and server min protocol = SMB2\_02 or newer. Review each share ACL.

**Verify:** `Run testparm -s during approved administration and confirm the effective values.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-306; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] BIND Open Recursion (NET-DB-010)

Category: data. Effort: low. Confidence: high.

BIND enables recursion without restricting recursive clients.

**Why it matters:** An open recursive resolver can be abused for reflection attacks and permits outsiders to consume resolver resources.

- **/etc/bind/named.conf.options:** BIND enables recursion without restricting recursive clients.
  - `recursion yes without a client ACL`

**Fix:** Disable recursion on authoritative-only servers, or set allow-recursion and allow-query-cache to explicit trusted networks.

**Verify:** `Run named-checkconf and query recursion from an untrusted test network under an approved test plan.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-406; OWASP Top 10 A05:2021; NIST SP 800-53 SC-5, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] BIND Zone Transfers Allowed to Any (NET-DB-011)

Category: data. Effort: low. Confidence: high.

BIND permits zone transfers to any client.

**Why it matters:** Unrestricted transfers disclose the complete DNS namespace and provide attackers with a detailed target inventory.

- **/etc/bind/named.conf.options:** BIND permits zone transfers to any client.
  - `allow-transfer includes any`

**Fix:** Set allow-transfer to none or explicit secondary server addresses, preferably protected with TSIG.

**Verify:** `Run named-checkconf and verify AXFR is refused from an unauthorized test host.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] Postfix Open Relay Controls (NET-DB-012)

Category: data. Effort: low. Confidence: high.

Postfix relay restrictions are missing or mynetworks trusts overly broad ranges.

**Why it matters:** An open relay enables spam and phishing, damages domain reputation, and can expose the host to blocklisting.

- **/etc/postfix/main.cf:** Postfix relay restrictions are missing or mynetworks trusts overly broad ranges.
  - `reject_unauth_destination missing`
  - `mynetworks trusts all addresses`

**Fix:** Set smtpd\_relay\_restrictions = permit\_mynetworks, permit\_sasl\_authenticated, reject\_unauth\_destination and narrow mynetworks.

**Verify:** `Run postconf -n during approved administration and perform only an authorized relay test.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-441; OWASP Top 10 A05:2021; NIST SP 800-53 AC-3, CM-6, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] IPv6 Listeners Without IPv6 Firewall (NET-FW-002)

Category: network. Effort: medium. Confidence: high.

Network-reachable IPv6 listeners exist without a default-deny IPv6 firewall policy.

**Why it matters:** An IPv4-only firewall does not protect IPv6 services, creating an alternate path that bypasses intended filtering.

- **IPv6 firewall:** IPv6 listeners exist but ip6tables input policy is not deny

**Fix:** Apply equivalent default-deny filtering for IPv6 with nftables or ip6tables and explicitly allow required services. Caution: allow SSH and ICMPv6 neighbour discovery and router messages before applying the default deny, or remote IPv6 sessions and IPv6 connectivity are cut.

**Verify:** `Run ip6tables -S and nft list ruleset, then confirm the input chain has a default drop or reject policy.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-923; NIST SP 800-53 SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

### \[HIGH\] Firewall Allows Sensitive Services From Anywhere (NET-FW-003)

Category: network. Effort: low. Confidence: high.

Checks whether the host firewall explicitly opens a listening database, administrative or application server port to every address on the internet.

**Why it matters:** A default-deny firewall only protects what it does not allow. A single "allow 3306 from anywhere" rule, often added while debugging, makes a database or admin panel as exposed as if there were no firewall at all.

- **firewall:** Sensitive services are open to every address
  - `tcp 0.0.0.0:3306 (MySQL/MariaDB) allowed from anywhere by iptables rule: -A INPUT -p tcp -m tcp --dport 3306 -j ACCEPT`

**Fix:** Delete the rule or restrict it to the addresses that need it, for example: ufw delete allow 3306 && ufw allow from 203.0.113.10 to any port 3306 proto tcp . Better, bind the service to 127.0.0.1 and reach it through an SSH tunnel or VPN.

**Verify:** `ufw status numbered  or  nft list ruleset  shows no rule opening that port to Anywhere.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-923, CWE-1327; OWASP Top 10 A05:2021; NIST SP 800-53 SC-7, AC-4, CM-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P12: Firewall Policy Versus What Is Listening, https://openvapt.com/academy/p12.html (offline: `openvapt learn P12`)

### \[HIGH\] Cleartext Legacy Services Listening (NET-SVC-001)

Category: network. Effort: medium. Confidence: high.

Checks for Telnet, FTP, rsh/rlogin, TFTP, plain POP3/IMAP/LDAP, SNMP v1/v2c and rsync daemons on network-reachable addresses.

**Why it matters:** These protocols send passwords and data unencrypted, so anyone on the path can read or alter them. Most also have weak or no authentication.

- **sockets:** Cleartext services are network-reachable
  - `tcp 0.0.0.0:21 FTP`
  - `udp 0.0.0.0:161 SNMP (v1/v2c community strings)`

**Fix:** Replace with encrypted equivalents: SSH/SFTP instead of Telnet/FTP/rsh, IMAPS (993)/POP3S (995), LDAPS (636), SNMPv3. Disable the old service: systemctl disable --now \<unit\>.

**Verify:** `ss -tulpn  should not show ports 21, 23, 69, 110, 143, 161, 389, 512-514 or 873 on public addresses.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; NIST SP 800-53 SC-8, CM-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

### \[HIGH\] Network Time Service Amplification Risk (NET-SVC-003)

Category: network. Effort: low. Confidence: high.

NTP or chrony serves broad clients or permits legacy monitoring commands that can amplify traffic.

**Why it matters:** Internet-reachable time services with broad client access or legacy mode 6 or 7 controls can be abused for reflection and amplification.

- **/etc/ntp.conf:** Time service access controls allow amplification-prone queries

**Fix:** Bind the time daemon to required interfaces, restrict clients to explicit networks, disable monitoring, and use noquery or equivalent controls.

**Verify:** `Confirm UDP port 123 is filtered from untrusted networks and review the daemon's effective access restrictions.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-406; NIST SP 800-53 SC-5, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[HIGH\] Apache Allows TLS 1.0 or 1.1 (WEB-APA-005)

Category: crypto. Effort: low. Confidence: high.

SSLProtocol permits SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server (the default  all -SSLv3  allows TLS 1.0 and 1.1).

**Why it matters:** TLS 1.0 and 1.1 are deprecated by RFC 8996 and lack the protections of TLS 1.2 and 1.3. Apache's default still allows them, so an explicit SSLProtocol is required. The directive is not the last word: mod\_ssl uses the system OpenSSL, and when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever the directive lists. In that case the check reports Pass and names the floor, because rating a server High for a line that has no effect would contradict the live handshake checks (WEB-TLS). The directive is still worth tidying so the configuration says what it does.

- **/etc/apache2/apache2.conf:** SSLProtocol permits SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server (the default  all -SSLv3  allows TLS 1.0 and 1.1).
  - `/etc/apache2/mods-enabled/ssl.conf: SSLProtocol all -SSLv2 (allows sslv3, tlsv1, tlsv1.1)`

**Fix:** Set  SSLProtocol -all +TLSv1.2 +TLSv1.3  (Mozilla intermediate) in mods-enabled/ssl.conf or ssl.conf, then apachectl configtest and reload.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -servername <site> -tls1_1  should fail to connect.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326, CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.3; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[HIGH\] Apache Cipher Suite Includes Weak Suites (WEB-APA-006)

Category: crypto. Effort: low. Confidence: high.

SSLCipherSuite includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or SSLHonorCipherOrder forces a legacy order.

**Why it matters:** Weak suites allow recorded traffic to be decrypted or sessions to be hijacked. The Mozilla guidelines give a short explicit list that modern clients all support.

- **/etc/apache2/apache2.conf:** SSLCipherSuite includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or SSLHonorCipherOrder forces a legacy order.
  - `/etc/apache2/mods-enabled/ssl.conf: weak tokens all, rc4-sha`
  - `SSLHonorCipherOrder on (Mozilla recommends off for modern and intermediate profiles)`

**Fix:** Use the Mozilla intermediate list for SSLCipherSuite and set  SSLHonorCipherOrder off , as the Mozilla SSL Configuration Generator produces for Apache.

**Verify:** `Run the nmap ssl-enum-ciphers script against 127.0.0.1 port 443 under your own test plan; it should list only grade A suites.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.2; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[HIGH\] Laravel Debug Mode On or Application Key Missing (WEB-APP-001)

Category: exposure. Effort: low. Confidence: high.

Checks Laravel .env files for APP\_DEBUG=true or an empty APP\_KEY.

**Why it matters:** With APP\_DEBUG on, Laravel's error page prints the environment, including database and mail passwords, to any visitor who triggers an error. APP\_KEY encrypts sessions and cookies; with it empty or default the application refuses to run or signs nothing.

- **/srv/shop-laravel/.env:** Laravel runs in debug mode or without an application key
  - `APP_DEBUG is true`
  - `APP_KEY is empty`

**Fix:** Set APP\_DEBUG=false and APP\_ENV=production in .env, run php artisan key:generate if APP\_KEY is empty, then php artisan config:cache. Keep .env outside the web root (Laravel's public/ folder) and readable only by the application user.

**Verify:** `grep -E '^APP_(DEBUG|KEY)=' .env  shows APP_DEBUG=false and a base64 key.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-209, CWE-321; OWASP Top 10 A05:2021; ASVS 14.3.2, 2.10.4; WSTG-CONF-02, WSTG-ERRH-01; NIST SP 800-53 CM-6, SI-11, IA-5

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Symfony Running in the Development Environment (WEB-APP-003)

Category: exposure. Effort: low. Confidence: high.

Checks Symfony .env files for APP\_ENV=dev or APP\_DEBUG=1.

**Why it matters:** The dev environment enables the web profiler and debug toolbar, which show requests, queries, configuration, the container and sometimes credentials to any visitor, and it skips the caches that production relies on.

- **/srv/shop-crm/.env:** Symfony runs in the development environment
  - `APP_ENV is dev`
  - `APP_DEBUG is on`

**Fix:** Set APP\_ENV=prod and APP\_DEBUG=0 in .env (or .env.local), run composer dump-env prod, clear the cache (php bin/console cache:clear) and remove the web profiler bundle from production.

**Verify:** `php bin/console about  shows Environment prod and Debug false.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-215; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-CONF-02, WSTG-ERRH-01; NIST SP 800-53 CM-6, SI-11

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Rails Master Key or Secrets File Exposed (WEB-APP-004)

Category: access. Effort: low. Confidence: high.

Checks Rails config/master.key, credentials key files and secrets.yml for world-read permissions or a location inside a web root.

**Why it matters:** master.key decrypts credentials.yml.enc, which holds every production secret, and secrets.yml holds the secret\_key\_base that signs sessions. Anyone who reads them can forge sessions and read the database password. Both are meant to stay out of version control and out of any public folder.

- **/srv/shop-rails/config/master.key:** Rails key or secrets file is exposed
  - `mode 0644 is readable by all users`

**Fix:** chmod 600 config/master.key config/credentials/\*.key; keep them out of git (.gitignore) and out of public/; load the key from RAILS\_MASTER\_KEY in the environment on servers. Rotate the credentials if the key was ever exposed.

**Verify:** `stat -c '%a %n' config/master.key  shows 600, and git ls-files config/master.key  prints nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-312, CWE-321; OWASP Top 10 A02:2021, A05:2021; ASVS 2.10.4, 14.1.3; WSTG-CONF-09; NIST SP 800-53 AC-3, SC-28, IA-5

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Spring Boot Actuator Endpoints Fully Exposed (WEB-APP-006)

Category: exposure. Effort: low. Confidence: high.

Checks Spring Boot application properties for management.endpoints.web.exposure.include=\* and probes the local site for an open /actuator/env.

**Why it matters:** Actuator's env, configprops, heapdump and threaddump endpoints print environment variables, configuration (including credentials) and process memory. Exposing them all over HTTP without authentication is one of the most common Java application leaks.

- **/opt/shop-api/application.properties:** Spring Boot actuator endpoints are fully exposed
  - `management.endpoints.web.exposure.include exposes sensitive actuator endpoints on the application port`

**Fix:** Set management.endpoints.web.exposure.include=health,info (and exclude env,heapdump,configprops), move management.server.port to a port that is not published, and require authentication for every actuator endpoint with Spring Security.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/actuator/env  prints 401, 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-497; OWASP Top 10 A05:2021, A01:2021; ASVS 14.3.2, 4.3.2; WSTG-CONF-02, WSTG-CONF-05; NIST SP 800-53 CM-7, AC-3, SI-11

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] CMS Configuration Readable by All Users (WEB-CMS-004)

Category: access. Effort: low. Confidence: high.

Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php can be read by every local user.

**Why it matters:** These files hold the database password and the keys that sign logins. If every account on the machine can read them, any other site, user or compromised process on the server can take over the CMS and its data.

- **/var/www/shop/wp-config.php:** CMS configuration is readable by all users
  - `WordPress configuration has mode 0666 (readable by all users)`
- **/var/www/shop/configuration.php:** CMS configuration is readable by all users
  - `Joomla configuration has mode 0644 (readable by all users)`
- **/var/www/shop/sites/default/settings.php:** CMS configuration is readable by all users
  - `Drupal configuration has mode 0664 (readable by all users)`

**Fix:** chmod 640 \<file\> and chown it to the site owner with the web server's group (for example chown deploy:www-data wp-config.php); on PHP-FPM pools that run as the site user, 600 or 400 also works. Rotate the database password if other people have shell access.

**Verify:** `stat -c '%a %U:%G %n' <file>  shows 640, 600 or 400.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-260; OWASP Top 10 A05:2021; WSTG-CONF-09; NIST SP 800-53 AC-3, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[HIGH\] CMS Configuration Writable by Other Users (WEB-CMS-005)

Category: access. Effort: low. Confidence: high.

Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php is group- or world-writable.

**Why it matters:** These files are PHP code that runs on every request. Whoever can write them can add a backdoor that runs as the web server, which is how many shared-hosting compromises spread from one site to the next.

- **/var/www/shop/wp-config.php:** CMS configuration is writable by other users
  - `WordPress configuration has mode 0666 (writable by group or others)`
- **/var/www/shop/sites/default/settings.php:** CMS configuration is writable by other users
  - `Drupal configuration has mode 0664 (writable by group or others)`

**Fix:** chmod 640 (or 440) \<file\>. Drupal also expects sites/default to be 555 after installation. Review the file for code you did not add.

**Verify:** `stat -c '%a %n' <file>  shows no write bit for group or others.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; OWASP Top 10 A05:2021; WSTG-CONF-09; NIST SP 800-53 AC-3, CM-5

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[HIGH\] Drupal Core Past End of Life (WEB-CMS-012)

Category: patch. Effort: high. Confidence: high.

Reads the installed Drupal core version from disk and compares its major release with the Drupal security coverage dates.

**Why it matters:** Drupal 7, 8 and 9 no longer receive security advisories from the Drupal Security Team. Drupalgeddon (CVE-2014-3704) and Drupalgeddon 2 (CVE-2018-7600) showed how quickly unpatched Drupal sites are taken over at scale.

- **/var/www/shop:** Drupal 8 reached end of life on 2020-11-02

**Fix:** Upgrade to a supported Drupal major with composer (drupal/core-recommended), run drush updatedb and test the site; for Drupal 7, migrate with the Migrate module or commission a rebuild.

**Verify:** `drush status --field=drupal-version  or  grep VERSION core/lib/Drupal.php`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2, SA-22

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Joomla Core Past End of Life (WEB-CMS-014)

Category: patch. Effort: high. Confidence: high.

Reads the installed Joomla version from administrator/manifests/files/joomla.xml on disk and compares it with the Joomla support dates.

**Why it matters:** Joomla 3 left security support in August 2023 and Joomla 4 in October 2025. Sites on those series receive no fixes for new core vulnerabilities, while the exact version remains easy to fingerprint.

- **/var/www/shop:** Joomla 3.9 left security support on 2023-08-17

**Fix:** Update to the current Joomla series through the Joomla Update component (back up first, check extension compatibility, then update), and keep the one-click updater enabled.

**Verify:** `grep '<version>' administrator/manifests/files/joomla.xml`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2, SA-22

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Drupal update.php Open Without Authentication (WEB-CMS-019)

Category: config. Effort: low. Confidence: high.

Checks Drupal settings.php for $settings\['update\_free\_access'\] = TRUE.

**Why it matters:** update\_free\_access lets anyone run update.php, which executes database update hooks as the site. It exists for one-off maintenance and Drupal's own status report warns when it is left on.

- **/var/www/shop/sites/intranet/settings.php:** Drupal update.php runs without authentication
  - `$settings['update_free_access'] is TRUE`

**Fix:** Set $settings\['update\_free\_access'\] = FALSE; in settings.php (the default) and run updates as user 1 or with drush updatedb.

**Verify:** `grep update_free_access sites/default/settings.php  shows FALSE.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306; OWASP Top 10 A01:2021, A05:2021; ASVS 4.3.2; WSTG-CONF-05; NIST SP 800-53 AC-3, CM-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[HIGH\] Vulnerable Web Application Libraries (WEB-CVE-001)

Category: patch. Effort: medium. Confidence: high.

Matches local web library versions against offline OSV advisories; WordPress core is inventoried and reported Incomplete because OSV publishes no WordPress feed.

**Why it matters:** Unpatched web libraries can expose users and server data to known vulnerabilities.

- **jquery:** jquery 3.4.1: LAB-CVE-2020-11022 (CVE-2020-11022), severity high
  - `matcher: range`
  - `source: /var/www/shop/package-lock.json`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`
- **rack:** rack 2.2.3: LAB-CVE-2022-30123 (CVE-2022-30123), severity critical
  - `matcher: range`
  - `source: /var/www/shop/admin/Gemfile.lock`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`
- **wordpress:** wordpress 5.2.1: LAB-CVE-2019-17671 (CVE-2019-17671), severity high
  - `matcher: range`
  - `source: /var/www/shop/wp-includes/version.php`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`
- **flask:** flask 2.0.1: LAB-CVE-2023-30861 (CVE-2023-30861), severity high
  - `matcher: range`
  - `source: /var/www/shop/api/requirements.txt`
  - `Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.`

**Fix:** Update the affected library or WordPress core and deploy a refreshed lockfile or asset. For a source build, run 'make vulndb' to embed a generated snapshot.

**Verify:** `Re-run OpenVAPT and verify that the vulnerable version is no longer deployed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2, RA-5

**Learn:** P10: Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities, https://openvapt.com/academy/p10.html (offline: `openvapt learn P10`)

**Learn:** A7: Security Testing in Agile Teams and CI, https://openvapt.com/academy/a7.html (offline: `openvapt learn A7`)

### \[HIGH\] Secrets or Backups Stored Inside a Web Root (WEB-DOC-001)

Category: exposure. Effort: low. Confidence: high.

Looks on disk inside each web document root for environment files, database dumps, backups, archives and private keys.

**Why it matters:** Anything inside a document root may be downloadable, now or after a configuration change. Dumps, backups and keys there are a leading cause of data breaches; finding them on disk catches files the URL checks cannot guess.

- **/var/www/shop:** 2 sensitive file(s) inside the web root
  - `/var/www/shop/backup.sql (database dump)`
  - `/var/www/shop/.env (environment secrets)`

**Fix:** Move each listed file outside the document root (for example to a 700 folder in the account's home), delete what is not needed, and rotate any secret or key that was exposed. Keep backups in a dedicated location outside every web root.

**Verify:** `Re-run this check; no files should be listed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-530, CWE-538, CWE-312; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 SC-28, CM-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Web Root Files Writable by Other Users (WEB-DOC-002)

Category: access. Effort: low. Confidence: high.

Looks for world-writable files inside web document roots.

**Why it matters:** A world-writable PHP or HTML file lets any local user, or any other compromised site on the same server, plant a web shell or inject malware into pages served to visitors.

- **/var/www/shop:** 2 world-writable file(s) in the web root
  - `0666 /var/www/shop/wp-config.php`
  - `0666 /var/www/shop/index.php`

**Fix:** chmod o-w \<file\> for each listed file (find \<docroot\> -type f -perm -0002 -exec chmod o-w {} +). Use 644 for files and 755 for folders, owned by the site account, and grant the web server write access only to upload folders.

**Verify:** `find <docroot> -type f -perm -0002  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; OWASP Top 10 A05:2021; WSTG-CONF-09; NIST SP 800-53 AC-3, AC-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Git Repository Exposed on the Website (WEB-EXP-001)

Category: exposure. Effort: low. Confidence: high.

Checks whether the .git folder can be downloaded from each site.

**Why it matters:** An exposed .git folder lets anyone rebuild the full source code and history, often including passwords, API keys and database credentials committed by mistake.

- **shop.example.test:** Exposed: /.git/HEAD (HTTP 200)
  - `/.git/HEAD (HTTP 200)`

**Fix:** Block dot-folders in the web server (Apache: RedirectMatch 404 /\\.git ; nginx: location \~ /\\.(?\!well-known) { deny all; }) and deploy without the .git folder in the web root. Rotate any secret that was ever committed.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/.git/HEAD  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-527, CWE-538; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Backup or Database Dump Files Downloadable (WEB-EXP-004)

Category: exposure. Effort: low. Confidence: high.

Checks for common backup archives and SQL dumps left in the web root.

**Why it matters:** Backups contain the whole site and often the whole database, including customer data and password hashes.

- **shop.example.test:** Exposed: /db.sql (HTTP 200)
  - `/db.sql (HTTP 200)`

**Fix:** Move backups outside the web root (for example /home/\<user\>/deploy-backups/ with mode 700), delete the exposed copies and treat their contents as leaked.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/backup.zip  should print 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-530; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Other Version Control Metadata Exposed (WEB-EXP-007)

Category: exposure. Effort: low. Confidence: high.

Checks for exposed Subversion, Mercurial and Bazaar metadata folders.

**Why it matters:** Like .git, these folders let anyone reconstruct source code and history, including secrets that were committed by mistake.

- **shop.example.test:** Exposed: /.svn/entries (HTTP 200)
  - `/.svn/entries (HTTP 200)`

**Fix:** Block dot-folders at the web server and deploy without version control metadata in the web root. Rotate any secret ever committed.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/.svn/entries  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-527; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Log Files Downloadable From the Website (WEB-EXP-008)

Category: exposure. Effort: low. Confidence: high.

Checks for application and debug logs published under the web root.

**Why it matters:** Logs expose file paths, SQL queries, user data, session identifiers and sometimes credentials or API keys.

- **shop.example.test:** Exposed: /wp-content/debug.log (HTTP 200)
  - `/wp-content/debug.log (HTTP 200)`

**Fix:** Move logs outside the web root, deny access to \*.log at the web server, disable WP\_DEBUG\_LOG in production, and review the exposed logs for leaked secrets.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/wp-content/debug.log  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-532, CWE-538; OWASP Top 10 A09:2021, A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Framework Debug Pages Exposed (WEB-EXP-012)

Category: exposure. Effort: low. Confidence: high.

Checks for development debug tools and verbose error pages left enabled in production.

**Why it matters:** Debug pages such as the Symfony profiler, Laravel's Whoops/Ignition pages or Django's debug 404 reveal source code, configuration, environment variables and sometimes allow code execution.

- **shop.example.test:** Exposed: /\_profiler/ (HTTP 200)
  - `/_profiler/ (HTTP 200)`

**Fix:** Disable debug mode in production (APP\_DEBUG=false, DEBUG = False, APP\_ENV=prod), remove profiler routes, and show generic error pages.

**Verify:** `Re-run this check; no debug pages should be detected.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-209; OWASP Top 10 A05:2021; ASVS 14.3.2, 7.4.1; WSTG-ERRH-01, WSTG-CONF-02; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] nginx Allows TLS 1.0 or 1.1 (WEB-NGX-003)

Category: crypto. Effort: low. Confidence: high.

ssl\_protocols lists SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server, so the allowed version depends on the nginx build.

**Why it matters:** TLS 1.0 and 1.1 are deprecated by RFC 8996 and lack the protections of TLS 1.2 and 1.3. Clients that negotiate them are exposed to downgrade and known cipher weaknesses. The directive is not the last word: nginx uses the system OpenSSL, and when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever the directive lists. In that case the check reports Pass and names the floor, because rating a server High for a line that has no effect would contradict the live handshake checks (WEB-TLS). The directive is still worth tidying so the configuration says what it does.

- **/etc/nginx/nginx.conf:** ssl\_protocols lists SSLv3, TLSv1 or TLSv1.1, or is not set on a TLS-enabled server, so the allowed version depends on the nginx build.
  - `/etc/nginx/nginx.conf: ssl_protocols TLSv1 TLSv1.1 TLSv1.2 (allows tlsv1, tlsv1.1)`

**Fix:** Set  ssl\_protocols TLSv1.2 TLSv1.3;  in the http block (the Mozilla intermediate profile), run nginx -t and reload.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -servername <site> -tls1_1  should fail to connect.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326, CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.3; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[HIGH\] nginx Cipher List Includes Weak Suites (WEB-NGX-004)

Category: crypto. Effort: low. Confidence: high.

ssl\_ciphers includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or ssl\_prefer\_server\_ciphers forces a legacy order.

**Why it matters:** Weak suites allow recorded traffic to be decrypted or sessions to be hijacked. Modern clients already prefer strong suites, so the Mozilla guidelines recommend a short explicit list and ssl\_prefer\_server\_ciphers off.

- **/etc/nginx/nginx.conf:** ssl\_ciphers includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or ssl\_prefer\_server\_ciphers forces a legacy order.
  - `/etc/nginx/nginx.conf: weak tokens all, rc4-sha, des-cbc3-sha`
  - `ssl_prefer_server_ciphers on (Mozilla recommends off for modern and intermediate profiles)`

**Fix:** Use the Mozilla intermediate list, for example  ssl\_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;  and  ssl\_prefer\_server\_ciphers off;

**Verify:** `Run the nmap ssl-enum-ciphers script against 127.0.0.1 port 443 under your own test plan; it should list only grade A suites.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.2; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[HIGH\] Node.js Version Past End of Life (WEB-NODE-001)

Category: patch. Effort: high. Confidence: high.

Checks whether the installed Node.js runtime is a release line that no longer receives security fixes.

**Why it matters:** After end of life the Node.js project publishes no more security releases, so every later vulnerability in the runtime, its bundled OpenSSL or its HTTP parser stays open for good.

- **node:** Node.js v18.19.1 reached end of life on 2025-04-30

**Fix:** Move to an Active or Maintenance LTS release (even-numbered majors), for example with the NodeSource or distribution packages, test the application, then restart its service.

**Verify:** `node --version  shows a supported LTS major.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2, SA-22

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[HIGH\] PHP Allows Remote File Inclusion (WEB-PHP-003)

Category: config. Effort: low. Confidence: high.

Checks whether include() and require() may load code from URLs.

**Why it matters:** With allow\_url\_include on, a single file-inclusion bug lets an attacker run their own PHP code from their own server (remote file inclusion).

- **php:** allow\_url\_include = 1

**Fix:** In php.ini set  allow\_url\_include = Off  (the default since PHP 5.2) and restart PHP.

**Verify:** `php -r "echo ini_get('allow_url_include');"  and check the FPM pool's php.ini or php_admin_value settings.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-98; OWASP Top 10 A03:2021, A05:2021; ASVS 12.3.3; NIST SP 800-53 CM-6, CM-7

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] PHP Version Past End of Security Support (WEB-PHP-004)

Category: patch. Effort: high. Confidence: high.

Checks whether the installed PHP branch still receives security fixes from the PHP project.

**Why it matters:** Unsupported PHP versions never get fixes for newly found vulnerabilities, some of which allow remote code execution.

- **php:** PHP 7.2 security support ended on 2020-11-30
  - `Check whether your distribution backports security fixes to this version`

**Fix:** Upgrade to a supported PHP branch (see php.net/supported-versions) and test the applications, or confirm your distribution still backports security fixes to this version.

**Verify:** `php -v`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2

**Learn:** F3: Vulnerabilities, CVE, CWE and CVSS, https://openvapt.com/academy/f3.html (offline: `openvapt learn F3`)

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[HIGH\] Django Debug Mode Enabled (WEB-PY-001)

Category: exposure. Effort: low. Confidence: high.

Checks Django settings files for DEBUG = True with no production override.

**Why it matters:** With DEBUG on, every error page shows source code, local variables, SQL queries and most settings, and Django keeps every SQL query in memory. Attackers trigger errors on purpose to read them.

- **/srv/shopadmin/shopadmin:** Django debug mode is on
  - `/srv/shopadmin/shopadmin/settings.py: DEBUG = True`

**Fix:** Set DEBUG = False in production, preferably from the environment (DEBUG = os.environ.get('DJANGO\_DEBUG') == '1'), set ALLOWED\_HOSTS, and restart the application. Run python manage.py check --deploy.

**Verify:** `python manage.py check --deploy  reports no DEBUG warning.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-215; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-ERRH-01; NIST SP 800-53 CM-6

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[HIGH\] Django Uses the Generated Development Secret Key (WEB-PY-003)

Category: crypto. Effort: low. Confidence: high.

Checks Django settings for a SECRET\_KEY that starts with django-insecure-, the marker of the key generated by startproject.

**Why it matters:** SECRET\_KEY signs sessions, password-reset tokens and other data. The generated key sits in source files that are usually copied into repositories and backups; anyone who has it can forge signed data and, with some session backends, take over accounts.

- **/srv/shopadmin/shopadmin:** Django uses the generated development SECRET\_KEY
  - `/srv/shopadmin/shopadmin/settings.py: uses the generated django-insecure SECRET_KEY`

**Fix:** Generate a new key (python -c 'from django.core.management.utils import get\_random\_secret\_key; print(get\_random\_secret\_key())'), load it from the environment or a root-readable file (SECRET\_KEY = os.environ\['DJANGO\_SECRET\_KEY'\]), and restart. Changing the key logs all users out.

**Verify:** `python manage.py check --deploy  reports no SECRET_KEY warning.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-798, CWE-321; OWASP Top 10 A02:2021, A05:2021; ASVS 2.10.4; NIST SP 800-53 IA-5, SC-12

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[HIGH\] Tomcat Manager Reachable Without Address Restriction (WEB-TOM-001)

Category: webapp. Effort: low. Confidence: high.

Checks whether the Tomcat Manager or Host Manager application is deployed without an address filter limiting it to localhost.

**Why it matters:** The Manager deploys WAR files, which means it runs any code it is given. Exposed to the network it is one stolen or guessed password away from full compromise, and it is a standard target of automated attacks.

- **/etc/tomcat9:** Tomcat Manager is deployed without an address restriction
  - `/usr/share/tomcat9-admin/manager is deployed with no RemoteAddrValve or RemoteCIDRValve restriction`

**Fix:** If you do not use them, delete the manager and host-manager folders (or apt remove tomcatN-admin). Otherwise keep the default valve in META-INF/context.xml: \<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1" /\> and reach the Manager through an SSH tunnel.

**Verify:** `grep -A2 RemoteAddrValve <webapps>/manager/META-INF/context.xml`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-923; OWASP Top 10 A01:2021, A05:2021; WSTG-CONF-05; NIST SP 800-53 AC-3, CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[MEDIUM\] Boot Loader Has No Password (HOST-BOOT-001)

Category: access. Effort: medium. Confidence: high.

Checks that GRUB is configured with a superuser and a hashed password so the boot entries cannot be edited at the console.

**Why it matters:** Without a boot loader password anyone at the console or virtual console can edit the kernel command line (for example add init=/bin/bash) and get a root shell without any credentials, bypassing every control on the running system.

- **/boot/grub:** No superuser or password directive in 1 GRUB file(s)

**Fix:** Run  grub-mkpasswd-pbkdf2  (grub2-setpassword on RHEL family), add  set superusers="admin"  and  password\_pbkdf2 admin \<hash\>  to /etc/grub.d/40\_custom (RHEL family writes /boot/grub2/user.cfg), then regenerate with  update-grub  or  grub2-mkconfig -o /boot/grub2/grub.cfg. Add  --unrestricted  to the normal menu entry so unattended reboots still work.

**Verify:** `grep -E 'set superusers|password_pbkdf2' /boot/grub/grub.cfg /boot/grub2/grub.cfg /boot/grub2/user.cfg /etc/grub.d/* 2>/dev/null | sed 's/grub.pbkdf2.*/<hash>/'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306; NIST SP 800-53 AC-3, AC-6, CM-6

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] UEFI Secure Boot Disabled (HOST-BOOT-002)

Category: config. Effort: high. Confidence: high.

Checks the firmware's SecureBoot variable to see whether the boot chain is verified.

**Why it matters:** Secure Boot makes the firmware refuse unsigned boot loaders and kernels, which blocks boot kits and unsigned kernels planted by an attacker with disk or console access. Without it, anything written to the boot partition runs at the next reboot.

- **/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c:** Secure Boot is disabled
  - `SecureBoot = 0`

**Fix:** Enable Secure Boot in the firmware set-up and boot a signed shim and kernel (every mainstream distribution ships them). Custom kernels and out-of-tree modules then need signing with a Machine Owner Key. Where the platform cannot do Secure Boot, record the exception. Caution: unsigned kernels and modules (for example DKMS drivers such as NVIDIA or ZFS) stop loading once it is on, so sign or replace them first and keep console access for the first boot.

**Verify:** `mokutil --sb-state  or  od -An -t u1 /sys/firmware/efi/efivars/SecureBoot-*  (the fifth byte is 1 when enabled).`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1326; NIST SP 800-53 SI-7, CM-6

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] Uncommon Filesystem Modules Not Disabled (HOST-BOOT-003)

Category: config. Effort: low. Confidence: high.

Checks that the cramfs, freevxfs, jffs2, hfs, hfsplus, udf and squashfs kernel modules are disabled in modprobe.d.

**Why it matters:** Each filesystem driver is kernel code that parses untrusted data. A local user who can mount removable media or a loop device gets the kernel to parse a crafted image with one of these rarely maintained drivers, and several have had memory-corruption bugs. Removing drivers nobody uses removes that attack surface.

- **/etc/modprobe.d:** Filesystem modules not disabled: cramfs, freevxfs, jffs2, hfs, hfsplus, udf, squashfs

**Fix:** Create /etc/modprobe.d/openvapt-filesystems.conf with, for each module:  install \<module\> /bin/false  and  blacklist \<module\>  then unload any loaded one with  modprobe -r \<module\>. Caution: first confirm that no listed module is in use (lsmod, findmnt); blocking a filesystem a mount or package format needs (for example squashfs for snap packages, or vfat for the EFI system partition) breaks those mounts at the next boot.

**Verify:** `for m in cramfs freevxfs jffs2 hfs hfsplus udf squashfs; do modprobe -n -v $m; lsmod | grep -w $m; done`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-6

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] USB Storage Driver Not Disabled (HOST-BOOT-004)

Category: config. Effort: low. Confidence: high.

Checks that the usb-storage kernel module is disabled in modprobe.d on servers that do not need removable media.

**Why it matters:** A USB stick plugged into a server is the simplest way to copy data out or bring malware in without touching the network. Disabling the driver makes the device invisible to the kernel.

- **/etc/modprobe.d:** usb-storage is not disabled in modprobe.d

**Fix:** Create /etc/modprobe.d/openvapt-usb.conf containing  install usb-storage /bin/false  and  blacklist usb-storage  then  modprobe -r usb-storage. Servers that need USB backups should record the exception instead.

**Verify:** `modprobe -n -v usb-storage  should print install /bin/false; lsmod | grep usb_storage  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 MP-7, CM-7

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] No File Integrity Monitoring (HOST-BOOT-006)

Category: logging. Effort: medium. Confidence: high.

Checks that a file integrity tool (AIDE, Tripwire, Samhain or equivalent) is installed and scheduled to run.

**Why it matters:** After a compromise the first question is which files changed. Without a baseline and a scheduled comparison, a replaced binary, an added SUID file or an edited PAM stack stays invisible for months.

- **integrity:** No file integrity tool found (AIDE, Tripwire, Samhain, osquery or OSSEC)

**Fix:** Install AIDE (apt install aide / dnf install aide), initialise the database with  aideinit  or  aide --init, move the new database into place, and enable the daily check (dailyaidecheck.timer on Debian family, or a cron.daily entry) that mails or logs the differences.

**Verify:** `aide --check  (or your tool's check command) runs and reports; systemctl list-timers | grep -i aide  or  ls /etc/cron.daily | grep -i aide`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; NIST SP 800-53 SI-7, CM-3

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] Docker Daemon Not Hardened (HOST-CTR-002)

Category: config. Effort: medium. Confidence: high.

Checks the Docker daemon defaults: no-new-privileges, inter-container communication, user namespace remapping and live restore.

**Why it matters:** Hardened daemon defaults limit what a compromised container can do: no-new-privileges blocks setuid escalation, disabling icc isolates containers, and user namespaces map container root to an unprivileged host user.

- **/etc/docker/daemon.json:** Docker daemon hardening options are missing
  - `no-new-privileges is not true`
  - `icc is not false`
  - `userns-remap is not set`
  - `live-restore is not true`

**Fix:** Add to /etc/docker/daemon.json:  "no-new-privileges": true, "icc": false, "userns-remap": "default", "live-restore": true  then restart Docker after testing your containers.

**Verify:** `cat /etc/docker/daemon.json ; docker info | grep -Ei 'security|userns'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-1188; NIST SP 800-53 CM-6, AC-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

### \[MEDIUM\] Docker Daemon Allows Insecure Registries or Debug Features (HOST-CTR-010)

Category: config. Effort: low. Confidence: high.

Checks daemon.json for insecure-registries, debug mode and experimental features.

**Why it matters:** An insecure registry is pulled over plain HTTP or with an unverified certificate, so anyone on the path can swap the image for a malicious one. Debug logging and experimental features widen the daemon's attack surface and leak internal detail into logs.

- **docker daemon:** Docker daemon allows insecure registries or debug features
  - `daemon.json insecure-registries: registry.example.test:5000`
  - `daemon.json debug is true`
  - `daemon.json experimental is true`

**Fix:** Remove insecure-registries (serve the registry over TLS with a trusted certificate, or add its CA to /etc/docker/certs.d/\<registry\>/ca.crt), set "debug": false and "experimental": false in daemon.json, and remove any --insecure-registry, --debug (-D) or --experimental flag from the dockerd ExecStart in docker.service drop-ins (then systemctl daemon-reload). Restart Docker in a maintenance window: running containers stop unless live-restore is on.

**Verify:** `docker info | grep -Ei 'insecure|debug|experimental'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-494, CWE-1188; NIST SP 800-53 SI-7, CM-7

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[MEDIUM\] Containers Run Without Memory or Process Limits (HOST-CTR-012)

Category: config. Effort: medium. Confidence: high.

Looks for containers with no memory limit and no PID limit.

**Why it matters:** A container with no limits can use all the host's memory or fork until the kernel runs out of process slots, taking the host and every neighbour down (a fork bomb or a memory leak is enough). Limits turn that into a problem for one container.

- **containers:** Containers have neither a memory nor a PID limit
  - `3f2a9c1d8e7b: no memory limit, no PID limit`

**Fix:** Recreate each container with --memory (for example 512m) and --pids-limit (for example 256), or set them in the compose file under deploy.resources.limits / pids\_limit.

**Verify:** `docker inspect --format '{{.Name}} mem={{.HostConfig.Memory}} pids={{.HostConfig.PidsLimit}}' $(docker ps -aq)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; NIST SP 800-53 SC-6, SC-5

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[MEDIUM\] Containers Run as Root With a Writable Root Filesystem (HOST-CTR-013)

Category: config. Effort: medium. Confidence: high.

Looks for containers whose process runs as root and whose root filesystem is writable.

**Why it matters:** Root inside the container is the first half of most container escapes, and a writable root filesystem lets an attacker install tools and persist. A non-root user plus --read-only with a few tmpfs mounts removes both at once for most services.

- **containers:** Containers run as root on a writable root filesystem
  - `3f2a9c1d8e7b: runs as root with a writable root filesystem`

**Fix:** Add USER \<uid\> to the image or run with --user 1000:1000, and start the container with --read-only plus --tmpfs /tmp for scratch space; recreate the container.

**Verify:** `docker inspect --format '{{.Name}} user={{.Config.User}} ro={{.HostConfig.ReadonlyRootfs}}' $(docker ps -aq)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-732; NIST SP 800-53 AC-6, CM-7

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[MEDIUM\] Boot Loader Configuration Exposed (HOST-FS-006)

Category: access. Effort: low. Confidence: high.

Checks that the GRUB configuration is not readable or writable by normal users.

**Why it matters:** The boot loader configuration controls kernel parameters at boot. If writable, it allows boot-time privilege escalation; if readable, it may expose a boot password hash.

- **/boot/grub/grub.cfg:** 1 file(s) are accessible by group or others
  - `0644 /boot/grub/grub.cfg`

**Fix:** chown root:root \<grub.cfg\> && chmod 600 \<grub.cfg\>  (for example /boot/grub/grub.cfg or /boot/grub2/grub.cfg).

**Verify:** `stat -c '%a %U %n' /boot/grub/grub.cfg /boot/grub2/grub.cfg`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[MEDIUM\] World-Writable Files Outside Temporary Directories (HOST-FS-014)

Category: access. Effort: low. Confidence: high.

Looks for files that any user can modify in program, service and home locations other than /etc (which HOST-FS-003 covers).

**Why it matters:** A world-writable script, binary or data file lets any local account, including a compromised web application, change what other users and services run or read.

- **filesystem:** 4 world-writable file(s) found
  - `0666 /srv/app/upload.sh`
  - `0666 /var/www/shop/index.php`
  - `0666 /var/www/shop/wp-config.php`
  - `0777 /usr/local/bin/backup.sh`

**Fix:** For each listed file: chmod o-w \<path\>. Then find the deployment step or chmod 777 habit that created it.

**Verify:** `find /usr/local /opt /srv /var/www /home /usr/bin /usr/sbin -xdev -type f -perm -0002  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] SUID or SGID Programs Outside the Known Baseline (HOST-FS-016)

Category: access. Effort: medium. Confidence: high.

Lists set-user-ID and set-group-ID programs in the system binary directories that are not on the documented baseline of what distributions ship.

**Why it matters:** Every SUID program is a door into another account, usually root. Programs such as nmap, vim, find, python or a custom helper with the SUID bit are a known route to instant root; the fewer unexplained SUID programs, the smaller that door.

- **filesystem:** 1 SUID/SGID program(s) are not on the baseline
  - `4755 /usr/bin/nmap`

**Fix:** For each listed program decide whether the bit is needed. Remove it with  chmod u-s,g-s \<path\>  or uninstall the package. Record the ones you keep so the next review is quick. Caution: programs such as passwd, sudo, su, mount and ping need their bit to work for normal users; removing it from those breaks password changes and administrator access.

**Verify:** `find /bin /sbin /usr/bin /usr/sbin /usr/libexec /usr/lib -xdev -type f -perm /6000  and compare with your record.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-6, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] World-Writable Directories Without the Sticky Bit (HOST-FS-017)

Category: access. Effort: low. Confidence: high.

Looks for shared writable directories outside /tmp and /etc that let any user delete or replace other users' files.

**Why it matters:** In a world-writable directory without the sticky bit, any user can remove or swap a file placed there by another user or by a service, which turns upload folders and spool directories into file-replacement attacks.

- **filesystem:** 4 shared directory(ies) lack the sticky bit
  - `0777 /opt/scripts/ (directory, no sticky bit)`
  - `0777 /srv/shop-laravel/bootstrap/cache/ (directory, no sticky bit)`
  - `0777 /srv/shop-laravel/storage/ (directory, no sticky bit)`
  - `0777 /var/www/uploads/ (directory, no sticky bit)`

**Fix:** chmod +t \<dir\>  for directories that must stay shared, or  chmod o-w \<dir\>  for those that should not be writable by everyone.

**Verify:** `find /var /home /opt /srv /usr/local -xdev -type d -perm -0002 ! -perm -1000  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-377; NIST SP 800-53 AC-3, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[MEDIUM\] Address Space Layout Randomisation Not Fully Enabled (HOST-KRN-001)

Category: config. Effort: low. Confidence: high.

Checks that the kernel randomises memory layout for stack, libraries, heap and VDSO.

**Why it matters:** ASLR makes memory-corruption exploits much harder because attackers cannot predict where code lives.

- **kernel.randomize\_va\_space:** kernel.randomize\_va\_space = 0 (hardened value: 2)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.randomize\_va\_space = 2  then apply with: sysctl --system

**Verify:** `sysctl kernel.randomize_va_space  should print 2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-344; NIST SP 800-53 CM-6, SI-16

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[MEDIUM\] Unprivileged eBPF Allowed (HOST-KRN-009)

Category: config. Effort: low. Confidence: high.

Checks that normal users cannot load eBPF programs into the kernel.

**Why it matters:** Unprivileged eBPF has been the entry point for many local privilege-escalation bugs.

- **kernel.unprivileged\_bpf\_disabled:** kernel.unprivileged\_bpf\_disabled = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.unprivileged\_bpf\_disabled = 1  then apply with: sysctl --system

**Verify:** `sysctl kernel.unprivileged_bpf_disabled  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 CM-6, SI-16

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[MEDIUM\] Root PATH Contains Unsafe Entries (HOST-PAM-008)

Category: identity. Effort: low. Confidence: high.

Checks that the PATH set for root does not include the current directory, empty entries, relative paths or directories other users can write to.

**Why it matters:** When root's PATH contains . or an empty element, typing ls in a directory an attacker controls runs the attacker's ls as root. A world-writable directory on the PATH has the same effect for every command name that is not found earlier. This is one of the oldest Unix privilege escalations and still common in copied dotfiles.

- **root PATH:** Root's PATH contains unsafe entries
  - `/root/.bashrc: entry . (current directory)`
  - `/root/.bashrc: empty entry (trailing colon) means the current directory`

**Fix:** Edit the listed file so PATH holds only absolute, root-owned directories, for example  PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" , remove any . or trailing colon, and  chmod o-w  any listed directory. Log out and in again.

**Verify:** `sudo -i sh -c 'echo $PATH'  should show no . or empty element;  ls -ld $(echo $PATH | tr : ' ')`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-426, CWE-427; NIST SP 800-53 CM-6, AC-6

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] Running Kernel Older Than Installed Kernel (HOST-PKG-005)

Category: patch. Effort: medium. Confidence: high.

Checks that the kernel in memory is the newest kernel installed, so kernel security fixes are actually in effect.

**Why it matters:** A kernel update protects nothing until the machine boots into it. The package manager reports the update as installed, so the gap is invisible unless the running version is compared with the installed one. HOST-UPD-002 reads the Debian reboot marker; this check works on every distribution by comparing versions.

- **kernel:** Running kernel 4.15.0-100-generic is older than installed kernel 4.15.0-150-generic; a reboot is needed

**Fix:** Schedule a maintenance window and reboot into the newest kernel. Where downtime is a problem, use the distribution's live patching service and still plan regular reboots.

**Verify:** `uname -r  should match the newest version in  dpkg -l 'linux-image-*' | grep ^ii  or  rpm -q kernel-core .`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] Weak Password Hashing Algorithm Configured (HOST-PWD-001)

Category: identity. Effort: low. Confidence: high.

Checks that new passwords are hashed with a modern, slow algorithm (SHA-512 or yescrypt).

**Why it matters:** If the shadow file ever leaks, fast or obsolete hashes such as MD5 or DES can be cracked in minutes. Slow, salted algorithms make offline cracking far more expensive.

- **/etc/login.defs:** ENCRYPT\_METHOD MD5 is weak

**Fix:** Set  ENCRYPT\_METHOD YESCRYPT  (or SHA512 on older systems) in /etc/login.defs, and make sure pam\_unix uses the same algorithm. Ask users to change passwords so existing hashes are upgraded.

**Verify:** `grep -i '^ENCRYPT_METHOD' /etc/login.defs`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-916, CWE-328; OWASP Top 10 A02:2021; NIST SP 800-53 IA-5(1)

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] Accounts With Obsolete Password Hashes (HOST-PWD-002)

Category: identity. Effort: low. Confidence: high.

Checks /etc/shadow for passwords still stored with MD5 or traditional DES crypt.

**Why it matters:** Old hashes survive configuration changes until the user changes the password. MD5-crypt and DES hashes can be cracked quickly with commodity hardware.

- **/etc/shadow:** Accounts use obsolete password hashes
  - `olduser (MD5-crypt)`

**Fix:** Configure a modern algorithm (HOST-PWD-001), then force a password change for each listed account (chage -d 0 \<user\>) or lock unused accounts (passwd -l \<user\>).

**Verify:** `Re-run this check; no accounts should be listed.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-916, CWE-328; OWASP Top 10 A02:2021; NIST SP 800-53 IA-5(1)

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] No Account Lockout After Failed Logins (HOST-PWD-004)

Category: identity. Effort: medium. Confidence: high.

Checks that PAM temporarily locks accounts after repeated failed logins.

**Why it matters:** Without lockout, local and PAM-backed remote logins can be guessed indefinitely. A temporary lock after a few failures slows attacks and makes them visible. This check covers whether lockout exists and its deny threshold; HOST-PAM-002 covers only how long the lock lasts (unlock\_time), so one missing lockout is reported once, here.

- **pam:** No effective account lockout in the authentication stack
  - `/etc/pam.d/common-auth: pam_faillock deny=100 (need 1 to 10)`

**Fix:** Enable pam\_faillock (authselect enable-feature with-faillock on RHEL family; pam-auth-update or a faillock profile on Debian/Ubuntu) with settings such as deny=5 unlock\_time=900 in /etc/security/faillock.conf. Keep a root session open and test a login in a second session before closing it: a wrong PAM line can lock every user out, including administrators. Do not lock root (no even\_deny\_root) unless console access is assured.

**Verify:** `grep -E 'pam_faillock|pam_tally2' /etc/pam.d/common-auth /etc/pam.d/system-auth`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307; OWASP Top 10 A07:2021; NIST SP 800-53 AC-7

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] SSH Password Authentication Enabled (HOST-SSH-002)

Category: identity. Effort: low. Confidence: high.

Checks whether SSH accepts passwords instead of only keys.

**Why it matters:** Internet-facing SSH receives constant automated password guessing. Public-key authentication cannot be guessed, phished as easily, or reused from another breached site.

- **sshd:** Password logins are accepted (PasswordAuthentication defaults to yes)
  - `passwordauthentication yes`

**Fix:** Install your public key for each admin user (ssh-copy-id), confirm key login works in a second session, then set  PasswordAuthentication no  and  KbdInteractiveAuthentication no  in /etc/ssh/sshd\_config and reload sshd.

**Verify:** `sshd -T | grep -Ei 'passwordauthentication|kbdinteractive'  should show 'no' for both.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307, CWE-521; NIST SP 800-53 IA-2(1), IA-5

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] SSH Offers Weak Cryptography (HOST-SSH-004)

Category: crypto. Effort: low. Confidence: high.

Checks the SSH ciphers, MACs, key exchange and host key algorithms for outdated choices.

**Why it matters:** CBC-mode ciphers, RC4, MD5/SHA-1 MACs and 1024-bit Diffie-Hellman have known practical weaknesses. Offering them lets a network attacker downgrade or weaken the session.

- **sshd:** Weak SSH algorithms are offered
  - `ciphers: aes128-cbc`
  - `macs: hmac-md5`
  - `kexalgorithms: diffie-hellman-group1-sha1`
  - `hostkeyalgorithms: ssh-rsa`

**Fix:** Remove the listed algorithms. On OpenSSH 8.x and later the defaults are already strong: delete any custom Ciphers/MACs/KexAlgorithms lines, or set explicit modern lists such as  Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com  then reload sshd.

**Verify:** `sshd -T | grep -Ei '^(ciphers|macs|kexalgorithms)'  should list no cbc, arcfour, md5 or sha1 entries, and hostkeyalgorithms should not list ssh-rsa.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-327; OWASP Top 10 A02:2021; NIST SP 800-53 SC-8, SC-13

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[MEDIUM\] SSH Forwarding Features Enabled (HOST-SSH-007)

Category: config. Effort: low. Confidence: high.

Checks effective SSH Match contexts for TCP, agent, tunnel and gateway forwarding.

**Why it matters:** Forwarding can turn an SSH account into a route to protected services or expose a user's authentication agent.

- **sshd:** SSH forwarding is enabled in at least one evaluated context
  - `allowtcpforwarding default yes`
  - `allowagentforwarding default yes`

**Fix:** Set AllowTcpForwarding no, AllowAgentForwarding no, PermitTunnel no and GatewayPorts no globally or in every applicable Match block, then reload sshd.

**Verify:** `Run sshd -T -C user=nobody,host=localhost,addr=192.0.2.1 and confirm each forwarding option is no.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-441; NIST SP 800-53 AC-4, CM-7

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[MEDIUM\] SSH Legacy Trust Options Enabled (HOST-SSH-011)

Category: identity. Effort: low. Confidence: high.

Checks for host-based trust and user environment options that weaken SSH authentication.

**Why it matters:** HostbasedAuthentication and ignoring .rhosts trust other machines instead of users. PermitUserEnvironment lets users set variables such as LD\_PRELOAD that can bypass restrictions.

- **sshd:** Legacy trust options are enabled
  - `hostbasedauthentication yes`
  - `ignorerhosts no`
  - `permituserenvironment yes`

**Fix:** Set  HostbasedAuthentication no ,  IgnoreRhosts yes  and  PermitUserEnvironment no  in /etc/ssh/sshd\_config, then reload sshd.

**Verify:** `sshd -T | grep -Ei 'hostbasedauthentication|ignorerhosts|permituserenvironment'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-290, CWE-15; NIST SP 800-53 IA-2, CM-6

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[MEDIUM\] Telnet Server Installed or Enabled (HOST-SVC-002)

Category: config. Effort: low. Confidence: high.

Checks whether a Telnet server is installed or set to start at boot.

**Why it matters:** Telnet sends user names, passwords and every keystroke in the clear. Anyone on the network path reads them. SSH replaced it decades ago; a Telnet server on a modern server is almost always forgotten rather than needed.

- **services:** 2 legacy service component(s) present
  - `package telnetd is installed`
  - `unit telnet.socket is enabled at boot`

**Fix:** apt purge telnetd inetutils-telnetd  or  dnf remove telnet-server , and  systemctl disable --now telnet.socket . Use SSH. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l telnetd inetutils-telnetd telnetd-ssl telnet-server 2>/dev/null | grep '^ii' ; systemctl is-enabled telnet.socket telnetd.service inetutils-telnetd.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-1188; NIST SP 800-53 CM-7, CM-7(1), SC-8

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] rsh, rlogin, rexec or finger Server Installed or Enabled (HOST-SVC-003)

Category: config. Effort: low. Confidence: high.

Checks whether the Berkeley r-services (rsh, rlogin, rexec) or a finger server is installed or enabled.

**Why it matters:** The r-services trust the client's claim of who it is and send everything unencrypted; .rhosts files (HOST-USR-007) exist for them. finger lists account names and login times to anyone who asks, which is reconnaissance for password guessing.

- **services:** 1 legacy service component(s) present
  - `package rsh-server is installed`

**Fix:** apt purge rsh-server rsh-redone-server fingerd  or  dnf remove rsh-server finger-server , and  systemctl disable --now rsh.socket rlogin.socket rexec.socket finger.socket . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l rsh-server rsh-redone-server fingerd finger-server efingerd cfingerd 2>/dev/null | grep '^ii' ; systemctl is-enabled rsh.socket rlogin.socket rexec.socket finger.socket rsh-redone-server.service fingerd.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-1188; NIST SP 800-53 CM-7, CM-7(1), SC-8

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] TFTP Server Installed or Enabled (HOST-SVC-004)

Category: config. Effort: low. Confidence: high.

Checks whether a TFTP server is installed or set to start at boot.

**Why it matters:** TFTP has no authentication and no encryption: any client that can reach it can read, and often write, every file in its directory. It is needed only on network boot (PXE) servers and for some network equipment, and is a frequent source of leaked configuration files. If this machine is one of the network boot (PXE) servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 1 legacy service component(s) present
  - `package tftpd-hpa is installed`

**Fix:** apt purge tftpd-hpa atftpd  or  dnf remove tftp-server , and  systemctl disable --now tftpd-hpa.service tftp.socket . Where PXE is required, serve only that directory, read-only, from a dedicated host. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l tftpd-hpa atftpd tftp-server tftpd 2>/dev/null | grep '^ii' ; systemctl is-enabled tftpd-hpa.service atftpd.service tftp.socket tftp.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-306, CWE-1188; NIST SP 800-53 CM-7, CM-7(1), SC-8

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] FTP Server Installed or Enabled (HOST-SVC-005)

Category: config. Effort: low. Confidence: high.

Checks whether an FTP server (vsftpd, ProFTPD or Pure-FTPd) is installed or set to start at boot.

**Why it matters:** Plain FTP sends passwords and files unencrypted and needs a wide range of ports open. SFTP over SSH, which the SSH server already provides, does the same job encrypted. An FTP server is usually a leftover from a web hosting panel. If this machine is one of the hosts that must serve legacy FTP clients that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 2 legacy service component(s) present
  - `package vsftpd is installed`
  - `unit vsftpd.service is enabled at boot`

**Fix:** Move users to SFTP (the OpenSSH internal-sftp subsystem), then  apt purge vsftpd proftpd-core pure-ftpd  or  dnf remove vsftpd proftpd  and  systemctl disable --now vsftpd . If FTP must stay, enforce TLS (FTPS) and restrict it to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l vsftpd proftpd-core proftpd-basic proftpd pure-ftpd pure-ftpd-common 2>/dev/null | grep '^ii' ; systemctl is-enabled vsftpd.service proftpd.service pure-ftpd.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-1188; NIST SP 800-53 CM-7, CM-7(1), SC-8

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] NIS Server or Client Installed or Enabled (HOST-SVC-006)

Category: config. Effort: low. Confidence: high.

Checks whether the Network Information Service (NIS, ypserv or ypbind) is installed or enabled.

**Why it matters:** NIS distributes account and password hash databases over the network without encryption or strong authentication; anyone who can reach the server can request the whole password map. HOST-USR-006 looks for its + entries in the account files; this check looks for the software itself.

- **services:** 1 legacy service component(s) present
  - `package nis is installed`

**Fix:** Replace NIS with a modern directory (SSSD with LDAPS or FreeIPA), then  apt purge nis  or  dnf remove ypserv ypbind  and  systemctl disable --now ypserv ypbind . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l nis ypserv ypbind yp-tools 2>/dev/null | grep '^ii' ; systemctl is-enabled ypserv.service ypbind.service nis.service yppasswdd.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-522; NIST SP 800-53 CM-7, CM-7(1), SC-8

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[MEDIUM\] Custom Services Run as Root (HOST-SYSD-001)

Category: identity. Effort: medium. Confidence: high.

Checks whether long-running services written by the administrator run as root instead of a dedicated user.

**Why it matters:** Application services (Node.js, Python, Java, custom daemons) are the code most exposed to attackers. If one runs as root, any bug in it gives the attacker full control of the machine instead of one unprivileged account.

- **systemd units:** 4 custom service(s) run as root
  - `shop-admin.service: no User= or DynamicUser=, runs as root (/etc/systemd/system/shop-admin.service)`
  - `shop-api.service: no User= or DynamicUser=, runs as root (/etc/systemd/system/shop-api.service)`
  - `shop-reports.service: no User= or DynamicUser=, runs as root (/etc/systemd/system/shop-reports.service)`
  - `shop-worker.service: no User= or DynamicUser=, runs as root (/etc/systemd/system/shop-worker.service)`

**Fix:** Create a dedicated account (useradd --system --no-create-home --shell /usr/sbin/nologin \<app\>), give it ownership of only the folders the app writes to, then add User=\<app\> and Group=\<app\> to the \[Service\] section (or DynamicUser=yes). Run systemctl daemon-reload and restart the service. Ports below 1024 can be allowed with AmbientCapabilities=CAP\_NET\_BIND\_SERVICE instead of root.

**Verify:** `systemctl show -p User,DynamicUser <unit>  and  ps -o user= -p $(systemctl show -p MainPID --value <unit>)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-6, CM-7

**Learn:** P11: Service Hardening With systemd, https://openvapt.com/academy/p11.html (offline: `openvapt learn P11`)

### \[MEDIUM\] Secrets Written Into Service Unit Files (HOST-SYSD-005)

Category: exposure. Effort: low. Confidence: high.

Checks whether passwords, tokens or keys are set with Environment= in systemd unit files.

**Why it matters:** Unit files are normally readable by every user, and systemctl show prints a service's Environment= values to any local user. A database password or API token placed there is effectively public on the machine.

- **systemd units:** Service unit files store secrets
  - `shop-api.service (values not shown): Environment= sets DB_PASSWORD, STRIPE_API_KEY`

**Fix:** Move secrets to a root-owned file readable only by root (chmod 600) and load it with EnvironmentFile=, or better use LoadCredential=/SetCredentialEncrypted= so the secret is only visible to the service. Then rotate every secret that was exposed.

**Verify:** `systemctl show -p Environment <unit>  should show no secret values.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-526, CWE-312; OWASP Top 10 A05:2021; NIST SP 800-53 IA-5, SC-28

**Learn:** P11: Service Hardening With systemd, https://openvapt.com/academy/p11.html (offline: `openvapt learn P11`)

### \[MEDIUM\] Reboot Required to Apply Updates (HOST-UPD-002)

Category: patch. Effort: medium. Confidence: high.

Checks whether installed updates (often kernel or core library fixes) are waiting for a reboot.

**Why it matters:** Updated packages do not protect anything until the running kernel and libraries are replaced. A pending reboot means known-fixed vulnerabilities are still live.

- **reboot:** A reboot is required to apply installed updates
  - `/var/run/reboot-required exists`
  - `linux-image-generic`
  - `libc6`

**Fix:** Schedule a maintenance window and reboot (or use live patching where supported).

**Verify:** `ls /var/run/reboot-required  should not exist after the reboot.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; NIST SP 800-53 SI-2

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[MEDIUM\] Duplicate User IDs (HOST-USR-004)

Category: identity. Effort: medium. Confidence: high.

Checks that no two accounts share the same numeric user id.

**Why it matters:** Accounts with the same UID are the same user to the kernel: they own each other's files and processes, and audit logs cannot tell them apart.

- **/etc/passwd:** Accounts share user ids
  - `UID 0: root, toor`

**Fix:** Give each account a unique UID (usermod -u \<new-uid\> \<user\>) and fix file ownership (find / -xdev -uid \<old-uid\>).

**Verify:** `cut -d: -f3 /etc/passwd | sort | uniq -d  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-286; NIST SP 800-53 AC-2, IA-2

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] Legacy NIS Entries in Account Files (HOST-USR-006)

Category: identity. Effort: low. Confidence: high.

Checks /etc/passwd, /etc/shadow and /etc/group for legacy '+' NIS inclusion entries.

**Why it matters:** '+' entries pull accounts from NIS, an unencrypted legacy directory service. If NIS is gone, the entry can still be abused to create unexpected accounts.

- **account files:** Legacy NIS '+' entries found
  - `/etc/passwd: +`

**Fix:** Remove the '+' lines (with vipw / vigr) and use a modern directory service such as SSSD with LDAPS if central accounts are needed.

**Verify:** `grep '^+' /etc/passwd /etc/shadow /etc/group  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-288; NIST SP 800-53 IA-2, CM-7

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[MEDIUM\] Duplicate Group IDs or Account Names (HOST-USR-008)

Category: identity. Effort: medium. Confidence: high.

Checks /etc/group for duplicate GIDs and group names, and /etc/passwd for duplicate user names.

**Why it matters:** Two groups with one GID are the same group to the kernel, so file permissions granted to one are granted to both. Duplicate names make tools and audit logs act on the wrong entry, and are a sign that the account files were edited by hand or by a broken script.

- **/etc/group:** Duplicate group ids or names in the account files
  - `GID 4: adm, staff`

**Fix:** groupmod -g \<new-gid\> \<group\>  for one of each duplicate pair, then fix file ownership (find / -xdev -gid \<old-gid\>); remove or rename the duplicate name entries with vigr and vipw, which lock the files while editing.

**Verify:** `cut -d: -f3 /etc/group | sort | uniq -d ; cut -d: -f1 /etc/group /etc/passwd | sort | uniq -d  print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-286; NIST SP 800-53 AC-2, AC-3

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[MEDIUM\] Password and Shadow Files Out of Step (HOST-USR-009)

Category: identity. Effort: low. Confidence: high.

Checks that every /etc/passwd account has a /etc/shadow entry, that no password hash sits in /etc/passwd, and that /etc/shadow has no orphan entries.

**Why it matters:** A hash in the world-readable /etc/passwd can be cracked offline by any user. An account with 'x' in passwd but no shadow line has an undefined password state that differs between tools, and shadow lines for deleted accounts are leftovers of a removal that did not finish.

- **/etc/passwd:** Account files are inconsistent
  - `olduser: /etc/shadow entry has no /etc/passwd account`
  - `daemon: /etc/passwd account has no /etc/shadow entry`
  - `toor: /etc/passwd account has no /etc/shadow entry`

**Fix:** pwconv  moves hashes into /etc/shadow and creates missing entries; userdel or vipw/vigr removes orphan lines; run pwck and grpck to confirm the files are consistent.

**Verify:** `pwck -r  reports no errors.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-256, CWE-522; NIST SP 800-53 IA-5, AC-2

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[MEDIUM\] Application Server Ports Reachable Without a Reverse Proxy (NET-APP-002)

Category: network. Effort: low. Confidence: high.

Checks whether application servers and developer tools (Node.js, Tomcat, Django, Flask, Vite) serve HTTP on network-reachable addresses.

**Why it matters:** Application servers are designed to sit behind a reverse proxy that adds TLS, security headers, logging and request limits. Reached directly they bypass all of that, and development servers on these ports often run with debug pages on. The port number alone does not prove a weakness, so this is rated Medium and should be reviewed.

- **sockets:** Application server ports are network-reachable
  - `tcp 0.0.0.0:3000 Node.js / Express app server, bound to all interfaces`
  - `a web server listens on port 80 or 443 on this machine: bind the application to 127.0.0.1 and publish it through that proxy`

**Fix:** Bind the application to 127.0.0.1 (Node.js: app.listen(3000, '127.0.0.1'); Gunicorn: --bind 127.0.0.1:8000; Tomcat: address="127.0.0.1" on the Connector) and publish it only through nginx or Apache.

**Verify:** `ss -tlnp  shows these ports on 127.0.0.1 or ::1 only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-1327; OWASP Top 10 A05:2021; NIST SP 800-53 SC-7, CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[MEDIUM\] Mail Cleartext Authentication Allowed (NET-DB-013)

Category: crypto. Effort: low. Confidence: high.

Postfix or Dovecot permits plaintext authentication without requiring TLS.

**Why it matters:** Credentials sent before transport encryption can be observed by any party able to intercept the connection.

- **/etc/postfix/main.cf, /etc/dovecot/conf.d/10-auth.conf:** Postfix or Dovecot permits plaintext authentication without requiring TLS.
  - `smtpd_tls_auth_only is no`
  - `disable_plaintext_auth is no`

**Fix:** Set smtpd\_tls\_auth\_only = yes and disable\_plaintext\_auth = yes. Use implicit TLS on ports 465, 993, and 995. STARTTLS assessment is out of scope.

**Verify:** `Confirm the effective mail configuration and that unauthenticated plaintext sessions do not advertise AUTH.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-523; OWASP Top 10 A02:2021; ASVS 9.1.1; NIST SP 800-53 SC-8, IA-5(1)

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

### \[MEDIUM\] MySQL Symbolic Links Allowed for Tables (NET-DB-014)

Category: data. Effort: low. Confidence: high.

MySQL or MariaDB does not disable symbolic links for table files (skip-symbolic-links or symbolic-links=0).

**Why it matters:** With symlinks allowed, a user who can create tables can point table files at paths outside the data directory and read or overwrite them with the server's privileges. The MySQL manual recommends disabling the feature unless it is needed.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** MySQL or MariaDB does not disable symbolic links for table files (skip-symbolic-links or symbolic-links=0).
  - `no symbolic-links setting; MySQL 5.x (dpkg status mysql-server-5.7) allows symbolic links by default`

**Fix:** Add  symbolic-links=0  (or  skip-symbolic-links ) under \[mysqld\] and restart the service in a maintenance window.

**Verify:** `SHOW VARIABLES LIKE 'have_symlink';  from an administrator session should print DISABLED.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-59, CWE-732; OWASP Top 10 A05:2021; ASVS 14.1.3; NIST SP 800-53 CM-6, AC-6

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] MySQL secure\_file\_priv Not Restricted (NET-DB-015)

Category: data. Effort: low. Confidence: high.

secure\_file\_priv is not set, or is set to an empty value, so LOAD DATA and SELECT ... INTO OUTFILE can read and write anywhere the server process can.

**Why it matters:** File import and export statements run with the database server's filesystem rights. Limiting them to one folder, or disabling them with NULL, stops an injected query from reading /etc files or dropping a web shell into the document root.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** secure\_file\_priv is not set, or is set to an empty value, so LOAD DATA and SELECT ... INTO OUTFILE can read and write anywhere the server process can.
  - `secure_file_priv is not set in the option files; MySQL 5.x (dpkg status mysql-server-5.7) leaves it empty by default`

**Fix:** Add  secure\_file\_priv = NULL  (no file operations) or  secure\_file\_priv = /var/lib/mysql-files  under \[mysqld\] and restart in a maintenance window.

**Verify:** `SHOW VARIABLES LIKE 'secure_file_priv';  should print NULL or the chosen folder.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-73, CWE-250; OWASP Top 10 A05:2021; ASVS 14.1.3; NIST SP 800-53 CM-6, AC-6, SI-10

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] MySQL Does Not Require Encrypted Client Connections (NET-DB-016)

Category: crypto. Effort: low. Confidence: high.

require\_secure\_transport is not ON, so clients may log in and query over plaintext TCP.

**Why it matters:** Without the requirement, a client that is misconfigured or downgraded sends credentials and data in the clear across the network. The setting is harmless for Unix socket connections and makes TLS a server-side rule rather than a client choice. When bind-address keeps the server on loopback (127.0.0.1, ::1 or localhost) the plaintext path never leaves the machine and only a local root user could read it, so the check reports Pass with a note instead of a Medium finding; the Medium applies to servers reachable from the network.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** require\_secure\_transport is not ON, so clients may log in and query over plaintext TCP.
  - `require_secure_transport is not set; the default is OFF`

**Fix:** Provision a server certificate (MySQL creates one by default), then add  require\_secure\_transport = ON  under \[mysqld\] and restart in a maintenance window. Test every application connection string first.

**Verify:** `SHOW VARIABLES LIKE 'require_secure_transport';  should print ON, and  a client that disables TLS (ssl-mode DISABLED) connecting to 127.0.0.1 should be refused.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; ASVS 9.2.2; NIST SP 800-53 SC-8, SC-13

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] PostgreSQL Uses md5 or password Authentication (NET-DB-019)

Category: identity. Effort: low. Confidence: high.

pg\_hba.conf has active rules with the md5 or password method instead of scram-sha-256.

**Why it matters:** The password method sends the password in clear text unless the connection is encrypted; md5 is a fast unsalted construction whose stored hashes work as passwords themselves (pass-the-hash) and which the PostgreSQL documentation describes as obsolete. SCRAM-SHA-256 fixes both.

- **/etc/postgresql/16/main/pg\_hba.conf:** pg\_hba.conf has active rules with the md5 or password method instead of scram-sha-256.
  - `/etc/postgresql/16/main/pg_hba.conf: host all all 10.0.0.0/8 md5`

**Fix:** Set  password\_encryption = scram-sha-256  in postgresql.conf, have every role reset its password so a SCRAM verifier is stored, then change the pg\_hba.conf methods to scram-sha-256 and reload.

**Verify:** `SELECT * FROM pg_hba_file_rules WHERE auth_method IN ('md5','password');  should return no rows.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-328, CWE-916; OWASP Top 10 A02:2021, A07:2021; ASVS 2.4.1; NIST SP 800-53 IA-5, IA-5(1), SC-13

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] PostgreSQL SSL Not Enabled (NET-DB-020)

Category: crypto. Effort: low. Confidence: high.

postgresql.conf does not set ssl = on, so TCP clients cannot encrypt their sessions and hostssl rules cannot apply.

**Why it matters:** Without SSL every query and result crosses the network in the clear. Enabling it costs one certificate and lets pg\_hba.conf require hostssl for remote clients. When listen\_addresses keeps the server on loopback (the PostgreSQL default, localhost) the plaintext path never leaves the machine and only a local root user could read it, so the check reports Pass with a note instead of a Medium finding; the Medium applies to servers reachable from the network.

- **/etc/postgresql/16/main/postgresql.conf:** postgresql.conf does not set ssl = on, so TCP clients cannot encrypt their sessions and hostssl rules cannot apply.
  - `ssl is not set; the default is off`

**Fix:** Install a certificate and key readable only by the postgres user, set  ssl = on , ssl\_cert\_file and ssl\_key\_file, reload, then change remote pg\_hba.conf rules from host to hostssl.

**Verify:** `SHOW ssl;  should print on, and  psql "sslmode=require host=127.0.0.1"  should connect.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; ASVS 9.2.2; NIST SP 800-53 SC-8, SC-13

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] PostgreSQL Password Encryption Not SCRAM (NET-DB-022)

Category: identity. Effort: low. Confidence: high.

password\_encryption is set to md5, so new and changed passwords are stored as md5 verifiers that work as pass-the-hash credentials.

**Why it matters:** The stored md5 value is itself sufficient to authenticate, so a leaked catalog is a leaked set of passwords. SCRAM-SHA-256 stores a salted, iterated verifier that is not usable directly. PostgreSQL 14 and newer default to scram-sha-256; older clusters often carry an explicit md5 line.

- **/etc/postgresql/16/main/postgresql.conf:** password\_encryption is set to md5, so new and changed passwords are stored as md5 verifiers that work as pass-the-hash credentials.
  - `/etc/postgresql/16/main/postgresql.conf: password_encryption = md5`

**Fix:** Set  password\_encryption = scram-sha-256 , reload, and ask every role to reset its password so the verifier is regenerated. Then move pg\_hba.conf to scram-sha-256 (NET-DB-019).

**Verify:** `SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';  should return no rows after the resets.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-916, CWE-328; OWASP Top 10 A02:2021; ASVS 2.4.1; NIST SP 800-53 IA-5(1), SC-13

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] Redis Dangerous Commands Not Restricted (NET-DB-023)

Category: data. Effort: low. Confidence: high.

redis.conf neither renames nor disables CONFIG, FLUSHALL, FLUSHDB, DEBUG and KEYS, and no ACL user excludes the @dangerous category.

**Why it matters:** An attacker who reaches Redis with any credential can wipe data with FLUSHALL, rewrite the configuration with CONFIG SET to write files (the classic path to a cron job or SSH key), or stall the server with KEYS \*. Removing or renaming these for application users limits what a stolen credential can do.

- **/etc/redis/redis.conf:** redis.conf neither renames nor disables CONFIG, FLUSHALL, FLUSHDB, DEBUG and KEYS, and no ACL user excludes the @dangerous category.
  - `no rename-command, user or aclfile directive`

**Fix:** Prefer ACLs on Redis 6 and newer:  user app on \>secret \~app:\* -@all +@read +@write -@dangerous . Otherwise add  rename-command FLUSHALL ""  (and the same for FLUSHDB, CONFIG, DEBUG, KEYS) and restart.

**Verify:** `redis-cli -a <secret> ACL LIST  or  redis-cli CONFIG GET dir  should be refused for the application user.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; OWASP Top 10 A01:2021, A05:2021; ASVS 14.1.3; NIST SP 800-53 AC-6, CM-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] BIND DNSSEC Validation Disabled (NET-DNS-002)

Category: data. Effort: low. Confidence: high.

named.conf sets dnssec-validation no, so the resolver accepts forged answers for signed zones.

**Why it matters:** DNSSEC validation lets a recursive resolver reject spoofed records for zones that are signed, which blocks cache poisoning of those names. BIND 9.16 and newer default to  auto  with the built-in root trust anchor, so an explicit  no  is a deliberate weakening.

- **/etc/bind/named.conf.options:** named.conf sets dnssec-validation no, so the resolver accepts forged answers for signed zones.
  - `dnssec-validation no`

**Fix:** Set  dnssec-validation auto;  in options (remove any  no ), run named-checkconf and rndc reconfig. Authoritative-only servers with recursion off may leave it unset.

**Verify:** `dig @127.0.0.1 dnssec-failed.org a  should return SERVFAIL, and a signed name should show the ad flag.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-347, CWE-350; OWASP Top 10 A08:2021; NIST SP 800-53 SC-20, SC-21

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] No Active Host Firewall (NET-FW-001)

Category: network. Effort: medium. Confidence: high.

Checks whether a host firewall (nftables, iptables, ufw or firewalld) is filtering incoming traffic.

**Why it matters:** A default-deny host firewall is a safety net: a service that is installed or misconfigured by mistake is still not reachable from the internet.

- **firewall:** iptables has no drop or reject rules (input policy ACCEPT)

**Fix:** Ubuntu: ufw default deny incoming && ufw allow OpenSSH && ufw allow 80,443/tcp && ufw enable . RHEL family: systemctl enable --now firewalld . Always allow SSH before enabling, or you will lock yourself out.

**Verify:** `ufw status verbose  or  firewall-cmd --list-all  or  nft list ruleset`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-923; NIST SP 800-53 SC-7

**Learn:** F1: Security Basics, https://openvapt.com/academy/f1.html (offline: `openvapt learn F1`)

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

### \[MEDIUM\] Container Ports Bypass the Host Firewall (NET-FW-005)

Category: network. Effort: medium. Confidence: high.

Checks whether Docker publishes container ports through its own iptables chains with no DOCKER-USER filtering.

**Why it matters:** Docker inserts its NAT and FORWARD rules ahead of ufw and of the INPUT chain, so a port published with -p 8080:80 is reachable from the internet even when ufw says the port is denied. Administrators routinely believe the firewall protects these containers when it does not.

- **iptables:** 1 published container port rule(s) are not filtered by the host firewall
  - `-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT`

**Fix:** Publish container ports on loopback only (-p 127.0.0.1:8080:80) and put a reverse proxy in front, or add filtering to DOCKER-USER, for example: iptables -I DOCKER-USER -i eth0 \! -s 203.0.113.0/24 -j DROP (persist it with your firewall tooling). Caution: test from an allowed address first; a wrong interface name or source range cuts access to every published container.

**Verify:** `docker ps --format '{{.Ports}}'  shows 127.0.0.1 bindings, or iptables -S DOCKER-USER shows DROP rules.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-923, CWE-1327; OWASP Top 10 A05:2021; NIST SP 800-53 SC-7, CM-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P12: Firewall Policy Versus What Is Listening, https://openvapt.com/academy/p12.html (offline: `openvapt learn P12`)

### \[MEDIUM\] Postfix SMTP TLS Not Offered or Has No Certificate (NET-MAIL-001)

Category: crypto. Effort: low. Confidence: high.

smtpd\_tls\_security\_level is none or unset, or is may or encrypt without a server certificate, so incoming SMTP sessions cannot be encrypted.

**Why it matters:** Without STARTTLS every message and every authentication exchange that other servers and clients send to this host crosses the network in the clear. Opportunistic TLS (may) with a valid certificate is the baseline that other mail servers expect.

- **/etc/postfix/main.cf:** smtpd\_tls\_security\_level is none or unset, or is may or encrypt without a server certificate, so incoming SMTP sessions cannot be encrypted.
  - `/etc/postfix/main.cf: smtpd_tls_security_level = may`
  - `TLS is enabled but no smtpd_tls_cert_file or smtpd_tls_chain_files is set`

**Fix:** Set  smtpd\_tls\_security\_level = may ,  smtpd\_tls\_chain\_files = /etc/letsencrypt/live/\<host\>/privkey.pem, /etc/letsencrypt/live/\<host\>/fullchain.pem  (or smtpd\_tls\_cert\_file and smtpd\_tls\_key\_file), then postfix reload.

**Verify:** `postconf smtpd_tls_security_level smtpd_tls_chain_files  and an authorised  openssl s_client -starttls smtp -connect 127.0.0.1:25  showing a certificate.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; ASVS 9.2.2; NIST SP 800-53 SC-8, SC-13

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] Postfix Allows TLS 1.0 or 1.1 (NET-MAIL-002)

Category: crypto. Effort: low. Confidence: high.

smtpd\_tls\_protocols or smtpd\_tls\_mandatory\_protocols is unset or still allows TLSv1 or TLSv1.1.

**Why it matters:** The Postfix default for opportunistic TLS still admits TLS 1.0 and 1.1, which RFC 8996 deprecates. Setting a floor of TLS 1.2 is compatible with every current mail server and client. Severity is Medium rather than High because SMTP opportunistic TLS already tolerates fallback to plaintext. Postfix uses the system OpenSSL, so when its configuration already sets a floor of TLS 1.2 (MinProtocol in openssl.cnf on Debian 12 and Ubuntu 22.04 and later, or a crypto policy of DEFAULT, FUTURE or FIPS on the RHEL family) the old versions cannot be negotiated whatever main.cf says; the check then reports Pass and names the floor, and the directives remain worth setting so the configuration says what it does.

- **/etc/postfix/main.cf:** smtpd\_tls\_protocols or smtpd\_tls\_mandatory\_protocols is unset or still allows TLSv1 or TLSv1.1.
  - `smtpd_tls_protocols = !SSLv2, !SSLv3 (allows TLS 1.0 or 1.1)`
  - `smtpd_tls_mandatory_protocols is not set; the default allows TLS 1.0 and 1.1`

**Fix:** Add  smtpd\_tls\_protocols = \>=TLSv1.2  and  smtpd\_tls\_mandatory\_protocols = \>=TLSv1.2  (Postfix 3.6 and newer; on older releases use  \!SSLv2, \!SSLv3, \!TLSv1, \!TLSv1.1 ), then postfix reload.

**Verify:** `postconf smtpd_tls_protocols smtpd_tls_mandatory_protocols`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326, CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.3; NIST SP 800-53 SC-8, SC-13

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] Dovecot Does Not Require TLS (NET-MAIL-004)

Category: crypto. Effort: low. Confidence: high.

Dovecot's ssl setting is yes or no instead of required, so IMAP and POP3 clients may use unencrypted sessions.

**Why it matters:** With ssl = yes, TLS is offered but a client may keep the whole session in the clear, and non-plaintext mechanisms still travel unprotected. ssl = required refuses any command before STARTTLS or implicit TLS, which makes disable\_plaintext\_auth (NET-DB-013) effective.

- **/etc/dovecot/conf.d/10-ssl.conf:** Dovecot's ssl setting is yes or no instead of required, so IMAP and POP3 clients may use unencrypted sessions.
  - `/etc/dovecot/conf.d/10-ssl.conf: ssl = yes`

**Fix:** Set  ssl = required  and  ssl\_min\_protocol = TLSv1.2  in conf.d/10-ssl.conf, then doveadm reload. Prefer implicit TLS ports 993 and 995 for clients. Caution: clients set to plaintext stop connecting, so confirm every mail client uses TLS first.

**Verify:** `doveconf ssl ssl_min_protocol  should print required and TLSv1.2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; ASVS 9.2.2; NIST SP 800-53 SC-8, SC-13

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[MEDIUM\] Management Panel or Monitoring Ports Network-Reachable (NET-SVC-004)

Category: network. Effort: low. Confidence: high.

Webmin, cPanel, WHM, RabbitMQ Management, Consul, the Docker API with TLS or the Prometheus node exporter listen on network-reachable addresses.

**Why it matters:** Management panels and exporters publish host metrics, service configuration and often a login form. They are a reconnaissance gift and a password guessing target, but they sit behind their own authentication or TLS, so the risk is lower than an open control plane.

- **sockets:** Management panel or monitoring ports are network-reachable
  - `tcp6 [::]:10000 Webmin, bound to all interfaces`
  - `tcp 0.0.0.0:15672 RabbitMQ Management, bound to all interfaces`

**Fix:** Bind the panel or exporter to 127.0.0.1 or a management address, put it behind a reverse proxy with authentication, or restrict the port to monitoring hosts in the firewall.

**Verify:** `ss -tulpn shows these ports on 127.0.0.1, ::1 or a management address only.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-1327; OWASP Top 10 A05:2021; NIST SP 800-53 AC-17, CM-7, SC-7

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

### \[MEDIUM\] Deprecated TLS 1.0 or 1.1 Accepted (NET-TLS-001)

Category: crypto. Effort: low. Confidence: low.

Checks whether local TLS services still accept TLS 1.0 or TLS 1.1.

**Why it matters:** TLS 1.0 and 1.1 were formally deprecated by RFC 8996 in 2021. They rely on outdated constructions (e.g. SHA-1 in the handshake) and fail PCI DSS and most compliance baselines.

- **shop.example.test on port 443:** Deprecated TLS versions accepted
  - `TLS 1.0 accepted`
  - `TLS 1.1 accepted`

**Fix:** Apache: SSLProtocol -all +TLSv1.2 +TLSv1.3 . nginx: ssl\_protocols TLSv1.2 TLSv1.3; . Postfix/Dovecot: set the minimum protocol to TLSv1.2. Reload the service afterwards.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -tls1_1 </dev/null  should fail to connect.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326, CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.3; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] TLS Certificate Expired or Expiring Soon (NET-TLS-002)

Category: crypto. Effort: low. Confidence: high.

Checks the expiry date of the certificate each local TLS service presents.

**Why it matters:** An expired certificate breaks the site for visitors and trains users to click through warnings, which is exactly what an attacker needs for interception.

- **shop.example.test on port 443:** Certificate expires in 8 days
  - `notAfter 2026-10-25`

**Fix:** Renew the certificate (certbot renew, or your provider's process) and make sure renewal is automated (systemctl list-timers | grep certbot).

**Verify:** `echo | openssl s_client -connect 127.0.0.1:443 -servername <site> 2>/dev/null | openssl x509 -noout -enddate`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-298; OWASP Top 10 A02:2021; ASVS 9.2.1; WSTG-CRYP-01; NIST SP 800-53 SC-12, SC-17

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] Self-Signed or Mismatched TLS Certificate (NET-TLS-003)

Category: crypto. Effort: low. Confidence: high.

Checks whether each site presents a self-signed certificate, or one that does not cover its name.

**Why it matters:** Browsers reject these certificates, so visitors either leave or learn to ignore warnings, which removes protection against interception.

- **shop.example.test:** Certificate is self-signed
  - `subject shop.example.test`

**Fix:** Issue a certificate from a public CA covering every site name, for example with certbot --apache or certbot --nginx -d example.com -d www\.example.com.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -servername <site> </dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-295, CWE-297; OWASP Top 10 A02:2021, A07:2021; ASVS 9.2.1; WSTG-CRYP-01; NIST SP 800-53 SC-17

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] Weak TLS 1.2 Cipher Suites Accepted (NET-TLS-004)

Category: crypto. Effort: low. Confidence: high.

A local TLS service accepts an RC4, 3DES, or CBC with SHA-1 cipher suite under TLS 1.2.

**Why it matters:** Obsolete cipher constructions lack modern authenticated encryption and are prohibited by current TLS hardening guidance.

- **shop.example.test on port 443:** Weak TLS 1.2 cipher suite accepted
  - `TLS_RSA_WITH_3DES_EDE_CBC_SHA`

**Fix:** Allow only modern AEAD suites for TLS 1.2 and prefer TLS 1.3. Remove RC4, 3DES, NULL, EXPORT, anonymous, and CBC with SHA-1 suites. Caution: very old clients and devices that support only those suites stop connecting; check the access logs for them first.

**Verify:** `Use an approved local TLS scanner and confirm each prohibited TLS 1.2 suite is rejected. Implicit TLS mail ports 465, 993, and 995 are included; STARTTLS is out of scope.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326, CWE-327; OWASP Top 10 A02:2021; ASVS 9.1.3; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-13

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] TLS Certificate Public Key Too Small (NET-TLS-005)

Category: crypto. Effort: medium. Confidence: low.

A TLS leaf certificate uses RSA below 2048 bits or an elliptic curve below 256 bits.

**Why it matters:** Undersized public keys provide inadequate security strength and may permit certificate impersonation as cryptanalytic capability improves.

- **shop.example.test on port 443:** Leaf certificate public key is too small
  - `key RSA 1024 bits, signature SHA1-RSA`

**Fix:** Replace the certificate and private key with RSA 2048 bits or stronger, or an approved EC key of at least 256 bits.

**Verify:** `Inspect the renewed leaf certificate key size on ports 443, 465, 993, 995, 8443, and 10000. STARTTLS is out of scope.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-326; OWASP Top 10 A02:2021; WSTG-CRYP-01; NIST SP 800-53 SC-12, SC-13

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] SHA-1 Signed TLS Leaf Certificate (NET-TLS-006)

Category: crypto. Effort: medium. Confidence: low.

A TLS leaf certificate is signed using SHA-1.

**Why it matters:** SHA-1 collision attacks invalidate its use for digital signatures and modern clients reject SHA-1 certificates.

- **shop.example.test on port 443:** Leaf certificate uses a SHA-1 signature
  - `key RSA 1024 bits, signature SHA1-RSA`

**Fix:** Reissue the certificate with SHA-256 or stronger and deploy the complete updated chain.

**Verify:** `Inspect the signature algorithm on each implicit TLS service. STARTTLS is out of scope.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-327; OWASP Top 10 A02:2021; WSTG-CRYP-01; NIST SP 800-53 SC-12, SC-13

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] Apache Directory Indexes Enabled in Configuration (WEB-APA-003)

Category: exposure. Effort: low. Confidence: high.

An Options directive enables Indexes (or All), so folders without an index page list their files.

**Why it matters:** Directory listings publish uploads, backups and scripts that were never linked. Debian ships Indexes on in the default /var/www block, so a fresh install fails this until changed.

- **/etc/apache2/apache2.conf:** An Options directive enables Indexes (or All), so folders without an index page list their files.
  - `/etc/apache2/apache2.conf: Options Indexes FollowSymLinks`

**Fix:** Change the block to  Options -Indexes +FollowSymLinks  (or remove Indexes). Where a listing is wanted, protect that Directory with Require.

**Verify:** `apachectl -t -D DUMP_CONFIG 2>/dev/null | grep -n Options  should show no Indexes except with a leading minus.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-548; OWASP Top 10 A01:2021, A05:2021; ASVS 4.3.2; WSTG-CONF-04; NIST SP 800-53 CM-7, AC-3

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[MEDIUM\] Apache Has No Slow Request Protection (WEB-APA-011)

Category: config. Effort: low. Confidence: high.

mod\_reqtimeout does not appear to be loaded and no RequestReadTimeout is set, so a client that sends its request very slowly can hold a worker for the whole Timeout.

**Why it matters:** Apache keeps one worker busy for each open request. Without minimum read rates, a few clients sending headers a byte at a time (the Slowloris technique) can use up every worker and take the site offline. The module is enabled by default on most distributions, so its absence usually means it was removed.

- **/etc/apache2/apache2.conf:** mod\_reqtimeout does not appear to be loaded and no RequestReadTimeout is set, so a client that sends its request very slowly can hold a worker for the whole Timeout.
  - `no reqtimeout_module and no RequestReadTimeout directive`

**Fix:** Enable the module (Debian and Ubuntu: a2enmod reqtimeout; other systems: LoadModule reqtimeout\_module modules/mod\_reqtimeout.so) and keep or set  RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500 , then reload.

**Verify:** `apachectl -M 2>/dev/null | grep reqtimeout`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; OWASP Top 10 A05:2021; NIST SP 800-53 SC-5, CM-6

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

**Learn:** P25: Availability Without Load Testing, https://openvapt.com/academy/p25.html (offline: `openvapt learn P25`)

### \[MEDIUM\] Application .env File Readable by All Users (WEB-APP-002)

Category: access. Effort: low. Confidence: high.

Checks the permission bits of framework .env files for world-read access.

**Why it matters:** .env files hold database passwords, API tokens and signing keys in plain text. A world-readable .env hands every account on the server, including other websites' PHP-FPM pools, the keys to this application.

- **/srv/shop-crm/.env:** .env file is readable by all users
  - `mode 0644 is readable by all users`
- **/srv/shop-laravel/.env:** .env file is readable by all users
  - `mode 0644 is readable by all users`
- **/var/www/shop/.env:** .env file is readable by all users
  - `mode 0644 is readable by all users`

**Fix:** chmod 640 .env && chown \<app-user\>:\<web-group\> .env  (or 600 when the application runs as its own user). Rotate the secrets if other people have had shell access.

**Verify:** `stat -c '%a %U:%G %n' .env  shows 640 or 600.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-312; OWASP Top 10 A05:2021, A02:2021; ASVS 14.1.3; WSTG-CONF-09; NIST SP 800-53 AC-3, SC-28

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Rails Production Does Not Force HTTPS (WEB-APP-005)

Category: webapp. Effort: low. Confidence: high.

Checks config/environments/production.rb for config.force\_ssl = true.

**Why it matters:** force\_ssl redirects plain HTTP to HTTPS, marks cookies Secure and sends Strict-Transport-Security. Without it a session cookie can travel over HTTP once and be read on the network, even when the site also works over HTTPS.

- **/srv/shop-rails/config/environments/production.rb:** Rails production does not force HTTPS
  - `config.force_ssl is not true`

**Fix:** Set config.force\_ssl = true in config/environments/production.rb (and config.assume\_ssl = true behind a TLS-terminating proxy), then restart the application.

**Verify:** `grep -n force_ssl config/environments/production.rb  shows true, and curl -I http://<site>/ returns a 301 to https.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-614; OWASP Top 10 A02:2021, A05:2021; ASVS 9.1.1, 3.4.1; WSTG-CRYP-03; NIST SP 800-53 SC-8, CM-6

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Git Repository Stored Inside a Web Root (WEB-APP-009)

Category: exposure. Effort: low. Confidence: high.

Looks on disk for a .git folder directly inside each web document root.

**Why it matters:** A .git folder in the document root is one web server rule away from being downloadable, which rebuilds the full source and its history, often with committed credentials. WEB-EXP-001 tests whether it is already reachable; this check finds it before a configuration change exposes it.

- **/var/www/shop:** A .git repository is stored inside the web root
  - `/var/www/shop/.git`

**Fix:** Deploy from a build step that copies only the needed files (git archive, rsync --exclude .git, or a CI artefact), or point the document root at a public/ sub-folder as Laravel, Symfony and Rails expect. Remove the .git folder from the web root and rotate any secret ever committed.

**Verify:** `ls -d <docroot>/.git  prints nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-527, CWE-538; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Framework Writable Folders Open to All Users (WEB-APP-010)

Category: access. Effort: low. Confidence: high.

Checks Laravel and Symfony cache, log and storage folders for world-write permission.

**Why it matters:** chmod -R 777 storage is a common fix for permission errors. Laravel's bootstrap/cache and Symfony's var/cache hold compiled PHP that the framework includes on every request, so any local user who can write there runs code as the web server. Logs and sessions in storage are readable and forgeable too.

- **/srv/shop-laravel/.env:** Framework cache or storage folders are world-writable
  - `0777 /srv/shop-laravel/storage`
  - `0777 /srv/shop-laravel/bootstrap/cache`

**Fix:** chmod -R o-w storage bootstrap/cache var  and give the web server group write access instead: chgrp -R www-data storage bootstrap/cache && chmod -R g+w storage bootstrap/cache.

**Verify:** `find storage bootstrap/cache var -perm -0002 -type d  prints nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-94; OWASP Top 10 A05:2021; ASVS 14.1.3; WSTG-CONF-09; NIST SP 800-53 AC-3, AC-6, CM-5

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Cookies Missing Secure, HttpOnly or SameSite (WEB-CK-001)

Category: webapp. Effort: medium. Confidence: high.

Checks the security flags on cookies the home page sets.

**Why it matters:** Without Secure, cookies leak over plain HTTP. Without HttpOnly, any XSS bug can steal sessions. Without SameSite, cross-site request forgery is easier.

- **shop.example.test:** Cookies are missing security flags
  - `PHPSESSID: missing Secure, HttpOnly, SameSite`

**Fix:** PHP: session.cookie\_secure=1, session.cookie\_httponly=1, session.cookie\_samesite=Lax in php.ini . Frameworks have equivalent settings (e.g. Laravel config/session.php). Use HttpOnly for every cookie JavaScript does not need to read.

**Verify:** `curl -sI https://<site>/ | grep -i set-cookie`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-614, CWE-1004, CWE-1275; OWASP Top 10 A05:2021, A07:2021; ASVS 3.4.1, 3.4.2, 3.4.3; WSTG-SESS-02; NIST SP 800-53 SC-8, SC-23

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

### \[MEDIUM\] WordPress Shows Debug Output to Visitors (WEB-CMS-006)

Category: exposure. Effort: low. Confidence: high.

Checks wp-config.php for WP\_DEBUG enabled without WP\_DEBUG\_DISPLAY set to false.

**Why it matters:** With WP\_DEBUG on and display not turned off, PHP notices and errors are printed into pages, revealing server paths, plugin names and sometimes database details to anyone browsing the site.

- **/var/www/shop/wp-config.php:** WordPress displays debug output
  - `WP_DEBUG is true and WP_DEBUG_DISPLAY is not false`

**Fix:** In production set define('WP\_DEBUG', false);. If you need logs, use define('WP\_DEBUG\_LOG', '/path/outside/webroot/wp-debug.log'); together with define('WP\_DEBUG\_DISPLAY', false);.

**Verify:** `grep -E "WP_DEBUG(_DISPLAY)?'" wp-config.php`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-209, CWE-489; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-ERRH-01; NIST SP 800-53 SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[MEDIUM\] WordPress Automatic Updates Disabled (WEB-CMS-008)

Category: patch. Effort: low. Confidence: high.

Checks whether wp-config.php turns off WordPress background updates, including security releases.

**Why it matters:** WordPress ships security fixes as automatic minor updates. Turning the updater off means the site stays vulnerable until someone remembers to update by hand, which is how most compromised WordPress sites fall behind.

- **/var/www/shop/wp-config.php:** WordPress automatic updates are disabled
  - `AUTOMATIC_UPDATER_DISABLED is true`

**Fix:** Remove those lines, or set define('WP\_AUTO\_UPDATE\_CORE', 'minor'); so at least security releases install automatically. Keep backups and enable plugin auto-updates where practical.

**Verify:** `Dashboard, Updates shows that automatic updates for maintenance and security releases are on.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[MEDIUM\] Joomla Debug or Verbose Error Reporting Enabled (WEB-CMS-009)

Category: exposure. Effort: low. Confidence: high.

Checks Joomla configuration.php for debug mode or maximum/development error reporting.

**Why it matters:** Joomla's debug console and verbose error reporting print queries, file paths and configuration details into pages that visitors can see.

- **/var/www/shop/configuration.php:** Joomla debug output is enabled
  - `$debug is enabled`
  - `$error_reporting is maximum`

**Fix:** In System, Global Configuration set Debug System to No and Error Reporting to None (or System Default), or set public $debug = false; and public $error\_reporting = 'none'; in configuration.php.

**Verify:** `grep -E 'debug|error_reporting' configuration.php`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-209, CWE-489; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-ERRH-01; NIST SP 800-53 SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[MEDIUM\] Drupal Trusted Host Patterns Not Set (WEB-CMS-013)

Category: webapp. Effort: low. Confidence: high.

Checks Drupal settings.php for the trusted\_host\_patterns setting.

**Why it matters:** Without trusted\_host\_patterns Drupal builds absolute links, including password reset links, from whatever Host header the request carries. An attacker who sends a crafted Host header can poison caches and redirect reset links to a domain they control.

- **/var/www/shop/sites/default/settings.php:** Drupal accepts any Host header
  - `$settings['trusted_host_patterns'] is not set`
- **/var/www/shop/sites/intranet/settings.php:** Drupal accepts any Host header
  - `$settings['trusted_host_patterns'] is not set`

**Fix:** Add to settings.php:  $settings\['trusted\_host\_patterns'\] = \['^www\\.example\\.com$', '^example\\.com$'\];  then clear caches (drush cr). The status report page should no longer warn about trusted hosts.

**Verify:** `drush status-report | grep -i 'trusted host'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-644, CWE-346; OWASP Top 10 A05:2021; ASVS 14.4.1; WSTG-INPV-17; NIST SP 800-53 CM-6, SI-10

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Magento Not in Production Mode (WEB-CMS-015)

Category: exposure. Effort: low. Confidence: high.

Checks app/etc/env.php for a MAGE\_MODE other than production.

**Why it matters:** Developer and default modes show exceptions with stack traces and file paths to shoppers, disable static file signing and compile code on the fly. Adobe documents production as the only mode for a live store.

- **/var/www/shop/app/etc/env.php:** MAGE\_MODE is developer

**Fix:** bin/magento deploy:mode:set production  (which also deploys static content and compiles DI), then clear caches. Keep developer mode for staging only.

**Verify:** `bin/magento deploy:mode:show  prints production.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-209; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-CONF-02, WSTG-ERRH-01; NIST SP 800-53 CM-6, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Ghost Configured With a Plain HTTP Site URL (WEB-CMS-016)

Category: webapp. Effort: low. Confidence: high.

Checks Ghost config.production.json for a url that starts with http:\/\/.

**Why it matters:** Ghost builds every link, including the admin login and member sign-in links, from the configured url. With http:\/\/ the admin session and members' tokens travel in the clear even when HTTPS is available, and browsers see mixed content.

- **/srv/shop-blog/config.production.json:** Ghost url uses plain http:\/\/

**Fix:** ghost config url https:\/\/\<your-domain\>  (or edit config.production.json), set up TLS with ghost setup ssl or your reverse proxy, then ghost restart.

**Verify:** `ghost config get url  prints an https:// address.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021, A05:2021; ASVS 9.1.1; WSTG-CRYP-03; NIST SP 800-53 SC-8, CM-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[MEDIUM\] Cross-Origin Resource Sharing Allows Any Origin With Credentials (WEB-COR-001)

Category: webapp. Effort: low. Confidence: high.

Checks the home page's CORS headers for a wildcard origin combined with credentials, or an origin of null.

**Why it matters:** A permissive CORS policy can let any website read responses on behalf of a logged-in visitor, exposing personal data and tokens.

- **shop.example.test:** Permissive CORS policy
  - `Access-Control-Allow-Origin: *`
  - `Access-Control-Allow-Credentials: true`

**Fix:** Return Access-Control-Allow-Origin only for an explicit allow-list of trusted origins, never '\*' or 'null' together with credentials, and add Vary: Origin.

**Verify:** `curl -sI https://<site>/ | grep -i access-control`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-942; OWASP Top 10 A05:2021, A01:2021; ASVS 14.5.3; WSTG-CLNT-07; NIST SP 800-53 AC-4, CM-6

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

### \[MEDIUM\] Archives or Certificates Inside a Web Root (WEB-DOC-003)

Category: exposure. Effort: low. Confidence: high.

Lists archives (\*.tar.gz, \*.tgz, \*.tar, \*.7z) and certificate files (\*.pem, \*.crt) inside each web document root for review, outside download folders.

**Why it matters:** A tarball inside a web root is often a site or database backup left after a migration, and a .pem file sometimes carries the private key next to the certificate. Both are downloadable by anyone who guesses the name. But download sites publish archives on purpose and public certificates are harmless, so this is a Medium review item, not a High finding; names that clearly mean backup, dump or key are rated High by WEB-DOC-001.

- **/var/www/shop:** 2 archive or certificate file(s) inside the web root to review
  - `/var/www/shop/shop-2026.tar.gz (archive; confirm it is a published download, not a site backup)`
  - `/var/www/shop/cert.pem (certificate or key material; confirm it holds no private key)`
  - `/var/www/shop/downloads/catalogue.tar.gz (in a download folder, expected)`

**Fix:** Confirm each file is meant to be public. Move backups and anything holding a private key outside the document root, rotate any exposed key, and keep published downloads in a dedicated folder such as /downloads/.

**Verify:** `Re-run this check; only files you have confirmed as intended downloads should remain, in a download folder.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-538, CWE-552; OWASP Top 10 A05:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 SC-28, CM-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] PHP Information Page Exposed (WEB-EXP-003)

Category: exposure. Effort: low. Confidence: high.

Checks for phpinfo() pages left on the site.

**Why it matters:** phpinfo() reveals PHP and module versions, file paths, environment variables and sometimes credentials, giving attackers a detailed map of the server.

- **shop.example.test:** Exposed: /phpinfo.php (HTTP 200)
  - `/phpinfo.php (HTTP 200)`

**Fix:** Delete the file. Never leave diagnostic pages on a production server.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/phpinfo.php  should print 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] Directory Listing Enabled (WEB-EXP-005)

Category: exposure. Effort: low. Confidence: high.

Checks whether the web server lists folder contents when there is no index page.

**Why it matters:** Directory listings reveal every file in a folder, including backups, uploads and scripts never meant to be linked.

- **shop.example.test:** Exposed: /uploads/ (HTTP 200)
  - `/uploads/ (HTTP 200)`

**Fix:** Apache: Options -Indexes . nginx: autoindex off; (the default). Add an empty index.html to folders that must stay browsable-free.

**Verify:** `curl -s https://<site>/uploads/ | grep -i 'index of'  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-548; OWASP Top 10 A01:2021, A05:2021; ASVS 4.3.2; WSTG-CONF-04; NIST SP 800-53 CM-7, AC-3

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] Web Server Status Page Exposed (WEB-EXP-006)

Category: exposure. Effort: low. Confidence: high.

Checks whether Apache server-status/server-info or nginx stub\_status are reachable for each site.

**Why it matters:** Status pages show live requests (including URLs with tokens), client IPs, virtual hosts and modules.

- **shop.example.test:** Exposed: /server-status (HTTP 200), no local-only restriction (Require local, Require ip 127.0.0.1 or allow 127.0.0.1 with deny all) found in the web server configuration; the scan requests from 127.0.0.1, so confirm from another machine
  - `/server-status (HTTP 200)`
  - `no local-only restriction (Require local, Require ip 127.0.0.1 or allow 127.0.0.1 with deny all) found in the web server configuration; the scan requests from 127.0.0.1, so confirm from another machine`

**Fix:** Restrict to administrators: \<Location /server-status\> Require local \</Location\> (Apache), or allow 127.0.0.1; deny all; (nginx). Remove the module if unused.

**Verify:** `From another machine: curl -s -o /dev/null -w '%{http_code}' https://<site>/server-status  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] Database Administration Tool Exposed (WEB-EXP-011)

Category: exposure. Effort: low. Confidence: high.

Checks whether phpMyAdmin or Adminer login pages are publicly reachable.

**Why it matters:** Database admin tools are among the most attacked paths on the internet: they invite password guessing and their own vulnerabilities give direct database access.

- **shop.example.test:** Exposed: /phpmyadmin/ (HTTP 200)
  - `/phpmyadmin/ (HTTP 200)`

**Fix:** Remove the tool, or restrict it to administrators by IP allow-list, VPN or an extra authentication layer, and keep it updated.

**Verify:** `From outside your network, the paths should return 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-306; OWASP Top 10 A05:2021, A07:2021; WSTG-CONF-05; NIST SP 800-53 CM-7, AC-3

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] Missing HTTP Strict Transport Security (HSTS) (WEB-HDR-001)

Category: webapp. Effort: low. Confidence: high.

Checks that HTTPS sites tell browsers to always use HTTPS, for at least six months.

**Why it matters:** Without HSTS, the first visit or any typed http:\/\/ link can be intercepted and downgraded to plain HTTP (SSL stripping) on public Wi-Fi.

- **shop.example.test:** Strict-Transport-Security header is missing

**Fix:** Apache: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" . nginx: add\_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; . Only add 'preload' once every subdomain supports HTTPS.

**Verify:** `curl -sI https://<site>/ | grep -i strict-transport-security`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319, CWE-523; OWASP Top 10 A05:2021; ASVS 14.4.5; WSTG-CONF-07; NIST SP 800-53 SC-8

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[MEDIUM\] Missing Content Security Policy (WEB-HDR-002)

Category: webapp. Effort: medium. Confidence: high.

Checks that each site sends a Content-Security-Policy header.

**Why it matters:** A CSP limits where scripts may load from, turning many cross-site scripting (XSS) bugs from account takeover into a blocked request.

- **shop.example.test:** Content-Security-Policy header is missing

**Fix:** Start with a report-only policy to find what the site loads, then enforce, for example: Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

**Verify:** `curl -sI https://<site>/ | grep -i content-security-policy`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1021, CWE-79; OWASP Top 10 A03:2021, A05:2021; ASVS 14.4.3; WSTG-CONF-12; NIST SP 800-53 SI-10

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[MEDIUM\] Site Can Be Framed (Clickjacking) (WEB-HDR-003)

Category: webapp. Effort: low. Confidence: high.

Checks that each site forbids being embedded in other sites' frames.

**Why it matters:** If a page can be framed, an attacker can overlay it invisibly and trick users into clicking buttons (clickjacking), such as 'delete' or 'transfer'.

- **shop.example.test:** Neither X-Frame-Options nor CSP frame-ancestors is set

**Fix:** Send  Content-Security-Policy: frame-ancestors 'self'  (modern) and  X-Frame-Options: SAMEORIGIN  (older browsers).

**Verify:** `curl -sI https://<site>/ | grep -Ei 'x-frame-options|frame-ancestors'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1021; OWASP Top 10 A04:2021; ASVS 14.4.7; WSTG-CLNT-09; NIST SP 800-53 SC-18

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[MEDIUM\] nginx Directory Listing Enabled in Configuration (WEB-NGX-002)

Category: exposure. Effort: low. Confidence: high.

An nginx server or location block sets autoindex on.

**Why it matters:** Directory listings publish every file in a folder, including uploads, backups and scripts that were never linked. The configuration view finds this even for folders the probe check did not request.

- **/etc/nginx/nginx.conf:** An nginx server or location block sets autoindex on.
  - `/etc/nginx/nginx.conf: autoindex on`

**Fix:** Remove  autoindex on;  or set  autoindex off;  in that block. Where a listing is really needed, protect the location with auth\_basic or an allow list.

**Verify:** `nginx -T | grep -n autoindex  should show only off or nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-548; OWASP Top 10 A01:2021, A05:2021; ASVS 4.3.2; WSTG-CONF-04; NIST SP 800-53 CM-7, AC-3

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[MEDIUM\] nginx Serves Hidden Files and Folders (WEB-NGX-007)

Category: exposure. Effort: low. Confidence: high.

No location block denies paths that begin with a dot, so .git, .env, .svn and .htaccess files under the document root are served if present.

**Why it matters:** Dot files hold repository history, credentials and server rules. One deny rule for every path segment starting with a dot removes a whole class of leaks, including files deployed by mistake later.

- **/etc/nginx/nginx.conf:** No location block denies paths that begin with a dot, so .git, .env, .svn and .htaccess files under the document root are served if present.
  - `no  location ~ /\.  block found`

**Fix:** Add to every server block:  location \~ /\\. { deny all; access\_log off; log\_not\_found off; }  and keep .well-known reachable with a more specific  location ^\~ /.well-known/  block before it.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/.git/HEAD  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-538, CWE-527; OWASP Top 10 A05:2021; ASVS 12.5.1, 14.3.3; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[MEDIUM\] PHP Displays Errors to Visitors (WEB-PHP-001)

Category: config. Effort: low. Confidence: high.

Checks whether PHP prints error messages into web pages.

**Why it matters:** Error messages reveal file paths, SQL queries and code structure, and can leak data. They help attackers turn a small bug into a working exploit.

- **php:** display\_errors = 1

**Fix:** In php.ini set  display\_errors = Off  and  log\_errors = On , then restart PHP-FPM or the web server.

**Verify:** `php -r "echo ini_get('display_errors');"  and check the FPM pool's php.ini or php_admin_value settings.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-209; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-ERRH-01; NIST SP 800-53 SI-11, CM-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] PHP Session Cookies Not Hardened (WEB-PHP-005)

Category: webapp. Effort: low. Confidence: high.

Checks PHP's session settings: HttpOnly and SameSite cookies and strict session id mode.

**Why it matters:** Session cookies are the keys to logged-in accounts. Without HttpOnly any XSS bug can steal them; without strict mode PHP accepts attacker-chosen session ids (session fixation).

- **php:** PHP session cookies are not hardened
  - `session.cookie_httponly = Off`
  - `session.use_strict_mode = 0`
  - `session.cookie_samesite is empty`

**Fix:** In php.ini set  session.cookie\_httponly = 1 ,  session.use\_strict\_mode = 1 ,  session.cookie\_samesite = Lax  and, for HTTPS sites,  session.cookie\_secure = 1 , then restart PHP-FPM.

**Verify:** `php -r "echo ini_get('session.cookie_httponly'), ini_get('session.use_strict_mode'), ini_get('session.cookie_samesite');"`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1004, CWE-384; OWASP Top 10 A07:2021; ASVS 3.4.2, 3.2.1; WSTG-SESS-02, WSTG-SESS-03; NIST SP 800-53 SC-23, CM-6

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[MEDIUM\] Django Accepts Any Host Header (WEB-PY-002)

Category: webapp. Effort: low. Confidence: high.

Checks Django settings for ALLOWED\_HOSTS = \['\*'\].

**Why it matters:** ALLOWED\_HOSTS protects against Host header attacks. With a wildcard, an attacker can make Django build password-reset and other absolute links that point at the attacker's domain, and can poison shared caches.

- **/srv/shopadmin/shopadmin:** Django accepts any Host header
  - `/srv/shopadmin/shopadmin/settings.py: ALLOWED_HOSTS allows any host`

**Fix:** List the real domain names: ALLOWED\_HOSTS = \['example.com', 'www\.example.com'\], and restart the application.

**Verify:** `python manage.py check --deploy`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-346; OWASP Top 10 A05:2021; WSTG-INPV-17; NIST SP 800-53 CM-6, SI-10

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[MEDIUM\] Python Development Server Used in a Service (WEB-PY-005)

Category: config. Effort: medium. Confidence: high.

Checks whether a service runs a development web server such as manage.py runserver, flask run or python -m http.server.

**Why it matters:** Development servers are documented as unsuitable for production: they have not been security-audited, handle load and slow clients poorly and often run with debugging features. python -m http.server publishes a whole folder.

- **systemd units:** Services run Python development servers
  - `shop-admin.service: runs a development server (manage.py runserver)`
  - `shop-reports.service: runs a development server (flask run)`

**Fix:** Serve the application with a production server (Gunicorn or uWSGI for WSGI, Uvicorn without --reload or Hypercorn for ASGI) bound to 127.0.0.1, behind nginx or Apache. Serve static files from the web server.

**Verify:** `systemctl show -p ExecStart <unit>  shows a production server.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-1188; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[MEDIUM\] Site Available Over Plain HTTP Without Redirect (WEB-TLS-001)

Category: crypto. Effort: low. Confidence: high.

Checks that every site redirects http:\/\/ requests to https:\/\/.

**Why it matters:** Pages served over plain HTTP can be read and changed by anyone on the network path, including injected malware and stolen session cookies.

- **shop.example.test:** Site content is served over plain HTTP
  - `HTTP status 200`

**Fix:** Apache: in the port 80 vhost add  Redirect permanent / https:\/\/\<site\>/ . nginx: return 301 https:\/\/$host$request\_uri; . Get a free certificate with certbot if HTTPS is not set up yet.

**Verify:** `curl -sI http://<site>/  should show a 301 with Location: https://...`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-319; OWASP Top 10 A02:2021; ASVS 9.1.1; WSTG-CRYP-01; NIST SP 800-53 SC-8

**Learn:** P4: TLS, Certificates and HTTPS, https://openvapt.com/academy/p4.html (offline: `openvapt learn P4`)

### \[MEDIUM\] Tomcat Shutdown Port Uses the Default Command (WEB-TOM-003)

Category: config. Effort: low. Confidence: high.

Checks whether Tomcat's shutdown port is enabled with the well-known default command SHUTDOWN.

**Why it matters:** Anyone who can connect to the shutdown port (8005 by default) and send the shutdown string stops Tomcat. With the default string, every local user and every compromised process on the machine can take the applications offline.

- **/etc/tomcat9:** Tomcat shutdown port uses the default command
  - `<Server port="8005" shutdown="SHUTDOWN">`

**Fix:** Disable the port with \<Server port="-1" ...\> and stop Tomcat through systemd, or set shutdown to a long random string. Restart Tomcat.

**Verify:** `grep '<Server' <conf>/server.xml  shows port="-1".`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 CM-6, CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[MEDIUM\] Tomcat Example and Documentation Applications Deployed (WEB-TOM-005)

Category: exposure. Effort: low. Confidence: high.

Checks whether the examples or docs web applications that ship with Tomcat are deployed.

**Why it matters:** The sample applications are written to demonstrate features, not to be safe. Some (such as the session example) let visitors change server-side session data, and all of them reveal the exact Tomcat version.

- **/etc/tomcat9:** Tomcat sample applications are deployed
  - `/var/lib/tomcat9/webapps/examples is deployed`

**Fix:** Delete the examples and docs folders from webapps (or apt remove tomcatN-examples tomcatN-docs) and restart Tomcat.

**Verify:** `ls <webapps>  lists only your own applications (and ROOT).`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; OWASP Top 10 A05:2021; WSTG-CONF-02; NIST SP 800-53 CM-7

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[LOW\] Audit Daemon Not Enabled at Boot (HOST-AUD-001)

Category: logging. Effort: low. Confidence: high.

Checks that the audit daemon (auditd) is set to start at every boot, not only running now.

**Why it matters:** An audit daemon that was started by hand stops recording at the next reboot, leaving a gap exactly when an attacker who forced a restart would want one. HOST-LOG-002 reports whether auditd is running; this check reports whether it will come back.

- **auditd:** auditd is disabled, so it will not record after the next reboot

**Fix:** systemctl enable auditd  (on some distributions: systemctl enable --now auditd). Then confirm the rules in /etc/audit/rules.d load at boot with augenrules --check.

**Verify:** `systemctl is-enabled auditd  should print enabled.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Identity Files Not Audited (HOST-AUD-002)

Category: logging. Effort: low. Confidence: high.

Checks that changes to the account, group, password and sudo files are recorded by the audit system.

**Why it matters:** An attacker who adds an account, joins a privileged group or grants themselves sudo edits one of a handful of files. A watch rule on each makes that change an audit event with the user, process and time attached; without it the change leaves no trace beyond the file itself.

- **audit rules:** Audit rules are missing
  - `no watch on /etc/passwd`
  - `no watch on /etc/group`
  - `no watch on /etc/shadow`
  - `no watch on /etc/gshadow`
  - `no watch on /etc/sudoers`
  - `no watch on /etc/sudoers.d`

**Fix:** Add to /etc/audit/rules.d/50-identity.rules:  -w /etc/passwd -p wa -k identity  and the same for /etc/group, /etc/shadow, /etc/gshadow, /etc/sudoers and /etc/sudoers.d/ , then run augenrules --load.

**Verify:** `auditctl -l | grep -E 'passwd|shadow|sudoers'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12, AC-2(4)

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Login and Logout Events Not Audited (HOST-AUD-003)

Category: logging. Effort: low. Confidence: high.

Checks that the files recording logins, failed logins and sessions are watched by the audit system.

**Why it matters:** Login records are the first thing an investigator asks for and the first thing an intruder cleans. Watching lastlog, the failed-login store and the wtmp and btmp session files makes tampering itself an audit event.

- **audit rules:** Audit rules are missing
  - `no watch on /var/log/lastlog`
  - `no watch on the failed-login store (/var/run/faillock or /var/log/faillog)`
  - `no watch on /var/log/wtmp`
  - `no watch on /var/log/btmp`

**Fix:** Add to /etc/audit/rules.d/50-login.rules:  -w /var/log/lastlog -p wa -k logins ,  -w /var/run/faillock -p wa -k logins ,  -w /var/log/wtmp -p wa -k session ,  -w /var/log/btmp -p wa -k session , then augenrules --load.

**Verify:** `auditctl -l | grep -E 'lastlog|faillock|wtmp|btmp'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12, AC-7

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] System Time Changes Not Audited (HOST-AUD-004)

Category: logging. Effort: low. Confidence: high.

Checks that the audit system records changes to the system clock and time zone.

**Why it matters:** Moving the clock is a classic way to confuse log timelines, forge timestamps or defeat certificate and token expiry. Auditing the time-setting system calls and /etc/localtime attributes every change to a user and process.

- **audit rules:** Audit rules are missing
  - `no rule for system call adjtimex`
  - `no rule for system call settimeofday`
  - `no rule for system call clock_settime`
  - `no watch on /etc/localtime`

**Fix:** Add to /etc/audit/rules.d/50-time.rules:  -a always,exit -F arch=b64 -S adjtimex,settimeofday,clock\_settime -k time-change  (and the same with arch=b32), plus  -w /etc/localtime -p wa -k time-change , then augenrules --load.

**Verify:** `auditctl -l | grep -E 'settimeofday|localtime'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-8, AU-12

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Kernel Module Loading Not Audited (HOST-AUD-005)

Category: logging. Effort: low. Confidence: high.

Checks that loading and unloading kernel modules is recorded by the audit system.

**Why it matters:** A rootkit is usually a kernel module. Auditing init\_module, finit\_module and delete\_module (or the kmod program) records the moment one is loaded, which is often the only evidence that survives.

- **audit rules:** Audit rules are missing
  - `no rule for init_module and delete_module, and no rule on the kmod program`

**Fix:** Add to /etc/audit/rules.d/50-modules.rules:  -a always,exit -F arch=b64 -S init\_module,finit\_module,delete\_module -k modules  (and arch=b32), or  -a always,exit -F path=/usr/bin/kmod -F perm=x -F auid\>=1000 -F auid\!=unset -k modules , then augenrules --load.

**Verify:** `auditctl -l | grep -E 'init_module|kmod'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12, CM-7

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Privileged Command Use Not Audited (HOST-AUD-006)

Category: logging. Effort: low. Confidence: high.

Checks that the audit system records when ordinary users run privileged (setuid or setgid) programs.

**Why it matters:** Programs such as sudo, su, passwd and mount run with elevated rights. Recording each execution by a real user (auid at or above the first normal user id) shows who used privilege and when, which is the core of a usable audit trail.

- **audit rules:** Audit rules are missing
  - `no execution rule with perm=x and an auid>= filter`

**Fix:** Generate rules for every setuid and setgid program:  find / -xdev \\( -perm -4000 -o -perm -2000 \\) -type f | awk '{print "-a always,exit -F path=" $1 " -F perm=x -F auid\>=1000 -F auid\!=unset -k privileged"}' \> /etc/audit/rules.d/50-privileged.rules  then augenrules --load.

**Verify:** `auditctl -l | grep 'perm=x'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12, AC-6(9)

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Audit Configuration Not Immutable (HOST-AUD-007)

Category: logging. Effort: low. Confidence: high.

Checks that the audit rule set ends with -e 2, which locks the rules until the next reboot.

**Why it matters:** Without the immutable flag, anyone who gains root can delete the audit rules (auditctl -D) and work unobserved. With -e 2 the rules can only be changed by rebooting, which is itself a loud, logged event.

- **audit rules:** Audit rules are missing
  - `last rule is not -e 2 (found: -f 1)`

**Fix:** Add  -e 2  as the last line of the last file in /etc/audit/rules.d (for example 99-finalize.rules), then augenrules --load and reboot. Plan rule changes before enabling it: every later change needs a reboot.

**Verify:** `auditctl -s | grep enabled  should print enabled 2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-9, AU-12

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Audit Log Files Readable by Others (HOST-AUD-008)

Category: logging. Effort: low. Confidence: high.

Checks that the audit log directory and files can be read only by root.

**Why it matters:** Audit logs record which user ran which command with which arguments, including file names and sometimes secrets passed on a command line. They are also the record an intruder most wants to edit. Both reasons call for root-only access.

- **/var/log/audit:** Audit logs are accessible beyond root
  - `directory /var/log/audit mode 0755 uid 0 (need 0750 or stricter, owned by root)`
  - `/var/log/audit/audit.log mode 0644 uid 0 (need 0640 or stricter, owned by root)`

**Fix:** chmod 0750 /var/log/audit && chmod 0600 /var/log/audit/\* && chown -R root:root /var/log/audit , and set log\_group = root in /etc/audit/auditd.conf so new files keep these modes.

**Verify:** `stat -c '%a %U %n' /var/log/audit /var/log/audit/*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-532, CWE-732; OWASP Top 10 A09:2021; NIST SP 800-53 AU-9, AC-6

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Audit Daemon Ignores a Full Log Disk (HOST-AUD-009)

Category: logging. Effort: low. Confidence: high.

Checks that auditd is told what to do when its log file is full or the disk runs low, rather than silently dropping events.

**Why it matters:** With max\_log\_file\_action = ignore the log grows until the disk is full; with suspend auditing simply stops. An unset space\_left\_action means nobody is warned before that happens. Either way an attacker who fills the disk switches auditing off.

- **/etc/audit/auditd.conf:** auditd will drop or stop auditing when the log disk fills
  - `max_log_file_action = ignore`
  - `space_left_action = ignore`
  - `admin_space_left_action is not set`

**Fix:** In /etc/audit/auditd.conf set  max\_log\_file\_action = keep\_logs  (or rotate with enough num\_logs),  space\_left\_action = email  (or syslog or exec), and  admin\_space\_left\_action = halt  or  single  as policy requires, then systemctl restart auditd. Caution: admin\_space\_left\_action = halt or single stops the server or drops it to single-user mode when the audit disk fills; choose it only where losing audit records is worse than downtime, and give /var/log/audit room and monitoring first.

**Verify:** `grep -E '^(max_log_file_action|space_left_action|admin_space_left_action)' /etc/audit/auditd.conf`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-4, AU-5

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Journal Not Persistent or Not Compressed (HOST-AUD-010)

Category: logging. Effort: low. Confidence: high.

Checks that systemd-journald is configured to keep the journal on disk and to compress it.

**Why it matters:** Storage=volatile keeps the journal only in memory, so a reboot (or a crash an attacker caused) erases the evidence. Compression keeps a persistent journal small enough to retain for weeks. HOST-LOG-001 looks for the journal directory; this check reads the configuration that decides what happens to it.

- **journald.conf:** The journal is not kept persistently and compressed
  - `Storage=volatile (/etc/systemd/journald.conf): the journal is lost at reboot`
  - `Compress=no (/etc/systemd/journald.conf)`

**Fix:** In /etc/systemd/journald.conf set  Storage=persistent  and  Compress=yes , create the directory with  mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal , then systemctl restart systemd-journald.

**Verify:** `journalctl --header | head  and  ls -d /var/log/journal`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-4, AU-9, AU-11

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Logs Not Forwarded to a Remote Collector (HOST-AUD-011)

Category: logging. Effort: medium. Confidence: high.

Checks that system logs are sent to another machine, so an intruder with root cannot erase the only copy.

**Why it matters:** Whoever gains root on a server can delete or edit its local logs. A copy that already left the machine, to a log server or a SIEM, is the record an investigation can trust. NIST SP 800-92 treats centralised log collection as a basic log management control.

- **syslog:** No remote log destination is configured; the only copy of the logs is on this machine

**Fix:** rsyslog: create /etc/rsyslog.d/90-remote.conf with  \*.\* action(type="omfwd" target="logs.example.internal" port="6514" protocol="tcp" StreamDriver="gtls" StreamDriverMode="1" StreamDriverAuthMode="x509/name")  and restart rsyslog. Or configure systemd-journal-upload with URL= pointing at a journal-remote collector. Prefer TLS so logs are not readable in transit.

**Verify:** `grep -rE '^[^#].*@|omfwd' /etc/rsyslog.conf /etc/rsyslog.d/  or  grep '^URL=' /etc/systemd/journal-upload.conf`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-9(2), AU-6, SI-4

**Learn:** P21: Audit Logging and Monitoring Depth, https://openvapt.com/academy/p21.html (offline: `openvapt learn P21`)

### \[LOW\] Kernel Module Loading Unrestricted (HOST-BOOT-005)

Category: config. Effort: medium. Confidence: high.

Checks that the running kernel refuses unsigned modules or has module loading switched off entirely.

**Why it matters:** A root-level attacker who can load any module owns the kernel: rootkits hide processes and files from every tool on the box. Signature enforcement or kernel.modules\_disabled = 1 after boot makes that persistence step fail.

- **kernel.modules\_disabled:** Any module can be loaded: modules\_disabled = 0 and no signature enforcement or lockdown

**Fix:** Boot with  module.sig\_enforce=1  (or  lockdown=integrity) on the kernel command line and sign any out-of-tree modules, or set  kernel.modules\_disabled = 1  from a late boot service once every needed module is loaded (it cannot be turned back on without a reboot).

**Verify:** `sysctl kernel.modules_disabled; cat /sys/module/module/parameters/sig_enforce /sys/kernel/security/lockdown`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 SI-7, CM-7

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Prelink Installed (HOST-BOOT-007)

Category: config. Effort: low. Confidence: high.

Checks that the prelink tool is absent, or disabled where the package is still installed.

**Why it matters:** Prelink rewrites binaries and libraries in place, which changes their hashes and defeats integrity monitoring and package verification, and it weakens address randomisation by fixing load addresses. Modern systems gain nothing from it.

- **prelink:** Prelink is installed
  - `/usr/sbin/prelink`
  - `/etc/prelink.conf`

**Fix:** Undo and remove it:  prelink -ua && apt purge prelink  (or  dnf remove prelink). Then re-initialise the integrity database (aide --init).

**Verify:** `command -v prelink  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-344; NIST SP 800-53 SI-7, CM-7

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Core Dumps Not Restricted (HOST-BOOT-008)

Category: exposure. Effort: low. Confidence: high.

Checks that process core dumps are turned off through PAM limits, and through systemd-coredump where the kernel hands dumps to it.

**Why it matters:** A core dump is a copy of a program's memory, including passwords, keys and session tokens it held. On a server nobody debugs, dumps are only a leak waiting in /var/lib/systemd/coredump or the working directory. HOST-KRN-004 covers setuid programs; this check covers every process.

- **core dumps:** No hard core 0 limit and no systemd-coredump Storage=none
  - `kernel.core_pattern = core`

**Fix:** Add  \* hard core 0  to /etc/security/limits.conf (or a file in limits.d), set  Storage=none  and  ProcessSizeMax=0  in the \[Coredump\] section of /etc/systemd/coredump.conf, and keep  fs.suid\_dumpable = 0.

**Verify:** `grep -E '^\*\s+hard\s+core\s+0' /etc/security/limits.conf /etc/security/limits.d/*; grep -E '^(Storage|ProcessSizeMax)' /etc/systemd/coredump.conf; cat /proc/sys/kernel/core_pattern`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-528; NIST SP 800-53 SI-11, CM-6, AC-6

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Docker Container Logs Have No Size Limit (HOST-CTR-005)

Category: logging. Effort: low. Confidence: high.

Checks whether the Docker daemon's default log driver caps log file size and rotation.

**Why it matters:** With the default json-file driver and no max-size, a chatty or attacked container can fill the disk with its own logs and take the host and every other container down. Bounded, rotated logs also keep evidence manageable for investigations.

- **/etc/docker/daemon.json:** Container logs are unbounded
  - `daemon log-driver json-file (default) has no max-size`
  - `3f2a9c1d8e7b: json-file logging without max-size`

**Fix:** Add to /etc/docker/daemon.json:  "log-driver": "json-file", "log-opts": {"max-size": "10m", "max-file": "3"}  (or use journald) and restart Docker; recreate containers that set their own LogConfig.

**Verify:** `docker info --format '{{.LoggingDriver}}' ; grep -A3 log-opts /etc/docker/daemon.json`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-779, CWE-770; NIST SP 800-53 AU-4, SC-5, CM-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[LOW\] Docker Userland Proxy Enabled (HOST-CTR-006)

Category: config. Effort: low. Confidence: high.

Checks whether the Docker daemon still uses the docker-proxy userland process for published ports.

**Why it matters:** The userland proxy is a root process per published port that exists only for an old loopback edge case. Hairpin NAT does the same job in the kernel with less code running as root and fewer processes to crash or exploit.

- **/etc/docker/daemon.json:** userland-proxy is not false

**Fix:** Add  "userland-proxy": false  to /etc/docker/daemon.json and restart Docker; test that published ports still answer from the host itself.

**Verify:** `ps -C docker-proxy  should list nothing after containers are recreated.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[LOW\] Docker Default Ulimits Not Set (HOST-CTR-007)

Category: config. Effort: low. Confidence: high.

Checks whether the Docker daemon sets default ulimits (open files and processes) for containers.

**Why it matters:** Without default limits a container inherits the daemon's generous limits and can exhaust file descriptors or spawn processes until the host starves. Daemon-wide defaults protect every container that forgets to set its own.

- **/etc/docker/daemon.json:** Default ulimits are not set
  - `default-ulimits has no nofile`
  - `default-ulimits has no nproc`

**Fix:** Add to /etc/docker/daemon.json:  "default-ulimits": {"nofile": {"Name": "nofile", "Hard": 65536, "Soft": 32768}, "nproc": {"Name": "nproc", "Hard": 4096, "Soft": 2048}}  and restart Docker; tune the numbers to your workloads.

**Verify:** `docker info | grep -A3 -i ulimit  or  docker run --rm alpine sh -c 'ulimit -n; ulimit -u'  (your own test container)`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; NIST SP 800-53 SC-6, CM-6

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[LOW\] Docker Content Trust Not Enabled (HOST-CTR-015)

Category: config. Effort: medium. Confidence: high.

Checks whether DOCKER\_CONTENT\_TRUST=1 is set system-wide so that only signed images are pulled.

**Why it matters:** Without content trust, docker pull accepts whatever the registry or a tampering proxy returns for a tag. Signature verification makes a substituted image fail to pull instead of running as root on your host.

- **/etc/environment:** DOCKER\_CONTENT\_TRUST is not set system-wide; unsigned images are accepted

**Fix:** Add  DOCKER\_CONTENT\_TRUST=1  to /etc/environment (or a file in /etc/profile.d) and sign your own images with docker trust sign; images without signatures then need an explicit --disable-content-trust for a deliberate exception.

**Verify:** `grep -r DOCKER_CONTENT_TRUST /etc/environment /etc/profile.d`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-494, CWE-347; NIST SP 800-53 SI-7, CM-14

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[LOW\] Vulnerability Snapshot Outdated or Test Data (HOST-CVE-003)

Category: patch. Effort: medium. Confidence: high.

Checks installed packages against an offline vulnerability snapshot.

**Why it matters:** Known vulnerable package versions can expose a server to documented attacks.

- **vulnerability snapshot:** Snapshot date 2021-01-01. An outdated snapshot misses newer vulnerabilities.

**Fix:** Install the vendor security update. For a source build, run 'make vulndb' to embed a snapshot generated from OSV and CISA KEV, then rebuild.

**Verify:** `Re-run OpenVAPT after patching and confirm the finding is absent.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** P10: Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities, https://openvapt.com/academy/p10.html (offline: `openvapt learn P10`)

### \[LOW\] Temporary Filesystems Allow Executables or Devices (HOST-FS-008)

Category: config. Effort: medium. Confidence: high.

Checks that /tmp, /var/tmp and /dev/shm are mounted with nodev and nosuid.

**Why it matters:** World-writable temporary areas are where attackers drop tools. nosuid stops planted setuid programs working there, and nodev stops device files being created.

- **/tmp:** /tmp is mounted without nodev, nosuid
  - `options rw,relatime`
- **/dev/shm:** /dev/shm is mounted without nodev, nosuid
  - `options rw`

**Fix:** Mount the path as tmpfs or a separate filesystem with options nodev,nosuid (and noexec where software allows), for example via systemd tmp.mount or /etc/fstab, then remount.

**Verify:** `findmnt -no OPTIONS /tmp /var/tmp /dev/shm`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 CM-6, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Default File Creation Mask Too Permissive (HOST-FS-009)

Category: access. Effort: low. Confidence: high.

Checks that the default umask makes new files unreadable to other users (027 or stricter).

**Why it matters:** With umask 022 every new file is readable by every local user, including configuration exports, reports and backups created by administrators.

- **/etc/login.defs:** UMASK 022 lets other users read new files (recommended 027)

**Fix:** Set  UMASK 027  in /etc/login.defs (and, if used, in /etc/profile or /etc/bash.bashrc).

**Verify:** `grep '^UMASK' /etc/login.defs`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[LOW\] /var/tmp Not a Separate Filesystem (HOST-FS-010)

Category: config. Effort: medium. Confidence: high.

Checks that /var/tmp is its own mount (or a bind mount of /tmp) with nodev, nosuid and noexec.

**Why it matters:** /var/tmp is world-writable and survives reboots. On the root filesystem it can be filled to exhaust disk space, and it accepts planted device files and executables unless mounted with restricting options.

- **/var/tmp:** /var/tmp is not a separate filesystem (it lives on the root filesystem)

**Fix:** Mount /var/tmp as a separate filesystem, or bind-mount it onto /tmp:  /tmp /var/tmp none bind,nodev,nosuid,noexec 0 0  in /etc/fstab, then reboot or remount. Caution: /var/tmp is meant to survive reboots and some installers and databases run programs from it, so check what uses it before binding it to /tmp with noexec, and keep console access when editing /etc/fstab (a wrong line can stop the boot).

**Verify:** `findmnt -no TARGET,OPTIONS /var/tmp`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 CM-6, CM-7, SC-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] /home Not a Separate Filesystem With nodev (HOST-FS-011)

Category: config. Effort: medium. Confidence: high.

Checks that user home directories live on their own mount with nodev (and nosuid where practical).

**Why it matters:** Users control the contents of /home. A separate mount stops a user filling the root filesystem, and nodev stops device files being created in a home directory to reach raw disks or memory.

- **/home:** /home is not a separate filesystem (it lives on the root filesystem)

**Fix:** Place /home on its own partition or logical volume and add nodev,nosuid to its options in /etc/fstab; remount with: mount -o remount /home . Caution: moving /home to a new volume needs a backup, a maintenance window and console access; a wrong /etc/fstab line can stop the boot.

**Verify:** `findmnt -no TARGET,OPTIONS /home`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 CM-6, CM-7, SC-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Log Directories on the Root Filesystem (HOST-FS-012)

Category: config. Effort: medium. Confidence: high.

Checks that /var/log (or /var) is a separate filesystem so runaway logging cannot fill the root disk.

**Why it matters:** Logs grow under attacker influence (failed logins, scanner noise). When they share the root filesystem, filling them stops the whole server, and when the disk is full new security events are lost.

- **/var/log:** /var/log is not a separate filesystem (it lives on the root filesystem)

**Fix:** Place /var/log (and ideally /var/log/audit) on its own partition or logical volume, listed in /etc/fstab with nodev,nosuid,noexec. Caution: moving log directories needs a backup, a maintenance window and console access; stop the logging services while copying, and a wrong /etc/fstab line can stop the boot.

**Verify:** `findmnt -no TARGET,OPTIONS /var/log /var`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; NIST SP 800-53 CM-6, CM-7, SC-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Temporary Filesystems Allow Executables (HOST-FS-013)

Category: config. Effort: medium. Confidence: high.

Checks that /tmp, /var/tmp and /dev/shm, where they are separate mounts, carry the noexec option.

**Why it matters:** Attackers who gain a foothold download tools into a world-writable temporary directory and run them from there. noexec makes that step fail and forces noisier workarounds.

- **/tmp:** /tmp allows executables
  - `options rw,relatime`
- **/dev/shm:** /dev/shm allows executables
  - `options rw`

**Fix:** Add noexec to the mount options of each temporary filesystem (systemd tmp.mount drop-in or /etc/fstab) and remount. Some package managers need exec on /tmp during installs; set TMPDIR for them rather than removing noexec.

**Verify:** `findmnt -no TARGET,OPTIONS /tmp /var/tmp /dev/shm`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 CM-6, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Files With No Known Owner or Group (HOST-FS-015)

Category: access. Effort: low. Confidence: high.

Looks for files whose owner or group id no longer appears in /etc/passwd or /etc/group.

**Why it matters:** When an account is deleted, its files keep the numeric id. The next account created with that id silently inherits them, which can hand a new user old keys, scripts or cron jobs.

- **filesystem:** 1 file(s) have an unknown owner or group
  - `uid=1003 gid=1003 /opt/legacy/app.bin (no such user or group)`

**Fix:** Decide who should own each listed file and apply  chown \<user\>:\<group\> \<path\>, or remove files that belonged to a deleted account after checking their contents.

**Verify:** `find /etc /usr/local /opt /srv /var/www /home -xdev \( -nouser -o -nogroup \)  should print nothing.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-282; NIST SP 800-53 AC-2, AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Kernel Symbol Map or Initial RAM Disk Readable by Others (HOST-FS-018)

Category: exposure. Effort: low. Confidence: high.

Checks that /boot/System.map-\* and the initial RAM disk images are not readable by every user.

**Why it matters:** System.map lists every kernel symbol address, which defeats kptr\_restrict and helps kernel exploits. An initial RAM disk can embed network credentials, disk-unlock key files or custom scripts that should not be world-readable.

- **/boot:** 1 boot file(s) are readable by others
  - `0644 /boot/System.map-4.15.0-20-generic`

**Fix:** chmod 600 /boot/System.map-\* /boot/initrd\* /boot/initramfs\*  and, on Debian family systems, set  UMASK=0077  in /etc/initramfs-tools/initramfs.conf so new images are created private.

**Verify:** `stat -c '%a %n' /boot/System.map-* /boot/initrd*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497, CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Cron and At Not Restricted to Allowed Users (HOST-FS-019)

Category: access. Effort: low. Confidence: high.

Checks that cron.allow and at.allow exist, are private to root, and that the deny lists are not relied upon instead.

**Why it matters:** By default every local account may schedule jobs. A deny list only names known accounts; an allow list says exactly who may schedule, which is what least privilege asks for and what makes persistence by a compromised service account harder.

- **/etc/cron.allow:** /etc/cron.allow does not exist; only /etc/cron.deny limits who may schedule cron jobs
- **/etc/at.allow:** /etc/at.allow does not exist; only /etc/at.deny limits who may schedule at jobs

**Fix:** Create the allow lists:  touch /etc/cron.allow /etc/at.allow && chmod 600 /etc/cron.allow /etc/at.allow && chown root:root /etc/cron.allow /etc/at.allow  then add one user name per line for the accounts that need scheduling, and remove /etc/cron.deny and /etc/at.deny. Caution: once cron.allow exists, crontabs of users not listed stop running, so list every service and user account that has a crontab (ls /var/spool/cron/crontabs or /var/spool/cron) before creating it.

**Verify:** `stat -c '%a %U %n' /etc/cron.allow /etc/at.allow; ls /etc/cron.deny /etc/at.deny 2>&1`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-862; NIST SP 800-53 AC-6, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Cron Configuration Readable by Other Users (HOST-FS-020)

Category: access. Effort: low. Confidence: high.

Checks that /etc/crontab is mode 600 and the cron.d, cron.hourly, cron.daily, cron.weekly and cron.monthly directories are mode 700, all owned by root.

**Why it matters:** Readable cron configuration tells a local attacker exactly which scripts root runs and when, which is the reconnaissance step before replacing one of them. HOST-FS-005 stops writing; this check stops reading.

- **cron:** 1 cron path(s) are readable by group or others
  - `0666 uid=0 /etc/crontab`

**Fix:** chown root:root /etc/crontab /etc/cron.\* && chmod 600 /etc/crontab && chmod 700 /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly

**Verify:** `stat -c '%a %U %n' /etc/crontab /etc/cron.*`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732; NIST SP 800-53 AC-3, AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Shell Profile Sets a Permissive File Creation Mask (HOST-FS-021)

Category: access. Effort: low. Confidence: high.

Checks that system-wide shell start-up files do not set a umask that leaves new files readable by every user.

**Why it matters:** A umask line in /etc/profile or /etc/bash.bashrc overrides the login.defs default for every interactive shell. Many distributions still ship  umask 022  there, which makes every file an administrator creates readable by all local accounts.

- **/etc/profile:** 1 shell file(s) set a umask that lets other users read new files
  - `/etc/profile: umask 022`

**Fix:** Change every  umask 022  line in the listed files to  umask 027, or remove the line so the login.defs value applies. Users start a new login shell to pick it up.

**Verify:** `grep -rn '^[[:space:]]*umask' /etc/profile /etc/bash.bashrc /etc/bashrc /etc/profile.d/ 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-276; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] No Brute-Force Protection Service (HOST-IDS-001)

Category: logging. Effort: low. Confidence: high.

Checks that a log-based intrusion prevention service (fail2ban, sshguard or CrowdSec) is running.

**Why it matters:** Internet-facing services receive constant guessing. Automatically blocking sources that fail repeatedly reduces noise and risk, and turns logs into active defence.

- **ids:** No brute-force protection service is active

**Fix:** Install and enable one, for example: apt install fail2ban && systemctl enable --now fail2ban , with an sshd jail enabled.

**Verify:** `systemctl is-active fail2ban`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307; OWASP Top 10 A07:2021; NIST SP 800-53 SI-4, AC-7

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] Kernel Pointers Exposed to Unprivileged Users (HOST-KRN-002)

Category: config. Effort: low. Confidence: high.

Checks that kernel memory addresses are hidden from normal users.

**Why it matters:** Leaked kernel addresses defeat kernel ASLR and are the first step of most local kernel exploits.

- **kernel.kptr\_restrict:** kernel.kptr\_restrict = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.kptr\_restrict = 1  then apply with: sysctl --system

**Verify:** `sysctl kernel.kptr_restrict  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; NIST SP 800-53 CM-6, SI-16

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Kernel Log Readable by Unprivileged Users (HOST-KRN-003)

Category: config. Effort: low. Confidence: high.

Checks that only privileged users can read the kernel log (dmesg).

**Why it matters:** The kernel log can reveal addresses, hardware and driver details useful for local exploitation.

- **kernel.dmesg\_restrict:** kernel.dmesg\_restrict = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.dmesg\_restrict = 1  then apply with: sysctl --system

**Verify:** `sysctl kernel.dmesg_restrict  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Setuid Programs May Dump Core (HOST-KRN-004)

Category: config. Effort: low. Confidence: high.

Checks that privileged programs do not write core dumps.

**Why it matters:** A core dump of a setuid program can contain password hashes or keys and may be readable by the user who crashed it.

- **fs.suid\_dumpable:** fs.suid\_dumpable = 2 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.suid\_dumpable = 0  then apply with: sysctl --system

**Verify:** `sysctl fs.suid_dumpable  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-528; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] IP Forwarding Enabled (HOST-KRN-005)

Category: config. Effort: low. Confidence: high.

Checks that the server does not route packets between networks.

**Why it matters:** A server that forwards packets can be used to pivot between networks. Only routers, VPN gateways and container hosts need this.

- **net.ipv4.ip\_forward:** net.ipv4.ip\_forward = 1; container runtime detected (/var/run/docker.sock); forwarding is required by the runtime, but forwarded traffic is accepted by default. Set the FORWARD policy to DROP or add DOCKER-USER rules (see NET-FW-005)
  - `container runtime detected (/var/run/docker.sock); forwarding is required by the runtime`
  - `iptables FORWARD policy ACCEPT`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.ip\_forward = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.ip_forward  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] ICMP Redirects Accepted (HOST-KRN-006)

Category: config. Effort: low. Confidence: high.

Checks that the kernel ignores ICMP redirect messages.

**Why it matters:** Forged ICMP redirects let an attacker on the local network reroute the server's traffic through themselves.

- **net.ipv4.conf.all.accept\_redirects:** net.ipv4.conf.all.accept\_redirects = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.accept\_redirects = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.accept_redirects  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Source-Routed Packets Accepted (HOST-KRN-007)

Category: config. Effort: low. Confidence: high.

Checks that the kernel drops packets that dictate their own route.

**Why it matters:** Source routing lets an attacker bypass network filtering and spoof trusted addresses.

- **net.ipv4.conf.all.accept\_source\_route:** net.ipv4.conf.all.accept\_source\_route = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.accept\_source\_route = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.accept_source_route  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] TCP SYN Cookies Disabled (HOST-KRN-008)

Category: config. Effort: low. Confidence: high.

Checks that SYN cookies protect the server from SYN flood attacks.

**Why it matters:** Without SYN cookies a simple flood of half-open connections can make every service unreachable.

- **net.ipv4.tcp\_syncookies:** net.ipv4.tcp\_syncookies = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.tcp\_syncookies = 1  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.tcp_syncookies  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; NIST SP 800-53 SC-5, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Process Tracing Not Restricted (HOST-KRN-010)

Category: config. Effort: low. Confidence: high.

Checks that processes cannot attach a debugger to other processes of the same user.

**Why it matters:** Unrestricted ptrace lets any compromised process read the memory of others, for example to steal SSH agent keys or browser sessions.

- **kernel.yama.ptrace\_scope:** kernel.yama.ptrace\_scope = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.yama.ptrace\_scope = 1  then apply with: sysctl --system

**Verify:** `sysctl kernel.yama.ptrace_scope  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Symlink Protection Disabled (HOST-KRN-011)

Category: config. Effort: low. Confidence: high.

Checks that the kernel blocks following symlinks owned by other users in world-writable sticky directories.

**Why it matters:** Without it, classic /tmp symlink races let local users trick root-run programs into overwriting files.

- **fs.protected\_symlinks:** fs.protected\_symlinks = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_symlinks = 1  then apply with: sysctl --system

**Verify:** `sysctl fs.protected_symlinks  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-59; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Hardlink Protection Disabled (HOST-KRN-012)

Category: config. Effort: low. Confidence: high.

Checks that users cannot create hard links to files they do not own.

**Why it matters:** Hard links to privileged files enable time-of-check races and keep vulnerable setuid binaries alive after updates.

- **fs.protected\_hardlinks:** fs.protected\_hardlinks = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_hardlinks = 1  then apply with: sysctl --system

**Verify:** `sysctl fs.protected_hardlinks  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-59; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Reverse Path Filtering Disabled (HOST-KRN-013)

Category: config. Effort: low. Confidence: high.

Checks that the kernel drops packets whose source address could not be routed back through the receiving interface.

**Why it matters:** Reverse path filtering blocks many spoofed-source packets used in attacks and amplification.

- **net.ipv4.conf.all.rp\_filter:** net.ipv4.conf.all.rp\_filter = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.rp\_filter = 1  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.rp_filter  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Martian Packets Not Logged (HOST-KRN-014)

Category: config. Effort: low. Confidence: high.

Checks that packets with impossible source addresses are logged.

**Why it matters:** Logging martian packets helps detect spoofing and misrouting attempts.

- **net.ipv4.conf.all.log\_martians:** net.ipv4.conf.all.log\_martians = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.log\_martians = 1  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.log_martians  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Server Sends ICMP Redirects (HOST-KRN-015)

Category: config. Effort: low. Confidence: high.

Checks that the server does not send ICMP redirects, which only routers should do.

**Why it matters:** A non-router sending redirects can be abused to influence other hosts' routing on the local network.

- **net.ipv4.conf.all.send\_redirects:** 1 of 1 setting(s) differ from the hardened value 0
  - `net/ipv4/conf/all/send_redirects = 1`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.send\_redirects = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.send_redirects  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Broadcast ICMP Echo Answered (HOST-KRN-016)

Category: config. Effort: low. Confidence: high.

Checks that the server ignores ping requests sent to broadcast addresses.

**Why it matters:** Answering broadcast pings makes the server a participant in Smurf-style amplification attacks.

- **net.ipv4.icmp\_echo\_ignore\_broadcasts:** net.ipv4.icmp\_echo\_ignore\_broadcasts = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.icmp\_echo\_ignore\_broadcasts = 1  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.icmp_echo_ignore_broadcasts  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-406; NIST SP 800-53 SC-5, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] IPv6 ICMP Redirects Accepted (HOST-KRN-017)

Category: config. Effort: low. Confidence: high.

Checks that the kernel ignores IPv6 ICMP redirect messages.

**Why it matters:** Forged IPv6 redirects let an attacker on the local network reroute traffic through themselves.

- **net.ipv6.conf.all.accept\_redirects:** 1 of 1 setting(s) differ from the hardened value 0
  - `net/ipv6/conf/all/accept_redirects = 1`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_redirects = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv6.conf.all.accept_redirects  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Performance Events Open to Unprivileged Users (HOST-KRN-018)

Category: config. Effort: low. Confidence: high.

Checks that unprivileged users cannot use kernel performance events to observe the system.

**Why it matters:** Unrestricted perf events expose kernel and other processes' activity and have been used in local exploits.

- **kernel.perf\_event\_paranoid:** kernel.perf\_event\_paranoid = 1 (hardened value: 2)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.perf\_event\_paranoid = 2  then apply with: sysctl --system

**Verify:** `sysctl kernel.perf_event_paranoid  should print 2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 CM-6, SI-16

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Magic SysRq Key Enabled (HOST-KRN-019)

Category: config. Effort: low. Confidence: high.

Checks that the magic SysRq key combination is disabled or limited to keyboard synchronisation.

**Why it matters:** With SysRq enabled, anyone at a console or with access to /proc/sysrq-trigger can reboot the machine, kill every process or dump memory without logging in.

- **kernel.sysrq:** kernel.sysrq = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.sysrq = 0  then apply with: sysctl --system

**Verify:** `sysctl kernel.sysrq  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] FIFO Protection in Shared Directories Disabled (HOST-KRN-020)

Category: config. Effort: low. Confidence: high.

Checks that the kernel refuses to open FIFOs in world-writable sticky directories that another user owns.

**Why it matters:** A FIFO planted in /tmp can make a privileged program block or write into an attacker-controlled pipe, a classic step in local attacks.

- **fs.protected\_fifos:** fs.protected\_fifos = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_fifos = 1  then apply with: sysctl --system

**Verify:** `sysctl fs.protected_fifos  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-59; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Regular File Protection in Shared Directories Disabled (HOST-KRN-021)

Category: config. Effort: low. Confidence: high.

Checks that the kernel refuses to open, with O\_CREAT, regular files in world-writable sticky directories that another user owns.

**Why it matters:** Without it, a user can pre-create a file that a root-run script later writes to in /tmp, redirecting privileged output into a file they control.

- **fs.protected\_regular:** fs.protected\_regular = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  fs.protected\_regular = 1  then apply with: sysctl --system

**Verify:** `sysctl fs.protected_regular  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-59; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Unprivileged User Namespaces Allowed (HOST-KRN-022)

Category: config. Effort: low. Confidence: high.

Checks that normal users cannot create user namespaces, unless the server runs containers that need them.

**Why it matters:** User namespaces give an unprivileged process a fake root inside a namespace, which exposes kernel code paths that were written for root only; many local kernel exploits start by unsharing a user namespace.

- **kernel.unprivileged\_userns\_clone:** kernel.unprivileged\_userns\_clone = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing  kernel.unprivileged\_userns\_clone = 0  (Debian family) or  user.max\_user\_namespaces = 0  (other kernels), then apply with: sysctl --system. Rootless Docker, Podman, Flatpak and some browsers need user namespaces, so test first.

**Verify:** `sysctl kernel.unprivileged_userns_clone user.max_user_namespaces`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 CM-6, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] IPv6 Source-Routed Packets Accepted (HOST-KRN-023)

Category: config. Effort: low. Confidence: high.

Checks that the kernel drops IPv6 packets that dictate their own route.

**Why it matters:** IPv6 routing headers can be used to bypass filters and spoof trusted addresses, as source routing could in IPv4.

- **net.ipv6.conf.all.accept\_source\_route:** 1 of 1 setting(s) differ from the hardened value 0
  - `net/ipv6/conf/all/accept_source_route = 1`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_source\_route = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv6.conf.all.accept_source_route  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Secure ICMP Redirects Accepted (HOST-KRN-024)

Category: config. Effort: low. Confidence: high.

Checks that the kernel also ignores ICMP redirects that come from its configured gateways.

**Why it matters:** An attacker who spoofs the gateway address can still reroute traffic unless secure redirects are off as well; a server has no reason to accept redirects at all.

- **net.ipv4.conf.all.secure\_redirects:** 1 of 1 setting(s) differ from the hardened value 0
  - `net/ipv4/conf/all/secure_redirects = 1`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.conf.all.secure\_redirects = 0  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.conf.all.secure_redirects  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] IPv6 Router Advertisements Accepted (HOST-KRN-025)

Category: config. Effort: low. Confidence: high.

Checks that the server does not reconfigure its IPv6 addresses and routes from router advertisements.

**Why it matters:** A rogue router advertisement on the local network can add a default route through an attacker's machine or assign addresses that bypass firewall rules; servers use static addressing.

- **net.ipv6.conf.all.accept\_ra:** 1 of 1 setting(s) differ from the hardened value 0
  - `net/ipv6/conf/all/accept_ra = 1`

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv6.conf.all.accept\_ra = 0  then apply with: sysctl --system. Caution: on a host that gets its IPv6 address or default route from router advertisements (SLAAC, most cloud dual-stack VMs), accept\_ra = 0 removes the IPv6 default route and cuts IPv6 connectivity. Use it only with static IPv6 addressing; otherwise keep accept\_ra = 1 and rely on RA Guard on the switch or a host firewall rule for RA sources.

**Verify:** `sysctl net.ipv6.conf.all.accept_ra  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-940; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Bogus ICMP Error Responses Logged (HOST-KRN-026)

Category: config. Effort: low. Confidence: high.

Checks that the kernel silently ignores malformed ICMP error responses instead of logging each one.

**Why it matters:** Logging every bogus ICMP error lets a remote sender fill the kernel log and disk at will, hiding real events among noise.

- **net.ipv4.icmp\_ignore\_bogus\_error\_responses:** net.ipv4.icmp\_ignore\_bogus\_error\_responses = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.ipv4.icmp\_ignore\_bogus\_error\_responses = 1  then apply with: sysctl --system

**Verify:** `sysctl net.ipv4.icmp_ignore_bogus_error_responses  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-779; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Kexec Kernel Replacement Allowed (HOST-KRN-027)

Category: config. Effort: low. Confidence: high.

Checks that a running kernel cannot be replaced at run time with kexec.

**Why it matters:** kexec lets root boot a different kernel without going through the firmware, which bypasses Secure Boot and module signing and is used to load unsigned kernels after a compromise.

- **kernel.kexec\_load\_disabled:** kernel.kexec\_load\_disabled = 0 (hardened value: 1)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.kexec\_load\_disabled = 1  then apply with: sysctl --system. Caution: kexec\_load\_disabled = 1 also stops kdump from loading its crash kernel (kdump.service on the RHEL family, kdump-tools on Debian and Ubuntu), so crash dumps are lost. Keep it 0 where kdump is used, or accept that trade-off deliberately. The setting cannot be turned back off without a reboot.

**Verify:** `sysctl kernel.kexec_load_disabled  should print 1.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] Unprivileged userfaultfd Allowed (HOST-KRN-028)

Category: config. Effort: low. Confidence: high.

Checks that only privileged processes can use userfaultfd.

**Why it matters:** userfaultfd lets a process pause the kernel at a chosen moment, which has turned many kernel race conditions into reliable local exploits.

- **vm.unprivileged\_userfaultfd:** vm.unprivileged\_userfaultfd = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  vm.unprivileged\_userfaultfd = 0  then apply with: sysctl --system. Caution: unprivileged live migration tools (for example QEMU post-copy migration or CRIU run as a normal user) need userfaultfd; test those first.

**Verify:** `sysctl vm.unprivileged_userfaultfd  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] TTY Line Discipline Autoloading Enabled (HOST-KRN-029)

Category: config. Effort: low. Confidence: high.

Checks that unprivileged users cannot make the kernel load terminal line discipline modules.

**Why it matters:** Automatically loaded line disciplines have carried several exploitable kernel bugs; disabling autoload shrinks the kernel attack surface reachable from a normal shell.

- **dev.tty.ldisc\_autoload:** dev.tty.ldisc\_autoload = 1 (hardened value: 0)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  dev.tty.ldisc\_autoload = 0  then apply with: sysctl --system

**Verify:** `sysctl dev.tty.ldisc_autoload  should print 0.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] BPF JIT Hardening Off (HOST-KRN-030)

Category: config. Effort: low. Confidence: high.

Checks that the BPF just-in-time compiler blinds constants in generated code for every user.

**Why it matters:** Without hardening, attacker-chosen constants in BPF programs land in executable kernel memory, a known technique for building kernel exploit payloads.

- **net.core.bpf\_jit\_harden:** net.core.bpf\_jit\_harden = 0 (hardened value: 2)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  net.core.bpf\_jit\_harden = 2  then apply with: sysctl --system

**Verify:** `sysctl net.core.bpf_jit_harden  should print 2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 SC-7, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] io\_uring Open to Unprivileged Users (HOST-KRN-031)

Category: config. Effort: low. Confidence: high.

Checks that io\_uring is disabled, or limited to privileged processes, on kernels that offer the setting.

**Why it matters:** io\_uring has produced a long run of local privilege-escalation bugs; several large operators disable it for untrusted workloads.

- **kernel.io\_uring\_disabled:** kernel.io\_uring\_disabled = 0 (hardened value: 2)

**Fix:** Create /etc/sysctl.d/60-openvapt-hardening.conf containing:  kernel.io\_uring\_disabled = 2  then apply with: sysctl --system. Caution: applications that use io\_uring for fast I/O (some databases, proxies and runtimes) fall back or fail to start when it is disabled; test them first, or use 1 to allow only a chosen group.

**Verify:** `sysctl kernel.io_uring_disabled  should print 2.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-749; NIST SP 800-53 AC-6, CM-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P17: Kernel, Filesystem and Boot Hardening, https://openvapt.com/academy/p17.html (offline: `openvapt learn P17`)

### \[LOW\] System Logging Not Persistent (HOST-LOG-001)

Category: logging. Effort: low. Confidence: high.

Checks that system logs survive a reboot (rsyslog/syslog-ng running, or a persistent journal).

**Why it matters:** Without persistent logs there is no evidence after an incident: no record of logins, sudo use, or service errors.

- **logging:** No syslog daemon active and the journal is not persistent

**Fix:** Either install and enable rsyslog, or make the journal persistent: mkdir -p /var/log/journal && systemd-tmpfiles --create --prefix /var/log/journal && systemctl restart systemd-journald

**Verify:** `ls -d /var/log/journal  or  systemctl is-active rsyslog`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-9, AU-11

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

**Learn:** A4: Building Your Own Check, https://openvapt.com/academy/a4.html (offline: `openvapt learn A4`)

### \[LOW\] Security Audit Daemon Not Running (HOST-LOG-002)

Category: logging. Effort: medium. Confidence: high.

Checks whether the Linux audit daemon (auditd) is running.

**Why it matters:** auditd records security-relevant events (changes to /etc/passwd, privilege use, file deletions) that ordinary logs miss, which incident responders rely on.

- **auditd:** auditd is not active

**Fix:** Install the audit package (apt install auditd, or dnf install audit), enable it (systemctl enable --now auditd) and load a rule set appropriate to your policy.

**Verify:** `systemctl is-active auditd  should print 'active'.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] Security Logs Readable by All Users (HOST-LOG-003)

Category: logging. Effort: low. Confidence: high.

Checks that authentication and system logs are not world-readable.

**Why it matters:** Authentication logs contain user names, source addresses and sometimes passwords typed into the user name field. Only administrators should read them.

- **/var/log/auth.log:** 1 file(s) are readable or writable by others
  - `0644 /var/log/auth.log`

**Fix:** chmod o-rwx /var/log/auth.log /var/log/secure /var/log/syslog /var/log/messages  and set the matching create mode in /etc/logrotate.d.

**Verify:** `stat -c '%a %n' /var/log/auth.log /var/log/secure`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-532; NIST SP 800-53 AC-3, AC-6

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] Log Rotation Not Configured (HOST-LOG-004)

Category: logging. Effort: low. Confidence: high.

Checks that logrotate (or journald size limits) keeps logs from filling the disk.

**Why it matters:** Unrotated logs eventually fill the disk and stop services, and an attacker can deliberately flood logs to cause that or to hide activity.

- **logs:** No log rotation or journal size limit configured

**Fix:** Install logrotate (apt install logrotate / dnf install logrotate) and keep its daily timer enabled, or set SystemMaxUse= in journald.conf.

**Verify:** `ls /etc/logrotate.conf ; systemctl is-active logrotate.timer`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; NIST SP 800-53 AU-4, AU-11

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] No Mandatory Access Control Enforced (HOST-MAC-001)

Category: config. Effort: high. Confidence: high.

Checks that AppArmor or SELinux is enabled and enforcing.

**Why it matters:** Mandatory access control confines services even when they run as root, so a compromised web server or database cannot freely read or change the rest of the system.

- **mac:** Neither SELinux (enforcing) nor AppArmor is active

**Fix:** Ubuntu/Debian: keep AppArmor enabled (systemctl enable --now apparmor) and its profiles in enforce mode. RHEL family: set SELINUX=enforcing in /etc/selinux/config and relabel. Test applications in permissive or complain mode first.

**Verify:** `getenforce  or  aa-status`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-3(3), AC-6

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Password Quality Module Not Enforced (HOST-PAM-001)

Category: identity. Effort: low. Confidence: high.

Checks that new passwords pass through pam\_pwquality with its dictionary check on, so dictionary words and user-name variants are rejected.

**Why it matters:** NIST SP 800-63B-4 asks verifiers to reject passwords found in dictionaries and blocklists rather than to impose composition rules. pam\_pwquality does that work on Linux, but only when the password stack actually calls it and its dictcheck and usercheck options are not switched off. A minlen in pwquality.conf (HOST-PWD-003) does nothing if the module is never invoked.

- **pam:** Password quality checks are not enforced
  - `no pam_pwquality line in /etc/pam.d/common-password`
  - `dictcheck = 0 (/etc/security/pwquality.conf.d/10-lab.conf)`

**Fix:** Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family). Make sure the password stack has  password requisite pam\_pwquality.so retry=3  before pam\_unix (pam-auth-update or authselect enable-feature with-pwquality), and in /etc/security/pwquality.conf keep  dictcheck = 1  and  usercheck = 1 . Pair it with a breached-password blocklist where possible. Keep a root session open and test a login and a password change in a second session before closing it: a wrong PAM line can lock every user out.

**Verify:** `grep pam_pwquality /etc/pam.d/common-password /etc/pam.d/system-auth 2>/dev/null; grep -rE '^(dictcheck|usercheck|enforcing)' /etc/security/pwquality.conf /etc/security/pwquality.conf.d/`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-521; OWASP Top 10 A07:2021; NIST SP 800-53 IA-5(1)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Account Lockout Releases Too Quickly (HOST-PAM-002)

Category: identity. Effort: low. Confidence: high.

Checks that a locked account stays locked for at least 15 minutes (or until an administrator unlocks it).

**Why it matters:** This check and HOST-PWD-004 split one control so a single root cause is never counted twice: HOST-PWD-004 covers whether lockout exists and its deny threshold (how many failures lock the account); this check covers only unlock\_time (how long the lock lasts). How long the lock lasts decides whether it slows an attacker: pam\_faillock's default unlock\_time of 600 seconds lets guessing resume every ten minutes. Fifteen minutes or more, or 0 (manual unlock), makes online guessing impractical.

- **/etc/pam.d/common-auth:** unlock\_time=60 seconds (need 900 or more, or 0 for manual unlock)

**Fix:** In /etc/security/faillock.conf set  unlock\_time = 900  (or 0 to require an administrator to run faillock --reset), keeping deny between 3 and 10. Keep a root session open and test in a second session before closing the first: a wrong PAM line can lock every user out, and unlock\_time = 0 means locked accounts, administrators included, wait for faillock --reset.

**Verify:** `grep -E 'unlock_time' /etc/security/faillock.conf /etc/pam.d/common-auth /etc/pam.d/system-auth 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307; OWASP Top 10 A07:2021; NIST SP 800-53 AC-7

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] su Not Restricted to an Administrative Group (HOST-PAM-003)

Category: identity. Effort: low. Confidence: high.

Checks that only members of a named group (wheel or sudo) may use su to become another user.

**Why it matters:** Without pam\_wheel, every local account, including a compromised web service user, can run su and start guessing the root password. Restricting su to an administrative group removes that path and leaves sudo, which logs each command, as the way to gain privilege.

- **/etc/pam.d/su:** No active pam\_wheel line: any user may attempt su

**Fix:** In /etc/pam.d/su uncomment or add  auth required pam\_wheel.so use\_uid group=sudo  (group=wheel on the RHEL family), and make sure the group contains only administrators. Caution: add every administrator who uses su to that group first and test from a second session, or they lose su access.

**Verify:** `grep -E '^auth\s+required\s+pam_wheel.so' /etc/pam.d/su`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-862; NIST SP 800-53 AC-6(2), AC-3

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Password History Not Enforced (HOST-PAM-004)

Category: identity. Effort: low. Confidence: high.

Checks that PAM remembers at least the last 5 passwords so a changed password cannot be changed straight back.

**Why it matters:** When a password must be changed after a suspected compromise, the point is lost if the user can set the old one again. pam\_pwhistory (or pam\_unix remember=) keeps recent hashes and refuses reuse. Five is a common minimum.

- **/etc/pam.d/common-password:** No remember= setting: old passwords can be reused at once

**Fix:** Add  password required pam\_pwhistory.so remember=5 use\_authtok  before pam\_unix in the password stack (pam-auth-update on Debian/Ubuntu, authselect enable-feature with-pwhistory on the RHEL family), or set  remember = 5  in /etc/security/pwhistory.conf. Keep a root session open and test a password change in a second session before closing it: a wrong PAM line can block password changes or logins.

**Verify:** `grep -E 'remember' /etc/pam.d/common-password /etc/pam.d/system-auth /etc/security/pwhistory.conf 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-262; OWASP Top 10 A07:2021; NIST SP 800-53 IA-5(1)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Minimum Password Age Not Set (HOST-PAM-005)

Category: identity. Effort: low. Confidence: high.

Checks that a password cannot be changed again on the same day it was set.

**Why it matters:** Password history (HOST-PAM-004) only works if users cannot cycle through several changes in a minute to get back to a favourite password. PASS\_MIN\_DAYS 1 closes that loop with no real cost to users.

- **/etc/login.defs:** PASS\_MIN\_DAYS 0 (1 or more)

**Fix:** Set  PASS\_MIN\_DAYS 1  in /etc/login.defs, and for existing accounts  chage --mindays 1 \<user\> .

**Verify:** `grep '^PASS_MIN_DAYS' /etc/login.defs`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-262; OWASP Top 10 A07:2021; NIST SP 800-53 IA-5(1)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Inactive Accounts Never Locked (HOST-PAM-007)

Category: identity. Effort: low. Confidence: high.

Checks that new accounts are set to lock automatically after a password has been expired and unused for at most 30 days.

**Why it matters:** Accounts of people who left, or test accounts nobody remembers, stay usable for years unless something disables them. The INACTIVE default in /etc/default/useradd makes new accounts lock themselves a fixed number of days after their password expires.

- **/etc/default/useradd:** INACTIVE=-1 (need 0 to 30)

**Fix:** useradd -D -f 30  (writes INACTIVE=30 to /etc/default/useradd), and for existing accounts  chage --inactive 30 \<user\> . Caution: this locks accounts whose password has expired for 30 days, so exempt break-glass and rarely used administrator accounts or keep their passwords current.

**Verify:** `useradd -D | grep INACTIVE`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-262; NIST SP 800-53 AC-2(3)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] No Shell Idle Timeout (HOST-PAM-009)

Category: identity. Effort: low. Confidence: high.

Checks that interactive shells log out after at most 15 minutes without input (TMOUT).

**Why it matters:** A root shell left open on an unattended terminal, or a forgotten SSH session on a shared workstation, is free privilege for whoever sits down next. TMOUT makes bash and ksh close an idle session automatically; readonly stops users from switching it off.

- **TMOUT:** No TMOUT is set in the shell start-up files

**Fix:** Create /etc/profile.d/99-tmout.sh with  TMOUT=900 ; readonly TMOUT ; export TMOUT  (mode 644, owned by root). Sessions that must stay open should use a multiplexer such as tmux with its own lock, not a longer TMOUT. Caution: a readonly TMOUT ends idle interactive shells, including foreground jobs left waiting in them; run long jobs under nohup, systemd-run or a multiplexer.

**Verify:** `grep -rE '^\s*(readonly |export )?TMOUT=' /etc/profile /etc/profile.d/ /etc/bash.bashrc /etc/bashrc 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-613; OWASP Top 10 A07:2021; NIST SP 800-53 AC-11, AC-12

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Many Old Kernels Retained (HOST-PKG-004)

Category: patch. Effort: low. Confidence: high.

Checks that no more than three kernel versions are installed.

**Why it matters:** Every installed kernel stays bootable from the boot menu, with every vulnerability it had. Old kernels also fill /boot until updates fail to install, which is a quiet way to stop receiving kernel patches. Keeping the running kernel plus one or two fallbacks is enough.

- **kernels:** 5 kernel versions installed (recommended at most 3)
  - `4.15.0-100-generic`
  - `4.15.0-112-generic`
  - `4.15.0-120-generic`
  - `4.15.0-140-generic`
  - `4.15.0-150-generic`

**Fix:** Debian/Ubuntu: apt-get autoremove --purge  removes kernels outside the keep list. RHEL family: set installonly\_limit=3 in /etc/dnf/dnf.conf and run  dnf remove --oldinstallonly . Never remove the running kernel (uname -r).

**Verify:** `dpkg -l 'linux-image-*' | grep '^ii'  or  rpm -q kernel kernel-core`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Package Lists Not Refreshed Automatically (HOST-PKG-006)

Category: patch. Effort: low. Confidence: high.

Checks that the package manager downloads fresh repository metadata on a schedule, so pending updates can be seen at all.

**Why it matters:** apt and dnf only know about updates listed in the metadata they last downloaded. Without a timer that refreshes it, HOST-PKG-001 and the administrator's own apt list --upgradable both report a clean system that is weeks behind. The refresh is read-only and separate from installing anything.

- **apt:** Package lists are not refreshed automatically (no APT::Periodic::Update-Package-Lists and apt-daily.timer not enabled)

**Fix:** Debian/Ubuntu: create /etc/apt/apt.conf.d/20auto-upgrades with  APT::Periodic::Update-Package-Lists "1";  (dpkg-reconfigure unattended-upgrades writes it) and  systemctl enable apt-daily.timer . RHEL family:  systemctl enable --now dnf-makecache.timer .

**Verify:** `systemctl is-enabled apt-daily.timer  or  systemctl is-enabled dnf-makecache.timer`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2, RA-5

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Short Minimum Password Length (HOST-PWD-003)

Category: identity. Effort: low. Confidence: high.

Checks that the password quality policy requires at least 15 characters.

**Why it matters:** Length is the single most effective property against guessing. NIST SP 800-63B-4 (2025) requires at least 15 characters for a password used as the only authentication factor, and recommends allowing long passphrases.

- **/etc/security/pwquality.conf:** minlen = 8 (NIST SP 800-63B-4 requires 15 or more for single-factor passwords)

**Fix:** Install libpam-pwquality (Debian/Ubuntu) or libpwquality (RHEL family) and set  minlen = 15  (or more) in /etc/security/pwquality.conf.

**Verify:** `grep -r '^minlen' /etc/security/pwquality.conf /etc/security/pwquality.conf.d/`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-521; OWASP Top 10 A07:2021; NIST SP 800-53 IA-5(1)

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[LOW\] SSH X11 Forwarding Enabled (HOST-SSH-005)

Category: config. Effort: low. Confidence: high.

Checks whether SSH forwards graphical X11 sessions.

**Why it matters:** Servers rarely need X11. Forwarding it exposes the connecting user's display to anyone with root on the server, and widens the attack surface of sshd.

- **sshd:** X11 forwarding is enabled
  - `x11forwarding yes`

**Fix:** Set  X11Forwarding no  in /etc/ssh/sshd\_config and reload sshd.

**Verify:** `sshd -T | grep -i x11forwarding  should print 'no'.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

### \[LOW\] SSH Allows Many Authentication Attempts (HOST-SSH-006)

Category: identity. Effort: low. Confidence: high.

Checks whether MaxAuthTries is above 4.

**Why it matters:** Each connection may try this many passwords or keys. A low limit slows guessing and makes attacks show up in the logs sooner.

- **sshd:** MaxAuthTries is 10 (recommended 4 or lower)

**Fix:** Set  MaxAuthTries 4  (or lower) in /etc/ssh/sshd\_config and reload sshd. Consider fail2ban or sshguard for rate limiting.

**Verify:** `sshd -T | grep -i maxauthtries  should print 4 or lower.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307; NIST SP 800-53 AC-7

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[LOW\] SSH Authentication Controls Too Permissive (HOST-SSH-008)

Category: identity. Effort: low. Confidence: high.

Checks effective SSH authentication method and login grace settings.

**Why it matters:** Long login windows and unconstrained single-factor authentication give online guessing and stolen credentials more opportunity.

- **sshd:** SSH authentication restrictions are too permissive
  - `logingracetime 120`
  - `password or keyboard-interactive login enabled without a second factor (authenticationmethods (not set, default applies))`

**Fix:** Set LoginGraceTime 60 or lower and choose AuthenticationMethods appropriate to the server, such as publickey, then restrict access with AllowUsers or AllowGroups where practical.

**Verify:** `Use sshd -T -C for each allowed user and source context and inspect logingracetime, authenticationmethods, allowusers and allowgroups.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307, CWE-308; NIST SP 800-53 AC-7, IA-2

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[LOW\] SSH Logging Level Too Low (HOST-SSH-012)

Category: logging. Effort: low. Confidence: high.

Checks that sshd logs at INFO or VERBOSE level.

**Why it matters:** QUIET, FATAL or ERROR levels drop login records, so failed and successful logins cannot be investigated. VERBOSE also records the key fingerprint used.

- **sshd:** LogLevel quiet drops login records

**Fix:** Set  LogLevel VERBOSE  (or INFO) in /etc/ssh/sshd\_config and reload sshd.

**Verify:** `sshd -T | grep -i loglevel`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-3

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] SSH Authorized Keys Without Restrictions or Protection (HOST-SSH-013)

Category: identity. Effort: low. Confidence: high.

Checks root's authorized\_keys for keys without from= or command= options, and every authorized\_keys file for permissive modes.

**Why it matters:** A key in root's authorized\_keys is a root password that never expires. Restricting it with from= (allowed source addresses) or command= (one fixed command) limits what a stolen key can do. An authorized\_keys file that others can write lets them add their own key; one that others can read tells them which keys to steal.

- **authorized\_keys:** SSH authorized keys lack restrictions or protection
  - `0644 /root/.ssh/authorized_keys is readable by others`
  - `/root/.ssh/authorized_keys: 1 root key(s) without from= or command= restrictions`

**Fix:** Prefix each root key with options, for example  from="203.0.113.0/24",no-port-forwarding,no-agent-forwarding  or  command="/usr/local/bin/backup",restrict ; chmod 600 \~/.ssh/authorized\_keys and 700 \~/.ssh for every account. Prefer sudo from a personal account over direct root keys.

**Verify:** `cut -c1-40 /root/.ssh/authorized_keys ; stat -c '%a %n' /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-250; NIST SP 800-53 IA-5, AC-6, AC-17

**Learn:** P2: SSH Security, https://openvapt.com/academy/p2.html (offline: `openvapt learn P2`)

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[LOW\] Sudo Commands Not Logged to a Dedicated File (HOST-SUDO-001)

Category: logging. Effort: low. Confidence: high.

Checks that sudo uses a pseudo-terminal and keeps its own log file.

**Why it matters:** Defaults use\_pty stops background processes from a sudo session lingering after it ends. A dedicated logfile keeps an easily reviewed record of privileged commands.

- **/etc/sudoers:** Missing: Defaults use\_pty, Defaults logfile=

**Fix:** With visudo add:  Defaults use\_pty  and  Defaults logfile="/var/log/sudo.log"

**Verify:** `grep -rE 'use_pty|logfile' /etc/sudoers /etc/sudoers.d`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; NIST SP 800-53 AU-2, AU-12

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] Unnecessary Legacy Services Running (HOST-SVC-001)

Category: config. Effort: low. Confidence: high.

Checks for desktop and legacy network services that servers rarely need.

**Why it matters:** Each running service adds attack surface. Zeroconf discovery, printing, RPC portmapping and inetd-style super-servers are common, long-forgotten exposures.

- **services:** Unnecessary services are active
  - `avahi-daemon`
  - `rpcbind`

**Fix:** systemctl disable --now \<service\>  for each listed service you do not need, or remove its package. Caution: check first that nothing depends on it (systemctl list-dependencies --reverse \<service\>) and keep console access when disabling network or remote-access services.

**Verify:** `systemctl is-active avahi-daemon cups rpcbind xinetd`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] Avahi or CUPS Installed or Enabled (HOST-SVC-007)

Category: config. Effort: low. Confidence: high.

Checks whether zero-configuration discovery (Avahi) or the printing system (CUPS) is installed or enabled.

**Why it matters:** Avahi announces the machine and its services to the local network, and CUPS listens for print jobs and runs a web interface. Both are desktop features with a history of vulnerabilities and no purpose on a server. If this machine is one of the desktops and print servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 3 legacy service component(s) present
  - `package avahi-daemon is installed`
  - `package cups is installed`
  - `unit avahi-daemon.service is enabled at boot`

**Fix:** apt purge avahi-daemon cups cups-daemon  or  dnf remove avahi cups , and  systemctl disable --now avahi-daemon.socket avahi-daemon.service cups.socket cups.service . Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l avahi-daemon cups cups-daemon 2>/dev/null | grep '^ii' ; systemctl is-enabled avahi-daemon.service avahi-daemon.socket cups.service cups.socket cups-browsed.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] NFS Server or rpcbind Installed or Enabled (HOST-SVC-008)

Category: config. Effort: low. Confidence: high.

Checks whether the NFS server or the RPC port mapper (rpcbind) is installed or enabled.

**Why it matters:** NFS exports whole directory trees with trust based on client IP address and numeric user ids, and rpcbind tells any caller which RPC services are running. Unless this machine is a file server, both are attack surface with no benefit. The rpcbind package is often pulled in as a dependency and left running. If this machine is one of the file servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 2 legacy service component(s) present
  - `package nfs-kernel-server is installed`
  - `package rpcbind is installed`

**Fix:** apt purge nfs-kernel-server rpcbind  or  dnf remove nfs-utils rpcbind  where NFS is not served, and  systemctl disable --now nfs-server rpcbind.socket rpcbind.service . Where NFS is required, export read-only with root\_squash to named hosts and firewall port 2049 and 111. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l nfs-kernel-server rpcbind 2>/dev/null | grep '^ii' ; systemctl is-enabled nfs-server.service rpcbind.service rpcbind.socket nfs-kernel-server.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-306; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] DHCP or DNS Server Installed or Enabled (HOST-SVC-009)

Category: config. Effort: low. Confidence: high.

Checks whether a DHCP server or an authoritative or recursive DNS server (BIND) is installed or enabled on a machine that is not a network server.

**Why it matters:** A rogue DHCP server hands out addresses and gateways to the whole network segment, and BIND is a large, privileged network service with a long vulnerability history. Both belong on dedicated infrastructure hosts. On a web or application server they are either forgotten or an attacker's foothold. If this machine is one of the dedicated network infrastructure hosts that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 1 legacy service component(s) present
  - `package isc-dhcp-server is installed`

**Fix:** apt purge isc-dhcp-server bind9  or  dnf remove dhcp-server bind , and  systemctl disable --now isc-dhcp-server named . If this machine is meant to be a DNS or DHCP server, record that in the suppression file and review NET-SVC findings for its configuration. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l isc-dhcp-server dhcp-server kea-dhcp4-server kea-dhcp6-server bind9 bind bind9-dnsutils-server 2>/dev/null | grep '^ii' ; systemctl is-enabled isc-dhcp-server.service dhcpd.service kea-dhcp4-server.service named.service bind9.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Samba File Server Installed or Enabled (HOST-SVC-010)

Category: config. Effort: low. Confidence: high.

Checks whether the Samba SMB file server is installed or set to start at boot.

**Why it matters:** SMB file sharing exposes files, printers and authentication to the network and has been the target of worms and several critical remote code execution flaws. It is needed only on file servers for Windows-style clients. If this machine is one of the file servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 1 legacy service component(s) present
  - `package samba is installed`

**Fix:** apt purge samba  or  dnf remove samba , and  systemctl disable --now smbd nmbd  (smb nmb on the RHEL family). Where it is required, disable SMB1, require signing and restrict shares to named users. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l samba 2>/dev/null | grep '^ii' ; systemctl is-enabled smbd.service nmbd.service smb.service nmb.service samba.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] SNMP Agent or Squid Proxy Installed or Enabled (HOST-SVC-011)

Category: config. Effort: low. Confidence: high.

Checks whether an SNMP agent or the Squid caching proxy is installed or enabled.

**Why it matters:** SNMP v1 and v2c authenticate with a community string sent in clear text (public and private are the usual defaults) and reveal routing tables, interfaces, processes and installed software. Squid turns the machine into a relay for other traffic and is often left open. Both need a deliberate configuration to be anything but a liability. If this machine is one of the monitored hosts using SNMPv3 and dedicated proxy servers that legitimately run it, record the reason in the suppression file so the finding stays visible but accepted.

- **services:** 1 legacy service component(s) present
  - `package snmpd is installed`

**Fix:** apt purge snmpd squid  or  dnf remove net-snmp squid , and  systemctl disable --now snmpd squid . If monitoring needs SNMP, use version 3 with authPriv and bind the agent to the management network. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l snmpd net-snmp squid squid-openssl 2>/dev/null | grep '^ii' ; systemctl is-enabled snmpd.service squid.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-319; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] inetd Super-Server Installed or Enabled (HOST-SVC-012)

Category: config. Effort: low. Confidence: high.

Checks whether xinetd or another inetd super-server is installed or enabled.

**Why it matters:** inetd-style super-servers exist to launch small network services (echo, chargen, telnet, tftp) on demand from a shared configuration. On a systemd machine every service they would start has a native unit; the super-server adds a second, rarely reviewed, place where network services are switched on.

- **services:** 2 legacy service component(s) present
  - `package xinetd is installed`
  - `unit xinetd.service is enabled at boot`

**Fix:** apt purge xinetd openbsd-inetd  or  dnf remove xinetd , and  systemctl disable --now xinetd . Review /etc/inetd.conf and /etc/xinetd.d first so nothing needed disappears. Before removing a package, check that no application on this machine or the network still uses the service, and keep console access when the service is remote-access related.

**Verify:** `dpkg -l xinetd openbsd-inetd inetutils-inetd 2>/dev/null | grep '^ii' ; systemctl is-enabled xinetd.service inetd.service openbsd-inetd.service inetutils-inetd.service 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 CM-7, CM-7(1)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[LOW\] Custom Services Lack Sandboxing (HOST-SYSD-002)

Category: config. Effort: medium. Confidence: high.

Checks whether custom long-running services use systemd's basic sandboxing options.

**Why it matters:** systemd can confine a service with a few lines: block privilege gain through setuid programs, mount the operating system read-only, give it a private /tmp and hide home directories. These limit what an attacker can do after exploiting the service, at almost no cost.

- **systemd units:** 4 custom service(s) lack basic sandboxing
  - `shop-admin.service is missing: NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes, ProtectHome=yes`
  - `shop-api.service is missing: NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes, ProtectHome=yes`
  - `shop-reports.service is missing: NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes, ProtectHome=yes`
  - `shop-worker.service is missing: NoNewPrivileges=yes, ProtectSystem=strict, PrivateTmp=yes, ProtectHome=yes`

**Fix:** Add the missing lines with systemctl edit \<unit\> (a drop-in), for example NoNewPrivileges=yes, ProtectSystem=strict, ReadWritePaths=/var/lib/\<app\>, PrivateTmp=yes, ProtectHome=yes. Restart the service and test it; add ReadWritePaths= for every folder the app must write. systemd-analyze security \<unit\> suggests further options.

**Verify:** `systemd-analyze security <unit>  shows a lower exposure score, and the service still works.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250, CWE-653; NIST SP 800-53 CM-6, SC-39, AC-6

**Learn:** P11: Service Hardening With systemd, https://openvapt.com/academy/p11.html (offline: `openvapt learn P11`)

### \[LOW\] Time Synchronisation Not Active (HOST-TIME-001)

Category: logging. Effort: low. Confidence: high.

Checks that the clock is kept in sync (chrony, ntpd or systemd-timesyncd).

**Why it matters:** Accurate time is needed to correlate logs during an investigation, and TLS certificates and one-time-password codes fail when the clock drifts.

- **time:** No time synchronisation service is active

**Fix:** Enable one time service, for example: systemctl enable --now systemd-timesyncd  (or install chrony).

**Verify:** `timedatectl  should show 'System clock synchronized: yes'.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; NIST SP 800-53 AU-8

**Learn:** P16: Logging, Auditing and Time, https://openvapt.com/academy/p16.html (offline: `openvapt learn P16`)

### \[LOW\] Automatic Security Updates Not Configured (HOST-UPD-001)

Category: patch. Effort: low. Confidence: high.

Checks that security updates are installed automatically.

**Why it matters:** Many compromises use vulnerabilities fixed days or weeks earlier. Automatic security updates close that window without relying on someone remembering.

- **updates:** No automatic security update mechanism is enabled

**Fix:** Debian/Ubuntu: apt install unattended-upgrades && dpkg-reconfigure -plow unattended-upgrades . RHEL family: dnf install dnf-automatic, set apply\_updates = yes and upgrade\_type = security, then systemctl enable --now dnf-automatic.timer .

**Verify:** `Re-run this check.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104; OWASP Top 10 A06:2021; NIST SP 800-53 SI-2(2)

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

### \[LOW\] System Accounts With Login Shells (HOST-USR-005)

Category: identity. Effort: low. Confidence: high.

Checks that service accounts (UID below UID\_MIN, usually 1000, other than root) cannot log in interactively.

**Why it matters:** Service accounts should never need a shell. A login shell gives an attacker who compromises the service, or guesses its password, an interactive foothold.

- **/etc/passwd:** System accounts have login shells
  - `daemon (/bin/bash)`

**Fix:** usermod -s /usr/sbin/nologin \<account\>  for each listed account (use /sbin/nologin on RHEL family).

**Verify:** `awk -F: '$3>0 && $3<1000 {print $1, $7}' /etc/passwd`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-2, CM-7

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[LOW\] Home Directories Accessible to Other Users (HOST-USR-010)

Category: access. Effort: low. Confidence: high.

Checks that the home directory of each interactive account denies read, write and execute to others.

**Why it matters:** Home directories hold shell history, SSH keys, application configuration and downloads. With others allowed in, any account on the machine can read another user's history and keys or, with write access, plant a .bashrc that runs when that user logs in.

- **/home:** Home directories are accessible to other users
  - `0755 /home/deploy (deploy) is accessible to others`

**Fix:** chmod 750 /home/\<user\>  for each listed account (700 if group access is not needed), and set HOME\_MODE 0750 in /etc/login.defs or DIR\_MODE=0750 in /etc/adduser.conf so new accounts start that way.

**Verify:** `stat -c '%a %n' /home/*  shows 750 or 700.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-732, CWE-276; NIST SP 800-53 AC-3, AC-6

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

**Learn:** P20: Secrets, Cron and Account Hygiene, https://openvapt.com/academy/p20.html (offline: `openvapt learn P20`)

### \[LOW\] MySQL Error Log Not Configured (NET-DB-017)

Category: logging. Effort: low. Confidence: high.

log\_error is not set, so server errors, failed logins and startup warnings go to the console or syslog without a dedicated, retained file.

**Why it matters:** The error log is the first place to see authentication failures, crashes and plugin warnings. A named file with rotation makes those events available to incident response and to the audit checks that read it.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** log\_error is not set, so server errors, failed logins and startup warnings go to the console or syslog without a dedicated, retained file.
  - `log_error is not set in the option files`

**Fix:** Add  log\_error = /var/log/mysql/error.log  under \[mysqld\], make the folder writable by the mysql user, and add a logrotate rule.

**Verify:** `SHOW VARIABLES LIKE 'log_error';  should print the file path.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; ASVS 7.1.1; NIST SP 800-53 AU-2, AU-12

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[LOW\] MySQL Resolves Client Host Names (NET-DB-018)

Category: data. Effort: low. Confidence: high.

skip-name-resolve is not set, so grants that name hosts depend on DNS answers and every connection waits on a reverse lookup.

**Why it matters:** Host-based grants that trust names can be satisfied by whoever controls DNS or the resolver path. With skip-name-resolve the server matches IP addresses only, which is both faster and not spoofable through DNS. Grants that use host names must be rewritten first.

- **/etc/mysql/mysql.conf.d/mysqld.cnf:** skip-name-resolve is not set, so grants that name hosts depend on DNS answers and every connection waits on a reverse lookup.
  - `skip-name-resolve is not set; the default is OFF`

**Fix:** Rewrite grants that use host names to IP addresses or '%', then add  skip-name-resolve  under \[mysqld\] and restart in a maintenance window.

**Verify:** `SHOW VARIABLES LIKE 'skip_name_resolve';  should print ON.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-350, CWE-290; OWASP Top 10 A05:2021; ASVS 14.1.3; NIST SP 800-53 CM-6, IA-3

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[LOW\] PostgreSQL Connection Logging Off (NET-DB-021)

Category: logging. Effort: low. Confidence: high.

log\_connections and log\_disconnections are off, so successful logins leave no record.

**Why it matters:** Failed logins are logged by default, but successful ones are not. Knowing which role connected from where and for how long is the base evidence for spotting credential misuse and for any later investigation.

- **/etc/postgresql/16/main/postgresql.conf:** log\_connections and log\_disconnections are off, so successful logins leave no record.
  - `log_connections is not set; the default is off`

**Fix:** Set  log\_connections = on  and  log\_disconnections = on  in postgresql.conf and reload. On PostgreSQL 18 and newer, log\_connections accepts a list such as  receipt,authentication,authorization .

**Verify:** `SHOW log_connections;  should print on (or a non-empty list).`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-778; OWASP Top 10 A09:2021; ASVS 7.1.3; NIST SP 800-53 AU-2, AU-12, AC-2(4)

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P8: Data Services: Databases, File Shares, DNS and Mail, https://openvapt.com/academy/p8.html (offline: `openvapt learn P8`)

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[LOW\] BIND Version String Disclosed (NET-DNS-001)

Category: exposure. Effort: low. Confidence: high.

named.conf has no version statement in options, so a CHAOS class query for version.bind returns the exact BIND release.

**Why it matters:** The version string lets an attacker match advisories to the server without any guessing. Replacing it costs nothing and does not break resolution.

- **/etc/bind/named.conf.options:** named.conf has no version statement in options, so a CHAOS class query for version.bind returns the exact BIND release.
  - `no version statement in options`

**Fix:** Add  version "not disclosed";  inside the options block and run named-checkconf, then rndc reconfig.

**Verify:** `dig @127.0.0.1 version.bind txt chaos +short  should print the chosen string.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; NIST SP 800-53 CM-6, SI-11

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[LOW\] Firewall Rules Open Ports With No Service (NET-FW-004)

Category: network. Effort: low. Confidence: high.

Checks for firewall allow rules whose port has no listening service on this machine.

**Why it matters:** Stale rules are holes waiting for a service. Anything later started on that port, by an administrator, a package or an attacker, becomes reachable without anyone deciding it should be. Rule sets also become impossible to review when they no longer match reality.

- **firewall:** 1 allow rule(s) open ports with no service
  - `port 8443/tcp allowed by iptables but nothing listens on it: -A INPUT -p tcp -m tcp --dport 8443 -j ACCEPT`

**Fix:** Remove rules for services that no longer exist (ufw status numbered, then ufw delete \<n\>), or note why a rule must stay. Review the firewall whenever a service is retired.

**Verify:** `Re-run this check; every allowed port should have a listening service.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 SC-7, CM-7, CM-3

**Learn:** P3: Network Exposure and Firewalls, https://openvapt.com/academy/p3.html (offline: `openvapt learn P3`)

**Learn:** P12: Firewall Policy Versus What Is Listening, https://openvapt.com/academy/p12.html (offline: `openvapt learn P12`)

### \[LOW\] Postfix VRFY Command Enabled (NET-MAIL-003)

Category: exposure. Effort: low. Confidence: high.

disable\_vrfy\_command is not yes, so anyone who connects can ask the server whether a mailbox exists.

**Why it matters:** VRFY lets a sender enumerate valid addresses one by one, which feeds spam lists and targeted phishing. Legitimate mail flow never needs it.

- **/etc/postfix/main.cf:** disable\_vrfy\_command is not yes, so anyone who connects can ask the server whether a mailbox exists.
  - `disable_vrfy_command is not set; the default is no`

**Fix:** Add  disable\_vrfy\_command = yes  to main.cf and postfix reload.

**Verify:** `postconf disable_vrfy_command  should print yes; an authorised  VRFY postmaster  over a local SMTP session should return 502.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-204; OWASP Top 10 A05:2021; NIST SP 800-53 CM-7, SI-11

**Learn:** P19: Database, Mail and DNS Hardening, https://openvapt.com/academy/p19.html (offline: `openvapt learn P19`)

### \[LOW\] Apache Reveals Version in Header and Footer (WEB-APA-001)

Category: exposure. Effort: low. Confidence: high.

ServerTokens is not Prod or ServerSignature is On, so responses and error pages show the Apache version and modules.

**Why it matters:** The Server header and the signature line on error pages tell an attacker the exact release and loaded modules. Hiding them removes a free hint; patching remains the real fix.

- **/etc/apache2/apache2.conf:** ServerTokens is not Prod or ServerSignature is On, so responses and error pages show the Apache version and modules.
  - `/etc/apache2/conf-enabled/security.conf: ServerTokens OS`
  - `/etc/apache2/conf-enabled/security.conf: ServerSignature On`

**Fix:** Set  ServerTokens Prod  and  ServerSignature Off  in apache2.conf or conf-enabled/security.conf (Debian) or httpd.conf (RHEL), then apachectl configtest and reload.

**Verify:** `curl -sI https://<site>/ | grep -i '^server:'  should print Server: Apache with nothing after it.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.3; WSTG-INFO-02; NIST SP 800-53 CM-6, SI-11

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Apache TRACE Method Enabled (WEB-APA-002)

Category: config. Effort: low. Confidence: high.

TraceEnable is not set to Off, so the HTTP TRACE method echoes requests back to the client.

**Why it matters:** TRACE reflects request headers, including cookies and authorisation, which older cross-site tracing attacks used to read them from the browser. No production site needs it.

- **/etc/apache2/apache2.conf:** TraceEnable is not set to Off, so the HTTP TRACE method echoes requests back to the client.
  - `/etc/apache2/conf-enabled/security.conf: TraceEnable On`

**Fix:** Add  TraceEnable Off  to the server configuration and reload.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' -X TRACE https://<site>/  should print 405.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-749; OWASP Top 10 A05:2021; ASVS 14.5.1; WSTG-CONF-06; NIST SP 800-53 CM-7

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Apache AllowOverride All Permits .htaccess Control (WEB-APA-004)

Category: config. Effort: low. Confidence: high.

A Directory block sets AllowOverride All, so any .htaccess file under it can change handlers, options and access rules.

**Why it matters:** With AllowOverride All, whoever can write a file into the document root (an upload bug, a compromised CMS) can turn images into executable scripts or lift access controls. Limiting overrides keeps those decisions in root-owned configuration.

- **/etc/apache2/apache2.conf:** A Directory block sets AllowOverride All, so any .htaccess file under it can change handlers, options and access rules.
  - `/etc/apache2/apache2.conf: AllowOverride All`

**Fix:** Set  AllowOverride None  and move needed rules into the vhost, or allow only what the application needs, for example  AllowOverride FileInfo Indexes Limit  for WordPress permalinks.

**Verify:** `grep -rn AllowOverride /etc/apache2 /etc/httpd 2>/dev/null  should show no All.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-15, CWE-1188; OWASP Top 10 A05:2021; ASVS 14.1.1; WSTG-CONF-05; NIST SP 800-53 CM-6, CM-7

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Apache OCSP Stapling Not Enabled (WEB-APA-007)

Category: crypto. Effort: low. Confidence: high.

A TLS-enabled Apache does not set SSLUseStapling on.

**Why it matters:** Without stapling, every browser asks the certificate authority whether the certificate is revoked, which slows the handshake and reveals the visited site to the CA. Stapling lets the server attach a fresh signed status itself.

- **/etc/apache2/apache2.conf:** A TLS-enabled Apache does not set SSLUseStapling on.
  - `SSLUseStapling is not set; the default is off`

**Fix:** Add  SSLUseStapling On  inside the vhost and  SSLStaplingCache "shmcb:logs/ssl\_stapling(32768)"  outside it, as the Mozilla generator produces.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -servername <site> -status  should print OCSP Response Status: successful.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-299; OWASP Top 10 A02:2021; ASVS 9.2.4; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-17

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Apache ETag Reveals Inode Numbers (WEB-APA-009)

Category: exposure. Effort: low. Confidence: high.

FileETag includes INode (or All), so ETag headers leak filesystem inode numbers of served files.

**Why it matters:** Inode numbers help distinguish servers behind a load balancer and have been used to infer file identity across hosts (CVE-2003-1418). The Apache 2.4 default of MTime Size does not include them.

- **/etc/apache2/apache2.conf:** FileETag includes INode (or All), so ETag headers leak filesystem inode numbers of served files.
  - `/etc/apache2/apache2.conf: FileETag INode MTime Size`

**Fix:** Set  FileETag MTime Size  or  FileETag None  and reload.

**Verify:** `curl -sI https://<site>/robots.txt | grep -i etag  should show a two-part value, not three.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.3; WSTG-INFO-02; NIST SP 800-53 SI-11, CM-6

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Apache Serves Repository and Environment Files (WEB-APA-010)

Category: exposure. Effort: low. Confidence: high.

No DirectoryMatch, FilesMatch or RedirectMatch rule denies .git, .svn, .hg or .env paths, so they are served if present under a document root.

**Why it matters:** Repository folders rebuild the source code and its history; .env files hold database passwords and application keys. Debian's default only protects .ht\* files. One rule for every dot path removes the whole class, including files deployed by mistake later. The rating is Low because this is a missing safety net, not an exposure: when such a file is actually present under a document root, WEB-APP-009 and WEB-DOC-001 report it at their own higher severity, and when none is present the check passes and only notes the missing rule.

- **/etc/apache2/apache2.conf:** No DirectoryMatch, FilesMatch or RedirectMatch rule denies .git, .svn, .hg or .env paths, so they are served if present under a document root.
  - `no DirectoryMatch, FilesMatch or RedirectMatch rule covers .git, .svn or .env, and these exist under a document root:`
  - `/var/www/shop/.git`
  - `/var/www/shop/.env`

**Fix:** Add to the server configuration:  \<DirectoryMatch "/\\.(git|svn|hg)"\> Require all denied \</DirectoryMatch\>  and  \<FilesMatch "^\\.(env|git|svn)"\> Require all denied \</FilesMatch\> , then reload.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/.git/HEAD  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-538, CWE-527; OWASP Top 10 A05:2021; ASVS 12.5.1, 14.3.3; WSTG-CONF-04; NIST SP 800-53 CM-7, SC-28

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Flask Debug Mode Enabled in Application Code (WEB-APP-007)

Category: config. Effort: low. Confidence: medium.

Looks for app.run(debug=True) or app.debug = True in Flask entry files. A heuristic; the service check WEB-PY-004 covers the environment switches.

**Why it matters:** Debug mode in code follows the application wherever it is deployed. If the file is ever run directly instead of through a WSGI server, the Werkzeug debugger and its code console come up for every visitor.

- **/srv/shop-reports/app.py:** Flask debug mode is enabled in code
  - `app.run(debug=True)`

**Fix:** Remove debug=True from app.run() and read it from the environment for local work only (app.run(debug=os.environ.get('FLASK\_DEBUG') == '1')); serve production with Gunicorn or uWSGI, which never call app.run().

**Verify:** `grep -n 'debug' app.py  shows no hard-coded True.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-215; OWASP Top 10 A05:2021; ASVS 14.3.2; WSTG-CONF-02; NIST SP 800-53 CM-6, CM-7

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[LOW\] WordPress Version Disclosed (WEB-CMS-001)

Category: exposure. Effort: low. Confidence: high.

Checks whether WordPress sites reveal their exact version in the page or in readme.html.

**Why it matters:** The version number lets automated tools pick matching exploits for outdated cores and plugins.

- **shop.example.test:** WordPress version is disclosed
  - `generator tag: WordPress 5.2.1`
  - `/readme.html is public and states WordPress 5.2.1`

**Fix:** Keep WordPress updated (automatic minor updates on). Remove the generator tag (remove\_action('wp\_head','wp\_generator')) and delete readme.html after each update, or block it at the web server.

**Verify:** `curl -s https://<site>/ | grep -i 'name="generator"'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021, A06:2021; ASVS 14.3.3; WSTG-INFO-08; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] WordPress XML-RPC Interface Enabled (WEB-CMS-002)

Category: webapp. Effort: low. Confidence: high.

Checks whether xmlrpc.php answers on WordPress sites.

**Why it matters:** XML-RPC allows many password guesses in one request (system.multicall) and is abused for pingback amplification. Few sites still need it.

- **shop.example.test:** xmlrpc.php is enabled

**Fix:** Block it at the web server (\<Files xmlrpc.php\> Require all denied \</Files\>) or disable it with a security plugin, unless Jetpack or the mobile app needs it.

**Verify:** `curl -s https://<site>/xmlrpc.php  should return 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-307; OWASP Top 10 A07:2021; ASVS 2.2.1; NIST SP 800-53 AC-7, CM-7

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] WordPress User Accounts Enumerable (WEB-CMS-003)

Category: exposure. Effort: low. Confidence: high.

Checks whether the WordPress REST API lists user accounts to anonymous visitors.

**Why it matters:** A list of login names halves the work of password guessing and phishing against administrators.

- **shop.example.test:** Exposed: /wp-json/wp/v2/users (HTTP 200)
  - `/wp-json/wp/v2/users (HTTP 200)`

**Fix:** Restrict the users endpoint to authenticated requests with a security plugin or a small rest\_endpoints filter, and use login names that differ from public display names.

**Verify:** `curl -s https://<site>/wp-json/wp/v2/users  should return 401, 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-204; OWASP Top 10 A07:2021; WSTG-IDNT-04; NIST SP 800-53 CM-6, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] WordPress Dashboard File Editor Enabled (WEB-CMS-007)

Category: config. Effort: low. Confidence: high.

Checks whether wp-config.php leaves the built-in theme and plugin file editor enabled.

**Why it matters:** The editor lets any administrator account change PHP files from the browser. A stolen or phished admin session then becomes code execution on the server in one step.

- **/var/www/shop/wp-config.php:** WordPress file editor is enabled
  - `DISALLOW_FILE_EDIT is not set to true`

**Fix:** Add define('DISALLOW\_FILE\_EDIT', true); to wp-config.php above the line that says to stop editing.

**Verify:** `The Theme File Editor and Plugin File Editor menus disappear from the dashboard.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-94, CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 CM-7, AC-6

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] Joomla Version Disclosed (WEB-CMS-010)

Category: exposure. Effort: low. Confidence: high.

Checks whether a Joomla site serves its version manifest at /administrator/manifests/files/joomla.xml.

**Why it matters:** The manifest gives the exact Joomla version, which lets automated tools pick exploits for known vulnerabilities, such as the unauthenticated information disclosure CVE-2023-23752.

- **shop.example.test:** Joomla version manifest is public
  - `version 3.9.0`

**Fix:** Block the folder at the web server (Apache: \<Directory "/path/administrator/manifests"\> Require all denied \</Directory\>; nginx: location ^\~ /administrator/manifests/ { deny all; }) and keep Joomla updated.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/administrator/manifests/files/joomla.xml  prints 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021, A06:2021; ASVS 14.3.3; WSTG-INFO-08; NIST SP 800-53 SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] Drupal Version Disclosed by Changelog (WEB-CMS-011)

Category: exposure. Effort: low. Confidence: high.

Checks whether a Drupal site serves CHANGELOG.txt, which names the exact Drupal version.

**Why it matters:** The changelog tells attackers precisely which Drupal security advisories apply, as was used at scale for Drupalgeddon 2 (CVE-2018-7600).

- **shop.example.test:** Drupal changelog is public
  - `/core/CHANGELOG.txt: Drupal 8.5.0, 2018-03-07`

**Fix:** Delete the changelog files after each update, or deny \*.txt files in the Drupal root and core folders at the web server, and keep Drupal updated.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/core/CHANGELOG.txt  prints 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021, A06:2021; ASVS 14.3.3; WSTG-INFO-08; NIST SP 800-53 SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

### \[LOW\] WordPress Author Archive Reveals Login Names (WEB-CMS-018)

Category: exposure. Effort: low. Confidence: high.

Checks whether /?author=1 redirects to an author archive, which exposes the first account's login name in the address.

**Why it matters:** The author archive address contains the user's slug, which by default equals the login name. Together with the REST API listing (WEB-CMS-003) this gives attackers the administrator's user name for password guessing without touching the login form.

- **shop.example.test:** /?author=1 redirects to the author archive
  - `Location: .../author/<login-name>/`

**Fix:** Return 404 for author queries by id (a small redirect rule or a security plugin's user enumeration protection), and give administrators a nickname and display name that differ from the login name.

**Verify:** `curl -sI https://<site>/?author=1 | grep -i location  prints nothing or a 404 page.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-204; OWASP Top 10 A07:2021; WSTG-IDNT-04; NIST SP 800-53 CM-6, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[LOW\] Dependency Manifests Exposed (WEB-EXP-009)

Category: exposure. Effort: low. Confidence: high.

Checks whether package manifests and lock files are downloadable.

**Why it matters:** Lock files list exact library versions, giving attackers a ready list to match against public vulnerabilities, and often reveal internal package names.

- **shop.example.test:** Exposed: /composer.lock (HTTP 200)
  - `/composer.lock (HTTP 200)`

**Fix:** Keep manifests outside the public web root (for example serve only a public/ subfolder), or deny them at the web server.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/composer.lock  should print 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-538; OWASP Top 10 A05:2021, A06:2021; ASVS 12.5.1; WSTG-CONF-04; NIST SP 800-53 CM-7

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[LOW\] Default Web Server Page Served (WEB-EXP-010)

Category: exposure. Effort: low. Confidence: high.

Checks whether a site shows the web server's default installation page.

**Why it matters:** Default pages reveal the server software and suggest an unmaintained or forgotten virtual host, which attackers prioritise.

- **shop.example.test:** Exposed: / (HTTP 200)
  - `/ (HTTP 200)`

**Fix:** Remove the default site or replace its index page, and disable unused virtual hosts.

**Verify:** `Open the site in a browser; the default page should not appear.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188, CWE-497; OWASP Top 10 A05:2021; WSTG-INFO-02, WSTG-CONF-02; NIST SP 800-53 CM-7

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[LOW\] Missing X-Content-Type-Options (WEB-HDR-004)

Category: webapp. Effort: low. Confidence: high.

Checks that browsers are told not to guess (sniff) content types.

**Why it matters:** Without nosniff, a browser may execute an uploaded text or image file as script or HTML, enabling XSS through file uploads.

- **shop.example.test:** X-Content-Type-Options is missing or not 'nosniff'
  - `  `

**Fix:** Send  X-Content-Type-Options: nosniff  on every response (Apache: Header always set X-Content-Type-Options nosniff; nginx: add\_header X-Content-Type-Options nosniff always;).

**Verify:** `curl -sI https://<site>/ | grep -i x-content-type-options`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-430; OWASP Top 10 A05:2021; ASVS 14.4.4; NIST SP 800-53 CM-6

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[LOW\] Missing Referrer-Policy (WEB-HDR-005)

Category: webapp. Effort: low. Confidence: high.

Checks that each site limits what URL information is leaked to other sites in the Referer header.

**Why it matters:** Full URLs can contain session tokens, password-reset links or personal data, which would otherwise be sent to every third-party resource and outbound link.

- **shop.example.test:** Referrer-Policy is missing or not ''
  - `  `

**Fix:** Send  Referrer-Policy: strict-origin-when-cross-origin  (or no-referrer). Never use unsafe-url.

**Verify:** `curl -sI https://<site>/ | grep -i referrer-policy`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-201; OWASP Top 10 A05:2021; ASVS 14.4.6; NIST SP 800-53 CM-6

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[LOW\] Missing Permissions-Policy (WEB-HDR-006)

Category: webapp. Effort: low. Confidence: high.

Checks that each site restricts powerful browser features (camera, microphone, geolocation) it does not use.

**Why it matters:** Restricting features limits what injected or third-party scripts can do in your visitors' browsers.

- **shop.example.test:** Permissions-Policy is missing or not ''
  - `  `

**Fix:** Send, for example,  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()  adjusted to the features the site really uses.

**Verify:** `curl -sI https://<site>/ | grep -i permissions-policy`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; OWASP Top 10 A05:2021; NIST SP 800-53 CM-6, CM-7

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[LOW\] Server Software Version Disclosed (WEB-HDR-007)

Category: exposure. Effort: low. Confidence: high.

Checks whether response headers reveal exact web server, PHP or framework versions.

**Why it matters:** Exact versions let attackers match the site against public exploit lists in seconds. Hiding them does not fix vulnerabilities but removes free reconnaissance.

- **shop.example.test:** Software versions are disclosed in headers
  - `Server: Apache/2.4.29 (Ubuntu)`
  - `X-Powered-By: PHP/7.2.24`

**Fix:** Apache: ServerTokens Prod and ServerSignature Off . nginx: server\_tokens off; . PHP: expose\_php = Off in php.ini . Remove X-Powered-By in the application or proxy.

**Verify:** `curl -sI https://<site>/ | grep -Ei '^(server|x-powered-by)'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.3; WSTG-INFO-02; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** A8: Tracing Findings to Security Requirements, https://openvapt.com/academy/a8.html (offline: `openvapt learn A8`)

### \[LOW\] nginx Version Shown in Server Header (WEB-NGX-001)

Category: exposure. Effort: low. Confidence: high.

nginx does not set server\_tokens off, so responses and error pages carry the exact nginx version.

**Why it matters:** The version string lets an attacker pick advisories that match the server instead of guessing. Hiding it is not a fix for old software, but there is no reason to publish it.

- **/etc/nginx/nginx.conf:** nginx does not set server\_tokens off, so responses and error pages carry the exact nginx version.
  - `/etc/nginx/conf.d/tokens.conf: server_tokens on`

**Fix:** In the http block of nginx.conf add  server\_tokens off;  then run nginx -t and reload.

**Verify:** `curl -sI https://<site>/ | grep -i '^server:'  should print Server: nginx with no version.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.3; WSTG-INFO-02; NIST SP 800-53 CM-6, SI-11

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] nginx OCSP Stapling Off or Session Tickets On (WEB-NGX-005)

Category: crypto. Effort: low. Confidence: high.

A TLS-enabled nginx does not enable ssl\_stapling, or leaves ssl\_session\_tickets on.

**Why it matters:** Without stapling, every browser asks the certificate authority whether the certificate is revoked, which slows the handshake and leaks the visited site to the CA. Session tickets encrypted with a long-lived key weaken forward secrecy when the key is never rotated; the Mozilla profiles turn them off.

- **/etc/nginx/nginx.conf:** A TLS-enabled nginx does not enable ssl\_stapling, or leaves ssl\_session\_tickets on.
  - `ssl_stapling is not set; the default is off`
  - `ssl_session_tickets is not set; the default is on`

**Fix:** Add  ssl\_stapling on; ssl\_stapling\_verify on; ssl\_session\_tickets off;  and a  resolver  directive, as the Mozilla generator produces.

**Verify:** `openssl s_client -connect 127.0.0.1:443 -servername <site> -status  should print OCSP Response Status: successful.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-299, CWE-326; OWASP Top 10 A02:2021; ASVS 9.2.4; WSTG-CRYP-01; NIST SP 800-53 SC-8, SC-17

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

### \[LOW\] Node.js Service Not Running in Production Mode (WEB-NODE-003)

Category: config. Effort: low. Confidence: high.

Checks whether Node.js services set NODE\_ENV=production.

**Why it matters:** Express and many other frameworks treat a missing NODE\_ENV as development: error pages then include stack traces with file paths, and caching and other production safeguards are off.

- **systemd units:** Node.js services are not in production mode
  - `shop-api.service: NODE_ENV=development`

**Fix:** Add Environment=NODE\_ENV=production to the \[Service\] section (systemctl edit \<unit\>), run systemctl daemon-reload and restart the service.

**Verify:** `systemctl show -p Environment <unit>  includes NODE_ENV=production.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-489, CWE-209; OWASP Top 10 A05:2021; ASVS 14.3.2; NIST SP 800-53 CM-6

**Learn:** P13: Application Servers: Tomcat, Node.js and Python, https://openvapt.com/academy/p13.html (offline: `openvapt learn P13`)

### \[LOW\] PHP Version Exposed in Headers (WEB-PHP-002)

Category: exposure. Effort: low. Confidence: high.

Checks whether PHP adds an X-Powered-By header with its version.

**Why it matters:** The header advertises the exact PHP version to every visitor and scanner.

- **php:** expose\_php = 1

**Fix:** In php.ini set  expose\_php = Off  and restart PHP.

**Verify:** `php -r "echo ini_get('expose_php');"  and check the FPM pool's php.ini or php_admin_value settings.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-497; OWASP Top 10 A05:2021; ASVS 14.3.3; WSTG-INFO-02; NIST SP 800-53 CM-7, SI-11

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

### \[INFO\] Docker Not Running in Rootless Mode (HOST-CTR-009)

Category: config. Effort: high. Confidence: high.

Reports whether the Docker daemon runs as root or in rootless mode. Informational.

**Why it matters:** In rootless mode the daemon and every container run inside a user namespace as an ordinary account, so a container breakout lands in that account rather than in root. It is not possible for every workload (some networking and storage features differ), so this is information for a design decision, not a failure.

- **docker:** Docker runs as the root daemon only; rootless mode is not in use

**Fix:** Where the workloads allow it, install rootless Docker (dockerd-rootless-setuptool.sh install) for the service account and disable the system daemon; otherwise keep userns-remap (HOST-CTR-002) as the fallback.

**Verify:** `docker info --format '{{.SecurityOptions}}'  lists name=rootless.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-250; NIST SP 800-53 AC-6, SC-39

**Learn:** P9: Containers and Kubernetes, https://openvapt.com/academy/p9.html (offline: `openvapt learn P9`)

**Learn:** A5: Container Runtime Hardening Depth, https://openvapt.com/academy/a5.html (offline: `openvapt learn A5`)

### \[INFO\] Password Expiry Warning Too Short (HOST-PAM-006)

Category: identity. Effort: low. Confidence: high.

Reports whether users are warned at least 7 days before a password expires.

**Why it matters:** Where passwords do expire, a short or missing warning means users are locked out without notice and support staff reset passwords under pressure, which is when weak temporary passwords and social engineering succeed. This is informational: with no expiry (HOST-PWD-005) the warning never fires.

- **/etc/login.defs:** PASS\_WARN\_AGE 3 (7 or more)

**Fix:** Set  PASS\_WARN\_AGE 7  in /etc/login.defs, and for existing accounts  chage --warndays 7 \<user\> .

**Verify:** `grep '^PASS_WARN_AGE' /etc/login.defs`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-262; OWASP Top 10 A07:2021; NIST SP 800-53 IA-5(1)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[INFO\] Third-Party Package Repositories Present (HOST-PKG-003)

Category: patch. Effort: medium. Confidence: high.

Lists package repositories that are not run by the distribution vendor, so each one can be justified.

**Why it matters:** Every repository can install software as root at the next update, so each one is a supplier you trust completely. Personal package archives, vendor repositories and forgotten test repositories are a common way for unpatched or abandoned software to stay on a server. This is informational: a third-party repository is a decision to review, not a defect by itself.

- **package repositories:** 1 third-party repository host(s) configured
  - `ppa.launchpadcontent.net (/etc/apt/sources.list.d/lab-ppa.list)`

**Fix:** For each listed repository, record who runs it and why it is needed; remove the ones nobody can justify (delete the file under /etc/apt/sources.list.d or /etc/yum.repos.d and apt-get update or dnf clean all). Keep the rest pinned to the packages they are for (apt preferences, dnf includepkgs).

**Verify:** `ls /etc/apt/sources.list.d/ /etc/yum.repos.d/ 2>/dev/null`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1357; OWASP Top 10 A08:2021; NIST SP 800-53 CM-8, SR-3, SR-4

**Learn:** P1: Linux Server Hardening, https://openvapt.com/academy/p1.html (offline: `openvapt learn P1`)

**Learn:** P22: Authentication Policy and Package Hygiene, https://openvapt.com/academy/p22.html (offline: `openvapt learn P22`)

### \[INFO\] Forced Periodic Password Rotation Configured (HOST-PWD-005)

Category: identity. Effort: low. Confidence: high.

Reports whether new accounts are forced to change passwords on a short fixed schedule.

**Why it matters:** NIST SP 800-63B-4 (section 3.1.1.2) says verifiers should not require periodic password changes, and should force a change only on evidence of compromise. Frequent forced rotation pushes users toward predictable variations. CIS Controls v8 safeguard 5.2 asks for unique passwords with a minimum length (8 characters where multi-factor authentication is used, 14 where it is not) and does not ask for periodic changes. This finding is informational: a short PASS\_MAX\_DAYS is not a vulnerability, but it is worth reviewing against current guidance.

- **/etc/login.defs:** PASS\_MAX\_DAYS 90 forces a password change every 90 days; NIST SP 800-63B-4 advises against periodic rotation

**Fix:** Prefer long passphrases, a breached-password blocklist and multi-factor authentication over forced rotation. If policy allows, set  PASS\_MAX\_DAYS 365  or  99999  in /etc/login.defs and change passwords when compromise is suspected.

**Verify:** `grep '^PASS_MAX_DAYS' /etc/login.defs`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-262; NIST SP 800-53 IA-5(1)

**Learn:** P7: Identity, Passwords and Privilege, https://openvapt.com/academy/p7.html (offline: `openvapt learn P7`)

**Learn:** P15: Access Control: Accounts, Sudo and Logins, https://openvapt.com/academy/p15.html (offline: `openvapt learn P15`)

### \[INFO\] Apache Has No Request Filtering Module (WEB-APA-008)

Category: config. Effort: low. Confidence: high.

Neither mod\_security (ModSecurity) nor mod\_evasive is loaded, so Apache has no request filtering or flood control in front of the application.

**Why it matters:** A web application firewall or flood limiter is a defence-in-depth layer, not a requirement, and a misconfigured one breaks sites. The result is informational so you can decide with the application owner.

- **/etc/apache2/apache2.conf:** Neither mod\_security (ModSecurity) nor mod\_evasive is loaded, so Apache has no request filtering or flood control in front of the application.
  - `no security2_module or evasive20_module is loaded`

**Fix:** Consider ModSecurity with the OWASP Core Rule Set in detection-only mode first, or mod\_evasive for simple rate limiting, and tune before blocking.

**Verify:** `apachectl -M 2>/dev/null | grep -E 'security2|evasive'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; OWASP Top 10 A05:2021; ASVS 11.1.4; NIST SP 800-53 SC-5, SI-10

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

**Learn:** P25: Availability Without Load Testing, https://openvapt.com/academy/p25.html (offline: `openvapt learn P25`)

### \[INFO\] Dependency Lock Files Older Than a Year (WEB-APP-008)

Category: patch. Effort: medium. Confidence: high.

Reports composer.lock, package-lock.json, yarn.lock, Gemfile.lock and poetry.lock files not modified for 365 days or more (dependency freshness). Informational.

**Why it matters:** A lock file pins exact library versions. One untouched for a year means the application has had no dependency updates in that time, so every library vulnerability published since is still present. The check cannot tell whether a specific vulnerability applies; the known-vulnerability checks and composer audit or npm audit do that.

- **/srv/shop-laravel/composer.lock:** Lock file last changed 800 days ago
- **/var/www/shop/admin/Gemfile.lock:** Lock file last changed 800 days ago
- **/var/www/shop/package-lock.json:** Lock file last changed 800 days ago

**Fix:** Run composer update / npm update / bundle update / poetry update in a test environment, run the audit command of each ecosystem (composer audit, npm audit, bundle audit, pip-audit) and deploy. Schedule dependency updates at least quarterly.

**Verify:** `stat -c '%y %n' composer.lock package-lock.json  shows a recent date.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1104, CWE-1395; OWASP Top 10 A06:2021; ASVS 14.2.1; NIST SP 800-53 SI-2, SA-22

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[INFO\] WordPress Cron Reachable by Visitors (WEB-CMS-017)

Category: config. Effort: low. Confidence: high.

Reports whether /wp-cron.php answers to anonymous requests on WordPress sites. Informational.

**Why it matters:** WP-Cron runs scheduled tasks when visitors load pages, and wp-cron.php can be requested directly. On busy or targeted sites that lets anyone make the server do work on demand, and it is a frequent target for resource exhaustion; a system cron job is the documented production setup.

- **shop.example.test:** wp-cron.php answers anonymous requests

**Fix:** Add define('DISABLE\_WP\_CRON', true); to wp-config.php, run  \*/5 \* \* \* \* www-data php /var/www/\<site\>/wp-cron.php  from the system cron (or curl the URL from cron), and deny direct requests to wp-cron.php at the web server.

**Verify:** `curl -s -o /dev/null -w '%{http_code}' https://<site>/wp-cron.php  prints 403 or 404.`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770; OWASP Top 10 A05:2021; WSTG-CONF-05; NIST SP 800-53 CM-7, SC-5

**Learn:** P6: Information Exposure and Misconfiguration, https://openvapt.com/academy/p6.html (offline: `openvapt learn P6`)

**Learn:** P14: CMS Hardening in Depth: WordPress, Joomla and Drupal, https://openvapt.com/academy/p14.html (offline: `openvapt learn P14`)

**Learn:** A6: Application Framework and CMS Configuration, https://openvapt.com/academy/a6.html (offline: `openvapt learn A6`)

### \[INFO\] nginx Request Limits Not Configured (WEB-NGX-006)

Category: config. Effort: low. Confidence: high.

nginx sets no client\_max\_body\_size, limit\_req\_zone or limit\_conn\_zone, so request size and rate rely on defaults alone.

**Why it matters:** Explicit limits bound what one client can upload or how fast it can call login and search pages. They are an availability and brute-force control, informational here because the right values depend on the application.

- **/etc/nginx/nginx.conf:** nginx sets no client\_max\_body\_size, limit\_req\_zone or limit\_conn\_zone, so request size and rate rely on defaults alone.
  - `no client_max_body_size, limit_req_zone or limit_conn_zone directive`

**Fix:** Set  client\_max\_body\_size  to what the application needs (the default is 1m) and add  limit\_req\_zone  for login and API locations with a sensible rate. See the nginx limit\_req and limit\_conn module documentation.

**Verify:** `nginx -T | grep -nE 'client_max_body_size|limit_(req|conn)_zone'`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-770, CWE-307; OWASP Top 10 A05:2021, A07:2021; ASVS 11.1.4; NIST SP 800-53 SC-5, AC-7

**Learn:** P18: Web Server Hardening: nginx and Apache, https://openvapt.com/academy/p18.html (offline: `openvapt learn P18`)

**Learn:** P25: Availability Without Load Testing, https://openvapt.com/academy/p25.html (offline: `openvapt learn P25`)

### \[INFO\] No security.txt Contact File (WEB-SEC-001)

Category: webapp. Effort: low. Confidence: high.

Checks whether each site publishes /.well-known/security.txt (RFC 9116).

**Why it matters:** security.txt tells researchers how to report a vulnerability. Without it, reports may go unheard or be disclosed publicly instead.

- **shop.example.test:** No /.well-known/security.txt with a Contact field

**Fix:** Publish /.well-known/security.txt with at least Contact: and Expires: fields (see securitytxt.org).

**Verify:** `curl -s https://<site>/.well-known/security.txt`

If you believe this is a false positive, report it with the False Positive form (https:\/\/github.com/OpenVAPT/openvapt/issues/new?template=false-positive.yml), or accept it with an expiry date in a suppression file (https:\/\/github.com/OpenVAPT/openvapt\#suppressing-accepted-findings).

**Standards:** CWE-1188; NIST SP 800-53 RA-5(11)

**Learn:** P5: Web Security Headers and Cookies, https://openvapt.com/academy/p5.html (offline: `openvapt learn P5`)

## Passed and Skipped Checks

| ID | Title | Status |
|---|---|---|
| NET-FW-006 | Firewall Exposure Map of Listening Services | pass |
| NET-LSN-001 | Inventory of Listening Network Services | pass |

## Coverage Gaps

Incomplete, Error and Skipped checks do not establish that the affected area is secure.

| ID | Status | Affected Area and Reason |
|---|---|---|
| NET-TLS-001 | incomplete | shop.example.test on port 10000: Modern TLS handshake failed, so legacy protocol rejection could not be verified |
| NET-TLS-005 | incomplete | shop.example.test on port 10000: TLS certificate could not be read |
| NET-TLS-006 | incomplete | shop.example.test on port 10000: TLS certificate could not be read |

## Methodology

- Scope: this machine only. Every network connection is restricted in code to loopback addresses (127.0.0.0/8, ::1). No other host was contacted.
- Approach: automated, non-intrusive vulnerability assessment following the Planning, Discovery and Reporting phases of NIST SP 800-115 (its four phases are Planning, Discovery, Attack and Reporting); the Attack phase is omitted and no exploitation was attempted.
- Discovery: configuration files, /proc and /sys were read; a short allow-list of read-only commands was run (for example sshd -T and apt-get -s upgrade, which only simulates).
- Web: websites were discovered from the local web server configuration and requested through 127.0.0.1 with GET requests for fixed paths only. No forms were submitted and no attack payloads were sent.
- Rating: each check carries a qualitative severity on the CVSS v3.1 scale with a representative base vector. Environmental factors on your network may raise or lower the real risk. Detective gaps such as missing logging, auditing or integrity monitoring carry a local attack vector: a missing record opens no network path, it makes a separate intrusion harder to trace.
- Evidence: secrets (passwords, keys, tokens, hashes) were redacted before writing this report.

## Limitations

- An automated self-assessment is not a penetration test. It does not prove that the machine is secure; absence of findings is not proof of absence of vulnerabilities.
- Only the checks listed in this report were performed. Application logic flaws (for example SQL injection, broken access control or business logic errors) require manual testing or code review.
- Some checks need root to read protected files or the real daemon state. A check that could not read what it needed is marked Skipped (it did not apply or could not start) or Incomplete (it ran, but part of its input was unreadable, truncated or missing); neither is a Pass.
- Results reflect the machine at the time of the scan. Configuration drift after the scan is not covered.
- End-of-life and version tables are dated and may be out of date for releases newer than this version of OpenVAPT.
- This report was produced by a pre-release build of OpenVAPT (Unreleased Build, v0.1.0-dev). Pre-release builds may be incomplete or wrong; confirm important findings by hand.

## How to Cite

```text
Report generated by OpenVAPT (unreleased build) on 2026-10-16.
```

Using OpenVAPT in research, teaching or a report? Please cite it.

**APA 7:**

```text
OpenVAPT. (2026). OpenVAPT: An open academic vulnerability assessment tool (Version unreleased build) [Computer software]. https://github.com/OpenVAPT/openvapt
```

**BibTeX:**

```bibtex
@software{openvapt_2026,
  author       = {{OpenVAPT}},
  title        = {{OpenVAPT: An Open Academic Vulnerability Assessment Tool}},
  year         = {2026},
  version      = {unreleased build},
  url          = {https://github.com/OpenVAPT/openvapt},
  note         = {Built by AI Cybersecurity Engineers. https://openvapt.com},
  license      = {LicenseRef-OpenVAPT-SAL-1.3}
}
```

**Credit when you share this report:**

```text
Produced with OpenVAPT (https://openvapt.com), built by AI Cybersecurity Engineers, used under the OpenVAPT Source-Available Licence 1.3. Use does not imply endorsement.
```
