Problem and scope
The platform serves millions of repositories. A repository can submit a lockfile, image manifest, or SBOM while vulnerability feeds add, revise, or withdraw records. Design ingestion, version matching, alert lifecycle, notifications, and remediation verification. Support npm, PyPI, and Maven; code scanning, automatic patch merging, and human vulnerability research are out of scope.
What the interviewer is evaluating
Separate “a dependency version matches a vulnerability” from “an alert has a lifecycle.” Ecosystem-aware versions, direct versus transitive dependencies, feed revisions, withdrawals, deduplication, replayable work, notification idempotency, and tenant authorization are the signals. A scan result is evidence for a snapshot, not permanent truth.
Clarifying questions
- Is each manifest locked to exact versions or allowed ranges? Should matching use resolved versions or declarations?
- Are transitive, development, container-layer, and private packages in scope?
- Can a feed withdraw, alias, or revise severity, and which historical snapshot is retained?
- Are alerts branch-scoped, and what happens when a branch is deleted?
- Which channels, cooldowns, and organization-level snoozes are required?
A 30-second answer framework
“I would retain an immutable manifest snapshot, parse an ecosystem-specific dependency graph, and match versions through an indexed vulnerability range. The result becomes an idempotent alert projection keyed by repository, component, vulnerability, and evidence snapshot. Feed revisions trigger incremental recomputation. Ingestion, feed updates, and notifications use durable replayable queues; notifications are aggregated per organization and carry an idempotency key. Alerts retain evidence, while a new commit is rescanned before it can be marked fixed. Reconciliation, authorization, and freshness metrics make the system explainable.”
Step-by-step deep design
The control plane stores organizations, repositories, branches, and notification policies. An ingestion API accepts a manifest with a commit hash, writes the original object and metadata, then emits manifest_received. Repeating the same repository, branch, commit, and manifest digest returns the existing snapshot. Large SBOMs use multipart upload and checksums; per-tenant quotas protect parsers.
Parsers normalize names and versions according to each ecosystem and expand the locked transitive graph. A snapshot records component coordinates, source paths, and development or production scope. Parse failures preserve the original file, error location, and retry state; “could not parse” must not become “no vulnerabilities.”
The feed ingester pulls or receives deltas from sources such as OSV, validates schema, source signatures, and feed versions, then stores affected ecosystems, package names, version ranges, aliases, severity, fixed versions, timestamps, and withdrawal markers. Range matching uses ecosystem semantics rather than string sorting. An inverted index makes high-volume components incremental.
An alert includes alert_id, organization, repository, branch, component coordinate, vulnerability ID, evidence snapshot, status, first-seen time, latest confirmation, fixing commit, and rule version. A unique constraint on (repository, branch, component, vulnerability_id, vulnerable_version) prevents duplicates; aliases are normalized before this key is built. States include OPEN, FIXED, DISMISSED, and REOPENED, with an audit event for every transition. Withdrawal stops new notifications but preserves history and its reason.
Notifications are outside the scan transaction. Alert events enter organization-partitioned queues; an aggregator groups similar alerts, applies cooldowns, and emits stable notification keys. Email, code-host comments, and chat adapters each have limits, retries, and delivery receipts; consumers deduplicate by key. Snoozes have scope, expiry, owner, and reason, while severity escalation can bypass an expired or invalid snooze.
Remediation verification accepts a new commit or scheduled snapshot, reparses the current graph, and applies the same matching rules. An alert closes only when the current default-branch snapshot no longer matches or an organization explicitly accepts the risk. A suggested fixed version is not proof of remediation. Deleted branches produce a termination event without erasing audit history.
Durability comes from replayable events and reconciliation. Compare stored manifests with parse results, matches, alert projections, feed versions, expected notifications, and adapter receipts. Monitor manifest-to-alert p95 latency, parse failures, feed lag, matching throughput, duplicate rate, withdrawal propagation, notification retries, and snooze hits. Inject queue duplicates, crashes during feed revision, stale indexes, adapter timeouts, and database recovery failures.
High-quality sample answer
“I would store the original manifest with its commit hash, parse an ecosystem-specific dependency graph, and maintain an inverted index over vulnerability ranges. Matching creates an idempotent alert with an evidence snapshot and rule version; a unique key prevents duplicate alerts for the same repository, component, and vulnerability. A withdrawal stops new notifications but keeps historical evidence.
Notification events are decoupled from scanning through durable queues, aggregated per organization, and retried with stable keys. Snoozes expire and are audited; severity escalation can wake them. Every new commit is rescanned, and only an actual non-match marks an alert fixed. Reconciliation checks the manifest, feed, alert, and notification boundaries, while metrics cover visibility, withdrawal propagation, backlog, duplicates, and adapter failures. This scales to millions of daily manifests and explains why an alert appeared, changed, or reopened.”
Common mistakes
- Compare package names only → ecosystem and range semantics create false matches → use ecosystem coordinates and range comparison.
- Treat parse failure as safe → a broken lockfile silently hides risk → retain failure evidence and retry.
- Create a new alert on every scan → one issue floods developers → build an idempotent alert key.
- Overwrite feed revisions → historical severity and withdrawal become unexplainable → version the source and retain evidence.
- Send notifications inside scanning → a slow channel blocks detection → decouple with durable events.
- Close on a suggested fix version → the actual commit may still match → rescan the current commit.
- Allow permanent snoozes → risk loses an owner → require expiry, reason, and reminders.
- Alert separately for aliases → one vulnerability appears many times → normalize aliases first.
Follow-up questions and answers
Follow-up 1: How do you handle a revised affected range?
Version the feed update, compute the affected component set, and recompute only matching snapshots. Keep the rule version and evidence on each alert; record every status change.
Follow-up 2: Why retain the original manifest?
Parsers and rules evolve. The original file supports replay under new rules and proves which commit produced the alert.
Follow-up 3: How do you reduce notification fatigue?
Aggregate by organization, vulnerability, and component with cooldowns and summaries. Snoozes are scoped, expiring, and audited; severity escalation can notify immediately.
Follow-up 4: How do you avoid transitive-dependency mistakes?
Use the locked graph, retain parent paths and dependency scope, and show “unknown” when no lockfile can establish the resolved version.
Follow-up 5: How do you model branches?
Treat a branch as a snapshot dimension and include it in the alert key. Deletion ends future work but does not erase audit history.
Follow-up 6: What if the vulnerability feed is unavailable?
Use the last verified version with an explicit freshness warning; do not claim a clean result. Back off, fail over to a mirror, and replay the version delta after recovery.