Skip to content

Add a product export API (manifest and NDJSON stream) - #16448

Open
svader0 wants to merge 16 commits into
devfrom
feature/product-export-api
Open

svader0 wants to merge 16 commits into
devfrom
feature/product-export-api

Conversation

@svader0

@svader0 svader0 commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Description

Two superuser-only endpoints export one product with everything under it, so a product can be backed up, moved to another DefectDojo instance, or mirrored.

  • GET /api/v2/export/manifest/: counts per product type and product, reference data (SLA configurations, development environments, regulations, test types, note types, product platforms, lifecycles and origins, users with contact info), the distinct scan types in use, the token's privilege, and the instance's install_id.
  • GET /api/v2/export/products/{id}/: an application/x-ndjson stream. One header line, then every engagement, test, finding and risk acceptance of the product as its own line in id order, then an end line with counts.

What the lines carry:

  • Each data object is the existing v2 serializer output for that object. Only the fields that were ids are swapped: a finding's endpoints are inlined with their status (endpoint status, or location reference status when Locations is on), files on engagements, tests and findings are inlined as base64 under a max_file_bytes cap (default 10 MiB; a larger file keeps its name and size with data_base64 null), request/response pairs cross as their stored base64, risk acceptance ids replace the nested accepted_risks, and a risk acceptance line carries its proof file and its notes.
  • System_Settings.install_id is a new read-only UUID (migration 0301_system_settings_install_id). The header and the manifest carry it so a tool can tell two instances apart.

How it stays flat: each collection is walked with iterator(chunk_size=500) plus per-chunk prefetches, one serializer instance per line type, one JSON line at a time into a StreamingHttpResponse. The manifest runs a fixed number of grouped queries.

Known limits:

  • No snapshot: if the instance has read-only mode (feat(system_settings): add a read-only mode #16386), turn it on first, or export during a quiet period.
  • install_id regenerates if the initializer re-runs first boot (the admin account missing). That path also resets every other system setting. A separate fix follows.
  • Under uwsgi the Python filesystem encoding is ASCII, so a risk acceptance proof with a non-ASCII file name looks missing in the export. The same proof cannot be downloaded today either. A separate fix follows.

Migrations: this PR and #16386 both add a 0301 migration on System_Settings. Whichever merges second renumbers to 0302 and points its dependency at the other.

Test results

  • unittests/test_apiv2_export.py (35 tests) and unittests/test_apiv2_export_performance.py (3 tests), run with Locations on and off. One test pins the query count per finding batch (17 with Locations on, 16 off), and a second fails on any query per row.
  • test_rest_framework, test_apiv2_methods_and_endpoints, test_system_settings and test_apiv2_user green.
  • makemigrations --check clean, spectacular --fail-on-warn silent, ruff clean.

Documentation

docs/content/automation/api/product_export.md documents both endpoints, the line shapes, the counts, and what does not cross (secrets and instance settings, endpoints with no finding, JIRA links, members and groups; the page lists all of them).

- Prefetch only the ids of a risk acceptance's accepted findings.
- Treat an empty file name as a missing file in the stream and the manifest.
- Return a 400, not a 500, for a max_file_bytes value with too many digits.
- Build the finding prefetch objects per queryset, not once at import.
- Resolve the endpoint mode and status serializer once per serializer.
- Let the database return distinct scan types.
- Build the end counts from COUNT_KEYS.
- Name the export file after the product id, not the raw URL value.
The inherited request_response field decoded the pairs with
errors="ignore", dropped blank lines and stopped at
DD_MAX_REQRESP_FROM_API. Finding lines now carry every stored pair as
request_response_pairs, in id order, with the base64 text unchanged.

File entries carry the stored file name, so the extension survives.
A non-finite float renders as null instead of ending the stream, the
product id route accepts ASCII digits only, and the stream response
tells nginx not to buffer it.
The stream seed now puts a note and a file on the engagement and on the
test, and stores a request/response pair with non-UTF-8 bytes and a
blank line. The tests read those values back from the lines, and the
end counts are checked against the seeded totals, so a dropped note,
file or byte fails the suite.

One seed helper runs once per class from setUpTestData. Tests that the
stream and manifest tests already cover end to end are removed, and the
performance tests no longer import from the export test module.
A DRF IntegerField now parses max_file_bytes and documents it in the
schema. The two routes sit in api_v2_urls beside the other plain API
views, so dojo/export/api/urls.py goes. The single-caller queryset and
tally helpers are inlined, the reference tables come from one
serializer map, both endpoint modes share one loop, and one file_size
helper serves the stream and the manifest. The views rely on
IsSuperUser alone, and the finding query no longer joins tables that
no serializer reads.
One counts table now compares the manifest with end.counts, and the
nesting rules, use cases and endpoint examples say each thing once.

The page now lists what the export leaves out (endpoints without
findings, JIRA keys, members, presets, configurations, contacts, import
history and more), says when install_id changes, says that a risk
acceptance line names one engagement, gives the line size and memory
use a reader must plan for, and says that token.expires_at is null
under session or basic authentication.
An empty max_file_bytes value uses the default cap. The docs now say
so, and a test pins it. The manifest-to-stream comparison runs at the
default cap and at a cap of 4 again, and checks files_over_cap and
finding_groups. The docs also name the report type and CI/CD
infrastructure ids that stay on engagement lines.
@svader0 svader0 added this to the 3.4.300 milestone Oct 20, 2026
@svader0 svader0 added enhancement New Migration Adding a new migration file. Take care when merging. apiv2 labels Oct 20, 2026
@dryrunsecurity

Copy link
Copy Markdown

DryRun Security

This pull request modifies sensitive codepaths in dojo/db_migrations/0301_system_settings_install_id.py and dojo/urls.py by an author not on the allowed list. These changes are flagged as low severity and are not blocking.

Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/db_migrations/0301_system_settings_install_id.py (drs_30e83fee)
Vulnerability Configured Sensitive Codepath Modified by Non-Allowed Author
Description File 'dojo/db_migrations/0301_system_settings_install_id.py' matches configured sensitive codepath pattern 'dojo/db_migrations/*.py' and was modified by 'svader0' (commit de75317) who is not in the allowed authors list.
Configured Sensitive Codepath Modified by Non-Allowed Author in dojo/urls.py (drs_606c52df)
Vulnerability Configured Sensitive Codepath Modified by Non-Allowed Author
Description File 'dojo/urls.py' matches configured sensitive codepath pattern 'dojo/urls.py' and was modified by 'svader0' (commit db063e1) who is not in the allowed authors list.

Comment to provide feedback on these findings.

Report false positive: @dryrunsecurity fp [FINDING ID] [FEEDBACK]
Report low-impact: @dryrunsecurity nit [FINDING ID] [FEEDBACK]

Example: @dryrunsecurity fp drs_90eda195 This code is not user-facing

All finding details can be found in the DryRun Security Dashboard.

This branch has not been deployed

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

Labels

docs enhancement New Migration Adding a new migration file. Take care when merging. unittests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant