JustScan is a self-hosted container image vulnerability scanner powered by Trivy or Artifactory Xray.
- Overview
- Key Features
- Architecture
- GitOps repository discovery
- CLI
- Quick Start
- Configuration Reference
- OIDC Configuration
- Frontend Configuration
- Deployment
- GitLab CI: Scan Before Push (Uploaded Archive)
- Common Issues
- Getting an NVD API Key
- Development
- Contributing
- License
JustScan helps teams scan container images for vulnerabilities before deployment. It supports local scanning via Trivy and remote scanning workflows via Artifactory Xray, with a web UI, API access, and organization-aware workflows.
- Self-hosted vulnerability scanning for container images
- Trivy-based local scanning support
- Artifactory Xray integration support
- Web UI with scan history and findings
- OIDC SSO support (Keycloak, Authentik, Okta, Azure AD, Google Workspace)
- API endpoints for CI/CD automation
- Docker Compose and Helm deployment options
Git repositories can be connected in the web UI to preview and schedule image scans. The dry run always discovers first and never queues scans.
- Auto (the default) detects unreferenced Kustomize deployment roots, renders them with Kustomize's built-in Helm support, and extracts images only from the resulting Kubernetes workloads. When no Kustomization exists, it scans plain Kubernetes workload manifests instead.
- Kustomize entrypoints renders only the relative repository paths supplied by the operator. Use this for repositories with several independent environments or conventions that cannot be inferred.
- Plain Kubernetes manifests skips rendering and extracts images from
containers,initContainers, andephemeralContainersin YAML manifests. This supports repositories that do not use Kustomize.
Helm is required when a selected Kustomization uses helmCharts; the provided container images include it. For local backend development, install Helm or choose plain-manifest discovery for repositories that do not need Helm rendering.
For repositories that combine several deployment mechanisms, add a repository-owned .justscan.yaml discovery configuration. It can compose Kustomize roots, direct Helm charts, and selected plain-manifest paths in one dry run. After deployment, see /docs/scan-and-analyze/gitops on your JustScan host; before deployment, see the hosted GitOps documentation.
Status pages can follow a whole Git repository or a curated set of discovered image names. Curated sources always use the repository's latest completed discovery, so the page moves forward as the selected images receive new tags in Git.
| Service | Tech | Default Port |
|---|---|---|
| Backend | Go (Gin) | 8080 |
| Frontend | Next.js | 3000 |
| Database | PostgreSQL 15+ | 5432 |
The justscan CLI submits registry and archive scans to a running JustScan instance. Registry
pipeline scans return CI-friendly policy verdict exit codes; local Docker/Podman/Apple Container and archive inputs
are streamed to the instance for remote analysis. It does not include a local scanner. Release
archives are attached to each JustScan GitHub Release; build it locally with:
cd services/cli
go build ./cmd/justscanConfigure a non-secret profile and provide the pipeline-scoped organization token through the environment:
justscan config set production \
--server https://justscan.example.com \
--org 00000000-0000-0000-0000-000000000000
export JUSTSCAN_TOKEN="<pipeline-scoped-org-token>"
justscan scan registry.example.com/my-app:1.2.3For an interactive user session, run justscan login --profile production --email you@example.com.
The CLI prompts for the password and stores the credential in the system keychain; use
JUSTSCAN_TOKEN only for CI/CD or other unattended automation.
After deployment, open /docs on the same JustScan host for the CLI guide, CI/CD provider
examples, GitOps discovery, operator configuration, and troubleshooting. If you have not deployed
JustScan yet, browse the hosted documentation.
- Go 1.22+
- Node.js 20+ / pnpm
- PostgreSQL 15+
- Trivy installed and on
$PATHfor local Trivy scans
Create a database and user:
CREATE DATABASE justscan;
CREATE USER justscan WITH PASSWORD 'yourpassword';
GRANT ALL PRIVILEGES ON DATABASE justscan TO justscan;Copy and edit the config file:
cp services/backend/config/config.yaml services/backend/config/config.local.yamlEdit config.yaml with your values (see Configuration Reference below).
cd services/backend
go run main.gocd services/frontend
pnpm install
pnpm devOpen http://localhost:3000 and register the first user.
If you plan to use OIDC, configure it before first login using the section below. When local_auth.enabled is set to false, the /login and /register password-based endpoints are disabled and users must sign in through your OIDC provider.
services/backend/config/config.yaml
| Key | Description | Example |
|---|---|---|
jwt.secret |
Required. Secret key used to sign and verify JWT tokens. Must be a long random string. If null or empty, authentication will fail. |
"a-very-long-random-secret-key-32chars" |
database.server |
PostgreSQL host | localhost |
database.port |
PostgreSQL port | 5432 |
database.name |
Database name | justscan |
database.user |
Database user | postgres |
database.password |
Database password | postgres |
| Key | Description | Default |
|---|---|---|
port |
HTTP listen port | 8080 |
log_level |
Log verbosity: debug, info, warn, error |
info |
allow_origins |
CORS allowed origins (list) | ["http://localhost:3000"] |
scanner.enable_trivy |
Enable local Trivy scans. Set to false for Artifactory Xray-only deployments |
true |
scanner.trivy_path |
Path to Trivy binary | trivy |
scanner.enable_grype |
Enable Grype augmentation for local Trivy scans | false |
scanner.grype_path |
Path to Grype binary | grype |
scanner.timeout |
Legacy fallback for the local scanner command timeout in seconds | 600 |
scanner.command_timeout_seconds |
Local scanner command timeout in seconds for Trivy, Grype, and SBOM execution | 7200 |
scanner.progress_heartbeat_seconds |
How often long-running scans refresh their liveness timestamp while work is still active | 30 |
scanner.stale_timeout_seconds |
Fail a scan only after this many seconds without recorded progress | 7200 |
scanner.concurrency |
Number of concurrent scans | 2 |
scanner.db_max_age_hours |
Maximum age of each Trivy DB before JustScan refreshes it automatically | 24 |
scanner.enable_osv_java_augmentation |
Query the free OSV API for additional Maven/Java advisories and merge them into scan results | true |
encryption.key |
Key for encrypting registry credentials at rest. Should be a 32-char string. | "" |
vuln_kb.nvd_api_key |
NVD API key for enriched CVE data (optional, see Getting an NVD API key) | "" |
vuln_kb.cache_days |
How long to cache NVD data | 7 |
oidc.enabled |
Enable OIDC single sign-on | false |
oidc.issuer_url |
OIDC issuer URL from your provider | "" |
oidc.client_id |
OIDC client ID | "" |
oidc.client_secret |
OIDC client secret. Prefer an environment variable in production. | "" |
oidc.redirect_uri |
Public backend callback URL registered in your OIDC provider | "" |
oidc.scopes |
Requested OIDC scopes | ["openid", "email", "profile"] |
oidc.admin_groups |
Group names that should map to the JustScan admin role |
[] |
oidc.admin_roles |
Role names that should map to the JustScan admin role |
[] |
oidc.groups_claim |
Claim name containing group memberships | "groups" |
oidc.roles_claim |
Claim name containing role memberships | "roles" |
local_auth.enabled |
Keep local username/password auth enabled alongside OIDC | true |
Artifactory Xray registries are configured in the JustScan UI and reuse their registry credential for both the Artifactory image pull and Xray API requests. Existing Xray registries default to Limited mode after upgrading.
- Limited is for consumer credentials. JustScan warms the image through Artifactory and waits for Xray to expose an artifact result, then imports it. It never triggers a rescan, so a completed result is valid but its freshness cannot be verified. Ensure the repository is indexed in Xray; remote artifacts must also be permitted to enter the Artifactory cache.
- Full is for a service account with Xray Read plus Manage Xray Metadata. After warming the image, JustScan requests
scanArtifactand waits for Xray to confirm a new completed run before importing findings. A denied request fails the scan with an actionable error; JustScan never silently downgrades this mode.
Full mode deliberately does not use Artifactory's legacy force-index endpoint. For both modes, Xray remains responsible for the vulnerability analysis and policy evaluation.
log_level: info
port: 8080
database:
server: localhost
port: 5432
name: justscan
user: postgres
password: postgres
jwt:
secret: "replace-this-with-a-long-random-secret-minimum-32-characters"
allow_origins:
- "http://localhost:3000"
scanner:
trivy_path: trivy
timeout: 600
command_timeout_seconds: 7200
progress_heartbeat_seconds: 30
stale_timeout_seconds: 7200
concurrency: 2
db_max_age_hours: 24
enable_osv_java_augmentation: true
encryption:
key: "replace-with-32-char-encryption-key"
vuln_kb:
nvd_api_key: ""
cache_days: 7
oidc:
enabled: false
issuer_url: ""
client_id: ""
client_secret: ""
redirect_uri: ""
scopes: ["openid", "email", "profile"]
admin_groups: []
admin_roles: []
groups_claim: "groups"
roles_claim: "roles"
local_auth:
enabled: true- By default, backend startup now enforces strong secrets:
jwt.secretmust be at least 32 charactersencryption.keymust be at least 32 characters
- For local development only, you can bypass this with:
security:
allow_insecure_defaults: trueOr with environment variable:
BACKEND_SECURITY_ALLOW_INSECURE_DEFAULTS=true
Pipeline callbacks are restricted to public HTTPS destinations and do not follow redirects. For a self-hosted callback receiver on a private network, explicitly allowlist the exact host or CIDR in the backend configuration:
security:
callback_allowed_hosts:
- ci.internal.example
callback_allowed_cidrs:
- 10.20.0.0/16JustScan supports OpenID Connect providers such as Keycloak, Authentik, Okta, Azure AD, and Google Workspace.
Set these values in services/backend/config/config.yaml or via BACKEND_... environment variables:
oidc:
enabled: true
issuer_url: "https://auth.example.com/application/o/justscan/"
client_id: "justscan"
client_secret: "replace-me"
redirect_uri: "https://scan.example.com/api/v1/auth/oidc/callback"
scopes: ["openid", "email", "profile"]
admin_groups:
- "justscan-admins"
admin_roles: []
groups_claim: "groups"
roles_claim: "roles"
local_auth:
enabled: trueoidc.redirect_urimust be the public backend callback URL and must exactly match the redirect URI registered in your OIDC provider.- The frontend URL must be listed in
allow_origins. - Put your primary frontend URL first in
allow_origins. After a successful OIDC login, JustScan redirects the browser to the firstallow_originsentry plus/auth/oidc/callback. - Notification messages also use the first
allow_originsentry when building direct links to scan detail pages such as/scans/<scanId>.
Example:
allow_origins:
- "https://scan.example.com"
oidc:
redirect_uri: "https://scan.example.com/api/v1/auth/oidc/callback"- Users are granted the JustScan
adminrole when any entry inoidc.admin_groupsmatches the configuredgroups_claim. - Users are also granted the JustScan
adminrole when any entry inoidc.admin_rolesmatches the configuredroles_claim. - Role mapping is evaluated on every OIDC login. Removing a user from the configured group or role removes their JustScan admin privileges on their next login.
- If a user signs in through OIDC for the first time and no account exists for the OIDC
subclaim, JustScan checks for an existing local account with the same email address. - If the email matches an existing local account, JustScan automatically links that account to the OIDC identity.
- If no matching email exists, JustScan creates a new local user record automatically.
- OIDC-created users do not use a local password.
local_auth.enabled: true: both local login and OIDC login are available.local_auth.enabled: false: users must sign in through OIDC; password login and self-registration are disabled.
- Keycloak: use the realm issuer URL, for example
https://keycloak.example.com/realms/justscan - Authentik: use the provider issuer URL shown in the Authentik application/provider settings
- Azure AD / Entra ID: use the OpenID issuer for your tenant and app registration
All config values can be overridden via environment variables using the BACKEND_ prefix with . replaced by _:
| Config key | Environment variable |
|---|---|
jwt.secret |
BACKEND_JWT_SECRET |
database.server |
BACKEND_DATABASE_SERVER |
database.port |
BACKEND_DATABASE_PORT |
database.name |
BACKEND_DATABASE_NAME |
database.user |
BACKEND_DATABASE_USER |
database.password |
BACKEND_DATABASE_PASSWORD |
encryption.key |
BACKEND_ENCRYPTION_KEY |
security.allow_insecure_defaults |
BACKEND_SECURITY_ALLOW_INSECURE_DEFAULTS |
security.callback_allowed_hosts |
BACKEND_SECURITY_CALLBACK_ALLOWED_HOSTS |
security.callback_allowed_cidrs |
BACKEND_SECURITY_CALLBACK_ALLOWED_CIDRS |
oidc.enabled |
BACKEND_OIDC_ENABLED |
oidc.issuer_url |
BACKEND_OIDC_ISSUER_URL |
oidc.client_id |
BACKEND_OIDC_CLIENT_ID |
oidc.client_secret |
BACKEND_OIDC_CLIENT_SECRET |
oidc.redirect_uri |
BACKEND_OIDC_REDIRECT_URI |
oidc.scopes |
BACKEND_OIDC_SCOPES |
oidc.admin_groups |
BACKEND_OIDC_ADMIN_GROUPS |
oidc.admin_roles |
BACKEND_OIDC_ADMIN_ROLES |
oidc.groups_claim |
BACKEND_OIDC_GROUPS_CLAIM |
oidc.roles_claim |
BACKEND_OIDC_ROLES_CLAIM |
local_auth.enabled |
BACKEND_LOCAL_AUTH_ENABLED |
The frontend uses a single environment variable:
| Variable | Description | Default |
|---|---|---|
NEXT_PUBLIC_API_URL |
Backend base URL | http://localhost:8080 |
Create services/frontend/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:8080docker compose up -dThe compose file starts PostgreSQL, the backend, and the frontend together.
When running from the default Trivy-enabled Docker images, JustScan refreshes Trivy's vulnerability DB and Java DB on container startup and again before scans whenever the cached DBs exceed scanner.db_max_age_hours. The cache is stored under /app/data/trivy-cache, so it survives container restarts when /app/data is persisted.
For Artifactory Xray-only deployments, use the scannerless backend image by setting JUSTSCAN_BACKEND_IMAGE_PREFIX=backend-minimal and setting scanner.enable_trivy: false plus scanner.enable_grype: false in deploy/docker-compose/backend-config.yaml. Keep the default backend image if any registry should run local Trivy scans.
JustScan can also augment Java findings for Maven packages using the free OSV API. To avoid unnecessary outbound calls and stay within public-service limits, package/version query results are cached locally in the database and refreshed using the same cache window configured by vuln_kb.cache_days.
The Kubernetes chart lives at deploy/helm/justscan for local development and is released as an OCI Helm chart in GHCR on every Git tag release.
- Kubernetes 1.24+
- Helm 3.8+
- Access to
ghcr.io/justlabv1/charts/justscanandghcr.io/justlabv1/justscanif the repository or packages are private
If GHCR access is private, log in first:
helm registry login ghcr.io -u YOUR_GITHUB_USERCreate a values file such as justscan-values.yaml:
ingress:
enabled: true
className: nginx
hosts:
- host: scan.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: justscan-tls
hosts:
- scan.example.com
backend:
image:
# Leave empty to use backend-<chart appVersion>.
# Use backend-minimal-1.2.3 for Artifactory Xray-only deployments.
tag: ""
secrets:
jwtSecret: "replace-with-a-long-random-secret"
encryptionKey: "replace-with-32-random-characters"
config:
allowOrigins:
- "https://scan.example.com"
oidc:
enabled: false
persistence:
enabled: true
size: 10Gi
postgresql:
enabled: true
auth:
password: "replace-with-a-db-password"Install a released chart version:
helm install justscan oci://ghcr.io/justlabv1/charts/justscan \
--version 1.2.3 \
--namespace justscan \
--create-namespace \
-f justscan-values.yamlUpgrade an existing release:
helm upgrade justscan oci://ghcr.io/justlabv1/charts/justscan \
--version 1.2.3 \
--namespace justscan \
-f justscan-values.yamlIf you need to publish a new Helm chart version without building or releasing a new app version, run the Release workflow manually from GitHub Actions.
- Set
chart_versionto the new Helm chart version you want to publish. - Set
app_versionto the existing app release whose image tags the chart should keep using.
Example: publish chart version 1.2.4 while still deploying app image tags from 1.2.3.
The workflow will package and push oci://ghcr.io/justlabv1/charts/justscan:1.2.4 with appVersion=1.2.3, so the chart still defaults to backend-1.2.3 and frontend-1.2.3 unless you override the image tags explicitly.
helm dependency build deploy/helm/justscan
helm install justscan deploy/helm/justscan \
--namespace justscan \
--create-namespace \
-f justscan-values.yaml- Released chart packages automatically default
backend.image.tagtobackend-<chart appVersion>andfrontend.image.tagtofrontend-<chart appVersion>. Override either tag only if you need to pin a different image. - For Artifactory Xray-only deployments, set
backend.image.tagtobackend-minimal-<version>and setbackend.config.scanner.enableTrivy=falseplusbackend.config.scanner.enableGrype=false. backend.secrets.jwtSecretis required for all non-trivial deployments.backend.secrets.encryptionKeyshould be a random 32-character string. It is used to encrypt registry credentials at rest.postgresql.auth.passwordis required whenpostgresql.enabled=true.- When
postgresql.enabled=true, the backend automatically usespostgresql.auth.database,postgresql.auth.username, and the Bitnami PostgreSQL password secret. You only needbackend.config.database.*for external databases. backend.secrets.dbPasswordis required whenpostgresql.enabled=falseand you connect to an external PostgreSQL instance.- Use
backend.secrets.existingSecretRefs.<field>.keyto map backend secret keys. Setbackend.secrets.existingSecretwhen most fields live in one Kubernetes Secret; any ref with an emptynameuses that Secret. Setbackend.secrets.existingSecretRefs.<field>.nameonly for fields that live in a different Secret. backend.config.allowOriginsmust include the URL users open in their browser.- If
backend.config.oidc.enabled=true, also setbackend.secrets.oidcClientSecret,backend.config.oidc.issuerUrl,backend.config.oidc.clientId, and eitherbackend.config.oidc.redirectUrioringress.enabled=truewith a valid host. - If your OIDC provider or another outbound HTTPS dependency uses a private or self-signed CA, set
backend.customCAs.configMapNameand/orbackend.customCAs.secretNameto mount PEM CA files into the backend container. backend.persistence.enabled=trueis recommended for production so Trivy DB and cache data survive pod restarts.
Example using different Secrets for the external database password and encryption key:
postgresql:
enabled: false
backend:
config:
database:
server: postgres.example.com
name: justscan
user: justscan
secrets:
existingSecret: justscan-auth
existingSecretRefs:
jwtSecret:
key: jwt-secret
dbPassword:
name: justscan-db-credentials
key: password
encryptionKey:
name: justscan-crypto
key: encryption-keyImportant values exposed by the chart include:
imagePullSecretsfor private image or chart pulls from GHCRnameOverride,fullnameOverride, andserviceAccount.namefor release naming and service account controlbackend.config.scanner.enableTrivy,backend.config.scanner.trivyPath,backend.config.scanner.grypePath,backend.config.scanner.enableGrype,backend.config.scanner.timeout,backend.config.scanner.commandTimeoutSeconds,backend.config.scanner.progressHeartbeatSeconds,backend.config.scanner.staleTimeoutSeconds,backend.config.scanner.concurrency,backend.config.scanner.dbMaxAgeHours, andbackend.config.scanner.enableOsvJavaAugmentationbackend.config.oidc.debug,backend.config.oidc.adminGroups,backend.config.oidc.adminRoles,backend.config.oidc.groupsClaim, andbackend.config.oidc.rolesClaimbackend.customCAs.configMapName,backend.customCAs.secretName, andbackend.customCAs.bundlePathfor custom trust anchors used by OIDC and other outbound TLS callsbackend.persistence.existingClaim,backend.persistence.size, andbackend.persistence.storageClassfrontend.config.apiUrlif the frontend must call a different backend URL than the in-cluster service
If your OIDC provider uses a self-signed or private CA, create a ConfigMap or Secret with the PEM certificate and reference it from the chart.
Example with a ConfigMap:
kubectl create configmap justscan-oidc-ca \
--from-file=oidc-ca.crt=./oidc-ca.crt \
--namespace justscanbackend:
customCAs:
configMapName: justscan-oidc-caExample with a Secret:
kubectl create secret generic justscan-oidc-ca \
--from-file=oidc-ca.crt=./oidc-ca.crt \
--namespace justscanbackend:
customCAs:
secretName: justscan-oidc-caThe backend entrypoint builds a combined CA bundle from the system trust store plus the mounted files and exports it through SSL_CERT_FILE and GIT_SSL_CAINFO before JustScan starts. Restart the backend pod after updating the referenced ConfigMap or Secret.
Show the full values schema:
helm show values oci://ghcr.io/justlabv1/charts/justscan --version 1.2.3You can scan an image in GitLab CI before pushing it to any registry by:
- Building the image in CI
- Exporting it as an archive (
docker save) - Uploading it to JustScan via
POST /api/v1/orgs/<org-uuid>/archive-scans - Polling scan status until completion
Set these in your GitLab project/group CI/CD variables:
JUSTSCAN_API_URL(example:https://scan.example.com)JUSTSCAN_API_TOKEN(JustScan personal or org token)JUSTSCAN_ORG_ID(the organization UUID to receive the scan)
stages:
- security
justscan_prepush_scan:
stage: security
image: docker:27
services:
- name: docker:27-dind
command: ["--tls=false"]
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
IMAGE_REF: "$CI_PROJECT_PATH_SLUG:$CI_COMMIT_SHORT_SHA"
ARCHIVE_PATH: "/tmp/image.tar"
script:
- apk add --no-cache curl jq
- docker build -t "$IMAGE_REF" .
- docker save "$IMAGE_REF" -o "$ARCHIVE_PATH"
- |
CREATE_RESPONSE="$(curl -sS -X POST "$JUSTSCAN_API_URL/api/v1/orgs/$JUSTSCAN_ORG_ID/archive-scans" \
-H "Authorization: Bearer $JUSTSCAN_API_TOKEN" \
-F "archive=@$ARCHIVE_PATH" \
-F "image_name=$CI_PROJECT_PATH" \
-F "image_tag=$CI_COMMIT_SHORT_SHA" \
-F "platform=linux/amd64")"
- echo "$CREATE_RESPONSE" | jq .
- SCAN_ID="$(echo "$CREATE_RESPONSE" | jq -r '.id')"
- test -n "$SCAN_ID" && test "$SCAN_ID" != "null"
- |
for i in $(seq 1 120); do
STATUS_RESPONSE="$(curl -sS "$JUSTSCAN_API_URL/api/v1/scans/$SCAN_ID" \
-H "Authorization: Bearer $JUSTSCAN_API_TOKEN")"
STATUS="$(echo "$STATUS_RESPONSE" | jq -r '.status')"
echo "Scan $SCAN_ID status: $STATUS"
if [ "$STATUS" = "completed" ]; then
CRITICAL="$(echo "$STATUS_RESPONSE" | jq -r '.critical_count // 0')"
HIGH="$(echo "$STATUS_RESPONSE" | jq -r '.high_count // 0')"
echo "Completed. critical=$CRITICAL high=$HIGH"
# Optional policy gate:
# test "$CRITICAL" -eq 0
exit 0
fi
if [ "$STATUS" = "failed" ] || [ "$STATUS" = "cancelled" ]; then
echo "JustScan scan failed:"
echo "$STATUS_RESPONSE" | jq .
exit 1
fi
sleep 5
done
echo "Timed out waiting for JustScan scan result"
exit 1- The organization upload endpoint is authenticated and authorizes the organization before accepting the archive body (
/api/v1/orgs/<org-uuid>/archive-scans). - Archive size limit is
5 GBby default for uploaded archive scans. - Uploaded archives are deleted by JustScan after scan processing.
- Use a pipeline token scoped to the target organization for CI/CD uploads.
Cause: jwt.secret is null or empty in config.yaml.
Fix: Set jwt.secret to a non-empty random string (minimum 32 characters recommended) and restart the backend. Any previously issued tokens will be invalidated and users will need to log in again.
Check:
- PostgreSQL is running on the configured host/port
- The database and user exist with correct permissions
database.passwordis correct
Check:
trivyis installed and accessible at the path configured inscanner.trivy_path- Run
trivy --versionto verify - If you are using the
backend-minimalimage, use registries configured for Artifactory Xray and keep local Trivy scanning disabled
Check:
encryption.keyis set in the config. Credentials are encrypted at rest using this key.- If the key is empty, encryption will fail silently.
Check:
- Your frontend origin is listed in
allow_originsinconfig.yaml - Example: if the frontend runs on port
3000, add"http://localhost:3000"to the list
The NVD (National Vulnerability Database) API key is used to enrich CVE entries with additional metadata from nvd.nist.gov. It is optional. JustScan and Trivy will still scan and report vulnerabilities without it, but CVE details may be less complete.
The key is free and provided by NIST (the US National Institute of Standards and Technology).
- Go to https://nvd.nist.gov/developers/request-an-api-key
- Enter your email address and submit the form
- NIST will email you an API key within a few minutes
- Set it in your config:
vuln_kb:
nvd_api_key: "your-key-here"
cache_days: 7Or via environment variable:
export BACKEND_VULN_KB_NVD_API_KEY="your-key-here"- Without a key: 5 requests / 30 seconds
- With a key: 50 requests / 30 seconds
For most self-hosted deployments the unauthenticated limit is sufficient. If you scan many images or refresh the CVE cache frequently, use a key to avoid rate limiting.
services/backend: Go backendservices/frontend: Next.js frontenddeploy/docker-compose: Docker Compose setupdeploy/helm/justscan: Helm chart
# Backend
cd services/backend
go run main.go
go test ./...
# Frontend
cd services/frontend
pnpm install
pnpm dev
pnpm lint
pnpm buildContributions are welcome. Please open an issue first for significant changes so implementation details can be aligned before you start.
This project is licensed under the MIT License. See LICENSE.