security: deterministic advisory scan as an ADDITIVE pre-step

Why: gitea 1.27.1 fixed CVE-2026-60004 + CVE-2026-59774 (both CVSS 9.8). The
2026-08-03 report printed gitea's CVE count as '1', the 2026-08-07 report as
'none'. Cause chain: the upgrade subagent read the GitHub release notes, which
name NEITHER cve (they are announced only in the vendor blog's security section),
so it recorded one unrelated minor item; the report then derived security content
from those notes plus model knowledge, and the model's training predates the CVEs.
Nothing in the pipeline ever queried an advisory source.

cc-ci-plan/advisory-scan.py — deterministic, per recipe, per upgrade window:
  1. GitHub Security Advisories API for every source repo in the upstream registry.
     PRIMARY: CVE + GHSA + severity + vulnerable/patched ranges, so 'fixed by THIS
     upgrade' is computed. Needs no new per-recipe config (134 registry URLs are
     already github.com).
  2. Vendor release/security pages — every registry URL, fetched + regex-scanned.
     This is the source that actually had the gitea CVEs.
  3. OSV where a package mapping exists — supplementary.
Each source reports its own status so 'checked, none found' is never confused with
'not checked'. Source selection was measured, not assumed: for these two CVEs OSV
404'd and NVD's API had them by neither CPE, id, nor keyword — advisory DBs lag the
vendor, hence 1+2 lead.

Wiring is strictly ADDITIVE:
- /recipe-upgrade gains step 2a: run the scan, paste the block into the per-recipe
  log, and report the UNION of it and the existing release-note reading. The scan
  may never lower a count established by reading.
- /recipe-report treats the block as a FURTHER source, prefers its advisory ids /
  severities / fixed-in versions for citation, and must render '?' (not 'none')
  when a scan is absent or has failed sources — the false-clean 'none' is exactly
  what happened on 2026-08-07.
- upstream/gitea.md records blog.gitea.com as the security-announcement URL.

Verified on the real regression: 1.27.0 -> 1.27.1 now yields exactly the 2 missed
criticals with their GHSA ids; the wider 1.26.2 -> 1.27.1 window yields 62.
This commit is contained in:
autonomic-bot
2026-08-10 18:25:58 +00:00
parent 02dbd71b49
commit 5775fe23f8
4 changed files with 336 additions and 0 deletions
+9
View File
@@ -37,6 +37,15 @@ keeps every weekly edition looking the same regardless of which model writes the
- **Security analysis.** Scan the per-recipe `upgrade_notes_md` + the summary (and use your own
knowledge of the version bumps) for upgrades that fix **CVEs / security issues**. For each recipe,
**count the CVEs** the PR fixes — this drives both the table's `cve` column and the priority sort.
- **ADDITIONALLY, and never instead:** each per-recipe log carries an `### Advisory scan
(deterministic pre-step)` block (from `cc-ci-plan/advisory-scan.py` — GitHub Security
Advisories + vendor security pages + OSV, with severities and fixed-in versions). Treat its
CVE list as a **further source** and report the **UNION** of it and what you found by reading.
Its entries are machine-derived with advisory IDs, so prefer them for CVE ids / severities /
fixed-in versions, and cite the GHSA where present in the Security Bulletin. If a recipe has
no scan block, or the block lists **failed sources**, the count is **not** authoritative:
render the cve cell as `?` (unknown), never `none` — a blank that reads as "clean" is exactly
how two CVSS-9.8 gitea RCEs were reported as "none" on 2026-08-07.
Anything **critical/high** also gets a `security` bulletin entry (recipe · CVE id(s) + severity ·
what it fixes · PR link); be specific about severity and what's exposed if not merged.
- **Lead — ONE short paragraph.** A tight, concrete opener in opus's voice: fleet state in a sentence
+29
View File
@@ -157,6 +157,35 @@ On cc-ci's `~/.abra/recipes/<recipe>` (wrap every abra call per the pseudo-TTY b
`open-recipe-pr.sh`). Do **not** push to upstream; the version bump + tag + publish are the operator's
final `abra recipe release` step.
### 2a. Advisory scan (deterministic; ADDITIVE — run it, never skip it)
Run the deterministic scanner for the exact upgrade window and **paste its markdown block verbatim
into the per-recipe log**:
```
python3 /srv/cc-ci/cc-ci-plan/advisory-scan.py <recipe> --from <old-app-version> --to <new-app-version>
```
It queries, per recipe: the **GitHub Security Advisories API** for every source repo in
`cc-ci-plan/upstream/<recipe>.md` (CVE + GHSA + severity + vulnerable/patched ranges, so
"fixed by THIS upgrade" is computed, not guessed), every **vendor release/security URL** in that
registry (fetched + regex-scanned for CVE ids), and **OSV** where a package mapping exists.
**This does NOT replace your own release-note reading — it is an ADDITIONAL evidence source.** Do
exactly what you did before, then union the two: the CVE count you report is the union of the CVEs
you found in the notes and the CVEs the scan found. Never let the scan lower a count you established
by reading.
Why it exists: gitea 1.27.1 fixed CVE-2026-60004 and CVE-2026-59774 (both CVSS 9.8). Both are named
only in the vendor's blog security section — the GitHub *release notes* mention neither — so the
release-note read found one unrelated minor item and the weekly report printed a CVE count of "1",
then "none". Advisory databases lagged too (OSV 404'd on both; NVD's API had neither by CPE, id, or
keyword), which is why the GitHub advisory API and the vendor pages lead.
If the scanner reports **failed sources**, say so in the log — an incomplete scan must not read as
a clean one. If a vendor publishes security notes at a URL the registry lacks (gitea's
`blog.gitea.com`), **add it to `cc-ci-plan/upstream/<recipe>.md`** so the next scan sees it.
### 2b. Direct deploy + inspect on cc-ci — live feedback BEFORE CI (recipe-maintainer style)
Before opening the PR / running `!testme`, deploy the WIP recipe **directly** on the cc-ci server and
watch it converge — the way recipe-maintainer tests on `cctest`. This gives you **live logs +