Home/Blog

CVE Slop: The Fake SQLite Bug That Scored 9.8

CVE-2026-51302, a fabricated SQLite use-after-free, scored 9.8 before rejection. How to triage a scary CVE and keep auto-patching safe.

CVE Slop: The Fake SQLite Bug That Scored 9.8

CVE Slop: The Fake SQLite Bug That Scored 9.8

On 27 July 2026, MITRE published CVE-2026-51302, a "use-after-free" in SQLite 3.41 that could supposedly give a remote attacker code execution. A day later CISA's enrichment team scored it 9.8 Critical. The bug did not exist. The functions it named were wrong for that version, and the proof-of-concept query ran cleanly. On 31 July the record was rejected as "not a security issue", along with five sibling CVEs against SQLite from the same source. SQLite's maintainers say the reports appear to be AI hallucinations. If your scanners, dashboards or patch bots saw SQLite 3.41.0 in that window, they saw a critical finding that was never real. That is the direct impact. The bigger lesson is about how your team decides that a CVE is real before acting on it.

What happened, from the record itself

Every CVE record's history is public. The CVE Program's cvelistV5 repository and the NVD change history API show exactly how this one moved through the pipeline.

Date (UTC)EventSource
2026-06-07CVE ID reserved, assigner "mitre" (the CNA of last resort)CVE record metadata
2026-07-27Record published. Vendor and product "n/a". Two references: SQLite's expr.c on GitHub and an advisory in a new GitHub repocvelistV5, NVD history
2026-07-27GitHub publishes GHSA-vrg3-8p22-cwh8 as an unreviewed advisory, severity criticalGitHub Advisory API
2026-07-28CISA ADP adds CVSS 3.1 base score 9.8, CWE-416, SSVC "Exploitation: poc", and tags the advisory repo as "exploit"cvelistV5
2026-07-29NVD initial analysis adds a CPE for sqlite:sqlite:3.41.0NVD history
2026-07-29SQLite author Richard Hipp posts "Fake CVEs against SQLite" on the SQLite forumSQLite forum
2026-07-30JFrog publishes a line-by-line teardown of six SQLite CVEsJFrog Security Research
2026-07-30CISA ADP lowers the score to 7.5, changes SSVC exploitation to "none", drops the exploit tagcvelistV5
2026-07-31CVE rejected: "withdrawn by its CNA. Further investigation showed that it was not a security issue"CVE.org, NVD

The other five, CVE-2026-51296, 51297, 51300, 51303 and 51304, follow the same path: published 27 July, rejected 31 July.

Look at what the 9.8 rested on. The CNA record had no affected vendor, no affected version range, no fix commit and no vendor advisory. One reference was a link to a whole source file on SQLite's master branch. The other was a Markdown file in a repository created for the purpose. That is enough to get a CVE ID, a critical score and a CPE match.

Why the report fell apart

The advisory claimed that sqlite3ReleaseTempReg frees heap memory that exprComputeOperands later reads. JFrog's analysis (30 July 2026) checked the claim against the tagged source:

  • exprComputeOperands() did not exist in SQLite 3.41. JFrog traces it to commits from mid-2025.
  • sqlite3ReleaseTempReg() does not free memory at all. It returns a register index to a small array for reuse, so a use-after-free through that path is impossible by design.
  • The PoC, a single SELECT CASE ... END FROM test; query, ran under AddressSanitizer without a crash.

The siblings were no better. One cited line numbers past the end of a 2,706-line json.c. One described a fix in 3.51.3, but the diff between 3.51.2 and 3.51.3 touched nothing in src/expr.c. One gave a function a signature it has never had. JFrog reports that of 55 advisories published by the same GitHub account, 54 were fabricated and one contained a real bug wrapped in unverified metadata.

Richard Hipp's forum post of 29 July is blunt: "MITRE apparently takes a CVE from anybody at any time, without any kind of validation. But boy do they require validation to get one taken down!" On 4 August he added that MITRE "did not make any attempt to contact the SQLite devs" before publishing. SQLite's CVE status page (archived 2 August) now lists all six as "Not a bug in SQLite".

This is not a one-off: the curl numbers

Maintainers have been describing this failure for two years. Daniel Stenberg of curl wrote in January 2024 that AI-assisted reports are harder to discard than old-fashioned junk: "The better the crap, the longer time and the more energy we have to spend on the report until we close it."

By July 2025 he put AI slop at about 20% of curl's bounty submissions for the year, with about 5% turning out to be real vulnerabilities. Each report pulled in three or four people of a seven-person security team, for 30 minutes to a few hours each. In January 2026 curl ended its bug bounty, effective 31 January, citing the "explosion in AI slop reports". After a failed move to GitHub, curl returned to HackerOne in March 2026 without cash rewards. By April, Stenberg reported that the confirmed rate was back to 15 to 16%, and that almost every report now used AI, but that most were high quality.

Two points follow from curl's experience. First, the cost lands on the people who can least spare it: the maintainers. Second, removing the payout and adding friction worked. AI-assisted research is fine. Unverified AI output is the problem.

Why the safety net did not catch it

For years, many teams treated an NVD score as a vetted fact. That assumption no longer holds. On 15 April 2026, NIST said CVE submissions grew 263% between 2020 and 2025 and that it would now prioritize enrichment for CVEs in CISA's KEV catalog, software used by the federal government, and critical software under Executive Order 14028. Everything else is marked "Lowest Priority: not scheduled for immediate enrichment".

So the scores you see on a new CVE often come from the CNA, from CISA's ADP, or from a vendor, based on the submitted text. In this case the text was fiction. A score computed from fiction is still fiction, it just has a decimal point.

There is also a lag problem. As of this writing, GHSA-vrg3-8p22-cwh8 still shows the 9.8 critical score in GitHub's database, marked unreviewed and not withdrawn, with no affected packages mapped. Rejection upstream does not instantly clean every downstream feed your tools read.

How to triage a scary CVE before acting

The goal is a five-minute check that separates "patch tonight" from "watch this". Every signal below is public and scriptable.

1. Who is the CNA? A CVE assigned by the vendor's own CNA (curl, GitHub and Red Hat are among them) has had a maintainer look at it. One assigned by mitre for a project that has its own security process deserves a harder look. It does not mean fake. It means unverified.

2. What do the references point to? Real records usually link a fix commit, a release note or a vendor advisory. Red flags from this case: a link to a whole source file, a fresh personal repo as the only advisory, "n/a" vendor and product.

3. Is there a reproducer, and does it reproduce? A memory-safety claim should come with a crash trace you can regenerate. If the PoC is one line of SQL and the advisory says "ASan will show a crash", run it.

4. What does the vendor say? Check the project's security page, advisories or forum. SQLite, for example, maintains a CVE status table. Silence for a day is normal. A vendor saying "not a bug" ends the discussion.

5. Is it in CISA KEV? The KEV catalog lists vulnerabilities with evidence of exploitation in the wild. KEV means act now. Absence from KEV means only that exploitation has not been confirmed.

6. What is the EPSS score? EPSS estimates the probability of exploitation activity in the next 30 days. FIRST's historical API shows CVE-2026-51302 at 0.00245 on 28 July and 0.00529 on 29 and 30 July: roughly a half-percent chance, while the CVSS base score read 9.8. When CVSS and EPSS disagree that sharply on a brand-new record, slow down and read the advisory.

Here is the whole check as a script. It needs curl and jq.

#!/usr/bin/env bash
# usage: ./cve-triage.sh CVE-2026-51302
set -euo pipefail
id="$1"
 
echo "== CVE.org record"
curl -s "https://cveawg.mitre.org/api/cve/$id" | jq '{
  state: .cveMetadata.state,
  cna: .cveMetadata.assignerShortName,
  published: .cveMetadata.datePublished,
  affected: [.containers.cna.affected[]? | {vendor, product}],
  references: [.containers.cna.references[]?.url],
  rejected: .containers.cna.rejectedReasons[0].value
}'
 
echo "== NVD change history (who scored it, when)"
curl -s "https://services.nvd.nist.gov/rest/json/cvehistory/2.0?cveId=$id" \
  | jq -r '.cveChanges[].change | "\(.created)  \(.eventName)  \(.sourceIdentifier)"'
 
echo "== CISA KEV"
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \
  | jq -r --arg id "$id" '[.vulnerabilities[] | select(.cveID == $id)] | if length == 0 then "not in KEV" else .[0] end'
 
echo "== EPSS"
curl -s "https://api.first.org/data/v1/epss?cve=$id" | jq '.data'

Read the output against this table before anyone opens a change ticket.

SignalAct nowVerify first
CNAVendor or project CNACNA of last resort, vendor not consulted
AffectedNamed product with version range"n/a" vendor, product or version
ReferencesFix commit, release, vendor advisoryWhole-file link, new personal repo only
ReproducerCrash trace you regeneratedClaimed crash, not reproduced
VendorAdvisory or fixed releaseSilent, or disputes the report
KEVListedNot listed
EPSSHigh, or rising fastLow while CVSS is critical

"Verify first" is not "ignore". It means one engineer spends an hour confirming exposure and reproducing, instead of the whole team shipping an emergency change.

Stop auto-patching from breaking production

In this case there was no SQLite 3.41 fix to install, so a fake critical mostly burned triage time and set off compliance clocks. The more dangerous pattern is a pipeline that treats "critical CVE" as permission to push a dependency bump to production without the normal gates. Blind speed on fake input is how a non-bug becomes a real outage. We covered the pipeline side in our DevSecOps CI/CD guide; the specific guardrails for this problem are:

Security updates go through CI like any other change. In Renovate, keep vulnerability PRs fast but not unattended, and let fresh releases age before they merge:

{
  "extends": ["config:recommended"],
  "vulnerabilityAlerts": {
    "labels": ["security"],
    "automerge": false
  },
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "minimumReleaseAge": "3 days",
      "automerge": true
    },
    {
      "matchUpdateTypes": ["major", "minor"],
      "automerge": false
    }
  ]
}

Time-box suppressions instead of deleting findings. When triage concludes a finding is bogus or not reachable, record the decision with an expiry so it resurfaces if the record changes. Trivy supports an expiry date per line in .trivyignore:

# SQLite maintainers: not a bug (sqlite.org/cves.html). CVE rejected 2026-07-31.
CVE-2026-51302 exp:2026-10-31

Separate the SLA clock from the evidence. If your policy says "criticals fixed in 7 days", define critical as "confirmed by the triage table", not "CVSS 9.0 or above in any feed". Auditors accept a documented, dated decision. They do not accept a mystery rollback.

Keep AI remediation agents on a leash. JFrog points out that an agent handed a fabricated CVE may try to "locate the vulnerable function, generate a patch" for code that does not exist. If you run agents that open fix PRs, gate them behind the same triage signals and a human review. Our AI agent hardening checklist covers scoping what those agents can touch.

For maintainers: require a reproducer

If you maintain a project people depend on, the SQLite and curl cases give you a template.

  • Publish a SECURITY.md that asks for evidence. Require the exact version or commit, build flags, a minimal reproducer, the observed output (sanitizer trace, crash log) and the reporter's own explanation. curl's disclosure policy has, since June 2026, asked reporters to explain issues "in your own human voice" rather than paste AI-generated text.
  • Use a private intake that keeps context. GitHub private vulnerability reporting or HackerOne keep reports, discussion and disclosure in one place. curl tried email and GitHub and went back to HackerOne because that worked for them.
  • Consider becoming a CNA. If your project is its own CNA, CVEs for it go through you. That is how curl controls its own records. It takes process, but it removes the path where a stranger's text becomes a CVE without anyone asking you.
  • Publish a status page for disputed CVEs. SQLite's table gave every downstream user a primary source to cite within days.
  • Remove cash as the only incentive when volume becomes noise. curl ended payouts and saw its confirmed rate recover.

For the wider dependency picture, our software supply chain security guide covers SBOMs and how to know which of your services even ship SQLite.

What to do this week

  1. Search your scanner history for CVE-2026-51296, 51297, 51300, 51302, 51303 and 51304. Close any open tickets with a link to the rejected CVE.org records.
  2. Check that your vulnerability feeds honour REJECTED status, and find out how long they take to do it.
  3. Add the triage script above, or its equivalent, to your on-call runbook.
  4. Change your patch SLA to key off confirmed severity, and require CI for security PRs.
  5. If you maintain code, add the reproducer requirement to SECURITY.md.

If you want a second pair of eyes on your triage process, get in touch.

Sources