Summary
There is no converter for JUnit XML, which is the most widely produced machine-readable test-results format across ecosystems. I would like to propose one, and to check the direction is welcome before anyone writes code.
Why this is a reasonable fit for OHDF
OHDF is increasingly used as a compliance evidence format, not only as a findings format. Two controls are evidenced by test execution rather than by scanner output:
- NIST SP 800-53r5 SA-11 — Developer Testing and Evaluation
- SSDF PW.8 — Test executable code to identify vulnerabilities and verify compliance
Today there is no path from a test suite into OHDF, so that evidence stays in CI artifacts with a retention window and never reaches Heimdall alongside the SAST, SCA and InSpec results it belongs next to.
The existing converter set already reaches beyond classic vulnerability scanners — cyclonedx-sbom is inventory, aws-config is configuration compliance, msft-secure-score is a posture score. Test results feel like a smaller step than any of those.
Why JUnit XML specifically
It is the closest thing the industry has to a universal test-results interchange format, emitted natively or via one flag by pytest (--junitxml), Playwright, Jest, Maven/Gradle, Go (gotestsum), RSpec (formatter), PHPUnit and .NET (via trx conversion). One converter covers most languages, which is not true of the per-vendor mappers.
For comparison, jfrog-xray-mapper.ts serves a single commercial product. JUnit XML serves nearly every test runner in use.
The mapping is unusually close to HDF's own shape
JUnit XML is testsuite → testcase, and HDF is control → results[]. That suggests a natural, low-invention mapping:
| JUnit |
HDF |
<testsuite name> |
control id / title |
<testcase> |
one entry in results[] |
<testcase> with no child |
status: passed |
<failure> / <error> |
status: failed, message into message |
<skipped> |
status: skipped, into skip_message |
time attribute |
run_time |
classname + name |
code_desc |
Suite-level granularity matters: mapping each test case to its own control would produce thousands of controls for a normal suite and make Heimdall unreadable. Mapping the suite to a control and its cases to results keeps it proportionate, and matches how HDF already represents a control checked several ways.
Open questions worth deciding before implementation
- Impact. Test results carry no severity. A fixed default (0.5?) with a CLI override seems least surprising, but there may be an existing convention I have missed.
- Tags. JUnit carries no control mappings. Allowing caller-supplied tags (
--tags nist=SA-11) would let the evidence land against the right control without the converter inventing one.
- Nested suites. Some producers emit
<testsuites> wrapping several <testsuite> elements; flattening versus one control per inner suite is a judgement call.
- Scope. Should a clean run with zero failures convert at all? Our position is yes — a suite that ran and passed is exactly the evidence that is hardest to produce after the fact — but it does mean an all-passing OHDF file, which is unusual for this toolchain.
Context
We hit this building a DevSecOps evidence profile: the pipeline runs pytest, Playwright and RSpec, all of which already emit JUnit XML, and none of which can reach the evidence store. We have written a local mapper as a stopgap and would be glad to contribute a proper converter upstream if the direction is welcome — happy to follow whatever shape you prefer for the open questions above.
Summary
There is no converter for JUnit XML, which is the most widely produced machine-readable test-results format across ecosystems. I would like to propose one, and to check the direction is welcome before anyone writes code.
Why this is a reasonable fit for OHDF
OHDF is increasingly used as a compliance evidence format, not only as a findings format. Two controls are evidenced by test execution rather than by scanner output:
Today there is no path from a test suite into OHDF, so that evidence stays in CI artifacts with a retention window and never reaches Heimdall alongside the SAST, SCA and InSpec results it belongs next to.
The existing converter set already reaches beyond classic vulnerability scanners —
cyclonedx-sbomis inventory,aws-configis configuration compliance,msft-secure-scoreis a posture score. Test results feel like a smaller step than any of those.Why JUnit XML specifically
It is the closest thing the industry has to a universal test-results interchange format, emitted natively or via one flag by pytest (
--junitxml), Playwright, Jest, Maven/Gradle, Go (gotestsum), RSpec (formatter), PHPUnit and .NET (via trx conversion). One converter covers most languages, which is not true of the per-vendor mappers.For comparison,
jfrog-xray-mapper.tsserves a single commercial product. JUnit XML serves nearly every test runner in use.The mapping is unusually close to HDF's own shape
JUnit XML is
testsuite→testcase, and HDF iscontrol→results[]. That suggests a natural, low-invention mapping:<testsuite name>id/title<testcase>results[]<testcase>with no childstatus: passed<failure>/<error>status: failed, message intomessage<skipped>status: skipped, intoskip_messagetimeattributerun_timeclassname+namecode_descSuite-level granularity matters: mapping each test case to its own control would produce thousands of controls for a normal suite and make Heimdall unreadable. Mapping the suite to a control and its cases to results keeps it proportionate, and matches how HDF already represents a control checked several ways.
Open questions worth deciding before implementation
--tags nist=SA-11) would let the evidence land against the right control without the converter inventing one.<testsuites>wrapping several<testsuite>elements; flattening versus one control per inner suite is a judgement call.Context
We hit this building a DevSecOps evidence profile: the pipeline runs pytest, Playwright and RSpec, all of which already emit JUnit XML, and none of which can reach the evidence store. We have written a local mapper as a stopgap and would be glad to contribute a proper converter upstream if the direction is welcome — happy to follow whatever shape you prefer for the open questions above.