The findings list says what is wrong, not what to patch first: one Chrome update closes a hundred rows, and one KEV on a single host matters more than all of them. The new /patch-plan page (GET /api/v1/vulnerabilities/patch-plan) groups open findings (open, patch_failed) on ACTIVE assets by the product to update, using the vulnerability_packages rows, else package_name, else the CVE id. Each group shows hosts, findings, CVEs, the fixed versions as reported (never compared) and summed/max priority_score. Groups with CVEs exploited in the wild (KEV / EUVD) rank first, then by summed priority. Every number comes from data the tool already scores. TypeSafe Jev (via OpenRouter Decisions, pinned typesafe/jev-1.13) is asked one question per open CVE, nightly at 06:00 after the last ingest: is it only exploitable with an optional feature or a non-default configuration? It is the one triage question no feed answers. KEV, EUVD, EPSS, SSVC and the CVSS vector already cover exploitation and attack vector, and exposure is a property of the host, not of the CVE text. The probability is stored in vulnerabilities.jev_config_dependent (migration 061); CVEs at 0.7 or above show under "Check config" on the plan. It never closes a finding (cross-source contract) and never enters priority_score. The job is off by default (Settings, AI card, or JEV_TRIAGE_ENABLED) and reuses the OpenRouter key. It sends only public CVE text. An account or network failure stops the run unstamped; a failure specific to one CVE skips only that CVE, so the job cannot stall on the same CVE every night.
29 lines
961 B
Python
29 lines
961 B
Python
"""Add vulnerabilities.jev_config_dependent + jev_checked_at
|
||
|
||
Revision ID: 061
|
||
Revises: 060
|
||
Create Date: 2026-09-19 10:00:00.000000
|
||
|
||
Jev's answer to "only exploitable with a non-default configuration?", per CVE
|
||
text. An operator hint on the patch plan; it never closes a finding and never
|
||
enters priority_score. No backfill: the nightly Jev job fills it when enabled.
|
||
Idempotent (IF NOT EXISTS), same shape as 053–058.
|
||
"""
|
||
from alembic import op
|
||
|
||
|
||
revision = "061"
|
||
down_revision = "060"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def upgrade() -> None:
|
||
op.execute("ALTER TABLE vulnerabilities ADD COLUMN IF NOT EXISTS jev_config_dependent DOUBLE PRECISION;")
|
||
op.execute("ALTER TABLE vulnerabilities ADD COLUMN IF NOT EXISTS jev_checked_at TIMESTAMP;")
|
||
|
||
|
||
def downgrade() -> None:
|
||
op.execute("ALTER TABLE vulnerabilities DROP COLUMN IF EXISTS jev_checked_at;")
|
||
op.execute("ALTER TABLE vulnerabilities DROP COLUMN IF EXISTS jev_config_dependent;")
|