Skip to main content

norm-findings

A lean CLI tool for normalizing security scanner findings based on DefectDojo parsers.

This project provides a standalone Python package and a minimal Docker image to convert findings from O(100) security scanners into a normalized format.

Open Source Attribution

This project is based on the excellent work of the DefectDojo community. We leverage their parser logic while providing a lean, dependency-minimized execution environment. See the NOTICE file for more details.

Installation

The default installation includes the core CLI and all parser dependencies, providing full functionality out-of-the-box.

Standard (Core + Parsers)

pip install .

Optional: Server Support

If you need the REST API server, install the server extra:

pip install ".[server]"

Optional: Development

For running tests or contributing:

pip install ".[dev]"

Running Tests

Unit Tests

Verify the core installation and stubs:

pytest tests/test_cli.py

E2E Parser Verification (Development only)

To verify all 200+ parsers against real DefectDojo sample data:

  1. Ensure the development dependencies are installed (pip install ".[dev]").
  2. Run the updater to fetch sample data:
    python -m norm_findings.updater
    
  3. Run the E2E tests:
    pytest tests/test_e2e.py
    

Usage

norm-findings supports 200+ security scanners and accepts parser names in a case-insensitive manner, with or without the "Parser" suffix.

CLI

The CLI supports all parsers generically - just specify the parser name (case-insensitive):

# Basic usage - parser names are case-insensitive
norm-findings convert --parser trivy --input-file trivy.json --output-file findings.json

# Works with any case
norm-findings convert --parser TRIVY --input-file trivy.json --output-file findings.json
norm-findings convert --parser Trivy --input-file trivy.json --output-file findings.json

# Works with or without "Parser" suffix
norm-findings convert --parser TrivyParser --input-file trivy.json --output-file findings.json
norm-findings convert --parser anchore_grype --input-file grype.json --output-file findings.json

# With custom test label
norm-findings convert --parser bandit --input-file bandit.json --output-file findings.json --test "My App v1.0"

# Print findings to console
norm-findings convert --parser semgrep --input-file semgrep.json --output-file findings.json --print

Available parsers (200+): trivy, anchore_grype, bandit, semgrep, snyk, checkmarx, fortify, zap, burp, aqua, blackduck, sonarqube, veracode, and many more. See parser_mapping.json for the complete list.

Docker

# Case-insensitive parser names work in Docker too
docker run -v $(pwd):/dojo -it ghcr.io/scribe-security/norm-findings:latest \
  convert --parser trivy --input-file /dojo/trivy.json --output-file /dojo/findings.json

docker run -v $(pwd):/dojo -it ghcr.io/scribe-security/norm-findings:latest \
  convert --parser ANCHORE_GRYPE --input-file /dojo/grype.json --output-file /dojo/findings.json

Using as a Library (API)

The recommended way to use norm-findings as a library is through the high-level API, which handles parser resolution automatically:

from norm_findings.api import parse_findings

# Parse findings - parser name is case-insensitive
findings = parse_findings(
    parser_name="trivy",  # Case-insensitive: "trivy", "TRIVY", "Trivy" all work
    input_file="trivy.json",
    test_label="My Application v1.0",
    output_file="findings.json"  # Optional: writes findings to file
)

# Process findings
for finding in findings:
    print(f"{finding.severity}: {finding.title}")
    if finding.description:
        print(f"  Description: {finding.description}")

More API Examples:

# Example 1: Parse without writing to file
findings = parse_findings(
    parser_name="anchore_grype",
    input_file="grype-results.json",
    test_label="Production Scan"
)
print(f"Found {len(findings)} vulnerabilities")

# Example 2: Parsing with Robustness (Capture Errors)
# For robustness, parsers log errors instead of crashing. 
# You can capture these errors using return_details=True.
result = parse_findings(
    parser_name="semgrep",
    input_file="semgrep_report.json",
    return_details=True
)

findings = result['findings']
errors = result['errors']

if not result['success']:
    print(f"Warning: Parsed {len(findings)} findings but encountered {len(errors)} errors:")
    for error in errors:
        print(error)

# Example 3: Error handling (Invalid Parser/File)
try:
    findings = parse_findings(
        parser_name="my_scanner",
        input_file="results.json"
    )
except ValueError as e:
    print(f"Parser error: {e}")

Advanced: Direct Parser Usage

If you need more control, you can instantiate parsers directly:

from norm_findings.parsers.trivy.parser import TrivyParser
from norm_findings.stubs.models import Test

parser = TrivyParser()
test = Test(product="My Application")

with open("trivy.json", "rb") as f:
    findings = parser.get_findings(f, test)

for finding in findings:
    print(f"Found: {finding.title} ({finding.severity})")

Legacy Version

The original monkey-patched version of this tool is preserved in the legacy-monkeypatch branch and tagged as v1.x-legacy.

To use the legacy version:

git checkout v1.x-legacy

Automatic Updates

norm-findings includes a built-in updater that fetches the latest parsers and tests from DefectDojo:

python -m norm_findings.updater

Development

Workflow

  1. Branching: Create a new branch for your feature or bugfix from main.
  2. Syncing Parsers: Run the updater to ensure you have the latest DefectDojo parsers:
    python -m norm_findings.updater
    
  3. Testing: Always run the test suite before pushing:
    pytest tests/test_cli.py
    pytest tests/test_e2e.py --ignore norm_findings/stubs/models.py
    
  4. Pushing: Push your branch to GitHub and open a Pull Request.

Versioning

norm-findings uses setuptools-scm for automatic versioning.

  • The version is automatically derived from the most recent Git tag.
  • When working on local uncommitted changes, the version will include a .dev suffix and the current timestamp.
  • The version is written to norm_findings/_version.py during the build process.

Releasing

Releases are automated via GitHub Actions and are triggered by pushing a version tag:

  1. Create a tag: Create a semantic version tag starting with v (e.g., v1.1.0):
    git tag -a v1.1.0 -m "Release version 1.1.0"
    
  2. Push the tag:
    git push origin v1.1.0
    
  3. Automated Pipeline: The build workflow will automatically:
    • Run all tests.
    • Build the Python wheel and source distribution.
    • Publish to PyPI.
    • Build and push the Docker image to GHCR (tagged with the version and latest).

Automatic Parser Updates

A daily GitHub Action runs the updater.py logic. If new parsers or updates are detected in DefectDojo:

  1. A new branch auto-update-parsers is created.
  2. A Pull Request is opened with a summary of the changes.
  3. Maintainers can review and merge the PR to keep norm-findings up-to-date.

Metadata

Release files for norm-findings 0.4.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for norm-findings 0.4.3
File Size Uploaded
norm_findings-0.4.3.tar.gz 331.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for norm-findings 0.4.3
File Interpreter ABI Platform
norm_findings-0.4.3-py3-none-any.whl Python 3 none any Details

Total release size: 870.7 kB

Release files / norm_findings-0.4.3.tar.gz

Download URL norm_findings-0.4.3.tar.gz
Size 331.8 kB
Tags Source
SHA-256 checksum
How to use checksums
c31d05ff9613040b43316a5d2f44e915819976d51e1bcd6826d2a0a27a7d4d35
BLAKE2b-256 checksum
How to use checksums
4729910e1f3b130d9ad36fbdb1865b34bfdb7a76b60cfe4d81a271c07d421322
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release files / norm_findings-0.4.3-py3-none-any.whl

Download URL norm_findings-0.4.3-py3-none-any.whl
Size 538.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a672f809d88e911569a58be0362f05d83c8e84daa3e21ddc42678d7caaff5c2
BLAKE2b-256 checksum
How to use checksums
eb4a3a3745751da794bf5274996637268d40fe1b7ea0a53669e5c3097534e919
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.4.3 This release

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page