Skip to content

Add Seal Security CSV parser - #15592

Open
amita-seal wants to merge 2 commits into
DefectDojo:devfrom
amita-seal:seal-parser
Open

Add Seal Security CSV parser#15592
amita-seal wants to merge 2 commits into
DefectDojo:devfrom
amita-seal:seal-parser

Conversation

@amita-seal

@amita-seal amita-seal commented Aug 9, 2026

Copy link
Copy Markdown

Description

Adds a parser for the CSV export of the Seal Security CLI, produced by seal scan --csv <file>. Seal backports security fixes into "sealed" versions of open-source packages, so a vulnerable dependency can be remediated without a major-version upgrade.

The export has one row per vulnerable package, with a pipe-separated list of vulnerability identifiers:

Library,Version,Ecosystem,Vulnerabilities,Can Seal,Sealed Version
lodash,4.17.15,NPM,CVE-2021-23337|CVE-2020-8203,TRUE,4.17.15-sp1

Each identifier becomes its own Finding, so that each one can be triaged and risk-accepted independently. Identifiers are not always CVEs: Seal reports the most specific identifier it has, falling back to a GitHub advisory or Snyk identifier when no CVE is assigned.

Two properties of the export are worth calling out, both covered by test files:

  • A scan without findings leaves the export file empty rather than writing a header, so a zero-byte file is a valid report that yields no findings.
  • A vulnerability that reaches the project through an embedded (shaded) package is reported as CVE-2021-1234(via shaded lib1&lib2). The identifier is used for the Finding, and the embedding packages are named in the description. The Finding's component stays the package that is actually present in the project.

When a sealed version is available, fix_available is set and the mitigation names the version to update to.

Severity

Not every Seal report carries severity, so the parser supports both shapes of the export.
When a Score column is present its value is mapped onto the standard severity bands;
when it is absent there is nothing in the report to derive a severity from, and findings
are imported as Medium. Either export imports cleanly, with no configuration and no
scan-type variant.

For that reason severity is deliberately left out of the deduplication hashcode. A
project can import a report without a score today and one with a score tomorrow, and
including severity would fork every existing finding into a duplicate at that point.

Test results

$ ./run-unittest.sh --test-case unittests.tools.test_seal_parser.TestSealParser
Ran 6 tests in 0.020s
OK

$ ./run-unittest.sh --test-case unittests.test_parsers
Ran 2 tests in 0.513s
OK

$ ./run-unittest.sh --test-case unittests.test_factory
$ ./run-unittest.sh --test-case unittests.test_deduplication_logic
Ran 89 tests in 17.577s
OK (skipped=2)

Ruff clean against the pinned ruff==0.16.1.

Documentation

docs/content/supported_tools/parsers/file/seal.md added.

Checklist

  • Make sure to rebase your PR against the very latest dev.
  • Features/Changes should be submitted against the dev.
  • Give a meaningful name to your PR, as it may end up being used in the release notes.
  • Your code is Ruff compliant (see ruff.toml).
  • Your code is python 3.13 compliant.
  • If this is a new feature and not a bug fix, you've included the proper documentation in the docs at https://github.com/DefectDojo/django-DefectDojo/tree/dev/docs as part of this PR.
  • Add applicable tests to the unit tests.
  • Add the proper label to categorize your PR.

🤖 Generated with Claude Code

Adds a parser for the CSV export of the Seal Security CLI, produced by
`seal scan --csv <file>`.

The export has one row per vulnerable package, with a pipe-separated list of
vulnerability identifiers. Each identifier becomes its own Finding so that it
can be triaged independently. Identifiers are not always CVEs: Seal reports the
most specific identifier it has, falling back to a GitHub advisory or Snyk
identifier when no CVE is assigned.

Two properties of the export are worth noting:

- A scan without findings leaves the file empty rather than writing a header,
  so an empty file is a valid report and yields no findings.
- A vulnerability reaching the project through an embedded (shaded) package is
  reported as `CVE-2021-1234(via shaded lib1&lib2)`. The identifier is used for
  the Finding and the embedding packages are named in the description.

The export has no severity column, so findings default to Medium. The parser
reads an optional Score column when present and maps it onto the standard
severity bands. Severity is deliberately left out of the deduplication hashcode
so that findings imported before and after a report gains that column are not
treated as distinct.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR docs unittests parser labels Aug 9, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@devGregA devGregA modified the milestones: 3.2.200, 3.3.0 Aug 10, 2026

@valentijnscholten valentijnscholten left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-structured parser with good coverage (empty/header-only, one/many vulns, shaded, score column). Excluding severity from the dedupe hashcode is the right call and nicely justified. One minor, non-blocking consistency nit:

dojo/tools/seal/parser.pyfix_available can disagree with the mitigation text

fix_available=can_seal is derived only from the Can Seal column, but the sealed-version mitigation is used only when can_seal and sealed_version:

can_seal = (row.get("Can Seal") or "").strip().upper() == "TRUE"
...
if can_seal and sealed_version:
    mitigation = SEALED_MITIGATION_TEMPLATE.format(...)
else:
    mitigation = NO_FIX_MITIGATION
...
finding = Finding(..., fix_available=can_seal, ...)

So a row with Can Seal=TRUE but an empty Sealed Version would yield a Finding marked fix_available=True whose mitigation reads "Seal has no sealed version for this package version yet." The sample data never hits that combination (every Can Seal=TRUE row carries a Sealed Version), so it isn't exercised by the tests — but the mitigation branch already anticipates that state, so it'd be good to keep the two consistent, e.g.:

fix_available=can_seal and bool(sealed_version),

Low severity — only matters if real Seal exports ever emit Can Seal=TRUE with a blank Sealed Version. Otherwise this looks close to merge-ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs parser settings_changes Needs changes to settings.py based on changes in settings.dist.py included in this PR unittests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants