Skip to content

fix(migration): re-derive base_score from advisory_vulnerability_score#2536

Open
ctron wants to merge 7 commits into
guacsec:mainfrom
ctron:hotfix/tc_5272_1
Open

fix(migration): re-derive base_score from advisory_vulnerability_score#2536
ctron wants to merge 7 commits into
guacsec:mainfrom
ctron:hotfix/tc_5272_1

Conversation

@ctron

@ctron ctron commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix migration m0002270 that re-derives vulnerability.base_score, base_severity, and base_type from advisory_vulnerability_score
  • Fixes two issues discovered while investigating TC-5272:
    1. NULL base_score ("Unknown" in UI): CVEs ingested on 0.2.x before base_score columns existed were never backfilled after upgrading
    2. Wrong base_type: Migration m0002170 hardcoded base_type = '3.0' for all existing scores, even v4.0 and v2.0 ones
  • Refactors CVE score extraction to use a single code path (removes duplicate extract_base_score/get_score logic)
  • Fixes score computation when CVE JSON only contains vectorString without individual metric fields

Workaround

To fix existing data without deploying, run this SQL directly on the database:

UPDATE vulnerability
SET base_score = best.score,
    base_severity = best.severity,
    base_type = best.type
FROM (
    SELECT DISTINCT ON (avs.vulnerability_id)
           avs.vulnerability_id,
           round(avs.score::numeric, 1) AS score,
           avs.severity,
           avs.type
    FROM advisory_vulnerability_score avs
    JOIN vulnerability v ON v.id = avs.vulnerability_id
    WHERE v.authoritative_advisory_id IS NOT NULL
      AND avs.advisory_id = v.authoritative_advisory_id
    ORDER BY avs.vulnerability_id, avs.type DESC, avs.score DESC
) best
WHERE best.vulnerability_id = vulnerability.id;

Note: This only works if the data migration m0002010_add_advisory_scores has been run and advisory_vulnerability_score is populated. If that table is empty, the data migration must be run first (trustd db data m0002010_add_advisory_scores).

Test plan

  • cvss_v4_score_visibility — verifies all endpoints return correct scores after fresh ingestion
  • cvss_v4_score_after_cve_history — verifies re-ingestion of all 6 historical revisions preserves correct scores
  • cvss_v4_score_after_migration::from_02z — reproduces TC-5272: CVE ingested on 0.2.x → NULL base_score after migration → fix migration backfills it
  • cvss_v4_score_after_migration::from_03z — CVE ingested on 0.3.z → wrong base_type after migration → fix migration corrects it
  • cvss_v4_score_after_migration::from_04z — CVE ingested on 0.4.z → wrong base_type after migration → fix migration corrects it
  • base_score_after_ingestion — existing parameterized test with new CVSS v4.0 case, all pass
  • Full workspace test suite passes

Implements TC-5272

🤖 Generated with Claude Code

ctron and others added 7 commits July 22, 2026 15:29
Add CVE-2025-6965 as a test fixture and verify that its CVSS v4.0 score
(High, 7.2) is correctly exposed through all vulnerability endpoints:
detail, detail with ?scores=true, and list.

Implements TC-5272

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend cvss_v4_score_visibility to simulate the post-migration state
where advisory_vulnerability_score has valid CVSS scores but
vulnerability.base_score is NULL. Re-running the data migration
(m0002010_add_advisory_scores) repopulates advisory scores but does
not backfill vulnerability.base_score, confirming the migration gap.

Implements TC-5272

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a test that imports a 0.4.z database dump (with CVE-2025-6965 ingested),
runs all pending migrations, and verifies that base_score preserves the
correct CVSS v4.0 type. Currently fails because migration m0002170 blindly
sets base_type='3.0' for all rows, even those with v4.0 or v2.0 scores.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_score

Migration m0002170 set base_type='3.0' for all vulnerability rows with a
non-null base_severity, but the ingestor had been writing v2.0 and v4.0
scores to those columns since October 2025. Add a corrective migration
that re-derives base_score, base_severity, and base_type from the
authoritative advisory's entries in advisory_vulnerability_score, using
the same "highest CVSS version, then highest score" logic as the Rust
ingestor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rized rstest

Add a database dump from the 0.3.z branch (with CVE-2025-6965 ingested)
and merge the 0.3.z and 0.4.z migration tests into a single parameterized
rstest. Both cases verify that base_score preserves the correct CVSS v4.0
type after migrating to current.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ions

Ingest all 6 historical revisions of CVE-2025-6965 from the cvelistV5
repo in chronological order and verify the final base_score is correct.
Confirms the CveLoader handles re-ingestion correctly — each update
preserves the CVSS v4.0 type and score.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nown score

Add a database dump from v0.2.19 (no base_score columns) to the migration
test. This reproduces the actual TC-5272 bug: CVE-2025-6965 was ingested
on 0.2.x before base_score existed, and after upgrading the score was
never backfilled — resulting in NULL base_score ("Unknown" in the UI).

Implements TC-5272

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @ctron, your pull request is larger than the review limit of 150000 diff characters

@ctron
ctron requested a review from rh-jfuller July 23, 2026 12:22
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 72.20%. Comparing base (e47dacf) to head (5126d25).

Files with missing lines Patch % Lines
...es/fundamental/src/vulnerability/endpoints/test.rs 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2536      +/-   ##
==========================================
+ Coverage   72.17%   72.20%   +0.03%     
==========================================
  Files         465      466       +1     
  Lines       28702    28719      +17     
  Branches    28702    28719      +17     
==========================================
+ Hits        20715    20736      +21     
+ Misses       6812     6802      -10     
- Partials     1175     1181       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant