In the (docker) container world, we should use cve scanners like Trivy (free) or JFrog Xray to scan for known vulnerabilities.
In most cases, this just works.
But it typically does not work for statically compiled and minified JavaScript apps like Angular or React apps.
This is how to make your minified frontend app scannable with common scanners.
In my tests during september 2025, this worked for:
- trivy (free)
- JFrog Xray (company wide, high priced license)
- Docker Desktop (personal company license)
It did not work for
- grype (free)
Vulnerability scanners typically do the following
- Download a database of known vulnerabilities.
In my tests JFrog Xray often knows much more vulnerabilities, than Trivy. JFrog seems to have quite a good database - Trying to find out, which components are inside an image and creating an SBOM on the fly.
They look for linux package managers, common named files, descriptor files like a package-lock.json, etc. to find out what is installed - Compare the created SBOM with the database of known vulnerabilities
Our frontend apps are compiled and minified to a folder of static html and javascript files.
These files are put into a webserver like nginx.
We loose the information, what the sources are, from that the app was built.
Vulnerability scanners can not find out anymore, which node_modules were needed to build the app, so they can not create a valid SBOM.
We need to help the vulnerability scanner by creating an SBOM at build time and provide it to the final docker image.
We use cdxgen here.
I also tested trivy to create an SBOM, but cdxgen created a way larger SBOM with much more identified packages.
If you want to try this out yourself, clone this gist:
git clone https://gist.github.com/ec02d97b26b891cea0c44b3d65d2b1aa.git
Lets say we have a small project with the provided package.json with a few dependencies.
Add the cdxgen
to the package.json with a run script
{
...
"scripts": {
"build-app": "echo 'Hello World' > index.html", // Here you would build your app
"create-sbom": "cdxgen --type npm --output sbom.cdx.json", // Create the sbom. File name is important
"pretty-sbom": "node -e \"const fs=require('fs');fs.writeFileSync('sbom.cdx.json',JSON.stringify(JSON.parse(fs.readFileSync('sbom.cdx.json','utf8')),null,2))\""
},
...
"devDependencies": {
"@cyclonedx/cdxgen": "11.5.0"
}
}
Install and create the sbom
npm install # or "npm ci" to be more strict
npm run build-app # run whatever you need for your app to build
# Create the sbom and prettify the resulting json
npm run create-sbom
npm run pretty-sbom
The file ending
*.cdx.json
is important, at least for Trivy, see SBOM Detection inside Targets
The final step is just copy the sbom.cdx.json into the root of your container image.
The vulnerability scanners will find it there.
Lets build a docker image based on nginx, with our app and the SBOM added:
docker-compose.yaml:
services:
app:
image: example.com/my-app:0.0.1
pull_policy: build
build:
context: .
dockerfile_inline: |
FROM nginx:1.28.0-alpine
# index.html is your app
COPY index.html /usr/share/nginx/html/index.html
# SBOM containing all modules that were needed to build the app
COPY sbom.cdx.json /sbom.cdx.json
ports:
- 80:80
# Build it, resulting in an image "example.com/my-app:0.0.1"
$ docker compose build
# Scan using trivy -> it should find vulnerabilities for axios and form-data
$ trivy image --severity HIGH,CRITICAL example.com/my-app:0.0.1
Node.js (node-pkg)
==================
Total: 2 (HIGH: 1, CRITICAL: 1)
┌───────────┬────────────────┬──────────┬────────┬───────────────────┬─────────────────────┬────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
├───────────┼────────────────┼──────────┼────────┼───────────────────┼─────────────────────┼────────────────────────────────────────────────┤
│ axios │ CVE-2025-58754 │ HIGH │ fixed │ 1.8.4 │ 1.12.0 │ axios: Axios DoS via lack of data size check │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-58754 │
├───────────┼────────────────┼──────────┤ ├───────────────────┼─────────────────────┼────────────────────────────────────────────────┤
│ form-data │ CVE-2025-7783 │ CRITICAL │ │ 4.0.2 │ 2.5.4, 3.0.4, 4.0.4 │ form-data: Unsafe random function in form-data │
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-7783 │
└───────────┴────────────────┴──────────┴────────┴───────────────────┴─────────────────────┴────────────────────────────────────────────────┘
# Scan using docker scout (only if you have a paid Docker Desktop)
$ docker scout cves --only-severity critical,high example.com/my-app:0.0.1
# In a paid Docker Desktop, you can also open the Docker Desktop UI -> Images -> Search "example.com/my-app:0.0.1" and click "Start analysis"
# If your "example.com" is your Artifactory with Xray enabled, push the app and view the results in Artifactorys web ui
$ docker compose build --push
Whenever you have a compiled/minified app, that can not be easily introspected by common scanners, provide an SBOM in the cyclonedx format and put it into /sbom.cdx.json
inside your resulting container image.
Then the vulnerability scanners should find it and use it as the source to compare against their vulnerability database.