Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Designing an AST Findings Management System

Hello! Welcome back.

In our recent lessons, we've designed the core engines for the three main pillars of Application Security Testing (AST): Static Application Security Testing (SAST), Dynamic Application Security Testing (DAST), and Software Composition Analysis (SCA). Each of these tools is a complex system in its own right, and they all share a common purpose: to generate findings—potential vulnerabilities in your software.

This leads us to a critical system design challenge. In any large-scale engineering environment, running these tools will produce a high volume of findings from different sources, in different formats, and with a lot of noise. How do we manage this flood of data effectively?

This is where our next topic comes in. The learning outcome for this lesson is to: Design a findings management system for AST tools, including deduplication, severity scoring, and integration with bug trackers.

We will architect a centralized platform that can ingest, normalize, triage, and track security findings from discovery to remediation. This system acts as the brain of an application security program, turning raw scanner output into an actionable workflow for engineering teams.

1. The Problem: From Detection to Management

At scale, the raw output of AST scanners creates significant problems. Without a centralized system, security and development teams face a chaotic reality. Let's look at a real-world example from GitHub, which faced precisely this issue before building its own solution.

Scaling vulnerability management across thousands of services and ...

This blog post from GitHub, 'Scaling vulnerability management...', perfectly articulates the problems that a findings management system is designed to solve. Pay close attention to the challenges they describe.

Please read the section 'Our challenges'. It details the operational overhead and inconsistent user experience that arise from having fragmented, bespoke processes for handling security findings.

As the article highlights, the key challenges are:

  • Operational Overhead: Multiple tools and processes create redundant work.
  • Inconsistent User Experience: Developers receive findings in different ways, leading to confusion.
  • Lack of Centralized View: It's impossible to measure the organization's overall security posture or prioritize effectively.
  • Noise: The same vulnerability might be reported by multiple scanners, creating duplicate work.

Our goal is to design a system that solves these problems. This type of system is often called a Vulnerability Management Platform, or more recently, an Application Security Posture Management (ASPM) tool.

2. High-Level Architecture

Let's start with a high-level view of the components. A findings management system is essentially a specialized data processing pipeline and web application.

DefectDojo High-Level Architecture
This is the high-level architecture of DefectDojo, a popular open-source findings management tool. It illustrates a typical design pattern for such systems: a web server (NGINX, uWSGI) handles user interactions, a relational database (e.g., MySQL/PostgreSQL) stores the findings, and a background task queue (Celery, Redis) processes asynchronous jobs like ingesting large scan reports.

Based on this, we can outline the core architectural blocks:

  1. Ingestion & Normalization Layer: Adapters that parse various scanner outputs and map them to a unified data model.
  2. Processing Engine: The core logic for deduplication, severity scoring, and lifecycle management.
  3. Data Store: A database optimized for storing and querying findings.
  4. Presentation Layer (UI/API): A web interface for security teams and an API for integrations.
  5. Integration & Notification Layer: Components that push data to external systems like bug trackers and send alerts.
  6. Asynchronous Task Workers: A queueing system to handle long-running tasks like parsing large reports or running analytics jobs without blocking the user interface.

Let's architect the most critical parts of this system, starting with how data gets in.

3. Ingestion and Normalization Pipeline

Our system needs to be a "single source of truth." To achieve this, it must be able to ingest findings from any source. This is what the GitHub article calls "broad intelligence ingestion."

The Architectural Task: Design a pipeline that takes heterogeneous input and produces a homogeneous output.

  1. Input Adapters (Parsers): The system's entry point is a set of parsers, one for each supported tool. For example:

    • A parser for SARIF (Static Analysis Results Interchange Format), a standard used by many SAST tools like GitHub's CodeQL.
    • A parser for ZAP's XML/JSON report format.
    • A parser for CycloneDX or SPDX, the standard formats for a Software Bill of Materials (SBOM) from SCA tools.
    • Parsers for manual sources like bug bounty reports or penetration test results.
  2. The Normalized Finding Model: Each parser's job is to transform the source data into a single, standardized data model. As a software engineer, you know the importance of a well-defined internal representation. A robust Finding model is the heart of our system. Its schema might include fields like:

    • title: A concise summary of the vulnerability.
    • description: A detailed explanation, including remediation advice.
    • severity_tool: The severity assigned by the source tool (e.g., "High", "Critical").
    • vulnerability_id: A stable identifier (e.g., CVE-2021-44228, CWE-79).
    • scanner_source: The tool that found the issue (e.g., 'Trivy', 'CodeQL').
    • location: A structured object describing where the finding is (e.g., { "file": "UserService.java", "line": 123 } or { "component": "log4j-core", "version": "2.14.1" }).
    • fingerprint: A unique, stable hash of the finding's core attributes, used for deduplication. We'll design this next.
    • status: The current state in the lifecycle (e.g., New, Triaged, Resolved, False Positive).

Once a finding is normalized, it's ready for processing. The first and most important step is to figure out if we've seen it before.

4. Designing the Deduplication Engine

Noise is the enemy of an effective security program. Deduplication is the primary weapon against it. The goal is to ensure that a single, unique vulnerability is represented by only one "original" finding in our system, even if it's reported by multiple scans or multiple tools.

Let's explore the architectural options for building a deduplication engine, using the open-source tool DefectDojo as our guide.

About Deduplication - DefectDojo Documentation

The documentation for DefectDojo provides an excellent, practical breakdown of different algorithms for deduplication. This will give us concrete patterns to use in our design.

Please read the following sections: 'About Deduplication' and 'How DefectDojo handles duplicates': This introduces the core concept of centralizing work on an 'original' finding. 'Overview' through 'Unique ID From Tool or Hash Code': This is the key part. It describes the specific algorithms we can use to identify a duplicate. Focus on understanding the difference between using a tool's ID and calculating a custom hash code.

Based on the reading, we can design our deduplication engine around a few key strategies. When a new, normalized finding comes in, our engine compares it against existing "active" findings in the database. A match is found if:

  1. Unique ID from Tool: The scanner provides a stable, unique ID for the vulnerability, and this ID matches an existing finding.

    • Logic: new_finding.vuln_id_from_tool == existing_finding.vuln_id_from_tool
    • Trade-off: Simple and fast, but relies entirely on the quality of the upstream tool. If the tool changes its ID format, deduplication breaks.
  2. Hash Code: Our system computes its own stable fingerprint (a hash) based on the finding's attributes. This gives us more control.

    • Logic: hash(new_finding.attributes) == hash(existing_finding.attributes)
    • Design Decision: Which attributes do we include in the hash? This is context-dependent:
      • For a SAST finding: hash(vulnerability_type + file_path + line_number)
      • For an SCA finding: hash(cve_id + component_name + package_path)
      • For a DAST finding: hash(vulnerability_type + url + parameter)
    • Trade-off: More resilient to changes in upstream tools, but requires careful design of the hash function for each finding type to avoid hash collisions or instability.
  3. Hybrid Approach (Recommended): A combination of the two provides the most robust solution.

    • Logic: A finding is a duplicate if (it has the same tool-provided ID) OR (it has the same calculated hash code).

When a duplicate is found, the system marks the new finding as a Duplicate of the original and can discard it or store it for audit purposes. All triage and remediation work is then centralized on the single, original finding.

5. Advanced Severity Scoring and Prioritization

Not all vulnerabilities are created equal. A "critical" finding in a non-production, internal-only test application is less important than a "medium" finding in your public-facing payment processing service. The "severity scoring" part of our outcome requires a design that goes beyond the simple High/Medium/Low rating from a scanner.

Let's design a risk-scoring engine that provides the "just-in-time context" mentioned in the GitHub article. Our system can calculate a more meaningful Risk Score using a multi-factor model:

Let's break down the components of this model:

  • Base Score: This is the initial severity from the scanner, often based on a CVSS (Common Vulnerability Scoring System) score. We can normalize this to a numeric value (e.g., Critical=10, High=8).
  • Asset Criticality: Our system must allow users to tag or classify the applications being scanned. For example, an application can be tagged with its environment (production, staging) and data sensitivity (PII, public). We can then create rules: if environment == 'production' -> AssetCriticality = 10.
  • Exploitability Intelligence: Is there a known, working exploit for this vulnerability? Our system can integrate with threat intelligence feeds like the CISA Known Exploited Vulnerabilities (KEV) catalog. if cve in KEV -> Exploitability = 10.
  • Compensating Controls: Is the vulnerability mitigated by another security layer? For example, if a DAST scanner finds an SQL injection, but the endpoint is protected by a WAF that blocks the attack, the risk is lower. This is harder to model but can be added as a manual adjustment factor during triage.

This approach transforms the system from a simple finding database into an intelligent prioritization engine.

6. Workflow Integration & Lifecycle Management

A findings management system is useless if it doesn't drive action. The final piece of our design is integrating with the developer's workflow, primarily through bug trackers like Jira or GitHub Issues. This directly addresses a key requirement from the GitHub article: "bring security to where developers work."

Here is the architectural flow for this integration:

  1. User-Triggered Action: Inside our platform, a security analyst triages a New finding, confirms it's a true positive, and clicks "Create Ticket".
  2. API Call to Bug Tracker: The system makes an API call to the configured bug tracker (e.g., POST /rest/api/2/issue for Jira). The request body is pre-populated with all the normalized finding data:
    • Title: The finding's title.
    • Description: A template containing the full description, location, evidence, and remediation advice. Crucially, it should include a link back to the finding in our management platform for a single source of truth.
    • Labels/Tags: security, sast, critical-risk.
    • Assignee: The team that owns the affected code, looked up from an internal ownership database.
  3. Two-Way Synchronization:
    • Our system stores the ID of the created Jira ticket (JIRA-1234) and updates the finding's status to Triaged or Pending Fix.
    • We configure a webhook in the bug tracker. When a developer moves the Jira ticket from In Progress to Done, Jira sends a POST request to an endpoint on our server.
    • Our webhook handler receives this event, looks up the associated finding, and can automatically update its status to Resolved. The finding is now queued for a verification scan.

Finally, our system must manage the full lifecycle, including exceptions, as described in the GitHub blog. We need to support statuses like False Positive and Risk Accepted. These often require an approval process. A sophisticated design could implement this using the integrated platform itself—for instance, requiring an approval on a GitHub Pull Request to a configuration file before a risk is formally accepted, creating an auditable trail.

Conclusion

In this lesson, we designed the architecture of a comprehensive findings management system. We moved from identifying the core problems of noise and lack of centralization to architecting a platform that addresses them through a structured pipeline.

Key Takeaways:

  • A findings management system's primary role is to be a single source of truth by ingesting findings from diverse sources into a normalized data model.
  • Deduplication is a non-negotiable feature for reducing noise. A robust design uses a hybrid approach, combining tool-provided IDs and custom-calculated hash-based fingerprints.
  • Effective severity scoring goes beyond scanner-provided ratings. A mature system calculates a risk score based on multiple factors, including asset criticality and real-world exploitability.
  • Deep workflow integration with bug trackers like Jira or GitHub Issues via APIs and webhooks is essential for developer adoption and closing the remediation loop.

Preview of the Next Lesson:

We have now designed the core engines for SAST, DAST, and SCA, and a system to manage their output. In our next lesson, we will step back and take a strategic view. We will address the outcome: "Evaluate the architectural trade-offs across SAST, DAST, and SCA regarding coverage, accuracy, and CI/CD integration speed." This will help us understand how to combine these tools into a cohesive and effective application security program.

Can't find a good explanation? Sign up and we'll make it for you

Sign up