diff --git a/CVE-CHANNEL.md b/CVE-CHANNEL.md new file mode 100644 index 0000000..b92e596 --- /dev/null +++ b/CVE-CHANNEL.md @@ -0,0 +1,87 @@ +# 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-2026-109618-01", + "name": "PHP upload-progress field present", + "pattern": "^php_session_upload_progress$", + "targets": ["param_names"], + "action": "block", + "severity": "critical", + "category": "cve", + "cve": "CVE-2026-109618, CVE-2026-109619", + "enabled": true, + "references": ["https://nvd.nist.gov/vuln/detail/CVE-2026-109618"] + }] + }] +} +``` + +`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. diff --git a/cve.json b/cve.json new file mode 100644 index 0000000..8725e08 --- /dev/null +++ b/cve.json @@ -0,0 +1,4 @@ +{ + "build_datetime": "2026-10-22T13:00:00Z", + "rulesets": [] +}