# The CVE channel (`cve.json`) This document carries virtual patches: rules written against a named vulnerability, published as data so that every edge node enforces them within the hour instead of waiting for an engine release. It is deliberately a second document in this repository, not a second repository and not part of `rulesets.json`. Each has its own `build_datetime`, so each is skipped or applied on its own, and a mistake in one cannot take out the other. Engine 3.0.47 and later fetch it hourly from: https://git.artica.center/Andre/edgeguard-owasp-feed/raw/branch/main/cve.json ## Publishing a patch Add a rule to the single ruleset, bump `build_datetime`, commit, push. An unchanged `build_datetime` means every node skips the document without reading it, so the stamp is what makes a publish take effect. ```json { "build_datetime": "2026-10-22T13:00:00Z", "rulesets": [{ "id": "edgeguard-cve", "name": "EdgeGuard CVE Virtual Patches", "version": "1", "source": "edgeguard-cve", "description": "Virtual patches for named vulnerabilities", "enabled": true, "priority": 1, "rules": [{ "id": "CVE-2025-31161-01", "name": "CrushFTP authentication bypass via crafted Authorization header", "pattern": "(?i)AWS4-HMAC-SHA256.*Credential=[^/]*/", "targets": ["headers"], "action": "block", "severity": "critical", "category": "cve", "cve": "CVE-2025-31161", "enabled": true, "references": ["https://nvd.nist.gov/vuln/detail/CVE-2025-31161"] }] }] } ``` That example is deliberately NOT the PHP upload-progress rule, which this document used to show. That CVE is patched in compiled code, so the guard below refuses it — the one example a reader was most likely to copy was the one example that could not be published. Anything already patched in code belongs in code; see the end of this page. `source` must be `edgeguard-cve`. That is the source the channel owns and the only one it may clear, which is also what makes a rule **withdrawable**: remove it, bump the stamp, and it stops being enforced everywhere within the hour. Publishing an empty `"rulesets": []` withdraws the lot. ## Targets The usual ones (`uri`, `query`, `args`, `body`, `headers`, `user_agent`, `cookies`, `filename`, `all`) plus: - **`param_names`** — the pattern is run against each parameter NAME on its own: query parameters, urlencoded body fields and multipart `Content-Disposition` names. An anchored pattern therefore anchors to one name, and a parameter whose *value* happens to be the string can never match. This is the shape CVE-2026-109618 needed and the reason it could not be written against `args`, where names and values arrive as one blob and `?note=PHP_SESSION_UPLOAD_PROGRESS` would have been refused. Write `param_names` patterns in lower case; both the original and the lowercased spelling of each name are tried. ## Severity is the score `critical` 5, `high` 4, `medium` 3, `low` 2, against a default anomaly threshold of 5. A patch that should refuse on its own is `critical`; one that should only contribute is `medium` or below. Nothing else sets the score. ## Two things to get right **Do not publish a rule for a CVE the engine already patches in code.** Both would fire on the same request, the anomaly score would be the sum, and the event would report one attack as two rules. `TestPublishedFeedDoesNotDuplicateACompiledRule` in the engine fails if this document names a CVE that `ruleCVEReferences` already covers. **A patch written in the hours after a disclosure is written against an advisory, not against traffic.** There is no backtest. If one turns out to refuse something real, the remedy is to withdraw it — which is why withdrawal is a first-class operation here.