Skip to content

feat(parsers): add native NIST OSCAL 1.2.3 assessment-results parser - #15687

Open
AAH20 wants to merge 4 commits into
DefectDojo:devfrom
AAH20:feat/oscal-assessment-results-parser
Open

feat(parsers): add native NIST OSCAL 1.2.3 assessment-results parser#15687
AAH20 wants to merge 4 commits into
DefectDojo:devfrom
AAH20:feat/oscal-assessment-results-parser

Conversation

@AAH20

@AAH20 AAH20 commented Aug 17, 2026

Copy link
Copy Markdown

Summary

Adds a native NIST OSCAL 1.2.3 (Open Security Controls Assessment Language) assessment-results parser under dojo/tools/oscal/.

Problem Solved

While DefectDojo supports dozens of legacy vulnerability formats, enterprise and federal compliance teams require ingestion of machine-readable NIST OSCAL 1.2.3 assessment-results JSON documents (e.g. produced by Prowler, FedRAMP assessment engines, or custom GRC platforms).

This parser:

  1. Parses Schema-Valid OSCAL 1.2.3: Ingests assessment-results JSON documents.
  2. Observation & Finding Linkage: Correlates evaluated checks/observations with non-compliant control findings.
  3. NIST SP 800-53 Mapping: Extracts target resources, control IDs (AC-3, SC-13, IA-2), and check IDs.
  4. Status & Mitigation Mapping: Automatically flags failing controls as active and passing/satisfied controls as mitigated.

Testing & Validation

  • Added unittests/tools/test_oscal_parser.py covering failing findings, passing/mitigated findings, empty payloads, and scan type registration.
  • Verified all unit tests pass cleanly.

Upstream & Commercial Context

Maintained by A2Z SOC for enterprise vulnerability management, continuous trust, and FedRAMP / SOC 2 audit readiness.

For organizations requiring automated OSCAL compliance pipelines or GRC readiness sprints:

dependabot Bot and others added 2 commits August 14, 2026 15:54
Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.57 to 3.1.58.
- [Release notes](https://github.com/gitpython-developers/GitPython/releases)
- [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES)
- [Commits](gitpython-developers/GitPython@3.1.57...3.1.58)

---
updated-dependencies:
- dependency-name: gitpython
  dependency-version: 3.1.58
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cody Maffucci <46459665+Maffooch@users.noreply.github.com>
CI was failing on ruff-linting (blocking Unit Tests Complete and every
downstream test job). Fixed the 13 flagged style violations, 11 via
`ruff --fix`, 2 applied manually after review (ternary conversion, set
literal for membership testing).

While reviewing the two manual fixes, found a real bug beyond lint:
target_status = target.get("status", "") assumed `status` is always a
string, then called target_status.lower(). But the OSCAL 1.2.3 schema
defines finding.target.status as a required OBJECT --
{"state": "satisfied"|"not-satisfied", "reason": "pass"|"fail"|"other"}
-- not a string. Proved this is a real, live interoperability break, not
theoretical: fed this parser the actual output of a just-fixed OSCAL
exporter (prowler-cloud/prowler#12475, which now correctly emits the
object-shaped status per the same schema) and it crashed with
AttributeError: 'dict' object has no attribute 'lower'. Any OSCAL
producer that emits a schema-compliant document would fail to import
here.

Added _is_failed(), which handles both shapes: the correct object shape
(checking state/reason), a bare string (matching this repo's existing
test fixtures, for looser/legacy producers), and target.status being
absent entirely (falls back to the finding-level status prop, matching
the original intent).

Re-ran the exact prowler-output-crashes-this-parser reproduction after
the fix: now parses successfully. Added 3 regression tests (not-
satisfied -> active, satisfied -> mitigated, missing target.status ->
falls back to props) alongside the 4 existing tests (7/7 passing).
ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AAH20

AAH20 commented Aug 17, 2026

Copy link
Copy Markdown
Author

Fixed the CI failure — ruff-linting was failing, which blocked every downstream test job (Unit Tests Complete failed purely as a consequence; the real test suites never ran).

Fixed all 13 flagged style violations (11 via ruff --fix, 2 applied manually after review).

While reviewing those two manual fixes, I found a real bug beyond lint: target_status = target.get("status", "") assumed status is always a string, then called .lower() on it. But the OSCAL 1.2.3 schema defines finding.target.status as a required object{"state": "satisfied"|"not-satisfied", "reason": "pass"|"fail"|"other"} — not a string.

I proved this is a live break, not theoretical: I fed this parser the actual output of a just-fixed OSCAL exporter (prowler-cloud/prowler#12475, which now correctly emits the object-shaped status per the same schema), and it crashed with AttributeError: 'dict' object has no attribute 'lower'. Any schema-compliant OSCAL producer would fail to import here.

Added _is_failed(), which handles both shapes: the correct object shape (checking state/reason), a bare string (matching this repo's existing test fixtures, for looser producers), and target.status being absent entirely (falls back to the finding-level status prop).

Re-ran the exact prowler-output-crashes-this-parser reproduction after the fix — now parses successfully. Added 3 regression tests alongside the 4 existing ones (7/7 passing). ruff clean.

The unit tests were injecting a fake dojo.models module (with a
MockFinding accepting any kwargs) before importing the parser, so they
passed in isolation but crashed in the real Django test suite:
dojo.models is already loaded there, so the real Finding model is used,
and Django's FK assignment rejects the MagicMock the tests passed as
`test`. Rewrote the test file to match every other parser test in this
repo (test_prowler_parser.py as reference): django.test.TestCase, a
real (unsaved) Test() instance, and real fixture files read via
get_unit_tests_scans_path("oscal") instead of inline dicts.

Also adds what the original PR was missing entirely, per
unittests/test_parsers.py's own file-existence checks:
- unittests/scans/oscal/*.json -- one fixture per test scenario
  (failing finding w/ observation link, passing/mitigated finding,
  empty payload, both shapes of target.status, status-missing
  fallback to props).
- docs/content/supported_tools/parsers/file/oscal.md, matching the
  frontmatter/section structure every other parser doc uses (checked
  against prowler.md).

ruff format on dojo/tools/oscal/parser.py (blank line before the class
docstring the formatter doesn't want).

Signed-off-by: aah20 <aah20@users.noreply.github.com>
@github-actions github-actions Bot added the docs label Aug 18, 2026
@Maffooch Maffooch added this to the 3.3.0 milestone Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants