Inspiration
Security scanners hand you a flat list — severity, a file, a line, and nothing about consequence. A "High" in dead code looks identical to a "High" in an auth helper imported by forty services, so the genuinely dangerous finding gets the same one-line comment as the noise. CVSS isn't risk. We wanted to answer the question every triage misses: if this is exploited, what else breaks — and who owns it? GitLab Orbit's code knowledge graph makes that answer computable for the first time.
What it does
When a security scan finds a vulnerability, Vulnerability Blast Radius:
- Discovers it via the GitLab GraphQL API (with EPSS, KEV, and reachability enrichment).
- Asks Orbit which code actually calls and imports the vulnerable file — the real blast radius.
- Scores a deterministic 0–100 contextual risk (severity + EPSS + KEV + reachability + blast size).
- Resolves the owner from CODEOWNERS so it lands with the right person.
- Posts an idempotent note back into GitLab — and proposes (never silently applies) a severity change.
Live example: a vulnerability in a Ruby model returns 11 cross-file dependents → 96/100 (Critical),
routed to @security-team. The traversal is language-agnostic and verified live on Rust, Go, and
Ruby — the languages Orbit fully indexes today (TypeScript/Python support is rolling out in Orbit).
How we built it
Python (uv-managed), 94 tests at 99% coverage, MIT-licensed. The risk score is pure and
deterministic — no LLM in the number — so it's reproducible and auditable. The core: an
orbit_client that runs a verified two-pass query_graph traversal (reverse CALLS + an
ImportedSymbol-routed IMPORTS), a GraphQL discovery layer, a CODEOWNERS resolver, and a
deterministic risk scorer. We shipped it as a custom Duo agent published to the AI Catalog
plus a Duo Chat skill. Every Orbit assumption was verified against the live API
(glab orbit remote dsl/schema/query), not the docs.
Challenges we ran into
- The intended
Vulnerability → Filegraph path doesn't exist in Orbit — we verified this live and bridged in via the file-path property, then traverse the code graph. IMPORTShas noDefinition→Definitionvariant — import-based dependents must route throughImportedSymbol(found by reading live DSL validation errors).- Orbit node IDs are strings (the docs example is misleading);
project.vulnerabilitiesis Ultimate + default-branch; enabling an agent is project-scoped, not via the global catalog. - Orbit is Beta and point-in-time, so we built drift-tolerant parsing and graceful degradation.
Accomplishments that we're proud of
- A deterministic, testable risk score — not an LLM guess — that gives the same number every run.
- The agent passed 11/11 manual scenarios including adversarial guardrail tests: it refuses to auto-change severity, fabricate a score, post duplicates, or act out of scope.
- Every Orbit traversal verified against the live graph — Rust, Go, and Ruby (the languages Orbit fully indexes today), with real cross-file dependent counts.
- Dogfooding proof: run live, the agent flagged a real CWE-377 insecure-temp-file in our own demo script — triage that works on actual findings, not just curated ones.
What we learned
- Orbit's real value for security is the code graph, not its (currently empty) security nodes — blast radius is a code-reachability question.
- "Verify against the live API, don't trust the example" — the string-ID gotcha would have silently broken parsing in production.
What's next for Vulnerability Blast Radius
- Tune blast-radius depth and add pipeline/job exposure to the score.
- Enrichment fallbacks for findings without a CVE; a weekly digest of the highest-blast-radius vulnerabilities per service; one-click enable for any team on Orbit + Duo.
Built With
- ai-catalog
- gitlab-duo-agent-platform
- gitlab-knowledge-graph
- gitlab-orbit
- graphql
- pydantic
- pytest
- python
- uv
Log in or sign up for Devpost to join the conversation.