<?php
/**
 * Per-family reference cards for the OWASP CRS + Coraza engine.
 *
 * Each entry corresponds to a 100-block of rule IDs in the CRS (e.g. 941xxx
 * = XSS) plus the synthetic ranges WHP uses (200xxx = engine, 990xxx = WHP
 * generated host-exception rules).
 *
 * Fields:
 *   name        — short human label
 *   file        — CRS source filename inside the coreruleset/coreruleset
 *                 repo's rules/ directory (null when no source file applies)
 *   description — what the family detects, in 1-2 sentences
 *   common_fp   — typical false-positive trigger, if any
 *   tuning      — concrete tuning guidance for an admin who's about to act
 *   doc_url     — best external reference URL (overrides the computed
 *                 GitHub URL for non-CRS families)
 */

if (!function_exists('coraza_rule_family_table')) {
    function coraza_rule_family_table(): array {
        static $table = null;
        if ($table !== null) {
            return $table;
        }
        $table = [
            '901' => [
                'name'        => 'CRS initialization',
                'file'        => 'REQUEST-901-INITIALIZATION.conf',
                'description' => 'CRS bootstrap: sets default actions, validates CRS setup, materializes anomaly-score variables. These rules don\'t themselves detect attacks; they make the rest of CRS work.',
                'common_fp'   => 'None expected.',
                'tuning'      => 'Don\'t override these.',
            ],
            '905' => [
                'name'        => 'Common exceptions',
                'file'        => 'REQUEST-905-COMMON-EXCEPTIONS.conf',
                'description' => 'Built-in exceptions for known-benign requests (e.g. Apache mod_security health checks) so they bypass CRS evaluation.',
                'common_fp'   => 'None.',
                'tuning'      => 'Don\'t disable.',
            ],
            '911' => [
                'name'        => 'HTTP method enforcement',
                'file'        => 'REQUEST-911-METHOD-ENFORCEMENT.conf',
                'description' => 'Restricts allowed HTTP methods to a configured allowlist (GET, POST, etc.). Anything exotic (TRACE, DEBUG, PROPFIND) is blocked.',
                'common_fp'   => 'WebDAV, CalDAV, and some APIs use PROPFIND/REPORT/PATCH.',
                'tuning'      => 'Expand the allowlist in crs-setup.conf rather than disabling rules per host.',
            ],
            '913' => [
                'name'        => 'Known scanner detection',
                'file'        => 'REQUEST-913-SCANNER-DETECTION.conf',
                'description' => 'Matches well-known security-scanner user agents (Nikto, sqlmap, Acunetix, etc.) and probe-style URIs (e.g. /scan.php). Aggressive — high catch rate, high false-positive rate on legitimate federated services.',
                'common_fp'   => 'Mastodon ActivityPub user agents, SiteLockSpider, legitimate vulnerability scanners run by the customer.',
                'tuning'      => 'For Mastodon instances, add a host exception for 913100 specifically; for customer-run scanners, allow their source IPs.',
            ],
            '920' => [
                'name'        => 'HTTP protocol enforcement',
                'file'        => 'REQUEST-920-PROTOCOL-ENFORCEMENT.conf',
                'description' => 'Enforces RFC compliance: malformed headers, missing Host, invalid Content-Length, suspicious URL encodings, oversized fields. 920440 specifically catches requests for backup files (.bak, .old, etc.) — promoted to enforce in the baked-in overrides.',
                'common_fp'   => 'Legacy clients, internal apps with malformed-but-functional requests, some Webhook tools.',
                'tuning'      => 'Tighten the underlying client if possible; per-host exception is a last resort.',
            ],
            '921' => [
                'name'        => 'HTTP protocol attacks',
                'file'        => 'REQUEST-921-PROTOCOL-ATTACK.conf',
                'description' => 'Request smuggling, header injection, HTTP/2 rapid reset, malicious transfer-encoding. Hard attacks; rarely legitimate.',
                'common_fp'   => 'Very rare. Some load balancers in front of WHP can introduce header oddities.',
                'tuning'      => 'Investigate before disabling.',
            ],
            '922' => [
                'name'        => 'Multipart / file upload attacks',
                'file'        => 'REQUEST-922-MULTIPART-ATTACK.conf',
                'description' => 'Multipart-form parsing attacks and malformed file uploads.',
                'common_fp'   => 'Some legacy form clients send technically-invalid multipart boundaries that still parse.',
                'tuning'      => 'Verify the upload is legitimate before per-host exemption — multipart parser bugs have been used as CVEs.',
            ],
            '930' => [
                'name'        => 'LFI — Local File Inclusion',
                'file'        => 'REQUEST-930-APPLICATION-ATTACK-LFI.conf',
                'description' => 'Detects path-traversal sequences (../, ..\\) and direct references to OS files (/etc/passwd, /proc/self/environ). 930120 (OS files) and 930130 (.env disclosure) are promoted to enforce in the baked overrides.',
                'common_fp'   => 'Apps that legitimately accept relative paths in query strings (file browsers, some CMS plugins).',
                'tuning'      => 'Sandbox the underlying path-handling code; per-host exception is risky here — false-positives often mask real attacks.',
            ],
            '931' => [
                'name'        => 'RFI — Remote File Inclusion',
                'file'        => 'REQUEST-931-APPLICATION-ATTACK-RFI.conf',
                'description' => 'Detects attempts to load remote files via include() or similar (e.g. ?page=http://attacker.com/x.txt).',
                'common_fp'   => 'Apps that legitimately fetch remote URLs based on user input (rare in shared hosting).',
                'tuning'      => 'Validate URL inputs server-side; per-rule exception is rarely appropriate.',
            ],
            '932' => [
                'name'        => 'RCE / OS command injection',
                'file'        => 'REQUEST-932-APPLICATION-ATTACK-RCE.conf',
                'description' => 'Detects shell metacharacters and command-injection patterns. Range 932100-932160 is promoted to enforce in the baked overrides because false-positive rate is low and impact of a miss is severe.',
                'common_fp'   => 'CMSes that allow code-snippet embedding (gist viewers, paste-bins) sometimes match.',
                'tuning'      => 'Don\'t weaken without strong evidence; if a specific paste-bin host needs it, exception only the affected URI.',
            ],
            '933' => [
                'name'        => 'PHP injection / webshells',
                'file'        => 'REQUEST-933-APPLICATION-ATTACK-PHP.conf',
                'description' => 'PHP-specific attacks: <?php tags in input, eval/assert/exec calls, known webshell signatures. 933170-933200 promoted to enforce.',
                'common_fp'   => 'PHP documentation / tutorial sites legitimately containing PHP code samples.',
                'tuning'      => 'Per-host exception for doc sites; otherwise leave enforced.',
            ],
            '934' => [
                'name'        => 'NodeJS / generic application attacks',
                'file'        => 'REQUEST-934-APPLICATION-ATTACK-GENERIC.conf',
                'description' => 'Prototype pollution, NodeJS-specific injection, SSRF probes, deserialization payloads.',
                'common_fp'   => 'JSON APIs accepting deeply-nested objects sometimes trigger prototype-pollution heuristics.',
                'tuning'      => 'Inspect matched data carefully before exempting — prototype pollution is being actively exploited.',
            ],
            '941' => [
                'name'        => 'XSS — Cross-Site Scripting',
                'file'        => 'REQUEST-941-APPLICATION-ATTACK-XSS.conf',
                'description' => 'Script-injection attempts: <script>, on* event handlers, javascript: URIs, attribute-based vectors. Uses libinjection (941100) for high-confidence catches plus pattern rules for specific obfuscations.',
                'common_fp'   => 'CMSes that accept HTML in user-supplied fields (wikis, podcast show notes, forums, rich-text editors).',
                'tuning'      => 'For CMSes that store HTML legitimately, add a per-host exception for the 941 family. The Coraza rule manager already shipped one for hub.hackerpublicradio.org on 2026-05-14.',
            ],
            '942' => [
                'name'        => 'SQL injection',
                'file'        => 'REQUEST-942-APPLICATION-ATTACK-SQLI.conf',
                'description' => 'SQL injection: SELECT/UNION/DROP keywords, boolean-based payloads, error-based extraction, time-based blind probes. 942550 specifically detects JSON-body SQLi.',
                'common_fp'   => 'Apps with literal SQL in user inputs (DB admin panels, query-builder UIs).',
                'tuning'      => 'Per-URI exception on the admin panel\'s submit endpoint rather than disabling the rule globally.',
            ],
            '943' => [
                'name'        => 'Session fixation',
                'file'        => 'REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf',
                'description' => 'Detects attempts to fix/reuse session identifiers via query-string or cookie manipulation.',
                'common_fp'   => 'Rare. Some legacy auth flows that put session IDs in URLs match.',
                'tuning'      => 'Fix the underlying auth flow first.',
            ],
            '944' => [
                'name'        => 'Java attacks (incl. Log4Shell)',
                'file'        => 'REQUEST-944-APPLICATION-ATTACK-JAVA.conf',
                'description' => 'Java-specific exploits: JNDI lookups (Log4Shell, CVE-2021-44228), OGNL expressions (Struts), JSP/serialization payloads. 944100-944300 promoted to enforce.',
                'common_fp'   => 'Very rare in a PHP-heavy shared-hosting environment.',
                'tuning'      => 'Leave enforced.',
            ],
            '949' => [
                'name'        => 'Inbound anomaly aggregator',
                'file'        => 'REQUEST-949-BLOCKING-EVALUATION.conf',
                'description' => 'The actual blocker for inbound requests. Earlier rules contribute to an anomaly score (typically +5 for critical, +4 high, +3 warning, +2 notice). 949110 issues the deny when the score crosses the threshold (default 5).',
                'common_fp'   => '949110 itself never has a false positive — it just sums what other rules contributed. If 949110 fires too often, look at the upstream rules that pushed the score over.',
                'tuning'      => 'Don\'t exempt 949110; exempt the upstream contributors per-host instead.',
            ],
            '950' => [
                'name'        => 'Outbound data leakage',
                'file'        => 'RESPONSE-950-DATA-LEAKAGES.conf',
                'description' => 'Inspects responses for leaked information (stack traces, source-code disclosure, debug headers).',
                'common_fp'   => 'Dev sites that intentionally show stack traces.',
                'tuning'      => 'Set the app to production-mode error handling rather than disabling the rule.',
            ],
            '951' => [
                'name'        => 'SQL data leakage',
                'file'        => 'RESPONSE-951-DATA-LEAKAGES-SQL.conf',
                'description' => 'Catches SQL error messages and structured-data leaks in responses.',
                'common_fp'   => 'phpMyAdmin and similar tools intentionally show SQL output.',
                'tuning'      => 'Per-URI exception for admin tools.',
            ],
            '952' => [
                'name'        => 'Java data leakage',
                'file'        => 'RESPONSE-952-DATA-LEAKAGES-JAVA.conf',
                'description' => 'Java exception stack traces and JBoss/Tomcat error pages.',
                'common_fp'   => 'Java dev environments.',
                'tuning'      => 'Production-mode error handling.',
            ],
            '953' => [
                'name'        => 'PHP data leakage',
                'file'        => 'RESPONSE-953-DATA-LEAKAGES-PHP.conf',
                'description' => 'PHP errors and notice/warning leakage in responses.',
                'common_fp'   => 'Customer PHP sites with display_errors=On.',
                'tuning'      => 'Set display_errors=Off on the customer site (proper fix) rather than exempting.',
            ],
            '954' => [
                'name'        => 'IIS data leakage',
                'file'        => 'RESPONSE-954-DATA-LEAKAGES-IIS.conf',
                'description' => 'IIS / .NET error pages leaking server internals.',
                'common_fp'   => 'Not applicable to this hosting stack.',
                'tuning'      => 'Leave alone.',
            ],
            '955' => [
                'name'        => 'Outbound webshell signatures',
                'file'        => 'RESPONSE-955-WEB-SHELLS.conf',
                'description' => 'Detects webshell output patterns in responses (uname output, file listings shaped like c99 / r57 shells).',
                'common_fp'   => 'Customer sites that legitimately show system info (admin dashboards).',
                'tuning'      => 'Investigate first — a legitimate admin dashboard returning uname is fine; the same payload from a content site is not.',
            ],
            '956' => [
                'name'        => 'Ruby data leakage',
                'file'        => 'RESPONSE-956-DATA-LEAKAGES-RUBY.conf',
                'description' => 'Ruby/Rails error pages and stack traces in responses.',
                'common_fp'   => 'Ruby dev sites with debug pages enabled.',
                'tuning'      => 'Production-mode error handling on the upstream Ruby app.',
            ],
            '959' => [
                'name'        => 'Outbound blocking evaluation',
                'file'        => 'RESPONSE-959-BLOCKING-EVALUATION.conf',
                'description' => 'Per-response aggregation of outbound anomaly score.',
                'common_fp'   => 'See 949 — never a direct false positive, only an aggregator.',
                'tuning'      => 'Exempt upstream contributors, not the aggregator.',
            ],
            '980' => [
                'name'        => 'Correlated outbound',
                'file'        => 'RESPONSE-980-CORRELATION.conf',
                'description' => 'Correlates inbound and outbound anomaly scores across the transaction. 980130 issues the outbound deny.',
                'common_fp'   => 'See 949.',
                'tuning'      => 'Exempt upstream contributors.',
            ],
            'engine' => [
                'name'        => 'Coraza engine built-in',
                'file'        => null,
                'description' => 'Rules baked into the Coraza engine itself, not part of CRS. Includes request-body parser checks, argument-count limits, transaction limits. These block independently — they do NOT contribute to the CRS anomaly score.',
                'common_fp'   => '200002 ("Failed to parse request body") fires on malformed XML-RPC bodies — frequent on /xmlrpc.php from scrapers that send broken payloads.',
                'tuning'      => 'Host-exempting an engine rule disables a fundamental safety check. Prefer to disable the underlying endpoint (e.g. block /xmlrpc.php if WordPress isn\'t using it) rather than exempt the rule.',
                'doc_url'     => 'https://coraza.io/docs/seclang/directives/',
            ],
            'whp-hostex' => [
                'name'        => 'WHP-generated host exception',
                'file'        => null,
                'description' => 'A SecRule synthesized by the WHP rule manager to disable a specific upstream rule for a specific list of hostnames. Generated from the coraza_rule_host_exceptions table; ID is 990000000 + target_rule_id.',
                'common_fp'   => 'Not applicable — these rules never produce hits in audit.log; they only suppress other rules.',
                'tuning'      => 'Manage via the "Hosts (N)" button on the target rule in the Firing or Catalog tab.',
                'doc_url'     => '',
            ],
        ];
        return $table;
    }
}

if (!function_exists('coraza_rule_family_for')) {
    /**
     * Resolve which family entry applies to a given rule ID.
     */
    function coraza_rule_family_for(int $rule_id): ?array {
        $table = coraza_rule_family_table();
        if ($rule_id >= 990000000) {
            return $table['whp-hostex'] + ['key' => 'whp-hostex'];
        }
        if ($rule_id >= 200000 && $rule_id < 210000) {
            return $table['engine'] + ['key' => 'engine'];
        }
        // Standard CRS: family is the first three digits of the rule ID.
        $family = (string)intdiv($rule_id, 1000);
        if (isset($table[$family])) {
            return $table[$family] + ['key' => $family];
        }
        return null;
    }
}

if (!function_exists('coraza_rule_doc_url')) {
    /**
     * Best external doc URL for a rule. CRS rules → GitHub source file in
     * coreruleset/coreruleset; engine rules → coraza.io directives doc;
     * generated host-exceptions → no external URL.
     */
    function coraza_rule_doc_url(int $rule_id): string {
        $fam = coraza_rule_family_for($rule_id);
        if ($fam === null) {
            return '';
        }
        if (!empty($fam['doc_url'])) {
            return (string)$fam['doc_url'];
        }
        if (!empty($fam['file'])) {
            return 'https://github.com/coreruleset/coreruleset/blob/main/rules/' . $fam['file'];
        }
        return '';
    }
}
