djust docs
Browse documentation
advanced

Scanning a djust app

Publish a CycloneDX SBOM of your app's vendored assets and Python-linked Rust crates, and scan it with Trivy, Syft, grype and OSV-Scanner.

On this page

djust can write a CycloneDX SBOM of every third-party asset it and your app declare (see Vendoring third-party JS), so container scanners can match a vendored JS package against a published advisory the same way they already do for Python and OS packages. This is ADR-040.

Setting up the app SBOM

  1. Set DJUST_SBOM_PATH to a full file path outside every directory your web server serves — outside STATIC_ROOT, MEDIA_ROOT, every STATICFILES_DIRS entry, and anything your reverse proxy or object storage exposes directly. djust only knows about the Django-side directories; check the rest against your own web server/CDN configuration. Name the file so it ends in .cdx.json — osv-scanner only recognizes a CycloneDX file with that extension:

    # settings.py
    DJUST_SBOM_PATH = "/var/lib/myapp/sbom/djust-assets.cdx.json"
    

    Unset (the default), nothing is written and manage.py check --deploy warns with djust.B011. Pointed inside a served directory, startup fails with djust.B012 and collectstatic refuses to write it. djust does not reject another file name, so check the suffix yourself: the file must end in .cdx.json for scanners to find it.

  2. Make sure "djust" is listed above "django.contrib.staticfiles" in INSTALLED_APPS. The SBOM is written by a collectstatic command override, and Django only lets an earlier app override a later app's management command. With DJUST_SBOM_PATH set and the order wrong, manage.py check --deploy fails with djust.B013; with no DJUST_SBOM_PATH set, an app in the old order sees no new check output at all — djust.B013 only fires once there's an SBOM to write.

  3. Run collectstatic as part of your build or deploy step. It writes the SBOM after collecting static files. manage.py check --deploy also verifies the file on disk still matches the currently declared assets (djust.B014) — useful as a build-time guard against a stale SBOM shipped in an image after a dependency bump.

You can also print or write the same document on demand, without running collectstatic:

manage.py djust_sbom -o /var/lib/myapp/sbom/djust-assets.cdx.json

The app SBOM's root component is named after DJUST_SBOM_NAME when it's set, or the first component of ROOT_URLCONF otherwise (for example myapp, for myapp.urls).

Exact commands

Use these exactly — some scanner subcommands silently ignore an embedded SBOM in a way that looks like "nothing to report" rather than an error.

# Trivy: scan a filesystem root or a built image, never `trivy fs` (repo
# mode) — it does not read an embedded *.cdx.json the way `rootfs`/`image` do.
trivy rootfs /app
trivy image myapp:latest

# Syft: needs the SBOM cataloger explicitly enabled to read *.cdx.json files.
syft dir:/app --select-catalogers +sbom-cataloger

# grype: scan an SBOM file directly.
grype sbom:/var/lib/myapp/sbom/djust-assets.cdx.json

# OSV-Scanner: the file must end in .cdx.json (see setup step 1).
osv-scanner scan source -L /var/lib/myapp/sbom/djust-assets.cdx.json

djust's own CI uses the same OSV-Scanner command against python/djust/djust.cdx.json, djust's own distribution SBOM (see .github/workflows/vendor.yml). That python/ prefix is only djust's own source layout — it's where the file is generated and committed in the djust repository. Once you pip install djust, the same file ships in two places: inside the installed package directory (<site-packages>/djust/djust.cdx.json) and under the wheel's PEP 770 metadata (<site-packages>/djust-<version>.dist-info/sboms/djust.cdx.json). Find the installed copy without knowing your site-packages path:

python -c "import djust, pathlib; print(pathlib.Path(djust.__file__).with_name('djust.cdx.json'))"

Known advisories that are accepted for now (unmaintained, not vulnerable) are listed in osv-scanner.toml with a reason and an ignoreUntil date — the same policy .cargo/audit.toml already applies to Rust advisories: unmaintained warns, a real vulnerability fails. An expired entry starts failing CI again until it's re-reviewed. A weekly scheduled workflow reruns the same scan against the latest tag of each supported release line and files one issue per advisory per line.

Coverage limits

  • pip-audit, Dependabot and Snyk do not see vendored JS. They read Python package metadata and lockfiles, not bundled browser code. The SBOM this guide describes exists specifically to close that gap for scanners that do read CycloneDX (Trivy, Syft, grype, OSV-Scanner).
  • Dependabot can still be fed the SBOM, through GitHub's dependency submission API — submit the document manage.py djust_sbom prints as part of your CI workflow.

Python dependencies

This SBOM covers browser assets (and, for djust's own distribution SBOM, the Rust crates linked into the extension) — it does not cover your Python environment. Use cyclonedx-py to generate an SBOM of your installed Python packages; run both tools in CI if you want full coverage.

See also