Multi-branch vulnerability aggregator, dependency chain tracer, and automated remediation engine for Node.js / npm repositories.
Managing security vulnerabilities across large Node.js repositories is often fragmented between branch checkouts, local npm audit scans, Jira security compliance tickets, and GitHub Dependabot alerts.
security-report streamlines this into an automated 3-step pipeline:
- Step 1: Aggregate & Analyze
- Resolves the target repository:
org/reposhorthand and Git URLs are cloned into an isolatedREPOS/<org>/<name>cache; a local path is used in place (nothing is copied intoREPOS/). - Detaches the base repository's
HEAD— including local targets — so every branch name is free, then creates onegit worktreeper target branch, reset to the latest fetchedorigin/<branch>when that remote branch exists (otherwise the existing local branch is used, or a new one is created). - Resolves dependency hierarchies and monorepo workspace relationships via
@npmcli/arboristvirtual trees, eliminating false cross-product ancestry when a package exists across multiple distinct versions. - Runs
npm auditwith intermediate package filtering: dependency carriers lacking direct advisories inviaare ignored as top-level findings and preserved in ancestor chains. - Cross-references findings with Jira CVE tickets and GitHub Dependabot alerts, querying GitHub Advisory API and OSV to resolve CVE identifiers and all active major version line fixes (e.g.
2.4.5,3.1.6,4.1.3). - Compares installed versions against target fixes to track
Status(✅ Resolvedvs⚠️ Open), identifying flaws that remain logged in Jira or Dependabot but are already solved in code. - Consolidates multiple CVEs and advisories per package to calculate the optimal safe version that resolves all flaws (prioritizing safe in-major updates when installed version is known).
- Outputs machine-readable JSON (
reports/security-report.json) and human-readable Markdown (reports/security-report.md) featuring:- Package Overview Table:
Package,Status(✅ Resolved/⚠️ Open),Severity,Type,Current Version,Target Fix(all major line fixes separated by<br>),CVEs(separated by<br>), andSources(linked advisories, Jira tickets, Dependabot alerts separated by<br>). - Detailed Package Findings: Unnumbered findings with status badges, explicit current and target fix versions, individual advisory links, and strictly partitioned Direct Dependencies and Transitive Dependency Paths in compact code blocks (
<pkgFile>/<section>/...).
- Package Overview Table:
- Resolves the target repository:
- Step 2: Automated Remediation (5-Tier Preference Hierarchy)
bump-direct-and-lockfile: For dual dependencies (Direct & Indirect, e.g.js-yaml), bumps the directpackage.jsonsemver constraint AND issues lockfile updates to synchronize all transitive instances.bump-direct: Directly updatespackage.jsonsemver constraints for direct dependencies (preserving formatting and^/~prefixes).bump-direct-parent: If a direct root parent (e.g.msw) has an update available that patches the transitive dependency, bumps the direct parent inpackage.json.lockfile-update: If the parent package's declared semver range already permits the safe patched version (e.g. parent requires>=0.7.0 <0.9.0and0.8.8is safe), updates the lockfile directly without introducing unnecessarypackage.jsonoverrides.package-override: Fallback applied only when parent ranges strictly forbid the safe version and no direct parent update exists. Overrides are placed in the rootpackage.json, scoped by workspace (overrides[workspaceScope][pkg] = ver) or parent package where applicable.- Packages already resolved in the repository skip redundant edits and lockfile changes.
- Performs atomic lockfile synchronization (
npm install --package-lock-only, thennpm update <pkg> --package-lock-onlyfor each package that must move within existing ranges) and post-remediation audit verification. - Without
--commit, the branch worktree is kept and its path printed — it holds the only copy of the applied fix. A failed remediation also keeps its worktree for inspection.
- Step 3: Structured Commit Generation & Publishing
- Generates conventional commit messages linking CVE IDs, GHSA identifiers, advisory URLs, Jira tickets, Dependabot alert numbers, and the exact dependency path.
- Stages the touched
package.jsonfiles (root and workspaces) pluspackage-lock.json, commits on the target branch, and optionally pushes. The commit is guaranteed to advancerefs/heads/<branch>; if the branch is held by another worktree the run aborts instead of stranding the commit.
- Node.js
>= 20.0.0 - Git
>= 2.20.0 - npm
>= 7.0.0
# Clone and install dependencies
git clone <this-repo-url> security-report
cd security-report
npm install
# Link binary globally or use directly via node
npm link
# or run with: ./bin/sec-remediate.js# 1. Scan multiple branches on a repository (Step 1)
sec-remediate scan facebook/react --branches main,release/18.x
# 2. Preview remediations and commit messages without modifying files (Dry-Run)
sec-remediate fix facebook/react --branches main --dry-run
# 3. Remediate and commit changes to the target branches (Step 2 + Step 3)
sec-remediate fix facebook/react --branches main,release/18.x --commit
# 4. Remediate, commit, and push to remote
sec-remediate fix facebook/react --branches main --commit --pushConfiguration is read from CLI options, environment variables, and a .security-report.json (or security-report.config.json) file in the working directory.
Precedence is CLI option > environment variable > config file, except for githubToken, jira.baseUrl, jira.email and jira.apiToken, where an explicit config-file value takes precedence over the environment variable.
| Variable | CLI Flag | Description | Default |
|---|---|---|---|
SEC_REPO |
repo / [repo] |
Target repository specifier (org/repo, URL, or local path) |
null |
SEC_REPOS_DIR |
--repos-dir <path> |
Directory where repositories are cloned and cached | ./REPOS |
SEC_REPORTS_DIR |
--reports-dir <path> |
Directory where JSON and Markdown reports are saved | ./reports |
SEC_BRANCH_MAP |
--branch-map <mapping> |
Translation table mapping upstream branches to downstream versions (e.g. main=8.3,release-0.11=8.2,release-0.10=8.1) |
Built-in MTA map |
GIT_PROTOCOL |
— | Git clone protocol (https or ssh) |
https |
GITHUB_TOKEN |
--github-token <token> |
GitHub Personal Access Token for Dependabot alerts and private repos | "" |
GITHUB_API_URL |
— | Base URL for GitHub API (for GitHub Enterprise) | https://api.github.com |
JIRA_BASE_URL |
--jira-base-url <url> |
Atlassian Jira instance URL (e.g., https://my-org.atlassian.net) |
"" |
JIRA_EMAIL |
--jira-email <email> |
Jira user email address (for Basic Auth) | "" |
JIRA_API_TOKEN |
--jira-api-token <token> |
Jira API token or Personal Access Token (PAT) | "" |
JIRA_PROJECT |
--jira-project <key> |
Jira Project Key for CVE tickets | MTA |
JIRA_JQL |
--jira-jql <query> |
Custom JQL query for Jira ticket retrieval | Auto-generated |
SEC_COLLECTORS |
--collectors <list> |
Active collectors to run (npm-audit, jira, dependabot) |
npm-audit,jira,dependabot |
SEC_DEBUG |
--debug |
Save raw collector outputs to security-<name>-collection.json |
false |
Note on Credentials:
.security-report.jsonholds API tokens and is listed in.gitignore— keep it untracked. The GitHub token is never written into a clone URL or.git/config: it is passed per git invocation viaGIT_CONFIG_*and stripped from any git error message. Library callers can bypass config-file discovery entirely withloadConfig({ noConfigFile: true }); the test suite relies on this to stay hermetic.
Note on Configuration Validation: If the Jira collector is enabled but its configuration is incomplete (missing
JIRA_BASE_URLorJIRA_API_TOKEN), the tool automatically disables the Jira collector, logs a warning notice, and continues scanning with the remaining active collectors without failing.
{
"repo": "konveyor/tackle2-ui",
"branches": ["main", "release-0.10", "release-0.9"],
"branchMap": {
"main": ["8.3.x", "8.3", "mta-8.3", "MTA 8.3"],
"release-0.11": ["8.3.x", "8.3", "mta-8.3", "MTA 8.3"],
"release-0.10": ["8.2.x", "8.2", "mta-8.2", "MTA 8.2"],
"release-0.9": ["8.1.x", "8.1", "mta-8.1", "MTA 8.1"]
},
"githubToken": "<token, or use `gh auth token`>",
"jira": {
"baseUrl": "https://redhat.atlassian.net",
"project": "MTA",
"email": "<name>@redhat.com",
"apiToken": "<token>",
"jql": "project = \"Migration Toolkit for Applications\" and labels = \"security\" and (summary ~ \"mta-ui-rhel8\" or summary ~ \"mta-ui-rhel9\" or summary ~ \"mta-ui-rhel10\") and status != Closed"
},
"reposDir": "REPOS",
"reportsDir": "reports",
"collectors": ["npm-audit", "jira", "dependabot"],
"allowOverrides": true,
"debug": false
}Scans the target repository across branches, blends vulnerability sources, and produces the intermediate reports. If [repo] is omitted, the repo value from the config file (or SEC_REPO) is used. report is a true alias and accepts every option below.
sec-remediate scan [repo] [options]Options:
-b, --branches <branches>: Comma-separated list of branches to scan (e.g.main,release/1.0).--all-branches: Scan all branches found in the repository.--repos-dir <path>: Custom directory to store cloned repositories (default:REPOS).--reports-dir <path>: Custom directory for generated reports (default:reports).--github-token <token>: GitHub API token.--jira-base-url <url>: Atlassian Jira base URL.--jira-email <email>: Jira user email.--jira-api-token <token>: Jira API token.--jira-project <project>: Jira Project Key (default:MTA).--jira-jql <query>: Custom JQL query for Jira ticket retrieval.--branch-map <mapping>: Upstream branch to downstream version mapping (e.g.main=8.3,release-0.11=8.2).--collectors <list>: Comma-separated list of collectors to enable (e.g.jiraornpm-audit,jira).--no-npm-audit: Disable npm audit collector.--no-jira: Disable Jira collector.--no-dependabot: Disable GitHub Dependabot collector.--debug: Save intermediate collector outputs tosecurity-<name>-collection.json.
Runs a scan, then applies remediations (package.json updates, lockfile synchronization), verifies the result with a second npm audit, and optionally creates Git commits. If [repo] is omitted, the repo value from the config file (or SEC_REPO) is used.
sec-remediate fix [repo] [options]Options:
-b, --branches <branches>: Comma-separated list of branches to remediate.--all-branches: Remediate all branches found in the repository.--dry-run: Print the changes and the generated commit message without touchingpackage.jsonor the lockfile. The scan reports in--reports-dirare still written.--commit: Create Git commits on the target branches with motivating links.--push: Push created commits to the remote repository. Requires--commit; on its own it has no effect.--no-overrides: Do not insert npmoverridesintopackage.json; findings that only have an override strategy are left unremediated.- All connection and path options from
scan(--github-token,--jira-*,--branch-map,--repos-dir,--reports-dir,--collectors,--no-*,--debug).
Without --commit and without --dry-run the changes are applied and the worktree is retained; the path is printed so the result can be reviewed and committed by hand.
| Package | Status | Severity | Type | Current Version | Target Fix | CVEs | Sources |
|---|---|---|---|---|---|---|---|
fast-uri |
HIGH | Direct | 2.4.2 |
2.4.53.1.64.1.3 |
CVE-2026-75931 |
npm audit | |
qs |
✅ Resolved | HIGH | Direct | 6.16.0 |
6.16.0 |
CVE-2026-82417 |
MTA-7680 #42 |
#### `js-yaml` — HIGH (⚠️ Open)
- **Status:** ⚠️ Open (installed version `3.15.1, 4.3.0` is vulnerable)
- **Current Version:** `3.15.1, 4.3.0`
- **Target Fix Version:** `4.3.2`
- **CVE Identifiers (1):** `CVE-2026-82418`
- **Jira Ticket:** [MTA-7685](https://issues.example.com/browse/MTA-7685)
- **Tracked Advisories & CVEs (1):**
- [Advisory](https://github.com/advisories/GHSA-xxxx) - [Jira MTA-7685](https://issues.example.com/browse/MTA-7685) - `CVE-2026-82418` js-yaml prototype pollution
- **Dependency Type:** Direct & Indirect
- **Direct Dependencies:**
```text
client/package.json/dependencies/js-yaml@^4.3.0
cypress/package.json/devDependencies/js-yaml@^4.3.0- Transitive Dependency Path:
package.json/devDependencies/eslint/.../js-yaml@3.15.1 client/package.json/dependencies/jest/.../js-yaml@3.15.1
Note: Compact path summary shown. To inspect full transitive tree, run
npm ls js-yamlornpm why js-yaml.
Consolidated Remediation Plan (bump-direct-and-lockfile):
Note: Package is both a direct dependency and required transitively. Resolution updates package.json semver and synchronizes lockfile for all instances.
- Update
package.jsonfile(s):- Bump
js-yamlfrom^4.3.0to^4.3.2independencies(client/package.json) - Bump
js-yamlfrom^4.3.0to^4.3.2indevDependencies(cypress/package.json)
- Bump
- Synchronize lockfile:
npm update js-yaml --package-lock-only
---
## 6. Sample Commit Message
When running with `--commit`, the engine creates structured commit messages:
```text
fix(deps): remediate 2 vulnerable packages on branch main
Automated security remediation applied based on aggregated audit findings.
### Resolved Packages & Security Advisories:
- [HIGH] tough-cookie (2.5.0 -> 4.1.3) (CVE-2023-26136)
- Title: Prototype Pollution in tough-cookie
- Advisory: https://github.com/advisories/GHSA-72xf-g2v4-qvf3
- Jira: MTA-7680 (https://issues.example.com/browse/MTA-7680)
- Dependabot: #42 (https://github.com/org/repo/security/dependabot/42)
- Chain: package.json/dependencies/request-lib/.../tough-cookie@2.5.0
- [CRITICAL] lodash (4.17.15 -> 4.17.21) (CVE-2021-23337)
- Title: Command Injection in lodash
- Advisory: https://github.com/advisories/GHSA-35jh-r3h4-6jhm
- Chain: package.json/dependencies/lodash@4.17.15
### Changes Applied:
- Updated lodash ^4.17.15 -> ^4.17.21 in dependencies
- Added override: "tough-cookie": "4.1.3"
- Synchronized package-lock.json
Verification: Post-remediation npm audit scan reported 0 remaining vulnerabilities.
npm testRuns the complete unit and integration suite on Node's native node:test runner (no external test framework). git and npm must be on PATH; the integration test creates throwaway repositories under the system temp directory.
The suite is hermetic: every test that touches loadConfig passes noConfigFile: true, so a .security-report.json in the working directory is never read and no credential ever leaves the machine during a test run.