|
| 1 | +-- Blast-radius analysis pipeline (CM-1328). Mirrors the 4-stage PoC |
| 2 | +-- (linux-foundation/blast-radius): intel -> dependents -> reachability -> report. |
| 3 | +-- Everything the PoC kept as JSON files under data/<VULN-ID>/ moves to these |
| 4 | +-- tables so runs are resumable, queryable, and inspectable across ecosystems. |
| 5 | +-- The aggregated report (naive vs true blast radius, bucket counts) is not |
| 6 | +-- materialized here — it's derived from blast_radius_verdicts at read time. |
| 7 | + |
| 8 | +-- One row per submitted job (backend's submitBlastRadiusJob). id matches the |
| 9 | +-- analysisId the API already generates before starting the Temporal workflow. |
| 10 | +CREATE TABLE IF NOT EXISTS blast_radius_analyses ( |
| 11 | + id UUID PRIMARY KEY, |
| 12 | + -- Raw GHSA/CVE id from the request; the advisory may not be synced from |
| 13 | + -- deps.dev BQ yet, so advisory_id is resolved (and filled in) later. |
| 14 | + advisory_osv_id TEXT NOT NULL, |
| 15 | + advisory_id BIGINT REFERENCES advisories (id), |
| 16 | + -- Raw value from the request (purl or bare name); may not resolve to a |
| 17 | + -- known package yet. package_id is filled in once/if it resolves. |
| 18 | + package_name TEXT, |
| 19 | + package_id BIGINT REFERENCES packages (id), |
| 20 | + ecosystem TEXT NOT NULL, |
| 21 | + status TEXT NOT NULL DEFAULT 'pending' |
| 22 | + CHECK (status IN ('pending', 'running', 'done', 'failed')), |
| 23 | + force BOOLEAN NOT NULL DEFAULT FALSE, |
| 24 | + -- Stage 2 (dependents) run metadata — the per-dependent rows live in |
| 25 | + -- blast_radius_dependents; these are the population-level numbers report |
| 26 | + -- rendering needs alongside them (dependents_doc.source / candidates_considered). |
| 27 | + dependents_source TEXT, |
| 28 | + candidates_considered INT, |
| 29 | + total_cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0, |
| 30 | + error TEXT, |
| 31 | + started_at TIMESTAMPTZ, |
| 32 | + completed_at TIMESTAMPTZ, |
| 33 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 34 | + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 35 | +); |
| 36 | + |
| 37 | +CREATE INDEX IF NOT EXISTS blast_radius_analyses_advisory_id_idx |
| 38 | + ON blast_radius_analyses (advisory_id); |
| 39 | +CREATE INDEX IF NOT EXISTS blast_radius_analyses_package_id_idx |
| 40 | + ON blast_radius_analyses (package_id) WHERE package_id IS NOT NULL; |
| 41 | +CREATE INDEX IF NOT EXISTS blast_radius_analyses_status_idx |
| 42 | + ON blast_radius_analyses (status); |
| 43 | + |
| 44 | +-- Stage 1 output — one row per analysis (symbol_spec.json equivalent). |
| 45 | +CREATE TABLE IF NOT EXISTS blast_radius_symbol_specs ( |
| 46 | + id BIGSERIAL PRIMARY KEY, |
| 47 | + analysis_id UUID NOT NULL UNIQUE REFERENCES blast_radius_analyses (id), |
| 48 | + vuln_id TEXT NOT NULL, |
| 49 | + aliases TEXT[], |
| 50 | + package TEXT NOT NULL, |
| 51 | + ecosystem TEXT NOT NULL, |
| 52 | + affected_ranges JSONB NOT NULL, |
| 53 | + vulnerable_versions TEXT[] NOT NULL, |
| 54 | + analyzed_version TEXT NOT NULL, |
| 55 | + related_affected_packages TEXT[], |
| 56 | + vulnerable_symbols JSONB NOT NULL, -- [{name, kind, defined_in, exported_as[], notes}] |
| 57 | + import_signatures JSONB NOT NULL, -- {style: [signature, ...]} |
| 58 | + exploit_preconditions TEXT, |
| 59 | + reachability_notes TEXT, |
| 60 | + confidence NUMERIC(3, 2) NOT NULL CHECK (confidence BETWEEN 0.00 AND 1.00), |
| 61 | + sources TEXT[], |
| 62 | + summary TEXT, |
| 63 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 64 | +); |
| 65 | + |
| 66 | +-- Stage 2 output — one row per candidate dependent, analyzed or excluded. |
| 67 | +-- Unifies dependents.json's "analyzed" and "excluded_by_range" lists: the |
| 68 | +-- excluded ones never got a resolved version/tarball, hence those columns |
| 69 | +-- are nullable. |
| 70 | +CREATE TABLE IF NOT EXISTS blast_radius_dependents ( |
| 71 | + id BIGSERIAL PRIMARY KEY, |
| 72 | + analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id), |
| 73 | + package_id BIGINT REFERENCES packages (id), |
| 74 | + name TEXT NOT NULL, |
| 75 | + version TEXT, |
| 76 | + downloads BIGINT, |
| 77 | + declared_range TEXT, |
| 78 | + dependency_kind TEXT, -- 'dependencies' | 'peerDependencies' | 'optionalDependencies' |
| 79 | + range_includes_vuln BOOLEAN, |
| 80 | + range_check TEXT, -- 'matched' | 'excluded' | 'unparseable-included' |
| 81 | + tarball_url TEXT, |
| 82 | + excluded_by_range BOOLEAN NOT NULL DEFAULT FALSE, |
| 83 | + exclusion_reason TEXT, |
| 84 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 85 | + UNIQUE (analysis_id, name) |
| 86 | +); |
| 87 | + |
| 88 | +CREATE INDEX IF NOT EXISTS blast_radius_dependents_analysis_id_idx |
| 89 | + ON blast_radius_dependents (analysis_id); |
| 90 | +CREATE INDEX IF NOT EXISTS blast_radius_dependents_package_id_idx |
| 91 | + ON blast_radius_dependents (package_id) WHERE package_id IS NOT NULL; |
| 92 | + |
| 93 | +-- Stage 3 output — one reachability verdict per analyzed (non-excluded) dependent. |
| 94 | +CREATE TABLE IF NOT EXISTS blast_radius_verdicts ( |
| 95 | + id BIGSERIAL PRIMARY KEY, |
| 96 | + analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id), |
| 97 | + dependent_id BIGINT NOT NULL UNIQUE REFERENCES blast_radius_dependents (id), |
| 98 | + uses_package BOOLEAN NOT NULL, |
| 99 | + imports_vulnerable_symbol BOOLEAN NOT NULL, |
| 100 | + import_style TEXT, -- 'main-member' | 'deep-import' | 'standalone-pkg' | 'reexport' | 'none' |
| 101 | + reachable_verdict TEXT NOT NULL CHECK (reachable_verdict IN ('affected', 'not_affected', 'unclear')), |
| 102 | + confidence NUMERIC(3, 2) NOT NULL CHECK (confidence BETWEEN 0.00 AND 1.00), |
| 103 | + evidence JSONB, -- [{file, line, snippet}] |
| 104 | + reasoning TEXT, |
| 105 | + model TEXT, |
| 106 | + turns_used INT, |
| 107 | + cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0, |
| 108 | + error TEXT, |
| 109 | + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() |
| 110 | +); |
| 111 | + |
| 112 | +CREATE INDEX IF NOT EXISTS blast_radius_verdicts_analysis_id_idx |
| 113 | + ON blast_radius_verdicts (analysis_id); |
| 114 | +CREATE INDEX IF NOT EXISTS blast_radius_verdicts_analysis_id_verdict_idx |
| 115 | + ON blast_radius_verdicts (analysis_id, reachable_verdict); |
| 116 | + |
| 117 | +-- Performance monitoring — one row per (analysis, stage), independent of the |
| 118 | +-- ecosystem-specific pipeline tables above so it keeps working unchanged as |
| 119 | +-- more ecosystems (and possibly different stage sets) come online. |
| 120 | +CREATE TABLE IF NOT EXISTS blast_radius_stage_runs ( |
| 121 | + id BIGSERIAL PRIMARY KEY, |
| 122 | + analysis_id UUID NOT NULL REFERENCES blast_radius_analyses (id), |
| 123 | + stage TEXT NOT NULL CHECK (stage IN ('intel', 'dependents', 'reachability', 'report')), |
| 124 | + status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'succeeded', 'failed')), |
| 125 | + model TEXT, -- agent model used by this stage, NULL for deterministic stages |
| 126 | + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), |
| 127 | + completed_at TIMESTAMPTZ, |
| 128 | + duration_ms BIGINT, |
| 129 | + cost_usd NUMERIC(10, 4) NOT NULL DEFAULT 0, |
| 130 | + error TEXT, |
| 131 | + UNIQUE (analysis_id, stage) |
| 132 | +); |
| 133 | + |
| 134 | +CREATE INDEX IF NOT EXISTS blast_radius_stage_runs_analysis_id_idx |
| 135 | + ON blast_radius_stage_runs (analysis_id); |
0 commit comments