Skip to content

Review-driven fixes, hardening, docs + risk-based prioritization (KEV/EPSS) - #4

Merged
asharahmed merged 6 commits into
mainfrom
improve/full-review-2026-07-07
Jul 7, 2026
Merged

Review-driven fixes, hardening, docs + risk-based prioritization (KEV/EPSS)#4
asharahmed merged 6 commits into
mainfrom
improve/full-review-2026-07-07

Conversation

@asharahmed

Copy link
Copy Markdown
Owner

Summary

A review-driven improvement pass over Ravelin: a multi-agent code review surfaced correctness bugs and hardening gaps, I fixed the high-value ones, then added the biggest maturity feature — risk-based prioritization (CISA KEV + FIRST EPSS → risk-adjusted SLA). Also ships the STRIDE threat model and architecture docs that were promised but missing.

Build is clean; the unit suite grows 76 → 107, all green. Integration/E2E tiers (which need Docker/real SQL) run in CI.

The branch is organized into three commits — fixes, docs, and the feature — grouped below.


🐛 Correctness & hardening (fix: commit)

  • Critical — empty-scan mass-resolve: an empty or errored scan (structurally-valid but zero findings) no longer auto-resolves every open finding and reports a false 100% compliance. An empty scan is now "no new information"; genuinely-fixed findings still resolve when they drop out of a non-empty scan.
  • Case-insensitive dedup: identity matching now uses OrdinalIgnoreCase to match Azure SQL's collation, fixing duplicate-INSERT 500s (e.g. Newtonsoft.Json vs newtonsoft.json; NuGet ids are case-insensitive).
  • Concurrent-ingest safety: IngestionService retries on the dedup unique-index collision so two overlapping scans reconcile instead of returning a 500 (a rowversion concurrency token is added alongside).
  • CSV formula injection: exported, untrusted scan fields beginning with = + - @ are neutralized so they can't execute in an auditor's spreadsheet.
  • Client auth: an expired/revoked token (401) now clears state and redirects to login instead of surfacing a raw error string; the login page guards returnUrl against open redirects.
  • JWT fail-closed (in the feature commit's Program.cs): removed the all-zeros signing-key fallback — Production refuses to start without a ≥32-byte key; dev/test use an explicit dev key. Tightened ClockSkew to 30s.
  • RBAC integration tests: a new authorization-matrix test suite (Viewer/Analyst/Admin/anonymous across admin, triage, SLA, and read endpoints) + a role-seeding/JWT fixture helper — the biggest coverage gap for a security tool.
  • Read-path scaling (feature commit's RavelinEndpoints.cs): AsNoTracking + SQL-side breach filtering + additive (non-breaking) pagination on the hot read queries.

📄 Docs (docs: commit)

  • docs/THREAT-MODEL.md — full STRIDE model: Mermaid DFD, trust boundaries, each threat mapped to its in-code mitigation, and an honest residual-risks section.
  • docs/architecture.md — Mermaid system diagram + walkthrough.
  • README — fixed stale drift (Key Vault is live, not "roadmap"; accurate stage table) and added a live-demo/API-docs section and a "What's inside" feature list.

✨ Risk-based prioritization (feat: commit)

Severity/CVSS alone is a weak triage signal — real programs prioritize by exploitation in the wild.

  • Enrichment: findings are enriched with CISA KEV (actively exploited) and FIRST EPSS (exploitation probability) via the public feeds. Runs as part of the hourly SLA re-evaluation and on demand (POST /api/admin/enrich). Config-gated and inert by default (VulnIntel:Enabled) — no external calls locally; feed outages are swallowed.
  • Risk-adjusted SLA: a KEV finding gets a 14-day deadline, EPSS ≥ 0.5 gets 30 days — tighten-only (never extends a Critical's 7 days). Bonus: an exploited Unknown-severity finding now gets tracked instead of escaping SLA.
  • Prioritization: findings sort KEV-first, then severity, then EPSS; a "⚠ Exploited" / "Likely exploited" badge + EPSS% shows on each finding; the dashboard gains an "Actively exploited" count.
  • New surface: GET /api/risk-policy, POST /api/admin/enrich, new Finding columns (+ migration), enriched DTOs, KEV/EPSS in CSV export.
  • Tests: risk ranking, risk-adjusted SLA math, CVE extraction, and KEV/EPSS JSON parsing.

Deploy / activation

VulnIntel__Enabled=true is added to the Container App (Terraform). To activate live: terraform apply → deploy (migrations apply on boot) → trigger POST /api/admin/enrich or wait for the hourly re-eval → the dashboard's "Actively exploited" count and finding badges populate.

Verification

  • dotnet build clean; dotnet test unit tier 107 passing.
  • Ran the real host locally and smoke-tested: boots cleanly with all changes, new endpoints published in OpenAPI and correctly auth-gated (401), security headers intact. Full data-flow needs SQL (no Docker locally) → covered by CI integration/E2E tiers.

Notes for the reviewer

  • The feat: commit's Program.cs / RavelinEndpoints.cs also carry the JWT fail-closed and read-path scaling changes, because those hardening edits share files with the feature (called out above and in the commit body).
  • Follow-ups not in this PR: the infra fixes from the review (tfstate lockdown, deploy health gate, CI identity least-privilege, managed-identity SQL auth) need terraform apply against live Azure; a TimeProvider refactor for deterministic time-tests; typed results for richer OpenAPI schemas.

…C tests

From a multi-agent review of the codebase:

- ScanReconciler: an empty/errored scan no longer auto-resolves every open
  finding (which reported false 100% compliance); dedup identity matching is
  now case-insensitive to match Azure SQL collation (fixes duplicate-INSERT
  500s on e.g. Newtonsoft.Json vs newtonsoft.json).
- IngestionService: retry on the dedup unique-index collision so two
  concurrent scans of a project reconcile instead of 500-ing.
- Csv.Field: neutralize spreadsheet formula-injection triggers (=,+,-,@) in
  exported, untrusted scan fields.
- Client: TokenHandler now clears the token and redirects to login on a 401
  (expired/revoked session) instead of leaking a raw error string; Login
  guards returnUrl against open-redirect.
- Tests: RBAC authorization-matrix integration tests + a role-seeding/JWT
  fixture helper (the biggest coverage gap for a security tool).
- docs/THREAT-MODEL.md: full STRIDE model with a Mermaid data-flow diagram,
  trust boundaries, each threat mapped to its in-code mitigation, and an
  honest residual-risks section.
- docs/architecture.md: Mermaid system diagram + walkthrough.
- README: fix stale drift (Key Vault is live, not 'on the roadmap'; accurate
  stage table), add a live-demo + API-docs section and a 'What's inside'
  feature list.
…ed SLA)

Severity/CVSS alone is a weak triage signal. Findings are now enriched with
CISA KEV (known actively exploited) and FIRST EPSS (predicted exploitation
probability), and those signals drive both a risk-adjusted SLA and prioritized
ordering.

- Domain: RiskEvaluator (rank + label), SlaEvaluator.ComputeDueDate risk-aware
  overload (KEV/high-EPSS tighten the deadline, never loosen; also gives an
  exploited Unknown-severity finding a deadline), CveIdentifier.
- Infrastructure: VulnerabilityIntelligenceClient (public KEV feed + EPSS API,
  pure testable parsers, failures swallowed), FindingEnrichmentService, wired
  into the hourly SLA re-evaluation; config-gated + inert by default
  (VulnIntel:Enabled), so local/dev/test make no external calls.
- API/UI: Finding gains KEV/EPSS columns (+migration); FindingDto/DashboardDto
  carry the signals; risk-first sort on findings; GET /api/risk-policy and
  POST /api/admin/enrich; dashboard 'Actively exploited' count + finding risk
  badges; VulnIntel__Enabled=true added to the Container App (Terraform).
- Tests: risk math, risk-adjusted SLA, CVE extraction, KEV/EPSS parsing
  (unit suite 76 -> 107 across the branch).

Program.cs and RavelinEndpoints.cs in this commit also carry same-review
hardening that shares these files: JWT fail-closed on a missing/weak signing
key (no more all-zeros fallback) + tighter ClockSkew, and read-path
AsNoTracking + SQL-side breach filtering + additive pagination.
Ravelin's own SCA gate flagged the transitive Microsoft.OpenApi 2.0.0 (pulled
by Microsoft.AspNetCore.OpenApi/Scalar) as vulnerable to GHSA-v5pm-xwqc-g5wc
(circular-schema parsing DoS; patched in 2.7.5). Pin it directly so the patched
version resolves across the whole solution. The dogfood loop working as intended.
The Azure DevOps pipeline ran 'dotnet test Ravelin.slnx' (whole solution),
which pulls in the Testcontainers integration tier and the Playwright E2E tier
on an agent that provisions neither — so the test step failed. Those tiers run
in GitHub Actions (security.yml), which sets them up. Run only the fast unit
project here, as the build/containerize/deploy gate.
A PR validation build (triggered by branch policy) shouldn't push a production
image to ACR or deploy to Container Apps — it should just build and unit-test.
Gate the Image and Deploy stages on Build.Reason != PullRequest so PR checks
stop at the Build stage; the full build->image->deploy still runs on main.
@asharahmed
asharahmed merged commit 2008bf3 into main Jul 7, 2026
10 checks passed
@asharahmed
asharahmed deleted the improve/full-review-2026-07-07 branch July 7, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant