npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@conterra/vuln-scan

v1.0.11

Published

con terra vulnerability scan process

Readme

ct-vuln-scan

The utility is a wrapper around the following tools:

  • grype - Vulnerability scanner for container images and filesystems.
  • trivy - Comprehensive and versatile security scanner.
  • oss index - Free catalogue of open source components and scanning tools to help developers identify vulnerabilities.

It also supports the OpenVex specification for vulnerability status and justification.

For an architectural overview see ARCHITECTURE.md. For build, test and contribution guidelines see AGENTS.md.

Pre-Requisites

You need to have docker and node (>= 20) installed on your machine.

Installation

The utility is published as an npm package and can be installed globally or locally.

# global
$ npm install -g @conterra/vuln-scan

# local
$ npm install @conterra/vuln-scan

After installation the following commands are available:

  • ct-vuln-scan - Triggers the vulnerability scan.
  • ct-vuln-add-vex - Interactive helper to add a new vex statement to a file.
  • ct-vuln-add-project - Adds a new project to the configuration file based on an existing project entry and adds it to all vex statements.
  • ct-vuln-remove-project - Removes a project from the configuration file and all vex statements (or only from vex statements matching a given vulnerability id).
  • ct-vuln-add-project-to-vex - Updates vex files to include a new project version. Already part of ct-vuln-add-project.
  • ct-vuln-jira-report - Reads a scan-summary.json and creates Jira issues for newly detected vulnerabilities.
  • ct-vuln-auto-vex - Auto-creates OpenVEX statements for newly detected vulnerabilities below a configurable severity threshold.

Configuration

Create a vuln-scan-conf.json file in the working directory.

Minimal configuration:

{
    "$schema": "./node_modules/@conterra/vuln-scan/dist/schema/conf-schema.json",
    "projects": [
        {
            "name": "mapapps",
            "version": "4.18.2",
            "purl": "pkg:maven/de.conterra.mapapps/[email protected]",
            "sbomFile": "./input/sboms/mapapps-4.18.2.cdx.json"
        }
    ]
}

Full configuration with all available options and their defaults:

{
    "$schema": "./node_modules/@conterra/vuln-scan/dist/schema/conf-schema.json",

    // Action on newly detected vulnerabilities: "fail" | "warn" | "ignore"
    // - "fail": exits with code 1
    // - "warn": logs ##vso[task.complete result=SucceededWithIssues;]
    // - "ignore": no effect
    "onNewVulnerabilities": "fail",
    // Action on vulnerabilities no longer detected, same values as above.
    "onNoLongerDetectedVulnerabilities": "ignore",
    // Maven repository to fetch sboms from.
    // Set MAVEN_REPO_USER / MAVEN_REPO_PW for authentication.
    "mavenRepo": "https://repository.conterra.de/repository/maven-mirror-ct",
    // Scanners to use. Pin a version with `<scanner>@<version>`, e.g. `[email protected]`.
    "scanners": ["grype", "trivy", "ossindex"],
    // (optional) URLs of additional vex files to download.
    "vexUrls": ["https://example.com/my-vex.json"],
    // Directory containing vex files.
    "vexDir": "./input/vex",
    // Directory for scan output.
    "outDir": "./output",
    // Cache directory for grype/trivy databases.
    "cacheDir": "./cache",
    // If true, results for each project are written to a sub-directory of outDir.
    "createSubDirsForProject": true,
    // Console output style:
    //   "BY_PROJECT_DETAILED" (default) - group by project, print CVE details
    //   "BY_PROJECT"                    - group by project, no CVE details
    //   "BY_CVE"                        - group by CVE, no CVE details
    //   "BY_CVE_DETAILED"               - group by CVE, print CVE details
    "consoleReport": "BY_PROJECT_DETAILED",
    "projects": [
        {
            "name": "mapapps",
            "version": "4.18.3",
            // identifier used to match vex statements
            "purl": "pkg:maven/de.conterra.mapapps/[email protected]",
            // maven coordinates used to download the sbom from mavenRepo
            "sbomMavenCoordinates": "de.conterra.mapapps:ct-mapapps-rollout:4.18.3:sbom"
        },
        {
            "name": "mapapps",
            "version": "4.18.2",
            "purl": "pkg:maven/de.conterra.mapapps/[email protected]",
            // local sbom file
            "sbomFile": "./input/sboms/mapapps-4.18.2.cdx.json",
            // (optional) skip this project
            "enabled": true,
            // (optional) scan but never break the build
            "silent": false,
            // (optional) only break the build for severities >= this value.
            // One of: "CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN"
            "minimumSeverity": "HIGH"
        }
    ]
}

Usage - Scan

Run from the directory containing vuln-scan-conf.json:

# scan all projects
$ ct-vuln-scan

# scan all versions of one project
$ ct-vuln-scan mapapps

# scan a specific project version
$ ct-vuln-scan mapapps 4.18.2

A typical working directory layout:

/vuln-scan/              # working directory
├── vuln-scan-conf.json
├── input/
│   ├── sbom/            # sbom files
│   └── vex/             # vex files
└── output/              # aggregated scan results

Environment variables

# (optional) maven repo authentication for sbom download
MAVEN_REPO_USER=[username]
MAVEN_REPO_PW=[password]

# (optional) sonatype OSS Index authenticated API
OSS_INDEX_API_USER=[username]
OSS_INDEX_API_TOKEN=[token]
OSS_INDEX_BASE_URL=[url]   # default: https://api.guide.sonatype.com (since 1.0.8)

# (optional) authentication for vex files referenced via vexUrls
VEX_URLS_USER=[username]
VEX_URLS_PW=[password]

# (optional) helps with the trivy DB GitHub rate limit
# https://aquasecurity.github.io/trivy/v0.38/docs/references/troubleshooting/#github-rate-limiting
TRIVY_GITHUB_TOKEN=[token]

# (optional) verbose logging
VERBOSE=true

# (optional) if "true", a project's product-id is automatically removed
# from vex statements covering vulnerabilities no longer detected for that project.
AUTO_REMOVE_UNUSED_STATEMENTS=true

These variables can also be defined in a .env file in the working directory.

Output files

Two summary files are written directly into outDir:

  • scan-summary.txt - The console "scan summary" output as plain text.

  • scan-summary.json - Machine-readable summary, for post-processing or downstream tools. Example (truncated):

    {
        "errors": [
            {
                "project": "[email protected]",
                "error": "Failed to fetch sbom file ..."
            }
        ],
        "vulnerabilities": {
            "CVE-2025-48988": {
                "id": "CVE-2025-48988",
                "severity": "HIGH",
                "title": "tomcat: Apache Tomcat DoS in multipart upload",
                "description": "...",
                "components": ["pkg:maven/org.apache.tomcat.embed/[email protected]"],
                "refs": ["https://avd.aquasec.com/nvd/cve-2025-48988"]
            }
        },
        "vulnerabilityToProject": {
            "CVE-2025-48988": ["[email protected]"]
        },
        "projectToVulnerability": {
            "[email protected]": ["CVE-2025-48988"]
        },
        "noLongerDetectedVulnerabilities": {
            "CVE-2023-52070": ["[email protected]"]
        }
    }

For each scanned project, raw scanner output, an aggregated JSON, a SARIF file (used by the Azure DevOps SARIF tab) and the matching OpenVEX statements are written to a <project-name>-<project-version>/ sub-directory (when createSubDirsForProject is true). See ARCHITECTURE.md §5 for the full file layout.

Azure DevOps Pipelines integration

Publish the output directory as build artifact CodeAnalysisLogs:

- task: PublishBuildArtifacts@1
  inputs:
      PathtoPublish: "output"
      ArtifactName: "CodeAnalysisLogs"

The SARIF SAST Scans Tab extension visualizes the .sarif.json files in the artifact.

If onNewVulnerabilities is set to warn, the pipeline will be marked as SucceededWithIssues when new vulnerabilities are found. With the default fail, the pipeline fails (exit code 1).

Usage - Add Vex

Experimental maintenance workflow for recording a decision about whether a vulnerability affects a project.

$ ct-vuln-add-vex

The interactive prompt creates a CVE-<id>.json file in vexDir, e.g.:

{
    "@context": "https://openvex.dev/ns/v0.2.0",
    "@id": "https://openvex.dev/docs/public/vex-fc763e6e...",
    "author": "conterra",
    "timestamp": "2024-10-01T18:54:23+02:00",
    "last_updated": "2024-10-01T19:58:13+02:00",
    "version": 2,
    "statements": [
        {
            "vulnerability": { "name": "CVE-2023-52070" },
            "timestamp": "2024-10-01T19:58:13+02:00",
            "products": [
                { "@id": "pkg:maven/de.conterra.mapapps/[email protected]" },
                { "@id": "pkg:maven/de.conterra.mapapps/[email protected]" }
            ],
            "status": "not_affected",
            "impact_statement": "<short rationale>"
        }
    ]
}

For details see the OpenVex spec.

Valid status values:

| Status | Description | | ------------------- | -------------------------------------------------------------------------- | | not_affected | The product is known to be not affected by this vulnerability. | | affected | The product is known to be affected by this vulnerability. | | fixed | The product contains a fix for this vulnerability. | | under_investigation | It is not yet known whether the product is affected; still being assessed. |

Valid justification values when status is not_affected (full text in the VEX Status Justification PDF):

| Justification | Short description | | ------------------------------------------------- | ------------------------------------------------------------ | | component_not_present | Vulnerable component is not present in the product. | | vulnerable_code_not_present | Vulnerable code is excluded by configuration or build. | | vulnerable_code_not_in_execute_path | Vulnerable code is shipped but never called. | | vulnerable_code_cannot_be_controlled_by_adversary | Attacker cannot influence the vulnerable code path. | | inline_mitigations_already_exist | Built-in, non-disable-able mitigations prevent exploitation. |

Usage - Add Project

Add a new project entry to the configuration and propagate it to vex statements:

$ ct-vuln-add-project <reference-name> <reference-version> <new-version>

# example: clone [email protected] into a new [email protected] entry
$ ct-vuln-add-project mapapps 4.18.2-SNAPSHOT 4.18.2

The new entry inherits all options from the reference project, and its product-id is added to every vex statement that already references the reference project.

Usage - Remove Project

Remove a project entry from the configuration and all matching vex statements:

$ ct-vuln-remove-project <name> <version>

$ ct-vuln-remove-project mapapps 4.18.2-SNAPSHOT

Remove project from vex files matching a vulnerability id

Useful when a single vulnerability is no longer relevant for a project but the project itself remains:

$ ct-vuln-remove-project <name> <version> <vuln-id>

$ ct-vuln-remove-project mapapps 4.18.2-SNAPSHOT CVE-2024-38820

Usage - Add Project to Vex

Add a new project version to all vex statements that already reference an existing version. Already part of ct-vuln-add-project; useful when the configuration entry already exists.

$ ct-vuln-add-project-to-vex <existingPurl> <newPurl>

$ ct-vuln-add-project-to-vex \
    pkg:maven/de.conterra.mapapps/[email protected] \
    pkg:maven/de.conterra.mapapps/[email protected]

Usage - Simulate a release

Combine the project commands to simulate releasing 4.18.3 and preparing the next dev version 4.18.4-SNAPSHOT:

$ ct-vuln-add-project mapapps 4.18.3-SNAPSHOT 4.18.3
$ ct-vuln-remove-project mapapps 4.18.3-SNAPSHOT
$ ct-vuln-add-project mapapps 4.18.3 4.18.4-SNAPSHOT

Usage - Jira Report

ct-vuln-jira-report reads scan-summary.json produced by ct-vuln-scan and creates Jira issues for new vulnerabilities. For each vulnerability the tool:

  1. Checks (via JQL) whether an issue with the same vulnerability ID already exists in the target Jira project.
  2. If not, creates a new issue with a structured description (CVE details, affected artefacts and products, placeholders for assessment and remediation) and tags every currently-affected scan project as a proj:<name>_v<version> label on the issue.
  3. If an issue already exists, the project tags are compared with the current scan result. For every newly-affected project the issue is updated:
    • the missing proj: label is added,
    • a comment is posted listing only the additional projects (without re-emitting the CVE details),
    • and if the issue is currently in status category done it is transitioned to status Open and re-added to the active sprint of its routed scrum board (when one is configured).

Project tags use the form proj:<name>_v<version> (the @ separator is replaced with _v to keep labels JQL-friendly). On the very first run after upgrading, existing Jira issues will not yet carry these labels, so every currently-matching project will be added at once and a single follow-up comment will be posted.

When an issue is updated because new projects became affected, any addToBoard issueLabels that would now match (per the same first-sprint-wins / Kanban-aggregate rules used at creation) are added to the issue retroactively. Labels already present on the issue are not re-emitted. Sprint placement is not changed for issues that are still in progress; only when a closed issue is re-opened is it placed in the active sprint of whichever scrum entry currently matches first — which may differ from the original sprint if the routing config has been edited since the issue was created.

Environment variables

[email protected]   # Jira account e-mail
JIRA_API_TOKEN=<token>       # https://id.atlassian.com/manage-profile/security/api-tokens

These can also be set in a .env file in the working directory.

Configuration - jira-conf.json

{
    "$schema": "./node_modules/@conterra/vuln-scan/dist/schema/jira-config-schema.json",
    "jiraUrl": "https://myorg.atlassian.net",
    "issues": [
        {
            "jiraProject": "PLATFORM",
            "issueType": "CVE",
            "issueLabels": ["security"],
            "addToBoard": [
                {
                    "sprintBoardId": 10,
                    "issueLabels": ["team-a"],
                    "matchProjects": ["mapapps@*"]
                },
                {
                    "issueLabels": ["team-b"],
                    "matchProjects": ["smartfinder@*"]
                }
            ],
            "reportProjects": ["mapapps@*", "smartfinder@4.*"]
        },
        {
            "jiraProject": "OTHER",
            "issueType": "Bug",
            "reportProjects": ["*"]
        }
    ]
}

| Field | Required | Description | | --------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | jiraUrl | yes | Base URL of the Jira instance. | | issues | yes | Routing rules. Each rule is evaluated independently, so a vulnerability can be reported to multiple projects. | | issues[].jiraProject | yes | Jira project key, e.g. PLATFORM. | | issues[].issueType | yes | Jira issue type, e.g. Bug, CVE. | | issues[].reportProjects | yes | Glob patterns (* wildcard) matched against scan project identifiers (name@version). | | issues[].issueLabels | no | Labels added to every issue created by this rule, merged with labels from matching addToBoard entries. | | issues[].addToBoard | no | Per-board routing rules; see below. | | addToBoard[].sprintBoardId | no | Scrum board id. If set, the issue is assigned to the board's active sprint via customfield_10020. | | addToBoard[].issueLabels | no | Labels contributed by this entry. | | addToBoard[].matchProjects | no | Glob patterns to activate this entry. Defaults to ["*"]. | | autoCloseBelowSeverity | no | Default auto-close threshold inherited by every issues[] entry that does not override it. One of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL. | | issues[].autoCloseBelowSeverity | no | Per-rule override of the top-level threshold. |

addToBoard resolution: only entries whose matchProjects cover the vulnerability's affected scan projects are considered. The first matching scrum entry (with sprintBoardId) claims the sprint; further scrum entries are ignored. Kanban entries (no sprintBoardId) all contribute their labels.

Auto-close low-severity issues

When autoCloseBelowSeverity (top-level or per-issue rule) is set, newly-created issues whose vulnerability severity is strictly below the threshold are immediately transitioned to a status whose Jira status category is done (the first such transition exposed by the workflow is used, regardless of the status's display name and locale). The board/sprint assignment, labels and comment described above still happen before the transition, so the issue is reachable on the configured board even though it starts out closed. A short German-language comment is added explaining the auto-closure.

For existing issues, the threshold only suppresses re-opening: when a closed sub-threshold issue gains a newly-affected project, the project label and "Zusätzlich gemeldet für" comment are added as usual, but the issue is not re-opened (and never re-closed). Issues at or above the threshold keep the existing re-open + re-add-to-active-sprint behavior.

Threshold semantics mirror ct-vuln-auto-vex: with "HIGH", severities MEDIUM, LOW and UNKNOWN are auto-closed, while HIGH and CRITICAL stay open.

CLI

Options:
  -c, --jira-config <path>   Path to jira-conf.json (default: ./jira-conf.json)
  -s, --summary <path>       Path to scan-summary.json (default: ./output/scan-summary.json)
      --dry-run              Print what would be created without calling Jira
  -h, --help                 Show help

Examples:

$ ct-vuln-jira-report
$ ct-vuln-jira-report --jira-config ./config/jira-conf.json --summary ./output/scan-summary.json
$ ct-vuln-jira-report --dry-run

Usage - Auto VEX

ct-vuln-auto-vex is a post-processor for ct-vuln-scan. It auto-creates OpenVEX statements for newly detected vulnerabilities whose severity is strictly below a configurable threshold, reducing manual triage noise for low-impact CVEs.

Generated files are written to <vexDir>/<subDir>/<CVE-ID>.json and are picked up automatically by the next scan run (the vex store recurses into sub-directories). Manual VEX files in vexDir always take precedence: any existing statement covering a CVE suppresses it from the "new vulnerabilities" list before this tool runs.

Configuration - auto-vex-conf.json

Place this file next to vuln-scan-conf.json.

{
    "$schema": "./node_modules/@conterra/vuln-scan/dist/schema/auto-vex-config.schema.json",
    "impactStatement": "Auto-acknowledged: no evidence of exploitability in our deployment context.",
    "maxSeverity": "HIGH",
    "author": "conterra",
    "subDir": "auto"
}

| Field | Required | Default | Description | | ----------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | impactStatement | yes | - | impact_statement written into every auto-generated VEX statement. | | maxSeverity | no | "HIGH" | Exclusive threshold; CVEs strictly below this severity get a statement. One of UNKNOWN, LOW, MEDIUM, HIGH, CRITICAL. With the default, MEDIUM is auto-vexed but HIGH is not. | | author | no | "conterra" | author field of the OpenVEX document. | | subDir | no | "auto" | Sub-directory of vexDir for generated files. |

Usage

# defaults: ./auto-vex-conf.json + ./output/scan-summary.json + vexDir from vuln-scan-conf.json
$ ct-vuln-auto-vex

# custom paths
$ ct-vuln-auto-vex --auto-vex-config ./config/auto-vex-conf.json --summary ./output/scan-summary.json

# preview without writing files
$ ct-vuln-auto-vex --dry-run

Behaviour

  • Only entries from summary.vulnerabilities are processed; this map already contains only newly detected CVEs, so previously declared vulnerabilities are never re-processed.
  • If <vexDir>/<subDir>/<CVE-ID>.json already exists, the new statement is appended (document version is bumped). Manual edits in the same file are preserved.
  • The generated statement uses status: "not_affected" and the configured impactStatement. No justification is set.
  • Affected scan project identifiers (name@version) are used as the product @id, matching what VexStore expects when populated from the scan summary.

Contributing

See AGENTS.md for toolchain, build, lint and test conventions.