{
  "schema": "openvapt.report/3",
  "tool": {
    "name": "OpenVAPT",
    "version": "v0.1.0-dev",
    "channel": "dev",
    "commit": "unknown",
    "url": "https://openvapt.com"
  },
  "scan": {
    "started": "2026-10-16T02:10:05.557105Z",
    "finished": "2026-10-16T02:10:05.568395Z",
    "host": "lab-hardened (simulated)",
    "os": "Ubuntu 24.04.1 LTS",
    "as_root": true,
    "checks": 310,
    "sites": [
      "shop.example.test"
    ],
    "vulndb": {
      "generated": "2026-10-16",
      "fixture": false,
      "advisories": 7,
      "age_days": 0,
      "stale": false
    }
  },
  "summary": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "low": 0,
    "info": 0,
    "passed": 310,
    "failed": 0,
    "skipped": 0,
    "errors": 0,
    "incomplete": 0,
    "incomplete_targets": 0
  },
  "findings": [
    {
      "id": "HOST-CTR-001",
      "title": "Docker API Exposed Over TCP Without TLS",
      "domain": "host",
      "severity": "critical",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the Docker daemon is configured to listen on a TCP socket without TLS client verification.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Docker documentation, Protect the Docker daemon socket",
          "url": "https://docs.docker.com/engine/security/protect-access/"
        },
        {
          "title": "OWASP Docker Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-319"
        ],
        "owasp": [
          "A05:2021",
          "A07:2021"
        ],
        "nist": [
          "AC-3",
          "SC-8"
        ],
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "docker",
          "message": "Docker API is not exposed over unauthenticated TCP"
        }
      ]
    },
    {
      "id": "HOST-CVE-002",
      "title": "Known Exploited Vulnerabilities Present",
      "domain": "host",
      "severity": "critical",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks installed packages against an offline vulnerability snapshot.",
      "rationale": "Known vulnerable package versions can expose a server to documented attacks.",
      "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.",
      "refs": [
        {
          "title": "OSV vulnerability database",
          "url": "https://osv.dev/"
        },
        {
          "title": "NVD",
          "url": "https://nvd.nist.gov/"
        },
        {
          "title": "CISA KEV",
          "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/cve.go",
      "academy": [
        {
          "id": "P10",
          "title": "Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities",
          "url": "https://openvapt.com/academy/p10.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "OS package inventory",
          "message": "No matching advisories in the embedded snapshot",
          "evidence": [
            "Snapshot date 2026-10-16. An outdated snapshot misses newer vulnerabilities."
          ]
        }
      ]
    },
    {
      "id": "HOST-K8S-001",
      "title": "Kubelet Allows Anonymous or Unauthorised Access",
      "domain": "host",
      "severity": "critical",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the kubelet configuration for anonymous authentication, AlwaysAllow authorisation or the unauthenticated read-only port.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Kubernetes documentation, Kubelet authentication and authorization",
          "url": "https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/"
        },
        {
          "title": "Kubernetes documentation, Securing a cluster",
          "url": "https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-862"
        ],
        "owasp": [
          "A01:2021",
          "A07:2021"
        ],
        "nist": [
          "AC-3",
          "IA-2"
        ],
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/lib/kubelet/config.yaml",
          "message": "Kubelet requires authentication and authorisation"
        }
      ]
    },
    {
      "id": "HOST-SSH-003",
      "title": "SSH Permits Empty Passwords",
      "domain": "host",
      "severity": "critical",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether SSH lets accounts with an empty password log in.",
      "rationale": "Any account without a password becomes an open door to anyone on the network.",
      "fix": "Set  PermitEmptyPasswords no  in /etc/ssh/sshd_config, reload sshd, and give every account a password or lock it (passwd -l \u003cuser\u003e).",
      "verify": "sshd -T | grep -i permitemptypasswords  should print 'no'.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-258"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "SSH server configuration: empty passwords",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_empty_passwords.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "Empty passwords are refused"
        }
      ]
    },
    {
      "id": "NET-DB-002",
      "title": "Redis Authentication and Network Binding",
      "domain": "network",
      "severity": "critical",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Redis lacks protected mode, authentication, or a loopback-only bind.",
      "rationale": "An unauthenticated network-reachable Redis instance can disclose or alter data and may permit dangerous administrative operations.",
      "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.",
      "refs": [
        {
          "title": "Redis Security",
          "url": "https://redis.io/docs/latest/operate/oss_and_stack/management/security/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/redis/redis.conf",
          "message": "Required security settings are present",
          "evidence": [
            "/etc/redis/redis.conf: protected-mode = yes",
            "/etc/redis/redis.conf: bind = 127.0.0.1 ::1",
            "/etc/redis/redis.conf: user = app on \u003eredacted ~* +@all"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-003",
      "title": "MongoDB Authorization and Network Binding",
      "domain": "network",
      "severity": "critical",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "MongoDB authorization is disabled or it binds beyond loopback.",
      "rationale": "MongoDB without authorization or with broad exposure can permit unauthenticated database access.",
      "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 \u003cfile\u003e --configExpand none in a maintenance workflow and confirm port 27017 is loopback-only.",
      "refs": [
        {
          "title": "MongoDB Security Checklist",
          "url": "https://www.mongodb.com/docs/manual/administration/security-checklist/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mongod.conf",
          "message": "Required security settings are present",
          "evidence": [
            "/etc/mongod.conf: authorization = enabled",
            "/etc/mongod.conf: bindip = 127.0.0.1"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-004",
      "title": "PostgreSQL Trust and Broad Client Access",
      "domain": "network",
      "severity": "critical",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "PostgreSQL accepts trust authentication or unrestricted client networks.",
      "rationale": "Trust authentication and all-address client rules can allow database access without a password from untrusted networks.",
      "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.",
      "refs": [
        {
          "title": "PostgreSQL Client Authentication",
          "url": "https://www.postgresql.org/docs/current/client-authentication.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postgresql/16/main/postgresql.conf",
          "message": "Required security settings are present"
        }
      ]
    },
    {
      "id": "NET-DB-007",
      "title": "Elasticsearch Security and Network Binding",
      "domain": "network",
      "severity": "critical",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Elasticsearch security is disabled or network.host is broadly reachable.",
      "rationale": "An exposed Elasticsearch cluster without authentication can disclose, alter, or delete indexed data.",
      "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.",
      "refs": [
        {
          "title": "Elastic Security Minimal Setup",
          "url": "https://www.elastic.co/docs/deploy-manage/security/set-up-minimal-security"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/elasticsearch/elasticsearch.yml",
          "message": "Required security settings are present",
          "evidence": [
            "/etc/elasticsearch/elasticsearch.yml: xpack.security.enabled = true",
            "/etc/elasticsearch/elasticsearch.yml: network.host = 127.0.0.1"
          ]
        }
      ]
    },
    {
      "id": "NET-SVC-002",
      "title": "Control Plane or Remote Desktop Ports Network-Reachable",
      "domain": "network",
      "severity": "critical",
      "category": "network",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Docker API, Kubernetes API, kubelet, etcd, VNC or RDP listen on network-reachable addresses.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Docker Protect the Daemon Socket",
          "url": "https://docs.docker.com/engine/security/protect-access/"
        },
        {
          "title": "Kubernetes Ports and Protocols",
          "url": "https://kubernetes.io/docs/reference/networking/ports-and-protocols/"
        },
        {
          "title": "IANA Service Name and Transport Protocol Port Number Registry",
          "url": "https://www.iana.org/assignments/service-names-port-numbers"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1327",
          "CWE-923"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-17",
          "CM-7",
          "SC-7"
        ],
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No control plane or remote desktop port is network-reachable"
        }
      ]
    },
    {
      "id": "WEB-EXP-002",
      "title": "Environment or Secrets File Exposed",
      "domain": "web",
      "severity": "critical",
      "category": "exposure",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether .env and similar configuration files can be downloaded.",
      "rationale": "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.",
      "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://\u003csite\u003e/.env  should print 403 or 404.",
      "refs": [
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-538",
          "CWE-260"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2",
          "2.10.4"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-NODE-002",
      "title": "Node.js Debugger (Inspector) Enabled in a Service",
      "domain": "web",
      "severity": "critical",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether a Node.js service is started with --inspect, which opens the V8 debugger.",
      "rationale": "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.",
      "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 \u003cunit\u003e | grep -c inspect  prints 0, and nothing listens on 9229.",
      "refs": [
        {
          "title": "Node.js documentation, Debugging guide (security implications)",
          "url": "https://nodejs.org/learn/getting-started/debugging"
        },
        {
          "title": "OWASP Node.js Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-306"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-7",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "No Node.js service starts the inspector"
        }
      ]
    },
    {
      "id": "WEB-PY-004",
      "title": "Python Interactive Debugger Enabled in a Service",
      "domain": "web",
      "severity": "critical",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether a service runs Flask or Werkzeug with the interactive debugger enabled.",
      "rationale": "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.",
      "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 \u003cunit\u003e  shows no debug switches.",
      "refs": [
        {
          "title": "Werkzeug documentation, Debugging Applications",
          "url": "https://werkzeug.palletsprojects.com/en/stable/debug/"
        },
        {
          "title": "Flask documentation, Debug mode",
          "url": "https://flask.palletsprojects.com/en/stable/quickstart/#debug-mode"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-94"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-7",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "No service enables the Flask or Werkzeug debugger"
        }
      ]
    },
    {
      "id": "WEB-TOM-002",
      "title": "Tomcat Administrative Users With Default or Weak Passwords",
      "domain": "web",
      "severity": "critical",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks tomcat-users.xml for manager or admin accounts with well-known default passwords, passwords equal to the user name, or short passwords.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Apache Tomcat 10.1, Security Considerations",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/security-howto.html"
        },
        {
          "title": "Apache Tomcat 10.1, Realm Configuration How-To (digested passwords)",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/realm-howto.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1392",
          "CWE-521",
          "CWE-256"
        ],
        "owasp": [
          "A07:2021"
        ],
        "asvs": [
          "2.1.1",
          "2.5.4"
        ],
        "wstg": [
          "WSTG-ATHN-02"
        ],
        "nist": [
          "IA-5"
        ],
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/web/tomcat.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/tomcat9",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-TOM-004",
      "title": "Tomcat AJP Connector Unprotected (Ghostcat)",
      "domain": "web",
      "severity": "critical",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks for an active AJP connector that has no shared secret and is not bound to loopback, or that disables the secret requirement.",
      "rationale": "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.",
      "fix": "If you do not use AJP (most sites proxy over HTTP), delete or comment out the AJP \u003cConnector\u003e. 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 \u003cconf\u003e/server.xml  and  ss -tlnp | grep 8009  shows nothing or 127.0.0.1 only.",
      "refs": [
        {
          "title": "Apache Tomcat 10.1, Security Considerations",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/security-howto.html"
        },
        {
          "title": "Apache Tomcat 10.1 Configuration Reference, The AJP Connector",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/config/ajp.html"
        },
        {
          "title": "NVD, CVE-2020-1938",
          "url": "https://nvd.nist.gov/vuln/detail/cve-2020-1938"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021",
          "A06:2021"
        ],
        "nist": [
          "SC-7",
          "AC-3",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/tomcat.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/tomcat9",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "HOST-CRON-001",
      "title": "Cron Jobs Run Programs Other Users Can Modify",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "fix": "chmod o-w \u003cprogram\u003e 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 '/^[^#]/ \u0026\u0026 NF\u003e6 {print $7}' /etc/crontab /etc/cron.d/*); do stat -c '%a %U %n' $p; done",
      "refs": [
        {
          "title": "crontab(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/crontab.5.html"
        },
        {
          "title": "MITRE ATT\u0026CK T1053.003, Scheduled Task/Job: Cron",
          "url": "https://attack.mitre.org/techniques/T1053/003/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-426"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "CM-5"
        ],
        "cis_topic": "Job schedulers: cron permissions",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "cron",
          "message": "2 cron program(s) checked; none are modifiable by other users"
        }
      ]
    },
    {
      "id": "HOST-CRON-002",
      "title": "World-Writable Directory in Cron's PATH",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the PATH that system cron jobs search for commands for world-writable or relative entries.",
      "rationale": "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).",
      "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",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1574.007, Hijack Execution Flow: Path Interception by PATH Environment Variable",
          "url": "https://attack.mitre.org/techniques/T1574/007/"
        },
        {
          "title": "crontab(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/crontab.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-426",
          "CWE-427"
        ],
        "nist": [
          "CM-6",
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "Root PATH integrity",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "cron PATH",
          "message": "6 PATH folders are absolute and not world-writable"
        }
      ]
    },
    {
      "id": "HOST-CTR-003",
      "title": "Privileged or Host-Namespace Containers",
      "domain": "host",
      "severity": "high",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for containers running privileged, sharing the host's PID, network or IPC namespace, or with dangerous added capabilities.",
      "rationale": "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.",
      "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)",
      "refs": [
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        },
        {
          "title": "OWASP Docker Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-653"
        ],
        "nist": [
          "AC-6",
          "SC-39"
        ],
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "containers",
          "message": "1 container(s) checked; none privileged or sharing host namespaces"
        }
      ]
    },
    {
      "id": "HOST-CTR-004",
      "title": "Containers Mount the Docker Socket or Host Root",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for containers that bind-mount the Docker socket, the host root filesystem or sensitive host paths.",
      "rationale": "A container with /var/run/docker.sock can control Docker and therefore the host. Mounting / or /etc gives direct write access to host configuration.",
      "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)",
      "refs": [
        {
          "title": "Docker documentation, Protect the Docker daemon socket",
          "url": "https://docs.docker.com/engine/security/protect-access/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-732"
        ],
        "nist": [
          "AC-6",
          "SC-39"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "containers",
          "message": "No containers mount the Docker socket or host root"
        }
      ]
    },
    {
      "id": "HOST-CTR-008",
      "title": "Docker Socket Accessible to Non-Administrators",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the Docker socket's permissions and who belongs to the docker group.",
      "rationale": "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.",
      "fix": "chmod 660 /var/run/docker.sock \u0026\u0026 chown root:docker /var/run/docker.sock  (the daemon recreates it with these defaults). Remove users from the docker group (gpasswd -d \u003cuser\u003e 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",
      "refs": [
        {
          "title": "Docker documentation, Protect the Docker daemon socket",
          "url": "https://docs.docker.com/engine/security/protect-access/"
        },
        {
          "title": "OWASP Docker Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-250"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "CIS Docker Benchmark 1.2.2 and 3.15 (only trusted users control the daemon; socket ownership and permissions)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/run/docker.sock",
          "message": "Docker socket is root-owned, mode 0660, and the docker group is empty"
        }
      ]
    },
    {
      "id": "HOST-CTR-011",
      "title": "Seccomp or AppArmor Disabled for Containers",
      "domain": "host",
      "severity": "high",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for the daemon or individual containers turning off the default seccomp profile, AppArmor or SELinux labelling.",
      "rationale": "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.",
      "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)",
      "refs": [
        {
          "title": "Docker documentation, Seccomp security profiles",
          "url": "https://docs.docker.com/engine/security/seccomp/"
        },
        {
          "title": "Docker documentation, AppArmor security profiles",
          "url": "https://docs.docker.com/engine/security/apparmor/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-653",
          "CWE-250"
        ],
        "nist": [
          "AC-6",
          "SC-39",
          "CM-7"
        ],
        "cis_topic": "CIS Docker Benchmark 5.1 and 5.22 (AppArmor profile; seccomp not unconfined)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "containers",
          "message": "1 container(s) checked; all keep the default confinement"
        }
      ]
    },
    {
      "id": "HOST-CTR-014",
      "title": "containerd Remote API or Podman Unconfined Defaults",
      "domain": "host",
      "severity": "high",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the containerd and Podman configuration files for a TCP API without TLS and for unconfined seccomp or disabled labelling defaults.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "containerd documentation, Operations (configuration)",
          "url": "https://github.com/containerd/containerd/blob/main/docs/ops.md"
        },
        {
          "title": "containers.conf(5), Podman configuration",
          "url": "https://github.com/containers/common/blob/main/docs/containers.conf.5.md"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "nist": [
          "AC-3",
          "SC-8",
          "CM-6"
        ],
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/containerd/config.toml",
          "message": "containerd has no unauthenticated TCP API"
        },
        {
          "status": "pass",
          "target": "/etc/containers/containers.conf",
          "message": "Podman defaults keep seccomp, AppArmor and labelling on"
        }
      ]
    },
    {
      "id": "HOST-CVE-001",
      "title": "Installed OS Packages With Known Vulnerabilities",
      "domain": "host",
      "severity": "high",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks installed packages against an offline vulnerability snapshot.",
      "rationale": "Known vulnerable package versions can expose a server to documented attacks.",
      "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.",
      "refs": [
        {
          "title": "OSV vulnerability database",
          "url": "https://osv.dev/"
        },
        {
          "title": "NVD",
          "url": "https://nvd.nist.gov/"
        },
        {
          "title": "CISA KEV",
          "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/cve.go",
      "academy": [
        {
          "id": "P10",
          "title": "Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities",
          "url": "https://openvapt.com/academy/p10.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "OS package inventory",
          "message": "No matching advisories in the embedded snapshot",
          "evidence": [
            "Snapshot date 2026-10-16. An outdated snapshot misses newer vulnerabilities."
          ]
        }
      ]
    },
    {
      "id": "HOST-DKR-001",
      "title": "Users With Root-Equivalent Docker Access",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether non-root users can control Docker, which is equivalent to root access.",
      "rationale": "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.",
      "fix": "Remove users who do not need it from the docker group (gpasswd -d \u003cuser\u003e 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",
      "refs": [
        {
          "title": "Docker documentation, Docker daemon attack surface",
          "url": "https://docs.docker.com/engine/security/#docker-daemon-attack-surface"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-266"
        ],
        "nist": [
          "AC-6"
        ],
        "cis_v8": [
          "5.4"
        ],
        "csf_2": [
          "PR.AA-05"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "docker",
          "message": "Docker access is restricted to root"
        }
      ]
    },
    {
      "id": "HOST-FS-001",
      "title": "Sensitive Account Files Have Unsafe Permissions",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks that /etc/shadow and /etc/gshadow are not readable by others, and /etc/passwd and /etc/group are not writable by others.",
      "rationale": "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.",
      "fix": "chown root:root /etc/passwd /etc/group \u0026\u0026 chmod 644 /etc/passwd /etc/group ; chown root:shadow /etc/shadow /etc/gshadow \u0026\u0026 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",
      "refs": [
        {
          "title": "shadow(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/shadow.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_topic": "File permissions: account database files",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/shadow",
          "message": "/etc/shadow permissions are safe",
          "evidence": [
            "mode 0640"
          ]
        },
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "/etc/passwd permissions are safe",
          "evidence": [
            "mode 0644"
          ]
        },
        {
          "status": "pass",
          "target": "/etc/group",
          "message": "/etc/group permissions are safe",
          "evidence": [
            "mode 0644"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-002",
      "title": "Temporary Directory Missing the Sticky Bit",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H",
      "status": "pass",
      "summary": "Checks that world-writable temp directories have the sticky bit set.",
      "rationale": "Without the sticky bit any user can delete or replace other users' files in /tmp, enabling symlink attacks against root-run scripts.",
      "fix": "chmod 1777 /tmp /var/tmp",
      "verify": "stat -c '%a %n' /tmp /var/tmp  should print 1777 for both.",
      "refs": [
        {
          "title": "chmod(1), Linux man-pages (restricted deletion flag)",
          "url": "https://man7.org/linux/man-pages/man1/chmod.1.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-377",
          "CWE-732"
        ],
        "nist": [
          "AC-3"
        ],
        "cis_topic": "Filesystem: sticky bit on world-writable directories",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/tmp",
          "message": "/tmp is protected",
          "evidence": [
            "mode 1777"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-003",
      "title": "World-Writable Files in System Configuration",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for files under /etc and in cron directories that any user can modify.",
      "rationale": "A world-writable config or cron file lets any local user change how a root-owned service behaves, often leading straight to root.",
      "fix": "For each listed file or directory: chmod o-w \u003cpath\u003e. 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.",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "CM-6"
        ],
        "cis_topic": "File permissions: no world-writable files",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc",
          "message": "No world-writable files among 23 checked"
        }
      ]
    },
    {
      "id": "HOST-FS-004",
      "title": "Unexpected SUID or SGID Programs",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for set-user-ID or set-group-ID programs in places where packages do not normally install them.",
      "rationale": "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.",
      "fix": "Confirm whether each program is needed. Remove the bit with chmod u-s,g-s \u003cfile\u003e, 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.",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1548.001, Setuid and Setgid",
          "url": "https://attack.mitre.org/techniques/T1548/001/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "CM-7",
          "AC-6"
        ],
        "cis_topic": "Filesystem: audit SUID and SGID executables",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "No SUID/SGID programs in unusual locations"
        }
      ]
    },
    {
      "id": "HOST-FS-005",
      "title": "Cron Files Writable by Non-Root Users",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that system cron files and directories are owned by root and not writable by others.",
      "rationale": "Cron runs these jobs as root. Anyone who can edit them can run any command as root at the next scheduled minute.",
      "fix": "chown root:root \u003cpath\u003e \u0026\u0026 chmod go-w \u003cpath\u003e  for each listed path (600 for /etc/crontab is a common hardening choice).",
      "verify": "stat -c '%a %U %n' /etc/crontab /etc/cron.*",
      "refs": [
        {
          "title": "crontab(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/crontab.5.html"
        },
        {
          "title": "MITRE ATT\u0026CK T1053.003, Cron",
          "url": "https://attack.mitre.org/techniques/T1053/003/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "CM-6"
        ],
        "cis_topic": "Job schedulers: cron permissions",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "cron",
          "message": "Cron files are root-owned and not group/other writable"
        }
      ]
    },
    {
      "id": "HOST-FS-007",
      "title": "Private TLS Keys Readable by Others",
      "domain": "host",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks that TLS private keys under the standard key directories are not readable by other users.",
      "rationale": "A stolen private key lets an attacker impersonate your websites and, with older key exchange, decrypt recorded traffic.",
      "fix": "chown root:root \u003ckey\u003e \u0026\u0026 chmod 600 \u003ckey\u003e  (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",
      "refs": [
        {
          "title": "OWASP Key Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/ssl/private/*",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-FS-022",
      "title": "Account Database Files or Their Backups Not Owned and Locked by Root",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks the owner and permissions of /etc/passwd, /etc/group, /etc/shadow, /etc/gshadow and their dash-suffixed backup copies.",
      "rationale": "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.",
      "fix": "chown root:root /etc/passwd /etc/passwd- /etc/group /etc/group- \u0026\u0026 chmod 644 /etc/passwd /etc/group \u0026\u0026 chmod 600 /etc/passwd- /etc/group- ; chown root:shadow /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow- \u0026\u0026 chmod 640 /etc/shadow /etc/gshadow \u0026\u0026 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*",
      "refs": [
        {
          "title": "shadow(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/shadow.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-522"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "IA-5"
        ],
        "cis_topic": "Account database file ownership and permissions (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "5 account file(s) are root-owned with the expected modes"
        }
      ]
    },
    {
      "id": "HOST-K8S-002",
      "title": "Kubernetes Data Directory Accessible to Others",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks that the etcd data directory is accessible only by its owner.",
      "rationale": "etcd stores every Kubernetes object, including Secrets, usually unencrypted at rest. Read access to its data directory exposes all cluster credentials.",
      "fix": "chmod 700 /var/lib/etcd \u0026\u0026 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",
      "refs": [
        {
          "title": "Kubernetes documentation, Operating etcd clusters for Kubernetes",
          "url": "https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/"
        },
        {
          "title": "Kubernetes documentation, Securing a cluster",
          "url": "https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/lib/etcd",
          "message": "etcd data directory is owner-only",
          "evidence": [
            "mode 0700"
          ]
        }
      ]
    },
    {
      "id": "HOST-K8S-003",
      "title": "Kubernetes Credential Files Readable by Others",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that kubeconfig files and Kubernetes PKI private keys are not readable by other users.",
      "rationale": "admin.conf and the cluster CA key grant full control of the cluster; any local user who can read them becomes a cluster administrator.",
      "fix": "chmod 600 /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key /etc/kubernetes/pki/etcd/*.key \u0026\u0026 chown root:root on each.",
      "verify": "stat -c '%a %n' /etc/kubernetes/*.conf /etc/kubernetes/pki/*.key",
      "refs": [
        {
          "title": "Kubernetes documentation, Securing a cluster",
          "url": "https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/kubernetes/admin.conf",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-OS-001",
      "title": "Operating System Past End of Security Support",
      "domain": "host",
      "severity": "high",
      "category": "patch",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the Linux distribution release still receives free security updates.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Ubuntu release cycle",
          "url": "https://ubuntu.com/about/release-cycle"
        },
        {
          "title": "Debian releases and LTS",
          "url": "https://wiki.debian.org/LTS"
        },
        {
          "title": "Red Hat Enterprise Linux life cycle",
          "url": "https://access.redhat.com/support/policy/updates/errata"
        },
        {
          "title": "endoflife.date (community-maintained lifecycle data)",
          "url": "https://endoflife.date/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SA-22",
          "SI-2"
        ],
        "cis_topic": "Supported operating system versions",
        "cis_v8": [
          "2.2"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/os_patch.go",
      "academy": [
        {
          "id": "F3",
          "title": "Vulnerabilities, CVE, CWE and CVSS",
          "url": "https://openvapt.com/academy/f3.html"
        },
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "Ubuntu 24.04.1 LTS",
          "message": "Supported until 2029-05-31"
        }
      ]
    },
    {
      "id": "HOST-PKG-001",
      "title": "Security Updates Waiting to Be Installed",
      "domain": "host",
      "severity": "high",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the package manager for published security updates that are not installed yet.",
      "rationale": "Most real-world compromises use vulnerabilities for which a fix was already available. Pending security updates are known, public weaknesses.",
      "fix": "Debian/Ubuntu: apt-get update \u0026\u0026 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.",
      "refs": [
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        },
        {
          "title": "Debian, UnattendedUpgrades",
          "url": "https://wiki.debian.org/UnattendedUpgrades"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_topic": "Patch management: install security updates",
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/os_patch.go",
      "academy": [
        {
          "id": "F3",
          "title": "Vulnerabilities, CVE, CWE and CVSS",
          "url": "https://openvapt.com/academy/f3.html"
        },
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "apt",
          "message": "No pending security updates (based on the last apt-get update)"
        }
      ]
    },
    {
      "id": "HOST-PKG-002",
      "title": "Repository Signature Checks Disabled",
      "domain": "host",
      "severity": "high",
      "category": "patch",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that the package manager verifies repository signatures, so a tampered mirror or intercepted download cannot install code as root.",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "apt-secure(8), Debian manual pages",
          "url": "https://manpages.debian.org/trixie/apt/apt-secure.8.en.html"
        },
        {
          "title": "dnf.conf(5), Ubuntu noble manual pages (gpgcheck)",
          "url": "https://manpages.ubuntu.com/manpages/noble/en/man5/dnf.conf.5.html"
        },
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-494",
          "CWE-347"
        ],
        "owasp": [
          "A08:2021"
        ],
        "nist": [
          "SI-7",
          "CM-14",
          "SR-3",
          "SR-11"
        ],
        "cis_topic": "Package manager repositories and GPG keys are configured",
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/pkg_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "package repositories",
          "message": "1 repository file(s) keep signature verification on"
        }
      ]
    },
    {
      "id": "HOST-SEC-001",
      "title": "Secrets in World-Readable Files",
      "domain": "host",
      "severity": "high",
      "category": "exposure",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Looks inside world-readable configuration files under /etc and the web roots for private keys, cloud access keys and password assignments.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, IA-5 Authenticator Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-312",
          "CWE-732",
          "CWE-798"
        ],
        "owasp": [
          "A02:2021",
          "A05:2021"
        ],
        "nist": [
          "IA-5",
          "SC-28",
          "AC-3"
        ],
        "cis_topic": "Protect credentials and keys at rest",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "No credential patterns in 5 world-readable configuration files"
        }
      ]
    },
    {
      "id": "HOST-SSH-001",
      "title": "SSH Allows Direct Root Login",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the SSH daemon accepts logins as root with a password.",
      "rationale": "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.",
      "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 \u0026\u0026 systemctl reload sshd",
      "verify": "sshd -T | grep -i permitrootlogin  should print 'permitrootlogin no'.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-6(2)",
          "IA-2"
        ],
        "cis_topic": "SSH server configuration: root login",
        "cis_v8": [
          "5.4"
        ],
        "csf_2": [
          "PR.AA-05"
        ]
      },
      "source": "internal/checks/host/ssh_root_login.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        },
        {
          "id": "P23",
          "title": "Test Design for Security Checks",
          "url": "https://openvapt.com/academy/p23.html"
        },
        {
          "id": "A2",
          "title": "Remediation and Verification",
          "url": "https://openvapt.com/academy/a2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "Direct root password login is disabled",
          "evidence": [
            "permitrootlogin no"
          ]
        }
      ]
    },
    {
      "id": "HOST-SSH-009",
      "title": "SSH Host Private Keys Readable by Others",
      "domain": "host",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks that SSH host private keys are readable only by root.",
      "rationale": "Anyone who can read a host private key can impersonate the server and intercept SSH sessions from users who trust it.",
      "fix": "chown root:root /etc/ssh/ssh_host_*_key \u0026\u0026 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",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/ssh/ssh_host_*_key",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-SSH-010",
      "title": "SSH Server Configuration Writable by Non-Root Users",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that the SSH daemon configuration cannot be modified by group or other users.",
      "rationale": "Whoever can edit sshd_config can enable root login, add their own keys file location or weaken authentication.",
      "fix": "chown root:root /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf \u0026\u0026 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/*",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/ssh/sshd_config",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-SYSD-003",
      "title": "Service Unit Files Writable by Non-Root Users",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether any systemd service unit or drop-in file can be changed by a user other than root.",
      "rationale": "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.",
      "fix": "chown root:root \u003cfile\u003e \u0026\u0026 chmod 644 \u003cfile\u003e 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).",
      "refs": [
        {
          "title": "systemd.unit(5), unit configuration",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html"
        },
        {
          "title": "MITRE ATT\u0026CK T1543.002, Create or Modify System Process: Systemd Service",
          "url": "https://attack.mitre.org/techniques/T1543/002/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "CM-5"
        ],
        "cis_topic": "Protect service definitions",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/systemd.go",
      "academy": [
        {
          "id": "P11",
          "title": "Service Hardening With systemd",
          "url": "https://openvapt.com/academy/p11.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "All 3 service units are owned by root and not writable by others"
        }
      ]
    },
    {
      "id": "HOST-SYSD-004",
      "title": "Service Programs in Writable Locations",
      "domain": "host",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether a service starts a program from a temporary folder, or a program file that other users can replace.",
      "rationale": "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.",
      "fix": "Install the program in a root-owned location (/usr/local/bin or /opt/\u003capp\u003e), 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 \u003cunit\u003e | awk '{print $2}' | sed 's/path=//;s/;//')",
      "refs": [
        {
          "title": "systemd.exec(5), execution environment configuration",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html"
        },
        {
          "title": "MITRE ATT\u0026CK T1574.010, Hijack Execution Flow: Services File Permissions Weakness",
          "url": "https://attack.mitre.org/techniques/T1574/010/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-427"
        ],
        "nist": [
          "AC-3",
          "CM-5"
        ],
        "cis_topic": "Protect service executables",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/systemd.go",
      "academy": [
        {
          "id": "P11",
          "title": "Service Hardening With systemd",
          "url": "https://openvapt.com/academy/p11.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "Service programs are in protected locations"
        }
      ]
    },
    {
      "id": "HOST-USR-001",
      "title": "Extra Accounts With Root Privileges (UID 0)",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks /etc/passwd for accounts other than root that have user id 0.",
      "rationale": "Any account with UID 0 is root, whatever its name. Extra UID 0 accounts are a classic backdoor and bypass sudo logging.",
      "fix": "Investigate who created each account. Remove it (userdel \u003cname\u003e) or give it a normal UID (usermod -u \u003cnew-uid\u003e \u003cname\u003e). Grant admin rights through sudo instead.",
      "verify": "awk -F: '$3==0' /etc/passwd  should print only the root line.",
      "refs": [
        {
          "title": "passwd(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/passwd.5.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-266"
        ],
        "nist": [
          "AC-2",
          "AC-6"
        ],
        "cis_topic": "User accounts: root is the only UID 0 account",
        "cis_v8": [
          "5.4"
        ],
        "csf_2": [
          "PR.AA-05"
        ]
      },
      "source": "internal/checks/host/accounts.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "Only root has UID 0"
        }
      ]
    },
    {
      "id": "HOST-USR-002",
      "title": "Accounts Without a Password",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks /etc/shadow for accounts whose password field is empty.",
      "rationale": "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.",
      "fix": "For each listed user either set a password (passwd \u003cuser\u003e) or lock the account (passwd -l \u003cuser\u003e).",
      "verify": "awk -F: '$2==\"\"' /etc/shadow  should print nothing.",
      "refs": [
        {
          "title": "shadow(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/shadow.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-258",
          "CWE-521"
        ],
        "nist": [
          "IA-5"
        ],
        "cis_topic": "User accounts: password fields are not empty",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/accounts.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/shadow",
          "message": "Every account has a password or is locked"
        }
      ]
    },
    {
      "id": "HOST-USR-003",
      "title": "Sudo Rules Without Password (NOPASSWD)",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks sudoers files for rules that grant root without asking for a password.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "sudoers(5) manual",
          "url": "https://www.sudo.ws/docs/man/sudoers.man/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306"
        ],
        "nist": [
          "AC-6",
          "IA-11"
        ],
        "cis_topic": "Sudo: re-authentication for privilege escalation",
        "cis_v8": [
          "5.4"
        ],
        "csf_2": [
          "PR.AA-05"
        ]
      },
      "source": "internal/checks/host/accounts.go",
      "academy": [
        {
          "id": "F1",
          "title": "Security Basics",
          "url": "https://openvapt.com/academy/f1.html"
        },
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/sudoers",
          "message": "No NOPASSWD sudo rules"
        }
      ]
    },
    {
      "id": "HOST-USR-007",
      "title": "Insecure Trust Files in Home Directories",
      "domain": "host",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Looks for .rhosts, .shosts and .netrc files in user home directories.",
      "rationale": ".rhosts and .shosts grant password-less trust to other hosts. .netrc files usually hold plain-text passwords for FTP or other services.",
      "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.",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1552.001, Unsecured Credentials: Credentials In Files",
          "url": "https://attack.mitre.org/techniques/T1552/001/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-256",
          "CWE-290"
        ],
        "nist": [
          "IA-5",
          "CM-6"
        ],
        "cis_topic": "No .rhosts, .netrc files",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "home directories",
          "message": "No .rhosts, .shosts or .netrc files"
        }
      ]
    },
    {
      "id": "NET-AI-001",
      "title": "AI Model Server or Dashboard Reachable From the Network",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether a self-hosted AI model server, AI web interface, AI cluster dashboard or vector database listens on a network-reachable address.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Ollama FAQ, How do I configure Ollama server?",
          "url": "https://github.com/ollama/ollama/blob/main/docs/faq.md"
        },
        {
          "title": "Ray documentation, Security",
          "url": "https://docs.ray.io/en/latest/ray-security/index.html"
        },
        {
          "title": "OWASP Top 10 for Large Language Model Applications",
          "url": "https://genai.owasp.org/llm-top-10/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021",
          "A07:2021"
        ],
        "nist": [
          "SC-7",
          "CM-7",
          "AC-3"
        ],
        "cis_topic": "Expose applications only through a hardened front end",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/ai_services.go",
      "academy": [
        {
          "id": "P24",
          "title": "Securing Self-Hosted AI Services",
          "url": "https://openvapt.com/academy/p24.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No AI model server, dashboard or vector database port is network-reachable"
        }
      ]
    },
    {
      "id": "NET-AI-002",
      "title": "Jupyter Configuration Weakens Authentication",
      "domain": "network",
      "severity": "high",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether a Jupyter configuration blanks the login token without setting a password, or turns off cross-site request protection.",
      "rationale": "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.",
      "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' \u003cconfig file\u003e  shows no empty token and no disabled XSRF check, and the server log prints a URL with a token at start-up.",
      "refs": [
        {
          "title": "Jupyter Server documentation, Security in the Jupyter Server",
          "url": "https://jupyter-server.readthedocs.io/en/latest/operators/security.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-352"
        ],
        "owasp": [
          "A07:2021",
          "A05:2021"
        ],
        "nist": [
          "IA-2",
          "AC-3"
        ],
        "cis_topic": "Require authentication for administrative and code execution interfaces",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/network/ai_services.go",
      "academy": [
        {
          "id": "P24",
          "title": "Securing Self-Hosted AI Services",
          "url": "https://openvapt.com/academy/p24.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/jupyter/jupyter_server_config.py",
          "message": "Jupyter keeps token or password authentication and XSRF protection"
        }
      ]
    },
    {
      "id": "NET-APP-001",
      "title": "Debugger or Application Control Ports Exposed",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether the Node.js inspector, Tomcat AJP or shutdown port, PHP-FPM FastCGI or Jupyter listen on network-reachable addresses.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Node.js, Inspector security implications",
          "url": "https://nodejs.org/en/learn/getting-started/debugging#security-implications"
        },
        {
          "title": "Apache Tomcat 9, AJP Connector",
          "url": "https://tomcat.apache.org/tomcat-9.0-doc/config/ajp.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-7",
          "CM-7"
        ],
        "cis_topic": "Expose applications only through a hardened front end",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No debugger or application control port is network-reachable"
        }
      ]
    },
    {
      "id": "NET-DB-001",
      "title": "Database or Cache Service Exposed to the Network",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether databases, caches, search engines or the Docker API listen on a network-reachable address.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP Database Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Database_Security_Cheat_Sheet.html"
        },
        {
          "title": "Redis security",
          "url": "https://redis.io/docs/latest/operate/oss_and_stack/management/security/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1327",
          "CWE-306"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-7",
          "CM-7"
        ],
        "cis_topic": "Network exposure of data services",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/listeners.go",
      "academy": [
        {
          "id": "F1",
          "title": "Security Basics",
          "url": "https://openvapt.com/academy/f1.html"
        },
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No database or cache ports are network-reachable"
        }
      ]
    },
    {
      "id": "NET-DB-005",
      "title": "MySQL or MariaDB Unsafe Network Options",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "MySQL or MariaDB enables local file loading, skips grants, or listens broadly with networking enabled.",
      "rationale": "These options can bypass authorization, expand file access, or expose the database to untrusted networks.",
      "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.",
      "refs": [
        {
          "title": "MySQL Server Security Guidelines",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/security-guidelines.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "Required security settings are present",
          "evidence": [
            "/etc/mysql/mysql.conf.d/mysqld.cnf: local_infile = OFF",
            "/etc/mysql/mysql.conf.d/mysqld.cnf: skip-networking = ON",
            "/etc/mysql/mysql.conf.d/mysqld.cnf: bind-address = 127.0.0.1"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-006",
      "title": "Memcached UDP or Broad Listen Address",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Memcached enables UDP or listens on a non-loopback address.",
      "rationale": "Unauthenticated Memcached exposure can disclose cached data and UDP mode has been abused for amplification attacks.",
      "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.",
      "refs": [
        {
          "title": "memcached Configuration",
          "url": "https://docs.memcached.org/serverguide/configuring/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-406",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-5",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/memcached.conf",
          "message": "Required security settings are present",
          "evidence": [
            "/etc/memcached.conf: -l 127.0.0.1",
            "/etc/memcached.conf: -U 0"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-008",
      "title": "Unsafe NFS Export Options",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "NFS exports are world-accessible or use no_root_squash or insecure.",
      "rationale": "These export options can grant untrusted clients filesystem access or preserve remote root privileges.",
      "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.",
      "refs": [
        {
          "title": "Linux exports(5)",
          "url": "https://man7.org/linux/man-pages/man5/exports.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-306"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/exports",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-DB-009",
      "title": "Unsafe Samba Guest or SMB1 Configuration",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Samba permits guest access, maps unknown users to guest, or allows SMB1.",
      "rationale": "Guest shares weaken accountability, while SMB1 lacks modern protocol protections and has a long history of severe flaws.",
      "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.",
      "refs": [
        {
          "title": "Samba smb.conf Manual",
          "url": "https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-306"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/samba/smb.conf",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-DB-010",
      "title": "BIND Open Recursion",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H",
      "status": "pass",
      "summary": "BIND enables recursion without restricting recursive clients.",
      "rationale": "An open recursive resolver can be abused for reflection attacks and permits outsiders to consume resolver resources.",
      "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.",
      "refs": [
        {
          "title": "BIND 9 Administrator Reference Manual - Access Control",
          "url": "https://bind9.readthedocs.io/en/latest/reference.html#access-control"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-406"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-5",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/bind/named.conf.options",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-DB-011",
      "title": "BIND Zone Transfers Allowed to Any",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "BIND permits zone transfers to any client.",
      "rationale": "Unrestricted transfers disclose the complete DNS namespace and provide attackers with a detailed target inventory.",
      "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.",
      "refs": [
        {
          "title": "BIND 9 Administrator Reference Manual - allow-transfer",
          "url": "https://bind9.readthedocs.io/en/latest/reference.html#namedconf-statement-allow-transfer"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/bind/named.conf.options",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-DB-012",
      "title": "Postfix Open Relay Controls",
      "domain": "network",
      "severity": "high",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L",
      "status": "pass",
      "summary": "Postfix relay restrictions are missing or mynetworks trusts overly broad ranges.",
      "rationale": "An open relay enables spam and phishing, damages domain reputation, and can expose the host to blocklisting.",
      "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.",
      "refs": [
        {
          "title": "Postfix SMTP Relay and Access Control",
          "url": "https://www.postfix.org/SMTPD_ACCESS_README.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-441"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-3",
          "CM-6",
          "SC-7"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postfix/main.cf",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-FW-002",
      "title": "IPv6 Listeners Without IPv6 Firewall",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Network-reachable IPv6 listeners exist without a default-deny IPv6 firewall policy.",
      "rationale": "An IPv4-only firewall does not protect IPv6 services, creating an alternate path that bypasses intended filtering.",
      "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.",
      "refs": [
        {
          "title": "NIST SP 800-119, Guidelines for the Secure Deployment of IPv6",
          "url": "https://csrc.nist.gov/pubs/sp/800/119/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-923"
        ],
        "nist": [
          "SC-7"
        ],
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/network_controls.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "IPv6 firewall",
          "message": "ip6tables has a default-deny input policy"
        }
      ]
    },
    {
      "id": "NET-FW-003",
      "title": "Firewall Allows Sensitive Services From Anywhere",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the host firewall explicitly opens a listening database, administrative or application server port to every address on the internet.",
      "rationale": "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.",
      "fix": "Delete the rule or restrict it to the addresses that need it, for example: ufw delete allow 3306 \u0026\u0026 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.",
      "refs": [
        {
          "title": "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy",
          "url": "https://csrc.nist.gov/pubs/sp/800/41/r1/final"
        },
        {
          "title": "ufw(8) manual (Ubuntu)",
          "url": "https://manpages.ubuntu.com/manpages/noble/man8/ufw.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-923",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-7",
          "AC-4",
          "CM-7"
        ],
        "cis_topic": "Restrict firewall rules to required sources",
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P12",
          "title": "Firewall Policy Versus What Is Listening",
          "url": "https://openvapt.com/academy/p12.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "firewall",
          "message": "No sensitive listening service is opened to every address"
        }
      ]
    },
    {
      "id": "NET-SVC-001",
      "title": "Cleartext Legacy Services Listening",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks for Telnet, FTP, rsh/rlogin, TFTP, plain POP3/IMAP/LDAP, SNMP v1/v2c and rsync daemons on network-reachable addresses.",
      "rationale": "These protocols send passwords and data unencrypted, so anyone on the path can read or alter them. Most also have weak or no authentication.",
      "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 \u003cunit\u003e.",
      "verify": "ss -tulpn  should not show ports 21, 23, 69, 110, 143, 161, 389, 512-514 or 873 on public addresses.",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "nist": [
          "SC-8",
          "CM-7"
        ],
        "cis_topic": "Remove insecure legacy services",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/listeners.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No cleartext legacy services are network-reachable"
        }
      ]
    },
    {
      "id": "NET-SVC-003",
      "title": "Network Time Service Amplification Risk",
      "domain": "network",
      "severity": "high",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "status": "pass",
      "summary": "NTP or chrony serves broad clients or permits legacy monitoring commands that can amplify traffic.",
      "rationale": "Internet-reachable time services with broad client access or legacy mode 6 or 7 controls can be abused for reflection and amplification.",
      "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.",
      "refs": [
        {
          "title": "CISA Alert TA14-013A, NTP Amplification Attacks Using CVE-2013-5211",
          "url": "https://www.cisa.gov/news-events/alerts/2014/01/13/ntp-amplification-attacks-using-cve-2013-5211"
        },
        {
          "title": "chrony Documentation - Access Control",
          "url": "https://chrony-project.org/doc/4.6/chrony.conf.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-406"
        ],
        "nist": [
          "SC-5",
          "SC-7"
        ],
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/network_controls.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/ntp.conf",
          "message": "Time service query access is restricted"
        }
      ]
    },
    {
      "id": "WEB-APA-005",
      "title": "Apache Allows TLS 1.0 or 1.1",
      "domain": "web",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "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).",
      "rationale": "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.",
      "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 \u003csite\u003e -tls1_1  should fail to connect.",
      "refs": [
        {
          "title": "Apache HTTP Server, SSLProtocol directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslprotocol"
        },
        {
          "title": "Mozilla, Server Side TLS guidelines",
          "url": "https://wiki.mozilla.org/Security/Server_Side_TLS"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        },
        {
          "title": "OpenSSL, config(5): system default TLS settings (MinProtocol)",
          "url": "https://docs.openssl.org/3.0/man5/config/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326",
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.3"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS protocol versions",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "SSLProtocol allows only TLS 1.2 or newer",
          "evidence": [
            "/etc/apache2/mods-enabled/ssl.conf: SSLProtocol -all +TLSv1.2 +TLSv1.3"
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-006",
      "title": "Apache Cipher Suite Includes Weak Suites",
      "domain": "web",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "SSLCipherSuite includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or SSLHonorCipherOrder forces a legacy order.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Mozilla SSL Configuration Generator",
          "url": "https://ssl-config.mozilla.org/"
        },
        {
          "title": "Apache HTTP Server, SSLCipherSuite directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslciphersuite"
        },
        {
          "title": "RFC 7525, Recommendations for Secure Use of TLS and DTLS",
          "url": "https://www.rfc-editor.org/rfc/rfc7525.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.2"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS cipher suites",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "No weak cipher token is listed"
        }
      ]
    },
    {
      "id": "WEB-APP-001",
      "title": "Laravel Debug Mode On or Application Key Missing",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks Laravel .env files for APP_DEBUG=true or an empty APP_KEY.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Laravel documentation, Deployment",
          "url": "https://laravel.com/docs/12.x/deployment"
        },
        {
          "title": "Laravel documentation, Configuration (debug mode, environment files)",
          "url": "https://laravel.com/docs/12.x/configuration"
        },
        {
          "title": "OWASP ASVS 4.0.3, V14 Configuration",
          "url": "https://github.com/OWASP/ASVS/blob/v4.0.3/4.0/en/0x22-V14-Config.md"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-209",
          "CWE-321"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2",
          "2.10.4"
        ],
        "wstg": [
          "WSTG-CONF-02",
          "WSTG-ERRH-01"
        ],
        "nist": [
          "CM-6",
          "SI-11",
          "IA-5"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-laravel/.env",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-APP-003",
      "title": "Symfony Running in the Development Environment",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks Symfony .env files for APP_ENV=dev or APP_DEBUG=1.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Symfony documentation, Configuring Symfony (environments)",
          "url": "https://symfony.com/doc/current/configuration.html"
        },
        {
          "title": "Symfony documentation, How to Deploy a Symfony Application",
          "url": "https://symfony.com/doc/current/deployment.html"
        },
        {
          "title": "OWASP ASVS 4.0.3, V14 Configuration",
          "url": "https://github.com/OWASP/ASVS/blob/v4.0.3/4.0/en/0x22-V14-Config.md"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-215"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-CONF-02",
          "WSTG-ERRH-01"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-crm/.env",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-APP-004",
      "title": "Rails Master Key or Secrets File Exposed",
      "domain": "web",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks Rails config/master.key, credentials key files and secrets.yml for world-read permissions or a location inside a web root.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Ruby on Rails Guides, Securing Rails Applications",
          "url": "https://guides.rubyonrails.org/security.html"
        },
        {
          "title": "Ruby on Rails Guides, Custom Credentials",
          "url": "https://guides.rubyonrails.org/security.html#custom-credentials"
        },
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-312",
          "CWE-321"
        ],
        "owasp": [
          "A02:2021",
          "A05:2021"
        ],
        "asvs": [
          "2.10.4",
          "14.1.3"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "SC-28",
          "IA-5"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-rails/config/master.key",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-APP-006",
      "title": "Spring Boot Actuator Endpoints Fully Exposed",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks Spring Boot application properties for management.endpoints.web.exposure.include=* and probes the local site for an open /actuator/env.",
      "rationale": "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.",
      "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://\u003csite\u003e/actuator/env  prints 401, 403 or 404.",
      "refs": [
        {
          "title": "Spring Boot documentation, Actuator endpoints",
          "url": "https://docs.spring.io/spring-boot/reference/actuator/endpoints.html"
        },
        {
          "title": "Spring Boot documentation, Securing HTTP endpoints",
          "url": "https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints.security"
        },
        {
          "title": "OWASP WSTG-CONF-02, Test Application Platform Configuration",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/02-Test_Application_Platform_Configuration/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-497"
        ],
        "owasp": [
          "A05:2021",
          "A01:2021"
        ],
        "asvs": [
          "14.3.2",
          "4.3.2"
        ],
        "wstg": [
          "WSTG-CONF-02",
          "WSTG-CONF-05"
        ],
        "nist": [
          "CM-7",
          "AC-3",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/opt/shop-api/application.properties",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-004",
      "title": "CMS Configuration Readable by All Users",
      "domain": "web",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php can be read by every local user.",
      "rationale": "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.",
      "fix": "chmod 640 \u003cfile\u003e 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' \u003cfile\u003e  shows 640, 600 or 400.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        },
        {
          "title": "Drupal.org, Securing file permissions and ownership",
          "url": "https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-260"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/wp-config.php",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/configuration.php",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/sites/default/settings.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-005",
      "title": "CMS Configuration Writable by Other Users",
      "domain": "web",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether wp-config.php, Joomla configuration.php or Drupal settings.php is group- or world-writable.",
      "rationale": "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.",
      "fix": "chmod 640 (or 440) \u003cfile\u003e. Drupal also expects sites/default to be 555 after installation. Review the file for code you did not add.",
      "verify": "stat -c '%a %n' \u003cfile\u003e  shows no write bit for group or others.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        },
        {
          "title": "Drupal.org, Securing file permissions and ownership",
          "url": "https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "CM-5"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/wp-config.php",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/configuration.php",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/sites/default/settings.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-012",
      "title": "Drupal Core Past End of Life",
      "domain": "web",
      "severity": "high",
      "category": "patch",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Reads the installed Drupal core version from disk and compares its major release with the Drupal security coverage dates.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Drupal.org, Drupal core release cycle and end of life",
          "url": "https://www.drupal.org/about/core/policies/core-release-cycles/schedule"
        },
        {
          "title": "NVD, CVE-2018-7600",
          "url": "https://nvd.nist.gov/vuln/detail/cve-2018-7600"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2",
          "SA-22"
        ],
        "cis_v8": [
          "2.2"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "Drupal 11 is a supported major release"
        }
      ]
    },
    {
      "id": "WEB-CMS-014",
      "title": "Joomla Core Past End of Life",
      "domain": "web",
      "severity": "high",
      "category": "patch",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Reads the installed Joomla version from administrator/manifests/files/joomla.xml on disk and compares it with the Joomla support dates.",
      "rationale": "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.",
      "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 '\u003cversion\u003e' administrator/manifests/files/joomla.xml",
      "refs": [
        {
          "title": "Joomla Documentation, Joomla Release and Support Cycle",
          "url": "https://docs.joomla.org/Joomla!_CMS_versions"
        },
        {
          "title": "Joomla Documentation, Security Checklist",
          "url": "https://docs.joomla.org/Security_Checklist"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2",
          "SA-22"
        ],
        "cis_v8": [
          "2.2"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "Joomla 5.2 is a supported series"
        }
      ]
    },
    {
      "id": "WEB-CMS-019",
      "title": "Drupal update.php Open Without Authentication",
      "domain": "web",
      "severity": "high",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H",
      "status": "pass",
      "summary": "Checks Drupal settings.php for $settings['update_free_access'] = TRUE.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Drupal.org, Running update.php (update_free_access)",
          "url": "https://www.drupal.org/docs/updating-drupal/update-core-via-drush-or-manually"
        },
        {
          "title": "Drupal.org, Securing file permissions and ownership",
          "url": "https://www.drupal.org/docs/administering-a-drupal-site/security-in-drupal/securing-file-permissions-and-ownership"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "asvs": [
          "4.3.2"
        ],
        "wstg": [
          "WSTG-CONF-05"
        ],
        "nist": [
          "AC-3",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/sites/default/settings.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CVE-001",
      "title": "Vulnerable Web Application Libraries",
      "domain": "web",
      "severity": "high",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Matches local web library versions against offline OSV advisories; WordPress core is inventoried and reported Incomplete because OSV publishes no WordPress feed.",
      "rationale": "Unpatched web libraries can expose users and server data to known 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.",
      "refs": [
        {
          "title": "OSV",
          "url": "https://osv.dev/"
        },
        {
          "title": "OSV schema, defined ecosystems (WordPress)",
          "url": "https://ossf.github.io/osv-schema/"
        },
        {
          "title": "NVD",
          "url": "https://nvd.nist.gov/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_v8": [
          "7.4"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/cve.go",
      "academy": [
        {
          "id": "P10",
          "title": "Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities",
          "url": "https://openvapt.com/academy/p10.html"
        },
        {
          "id": "A7",
          "title": "Security Testing in Agile Teams and CI",
          "url": "https://openvapt.com/academy/a7.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "web inventory",
          "message": "No matching advisories in the embedded snapshot",
          "evidence": [
            "Snapshot date 2026-10-16. An outdated snapshot misses newer vulnerabilities."
          ]
        }
      ]
    },
    {
      "id": "WEB-DOC-001",
      "title": "Secrets or Backups Stored Inside a Web Root",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Looks on disk inside each web document root for environment files, database dumps, backups, archives and private keys.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files for Sensitive Information",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        },
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-530",
          "CWE-538",
          "CWE-312"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "SC-28",
          "CM-6"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/docroot.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "No sensitive files among 9 checked"
        }
      ]
    },
    {
      "id": "WEB-DOC-002",
      "title": "Web Root Files Writable by Other Users",
      "domain": "web",
      "severity": "high",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Looks for world-writable files inside web document roots.",
      "rationale": "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.",
      "fix": "chmod o-w \u003cfile\u003e for each listed file (find \u003cdocroot\u003e -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 \u003cdocroot\u003e -type f -perm -0002  should print nothing.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-09, Test File Permission",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/09-Test_File_Permission/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/docroot.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "No world-writable files"
        }
      ]
    },
    {
      "id": "WEB-EXP-001",
      "title": "Git Repository Exposed on the Website",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether the .git folder can be downloaded from each site.",
      "rationale": "An exposed .git folder lets anyone rebuild the full source code and history, often including passwords, API keys and database credentials committed by mistake.",
      "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://\u003csite\u003e/.git/HEAD  should print 403 or 404.",
      "refs": [
        {
          "title": "OWASP WSTG, Review Old Backup and Unreferenced Files (WSTG-CONF-04)",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-527",
          "CWE-538"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-004",
      "title": "Backup or Database Dump Files Downloadable",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks for common backup archives and SQL dumps left in the web root.",
      "rationale": "Backups contain the whole site and often the whole database, including customer data and password hashes.",
      "fix": "Move backups outside the web root (for example /home/\u003cuser\u003e/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://\u003csite\u003e/backup.zip  should print 404.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-530"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-007",
      "title": "Other Version Control Metadata Exposed",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks for exposed Subversion, Mercurial and Bazaar metadata folders.",
      "rationale": "Like .git, these folders let anyone reconstruct source code and history, including secrets that were committed by mistake.",
      "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://\u003csite\u003e/.svn/entries  should print 403 or 404.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files for Sensitive Information",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-527"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-008",
      "title": "Log Files Downloadable From the Website",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks for application and debug logs published under the web root.",
      "rationale": "Logs expose file paths, SQL queries, user data, session identifiers and sometimes credentials or API keys.",
      "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://\u003csite\u003e/wp-content/debug.log  should print 403 or 404.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files for Sensitive Information",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        },
        {
          "title": "OWASP Logging Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-532",
          "CWE-538"
        ],
        "owasp": [
          "A09:2021",
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-012",
      "title": "Framework Debug Pages Exposed",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks for development debug tools and verbose error pages left enabled in production.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP WSTG-ERRH-01, Testing for Improper Error Handling",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/08-Testing_for_Error_Handling/01-Testing_For_Improper_Error_Handling/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-209"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2",
          "7.4.1"
        ],
        "wstg": [
          "WSTG-ERRH-01",
          "WSTG-CONF-02"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-NGX-003",
      "title": "nginx Allows TLS 1.0 or 1.1",
      "domain": "web",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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 \u003csite\u003e -tls1_1  should fail to connect.",
      "refs": [
        {
          "title": "nginx documentation, ngx_http_ssl_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_ssl_module.html"
        },
        {
          "title": "Mozilla, Server Side TLS guidelines",
          "url": "https://wiki.mozilla.org/Security/Server_Side_TLS"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        },
        {
          "title": "RFC 8996, Deprecating TLS 1.0 and TLS 1.1",
          "url": "https://www.rfc-editor.org/rfc/rfc8996.html"
        },
        {
          "title": "OpenSSL, config(5): system default TLS settings (MinProtocol)",
          "url": "https://docs.openssl.org/3.0/man5/config/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326",
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.3"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS protocol versions",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "ssl_protocols allows only TLS 1.2 or newer",
          "evidence": [
            "/etc/nginx/nginx.conf: ssl_protocols TLSv1.2 TLSv1.3"
          ]
        }
      ]
    },
    {
      "id": "WEB-NGX-004",
      "title": "nginx Cipher List Includes Weak Suites",
      "domain": "web",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "ssl_ciphers includes NULL, export, DES or 3DES, RC4, MD5, anonymous or other legacy suites, or ssl_prefer_server_ciphers forces a legacy order.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Mozilla SSL Configuration Generator",
          "url": "https://ssl-config.mozilla.org/"
        },
        {
          "title": "Mozilla, Server Side TLS guidelines",
          "url": "https://wiki.mozilla.org/Security/Server_Side_TLS"
        },
        {
          "title": "RFC 7525, Recommendations for Secure Use of TLS and DTLS",
          "url": "https://www.rfc-editor.org/rfc/rfc7525.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.2"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS cipher suites",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "No weak cipher token is listed"
        }
      ]
    },
    {
      "id": "WEB-NODE-001",
      "title": "Node.js Version Past End of Life",
      "domain": "web",
      "severity": "high",
      "category": "patch",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the installed Node.js runtime is a release line that no longer receives security fixes.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Node.js Release Working Group, release schedule",
          "url": "https://github.com/nodejs/Release"
        },
        {
          "title": "endoflife.date, Node.js (community-maintained lifecycle data)",
          "url": "https://endoflife.date/nodejs"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2",
          "SA-22"
        ],
        "cis_topic": "Use supported software",
        "cis_v8": [
          "2.2"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "node",
          "message": "Node.js v24.11.0 is supported until 2028-04-30"
        }
      ]
    },
    {
      "id": "WEB-PHP-003",
      "title": "PHP Allows Remote File Inclusion",
      "domain": "web",
      "severity": "high",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether include() and require() may load code from URLs.",
      "rationale": "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).",
      "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.",
      "refs": [
        {
          "title": "PHP manual, php.ini directives",
          "url": "https://www.php.net/manual/en/ini.list.php"
        },
        {
          "title": "OWASP PHP Configuration Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-98"
        ],
        "owasp": [
          "A03:2021",
          "A05:2021"
        ],
        "asvs": [
          "12.3.3"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/php.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "php",
          "message": "allow_url_include = 0"
        }
      ]
    },
    {
      "id": "WEB-PHP-004",
      "title": "PHP Version Past End of Security Support",
      "domain": "web",
      "severity": "high",
      "category": "patch",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the installed PHP branch still receives security fixes from the PHP project.",
      "rationale": "Unsupported PHP versions never get fixes for newly found vulnerabilities, some of which allow remote code execution.",
      "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",
      "refs": [
        {
          "title": "PHP, Supported Versions",
          "url": "https://www.php.net/supported-versions.php"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2"
        ],
        "cis_v8": [
          "2.2"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/php.go",
      "academy": [
        {
          "id": "F3",
          "title": "Vulnerabilities, CVE, CWE and CVSS",
          "url": "https://openvapt.com/academy/f3.html"
        },
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "php",
          "message": "PHP 8.3 supported until 2027-12-31"
        }
      ]
    },
    {
      "id": "WEB-PY-001",
      "title": "Django Debug Mode Enabled",
      "domain": "web",
      "severity": "high",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks Django settings files for DEBUG = True with no production override.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Django documentation, Deployment checklist",
          "url": "https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/"
        },
        {
          "title": "Django settings reference, DEBUG",
          "url": "https://docs.djangoproject.com/en/6.1/ref/settings/#debug"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-215"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-ERRH-01"
        ],
        "nist": [
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shopadmin/shopadmin",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-PY-003",
      "title": "Django Uses the Generated Development Secret Key",
      "domain": "web",
      "severity": "high",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks Django settings for a SECRET_KEY that starts with django-insecure-, the marker of the key generated by startproject.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Django documentation, Deployment checklist",
          "url": "https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/"
        },
        {
          "title": "Django settings reference, SECRET_KEY",
          "url": "https://docs.djangoproject.com/en/6.1/ref/settings/#secret-key"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-798",
          "CWE-321"
        ],
        "owasp": [
          "A02:2021",
          "A05:2021"
        ],
        "asvs": [
          "2.10.4"
        ],
        "nist": [
          "IA-5",
          "SC-12"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shopadmin/shopadmin",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-TOM-001",
      "title": "Tomcat Manager Reachable Without Address Restriction",
      "domain": "web",
      "severity": "high",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks whether the Tomcat Manager or Host Manager application is deployed without an address filter limiting it to localhost.",
      "rationale": "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.",
      "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: \u003cValve className=\"org.apache.catalina.valves.RemoteAddrValve\" allow=\"127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1\" /\u003e and reach the Manager through an SSH tunnel.",
      "verify": "grep -A2 RemoteAddrValve \u003cwebapps\u003e/manager/META-INF/context.xml",
      "refs": [
        {
          "title": "Apache Tomcat 10.1, Security Considerations",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/security-howto.html"
        },
        {
          "title": "Apache Tomcat 10.1, Manager App How-To",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/manager-howto.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-923"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-05"
        ],
        "nist": [
          "AC-3",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/tomcat.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/tomcat9",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "HOST-BOOT-001",
      "title": "Boot Loader Has No Password",
      "domain": "host",
      "severity": "medium",
      "category": "access",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that GRUB is configured with a superuser and a hashed password so the boot entries cannot be edited at the console.",
      "rationale": "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.",
      "fix": "Run  grub-mkpasswd-pbkdf2  (grub2-setpassword on RHEL family), add  set superusers=\"admin\"  and  password_pbkdf2 admin \u003chash\u003e  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\u003e/dev/null | sed 's/grub.pbkdf2.*/\u003chash\u003e/'",
      "refs": [
        {
          "title": "GNU GRUB Manual, Security",
          "url": "https://www.gnu.org/software/grub/manual/grub/html_node/Security.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Boot loader password (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/boot/grub",
          "message": "GRUB has a superuser and a hashed password",
          "evidence": [
            "superusers in /etc/grub.d/40_custom",
            "password directive in /etc/grub.d/40_custom"
          ]
        }
      ]
    },
    {
      "id": "HOST-BOOT-002",
      "title": "UEFI Secure Boot Disabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks the firmware's SecureBoot variable to see whether the boot chain is verified.",
      "rationale": "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.",
      "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).",
      "refs": [
        {
          "title": "UEFI Specification, Secure Boot and Driver Signing",
          "url": "https://uefi.org/specs/UEFI/2.10/32_Secure_Boot_and_Driver_Signing.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1326"
        ],
        "nist": [
          "SI-7",
          "CM-6"
        ],
        "cis_topic": "Boot integrity (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c",
          "message": "Secure Boot is enabled",
          "evidence": [
            "SecureBoot = 1"
          ]
        }
      ]
    },
    {
      "id": "HOST-BOOT-003",
      "title": "Uncommon Filesystem Modules Not Disabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks that the cramfs, freevxfs, jffs2, hfs, hfsplus, udf and squashfs kernel modules are disabled in modprobe.d.",
      "rationale": "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.",
      "fix": "Create /etc/modprobe.d/openvapt-filesystems.conf with, for each module:  install \u003cmodule\u003e /bin/false  and  blacklist \u003cmodule\u003e  then unload any loaded one with  modprobe -r \u003cmodule\u003e. 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",
      "refs": [
        {
          "title": "modprobe.d(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/modprobe.d.5.html"
        },
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-6"
        ],
        "cis_topic": "Disable unused filesystem kernel modules (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/modprobe.d",
          "message": "All 7 uncommon filesystem modules are disabled",
          "evidence": [
            "install cramfs /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist cramfs (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install freevxfs /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist freevxfs (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install jffs2 /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist jffs2 (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install hfs /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist hfs (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install hfsplus /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist hfsplus (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install udf /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist udf (/etc/modprobe.d/openvapt-filesystems.conf)",
            "install squashfs /bin/false (/etc/modprobe.d/openvapt-filesystems.conf)",
            "blacklist squashfs (/etc/modprobe.d/openvapt-filesystems.conf)"
          ]
        }
      ]
    },
    {
      "id": "HOST-BOOT-004",
      "title": "USB Storage Driver Not Disabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks that the usb-storage kernel module is disabled in modprobe.d on servers that do not need removable media.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "modprobe.d(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/modprobe.d.5.html"
        },
        {
          "title": "MITRE ATT\u0026CK T1091, Replication Through Removable Media",
          "url": "https://attack.mitre.org/techniques/T1091/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "MP-7",
          "CM-7"
        ],
        "cis_topic": "Disable USB storage (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/modprobe.d",
          "message": "usb-storage is disabled",
          "evidence": [
            "install usb-storage /bin/false (/etc/modprobe.d/openvapt-usb.conf)",
            "blacklist usb-storage (/etc/modprobe.d/openvapt-usb.conf)"
          ]
        }
      ]
    },
    {
      "id": "HOST-BOOT-006",
      "title": "No File Integrity Monitoring",
      "domain": "host",
      "severity": "medium",
      "category": "logging",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N",
      "status": "pass",
      "summary": "Checks that a file integrity tool (AIDE, Tripwire, Samhain or equivalent) is installed and scheduled to run.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "AIDE, Advanced Intrusion Detection Environment",
          "url": "https://aide.github.io/"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "nist": [
          "SI-7",
          "CM-3"
        ],
        "cis_topic": "File integrity checking (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "integrity",
          "message": "aide installed and scheduled",
          "evidence": [
            "/etc/cron.daily/aide"
          ]
        }
      ]
    },
    {
      "id": "HOST-CTR-002",
      "title": "Docker Daemon Not Hardened",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks the Docker daemon defaults: no-new-privileges, inter-container communication, user namespace remapping and live restore.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "Docker documentation, Docker Engine security",
          "url": "https://docs.docker.com/engine/security/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-1188"
        ],
        "nist": [
          "CM-6",
          "AC-6"
        ],
        "cis_topic": "Docker daemon configuration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/docker/daemon.json",
          "message": "Docker daemon hardening options are set"
        }
      ]
    },
    {
      "id": "HOST-CTR-010",
      "title": "Docker Daemon Allows Insecure Registries or Debug Features",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N",
      "status": "pass",
      "summary": "Checks daemon.json for insecure-registries, debug mode and experimental features.",
      "rationale": "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.",
      "fix": "Remove insecure-registries (serve the registry over TLS with a trusted certificate, or add its CA to /etc/docker/certs.d/\u003cregistry\u003e/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'",
      "refs": [
        {
          "title": "Docker documentation, dockerd daemon reference",
          "url": "https://docs.docker.com/reference/cli/dockerd/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-494",
          "CWE-1188"
        ],
        "nist": [
          "SI-7",
          "CM-7"
        ],
        "cis_topic": "CIS Docker Benchmark 2.4 and 2.10 (no insecure registries; no experimental features)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "docker daemon",
          "message": "No insecure registries, debug or experimental settings in daemon.json or on the dockerd command line"
        }
      ]
    },
    {
      "id": "HOST-CTR-012",
      "title": "Containers Run Without Memory or Process Limits",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "status": "pass",
      "summary": "Looks for containers with no memory limit and no PID limit.",
      "rationale": "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.",
      "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)",
      "refs": [
        {
          "title": "Docker documentation, Resource constraints",
          "url": "https://docs.docker.com/engine/containers/resource_constraints/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "nist": [
          "SC-6",
          "SC-5"
        ],
        "cis_topic": "CIS Docker Benchmark 5.11 and 5.29 (memory limit; PIDs cgroup limit)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "containers",
          "message": "1 container(s) checked; each has a memory or PID limit"
        }
      ]
    },
    {
      "id": "HOST-CTR-013",
      "title": "Containers Run as Root With a Writable Root Filesystem",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Looks for containers whose process runs as root and whose root filesystem is writable.",
      "rationale": "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.",
      "fix": "Add USER \u003cuid\u003e 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)",
      "refs": [
        {
          "title": "OWASP Docker Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-732"
        ],
        "nist": [
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "CIS Docker Benchmark 4.1 and 5.13 (non-root user; read-only root filesystem)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "containers",
          "message": "1 container(s) checked; none run as root on a writable root filesystem"
        }
      ]
    },
    {
      "id": "HOST-FS-006",
      "title": "Boot Loader Configuration Exposed",
      "domain": "host",
      "severity": "medium",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that the GRUB configuration is not readable or writable by normal users.",
      "rationale": "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.",
      "fix": "chown root:root \u003cgrub.cfg\u003e \u0026\u0026 chmod 600 \u003cgrub.cfg\u003e  (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",
      "refs": [
        {
          "title": "GNU GRUB Manual, Security",
          "url": "https://www.gnu.org/software/grub/manual/grub/html_node/Security.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/boot/grub/grub.cfg",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-FS-014",
      "title": "World-Writable Files Outside Temporary Directories",
      "domain": "host",
      "severity": "medium",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "status": "pass",
      "summary": "Looks for files that any user can modify in program, service and home locations other than /etc (which HOST-FS-003 covers).",
      "rationale": "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.",
      "fix": "For each listed file: chmod o-w \u003cpath\u003e. 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.",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "No world-writable files (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "No world-writable files among 24 checked"
        }
      ]
    },
    {
      "id": "HOST-FS-016",
      "title": "SUID or SGID Programs Outside the Known Baseline",
      "domain": "host",
      "severity": "medium",
      "category": "access",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "fix": "For each listed program decide whether the bit is needed. Remove it with  chmod u-s,g-s \u003cpath\u003e  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.",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1548.001, Setuid and Setgid",
          "url": "https://attack.mitre.org/techniques/T1548/001/"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "Audit SUID and SGID executables (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "All 2 SUID/SGID programs are on the baseline"
        }
      ]
    },
    {
      "id": "HOST-FS-017",
      "title": "World-Writable Directories Without the Sticky Bit",
      "domain": "host",
      "severity": "medium",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "status": "pass",
      "summary": "Looks for shared writable directories outside /tmp and /etc that let any user delete or replace other users' files.",
      "rationale": "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.",
      "fix": "chmod +t \u003cdir\u003e  for directories that must stay shared, or  chmod o-w \u003cdir\u003e  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.",
      "refs": [
        {
          "title": "chmod(1), Linux man-pages (restricted deletion flag)",
          "url": "https://man7.org/linux/man-pages/man1/chmod.1.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-377"
        ],
        "nist": [
          "AC-3",
          "CM-6"
        ],
        "cis_topic": "Sticky bit on world-writable directories (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "No world-writable directories without the sticky bit among 8 checked"
        }
      ]
    },
    {
      "id": "HOST-KRN-001",
      "title": "Address Space Layout Randomisation Not Fully Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the kernel randomises memory layout for stack, libraries, heap and VDSO.",
      "rationale": "ASLR makes memory-corruption exploits much harder because attackers cannot predict where code lives.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-344"
        ],
        "nist": [
          "CM-6",
          "SI-16"
        ],
        "cis_topic": "Kernel parameters: kernel.randomize_va_space",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.randomize_va_space",
          "message": "kernel.randomize_va_space = 2"
        }
      ]
    },
    {
      "id": "HOST-KRN-009",
      "title": "Unprivileged eBPF Allowed",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks that normal users cannot load eBPF programs into the kernel.",
      "rationale": "Unprivileged eBPF has been the entry point for many local privilege-escalation bugs.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "CM-6",
          "SI-16"
        ],
        "cis_topic": "Kernel parameters: kernel.unprivileged_bpf_disabled",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.unprivileged_bpf_disabled",
          "message": "kernel.unprivileged_bpf_disabled = 2"
        }
      ]
    },
    {
      "id": "HOST-PAM-008",
      "title": "Root PATH Contains Unsafe Entries",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:H",
      "status": "pass",
      "summary": "Checks that the PATH set for root does not include the current directory, empty entries, relative paths or directories other users can write to.",
      "rationale": "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.",
      "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 : ' ')",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1574.007, Hijack Execution Flow: Path Interception by PATH Environment Variable",
          "url": "https://attack.mitre.org/techniques/T1574/007/"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-426",
          "CWE-427"
        ],
        "nist": [
          "CM-6",
          "AC-6"
        ],
        "cis_topic": "Root PATH integrity (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "root PATH",
          "message": "PATH assignments contain only absolute, root-owned directories"
        }
      ]
    },
    {
      "id": "HOST-PKG-005",
      "title": "Running Kernel Older Than Installed Kernel",
      "domain": "host",
      "severity": "medium",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks that the kernel in memory is the newest kernel installed, so kernel security fixes are actually in effect.",
      "rationale": "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.",
      "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 .",
      "refs": [
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_topic": "Apply installed kernel updates (CIS Controls v8 7.4)",
        "cis_v8": [
          "7.4"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/pkg_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel",
          "message": "Running kernel 6.8.0-45-generic is the newest installed"
        }
      ]
    },
    {
      "id": "HOST-PWD-001",
      "title": "Weak Password Hashing Algorithm Configured",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks that new passwords are hashed with a modern, slow algorithm (SHA-512 or yescrypt).",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-916",
          "CWE-328"
        ],
        "owasp": [
          "A02:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password hashing algorithm",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/login.defs",
          "message": "ENCRYPT_METHOD YESCRYPT"
        }
      ]
    },
    {
      "id": "HOST-PWD-002",
      "title": "Accounts With Obsolete Password Hashes",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Checks /etc/shadow for passwords still stored with MD5 or traditional DES crypt.",
      "rationale": "Old hashes survive configuration changes until the user changes the password. MD5-crypt and DES hashes can be cracked quickly with commodity hardware.",
      "fix": "Configure a modern algorithm (HOST-PWD-001), then force a password change for each listed account (chage -d 0 \u003cuser\u003e) or lock unused accounts (passwd -l \u003cuser\u003e).",
      "verify": "Re-run this check; no accounts should be listed.",
      "refs": [
        {
          "title": "crypt(5), libxcrypt, Debian trixie manual pages (hashing methods)",
          "url": "https://manpages.debian.org/trixie/libcrypt-dev/crypt.5.en.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-916",
          "CWE-328"
        ],
        "owasp": [
          "A02:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/shadow",
          "message": "No obsolete password hashes"
        }
      ]
    },
    {
      "id": "HOST-PWD-004",
      "title": "No Account Lockout After Failed Logins",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that PAM temporarily locks accounts after repeated failed logins.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "pam_faillock(8), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man8/pam_faillock.8.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "AC-7"
        ],
        "cis_topic": "Lockout for failed password attempts",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/pam.d/common-auth",
          "message": "Account lockout configured: pam_faillock preauth and authfail, deny=5"
        }
      ]
    },
    {
      "id": "HOST-SSH-002",
      "title": "SSH Password Authentication Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether SSH accepts passwords instead of only keys.",
      "rationale": "Internet-facing SSH receives constant automated password guessing. Public-key authentication cannot be guessed, phished as easily, or reused from another breached site.",
      "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.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307",
          "CWE-521"
        ],
        "nist": [
          "IA-2(1)",
          "IA-5"
        ],
        "cis_topic": "SSH server configuration: authentication methods",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_password_auth.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "Only key-based SSH authentication is accepted"
        }
      ]
    },
    {
      "id": "HOST-SSH-004",
      "title": "SSH Offers Weak Cryptography",
      "domain": "host",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks the SSH ciphers, MACs, key exchange and host key algorithms for outdated choices.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        },
        {
          "title": "RFC 9142, Key Exchange Method Updates for SSH",
          "url": "https://www.rfc-editor.org/rfc/rfc9142.html"
        },
        {
          "title": "NIST SP 800-131A Rev. 2, Transitioning Cryptographic Algorithms",
          "url": "https://csrc.nist.gov/pubs/sp/800/131/a/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "SSH server configuration: approved ciphers, MACs and KEX",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/host/ssh_weak_crypto.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "No weak SSH algorithms offered"
        }
      ]
    },
    {
      "id": "HOST-SSH-007",
      "title": "SSH Forwarding Features Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks effective SSH Match contexts for TCP, agent, tunnel and gateway forwarding.",
      "rationale": "Forwarding can turn an SSH account into a route to protected services or expose a user's authentication agent.",
      "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.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-441"
        ],
        "nist": [
          "AC-4",
          "CM-7"
        ],
        "cis_topic": "SSH server configuration: forwarding",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_forwarding.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "SSH forwarding is disabled in all evaluated contexts"
        }
      ]
    },
    {
      "id": "HOST-SSH-011",
      "title": "SSH Legacy Trust Options Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks for host-based trust and user environment options that weaken SSH authentication.",
      "rationale": "HostbasedAuthentication and ignoring .rhosts trust other machines instead of users. PermitUserEnvironment lets users set variables such as LD_PRELOAD that can bypass restrictions.",
      "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'",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-290",
          "CWE-15"
        ],
        "nist": [
          "IA-2",
          "CM-6"
        ],
        "cis_topic": "SSH host-based authentication and user environment",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "No legacy trust options enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-002",
      "title": "Telnet Server Installed or Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether a Telnet server is installed or set to start at boot.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled telnet.socket telnetd.service inetutils-telnetd.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1040, Network Sniffing",
          "url": "https://attack.mitre.org/techniques/T1040/"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)",
          "SC-8"
        ],
        "cis_topic": "Telnet server is not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-003",
      "title": "rsh, rlogin, rexec or finger Server Installed or Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether the Berkeley r-services (rsh, rlogin, rexec) or a finger server is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled rsh.socket rlogin.socket rexec.socket finger.socket rsh-redone-server.service fingerd.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1040, Network Sniffing",
          "url": "https://attack.mitre.org/techniques/T1040/"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)",
          "SC-8"
        ],
        "cis_topic": "rsh and finger servers are not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-004",
      "title": "TFTP Server Installed or Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether a TFTP server is installed or set to start at boot.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled tftpd-hpa.service atftpd.service tftp.socket tftp.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-306",
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)",
          "SC-8"
        ],
        "cis_topic": "TFTP server is not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-005",
      "title": "FTP Server Installed or Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether an FTP server (vsftpd, ProFTPD or Pure-FTPd) is installed or set to start at boot.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled vsftpd.service proftpd.service pure-ftpd.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1040, Network Sniffing",
          "url": "https://attack.mitre.org/techniques/T1040/"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)",
          "SC-8"
        ],
        "cis_topic": "FTP server is not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-006",
      "title": "NIS Server or Client Installed or Enabled",
      "domain": "host",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether the Network Information Service (NIS, ypserv or ypbind) is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled ypserv.service ypbind.service nis.service yppasswdd.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "MITRE ATT\u0026CK T1040, Network Sniffing",
          "url": "https://attack.mitre.org/techniques/T1040/"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-522"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)",
          "SC-8"
        ],
        "cis_topic": "NIS server and client are not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SYSD-001",
      "title": "Custom Services Run as Root",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether long-running services written by the administrator run as root instead of a dedicated user.",
      "rationale": "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.",
      "fix": "Create a dedicated account (useradd --system --no-create-home --shell /usr/sbin/nologin \u003capp\u003e), give it ownership of only the folders the app writes to, then add User=\u003capp\u003e and Group=\u003capp\u003e 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 \u003cunit\u003e  and  ps -o user= -p $(systemctl show -p MainPID --value \u003cunit\u003e)",
      "refs": [
        {
          "title": "systemd.exec(5), execution environment configuration",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security (least privilege for services)",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "Run services with least privilege",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/systemd.go",
      "academy": [
        {
          "id": "P11",
          "title": "Service Hardening With systemd",
          "url": "https://openvapt.com/academy/p11.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "All 2 custom long-running services run as a non-root user"
        }
      ]
    },
    {
      "id": "HOST-SYSD-005",
      "title": "Secrets Written Into Service Unit Files",
      "domain": "host",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether passwords, tokens or keys are set with Environment= in systemd unit files.",
      "rationale": "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.",
      "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 \u003cunit\u003e  should show no secret values.",
      "refs": [
        {
          "title": "systemd.exec(5), execution environment configuration",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html"
        },
        {
          "title": "systemd, System and Service Credentials",
          "url": "https://systemd.io/CREDENTIALS/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-526",
          "CWE-312"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "IA-5",
          "SC-28"
        ],
        "cis_topic": "Protect stored credentials",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/systemd.go",
      "academy": [
        {
          "id": "P11",
          "title": "Service Hardening With systemd",
          "url": "https://openvapt.com/academy/p11.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "No Environment= setting names look like secrets"
        }
      ]
    },
    {
      "id": "HOST-UPD-002",
      "title": "Reboot Required to Apply Updates",
      "domain": "host",
      "severity": "medium",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether installed updates (often kernel or core library fixes) are waiting for a reboot.",
      "rationale": "Updated packages do not protect anything until the running kernel and libraries are replaced. A pending reboot means known-fixed vulnerabilities are still live.",
      "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.",
      "refs": [
        {
          "title": "NIST SP 800-40 Rev. 4",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "nist": [
          "SI-2"
        ],
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "reboot",
          "message": "No reboot is pending"
        }
      ]
    },
    {
      "id": "HOST-USR-004",
      "title": "Duplicate User IDs",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that no two accounts share the same numeric user id.",
      "rationale": "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.",
      "fix": "Give each account a unique UID (usermod -u \u003cnew-uid\u003e \u003cuser\u003e) and fix file ownership (find / -xdev -uid \u003cold-uid\u003e).",
      "verify": "cut -d: -f3 /etc/passwd | sort | uniq -d  should print nothing.",
      "refs": [
        {
          "title": "passwd(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/passwd.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-286"
        ],
        "nist": [
          "AC-2",
          "IA-2"
        ],
        "cis_topic": "No duplicate UIDs",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "Every account has a unique UID"
        }
      ]
    },
    {
      "id": "HOST-USR-006",
      "title": "Legacy NIS Entries in Account Files",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks /etc/passwd, /etc/shadow and /etc/group for legacy '+' NIS inclusion entries.",
      "rationale": "'+' entries pull accounts from NIS, an unencrypted legacy directory service. If NIS is gone, the entry can still be abused to create unexpected accounts.",
      "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.",
      "refs": [
        {
          "title": "nsswitch.conf(5), Linux man-pages (compat mode)",
          "url": "https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-288"
        ],
        "nist": [
          "IA-2",
          "CM-7"
        ],
        "cis_topic": "No legacy + entries",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "account files",
          "message": "No legacy NIS entries"
        }
      ]
    },
    {
      "id": "HOST-USR-008",
      "title": "Duplicate Group IDs or Account Names",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks /etc/group for duplicate GIDs and group names, and /etc/passwd for duplicate user names.",
      "rationale": "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.",
      "fix": "groupmod -g \u003cnew-gid\u003e \u003cgroup\u003e  for one of each duplicate pair, then fix file ownership (find / -xdev -gid \u003cold-gid\u003e); 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.",
      "refs": [
        {
          "title": "group(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/group.5.html"
        },
        {
          "title": "passwd(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/passwd.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-286"
        ],
        "nist": [
          "AC-2",
          "AC-3"
        ],
        "cis_topic": "No duplicate GIDs, group names or user names",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        },
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/group",
          "message": "GIDs, group names and user names are unique"
        }
      ]
    },
    {
      "id": "HOST-USR-009",
      "title": "Password and Shadow Files Out of Step",
      "domain": "host",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "shadow(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/shadow.5.html"
        },
        {
          "title": "passwd(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/passwd.5.html"
        },
        {
          "title": "pwck(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/pwck.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-256",
          "CWE-522"
        ],
        "nist": [
          "IA-5",
          "AC-2"
        ],
        "cis_topic": "passwd and shadow consistency",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        },
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "2 accounts match between passwd and shadow"
        }
      ]
    },
    {
      "id": "NET-APP-002",
      "title": "Application Server Ports Reachable Without a Reverse Proxy",
      "domain": "network",
      "severity": "medium",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether application servers and developer tools (Node.js, Tomcat, Django, Flask, Vite) serve HTTP on network-reachable addresses.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP Node.js Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html"
        },
        {
          "title": "Gunicorn documentation, Deploying Gunicorn (behind a proxy)",
          "url": "https://gunicorn.org/deploy/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-7",
          "CM-7"
        ],
        "cis_topic": "Expose applications only through a hardened front end",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No application server port is network-reachable"
        }
      ]
    },
    {
      "id": "NET-DB-013",
      "title": "Mail Cleartext Authentication Allowed",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "Postfix or Dovecot permits plaintext authentication without requiring TLS.",
      "rationale": "Credentials sent before transport encryption can be observed by any party able to intercept the connection.",
      "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.",
      "refs": [
        {
          "title": "Dovecot Authentication Settings",
          "url": "https://doc.dovecot.org/main/core/config/auth/settings.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-523"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.1"
        ],
        "nist": [
          "SC-8",
          "IA-5(1)"
        ],
        "cis_topic": "Secure data service configuration",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postfix/main.cf, /etc/dovecot/conf.d/10-auth.conf",
          "message": "No unsafe setting was found"
        }
      ]
    },
    {
      "id": "NET-DB-014",
      "title": "MySQL Symbolic Links Allowed for Tables",
      "domain": "network",
      "severity": "medium",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "MySQL or MariaDB does not disable symbolic links for table files (skip-symbolic-links or symbolic-links=0).",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "MySQL 8.4 Reference Manual, Using Symbolic Links",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/symbolic-links.html"
        },
        {
          "title": "MySQL 8.4 Reference Manual, Security Guidelines",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/security-guidelines.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-59",
          "CWE-732"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "nist": [
          "CM-6",
          "AC-6"
        ],
        "cis_topic": "Database server hardening",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/mysql/conf.d/hardening.cnf: mysqld.symbolic-links = 0"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-015",
      "title": "MySQL secure_file_priv Not Restricted",
      "domain": "network",
      "severity": "medium",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "MySQL 8.4 Reference Manual, secure_file_priv",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_secure_file_priv"
        },
        {
          "title": "MySQL 8.4 Reference Manual, Security Guidelines",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/security-guidelines.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-73",
          "CWE-250"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "nist": [
          "CM-6",
          "AC-6",
          "SI-10"
        ],
        "cis_topic": "Database server hardening",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/mysql/conf.d/hardening.cnf: mysqld.secure_file_priv = /var/lib/mysql-files"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-016",
      "title": "MySQL Does Not Require Encrypted Client Connections",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "require_secure_transport is not ON, so clients may log in and query over plaintext TCP.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "MySQL 8.4 Reference Manual, Configuring MySQL to Use Encrypted Connections",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/using-encrypted-connections.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, SC-8 Transmission Confidentiality and Integrity",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.2"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "Database transport encryption",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "The hardening setting is present",
          "evidence": [
            "skip-networking is on; no TCP connections are accepted"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-019",
      "title": "PostgreSQL Uses md5 or password Authentication",
      "domain": "network",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "pg_hba.conf has active rules with the md5 or password method instead of scram-sha-256.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "PostgreSQL, Authentication Methods",
          "url": "https://www.postgresql.org/docs/current/auth-methods.html"
        },
        {
          "title": "PostgreSQL, Password Authentication",
          "url": "https://www.postgresql.org/docs/current/auth-password.html"
        },
        {
          "title": "NIST SP 800-63B, Digital Identity Guidelines: Authentication",
          "url": "https://pages.nist.gov/800-63-3/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-328",
          "CWE-916"
        ],
        "owasp": [
          "A02:2021",
          "A07:2021"
        ],
        "asvs": [
          "2.4.1"
        ],
        "nist": [
          "IA-5",
          "IA-5(1)",
          "SC-13"
        ],
        "cis_topic": "Database password hashing and authentication method",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postgresql/16/main/pg_hba.conf",
          "message": "No pg_hba.conf rule uses md5 or password"
        }
      ]
    },
    {
      "id": "NET-DB-020",
      "title": "PostgreSQL SSL Not Enabled",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "postgresql.conf does not set ssl = on, so TCP clients cannot encrypt their sessions and hostssl rules cannot apply.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "PostgreSQL, Secure TCP/IP Connections with SSL",
          "url": "https://www.postgresql.org/docs/current/ssl-tcp.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, SC-8 Transmission Confidentiality and Integrity",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.2"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "Database transport encryption",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postgresql/16/main/postgresql.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "listen_addresses = 127.0.0.1 is loopback only, so plaintext connections never leave this machine; enable ssl before exposing the port"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-022",
      "title": "PostgreSQL Password Encryption Not SCRAM",
      "domain": "network",
      "severity": "medium",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "password_encryption is set to md5, so new and changed passwords are stored as md5 verifiers that work as pass-the-hash credentials.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "PostgreSQL, Password Authentication",
          "url": "https://www.postgresql.org/docs/current/auth-password.html"
        },
        {
          "title": "PostgreSQL, Connections and Authentication",
          "url": "https://www.postgresql.org/docs/current/runtime-config-connection.html"
        },
        {
          "title": "NIST SP 800-63B, Digital Identity Guidelines: Authentication",
          "url": "https://pages.nist.gov/800-63-3/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-916",
          "CWE-328"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "2.4.1"
        ],
        "nist": [
          "IA-5(1)",
          "SC-13"
        ],
        "cis_topic": "Database password hashing",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postgresql/16/main/postgresql.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/postgresql/16/main/postgresql.conf: password_encryption = scram-sha-256"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-023",
      "title": "Redis Dangerous Commands Not Restricted",
      "domain": "network",
      "severity": "medium",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "redis.conf neither renames nor disables CONFIG, FLUSHALL, FLUSHDB, DEBUG and KEYS, and no ACL user excludes the @dangerous category.",
      "rationale": "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.",
      "fix": "Prefer ACLs on Redis 6 and newer:  user app on \u003esecret ~app:* -@all +@read +@write -@dangerous . Otherwise add  rename-command FLUSHALL \"\"  (and the same for FLUSHDB, CONFIG, DEBUG, KEYS) and restart.",
      "verify": "redis-cli -a \u003csecret\u003e ACL LIST  or  redis-cli CONFIG GET dir  should be refused for the application user.",
      "refs": [
        {
          "title": "Redis Security",
          "url": "https://redis.io/docs/latest/operate/oss_and_stack/management/security/"
        },
        {
          "title": "Redis ACL",
          "url": "https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "nist": [
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "Data store administrative command restriction",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/redis/redis.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/redis/redis.conf: rename-command = CONFIG ",
            "/etc/redis/redis.conf: rename-command = FLUSHALL ",
            "/etc/redis/redis.conf: rename-command = FLUSHDB ",
            "/etc/redis/redis.conf: user = app on \u003eredacted ~* +@all"
          ]
        }
      ]
    },
    {
      "id": "NET-DNS-002",
      "title": "BIND DNSSEC Validation Disabled",
      "domain": "network",
      "severity": "medium",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N",
      "status": "pass",
      "summary": "named.conf sets dnssec-validation no, so the resolver accepts forged answers for signed zones.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "BIND 9 Administrator Reference Manual, dnssec-validation",
          "url": "https://bind9.readthedocs.io/en/latest/reference.html#namedconf-statement-dnssec-validation"
        },
        {
          "title": "NIST SP 800-81 Rev. 2, Secure Domain Name System Deployment Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/81/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-347",
          "CWE-350"
        ],
        "owasp": [
          "A08:2021"
        ],
        "nist": [
          "SC-20",
          "SC-21"
        ],
        "cis_topic": "DNS resolver validation",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/bind/named.conf.options",
          "message": "DNSSEC validation is enabled",
          "evidence": [
            "dnssec-validation auto"
          ]
        }
      ]
    },
    {
      "id": "NET-FW-001",
      "title": "No Active Host Firewall",
      "domain": "network",
      "severity": "medium",
      "category": "network",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether a host firewall (nftables, iptables, ufw or firewalld) is filtering incoming traffic.",
      "rationale": "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.",
      "fix": "Ubuntu: ufw default deny incoming \u0026\u0026 ufw allow OpenSSH \u0026\u0026 ufw allow 80,443/tcp \u0026\u0026 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",
      "refs": [
        {
          "title": "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy",
          "url": "https://csrc.nist.gov/pubs/sp/800/41/r1/final"
        },
        {
          "title": "nftables wiki",
          "url": "https://wiki.nftables.org/wiki-nftables/index.php/Main_Page"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-923"
        ],
        "nist": [
          "SC-7"
        ],
        "cis_topic": "Host-based firewall configured",
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall.go",
      "academy": [
        {
          "id": "F1",
          "title": "Security Basics",
          "url": "https://openvapt.com/academy/f1.html"
        },
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "firewall",
          "message": "ufw has a default-deny incoming policy"
        }
      ]
    },
    {
      "id": "NET-FW-005",
      "title": "Container Ports Bypass the Host Firewall",
      "domain": "network",
      "severity": "medium",
      "category": "network",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether Docker publishes container ports through its own iptables chains with no DOCKER-USER filtering.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Docker documentation, Packet filtering and firewalls",
          "url": "https://docs.docker.com/engine/network/packet-filtering-firewalls/"
        },
        {
          "title": "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy",
          "url": "https://csrc.nist.gov/pubs/sp/800/41/r1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-923",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-7",
          "CM-7"
        ],
        "cis_topic": "Filter container traffic",
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P12",
          "title": "Firewall Policy Versus What Is Listening",
          "url": "https://openvapt.com/academy/p12.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "iptables",
          "message": "No published container ports found in the Docker chain"
        }
      ]
    },
    {
      "id": "NET-MAIL-001",
      "title": "Postfix SMTP TLS Not Offered or Has No Certificate",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "smtpd_tls_security_level is none or unset, or is may or encrypt without a server certificate, so incoming SMTP sessions cannot be encrypted.",
      "rationale": "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.",
      "fix": "Set  smtpd_tls_security_level = may ,  smtpd_tls_chain_files = /etc/letsencrypt/live/\u003chost\u003e/privkey.pem, /etc/letsencrypt/live/\u003chost\u003e/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.",
      "refs": [
        {
          "title": "Postfix TLS Support",
          "url": "https://www.postfix.org/TLS_README.html"
        },
        {
          "title": "Postfix Configuration Parameters",
          "url": "https://www.postfix.org/postconf.5.html"
        },
        {
          "title": "NIST SP 800-177 Rev. 1, Trustworthy Email",
          "url": "https://csrc.nist.gov/pubs/sp/800/177/r1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.2"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "Mail transport encryption",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postfix/main.cf",
          "message": "STARTTLS is offered with a certificate",
          "evidence": [
            "/etc/postfix/main.cf: smtpd_tls_security_level = may",
            "/etc/postfix/main.cf: smtpd_tls_chain_files = /etc/letsencrypt/live/mail/privkey.pem, /etc/letsencrypt/live/mail/fullchain.pem"
          ]
        }
      ]
    },
    {
      "id": "NET-MAIL-002",
      "title": "Postfix Allows TLS 1.0 or 1.1",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "smtpd_tls_protocols or smtpd_tls_mandatory_protocols is unset or still allows TLSv1 or TLSv1.1.",
      "rationale": "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.",
      "fix": "Add  smtpd_tls_protocols = \u003e=TLSv1.2  and  smtpd_tls_mandatory_protocols = \u003e=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",
      "refs": [
        {
          "title": "Postfix TLS Support",
          "url": "https://www.postfix.org/TLS_README.html"
        },
        {
          "title": "RFC 8996, Deprecating TLS 1.0 and TLS 1.1",
          "url": "https://www.rfc-editor.org/rfc/rfc8996.html"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326",
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.3"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS protocol versions",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postfix/main.cf",
          "message": "Postfix requires TLS 1.2 or newer",
          "evidence": [
            "smtpd_tls_protocols = \u003e=TLSv1.2",
            "smtpd_tls_mandatory_protocols = \u003e=TLSv1.2"
          ]
        }
      ]
    },
    {
      "id": "NET-MAIL-004",
      "title": "Dovecot Does Not Require TLS",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Dovecot's ssl setting is yes or no instead of required, so IMAP and POP3 clients may use unencrypted sessions.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Dovecot SSL/TLS Configuration",
          "url": "https://doc.dovecot.org/main/core/config/ssl.html"
        },
        {
          "title": "RFC 8996, Deprecating TLS 1.0 and TLS 1.1",
          "url": "https://www.rfc-editor.org/rfc/rfc8996.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.2"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "Mail access encryption",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/dovecot/conf.d/10-ssl.conf",
          "message": "Dovecot requires TLS",
          "evidence": [
            "/etc/dovecot/conf.d/10-ssl.conf: ssl = required",
            "/etc/dovecot/conf.d/10-ssl.conf: ssl_min_protocol = TLSv1.2"
          ]
        }
      ]
    },
    {
      "id": "NET-SVC-004",
      "title": "Management Panel or Monitoring Ports Network-Reachable",
      "domain": "network",
      "severity": "medium",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Webmin, cPanel, WHM, RabbitMQ Management, Consul, the Docker API with TLS or the Prometheus node exporter listen on network-reachable addresses.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Prometheus, Security model",
          "url": "https://prometheus.io/docs/operating/security/"
        },
        {
          "title": "IANA Service Name and Transport Protocol Port Number Registry",
          "url": "https://www.iana.org/assignments/service-names-port-numbers"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-1327"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "AC-17",
          "CM-7",
          "SC-7"
        ],
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/services.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "No management panel or monitoring port is network-reachable"
        }
      ]
    },
    {
      "id": "NET-TLS-001",
      "title": "Deprecated TLS 1.0 or 1.1 Accepted",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether local TLS services still accept TLS 1.0 or TLS 1.1.",
      "rationale": "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.",
      "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 \u003c/dev/null  should fail to connect.",
      "refs": [
        {
          "title": "RFC 8996, Deprecating TLS 1.0 and TLS 1.1",
          "url": "https://www.rfc-editor.org/rfc/rfc8996.html"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        },
        {
          "title": "Mozilla Server Side TLS guidelines",
          "url": "https://wiki.mozilla.org/Security/Server_Side_TLS"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326",
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.3"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_topic": "TLS protocol versions",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test on port 443",
          "message": "TLS 1.0 and 1.1 refused"
        }
      ]
    },
    {
      "id": "NET-TLS-002",
      "title": "TLS Certificate Expired or Expiring Soon",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks the expiry date of the certificate each local TLS service presents.",
      "rationale": "An expired certificate breaks the site for visitors and trains users to click through warnings, which is exactly what an attacker needs for interception.",
      "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 \u003csite\u003e 2\u003e/dev/null | openssl x509 -noout -enddate",
      "refs": [
        {
          "title": "Let's Encrypt, certificate lifetimes and renewal",
          "url": "https://letsencrypt.org/docs/faq/"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-298"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.1"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-12",
          "SC-17"
        ],
        "cis_topic": "Certificate validity",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test on port 443",
          "message": "Certificate valid for 79 more days",
          "evidence": [
            "notAfter 2027-01-04"
          ]
        }
      ]
    },
    {
      "id": "NET-TLS-003",
      "title": "Self-Signed or Mismatched TLS Certificate",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether each site presents a self-signed certificate, or one that does not cover its name.",
      "rationale": "Browsers reject these certificates, so visitors either leave or learn to ignore warnings, which removes protection against interception.",
      "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 \u003csite\u003e \u003c/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltName",
      "refs": [
        {
          "title": "RFC 6125, Representation and Verification of Domain-Based Application Service Identity",
          "url": "https://www.rfc-editor.org/rfc/rfc6125.html"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-295",
          "CWE-297"
        ],
        "owasp": [
          "A02:2021",
          "A07:2021"
        ],
        "asvs": [
          "9.2.1"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-17"
        ],
        "cis_topic": "Certificate trust",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Certificate is CA-issued and covers the site name"
        }
      ]
    },
    {
      "id": "NET-TLS-004",
      "title": "Weak TLS 1.2 Cipher Suites Accepted",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "A local TLS service accepts an RC4, 3DES, or CBC with SHA-1 cipher suite under TLS 1.2.",
      "rationale": "Obsolete cipher constructions lack modern authenticated encryption and are prohibited by current TLS hardening guidance.",
      "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.",
      "refs": [
        {
          "title": "RFC 8996, Deprecating TLS 1.0 and TLS 1.1",
          "url": "https://www.rfc-editor.org/rfc/rfc8996.html"
        },
        {
          "title": "NIST SP 800-52 Rev. 2, Guidelines for TLS Implementations",
          "url": "https://csrc.nist.gov/pubs/sp/800/52/r2/final"
        },
        {
          "title": "Mozilla Server Side TLS guidelines",
          "url": "https://wiki.mozilla.org/Security/Server_Side_TLS"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326",
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.3"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-13"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls_depth.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test on port 443",
          "message": "Testable weak TLS 1.2 cipher suites were rejected"
        }
      ]
    },
    {
      "id": "NET-TLS-005",
      "title": "TLS Certificate Public Key Too Small",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "A TLS leaf certificate uses RSA below 2048 bits or an elliptic curve below 256 bits.",
      "rationale": "Undersized public keys provide inadequate security strength and may permit certificate impersonation as cryptanalytic capability improves.",
      "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.",
      "refs": [
        {
          "title": "NIST SP 800-131A Rev. 2, Transitioning the Use of Cryptographic Algorithms and Key Lengths",
          "url": "https://csrc.nist.gov/pubs/sp/800/131/a/r2/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-326"
        ],
        "owasp": [
          "A02:2021"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-12",
          "SC-13"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls_depth.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test on port 443",
          "message": "Certificate key strength and signature metadata passed this check"
        }
      ]
    },
    {
      "id": "NET-TLS-006",
      "title": "SHA-1 Signed TLS Leaf Certificate",
      "domain": "network",
      "severity": "medium",
      "category": "crypto",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N",
      "status": "pass",
      "summary": "A TLS leaf certificate is signed using SHA-1.",
      "rationale": "SHA-1 collision attacks invalidate its use for digital signatures and modern clients reject SHA-1 certificates.",
      "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.",
      "refs": [
        {
          "title": "NIST Transitioning Away from SHA-1",
          "url": "https://csrc.nist.gov/News/2022/nist-transitioning-away-from-sha-1-for-all-apps"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-327"
        ],
        "owasp": [
          "A02:2021"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-12",
          "SC-13"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/network/tls_depth.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test on port 443",
          "message": "Certificate key strength and signature metadata passed this check"
        }
      ]
    },
    {
      "id": "WEB-APA-003",
      "title": "Apache Directory Indexes Enabled in Configuration",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "An Options directive enables Indexes (or All), so folders without an index page list their files.",
      "rationale": "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.",
      "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\u003e/dev/null | grep -n Options  should show no Indexes except with a leading minus.",
      "refs": [
        {
          "title": "Apache HTTP Server, Options directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#options"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-548"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "asvs": [
          "4.3.2"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "AC-3"
        ],
        "cis_topic": "Web server directory listing",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "No Options directive enables Indexes"
        }
      ]
    },
    {
      "id": "WEB-APA-011",
      "title": "Apache Has No Slow Request Protection",
      "domain": "web",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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\u003e/dev/null | grep reqtimeout",
      "refs": [
        {
          "title": "Apache HTTP Server, mod_reqtimeout",
          "url": "https://httpd.apache.org/docs/2.4/mod/mod_reqtimeout.html"
        },
        {
          "title": "Apache HTTP Server, Security Tips",
          "url": "https://httpd.apache.org/docs/2.4/misc/security_tips.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "SC-5",
          "CM-6"
        ],
        "cis_topic": "Web server request time limits",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        },
        {
          "id": "P25",
          "title": "Availability Without Load Testing",
          "url": "https://openvapt.com/academy/p25.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "Slow request protection is configured",
          "evidence": [
            "/etc/apache2/mods-enabled/reqtimeout.load: LoadModule reqtimeout_module /usr/lib/apache2/modules/mod_reqtimeout.so",
            "/etc/apache2/mods-enabled/reqtimeout.conf: RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500",
            "/etc/apache2/mods-enabled/reqtimeout.load"
          ]
        }
      ]
    },
    {
      "id": "WEB-APP-002",
      "title": "Application .env File Readable by All Users",
      "domain": "web",
      "severity": "medium",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks the permission bits of framework .env files for world-read access.",
      "rationale": ".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.",
      "fix": "chmod 640 .env \u0026\u0026 chown \u003capp-user\u003e:\u003cweb-group\u003e .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.",
      "refs": [
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        },
        {
          "title": "OWASP ASVS 4.0.3, V14 Configuration",
          "url": "https://github.com/OWASP/ASVS/blob/v4.0.3/4.0/en/0x22-V14-Config.md"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-312"
        ],
        "owasp": [
          "A05:2021",
          "A02:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-crm/.env",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/srv/shop-laravel/.env",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-APP-005",
      "title": "Rails Production Does Not Force HTTPS",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks config/environments/production.rb for config.force_ssl = true.",
      "rationale": "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.",
      "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://\u003csite\u003e/ returns a 301 to https.",
      "refs": [
        {
          "title": "Ruby on Rails Guides, Configuring Rails Applications",
          "url": "https://guides.rubyonrails.org/configuring.html"
        },
        {
          "title": "Ruby on Rails Guides, Securing Rails Applications",
          "url": "https://guides.rubyonrails.org/security.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-614"
        ],
        "owasp": [
          "A02:2021",
          "A05:2021"
        ],
        "asvs": [
          "9.1.1",
          "3.4.1"
        ],
        "wstg": [
          "WSTG-CRYP-03"
        ],
        "nist": [
          "SC-8",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-rails/config/environments/production.rb",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-APP-009",
      "title": "Git Repository Stored Inside a Web Root",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Looks on disk for a .git folder directly inside each web document root.",
      "rationale": "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.",
      "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 \u003cdocroot\u003e/.git  prints nothing.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        },
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-527",
          "CWE-538"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "No .git folder in the web root"
        }
      ]
    },
    {
      "id": "WEB-APP-010",
      "title": "Framework Writable Folders Open to All Users",
      "domain": "web",
      "severity": "medium",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L",
      "status": "pass",
      "summary": "Checks Laravel and Symfony cache, log and storage folders for world-write permission.",
      "rationale": "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.",
      "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 \u0026\u0026 chmod -R g+w storage bootstrap/cache.",
      "verify": "find storage bootstrap/cache var -perm -0002 -type d  prints nothing.",
      "refs": [
        {
          "title": "Laravel documentation, Deployment",
          "url": "https://laravel.com/docs/12.x/deployment"
        },
        {
          "title": "Symfony documentation, Setting up or Fixing File Permissions",
          "url": "https://symfony.com/doc/current/setup/file_permissions.html"
        },
        {
          "title": "OWASP WSTG-CONF-09, Test File Permission",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/09-Test_File_Permission/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-94"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "wstg": [
          "WSTG-CONF-09"
        ],
        "nist": [
          "AC-3",
          "AC-6",
          "CM-5"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-crm/.env",
          "message": "No issue found"
        },
        {
          "status": "pass",
          "target": "/srv/shop-laravel/.env",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CK-001",
      "title": "Cookies Missing Secure, HttpOnly or SameSite",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks the security flags on cookies the home page sets.",
      "rationale": "Without Secure, cookies leak over plain HTTP. Without HttpOnly, any XSS bug can steal sessions. Without SameSite, cross-site request forgery is easier.",
      "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://\u003csite\u003e/ | grep -i set-cookie",
      "refs": [
        {
          "title": "OWASP Session Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html"
        },
        {
          "title": "RFC 6265, HTTP State Management Mechanism",
          "url": "https://www.rfc-editor.org/rfc/rfc6265.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-614",
          "CWE-1004",
          "CWE-1275"
        ],
        "owasp": [
          "A05:2021",
          "A07:2021"
        ],
        "asvs": [
          "3.4.1",
          "3.4.2",
          "3.4.3"
        ],
        "wstg": [
          "WSTG-SESS-02"
        ],
        "nist": [
          "SC-8",
          "SC-23"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/transport.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "All cookies carry Secure, HttpOnly and SameSite"
        }
      ]
    },
    {
      "id": "WEB-CMS-006",
      "title": "WordPress Shows Debug Output to Visitors",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks wp-config.php for WP_DEBUG enabled without WP_DEBUG_DISPLAY set to false.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "WordPress Developer Resources, Debugging in WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-209",
          "CWE-489"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-ERRH-01"
        ],
        "nist": [
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/wp-config.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-008",
      "title": "WordPress Automatic Updates Disabled",
      "domain": "web",
      "severity": "medium",
      "category": "patch",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks whether wp-config.php turns off WordPress background updates, including security releases.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Configuring Automatic Background Updates",
          "url": "https://developer.wordpress.org/advanced-administration/upgrade/upgrading/#configuring-automatic-background-updates"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2"
        ],
        "cis_v8": [
          "7.4"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/wp-config.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-009",
      "title": "Joomla Debug or Verbose Error Reporting Enabled",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks Joomla configuration.php for debug mode or maximum/development error reporting.",
      "rationale": "Joomla's debug console and verbose error reporting print queries, file paths and configuration details into pages that visitors can see.",
      "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",
      "refs": [
        {
          "title": "Joomla Documentation, Security Checklist: Site Administration",
          "url": "https://docs.joomla.org/Security_Checklist/Site_Administration"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-209",
          "CWE-489"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-ERRH-01"
        ],
        "nist": [
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/configuration.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-013",
      "title": "Drupal Trusted Host Patterns Not Set",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks Drupal settings.php for the trusted_host_patterns setting.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "Drupal.org, Trusted Host settings",
          "url": "https://www.drupal.org/docs/getting-started/installing-drupal/trusted-host-settings"
        },
        {
          "title": "OWASP WSTG-INPV-17, Testing for Host Header Injection",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-644",
          "CWE-346"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.4.1"
        ],
        "wstg": [
          "WSTG-INPV-17"
        ],
        "nist": [
          "CM-6",
          "SI-10"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/sites/default/settings.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-015",
      "title": "Magento Not in Production Mode",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks app/etc/env.php for a MAGE_MODE other than production.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Adobe Commerce documentation, Set the Magento mode",
          "url": "https://experienceleague.adobe.com/en/docs/commerce-operations/configuration-guide/cli/set-mode"
        },
        {
          "title": "Adobe Commerce documentation, Security best practices",
          "url": "https://experienceleague.adobe.com/en/docs/commerce-admin/systems/security/security-best-practices"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-209"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-CONF-02",
          "WSTG-ERRH-01"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/app/etc/env.php",
          "message": "MAGE_MODE is production"
        }
      ]
    },
    {
      "id": "WEB-CMS-016",
      "title": "Ghost Configured With a Plain HTTP Site URL",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks Ghost config.production.json for a url that starts with http://.",
      "rationale": "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.",
      "fix": "ghost config url https://\u003cyour-domain\u003e  (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.",
      "refs": [
        {
          "title": "Ghost documentation, Configuration (url)",
          "url": "https://ghost.org/docs/config/"
        },
        {
          "title": "OWASP Transport Layer Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021",
          "A05:2021"
        ],
        "asvs": [
          "9.1.1"
        ],
        "wstg": [
          "WSTG-CRYP-03"
        ],
        "nist": [
          "SC-8",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-blog/config.production.json",
          "message": "Ghost url uses https://"
        }
      ]
    },
    {
      "id": "WEB-COR-001",
      "title": "Cross-Origin Resource Sharing Allows Any Origin With Credentials",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
      "status": "pass",
      "summary": "Checks the home page's CORS headers for a wildcard origin combined with credentials, or an origin of null.",
      "rationale": "A permissive CORS policy can let any website read responses on behalf of a logged-in visitor, exposing personal data and tokens.",
      "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://\u003csite\u003e/ | grep -i access-control",
      "refs": [
        {
          "title": "OWASP WSTG-CLNT-07, Testing Cross Origin Resource Sharing",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing/"
        },
        {
          "title": "MDN Web Docs, Access-Control-Allow-Origin",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Origin"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-942"
        ],
        "owasp": [
          "A05:2021",
          "A01:2021"
        ],
        "asvs": [
          "14.5.3"
        ],
        "wstg": [
          "WSTG-CLNT-07"
        ],
        "nist": [
          "AC-4",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/docroot.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "No permissive CORS policy on the home page"
        }
      ]
    },
    {
      "id": "WEB-DOC-003",
      "title": "Archives or Certificates Inside a Web Root",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Lists archives (*.tar.gz, *.tgz, *.tar, *.7z) and certificate files (*.pem, *.crt) inside each web document root for review, outside download folders.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files for Sensitive Information",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        },
        {
          "title": "OWASP Secrets Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-538",
          "CWE-552"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "SC-28",
          "CM-6"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/docroot.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop",
          "message": "No archives or certificates to review among 9 files",
          "evidence": [
            "/var/www/shop/downloads/catalogue.tar.gz (in a download folder, expected)"
          ]
        }
      ]
    },
    {
      "id": "WEB-EXP-003",
      "title": "PHP Information Page Exposed",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks for phpinfo() pages left on the site.",
      "rationale": "phpinfo() reveals PHP and module versions, file paths, environment variables and sometimes credentials, giving attackers a detailed map of the server.",
      "fix": "Delete the file. Never leave diagnostic pages on a production server.",
      "verify": "curl -s -o /dev/null -w '%{http_code}' https://\u003csite\u003e/phpinfo.php  should print 404.",
      "refs": [
        {
          "title": "PHP manual, phpinfo()",
          "url": "https://www.php.net/manual/en/function.phpinfo.php"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-005",
      "title": "Directory Listing Enabled",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether the web server lists folder contents when there is no index page.",
      "rationale": "Directory listings reveal every file in a folder, including backups, uploads and scripts never meant to be linked.",
      "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://\u003csite\u003e/uploads/ | grep -i 'index of'  should print nothing.",
      "refs": [
        {
          "title": "Apache HTTP Server, Options directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#options"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-548"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "asvs": [
          "4.3.2"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "AC-3"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-006",
      "title": "Web Server Status Page Exposed",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether Apache server-status/server-info or nginx stub_status are reachable for each site.",
      "rationale": "Status pages show live requests (including URLs with tokens), client IPs, virtual hosts and modules.",
      "fix": "Restrict to administrators: \u003cLocation /server-status\u003e Require local \u003c/Location\u003e (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://\u003csite\u003e/server-status  should print 403 or 404.",
      "refs": [
        {
          "title": "Apache HTTP Server, mod_status",
          "url": "https://httpd.apache.org/docs/2.4/mod/mod_status.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-011",
      "title": "Database Administration Tool Exposed",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether phpMyAdmin or Adminer login pages are publicly reachable.",
      "rationale": "Database admin tools are among the most attacked paths on the internet: they invite password guessing and their own vulnerabilities give direct database access.",
      "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.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-05, Enumerate Infrastructure and Application Admin Interfaces",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/05-Enumerate_Infrastructure_and_Application_Admin_Interfaces/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-306"
        ],
        "owasp": [
          "A05:2021",
          "A07:2021"
        ],
        "wstg": [
          "WSTG-CONF-05"
        ],
        "nist": [
          "CM-7",
          "AC-3"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-HDR-001",
      "title": "Missing HTTP Strict Transport Security (HSTS)",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks that HTTPS sites tell browsers to always use HTTPS, for at least six months.",
      "rationale": "Without HSTS, the first visit or any typed http:// link can be intercepted and downgraded to plain HTTP (SSL stripping) on public Wi-Fi.",
      "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://\u003csite\u003e/ | grep -i strict-transport-security",
      "refs": [
        {
          "title": "RFC 6797, HTTP Strict Transport Security",
          "url": "https://www.rfc-editor.org/rfc/rfc6797.html"
        },
        {
          "title": "OWASP HTTP Headers Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html"
        },
        {
          "title": "MDN Web Docs, Strict-Transport-Security",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319",
          "CWE-523"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.4.5"
        ],
        "wstg": [
          "WSTG-CONF-07"
        ],
        "nist": [
          "SC-8"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "HSTS is set",
          "evidence": [
            "max-age=31536000; includeSubDomains"
          ]
        }
      ]
    },
    {
      "id": "WEB-HDR-002",
      "title": "Missing Content Security Policy",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that each site sends a Content-Security-Policy header.",
      "rationale": "A CSP limits where scripts may load from, turning many cross-site scripting (XSS) bugs from account takeover into a blocked request.",
      "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://\u003csite\u003e/ | grep -i content-security-policy",
      "refs": [
        {
          "title": "OWASP Content Security Policy Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html"
        },
        {
          "title": "W3C Content Security Policy Level 3",
          "url": "https://www.w3.org/TR/CSP3/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1021",
          "CWE-79"
        ],
        "owasp": [
          "A03:2021",
          "A05:2021"
        ],
        "asvs": [
          "14.4.3"
        ],
        "wstg": [
          "WSTG-CONF-12"
        ],
        "nist": [
          "SI-10"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Content-Security-Policy is set",
          "evidence": [
            "default-src 'self'; object-src 'none'; frame-ancestors 'self'"
          ]
        }
      ]
    },
    {
      "id": "WEB-HDR-003",
      "title": "Site Can Be Framed (Clickjacking)",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that each site forbids being embedded in other sites' frames.",
      "rationale": "If a page can be framed, an attacker can overlay it invisibly and trick users into clicking buttons (clickjacking), such as 'delete' or 'transfer'.",
      "fix": "Send  Content-Security-Policy: frame-ancestors 'self'  (modern) and  X-Frame-Options: SAMEORIGIN  (older browsers).",
      "verify": "curl -sI https://\u003csite\u003e/ | grep -Ei 'x-frame-options|frame-ancestors'",
      "refs": [
        {
          "title": "OWASP Clickjacking Defense Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html"
        },
        {
          "title": "MDN Web Docs, X-Frame-Options",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1021"
        ],
        "owasp": [
          "A04:2021"
        ],
        "asvs": [
          "14.4.7"
        ],
        "wstg": [
          "WSTG-CLNT-09"
        ],
        "nist": [
          "SC-18"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Framing is restricted",
          "evidence": [
            "frame-ancestors 'self'"
          ]
        }
      ]
    },
    {
      "id": "WEB-NGX-002",
      "title": "nginx Directory Listing Enabled in Configuration",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "An nginx server or location block sets autoindex on.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "nginx documentation, ngx_http_autoindex_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_autoindex_module.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-548"
        ],
        "owasp": [
          "A01:2021",
          "A05:2021"
        ],
        "asvs": [
          "4.3.2"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "AC-3"
        ],
        "cis_topic": "Web server directory listing",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "autoindex is off or not enabled"
        }
      ]
    },
    {
      "id": "WEB-NGX-007",
      "title": "nginx Serves Hidden Files and Folders",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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://\u003csite\u003e/.git/HEAD  should print 403 or 404.",
      "refs": [
        {
          "title": "nginx documentation, ngx_http_core_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_core_module.html"
        },
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-538",
          "CWE-527"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1",
          "14.3.3"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_topic": "Web server access to hidden files",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "a location block matches dot paths"
          ]
        }
      ]
    },
    {
      "id": "WEB-PHP-001",
      "title": "PHP Displays Errors to Visitors",
      "domain": "web",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether PHP prints error messages into web pages.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "PHP manual, php.ini directives",
          "url": "https://www.php.net/manual/en/ini.list.php"
        },
        {
          "title": "OWASP PHP Configuration Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-209"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-ERRH-01"
        ],
        "nist": [
          "SI-11",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/php.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "php",
          "message": "display_errors = Off"
        }
      ]
    },
    {
      "id": "WEB-PHP-005",
      "title": "PHP Session Cookies Not Hardened",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks PHP's session settings: HttpOnly and SameSite cookies and strict session id mode.",
      "rationale": "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).",
      "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');\"",
      "refs": [
        {
          "title": "PHP manual, php.ini directives",
          "url": "https://www.php.net/manual/en/ini.list.php"
        },
        {
          "title": "PHP manual, Session security",
          "url": "https://www.php.net/manual/en/session.security.php"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1004",
          "CWE-384"
        ],
        "owasp": [
          "A07:2021"
        ],
        "asvs": [
          "3.4.2",
          "3.2.1"
        ],
        "wstg": [
          "WSTG-SESS-02",
          "WSTG-SESS-03"
        ],
        "nist": [
          "SC-23",
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/php.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "php",
          "message": "PHP session cookies are hardened"
        }
      ]
    },
    {
      "id": "WEB-PY-002",
      "title": "Django Accepts Any Host Header",
      "domain": "web",
      "severity": "medium",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks Django settings for ALLOWED_HOSTS = ['*'].",
      "rationale": "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.",
      "fix": "List the real domain names: ALLOWED_HOSTS = ['example.com', 'www.example.com'], and restart the application.",
      "verify": "python manage.py check --deploy",
      "refs": [
        {
          "title": "Django documentation, Deployment checklist",
          "url": "https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/"
        },
        {
          "title": "Django settings reference, ALLOWED_HOSTS",
          "url": "https://docs.djangoproject.com/en/6.1/ref/settings/#allowed-hosts"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-346"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-INPV-17"
        ],
        "nist": [
          "CM-6",
          "SI-10"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shopadmin/shopadmin",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-PY-005",
      "title": "Python Development Server Used in a Service",
      "domain": "web",
      "severity": "medium",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L",
      "status": "pass",
      "summary": "Checks whether a service runs a development web server such as manage.py runserver, flask run or python -m http.server.",
      "rationale": "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.",
      "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 \u003cunit\u003e  shows a production server.",
      "refs": [
        {
          "title": "Django documentation, django-admin runserver (not for production)",
          "url": "https://docs.djangoproject.com/en/6.1/ref/django-admin/#runserver"
        },
        {
          "title": "Flask documentation, Deploying to Production",
          "url": "https://flask.palletsprojects.com/en/stable/deploying/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "No service runs a Python development server"
        }
      ]
    },
    {
      "id": "WEB-TLS-001",
      "title": "Site Available Over Plain HTTP Without Redirect",
      "domain": "web",
      "severity": "medium",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "status": "pass",
      "summary": "Checks that every site redirects http:// requests to https://.",
      "rationale": "Pages served over plain HTTP can be read and changed by anyone on the network path, including injected malware and stolen session cookies.",
      "fix": "Apache: in the port 80 vhost add  Redirect permanent / https://\u003csite\u003e/ . nginx: return 301 https://$host$request_uri; . Get a free certificate with certbot if HTTPS is not set up yet.",
      "verify": "curl -sI http://\u003csite\u003e/  should show a 301 with Location: https://...",
      "refs": [
        {
          "title": "OWASP Transport Layer Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-319"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.1.1"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8"
        ],
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/transport.go",
      "academy": [
        {
          "id": "P4",
          "title": "TLS, Certificates and HTTPS",
          "url": "https://openvapt.com/academy/p4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "HTTP redirects to HTTPS",
          "evidence": [
            "https://shop.example.test/"
          ]
        }
      ]
    },
    {
      "id": "WEB-TOM-003",
      "title": "Tomcat Shutdown Port Uses the Default Command",
      "domain": "web",
      "severity": "medium",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "status": "pass",
      "summary": "Checks whether Tomcat's shutdown port is enabled with the well-known default command SHUTDOWN.",
      "rationale": "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.",
      "fix": "Disable the port with \u003cServer port=\"-1\" ...\u003e and stop Tomcat through systemd, or set shutdown to a long random string. Restart Tomcat.",
      "verify": "grep '\u003cServer' \u003cconf\u003e/server.xml  shows port=\"-1\".",
      "refs": [
        {
          "title": "Apache Tomcat 10.1, Security Considerations",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/security-howto.html"
        },
        {
          "title": "Apache Tomcat 10.1 Configuration Reference, The Server Component",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/config/server.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/tomcat.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/tomcat9",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-TOM-005",
      "title": "Tomcat Example and Documentation Applications Deployed",
      "domain": "web",
      "severity": "medium",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether the examples or docs web applications that ship with Tomcat are deployed.",
      "rationale": "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.",
      "fix": "Delete the examples and docs folders from webapps (or apt remove tomcatN-examples tomcatN-docs) and restart Tomcat.",
      "verify": "ls \u003cwebapps\u003e  lists only your own applications (and ROOT).",
      "refs": [
        {
          "title": "Apache Tomcat 10.1, Security Considerations",
          "url": "https://tomcat.apache.org/tomcat-10.1-doc/security-howto.html"
        },
        {
          "title": "OWASP WSTG-CONF-02, Test Application Platform Configuration",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/02-Test_Application_Platform_Configuration/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-02"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/tomcat.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/tomcat9",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "HOST-AUD-001",
      "title": "Audit Daemon Not Enabled at Boot",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the audit daemon (auditd) is set to start at every boot, not only running now.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "auditd(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditd.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12"
        ],
        "cis_topic": "Audit daemon enabled at boot (CIS Controls v8 8.2)",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "auditd",
          "message": "auditd is enabled"
        }
      ]
    },
    {
      "id": "HOST-AUD-002",
      "title": "Identity Files Not Audited",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that changes to the account, group, password and sudo files are recorded by the audit system.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12",
          "AC-2(4)"
        ],
        "cis_topic": "Audit changes to identity files (CIS Controls v8 8.5)",
        "cis_v8": [
          "8.5"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-003",
      "title": "Login and Logout Events Not Audited",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the files recording logins, failed logins and sessions are watched by the audit system.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12",
          "AC-7"
        ],
        "cis_topic": "Audit login, logout and session events (CIS Controls v8 8.5)",
        "cis_v8": [
          "8.5"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-004",
      "title": "System Time Changes Not Audited",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the audit system records changes to the system clock and time zone.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-8",
          "AU-12"
        ],
        "cis_topic": "Audit date and time modification (CIS Controls v8 8.5)",
        "cis_v8": [
          "8.5"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-005",
      "title": "Kernel Module Loading Not Audited",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that loading and unloading kernel modules is recorded by the audit system.",
      "rationale": "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.",
      "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\u003e=1000 -F auid!=unset -k modules , then augenrules --load.",
      "verify": "auditctl -l | grep -E 'init_module|kmod'",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12",
          "CM-7"
        ],
        "cis_topic": "Audit kernel module loading and unloading (CIS Controls v8 8.5)",
        "cis_v8": [
          "8.5"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-006",
      "title": "Privileged Command Use Not Audited",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the audit system records when ordinary users run privileged (setuid or setgid) programs.",
      "rationale": "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.",
      "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\u003e=1000 -F auid!=unset -k privileged\"}' \u003e /etc/audit/rules.d/50-privileged.rules  then augenrules --load.",
      "verify": "auditctl -l | grep 'perm=x'",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12",
          "AC-6(9)"
        ],
        "cis_topic": "Audit use of privileged commands (CIS Controls v8 8.5)",
        "cis_v8": [
          "8.5"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-007",
      "title": "Audit Configuration Not Immutable",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the audit rule set ends with -e 2, which locks the rules until the next reboot.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "auditctl(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditctl.8.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-9",
          "AU-12"
        ],
        "cis_topic": "Audit configuration is immutable (CIS Controls v8 8.2)",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "audit rules",
          "message": "The required audit rules are present in the rule files and auditd is active",
          "evidence": [
            "loaded rules were not verified (auditctl -l is not run); the rule files on disk were read"
          ]
        }
      ]
    },
    {
      "id": "HOST-AUD-008",
      "title": "Audit Log Files Readable by Others",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the audit log directory and files can be read only by root.",
      "rationale": "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.",
      "fix": "chmod 0750 /var/log/audit \u0026\u0026 chmod 0600 /var/log/audit/* \u0026\u0026 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/*",
      "refs": [
        {
          "title": "auditd.conf(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/auditd.conf.5.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-532",
          "CWE-732"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-9",
          "AC-6"
        ],
        "cis_topic": "Audit log files and directory are protected (CIS Controls v8 8.2)",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/log/audit",
          "message": "Audit log directory and files are restricted to root"
        }
      ]
    },
    {
      "id": "HOST-AUD-009",
      "title": "Audit Daemon Ignores a Full Log Disk",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that auditd is told what to do when its log file is full or the disk runs low, rather than silently dropping events.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "auditd.conf(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/auditd.conf.5.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-4",
          "AU-5"
        ],
        "cis_topic": "Audit log storage and full-disk actions (CIS Controls v8 8.3)",
        "cis_v8": [
          "8.3"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/audit/auditd.conf",
          "message": "Full-disk actions are configured"
        }
      ]
    },
    {
      "id": "HOST-AUD-010",
      "title": "Journal Not Persistent or Not Compressed",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that systemd-journald is configured to keep the journal on disk and to compress it.",
      "rationale": "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.",
      "fix": "In /etc/systemd/journald.conf set  Storage=persistent  and  Compress=yes , create the directory with  mkdir -p /var/log/journal \u0026\u0026 systemd-tmpfiles --create --prefix /var/log/journal , then systemctl restart systemd-journald.",
      "verify": "journalctl --header | head  and  ls -d /var/log/journal",
      "refs": [
        {
          "title": "journald.conf(5), systemd",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html"
        },
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-4",
          "AU-9",
          "AU-11"
        ],
        "cis_topic": "journald persistent storage and compression (CIS Controls v8 8.3)",
        "cis_v8": [
          "8.3"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "journald.conf",
          "message": "Storage=persistent, compression on"
        }
      ]
    },
    {
      "id": "HOST-AUD-011",
      "title": "Logs Not Forwarded to a Remote Collector",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that system logs are sent to another machine, so an intruder with root cannot erase the only copy.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "rsyslog.conf(5), Debian trixie manual pages (forwarding actions)",
          "url": "https://manpages.debian.org/trixie/rsyslog/rsyslog.conf.5.en.html"
        },
        {
          "title": "journal-upload.conf(5), systemd",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/journal-upload.conf.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-9(2)",
          "AU-6",
          "SI-4"
        ],
        "cis_topic": "Centralised log collection (CIS Controls v8 8.9)",
        "cis_v8": [
          "8.9"
        ],
        "csf_2": [
          "PR.PS-04",
          "DE.CM-09"
        ]
      },
      "source": "internal/checks/host/audit_journal.go",
      "academy": [
        {
          "id": "P21",
          "title": "Audit Logging and Monitoring Depth",
          "url": "https://openvapt.com/academy/p21.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/rsyslog.d/90-remote.conf",
          "message": "rsyslog forwards logs to a remote host"
        }
      ]
    },
    {
      "id": "HOST-BOOT-005",
      "title": "Kernel Module Loading Unrestricted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L",
      "status": "pass",
      "summary": "Checks that the running kernel refuses unsigned modules or has module loading switched off entirely.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Linux kernel documentation, Kernel module signing facility",
          "url": "https://docs.kernel.org/admin-guide/module-signing.html"
        },
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "SI-7",
          "CM-7"
        ],
        "cis_topic": "Restrict kernel module loading (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel modules",
          "message": "Unsigned modules are refused",
          "evidence": [
            "/sys/module/module/parameters/sig_enforce = Y"
          ]
        }
      ]
    },
    {
      "id": "HOST-BOOT-007",
      "title": "Prelink Installed",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the prelink tool is absent, or disabled where the package is still installed.",
      "rationale": "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.",
      "fix": "Undo and remove it:  prelink -ua \u0026\u0026 apt purge prelink  (or  dnf remove prelink). Then re-initialise the integrity database (aide --init).",
      "verify": "command -v prelink  should print nothing.",
      "refs": [
        {
          "title": "prelink(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/prelink.8.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-344"
        ],
        "nist": [
          "SI-7",
          "CM-7"
        ],
        "cis_topic": "Prelink not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "prelink",
          "message": "Prelink is not installed"
        }
      ]
    },
    {
      "id": "HOST-BOOT-008",
      "title": "Core Dumps Not Restricted",
      "domain": "host",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that process core dumps are turned off through PAM limits, and through systemd-coredump where the kernel hands dumps to it.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "limits.conf(5), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man5/limits.conf.5.html"
        },
        {
          "title": "coredump.conf(5), systemd",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/coredump.conf.html"
        },
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-528"
        ],
        "nist": [
          "SI-11",
          "CM-6",
          "AC-6"
        ],
        "cis_topic": "Restrict core dumps (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/boot.go",
      "academy": [
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "core dumps",
          "message": "Core dumps are restricted",
          "evidence": [
            "/etc/security/limits.conf: * hard core 0",
            "kernel.core_pattern = |/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h",
            "/etc/systemd/coredump.conf: Storage=none or ProcessSizeMax=0"
          ]
        }
      ]
    },
    {
      "id": "HOST-CTR-005",
      "title": "Docker Container Logs Have No Size Limit",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks whether the Docker daemon's default log driver caps log file size and rotation.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Docker documentation, Configure logging drivers",
          "url": "https://docs.docker.com/engine/logging/configure/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-779",
          "CWE-770"
        ],
        "nist": [
          "AU-4",
          "SC-5",
          "CM-6"
        ],
        "cis_topic": "CIS Docker Benchmark 2.12 (centralised and bounded container logging)",
        "cis_v8": [
          "8.3"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/docker/daemon.json",
          "message": "Container logging is bounded or shipped to a log service",
          "evidence": [
            "log-driver json-file, max-size 10m"
          ]
        }
      ]
    },
    {
      "id": "HOST-CTR-006",
      "title": "Docker Userland Proxy Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks whether the Docker daemon still uses the docker-proxy userland process for published ports.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Docker documentation, dockerd daemon reference",
          "url": "https://docs.docker.com/reference/cli/dockerd/"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-6"
        ],
        "cis_topic": "CIS Docker Benchmark 2.14 (disable the userland proxy)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/docker/daemon.json",
          "message": "userland-proxy is false"
        }
      ]
    },
    {
      "id": "HOST-CTR-007",
      "title": "Docker Default Ulimits Not Set",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks whether the Docker daemon sets default ulimits (open files and processes) for containers.",
      "rationale": "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.",
      "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)",
      "refs": [
        {
          "title": "Docker documentation, dockerd daemon reference",
          "url": "https://docs.docker.com/reference/cli/dockerd/"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "nist": [
          "SC-6",
          "CM-6"
        ],
        "cis_topic": "CIS Docker Benchmark 2.7 (default ulimit)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/docker/daemon.json",
          "message": "default-ulimits sets nofile and nproc"
        }
      ]
    },
    {
      "id": "HOST-CTR-015",
      "title": "Docker Content Trust Not Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether DOCKER_CONTENT_TRUST=1 is set system-wide so that only signed images are pulled.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Docker documentation, Content trust in Docker",
          "url": "https://docs.docker.com/engine/security/trust/"
        },
        {
          "title": "CIS Docker Benchmark (referenced by recommendation number)",
          "url": "https://www.cisecurity.org/benchmark/docker"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-494",
          "CWE-347"
        ],
        "nist": [
          "SI-7",
          "CM-14"
        ],
        "cis_topic": "CIS Docker Benchmark 4.5 (enable content trust)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/environment",
          "message": "DOCKER_CONTENT_TRUST=1 is set system-wide"
        }
      ]
    },
    {
      "id": "HOST-CVE-003",
      "title": "Vulnerability Snapshot Outdated or Test Data",
      "domain": "host",
      "severity": "low",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks installed packages against an offline vulnerability snapshot.",
      "rationale": "Known vulnerable package versions can expose a server to documented attacks.",
      "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.",
      "refs": [
        {
          "title": "OSV vulnerability database",
          "url": "https://osv.dev/"
        },
        {
          "title": "NVD",
          "url": "https://nvd.nist.gov/"
        },
        {
          "title": "CISA KEV",
          "url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/cve.go",
      "academy": [
        {
          "id": "P10",
          "title": "Vulnerability Management: CVE, OSV and Known Exploited Vulnerabilities",
          "url": "https://openvapt.com/academy/p10.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "vulnerability snapshot",
          "message": "Snapshot date 2026-10-16. An outdated snapshot misses newer vulnerabilities."
        }
      ]
    },
    {
      "id": "HOST-FS-008",
      "title": "Temporary Filesystems Allow Executables or Devices",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that /tmp, /var/tmp and /dev/shm are mounted with nodev and nosuid.",
      "rationale": "World-writable temporary areas are where attackers drop tools. nosuid stops planted setuid programs working there, and nodev stops device files being created.",
      "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",
      "refs": [
        {
          "title": "mount(8), Linux man-pages (nodev, nosuid, noexec)",
          "url": "https://man7.org/linux/man-pages/man8/mount.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_topic": "Separate partitions and mount options for temporary directories",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/tmp",
          "message": "/tmp has nodev and nosuid",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        },
        {
          "status": "pass",
          "target": "/var/tmp",
          "message": "/var/tmp has nodev and nosuid",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        },
        {
          "status": "pass",
          "target": "/dev/shm",
          "message": "/dev/shm has nodev and nosuid",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-009",
      "title": "Default File Creation Mask Too Permissive",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the default umask makes new files unreadable to other users (027 or stricter).",
      "rationale": "With umask 022 every new file is readable by every local user, including configuration exports, reports and backups created by administrators.",
      "fix": "Set  UMASK 027  in /etc/login.defs (and, if used, in /etc/profile or /etc/bash.bashrc).",
      "verify": "grep '^UMASK' /etc/login.defs",
      "refs": [
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        },
        {
          "title": "umask(2), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man2/umask.2.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Default user umask",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/login.defs",
          "message": "UMASK 027"
        }
      ]
    },
    {
      "id": "HOST-FS-010",
      "title": "/var/tmp Not a Separate Filesystem",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L",
      "status": "pass",
      "summary": "Checks that /var/tmp is its own mount (or a bind mount of /tmp) with nodev, nosuid and noexec.",
      "rationale": "/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.",
      "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",
      "refs": [
        {
          "title": "mount(8), Linux man-pages (nodev, nosuid, noexec)",
          "url": "https://man7.org/linux/man-pages/man8/mount.8.html"
        },
        {
          "title": "proc(5), Linux man-pages (/proc/[pid]/mounts)",
          "url": "https://man7.org/linux/man-pages/man5/proc.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "CM-6",
          "CM-7",
          "SC-5"
        ],
        "cis_topic": "Separate partitions and mount options (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/tmp",
          "message": "/var/tmp is a separate mount",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-011",
      "title": "/home Not a Separate Filesystem With nodev",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L",
      "status": "pass",
      "summary": "Checks that user home directories live on their own mount with nodev (and nosuid where practical).",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "mount(8), Linux man-pages (nodev, nosuid, noexec)",
          "url": "https://man7.org/linux/man-pages/man8/mount.8.html"
        },
        {
          "title": "proc(5), Linux man-pages (/proc/[pid]/mounts)",
          "url": "https://man7.org/linux/man-pages/man5/proc.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "CM-6",
          "CM-7",
          "SC-5"
        ],
        "cis_topic": "Separate partitions and mount options (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/home",
          "message": "/home is a separate mount",
          "evidence": [
            "options rw,nosuid,nodev"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-012",
      "title": "Log Directories on the Root Filesystem",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks that /var/log (or /var) is a separate filesystem so runaway logging cannot fill the root disk.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "mount(8), Linux man-pages (nodev, nosuid, noexec)",
          "url": "https://man7.org/linux/man-pages/man8/mount.8.html"
        },
        {
          "title": "proc(5), Linux man-pages (/proc/[pid]/mounts)",
          "url": "https://man7.org/linux/man-pages/man5/proc.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "nist": [
          "CM-6",
          "CM-7",
          "SC-5"
        ],
        "cis_topic": "Separate partitions and mount options (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/log",
          "message": "/var/log is a separate mount",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-013",
      "title": "Temporary Filesystems Allow Executables",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that /tmp, /var/tmp and /dev/shm, where they are separate mounts, carry the noexec option.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "mount(8), Linux man-pages (nodev, nosuid, noexec)",
          "url": "https://man7.org/linux/man-pages/man8/mount.8.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_topic": "noexec on temporary filesystems (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/tmp",
          "message": "/tmp is mounted noexec",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        },
        {
          "status": "pass",
          "target": "/var/tmp",
          "message": "/var/tmp is mounted noexec",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        },
        {
          "status": "pass",
          "target": "/dev/shm",
          "message": "/dev/shm is mounted noexec",
          "evidence": [
            "options rw,nosuid,nodev,noexec"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-015",
      "title": "Files With No Known Owner or Group",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Looks for files whose owner or group id no longer appears in /etc/passwd or /etc/group.",
      "rationale": "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.",
      "fix": "Decide who should own each listed file and apply  chown \u003cuser\u003e:\u003cgroup\u003e \u003cpath\u003e, 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.",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        },
        {
          "title": "passwd(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/passwd.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-282"
        ],
        "nist": [
          "AC-2",
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "No unowned or ungrouped files (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "filesystem",
          "message": "Every one of 43 files has a known owner and group"
        }
      ]
    },
    {
      "id": "HOST-FS-018",
      "title": "Kernel Symbol Map or Initial RAM Disk Readable by Others",
      "domain": "host",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that /boot/System.map-* and the initial RAM disk images are not readable by every user.",
      "rationale": "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.",
      "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*",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497",
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_topic": "Boot file permissions (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/boot",
          "message": "1 boot file(s) are private to root"
        }
      ]
    },
    {
      "id": "HOST-FS-019",
      "title": "Cron and At Not Restricted to Allowed Users",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that cron.allow and at.allow exist, are private to root, and that the deny lists are not relied upon instead.",
      "rationale": "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.",
      "fix": "Create the allow lists:  touch /etc/cron.allow /etc/at.allow \u0026\u0026 chmod 600 /etc/cron.allow /etc/at.allow \u0026\u0026 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\u003e\u00261",
      "refs": [
        {
          "title": "crontab(1), Linux man-pages (cron.allow and cron.deny)",
          "url": "https://man7.org/linux/man-pages/man1/crontab.1.html"
        },
        {
          "title": "at(1), Linux man-pages (at.allow and at.deny)",
          "url": "https://man7.org/linux/man-pages/man1/at.1.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-862"
        ],
        "nist": [
          "AC-6",
          "CM-7"
        ],
        "cis_topic": "Restrict cron and at to authorised users (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/cron.allow",
          "message": "/etc/cron.allow restricts cron to listed users",
          "evidence": [
            "mode 0600"
          ]
        },
        {
          "status": "pass",
          "target": "/etc/at.allow",
          "message": "/etc/at.allow restricts at to listed users",
          "evidence": [
            "mode 0600"
          ]
        }
      ]
    },
    {
      "id": "HOST-FS-020",
      "title": "Cron Configuration Readable by Other Users",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "fix": "chown root:root /etc/crontab /etc/cron.* \u0026\u0026 chmod 600 /etc/crontab \u0026\u0026 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.*",
      "refs": [
        {
          "title": "crontab(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/crontab.5.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_topic": "Cron file permissions (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "cron",
          "message": "1 cron path(s) are private to root"
        }
      ]
    },
    {
      "id": "HOST-FS-021",
      "title": "Shell Profile Sets a Permissive File Creation Mask",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that system-wide shell start-up files do not set a umask that leaves new files readable by every user.",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "umask(2), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man2/umask.2.html"
        },
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-276"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Default user umask in shell start-up files (CIS Controls v8 3.3)",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/files_mounts.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/profile",
          "message": "Shell start-up files set a restrictive umask",
          "evidence": [
            "/etc/profile: umask 027"
          ]
        }
      ]
    },
    {
      "id": "HOST-IDS-001",
      "title": "No Brute-Force Protection Service",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that a log-based intrusion prevention service (fail2ban, sshguard or CrowdSec) is running.",
      "rationale": "Internet-facing services receive constant guessing. Automatically blocking sources that fail repeatedly reduces noise and risk, and turns logs into active defence.",
      "fix": "Install and enable one, for example: apt install fail2ban \u0026\u0026 systemctl enable --now fail2ban , with an sshd jail enabled.",
      "verify": "systemctl is-active fail2ban",
      "refs": [
        {
          "title": "fail2ban project",
          "url": "https://github.com/fail2ban/fail2ban"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "SI-4",
          "AC-7"
        ],
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fail2ban",
          "message": "fail2ban is active"
        }
      ]
    },
    {
      "id": "HOST-KRN-002",
      "title": "Kernel Pointers Exposed to Unprivileged Users",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that kernel memory addresses are hidden from normal users.",
      "rationale": "Leaked kernel addresses defeat kernel ASLR and are the first step of most local kernel exploits.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "nist": [
          "CM-6",
          "SI-16"
        ],
        "cis_topic": "Kernel parameters: kernel.kptr_restrict",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.kptr_restrict",
          "message": "kernel.kptr_restrict = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-003",
      "title": "Kernel Log Readable by Unprivileged Users",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that only privileged users can read the kernel log (dmesg).",
      "rationale": "The kernel log can reveal addresses, hardware and driver details useful for local exploitation.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: kernel.dmesg_restrict",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.dmesg_restrict",
          "message": "kernel.dmesg_restrict = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-004",
      "title": "Setuid Programs May Dump Core",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that privileged programs do not write core dumps.",
      "rationale": "A core dump of a setuid program can contain password hashes or keys and may be readable by the user who crashed it.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/fs",
          "url": "https://docs.kernel.org/admin-guide/sysctl/fs.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-528"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: fs.suid_dumpable",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fs.suid_dumpable",
          "message": "fs.suid_dumpable = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-005",
      "title": "IP Forwarding Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the server does not route packets between networks.",
      "rationale": "A server that forwards packets can be used to pivot between networks. Only routers, VPN gateways and container hosts need this.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.ip_forward",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.ip_forward",
          "message": "net.ipv4.ip_forward = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-006",
      "title": "ICMP Redirects Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel ignores ICMP redirect messages.",
      "rationale": "Forged ICMP redirects let an attacker on the local network reroute the server's traffic through themselves.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.accept_redirects",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.accept_redirects",
          "message": "net.ipv4.conf.all.accept_redirects = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-007",
      "title": "Source-Routed Packets Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel drops packets that dictate their own route.",
      "rationale": "Source routing lets an attacker bypass network filtering and spoof trusted addresses.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.accept_source_route",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.accept_source_route",
          "message": "net.ipv4.conf.all.accept_source_route = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-008",
      "title": "TCP SYN Cookies Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks that SYN cookies protect the server from SYN flood attacks.",
      "rationale": "Without SYN cookies a simple flood of half-open connections can make every service unreachable.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "nist": [
          "SC-5",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.tcp_syncookies",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.tcp_syncookies",
          "message": "net.ipv4.tcp_syncookies = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-010",
      "title": "Process Tracing Not Restricted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that processes cannot attach a debugger to other processes of the same user.",
      "rationale": "Unrestricted ptrace lets any compromised process read the memory of others, for example to steal SSH agent keys or browser sessions.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, Yama LSM",
          "url": "https://docs.kernel.org/admin-guide/LSM/Yama.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: kernel.yama.ptrace_scope",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.yama.ptrace_scope",
          "message": "kernel.yama.ptrace_scope = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-011",
      "title": "Symlink Protection Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the kernel blocks following symlinks owned by other users in world-writable sticky directories.",
      "rationale": "Without it, classic /tmp symlink races let local users trick root-run programs into overwriting files.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/fs",
          "url": "https://docs.kernel.org/admin-guide/sysctl/fs.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-59"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: fs.protected_symlinks",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fs.protected_symlinks",
          "message": "fs.protected_symlinks = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-012",
      "title": "Hardlink Protection Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that users cannot create hard links to files they do not own.",
      "rationale": "Hard links to privileged files enable time-of-check races and keep vulnerable setuid binaries alive after updates.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/fs",
          "url": "https://docs.kernel.org/admin-guide/sysctl/fs.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-59"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: fs.protected_hardlinks",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fs.protected_hardlinks",
          "message": "fs.protected_hardlinks = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-013",
      "title": "Reverse Path Filtering Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel drops packets whose source address could not be routed back through the receiving interface.",
      "rationale": "Reverse path filtering blocks many spoofed-source packets used in attacks and amplification.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.rp_filter",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.rp_filter",
          "message": "net.ipv4.conf.all.rp_filter = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-014",
      "title": "Martian Packets Not Logged",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that packets with impossible source addresses are logged.",
      "rationale": "Logging martian packets helps detect spoofing and misrouting attempts.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.log_martians",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.log_martians",
          "message": "net.ipv4.conf.all.log_martians = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-015",
      "title": "Server Sends ICMP Redirects",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the server does not send ICMP redirects, which only routers should do.",
      "rationale": "A non-router sending redirects can be abused to influence other hosts' routing on the local network.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.send_redirects",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.send_redirects",
          "message": "net.ipv4.conf.all.send_redirects = 0 for all, default and every interface (1 settings)"
        }
      ]
    },
    {
      "id": "HOST-KRN-016",
      "title": "Broadcast ICMP Echo Answered",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks that the server ignores ping requests sent to broadcast addresses.",
      "rationale": "Answering broadcast pings makes the server a participant in Smurf-style amplification attacks.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-406"
        ],
        "nist": [
          "SC-5",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.icmp_echo_ignore_broadcasts",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.icmp_echo_ignore_broadcasts",
          "message": "net.ipv4.icmp_echo_ignore_broadcasts = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-017",
      "title": "IPv6 ICMP Redirects Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel ignores IPv6 ICMP redirect messages.",
      "rationale": "Forged IPv6 redirects let an attacker on the local network reroute traffic through themselves.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv6.conf.all.accept_redirects",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv6.conf.all.accept_redirects",
          "message": "net.ipv6.conf.all.accept_redirects = 0 for all, default and every interface (1 settings)"
        }
      ]
    },
    {
      "id": "HOST-KRN-018",
      "title": "Performance Events Open to Unprivileged Users",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that unprivileged users cannot use kernel performance events to observe the system.",
      "rationale": "Unrestricted perf events expose kernel and other processes' activity and have been used in local exploits.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/kernel",
          "url": "https://docs.kernel.org/admin-guide/sysctl/kernel.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "CM-6",
          "SI-16"
        ],
        "cis_topic": "Kernel parameters: kernel.perf_event_paranoid",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.perf_event_paranoid",
          "message": "kernel.perf_event_paranoid = 3"
        }
      ]
    },
    {
      "id": "HOST-KRN-019",
      "title": "Magic SysRq Key Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L",
      "status": "pass",
      "summary": "Checks that the magic SysRq key combination is disabled or limited to keyboard synchronisation.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, Linux Magic System Request Key Hacks",
          "url": "https://docs.kernel.org/admin-guide/sysrq.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: kernel.sysrq",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.sysrq",
          "message": "kernel.sysrq = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-020",
      "title": "FIFO Protection in Shared Directories Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the kernel refuses to open FIFOs in world-writable sticky directories that another user owns.",
      "rationale": "A FIFO planted in /tmp can make a privileged program block or write into an attacker-controlled pipe, a classic step in local attacks.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/fs",
          "url": "https://docs.kernel.org/admin-guide/sysctl/fs.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-59"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: fs.protected_fifos",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fs.protected_fifos",
          "message": "fs.protected_fifos = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-021",
      "title": "Regular File Protection in Shared Directories Disabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the kernel refuses to open, with O_CREAT, regular files in world-writable sticky directories that another user owns.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/fs",
          "url": "https://docs.kernel.org/admin-guide/sysctl/fs.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-59"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: fs.protected_regular",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "fs.protected_regular",
          "message": "fs.protected_regular = 2"
        }
      ]
    },
    {
      "id": "HOST-KRN-022",
      "title": "Unprivileged User Namespaces Allowed",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that normal users cannot create user namespaces, unless the server runs containers that need them.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "user_namespaces(7), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man7/user_namespaces.7.html"
        },
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_topic": "Kernel parameters: user namespaces (CIS Controls v8 4.1)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.unprivileged_userns_clone",
          "message": "kernel.unprivileged_userns_clone = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-023",
      "title": "IPv6 Source-Routed Packets Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel drops IPv6 packets that dictate their own route.",
      "rationale": "IPv6 routing headers can be used to bypass filters and spoof trusted addresses, as source routing could in IPv4.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv6.conf.all.accept_source_route",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv6.conf.all.accept_source_route",
          "message": "net.ipv6.conf.all.accept_source_route = 0 for all, default and every interface (1 settings)"
        }
      ]
    },
    {
      "id": "HOST-KRN-024",
      "title": "Secure ICMP Redirects Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the kernel also ignores ICMP redirects that come from its configured gateways.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.conf.all.secure_redirects",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.conf.all.secure_redirects",
          "message": "net.ipv4.conf.all.secure_redirects = 0 for all, default and every interface (1 settings)"
        }
      ]
    },
    {
      "id": "HOST-KRN-025",
      "title": "IPv6 Router Advertisements Accepted",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the server does not reconfigure its IPv6 addresses and routes from router advertisements.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-940"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv6.conf.all.accept_ra",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv6.conf.all.accept_ra",
          "message": "net.ipv6.conf.all.accept_ra = 0 for all, default and every interface (1 settings)"
        }
      ]
    },
    {
      "id": "HOST-KRN-026",
      "title": "Bogus ICMP Error Responses Logged",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks that the kernel silently ignores malformed ICMP error responses instead of logging each one.",
      "rationale": "Logging every bogus ICMP error lets a remote sender fill the kernel log and disk at will, hiding real events among noise.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, IP sysctl",
          "url": "https://docs.kernel.org/networking/ip-sysctl.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-779"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.ipv4.icmp_ignore_bogus_error_responses",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.ipv4.icmp_ignore_bogus_error_responses",
          "message": "net.ipv4.icmp_ignore_bogus_error_responses = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-027",
      "title": "Kexec Kernel Replacement Allowed",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:L",
      "status": "pass",
      "summary": "Checks that a running kernel cannot be replaced at run time with kexec.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: kernel.kexec_load_disabled",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.kexec_load_disabled",
          "message": "kernel.kexec_load_disabled = 1"
        }
      ]
    },
    {
      "id": "HOST-KRN-028",
      "title": "Unprivileged userfaultfd Allowed",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that only privileged processes can use userfaultfd.",
      "rationale": "userfaultfd lets a process pause the kernel at a chosen moment, which has turned many kernel race conditions into reliable local exploits.",
      "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.",
      "refs": [
        {
          "title": "Linux kernel documentation, /proc/sys/vm",
          "url": "https://docs.kernel.org/admin-guide/sysctl/vm.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: vm.unprivileged_userfaultfd",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "vm.unprivileged_userfaultfd",
          "message": "vm.unprivileged_userfaultfd = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-029",
      "title": "TTY Line Discipline Autoloading Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that unprivileged users cannot make the kernel load terminal line discipline modules.",
      "rationale": "Automatically loaded line disciplines have carried several exploitable kernel bugs; disabling autoload shrinks the kernel attack surface reachable from a normal shell.",
      "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.",
      "refs": [
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: dev.tty.ldisc_autoload",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "dev.tty.ldisc_autoload",
          "message": "dev.tty.ldisc_autoload = 0"
        }
      ]
    },
    {
      "id": "HOST-KRN-030",
      "title": "BPF JIT Hardening Off",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the BPF just-in-time compiler blinds constants in generated code for every user.",
      "rationale": "Without hardening, attacker-chosen constants in BPF programs land in executable kernel memory, a known technique for building kernel exploit payloads.",
      "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.",
      "refs": [
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "SC-7",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: net.core.bpf_jit_harden",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "net.core.bpf_jit_harden",
          "message": "net.core.bpf_jit_harden = 2"
        }
      ]
    },
    {
      "id": "HOST-KRN-031",
      "title": "io_uring Open to Unprivileged Users",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that io_uring is disabled, or limited to privileged processes, on kernels that offer the setting.",
      "rationale": "io_uring has produced a long run of local privilege-escalation bugs; several large operators disable it for untrusted workloads.",
      "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.",
      "refs": [
        {
          "title": "Kernel Self Protection Project, Recommended Settings",
          "url": "https://kspp.github.io/Recommended_Settings"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-749"
        ],
        "nist": [
          "AC-6",
          "CM-6"
        ],
        "cis_topic": "Kernel parameters: kernel.io_uring_disabled",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/kernel.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P17",
          "title": "Kernel, Filesystem and Boot Hardening",
          "url": "https://openvapt.com/academy/p17.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernel.io_uring_disabled",
          "message": "kernel.io_uring_disabled = 2"
        }
      ]
    },
    {
      "id": "HOST-LOG-001",
      "title": "System Logging Not Persistent",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that system logs survive a reboot (rsyslog/syslog-ng running, or a persistent journal).",
      "rationale": "Without persistent logs there is no evidence after an incident: no record of logins, sudo use, or service errors.",
      "fix": "Either install and enable rsyslog, or make the journal persistent: mkdir -p /var/log/journal \u0026\u0026 systemd-tmpfiles --create --prefix /var/log/journal \u0026\u0026 systemctl restart systemd-journald",
      "verify": "ls -d /var/log/journal  or  systemctl is-active rsyslog",
      "refs": [
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        },
        {
          "title": "journald.conf(5)",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-9",
          "AU-11"
        ],
        "cis_topic": "Logging: persistent system logs",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/logging_time.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        },
        {
          "id": "A4",
          "title": "Building Your Own Check",
          "url": "https://openvapt.com/academy/a4.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "logging",
          "message": "rsyslog is active"
        }
      ]
    },
    {
      "id": "HOST-LOG-002",
      "title": "Security Audit Daemon Not Running",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether the Linux audit daemon (auditd) is running.",
      "rationale": "auditd records security-relevant events (changes to /etc/passwd, privilege use, file deletions) that ordinary logs miss, which incident responders rely on.",
      "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'.",
      "refs": [
        {
          "title": "auditd(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/auditd.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12"
        ],
        "cis_topic": "Logging and auditing: auditd",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/logging_time.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "auditd",
          "message": "auditd is active"
        }
      ]
    },
    {
      "id": "HOST-LOG-003",
      "title": "Security Logs Readable by All Users",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that authentication and system logs are not world-readable.",
      "rationale": "Authentication logs contain user names, source addresses and sometimes passwords typed into the user name field. Only administrators should read them.",
      "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",
      "refs": [
        {
          "title": "NIST SP 800-92, Guide to Computer Security Log Management",
          "url": "https://csrc.nist.gov/pubs/sp/800/92/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-532"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/log/auth.log",
          "message": "1 file(s) have safe permissions"
        }
      ]
    },
    {
      "id": "HOST-LOG-004",
      "title": "Log Rotation Not Configured",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks that logrotate (or journald size limits) keeps logs from filling the disk.",
      "rationale": "Unrotated logs eventually fill the disk and stop services, and an attacker can deliberately flood logs to cause that or to hide activity.",
      "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",
      "refs": [
        {
          "title": "logrotate(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/logrotate.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "nist": [
          "AU-4",
          "AU-11"
        ],
        "cis_v8": [
          "8.3"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/logrotate.conf",
          "message": "logrotate is configured"
        }
      ]
    },
    {
      "id": "HOST-MAC-001",
      "title": "No Mandatory Access Control Enforced",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that AppArmor or SELinux is enabled and enforcing.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Linux kernel documentation, Linux Security Module development and LSMs",
          "url": "https://docs.kernel.org/admin-guide/LSM/index.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-3(3)",
          "AC-6"
        ],
        "cis_topic": "Mandatory access control",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "apparmor",
          "message": "AppArmor is enabled"
        }
      ]
    },
    {
      "id": "HOST-PAM-001",
      "title": "Password Quality Module Not Enforced",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that new passwords pass through pam_pwquality with its dictionary check on, so dictionary words and user-name variants are rejected.",
      "rationale": "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.",
      "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\u003e/dev/null; grep -rE '^(dictcheck|usercheck|enforcing)' /etc/security/pwquality.conf /etc/security/pwquality.conf.d/",
      "refs": [
        {
          "title": "pwquality.conf(5), libpwquality, Debian trixie manual pages",
          "url": "https://manpages.debian.org/trixie/libpwquality-common/pwquality.conf.5.en.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-521"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password creation requirements enforced through PAM (CIS Controls v8 5.2)",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/pam.d/common-password",
          "message": "pam_pwquality is active with dictionary and user-name checks"
        }
      ]
    },
    {
      "id": "HOST-PAM-002",
      "title": "Account Lockout Releases Too Quickly",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that a locked account stays locked for at least 15 minutes (or until an administrator unlocks it).",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "pam_faillock(8), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man8/pam_faillock.8.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "AC-7"
        ],
        "cis_topic": "Lockout duration for failed password attempts (CIS Controls v8 5.2)",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/security/faillock.conf",
          "message": "unlock_time=900"
        }
      ]
    },
    {
      "id": "HOST-PAM-003",
      "title": "su Not Restricted to an Administrative Group",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that only members of a named group (wheel or sudo) may use su to become another user.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "pam_wheel(8), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man8/pam_wheel.8.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-862"
        ],
        "nist": [
          "AC-6(2)",
          "AC-3"
        ],
        "cis_topic": "Access to the su command is restricted (CIS Controls v8 5.4)",
        "cis_v8": [
          "5.4"
        ],
        "csf_2": [
          "PR.AA-05"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/pam.d/su",
          "message": "su is restricted by pam_wheel: auth required pam_wheel.so use_uid group=sudo"
        }
      ]
    },
    {
      "id": "HOST-PAM-004",
      "title": "Password History Not Enforced",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that PAM remembers at least the last 5 passwords so a changed password cannot be changed straight back.",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "pam_pwhistory(8), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man8/pam_pwhistory.8.html"
        },
        {
          "title": "pam_unix(8), Linux-PAM",
          "url": "https://man7.org/linux/man-pages/man8/pam_unix.8.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-262"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password reuse is limited (CIS Controls v8 5.2)",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/pam.d/common-password",
          "message": "remember=5"
        }
      ]
    },
    {
      "id": "HOST-PAM-005",
      "title": "Minimum Password Age Not Set",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that a password cannot be changed again on the same day it was set.",
      "rationale": "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.",
      "fix": "Set  PASS_MIN_DAYS 1  in /etc/login.defs, and for existing accounts  chage --mindays 1 \u003cuser\u003e .",
      "verify": "grep '^PASS_MIN_DAYS' /etc/login.defs",
      "refs": [
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-262"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Minimum days between password changes (CIS Controls v8 5.2)",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/login.defs",
          "message": "PASS_MIN_DAYS 1"
        }
      ]
    },
    {
      "id": "HOST-PAM-007",
      "title": "Inactive Accounts Never Locked",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that new accounts are set to lock automatically after a password has been expired and unused for at most 30 days.",
      "rationale": "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.",
      "fix": "useradd -D -f 30  (writes INACTIVE=30 to /etc/default/useradd), and for existing accounts  chage --inactive 30 \u003cuser\u003e . 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",
      "refs": [
        {
          "title": "useradd(8), Linux man-pages (default INACTIVE value)",
          "url": "https://man7.org/linux/man-pages/man8/useradd.8.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-262"
        ],
        "nist": [
          "AC-2(3)"
        ],
        "cis_topic": "Inactive password lock (CIS Controls v8 5.3)",
        "cis_v8": [
          "5.3"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/default/useradd",
          "message": "INACTIVE=30"
        }
      ]
    },
    {
      "id": "HOST-PAM-009",
      "title": "No Shell Idle Timeout",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that interactive shells log out after at most 15 minutes without input (TMOUT).",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "Bash Reference Manual, Bash Variables (TMOUT)",
          "url": "https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html"
        },
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-613"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "AC-11",
          "AC-12"
        ],
        "cis_topic": "Default user shell timeout (CIS Controls v8 4.3)",
        "cis_v8": [
          "4.3"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/profile.d/99-tmout.sh",
          "message": "TMOUT=900"
        }
      ]
    },
    {
      "id": "HOST-PKG-004",
      "title": "Many Old Kernels Retained",
      "domain": "host",
      "severity": "low",
      "category": "patch",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that no more than three kernel versions are installed.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        },
        {
          "title": "apt-get(8), Debian trixie manual pages (autoremove)",
          "url": "https://manpages.debian.org/trixie/apt/apt-get.8.en.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "CM-7"
        ],
        "cis_topic": "Remove unneeded software versions",
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/pkg_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "kernels",
          "message": "2 kernel version(s) installed",
          "evidence": [
            "6.8.0-40-generic",
            "6.8.0-45-generic"
          ]
        }
      ]
    },
    {
      "id": "HOST-PKG-006",
      "title": "Package Lists Not Refreshed Automatically",
      "domain": "host",
      "severity": "low",
      "category": "patch",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the package manager downloads fresh repository metadata on a schedule, so pending updates can be seen at all.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "Debian, UnattendedUpgrades (APT::Periodic settings)",
          "url": "https://wiki.debian.org/UnattendedUpgrades"
        },
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2",
          "RA-5"
        ],
        "cis_topic": "Automated patch management (CIS Controls v8 7.4)",
        "cis_v8": [
          "7.4"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/pkg_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apt/apt.conf.d/20auto-upgrades",
          "message": "APT::Periodic::Update-Package-Lists is 1"
        }
      ]
    },
    {
      "id": "HOST-PWD-003",
      "title": "Short Minimum Password Length",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the password quality policy requires at least 15 characters.",
      "rationale": "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.",
      "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/",
      "refs": [
        {
          "title": "pwquality.conf(5), libpwquality, Debian trixie manual pages",
          "url": "https://manpages.debian.org/trixie/libpwquality-common/pwquality.conf.5.en.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-521"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password creation requirements",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/security/pwquality.conf",
          "message": "minlen = 15"
        }
      ]
    },
    {
      "id": "HOST-SSH-005",
      "title": "SSH X11 Forwarding Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether SSH forwards graphical X11 sessions.",
      "rationale": "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.",
      "fix": "Set  X11Forwarding no  in /etc/ssh/sshd_config and reload sshd.",
      "verify": "sshd -T | grep -i x11forwarding  should print 'no'.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_topic": "SSH server configuration: X11 forwarding",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_misc.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "X11 forwarding is disabled"
        }
      ]
    },
    {
      "id": "HOST-SSH-006",
      "title": "SSH Allows Many Authentication Attempts",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether MaxAuthTries is above 4.",
      "rationale": "Each connection may try this many passwords or keys. A low limit slows guessing and makes attacks show up in the logs sooner.",
      "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.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307"
        ],
        "nist": [
          "AC-7"
        ],
        "cis_topic": "SSH server configuration: MaxAuthTries",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_misc.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "MaxAuthTries is 3"
        }
      ]
    },
    {
      "id": "HOST-SSH-008",
      "title": "SSH Authentication Controls Too Permissive",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks effective SSH authentication method and login grace settings.",
      "rationale": "Long login windows and unconstrained single-factor authentication give online guessing and stolen credentials more opportunity.",
      "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.",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307",
          "CWE-308"
        ],
        "nist": [
          "AC-7",
          "IA-2"
        ],
        "cis_topic": "SSH server configuration: authentication restrictions",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/ssh_forwarding.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "SSH authentication restrictions are configured"
        }
      ]
    },
    {
      "id": "HOST-SSH-012",
      "title": "SSH Logging Level Too Low",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that sshd logs at INFO or VERBOSE level.",
      "rationale": "QUIET, FATAL or ERROR levels drop login records, so failed and successful logins cannot be investigated. VERBOSE also records the key fingerprint used.",
      "fix": "Set  LogLevel VERBOSE  (or INFO) in /etc/ssh/sshd_config and reload sshd.",
      "verify": "sshd -T | grep -i loglevel",
      "refs": [
        {
          "title": "OpenBSD, sshd_config(5) manual page",
          "url": "https://man.openbsd.org/sshd_config"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-3"
        ],
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sshd",
          "message": "LogLevel verbose"
        }
      ]
    },
    {
      "id": "HOST-SSH-013",
      "title": "SSH Authorized Keys Without Restrictions or Protection",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks root's authorized_keys for keys without from= or command= options, and every authorized_keys file for permissive modes.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "sshd(8), AUTHORIZED_KEYS FILE FORMAT, OpenBSD manual pages",
          "url": "https://man.openbsd.org/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT"
        },
        {
          "title": "NIST IR 7966, Security of Interactive and Automated Access Management Using Secure Shell (SSH)",
          "url": "https://csrc.nist.gov/pubs/ir/7966/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-250"
        ],
        "nist": [
          "IA-5",
          "AC-6",
          "AC-17"
        ],
        "cis_topic": "SSH key management and file permissions",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P2",
          "title": "SSH Security",
          "url": "https://openvapt.com/academy/p2.html"
        },
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "authorized_keys",
          "message": "1 authorized_keys file(s) are protected and root keys carry restrictions"
        }
      ]
    },
    {
      "id": "HOST-SUDO-001",
      "title": "Sudo Commands Not Logged to a Dedicated File",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that sudo uses a pseudo-terminal and keeps its own log file.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "sudoers(5) manual",
          "url": "https://www.sudo.ws/docs/man/sudoers.man/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "nist": [
          "AU-2",
          "AU-12"
        ],
        "cis_topic": "Sudo uses pty and log file",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        },
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/sudoers",
          "message": "sudo uses a pty and a dedicated log file"
        }
      ]
    },
    {
      "id": "HOST-SVC-001",
      "title": "Unnecessary Legacy Services Running",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks for desktop and legacy network services that servers rarely need.",
      "rationale": "Each running service adds attack surface. Zeroconf discovery, printing, RPC portmapping and inetd-style super-servers are common, long-forgotten exposures.",
      "fix": "systemctl disable --now \u003cservice\u003e  for each listed service you do not need, or remove its package. Caution: check first that nothing depends on it (systemctl list-dependencies --reverse \u003cservice\u003e) and keep console access when disabling network or remote-access services.",
      "verify": "systemctl is-active avahi-daemon cups rpcbind xinetd",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_topic": "Remove or disable unnecessary services",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "No unnecessary legacy services are active"
        }
      ]
    },
    {
      "id": "HOST-SVC-007",
      "title": "Avahi or CUPS Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether zero-configuration discovery (Avahi) or the printing system (CUPS) is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled avahi-daemon.service avahi-daemon.socket cups.service cups.socket cups-browsed.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "Avahi and CUPS are not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-008",
      "title": "NFS Server or rpcbind Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether the NFS server or the RPC port mapper (rpcbind) is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled nfs-server.service rpcbind.service rpcbind.socket nfs-kernel-server.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NFS security, Linux kernel documentation (exports and rpcbind)",
          "url": "https://docs.kernel.org/filesystems/nfs/index.html"
        },
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-306"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "NFS and rpcbind are not installed unless required (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-009",
      "title": "DHCP or DNS Server Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled isc-dhcp-server.service dhcpd.service kea-dhcp4-server.service named.service bind9.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "DHCP and DNS servers are not installed unless required (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-010",
      "title": "Samba File Server Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether the Samba SMB file server is installed or set to start at boot.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled smbd.service nmbd.service smb.service nmb.service samba.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "Samba is not installed unless required (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-011",
      "title": "SNMP Agent or Squid Proxy Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether an SNMP agent or the Squid caching proxy is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled snmpd.service squid.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-319"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "SNMP and proxy servers are not installed unless required (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SVC-012",
      "title": "inetd Super-Server Installed or Enabled",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether xinetd or another inetd super-server is installed or enabled.",
      "rationale": "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.",
      "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\u003e/dev/null | grep '^ii' ; systemctl is-enabled xinetd.service inetd.service openbsd-inetd.service inetutils-inetd.service 2\u003e/dev/null",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "CM-7",
          "CM-7(1)"
        ],
        "cis_topic": "xinetd and inetd are not installed (CIS Controls v8 4.8)",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/host/legacy_services.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "services",
          "message": "Not installed and not enabled"
        }
      ]
    },
    {
      "id": "HOST-SYSD-002",
      "title": "Custom Services Lack Sandboxing",
      "domain": "host",
      "severity": "low",
      "category": "config",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether custom long-running services use systemd's basic sandboxing options.",
      "rationale": "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.",
      "fix": "Add the missing lines with systemctl edit \u003cunit\u003e (a drop-in), for example NoNewPrivileges=yes, ProtectSystem=strict, ReadWritePaths=/var/lib/\u003capp\u003e, PrivateTmp=yes, ProtectHome=yes. Restart the service and test it; add ReadWritePaths= for every folder the app must write. systemd-analyze security \u003cunit\u003e suggests further options.",
      "verify": "systemd-analyze security \u003cunit\u003e  shows a lower exposure score, and the service still works.",
      "refs": [
        {
          "title": "systemd.exec(5), execution environment configuration",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html"
        },
        {
          "title": "systemd-analyze(1), security verb",
          "url": "https://www.freedesktop.org/software/systemd/man/latest/systemd-analyze.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250",
          "CWE-653"
        ],
        "nist": [
          "CM-6",
          "SC-39",
          "AC-6"
        ],
        "cis_topic": "Service isolation",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/systemd.go",
      "academy": [
        {
          "id": "P11",
          "title": "Service Hardening With systemd",
          "url": "https://openvapt.com/academy/p11.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "Custom services use the basic sandboxing options"
        }
      ]
    },
    {
      "id": "HOST-TIME-001",
      "title": "Time Synchronisation Not Active",
      "domain": "host",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "Checks that the clock is kept in sync (chrony, ntpd or systemd-timesyncd).",
      "rationale": "Accurate time is needed to correlate logs during an investigation, and TLS certificates and one-time-password codes fail when the clock drifts.",
      "fix": "Enable one time service, for example: systemctl enable --now systemd-timesyncd  (or install chrony).",
      "verify": "timedatectl  should show 'System clock synchronized: yes'.",
      "refs": [
        {
          "title": "NIST SP 800-53 Rev. 5, Security and Privacy Controls",
          "url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "nist": [
          "AU-8"
        ],
        "cis_topic": "Time synchronisation",
        "cis_v8": [
          "8.4"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/host/logging_time.go",
      "academy": [
        {
          "id": "P16",
          "title": "Logging, Auditing and Time",
          "url": "https://openvapt.com/academy/p16.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "time",
          "message": "chronyd is active"
        }
      ]
    },
    {
      "id": "HOST-UPD-001",
      "title": "Automatic Security Updates Not Configured",
      "domain": "host",
      "severity": "low",
      "category": "patch",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that security updates are installed automatically.",
      "rationale": "Many compromises use vulnerabilities fixed days or weeks earlier. Automatic security updates close that window without relying on someone remembering.",
      "fix": "Debian/Ubuntu: apt install unattended-upgrades \u0026\u0026 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.",
      "refs": [
        {
          "title": "Debian, UnattendedUpgrades",
          "url": "https://wiki.debian.org/UnattendedUpgrades"
        },
        {
          "title": "NIST SP 800-40 Rev. 4",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104"
        ],
        "owasp": [
          "A06:2021"
        ],
        "nist": [
          "SI-2(2)"
        ],
        "cis_v8": [
          "7.3"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/host/hardening.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apt/apt.conf.d/20auto-upgrades",
          "message": "Unattended upgrades are enabled"
        }
      ]
    },
    {
      "id": "HOST-USR-005",
      "title": "System Accounts With Login Shells",
      "domain": "host",
      "severity": "low",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks that service accounts (UID below UID_MIN, usually 1000, other than root) cannot log in interactively.",
      "rationale": "Service accounts should never need a shell. A login shell gives an attacker who compromises the service, or guesses its password, an interactive foothold.",
      "fix": "usermod -s /usr/sbin/nologin \u003caccount\u003e  for each listed account (use /sbin/nologin on RHEL family).",
      "verify": "awk -F: '$3\u003e0 \u0026\u0026 $3\u003c1000 {print $1, $7}' /etc/passwd",
      "refs": [
        {
          "title": "nologin(8), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man8/nologin.8.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-2",
          "CM-7"
        ],
        "cis_topic": "System accounts are secured",
        "cis_v8": [
          "4.7"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/passwd",
          "message": "System accounts have no login shell"
        }
      ]
    },
    {
      "id": "HOST-USR-010",
      "title": "Home Directories Accessible to Other Users",
      "domain": "host",
      "severity": "low",
      "category": "access",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that the home directory of each interactive account denies read, write and execute to others.",
      "rationale": "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.",
      "fix": "chmod 750 /home/\u003cuser\u003e  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.",
      "refs": [
        {
          "title": "NIST SP 800-123, Guide to General Server Security",
          "url": "https://csrc.nist.gov/pubs/sp/800/123/final"
        },
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-732",
          "CWE-276"
        ],
        "nist": [
          "AC-3",
          "AC-6"
        ],
        "cis_topic": "Home directory permissions",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/host/hygiene.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        },
        {
          "id": "P20",
          "title": "Secrets, Cron and Account Hygiene",
          "url": "https://openvapt.com/academy/p20.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/home",
          "message": "1 home directories deny access to others"
        }
      ]
    },
    {
      "id": "NET-DB-017",
      "title": "MySQL Error Log Not Configured",
      "domain": "network",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "log_error is not set, so server errors, failed logins and startup warnings go to the console or syslog without a dedicated, retained file.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "MySQL 8.4 Reference Manual, The Error Log",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/error-log.html"
        },
        {
          "title": "MySQL 8.4 Reference Manual, Server System Variables",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "asvs": [
          "7.1.1"
        ],
        "nist": [
          "AU-2",
          "AU-12"
        ],
        "cis_topic": "Database error logging",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/mysql/conf.d/hardening.cnf: mysqld.log_error = /var/log/mysql/error.log"
          ]
        }
      ]
    },
    {
      "id": "NET-DB-018",
      "title": "MySQL Resolves Client Host Names",
      "domain": "network",
      "severity": "low",
      "category": "data",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "skip-name-resolve is not set, so grants that name hosts depend on DNS answers and every connection waits on a reverse lookup.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "MySQL 8.4 Reference Manual, skip_name_resolve",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_skip_name_resolve"
        },
        {
          "title": "MySQL 8.4 Reference Manual, Security Guidelines",
          "url": "https://dev.mysql.com/doc/refman/8.4/en/security-guidelines.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-350",
          "CWE-290"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.1.3"
        ],
        "nist": [
          "CM-6",
          "IA-3"
        ],
        "cis_topic": "Database server hardening",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/mysql/mysql.conf.d/mysqld.cnf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/mysql/conf.d/hardening.cnf: mysqld.skip-name-resolve = "
          ]
        }
      ]
    },
    {
      "id": "NET-DB-021",
      "title": "PostgreSQL Connection Logging Off",
      "domain": "network",
      "severity": "low",
      "category": "logging",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
      "status": "pass",
      "summary": "log_connections and log_disconnections are off, so successful logins leave no record.",
      "rationale": "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.",
      "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).",
      "refs": [
        {
          "title": "PostgreSQL, Error Reporting and Logging",
          "url": "https://www.postgresql.org/docs/current/runtime-config-logging.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-778"
        ],
        "owasp": [
          "A09:2021"
        ],
        "asvs": [
          "7.1.3"
        ],
        "nist": [
          "AU-2",
          "AU-12",
          "AC-2(4)"
        ],
        "cis_topic": "Database connection logging",
        "cis_v8": [
          "8.2"
        ],
        "csf_2": [
          "PR.PS-04"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P8",
          "title": "Data Services: Databases, File Shares, DNS and Mail",
          "url": "https://openvapt.com/academy/p8.html"
        },
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postgresql/16/main/postgresql.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/postgresql/16/main/postgresql.conf: log_connections = on",
            "/etc/postgresql/16/main/postgresql.conf: log_disconnections = on"
          ]
        }
      ]
    },
    {
      "id": "NET-DNS-001",
      "title": "BIND Version String Disclosed",
      "domain": "network",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "named.conf has no version statement in options, so a CHAOS class query for version.bind returns the exact BIND release.",
      "rationale": "The version string lets an attacker match advisories to the server without any guessing. Replacing it costs nothing and does not break resolution.",
      "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.",
      "refs": [
        {
          "title": "BIND 9 Administrator Reference Manual, version statement",
          "url": "https://bind9.readthedocs.io/en/latest/reference.html#namedconf-statement-version"
        },
        {
          "title": "BIND 9 Administrator Reference Manual, options",
          "url": "https://bind9.readthedocs.io/en/latest/reference.html#options-block-grammar"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_topic": "DNS server version disclosure",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/bind/named.conf.options",
          "message": "The BIND version string is replaced",
          "evidence": [
            "a version statement is present"
          ]
        }
      ]
    },
    {
      "id": "NET-FW-004",
      "title": "Firewall Rules Open Ports With No Service",
      "domain": "network",
      "severity": "low",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks for firewall allow rules whose port has no listening service on this machine.",
      "rationale": "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.",
      "fix": "Remove rules for services that no longer exist (ufw status numbered, then ufw delete \u003cn\u003e), 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.",
      "refs": [
        {
          "title": "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy",
          "url": "https://csrc.nist.gov/pubs/sp/800/41/r1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "SC-7",
          "CM-7",
          "CM-3"
        ],
        "cis_topic": "Review firewall rules periodically",
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P12",
          "title": "Firewall Policy Versus What Is Listening",
          "url": "https://openvapt.com/academy/p12.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "firewall",
          "message": "Every single-port allow rule matches a listening service"
        }
      ]
    },
    {
      "id": "NET-MAIL-003",
      "title": "Postfix VRFY Command Enabled",
      "domain": "network",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "disable_vrfy_command is not yes, so anyone who connects can ask the server whether a mailbox exists.",
      "rationale": "VRFY lets a sender enumerate valid addresses one by one, which feeds spam lists and targeted phishing. Legitimate mail flow never needs it.",
      "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.",
      "refs": [
        {
          "title": "Postfix, disable_vrfy_command",
          "url": "https://www.postfix.org/postconf.5.html#disable_vrfy_command"
        },
        {
          "title": "RFC 5321, Simple Mail Transfer Protocol, section 7.3",
          "url": "https://www.rfc-editor.org/rfc/rfc5321.html#section-7.3"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-204"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_topic": "Mail server user enumeration",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/network/data_depth.go",
      "academy": [
        {
          "id": "P19",
          "title": "Database, Mail and DNS Hardening",
          "url": "https://openvapt.com/academy/p19.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/postfix/main.cf",
          "message": "VRFY is disabled",
          "evidence": [
            "/etc/postfix/main.cf: disable_vrfy_command = yes"
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-001",
      "title": "Apache Reveals Version in Header and Footer",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "ServerTokens is not Prod or ServerSignature is On, so responses and error pages show the Apache version and modules.",
      "rationale": "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.",
      "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://\u003csite\u003e/ | grep -i '^server:'  should print Server: Apache with nothing after it.",
      "refs": [
        {
          "title": "Apache HTTP Server, ServerTokens directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#servertokens"
        },
        {
          "title": "OWASP WSTG-INFO-02, Fingerprint Web Server",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/01-Information_Gathering/02-Fingerprint_Web_Server/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-02"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_topic": "Web server banner and version disclosure",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/apache2/conf-enabled/security.conf: ServerTokens Prod",
            "/etc/apache2/conf-enabled/security.conf: ServerSignature Off"
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-002",
      "title": "Apache TRACE Method Enabled",
      "domain": "web",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "TraceEnable is not set to Off, so the HTTP TRACE method echoes requests back to the client.",
      "rationale": "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.",
      "fix": "Add  TraceEnable Off  to the server configuration and reload.",
      "verify": "curl -s -o /dev/null -w '%{http_code}' -X TRACE https://\u003csite\u003e/  should print 405.",
      "refs": [
        {
          "title": "Apache HTTP Server, TraceEnable directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#traceenable"
        },
        {
          "title": "OWASP WSTG-CONF-06, Test HTTP Methods",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-749"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.5.1"
        ],
        "wstg": [
          "WSTG-CONF-06"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_topic": "HTTP methods allowed by the web server",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/apache2/conf-enabled/security.conf: TraceEnable Off"
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-004",
      "title": "Apache AllowOverride All Permits .htaccess Control",
      "domain": "web",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "A Directory block sets AllowOverride All, so any .htaccess file under it can change handlers, options and access rules.",
      "rationale": "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.",
      "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\u003e/dev/null  should show no All.",
      "refs": [
        {
          "title": "Apache HTTP Server, AllowOverride directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride"
        },
        {
          "title": "Apache HTTP Server, Security Tips",
          "url": "https://httpd.apache.org/docs/2.4/misc/security_tips.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-15",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.1.1"
        ],
        "wstg": [
          "WSTG-CONF-05"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_topic": "Web server per-directory override scope",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "No AllowOverride All is set"
        }
      ]
    },
    {
      "id": "WEB-APA-007",
      "title": "Apache OCSP Stapling Not Enabled",
      "domain": "web",
      "severity": "low",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "A TLS-enabled Apache does not set SSLUseStapling on.",
      "rationale": "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.",
      "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 \u003csite\u003e -status  should print OCSP Response Status: successful.",
      "refs": [
        {
          "title": "Apache HTTP Server, SSLUseStapling directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslusestapling"
        },
        {
          "title": "Mozilla SSL Configuration Generator",
          "url": "https://ssl-config.mozilla.org/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-299"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.4"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-17"
        ],
        "cis_topic": "Certificate status checking",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/apache2/mods-enabled/ssl.conf: SSLUseStapling On",
            "/etc/apache2/mods-enabled/ssl.conf: SSLStaplingCache \"shmcb:logs/ssl_stapling(32768)\""
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-009",
      "title": "Apache ETag Reveals Inode Numbers",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "FileETag includes INode (or All), so ETag headers leak filesystem inode numbers of served files.",
      "rationale": "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.",
      "fix": "Set  FileETag MTime Size  or  FileETag None  and reload.",
      "verify": "curl -sI https://\u003csite\u003e/robots.txt | grep -i etag  should show a two-part value, not three.",
      "refs": [
        {
          "title": "Apache HTTP Server, FileETag directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#fileetag"
        },
        {
          "title": "CVE-2003-1418, Apache inode disclosure in ETag",
          "url": "https://www.cve.org/CVERecord?id=CVE-2003-1418"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-02"
        ],
        "nist": [
          "SI-11",
          "CM-6"
        ],
        "cis_topic": "Web server information disclosure in headers",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "FileETag does not include inode numbers"
        }
      ]
    },
    {
      "id": "WEB-APA-010",
      "title": "Apache Serves Repository and Environment Files",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "No DirectoryMatch, FilesMatch or RedirectMatch rule denies .git, .svn, .hg or .env paths, so they are served if present under a document root.",
      "rationale": "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.",
      "fix": "Add to the server configuration:  \u003cDirectoryMatch \"/\\.(git|svn|hg)\"\u003e Require all denied \u003c/DirectoryMatch\u003e  and  \u003cFilesMatch \"^\\.(env|git|svn)\"\u003e Require all denied \u003c/FilesMatch\u003e , then reload.",
      "verify": "curl -s -o /dev/null -w '%{http_code}' https://\u003csite\u003e/.git/HEAD  should print 403 or 404.",
      "refs": [
        {
          "title": "Apache HTTP Server, DirectoryMatch directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#directorymatch"
        },
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-538",
          "CWE-527"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "12.5.1",
          "14.3.3"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7",
          "SC-28"
        ],
        "cis_topic": "Web server access to hidden files",
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "Repository and environment paths are either denied by a rule or absent from every document root",
          "evidence": [
            "/etc/apache2/apache2.conf: \u003cDirectoryMatch \"/\\.(git|svn|hg)\"\u003e"
          ]
        }
      ]
    },
    {
      "id": "WEB-APP-007",
      "title": "Flask Debug Mode Enabled in Application Code",
      "domain": "web",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "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.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Flask documentation, Debug mode",
          "url": "https://flask.palletsprojects.com/en/stable/quickstart/#debug-mode"
        },
        {
          "title": "Flask documentation, Deploying to Production",
          "url": "https://flask.palletsprojects.com/en/stable/deploying/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-215"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "wstg": [
          "WSTG-CONF-02"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "medium",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-reports/app.py",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-001",
      "title": "WordPress Version Disclosed",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether WordPress sites reveal their exact version in the page or in readme.html.",
      "rationale": "The version number lets automated tools pick matching exploits for outdated cores and plugins.",
      "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://\u003csite\u003e/ | grep -i 'name=\"generator\"'",
      "refs": [
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021",
          "A06:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-08"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "No WordPress version disclosure found"
        }
      ]
    },
    {
      "id": "WEB-CMS-002",
      "title": "WordPress XML-RPC Interface Enabled",
      "domain": "web",
      "severity": "low",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "status": "pass",
      "summary": "Checks whether xmlrpc.php answers on WordPress sites.",
      "rationale": "XML-RPC allows many password guesses in one request (system.multicall) and is abused for pingback amplification. Few sites still need it.",
      "fix": "Block it at the web server (\u003cFiles xmlrpc.php\u003e Require all denied \u003c/Files\u003e) or disable it with a security plugin, unless Jetpack or the mobile app needs it.",
      "verify": "curl -s https://\u003csite\u003e/xmlrpc.php  should return 403 or 404.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Brute Force Attacks (XML-RPC)",
          "url": "https://developer.wordpress.org/advanced-administration/security/brute-force/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-307"
        ],
        "owasp": [
          "A07:2021"
        ],
        "asvs": [
          "2.2.1"
        ],
        "nist": [
          "AC-7",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "xmlrpc.php is not reachable"
        }
      ]
    },
    {
      "id": "WEB-CMS-003",
      "title": "WordPress User Accounts Enumerable",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether the WordPress REST API lists user accounts to anonymous visitors.",
      "rationale": "A list of login names halves the work of password guessing and phishing against administrators.",
      "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://\u003csite\u003e/wp-json/wp/v2/users  should return 401, 403 or 404.",
      "refs": [
        {
          "title": "WordPress Developer Resources, REST API Users endpoint",
          "url": "https://developer.wordpress.org/rest-api/reference/users/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-204"
        ],
        "owasp": [
          "A07:2021"
        ],
        "wstg": [
          "WSTG-IDNT-04"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-CMS-007",
      "title": "WordPress Dashboard File Editor Enabled",
      "domain": "web",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N",
      "status": "pass",
      "summary": "Checks whether wp-config.php leaves the built-in theme and plugin file editor enabled.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        },
        {
          "title": "WordPress Developer Resources, Editing wp-config.php",
          "url": "https://developer.wordpress.org/advanced-administration/wordpress/wp-config/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-94",
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "CM-7",
          "AC-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/var/www/shop/wp-config.php",
          "message": "No issue found"
        }
      ]
    },
    {
      "id": "WEB-CMS-010",
      "title": "Joomla Version Disclosed",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether a Joomla site serves its version manifest at /administrator/manifests/files/joomla.xml.",
      "rationale": "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.",
      "fix": "Block the folder at the web server (Apache: \u003cDirectory \"/path/administrator/manifests\"\u003e Require all denied \u003c/Directory\u003e; nginx: location ^~ /administrator/manifests/ { deny all; }) and keep Joomla updated.",
      "verify": "curl -s -o /dev/null -w '%{http_code}' https://\u003csite\u003e/administrator/manifests/files/joomla.xml  prints 403 or 404.",
      "refs": [
        {
          "title": "Joomla Documentation, Security Checklist",
          "url": "https://docs.joomla.org/Security_Checklist"
        },
        {
          "title": "NVD, CVE-2023-23752",
          "url": "https://nvd.nist.gov/vuln/detail/cve-2023-23752"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021",
          "A06:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-08"
        ],
        "nist": [
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "No Joomla version manifest is served"
        }
      ]
    },
    {
      "id": "WEB-CMS-011",
      "title": "Drupal Version Disclosed by Changelog",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether a Drupal site serves CHANGELOG.txt, which names the exact Drupal version.",
      "rationale": "The changelog tells attackers precisely which Drupal security advisories apply, as was used at scale for Drupalgeddon 2 (CVE-2018-7600).",
      "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://\u003csite\u003e/core/CHANGELOG.txt  prints 403 or 404.",
      "refs": [
        {
          "title": "Drupal.org, Security advisories",
          "url": "https://www.drupal.org/security"
        },
        {
          "title": "NVD, CVE-2018-7600",
          "url": "https://nvd.nist.gov/vuln/detail/cve-2018-7600"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021",
          "A06:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-08"
        ],
        "nist": [
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_more.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "No Drupal changelog is served"
        }
      ]
    },
    {
      "id": "WEB-CMS-018",
      "title": "WordPress Author Archive Reveals Login Names",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether /?author=1 redirects to an author archive, which exposes the first account's login name in the address.",
      "rationale": "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.",
      "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://\u003csite\u003e/?author=1 | grep -i location  prints nothing or a 404 page.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Brute Force Attacks",
          "url": "https://developer.wordpress.org/advanced-administration/security/brute-force/"
        },
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-204"
        ],
        "owasp": [
          "A07:2021"
        ],
        "wstg": [
          "WSTG-IDNT-04"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "/?author=1 does not redirect to an archive"
        }
      ]
    },
    {
      "id": "WEB-EXP-009",
      "title": "Dependency Manifests Exposed",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether package manifests and lock files are downloadable.",
      "rationale": "Lock files list exact library versions, giving attackers a ready list to match against public vulnerabilities, and often reveal internal package names.",
      "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://\u003csite\u003e/composer.lock  should print 403 or 404.",
      "refs": [
        {
          "title": "OWASP WSTG-CONF-04, Review Old Backup and Unreferenced Files for Sensitive Information",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/04-Review_Old_Backup_and_Unreferenced_Files_for_Sensitive_Information/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-538"
        ],
        "owasp": [
          "A05:2021",
          "A06:2021"
        ],
        "asvs": [
          "12.5.1"
        ],
        "wstg": [
          "WSTG-CONF-04"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_v8": [
          "3.3"
        ],
        "csf_2": [
          "PR.AA-05",
          "PR.DS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-EXP-010",
      "title": "Default Web Server Page Served",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether a site shows the web server's default installation page.",
      "rationale": "Default pages reveal the server software and suggest an unmaintained or forgotten virtual host, which attackers prioritise.",
      "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.",
      "refs": [
        {
          "title": "OWASP WSTG-INFO-02, Fingerprint Web Server",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/01-Information_Gathering/02-Fingerprint_Web_Server/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188",
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-INFO-02",
          "WSTG-CONF-02"
        ],
        "nist": [
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/exposure.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Not exposed"
        }
      ]
    },
    {
      "id": "WEB-HDR-004",
      "title": "Missing X-Content-Type-Options",
      "domain": "web",
      "severity": "low",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that browsers are told not to guess (sniff) content types.",
      "rationale": "Without nosniff, a browser may execute an uploaded text or image file as script or HTML, enabling XSS through file uploads.",
      "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://\u003csite\u003e/ | grep -i x-content-type-options",
      "refs": [
        {
          "title": "OWASP Secure Headers Project",
          "url": "https://owasp.org/projects/secure-headers-project"
        },
        {
          "title": "MDN Web Docs, X-Content-Type-Options",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-430"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.4.4"
        ],
        "nist": [
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "X-Content-Type-Options is set",
          "evidence": [
            "nosniff"
          ]
        }
      ]
    },
    {
      "id": "WEB-HDR-005",
      "title": "Missing Referrer-Policy",
      "domain": "web",
      "severity": "low",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that each site limits what URL information is leaked to other sites in the Referer header.",
      "rationale": "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.",
      "fix": "Send  Referrer-Policy: strict-origin-when-cross-origin  (or no-referrer). Never use unsafe-url.",
      "verify": "curl -sI https://\u003csite\u003e/ | grep -i referrer-policy",
      "refs": [
        {
          "title": "OWASP Secure Headers Project",
          "url": "https://owasp.org/projects/secure-headers-project"
        },
        {
          "title": "MDN Web Docs, Referrer-Policy",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-201"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.4.6"
        ],
        "nist": [
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Referrer-Policy is set",
          "evidence": [
            "strict-origin-when-cross-origin"
          ]
        }
      ]
    },
    {
      "id": "WEB-HDR-006",
      "title": "Missing Permissions-Policy",
      "domain": "web",
      "severity": "low",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks that each site restricts powerful browser features (camera, microphone, geolocation) it does not use.",
      "rationale": "Restricting features limits what injected or third-party scripts can do in your visitors' browsers.",
      "fix": "Send, for example,  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()  adjusted to the features the site really uses.",
      "verify": "curl -sI https://\u003csite\u003e/ | grep -i permissions-policy",
      "refs": [
        {
          "title": "OWASP Secure Headers Project",
          "url": "https://owasp.org/projects/secure-headers-project"
        },
        {
          "title": "MDN Web Docs, Permissions-Policy",
          "url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "nist": [
          "CM-6",
          "CM-7"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "Permissions-Policy is set",
          "evidence": [
            "camera=(), microphone=()"
          ]
        }
      ]
    },
    {
      "id": "WEB-HDR-007",
      "title": "Server Software Version Disclosed",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether response headers reveal exact web server, PHP or framework versions.",
      "rationale": "Exact versions let attackers match the site against public exploit lists in seconds. Hiding them does not fix vulnerabilities but removes free reconnaissance.",
      "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://\u003csite\u003e/ | grep -Ei '^(server|x-powered-by)'",
      "refs": [
        {
          "title": "OWASP HTTP Headers Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html"
        },
        {
          "title": "Apache HTTP Server, ServerTokens directive",
          "url": "https://httpd.apache.org/docs/2.4/mod/core.html#servertokens"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-02"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/headers.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "A8",
          "title": "Tracing Findings to Security Requirements",
          "url": "https://openvapt.com/academy/a8.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "No version details in headers"
        }
      ]
    },
    {
      "id": "WEB-NGX-001",
      "title": "nginx Version Shown in Server Header",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "nginx does not set server_tokens off, so responses and error pages carry the exact nginx version.",
      "rationale": "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.",
      "fix": "In the http block of nginx.conf add  server_tokens off;  then run nginx -t and reload.",
      "verify": "curl -sI https://\u003csite\u003e/ | grep -i '^server:'  should print Server: nginx with no version.",
      "refs": [
        {
          "title": "nginx documentation, ngx_http_core_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_core_module.html"
        },
        {
          "title": "OWASP WSTG-INFO-02, Fingerprint Web Server",
          "url": "https://wstg.owasp.org/v4.2/4-Web_Application_Security_Testing/01-Information_Gathering/02-Fingerprint_Web_Server/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-02"
        ],
        "nist": [
          "CM-6",
          "SI-11"
        ],
        "cis_topic": "Web server banner and version disclosure",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/nginx/nginx.conf: server_tokens off"
          ]
        }
      ]
    },
    {
      "id": "WEB-NGX-005",
      "title": "nginx OCSP Stapling Off or Session Tickets On",
      "domain": "web",
      "severity": "low",
      "category": "crypto",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "A TLS-enabled nginx does not enable ssl_stapling, or leaves ssl_session_tickets on.",
      "rationale": "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.",
      "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 \u003csite\u003e -status  should print OCSP Response Status: successful.",
      "refs": [
        {
          "title": "Mozilla SSL Configuration Generator",
          "url": "https://ssl-config.mozilla.org/"
        },
        {
          "title": "nginx documentation, ngx_http_ssl_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_ssl_module.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-299",
          "CWE-326"
        ],
        "owasp": [
          "A02:2021"
        ],
        "asvs": [
          "9.2.4"
        ],
        "wstg": [
          "WSTG-CRYP-01"
        ],
        "nist": [
          "SC-8",
          "SC-17"
        ],
        "cis_topic": "Certificate status checking and session resumption",
        "cis_v8": [
          "3.10"
        ],
        "csf_2": [
          "PR.DS-02"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "The hardening setting is present",
          "evidence": [
            "/etc/nginx/nginx.conf: ssl_stapling on",
            "/etc/nginx/nginx.conf: ssl_stapling_verify on",
            "/etc/nginx/nginx.conf: ssl_session_tickets off"
          ]
        }
      ]
    },
    {
      "id": "WEB-NODE-003",
      "title": "Node.js Service Not Running in Production Mode",
      "domain": "web",
      "severity": "low",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether Node.js services set NODE_ENV=production.",
      "rationale": "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.",
      "fix": "Add Environment=NODE_ENV=production to the [Service] section (systemctl edit \u003cunit\u003e), run systemctl daemon-reload and restart the service.",
      "verify": "systemctl show -p Environment \u003cunit\u003e  includes NODE_ENV=production.",
      "refs": [
        {
          "title": "Express documentation, Production best practices: performance and reliability",
          "url": "https://expressjs.com/en/advanced/best-practice-performance/"
        },
        {
          "title": "OWASP Node.js Security Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Nodejs_Security_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-489",
          "CWE-209"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.2"
        ],
        "nist": [
          "CM-6"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/runtimes.go",
      "academy": [
        {
          "id": "P13",
          "title": "Application Servers: Tomcat, Node.js and Python",
          "url": "https://openvapt.com/academy/p13.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "systemd units",
          "message": "Node.js services run with NODE_ENV=production"
        }
      ]
    },
    {
      "id": "WEB-PHP-002",
      "title": "PHP Version Exposed in Headers",
      "domain": "web",
      "severity": "low",
      "category": "exposure",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether PHP adds an X-Powered-By header with its version.",
      "rationale": "The header advertises the exact PHP version to every visitor and scanner.",
      "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.",
      "refs": [
        {
          "title": "PHP manual, php.ini directives",
          "url": "https://www.php.net/manual/en/ini.list.php"
        },
        {
          "title": "OWASP PHP Configuration Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/PHP_Configuration_Cheat_Sheet.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-497"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "14.3.3"
        ],
        "wstg": [
          "WSTG-INFO-02"
        ],
        "nist": [
          "CM-7",
          "SI-11"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/php.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "php",
          "message": "expose_php = Off"
        }
      ]
    },
    {
      "id": "HOST-CTR-009",
      "title": "Docker Not Running in Rootless Mode",
      "domain": "host",
      "severity": "info",
      "category": "config",
      "effort": "high",
      "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Reports whether the Docker daemon runs as root or in rootless mode. Informational.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "Docker documentation, Rootless mode",
          "url": "https://docs.docker.com/engine/security/rootless/"
        },
        {
          "title": "NIST SP 800-190, Application Container Security Guide",
          "url": "https://csrc.nist.gov/pubs/sp/800/190/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-250"
        ],
        "nist": [
          "AC-6",
          "SC-39"
        ],
        "cis_topic": "CIS Docker Benchmark 2.18 (run the daemon in rootless mode)",
        "cis_v8": [
          "4.1"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/host/containers_depth.go",
      "academy": [
        {
          "id": "P9",
          "title": "Containers and Kubernetes",
          "url": "https://openvapt.com/academy/p9.html"
        },
        {
          "id": "A5",
          "title": "Container Runtime Hardening Depth",
          "url": "https://openvapt.com/academy/a5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "docker",
          "message": "A rootless Docker installation is present",
          "evidence": [
            "/home/deploy/.config/docker/daemon.json"
          ]
        }
      ]
    },
    {
      "id": "HOST-PAM-006",
      "title": "Password Expiry Warning Too Short",
      "domain": "host",
      "severity": "info",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Reports whether users are warned at least 7 days before a password expires.",
      "rationale": "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.",
      "fix": "Set  PASS_WARN_AGE 7  in /etc/login.defs, and for existing accounts  chage --warndays 7 \u003cuser\u003e .",
      "verify": "grep '^PASS_WARN_AGE' /etc/login.defs",
      "refs": [
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-262"
        ],
        "owasp": [
          "A07:2021"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password expiration warning days (CIS Controls v8 5.2)",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/pam_login.go",
      "academy": [
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/login.defs",
          "message": "PASS_WARN_AGE 7"
        }
      ]
    },
    {
      "id": "HOST-PKG-003",
      "title": "Third-Party Package Repositories Present",
      "domain": "host",
      "severity": "info",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Lists package repositories that are not run by the distribution vendor, so each one can be justified.",
      "rationale": "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.",
      "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\u003e/dev/null",
      "refs": [
        {
          "title": "sources.list(5), Debian manual pages",
          "url": "https://manpages.debian.org/trixie/apt/sources.list.5.en.html"
        },
        {
          "title": "NIST SP 800-40 Rev. 4, Guide to Enterprise Patch Management Planning",
          "url": "https://csrc.nist.gov/pubs/sp/800/40/r4/final"
        },
        {
          "title": "NIST SP 800-161 Rev. 1, Cybersecurity Supply Chain Risk Management Practices",
          "url": "https://csrc.nist.gov/pubs/sp/800/161/r1/upd1/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1357"
        ],
        "owasp": [
          "A08:2021"
        ],
        "nist": [
          "CM-8",
          "SR-3",
          "SR-4"
        ],
        "cis_topic": "Software inventory: authorised repositories (CIS Controls v8 2.5)",
        "cis_v8": [
          "2.5"
        ],
        "csf_2": [
          "PR.PS-05"
        ]
      },
      "source": "internal/checks/host/pkg_depth.go",
      "academy": [
        {
          "id": "P1",
          "title": "Linux Server Hardening",
          "url": "https://openvapt.com/academy/p1.html"
        },
        {
          "id": "P22",
          "title": "Authentication Policy and Package Hygiene",
          "url": "https://openvapt.com/academy/p22.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "package repositories",
          "message": "Only distribution vendor repositories are configured"
        }
      ]
    },
    {
      "id": "HOST-PWD-005",
      "title": "Forced Periodic Password Rotation Configured",
      "domain": "host",
      "severity": "info",
      "category": "identity",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Reports whether new accounts are forced to change passwords on a short fixed schedule.",
      "rationale": "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.",
      "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",
      "refs": [
        {
          "title": "login.defs(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/login.defs.5.html"
        },
        {
          "title": "NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management",
          "url": "https://pages.nist.gov/800-63-4/sp800-63b.html"
        },
        {
          "title": "CIS Critical Security Controls v8, Control 5 (Account Management)",
          "url": "https://www.cisecurity.org/controls/account-management"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-262"
        ],
        "nist": [
          "IA-5(1)"
        ],
        "cis_topic": "Password expiration",
        "cis_v8": [
          "5.2"
        ],
        "csf_2": [
          "PR.AA-01"
        ]
      },
      "source": "internal/checks/host/passwords.go",
      "academy": [
        {
          "id": "P7",
          "title": "Identity, Passwords and Privilege",
          "url": "https://openvapt.com/academy/p7.html"
        },
        {
          "id": "P15",
          "title": "Access Control: Accounts, Sudo and Logins",
          "url": "https://openvapt.com/academy/p15.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/login.defs",
          "message": "PASS_MAX_DAYS 365: no short forced rotation"
        }
      ]
    },
    {
      "id": "NET-FW-006",
      "title": "Firewall Exposure Map of Listening Services",
      "domain": "network",
      "severity": "info",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Shows, for every network-reachable listening socket, what the host firewall does with traffic to it.",
      "rationale": "Exposure is the combination of what listens and what the firewall lets through. Seeing both side by side is how assessors find services that are open by accident and confirm that the rest are blocked on purpose.",
      "fix": "For each socket labelled allowed from anywhere or reachable, decide whether it must be public. If not, bind it to 127.0.0.1 or restrict the firewall rule.",
      "verify": "Re-run this check after changes and compare the map.",
      "refs": [
        {
          "title": "NIST SP 800-41 Rev. 1, Guidelines on Firewalls and Firewall Policy",
          "url": "https://csrc.nist.gov/pubs/sp/800/41/r1/final"
        },
        {
          "title": "NIST SP 800-115, Technical Guide to Information Security Testing and Assessment",
          "url": "https://csrc.nist.gov/pubs/sp/800/115/final"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-923"
        ],
        "nist": [
          "CM-7",
          "CM-8",
          "SC-7"
        ],
        "cis_topic": "Network services inventory",
        "cis_v8": [
          "4.4"
        ],
        "csf_2": [
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/firewall_exposure.go",
      "academy": [
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "P12",
          "title": "Firewall Policy Versus What Is Listening",
          "url": "https://openvapt.com/academy/p12.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "firewall",
          "message": "5 network-reachable sockets, 4 open to every address",
          "evidence": [
            "firewall: ufw, default deny: true",
            "tcp 0.0.0.0:22 (SSH): allowed from anywhere",
            "tcp 0.0.0.0:80 (HTTP): allowed from anywhere",
            "udp 0.0.0.0:123: blocked by default deny",
            "tcp 0.0.0.0:443 (HTTPS): allowed from anywhere",
            "tcp6 [::]:443 (HTTPS): allowed from anywhere"
          ]
        }
      ]
    },
    {
      "id": "NET-LSN-001",
      "title": "Inventory of Listening Network Services",
      "domain": "network",
      "severity": "info",
      "category": "network",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Lists every TCP and UDP port this machine listens on, and whether it is reachable from other machines.",
      "rationale": "Attack surface starts with what is listening. Every exposed port should be known and justified (NIST SP 800-115, discovery phase).",
      "fix": "Stop or uninstall services you do not need (systemctl disable --now \u003cunit\u003e). Bind internal services to 127.0.0.1. Block the rest at the firewall.",
      "verify": "ss -tulpn  shows the same list with process names.",
      "refs": [
        {
          "title": "NIST SP 800-115, Technical Guide to Information Security Testing and Assessment",
          "url": "https://csrc.nist.gov/pubs/sp/800/115/final"
        },
        {
          "title": "proc_net(5), Linux man-pages",
          "url": "https://man7.org/linux/man-pages/man5/proc_net.5.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1327"
        ],
        "nist": [
          "CM-7",
          "CM-8"
        ],
        "cis_topic": "Network services inventory",
        "cis_v8": [
          "4.8"
        ],
        "csf_2": [
          "PR.PS-01",
          "PR.IR-01"
        ]
      },
      "source": "internal/checks/network/listeners.go",
      "academy": [
        {
          "id": "F2",
          "title": "What VAPT Is, and Doing It Legally",
          "url": "https://openvapt.com/academy/f2.html"
        },
        {
          "id": "P3",
          "title": "Network Exposure and Firewalls",
          "url": "https://openvapt.com/academy/p3.html"
        },
        {
          "id": "A1",
          "title": "Running an Assessment",
          "url": "https://openvapt.com/academy/a1.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "sockets",
          "message": "9 listening sockets, 5 reachable from the network",
          "evidence": [
            "network-reachable: 5",
            "tcp 0.0.0.0:22 (SSH)",
            "tcp 0.0.0.0:80 (HTTP)",
            "udp 0.0.0.0:123",
            "tcp 0.0.0.0:443 (HTTPS)",
            "tcp6 [::]:443 (HTTPS)",
            "local only: 4",
            "tcp 127.0.0.1:3000",
            "tcp 127.0.0.1:3306 (MySQL/MariaDB)",
            "tcp 127.0.0.1:6379 (Redis)",
            "tcp 127.0.0.1:8080"
          ]
        }
      ]
    },
    {
      "id": "WEB-APA-008",
      "title": "Apache Has No Request Filtering Module",
      "domain": "web",
      "severity": "info",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Neither mod_security (ModSecurity) nor mod_evasive is loaded, so Apache has no request filtering or flood control in front of the application.",
      "rationale": "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.",
      "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\u003e/dev/null | grep -E 'security2|evasive'",
      "refs": [
        {
          "title": "OWASP ModSecurity Core Rule Set",
          "url": "https://coreruleset.org/docs/"
        },
        {
          "title": "Apache HTTP Server, Security Tips",
          "url": "https://httpd.apache.org/docs/2.4/misc/security_tips.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "owasp": [
          "A05:2021"
        ],
        "asvs": [
          "11.1.4"
        ],
        "nist": [
          "SC-5",
          "SI-10"
        ],
        "cis_topic": "Web application filtering",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        },
        {
          "id": "P25",
          "title": "Availability Without Load Testing",
          "url": "https://openvapt.com/academy/p25.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/apache2/apache2.conf",
          "message": "A request filtering module is loaded",
          "evidence": [
            "/etc/apache2/mods-enabled/security2.load: LoadModule security2_module /usr/lib/apache2/modules/mod_security2.so",
            "/etc/apache2/mods-enabled/security2.load"
          ]
        }
      ]
    },
    {
      "id": "WEB-APP-008",
      "title": "Dependency Lock Files Older Than a Year",
      "domain": "web",
      "severity": "info",
      "category": "patch",
      "effort": "medium",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Reports composer.lock, package-lock.json, yarn.lock, Gemfile.lock and poetry.lock files not modified for 365 days or more (dependency freshness). Informational.",
      "rationale": "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.",
      "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.",
      "refs": [
        {
          "title": "OWASP Vulnerable Dependency Management Cheat Sheet",
          "url": "https://cheatsheetseries.owasp.org/cheatsheets/Vulnerable_Dependency_Management_Cheat_Sheet.html"
        },
        {
          "title": "OWASP Top 10 2021, A06 Vulnerable and Outdated Components",
          "url": "https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1104",
          "CWE-1395"
        ],
        "owasp": [
          "A06:2021"
        ],
        "asvs": [
          "14.2.1"
        ],
        "nist": [
          "SI-2",
          "SA-22"
        ],
        "cis_v8": [
          "7.4"
        ],
        "csf_2": [
          "ID.RA-01",
          "PR.PS-02"
        ]
      },
      "source": "internal/checks/web/frameworks.go",
      "academy": [
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/srv/shop-laravel/composer.lock",
          "message": "Lock file changed 20 days ago"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/admin/Gemfile.lock",
          "message": "Lock file changed 20 days ago"
        },
        {
          "status": "pass",
          "target": "/var/www/shop/package-lock.json",
          "message": "Lock file changed 30 days ago"
        }
      ]
    },
    {
      "id": "WEB-CMS-017",
      "title": "WordPress Cron Reachable by Visitors",
      "domain": "web",
      "severity": "info",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Reports whether /wp-cron.php answers to anonymous requests on WordPress sites. Informational.",
      "rationale": "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.",
      "fix": "Add define('DISABLE_WP_CRON', true); to wp-config.php, run  */5 * * * * www-data php /var/www/\u003csite\u003e/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://\u003csite\u003e/wp-cron.php  prints 403 or 404.",
      "refs": [
        {
          "title": "WordPress Developer Resources, Hooking WP-Cron Into the System Task Scheduler",
          "url": "https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/"
        },
        {
          "title": "WordPress Developer Resources, Hardening WordPress",
          "url": "https://developer.wordpress.org/advanced-administration/security/hardening/"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770"
        ],
        "owasp": [
          "A05:2021"
        ],
        "wstg": [
          "WSTG-CONF-05"
        ],
        "nist": [
          "CM-7",
          "SC-5"
        ],
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/cms_depth.go",
      "academy": [
        {
          "id": "P6",
          "title": "Information Exposure and Misconfiguration",
          "url": "https://openvapt.com/academy/p6.html"
        },
        {
          "id": "P14",
          "title": "CMS Hardening in Depth: WordPress, Joomla and Drupal",
          "url": "https://openvapt.com/academy/p14.html"
        },
        {
          "id": "A6",
          "title": "Application Framework and CMS Configuration",
          "url": "https://openvapt.com/academy/a6.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "wp-cron.php is not reachable"
        }
      ]
    },
    {
      "id": "WEB-NGX-006",
      "title": "nginx Request Limits Not Configured",
      "domain": "web",
      "severity": "info",
      "category": "config",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "nginx sets no client_max_body_size, limit_req_zone or limit_conn_zone, so request size and rate rely on defaults alone.",
      "rationale": "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.",
      "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'",
      "refs": [
        {
          "title": "nginx documentation, ngx_http_limit_req_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_limit_req_module.html"
        },
        {
          "title": "nginx documentation, ngx_http_core_module",
          "url": "https://nginx.org/en/docs/http/ngx_http_core_module.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-770",
          "CWE-307"
        ],
        "owasp": [
          "A05:2021",
          "A07:2021"
        ],
        "asvs": [
          "11.1.4"
        ],
        "nist": [
          "SC-5",
          "AC-7"
        ],
        "cis_topic": "Web server request limits",
        "cis_v8": [
          "16.7"
        ],
        "csf_2": [
          "PR.PS-01"
        ]
      },
      "source": "internal/checks/web/serverconf.go",
      "academy": [
        {
          "id": "P18",
          "title": "Web Server Hardening: nginx and Apache",
          "url": "https://openvapt.com/academy/p18.html"
        },
        {
          "id": "P25",
          "title": "Availability Without Load Testing",
          "url": "https://openvapt.com/academy/p25.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "/etc/nginx/nginx.conf",
          "message": "Request size or rate limits are configured",
          "evidence": [
            "/etc/nginx/nginx.conf: client_max_body_size 8m",
            "/etc/nginx/nginx.conf: limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m"
          ]
        }
      ]
    },
    {
      "id": "WEB-SEC-001",
      "title": "No security.txt Contact File",
      "domain": "web",
      "severity": "info",
      "category": "webapp",
      "effort": "low",
      "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N",
      "status": "pass",
      "summary": "Checks whether each site publishes /.well-known/security.txt (RFC 9116).",
      "rationale": "security.txt tells researchers how to report a vulnerability. Without it, reports may go unheard or be disclosed publicly instead.",
      "fix": "Publish /.well-known/security.txt with at least Contact: and Expires: fields (see securitytxt.org).",
      "verify": "curl -s https://\u003csite\u003e/.well-known/security.txt",
      "refs": [
        {
          "title": "RFC 9116, A File Format to Aid in Security Vulnerability Disclosure",
          "url": "https://www.rfc-editor.org/rfc/rfc9116.html"
        }
      ],
      "standards": {
        "cwe": [
          "CWE-1188"
        ],
        "nist": [
          "RA-5(11)"
        ],
        "cis_v8": [
          "16.2"
        ],
        "csf_2": [
          "ID.RA-08"
        ]
      },
      "source": "internal/checks/web/docroot.go",
      "academy": [
        {
          "id": "P5",
          "title": "Web Security Headers and Cookies",
          "url": "https://openvapt.com/academy/p5.html"
        }
      ],
      "confidence": "high",
      "results": [
        {
          "status": "pass",
          "target": "shop.example.test",
          "message": "security.txt is published"
        }
      ]
    }
  ],
  "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."
  ],
  "disclaimer": "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.",
  "citation": {
    "apa": "OpenVAPT. (2026). OpenVAPT: An open academic vulnerability assessment tool (Version unreleased build) [Computer software]. https://github.com/OpenVAPT/openvapt",
    "bibtex": "@software{openvapt_2026,\n  author       = {{OpenVAPT}},\n  title        = {{OpenVAPT: An Open Academic Vulnerability Assessment Tool}},\n  year         = {2026},\n  version      = {unreleased build},\n  url          = {https://github.com/OpenVAPT/openvapt},\n  note         = {Built by AI Cybersecurity Engineers. https://openvapt.com},\n  license      = {LicenseRef-OpenVAPT-SAL-1.3}\n}",
    "reference": "Report generated by OpenVAPT (unreleased build) on 2026-10-16.",
    "credit": "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."
  }
}