CVE-CHANNEL.md showed the PHP upload-progress rule as its worked example, and that CVE is patched in compiled code. The guard forty lines below the example exists to refuse exactly that — both rules would fire, the scores would add, and one attack would be reported as two. So the example a reader was most likely to copy was the only example that could not be published: published rule CVE-2026-109618-01 patches CVE-2026-109618, which the compiled rule "php_upload_progress" already patches The example is now a CVE that is not patched in code, extracted from this file and run through the engine's own guard to prove it validates. A sentence beside the example says why it is not the PHP one, instead of leaving the reader to find the warning at the bottom after the refusal. cve.json is untouched and its build_datetime is unchanged, so nothing an edge node enforces changes with this commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N5fpE6Gu4W6JoFMXyb7t7r
94 lines
3.7 KiB
Markdown
94 lines
3.7 KiB
Markdown
# 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.
|