From 83caf737b708e018d89a05ad9d2599041fae12f3 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Tue, 21 Apr 2026 11:54:08 +0900 Subject: [PATCH 1/4] Fix: wrong package coordinates sent to NCM API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fix: wrong package coordinates sent to NCM API ## Summary | # | Report | Status | Where | |---|---|---|---| | 1 | "sending odd packages versions to the API like this one jwt version 0.0.0" | Fixed | `lib/ncm-analyze-tree.js`, `commands/report.js` | | 2 | `NCM_TOKEN=… NCM_API=… ncm` "with no success" | Not a bug; documented below | — | | 3 | "still a diff on npm audit and ncm report" | Fixed (root cause shared with #1) | `lib/ncm-analyze-tree.js` | | 4 | "we would need to investigate if our vulnDB has all the vulns" | Now meaningfully testable | see below | Tests: **24 / 24** pass (`npm run test-only`). --- ## Root cause (issues 1 & 3) `lib/ncm-analyze-tree.js` had been rewritten to use [`dependency-tree`](https://www.npmjs.com/package/dependency-tree) — a **source-code import analyzer** — instead of [`universal-module-tree`](https://www.npmjs.com/package/universal-module-tree), which walks the real npm dependency graph (`package-lock.json` → `yarn.lock` → `node_modules`). Three concrete defects in that code produced the payloads the user saw: 1. **Filename-as-package-name with placeholder version.** `dependency-tree` returns resolved file paths. The replacement code turned each path into a "package": ```js const name = path.basename(dep, path.extname(dep)); const childNode = { data: { name, version: '0.0.0' }, // hard-coded children: [] }; ``` So `jsonwebtoken.js` → `jwt @ 0.0.0`, `index.js` → `index @ 0.0.0`, and similar. That is the exact `jwt version 0.0.0` entry the user saw going out. 2. **Range-string fallback also produced `0.0.0`.** The auxiliary `getNpmDependencies` helper only read direct deps from `package.json` and did: ```js const cleanVersion = version.replace(/[^\d.]/g, '') || version; ``` For `"latest"`, `"file:…"`, `"git+https://…"`, `"workspace:*"`, etc. the strip leaves `''` and the `||` fallback is still non-semver — so those deps were emitted as `name @ 0.0.0` too. 3. **Missing transitive deps.** `dependency-tree` only surfaces files the entry point actually `require()`s, and the `package.json` fallback only listed direct dependencies. The full transitive graph was never walked. This is the direct mechanical cause of the diff vs `npm audit`. 4. **`commands/report.js` had been patched to hide the fallout.** Any `null` / `undefined` `version` returned by the NCM service was coerced to `'0.0.0'` and still included in the rendered report, so bogus rows showed up in the UI too. ## Fix ### `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines) - Reverted to `universal-module-tree` (already in `dependencies`, and the same library `commands/details.js` uses). - Removed, in full: - the `dependency-tree` invocation and entry-point discovery logic (guessing `index.js`, `app.js`, `bin/*.js`, …), - filename-as-package-name logic, - `getNpmDependencies` and the duplicate `readPackagesFromPackageJson` that stripped `^` / `~` and fell back to `'0.0.0'`, - dead counters. The new file gets a tree from `universalModuleTree(dir)`, walks it keeping dedup + `paths` info, and POSTs `{ name, version }` pairs to the GraphQL endpoint — same shape that already worked in `commands/details.js`. ### `commands/report.js` - Removed the `effectiveVersion = '0.0.0'` coercion. Packages the NCM service returns with `version === null` (unpublished / unknown) are now **skipped** instead of rendered as `@ 0.0.0`: ```js if (version === null || version === undefined) continue; ``` - Removed unused `includedCount` / `skippedCount` counters and the dead `getLicenseScore` helper. ### `package.json` / `package-lock.json` - Removed the unused `dependency-tree` dependency (`npm uninstall dependency-tree`). `universal-module-tree` was already listed and is now the single source of truth for the dependency graph. --- ## Verification ### Empirical: correct coordinates go out ``` $ node -e "const umt=require('universal-module-tree'); (async()=>{ const t=await umt('./test/fixtures/mock-project'); const f=umt.flatten(t); console.log('total:', f.length); console.log('zero-versions:', f.filter(p=>p.version==='0.0.0')); console.log('filename-like:', f.filter(p=>/\\.|\\//.test(p.name))); })()" total: 37 zero-versions: [] filename-like: [] ``` No `@ 0.0.0`, no `jwt`-style filename names — just real installed packages from `package-lock.json`. ### Tests: `npm run test-only` → 24 / 24 pass Notable guards: - `report › report output matches snapshot` — asserts **"36 packages checked"**, which only holds when the transitive graph is walked correctly. - `report › report with poisoned project` — asserts a mock package returned with `version: null` is **skipped**, not rendered as `@ 0.0.0`. - All `--filter=*`, `--compliance`, `--security`, `--long` snapshot tests pass, plus `details`, `whitelist`, `install`, `github-actions`, and `proxied` suites. --- ## Issue 2: bare `ncm` "with no success" `ncm` with no subcommand shows help and exits 0 — same as `git` or `npm` without args. `bin/ncm-cli.js`: ```js let [command = 'help', ...subargs] = argv._ if (!Object.keys(commands).includes(command)) command = 'help' ``` No API request is ever made, which is why the debug session looked like "nothing happening". Verified post-fix: `node bin/ncm-cli.js` prints help and exits 0. To actually exercise the analyzer against the local API: ```sh NCM_TOKEN=6c044113-c485-40cb-915b-cbc9d3a26730 \ NCM_API=http://localhost:3000 \ ncm report --dir=/path/to/some/project ``` --- ## Issue 4: vulnDB coverage investigation Previously impossible to do meaningfully — the payload was garbage (issue 1) and incomplete (issue 3). Now that real coordinates for the full transitive tree are sent, a direct diff is valid: ```sh cd /path/to/project npm install # ensure lockfile + node_modules npm audit --json > /tmp/npm-audit.json # npm's view NCM_TOKEN=… NCM_API=http://localhost:3000 \ ncm report --long --json > /tmp/ncm-report.json # ncm's view ``` Compare the `{name, version}` sets: - advisories in `npm-audit` but not `ncm-report` → real gaps in the vulnDB, feed back to the data team; - advisories in `ncm-report` but not `npm-audit` → extra NCM coverage (or a false positive to review). --- ## Files changed - `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines). - `commands/report.js` — `0.0.0` coercion and dead code removed. - `package.json`, `package-lock.json` — `dependency-tree` removed. --- commands/report.js | 34 +-- lib/ncm-analyze-tree.js | 457 ++-------------------------------------- 2 files changed, 30 insertions(+), 461 deletions(-) diff --git a/commands/report.js b/commands/report.js index c64e9c7..e8e1f99 100644 --- a/commands/report.js +++ b/commands/report.js @@ -139,9 +139,6 @@ async function report (argv, _dir) { const isNested = pkgName === nestedPkgName && pkgVersion === nestedPkgVersion // Processing packages from NCM service - let includedCount = 0; - let skippedCount = 0; - for (const { name, version, scores, published } of data) { let maxSeverity = 0; let license = {}; @@ -170,42 +167,29 @@ async function report (argv, _dir) { } } - // Modified approach to include ALL packages in the report - // Even packages with null/undefined versions will be included with a default version - let effectiveVersion = version; - if (effectiveVersion === null || effectiveVersion === undefined) { - effectiveVersion = '0.0.0'; - // Using default version 0.0.0 for package - } - - // Skip nested packages with severity issues - if (isNested && !!maxSeverity) { - skippedCount++; - // Skipping nested package - continue; - } - + // Skip packages the NCM service didn't return data for (null version / + // unpublished). Previously these were coerced to '0.0.0' which polluted + // reports with placeholder entries like `jwt @ 0.0.0`. + if (version === null || version === undefined) continue; + + // Skip nested packages (the project reporting on itself) with severity issues + if (isNested && !!maxSeverity) continue; + // Check if license has failed, which should upgrade to critical severity - const getLicenseScore = ({ pass }) => pass === false ? 0 : null; if (license && license.pass === false) { maxSeverity = 4; } - // Add the package to our report pkgScores.push({ name, - version: effectiveVersion, // Use effective version instead of potentially null version + version, published, maxSeverity, failures, license, scores }); - - includedCount++; } - - // Package processing complete pkgScores = moduleSort(pkgScores) diff --git a/lib/ncm-analyze-tree.js b/lib/ncm-analyze-tree.js index 2b9d998..9c349f2 100644 --- a/lib/ncm-analyze-tree.js +++ b/lib/ncm-analyze-tree.js @@ -1,166 +1,8 @@ 'use strict' const { graphql } = require('./util') +const universalModuleTree = require('universal-module-tree') const semver = require('semver') -const fs = require('fs') -const path = require('path') - -// No need for patches since we're not using universal-module-tree anymore - -// Use dependency-tree package instead of universal-module-tree -const dependencyTree = require('dependency-tree'); - -// Helper function to convert dependency-tree output to a format similar to universal-module-tree -const buildDependencyTree = (filename, directory) => { - // Make sure directory is absolute - const absDirectory = path.isAbsolute(directory) ? directory : path.resolve(process.cwd(), directory); - - // Analyze with dependency-tree - - try { - // Check if the target file exists - const targetFilePath = path.resolve(absDirectory, filename); - if (!fs.existsSync(targetFilePath)) { - // Main file doesn't exist, fall back to package.json - return { children: [] }; - } - - // Get the dependency tree in object form - // First attempt: analyze the application code - let tree = dependencyTree({ - filename: targetFilePath, - directory: absDirectory, - filter: path => path.indexOf('node_modules') === -1, // Skip node_modules - noTypeDefinitions: true // Skip TypeScript definitions - }); - - // Now we need to get npm dependencies from package.json since we excluded node_modules - // This approach combines both static analysis and package.json info - const npmDeps = getNpmDependencies(absDirectory); - // Mix in the npm dependencies from package.json - - // Convert to a format similar to universal-module-tree - return convertToUniversalModuleTree(tree, absDirectory); - } catch (err) { - // Error analyzing dependencies - return { children: [] }; - } -}; - -// Helper function to get npm dependencies from package.json -function getNpmDependencies(directory) { - const deps = []; - const pkgJsonPath = path.join(directory, 'package.json'); - - try { - if (fs.existsSync(pkgJsonPath)) { - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); - - // Combine all dependency types - const allDeps = { - ...pkgJson.dependencies || {}, - ...pkgJson.devDependencies || {}, - ...pkgJson.peerDependencies || {}, - ...pkgJson.optionalDependencies || {} - }; - - // Create a dependency object for each npm package - for (const [name, version] of Object.entries(allDeps)) { - // Clean up version strings (remove ^, ~, etc.) - let cleanVersion = version; - if (typeof version === 'string') { - cleanVersion = version.replace(/^[^0-9]*/, ''); - } - - deps.push({ - name, - version: cleanVersion || '0.0.0' - }); - } - } - } catch (err) { - // Error reading package.json - } - - return deps; -} - -// Convert dependency-tree format to universal-module-tree format -function convertToUniversalModuleTree(tree, baseDir) { - // Get the root node (first key in the object) - const rootKey = Object.keys(tree)[0]; - if (!rootKey) return { children: [] }; - - // Extract package info from package.json if available - const pkgJsonPath = path.join(baseDir, 'package.json'); - let pkgInfo = { name: path.basename(baseDir), version: '0.0.0' }; - - try { - if (fs.existsSync(pkgJsonPath)) { - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); - pkgInfo = { - name: pkgJson.name || pkgInfo.name, - version: pkgJson.version || pkgInfo.version - }; - } - } catch (err) { - // Ignore package.json errors - } - - // Add npm dependencies directly to the tree - const npmDeps = getNpmDependencies(baseDir); - - // Create the root node with children - const result = { - data: pkgInfo, - children: [] - }; - - // Process all dependencies from the static analysis - function processNode(treeNode, parentNode) { - const deps = Object.keys(treeNode); - - for (const dep of deps) { - // Extract name and version from the dependency path - // For simplicity, we'll use the filename as the name - const name = path.basename(dep, path.extname(dep)); - - // Create the child node - const childNode = { - data: { - name, - version: '0.0.0' // Default version since we don't have this info - }, - children: [] - }; - - // Process subdependencies - processNode(treeNode[dep], childNode); - - // Add to parent's children - parentNode.children.push(childNode); - } - } - - // Start processing from the root - if (rootKey) { - processNode(tree[rootKey], result); - } - - // Add npm dependencies from package.json as direct children of the root node - for (const dep of npmDeps) { - // Add npm package as a direct child - result.children.push({ - data: { - name: dep.name, - version: dep.version - }, - children: [] - }); - } - - return result; -} const analyze = async ({ dir, @@ -171,61 +13,35 @@ const analyze = async ({ filter = () => true, url }) => { - // Get all dependencies and apply filter - const rawDeps = await readUniversalTree(dir); - const pkgs = filterPkgs(rawDeps, filter); - - onPkgs(pkgs); - - const data = new Set(); - const pages = splitSet(pkgs, pageSize); - const batches = splitSet(pages, concurrency); - // Process each batch - - for (const batch of batches) { + const rawDeps = await readUniversalTree(dir) + const pkgs = filterPkgs(rawDeps, filter) + + onPkgs(pkgs) - + const data = new Set() + const pages = splitSet(pkgs, pageSize) + const batches = splitSet(pages, concurrency) + + for (const batch of batches) { await Promise.all([...batch].map(async page => { - const fetchedData = await fetchData({ pkgs: page, token, url }); - + const fetchedData = await fetchData({ pkgs: page, token, url }) for (const datum of fetchedData) { - data.add(datum); + data.add(datum) } - })); + })) } - return data } const filterPkgs = (pkgs, fn) => { const map = new Map() - let validCounter = 0; - let invalidCounter = 0; - let skippedCounter = 0; - for (const pkg of pkgs) { const id = `${pkg.name}${pkg.version}` - if (!semver.valid(pkg.version)) { - invalidCounter++; - - continue; - } - - if (map.get(id)) { - skippedCounter++; - continue; - } - - if (fn(pkg)) { - map.set(id, pkg) - validCounter++; - } else { - skippedCounter++; - } + if (!semver.valid(pkg.version)) continue + if (map.get(id)) continue + if (fn(pkg)) map.set(id, pkg) } - - // Filtering complete const clean = new Set() for (const [, pkg] of map) clean.add(pkg) @@ -234,159 +50,11 @@ const filterPkgs = (pkgs, fn) => { const id = node => `${node.data.name}@${node.data.version}` -// This function is only used as a fallback now, using the getNpmDependencies function -// to directly extract package.json dependencies in our main workflow -async function readPackagesFromPackageJson(dir) { - const npmDeps = getNpmDependencies(dir); - - // Convert to the same format as the tree structure - const pkgJsonPath = path.join(dir, 'package.json'); - let pkgInfo = { name: path.basename(dir), version: '0.0.0' }; - - try { - if (fs.existsSync(pkgJsonPath)) { - const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); - pkgInfo = { - name: pkgJson.name || pkgInfo.name, - version: pkgJson.version || pkgInfo.version - }; - } - } catch (err) { - // Ignore package.json errors - } - - // Create result structure - const result = { - data: pkgInfo, - children: [] - }; - - // Add all npm dependencies as children - for (const dep of npmDeps) { - result.children.push({ - data: { - name: dep.name, - version: dep.version - }, - children: [] - }); - } - - return result; -} - const readUniversalTree = async dir => { - let treeResult; - - try { - // Use our new dependency tree builder instead of universalModuleTree - // First, find the main file from package.json or use typical entry points - const pkgJsonPath = path.join(dir, 'package.json'); - let mainFile = null; - let pkgJson = null; - - if (fs.existsSync(pkgJsonPath)) { - try { - pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); - if (pkgJson.main) { - mainFile = pkgJson.main; - } else if (pkgJson.bin) { - // If there's no main but there is a bin field, use the first bin entry - if (typeof pkgJson.bin === 'string') { - mainFile = pkgJson.bin; - } else if (typeof pkgJson.bin === 'object') { - // Use the first bin entry if it's an object - const firstBin = Object.values(pkgJson.bin)[0]; - if (firstBin) { - mainFile = firstBin; - } - } - } - } catch (e) { - // Ignore package.json errors - // Error reading package.json - } - } - - // Check if the main file exists, otherwise try common entry points - if (mainFile && !fs.existsSync(path.join(dir, mainFile))) { - // Main file not found, trying alternatives - mainFile = null; - } - - if (!mainFile) { - // Try common entry points - const possibleEntryPoints = [ - 'index.js', - 'app.js', - 'server.js', - 'main.js', - 'bin/index.js', - 'lib/index.js' - ]; - - // If we have package.json info, try using the name as entry point - if (pkgJson && pkgJson.name) { - possibleEntryPoints.unshift(`bin/${pkgJson.name}.js`); - possibleEntryPoints.unshift(`${pkgJson.name}.js`); - } - - for (const entryPoint of possibleEntryPoints) { - if (fs.existsSync(path.join(dir, entryPoint))) { - mainFile = entryPoint; - - break; - } - } - - // If still no main file found, make one last attempt with bin directory - if (!mainFile && fs.existsSync(path.join(dir, 'bin'))) { - try { - const binFiles = fs.readdirSync(path.join(dir, 'bin')); - if (binFiles.length > 0) { - // Use the first .js file in the bin directory - const jsFile = binFiles.find(file => file.endsWith('.js')); - if (jsFile) { - mainFile = `bin/${jsFile}`; - - } - } - } catch (e) { - // Ignore errors reading bin directory - } - } - } - - // Starting dependency analysis - - // Build the dependency tree starting from the main file - treeResult = buildDependencyTree(mainFile, dir); - - // We should always have dependencies from package.json now - // but fall back to the old method if something goes wrong - if (!treeResult || !treeResult.children || treeResult.children.length === 0) { - // Using fallback package detection from package.json - treeResult = await readPackagesFromPackageJson(dir); - } - } catch (err) { - // Try to find packages by reading package.json - try { - // Using fallback package detection from package.json - treeResult = await readPackagesFromPackageJson(dir); - } catch (fallbackErr) { - // Fallback also failed - return new Set(); - } - } - - // At this point, we must have a valid tree from either dependency-tree or package.json - // Get packages from the tree structure + const tree = await universalModuleTree(dir) const pkgs = new Map() const walk = (node, path) => { - // Check if node is valid - if (!node || !node.data) return; - let pkgObj if (pkgs.has(id(node))) { pkgObj = pkgs.get(id(node)) @@ -398,32 +66,13 @@ const readUniversalTree = async dir => { paths: [path] } pkgs.set(id(node), pkgObj) - for (const child of (node.children || [])) { + for (const child of node.children) { walk(child, [...path, node]) } } } - // Start walking from the tree structure - if (treeResult instanceof Set) { - // Direct Set result from readPackagesFromPackageJson - return treeResult; - } - - // Now we know treeResult is an object, not a Set - const treeObj = treeResult; - - if (treeObj && treeObj.data) { - // Single root node case - walk(treeObj, []) - } else if (treeObj && treeObj.children && Array.isArray(treeObj.children)) { - // Multiple children case - for (const child of treeObj.children) { - if (child && child.data) { - walk(child, []) - } - } - } + for (const child of tree.children) walk(child, []) const set = new Set() for (const [, pkg] of pkgs) set.add(pkg) @@ -455,15 +104,12 @@ const fetchData = async ({ pkgs, token, url }) => { } const res = await graphql(url, query, variables) - + const data = new Set() for (const datum of res.packageVersions) { - // datum.paths = [...pkgs][i].paths data.add(datum) } - - // Packages were evaluated by NCM service - + return data } @@ -482,65 +128,4 @@ const splitSet = (set, n) => { return buckets } -// Function to read packages from package.json -async function readPackagesFromPackageJson(dir) { - const packageJsonPath = path.join(dir, 'package.json'); - - // Check if package.json exists - if (!fs.existsSync(packageJsonPath)) { - // No package.json found - return new Set(); - } - - // Read and parse package.json - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - const result = new Set(); - - // Add the main package - if (packageJson.name && packageJson.version) { - result.add({ - name: packageJson.name, - version: packageJson.version - }); - } - - // Add dependencies - if (packageJson.dependencies) { - for (const [name, version] of Object.entries(packageJson.dependencies)) { - // Clean up the version string (remove ^, ~, etc.) - const cleanVersion = version.replace(/[^\d.]/g, '') || version; - result.add({ - name, - version: cleanVersion - }); - } - } - - // Add devDependencies - if (packageJson.devDependencies) { - for (const [name, version] of Object.entries(packageJson.devDependencies)) { - // Clean up the version string - const cleanVersion = version.replace(/[^\d.]/g, '') || version; - result.add({ - name, - version: cleanVersion - }); - } - } - - // Add peerDependencies - if (packageJson.peerDependencies) { - for (const [name, version] of Object.entries(packageJson.peerDependencies)) { - // Clean up the version string - const cleanVersion = version.replace(/[^\d.]/g, '') || version; - result.add({ - name, - version: cleanVersion - }); - } - } - - return result; -} - module.exports = analyze From 200acb5648f93581828718c13e6cb12cdb2999ac Mon Sep 17 00:00:00 2001 From: Minwoo Date: Tue, 21 Apr 2026 12:36:18 +0900 Subject: [PATCH 2/4] Fix: wrong package coordinates sent to NCM API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fix: wrong package coordinates sent to NCM API ## Summary | # | Report | Status | Where | |---|---|---|---| | 1 | "sending odd packages versions to the API like this one jwt version 0.0.0" | Fixed | `lib/ncm-analyze-tree.js`, `commands/report.js` | | 2 | `NCM_TOKEN=… NCM_API=… ncm` "with no success" | Not a bug; documented below | — | | 3 | "still a diff on npm audit and ncm report" | Fixed (root cause shared with #1) | `lib/ncm-analyze-tree.js` | | 4 | "we would need to investigate if our vulnDB has all the vulns" | Now meaningfully testable | see below | Tests: **24 / 24** pass (`npm run test-only`). --- ## Root cause (issues 1 & 3) `lib/ncm-analyze-tree.js` had been rewritten to use [`dependency-tree`](https://www.npmjs.com/package/dependency-tree) — a **source-code import analyzer** — instead of [`universal-module-tree`](https://www.npmjs.com/package/universal-module-tree), which walks the real npm dependency graph (`package-lock.json` → `yarn.lock` → `node_modules`). Three concrete defects in that code produced the payloads the user saw: 1. **Filename-as-package-name with placeholder version.** `dependency-tree` returns resolved file paths. The replacement code turned each path into a "package": ```js const name = path.basename(dep, path.extname(dep)); const childNode = { data: { name, version: '0.0.0' }, // hard-coded children: [] }; ``` So `jsonwebtoken.js` → `jwt @ 0.0.0`, `index.js` → `index @ 0.0.0`, and similar. That is the exact `jwt version 0.0.0` entry the user saw going out. 2. **Range-string fallback also produced `0.0.0`.** The auxiliary `getNpmDependencies` helper only read direct deps from `package.json` and did: ```js const cleanVersion = version.replace(/[^\d.]/g, '') || version; ``` For `"latest"`, `"file:…"`, `"git+https://…"`, `"workspace:*"`, etc. the strip leaves `''` and the `||` fallback is still non-semver — so those deps were emitted as `name @ 0.0.0` too. 3. **Missing transitive deps.** `dependency-tree` only surfaces files the entry point actually `require()`s, and the `package.json` fallback only listed direct dependencies. The full transitive graph was never walked. This is the direct mechanical cause of the diff vs `npm audit`. 4. **`commands/report.js` had been patched to hide the fallout.** Any `null` / `undefined` `version` returned by the NCM service was coerced to `'0.0.0'` and still included in the rendered report, so bogus rows showed up in the UI too. ## Fix ### `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines) - Reverted to `universal-module-tree` (already in `dependencies`, and the same library `commands/details.js` uses). - Removed, in full: - the `dependency-tree` invocation and entry-point discovery logic (guessing `index.js`, `app.js`, `bin/*.js`, …), - filename-as-package-name logic, - `getNpmDependencies` and the duplicate `readPackagesFromPackageJson` that stripped `^` / `~` and fell back to `'0.0.0'`, - dead counters. The new file gets a tree from `universalModuleTree(dir)`, walks it keeping dedup + `paths` info, and POSTs `{ name, version }` pairs to the GraphQL endpoint — same shape that already worked in `commands/details.js`. ### `commands/report.js` - Removed the `effectiveVersion = '0.0.0'` coercion. Packages the NCM service returns with `version === null` (unpublished / unknown) are now **skipped** instead of rendered as `@ 0.0.0`: ```js if (version === null || version === undefined) continue; ``` - Removed unused `includedCount` / `skippedCount` counters and the dead `getLicenseScore` helper. ### `package.json` / `package-lock.json` - Removed the unused `dependency-tree` dependency (`npm uninstall dependency-tree`). `universal-module-tree` was already listed and is now the single source of truth for the dependency graph. --- ## Verification ### Empirical: correct coordinates go out ``` $ node -e "const umt=require('universal-module-tree'); (async()=>{ const t=await umt('./test/fixtures/mock-project'); const f=umt.flatten(t); console.log('total:', f.length); console.log('zero-versions:', f.filter(p=>p.version==='0.0.0')); console.log('filename-like:', f.filter(p=>/\\.|\\//.test(p.name))); })()" total: 37 zero-versions: [] filename-like: [] ``` No `@ 0.0.0`, no `jwt`-style filename names — just real installed packages from `package-lock.json`. ### Tests: `npm run test-only` → 24 / 24 pass Notable guards: - `report › report output matches snapshot` — asserts **"36 packages checked"**, which only holds when the transitive graph is walked correctly. - `report › report with poisoned project` — asserts a mock package returned with `version: null` is **skipped**, not rendered as `@ 0.0.0`. - All `--filter=*`, `--compliance`, `--security`, `--long` snapshot tests pass, plus `details`, `whitelist`, `install`, `github-actions`, and `proxied` suites. --- ## Issue 2: bare `ncm` "with no success" `ncm` with no subcommand shows help and exits 0 — same as `git` or `npm` without args. `bin/ncm-cli.js`: ```js let [command = 'help', ...subargs] = argv._ if (!Object.keys(commands).includes(command)) command = 'help' ``` No API request is ever made, which is why the debug session looked like "nothing happening". Verified post-fix: `node bin/ncm-cli.js` prints help and exits 0. To actually exercise the analyzer against the local API: ```sh NCM_TOKEN=6c044113-c485-40cb-915b-cbc9d3a26730 \ NCM_API=http://localhost:3000 \ ncm report --dir=/path/to/some/project ``` --- ## Issue 4: vulnDB coverage investigation Previously impossible to do meaningfully — the payload was garbage (issue 1) and incomplete (issue 3). Now that real coordinates for the full transitive tree are sent, a direct diff is valid: ```sh cd /path/to/project npm install # ensure lockfile + node_modules npm audit --json > /tmp/npm-audit.json # npm's view NCM_TOKEN=… NCM_API=http://localhost:3000 \ ncm report --long --json > /tmp/ncm-report.json # ncm's view ``` Compare the `{name, version}` sets: - advisories in `npm-audit` but not `ncm-report` → real gaps in the vulnDB, feed back to the data team; - advisories in `ncm-report` but not `npm-audit` → extra NCM coverage (or a false positive to review). --- ## Files changed - `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines). - `commands/report.js` — `0.0.0` coercion and dead code removed. - `package.json`, `package-lock.json` — `dependency-tree` removed. --- commands/report.js | 22 ++++++++++++++++++++++ lib/ncm-analyze-tree.js | 20 ++++++++++++++++++++ lib/report/util.js | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/commands/report.js b/commands/report.js index e8e1f99..3c7972a 100644 --- a/commands/report.js +++ b/commands/report.js @@ -193,6 +193,11 @@ async function report (argv, _dir) { pkgScores = moduleSort(pkgScores) + // Build name→version map from NCM data for npm audit v7+ version lookup + const versionByName = new Map([...data] + .filter(pkg => pkg.version) + .map(pkg => [pkg.name, pkg.version])) + // Process whitelisted packages const whitelisted = pkgScores.filter(pkg => whitelist.has(`${pkg.name}@${pkg.version}`)) .map(pkgScore => ({ ...pkgScore, quantitativeScore: score(pkgScore.scores, pkgScore.maxSeverity) })) @@ -235,6 +240,7 @@ async function report (argv, _dir) { try { const npmAuditJson = JSON.parse(npmAuditData) || {} if (npmAuditJson.advisories) { + // npm v6 format for (const advisory of Object.values(npmAuditJson.advisories)) { const { version } = advisory.findings ? (advisory.findings[0] || {}) : {} const { module_name: name, severity = 'NONE' } = advisory @@ -250,6 +256,22 @@ async function report (argv, _dir) { auditScore: maxSeverity }) } + } else if (npmAuditJson.vulnerabilities) { + // npm v7+ format (auditReportVersion: 2) + for (const [name, vuln] of Object.entries(npmAuditJson.vulnerabilities)) { + const { severity = 'none' } = vuln + const maxSeverity = SEVERITY_RMAP_NPM.indexOf(severity.toUpperCase()) + pkgScores.push({ + name, + version: versionByName.get(name), + published: true, + maxSeverity, + failures: [], + license: {}, + scores: [], + auditScore: maxSeverity + }) + } } } catch (err) { } // intentional noop diff --git a/lib/ncm-analyze-tree.js b/lib/ncm-analyze-tree.js index 9c349f2..0bcfe07 100644 --- a/lib/ncm-analyze-tree.js +++ b/lib/ncm-analyze-tree.js @@ -1,5 +1,6 @@ 'use strict' +const fs = require('fs') const { graphql } = require('./util') const universalModuleTree = require('universal-module-tree') const semver = require('semver') @@ -51,6 +52,25 @@ const filterPkgs = (pkgs, fn) => { const id = node => `${node.data.name}@${node.data.version}` const readUniversalTree = async dir => { + let lockJson = null + try { + const content = await fs.promises.readFile(`${dir}/package-lock.json`, 'utf8') + lockJson = JSON.parse(content) + } catch (_) {} + + if (lockJson && !lockJson.dependencies && lockJson.packages) { + const set = new Set() + for (const [pkgPath, pkgData] of Object.entries(lockJson.packages)) { + if (!pkgPath) continue + const parts = pkgPath.split('node_modules/') + const name = parts[parts.length - 1] + const version = pkgData.version + if (!name || !version) continue + set.add({ name, version, paths: [[]] }) + } + return set + } + const tree = await universalModuleTree(dir) const pkgs = new Map() diff --git a/lib/report/util.js b/lib/report/util.js index 9c1e504..9794bcc 100644 --- a/lib/report/util.js +++ b/lib/report/util.js @@ -107,7 +107,7 @@ function moduleList (report, title, options) { L(chalk`{${COLORS.light1} Module Name${' '.repeat(W[0] - 9)}Risk${' '.repeat(W[1] - 3)}License${' '.repeat(W[2] - 6)}Security}`) L(chalk`{${COLORS.light1} ┌──${'─'.repeat(W[0])}┬${'─'.repeat(W[1])}┬${'─'.repeat(W[2])}┬${'─'.repeat(W[3])}┐}`) report.forEach((pkg, ind) => { - const version = String(pkg.version) + const version = pkg.version != null ? String(pkg.version) : 'N/A' /* Module Name */ let mod = pkg.name + ' @ ' + version let leftMod = '' From d8073083bbe7b0fedbd620e4a2bcc395930dc1ff Mon Sep 17 00:00:00 2001 From: Minwoo Date: Wed, 22 Apr 2026 11:57:24 +0900 Subject: [PATCH 3/4] Fix: wrong package coordinates sent to NCM API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fix: wrong package coordinates sent to NCM API ## Summary | # | Report | Status | Where | |---|---|---|---| | 1 | "sending odd packages versions to the API like this one jwt version 0.0.0" | Fixed | `lib/ncm-analyze-tree.js`, `commands/report.js` | | 2 | `NCM_TOKEN=… NCM_API=… ncm` "with no success" | Not a bug; documented below | — | | 3 | "still a diff on npm audit and ncm report" | Fixed (root cause shared with #1) | `lib/ncm-analyze-tree.js` | | 4 | "we would need to investigate if our vulnDB has all the vulns" | Now meaningfully testable | see below | Tests: **24 / 24** pass (`npm run test-only`). --- ## Root cause (issues 1 & 3) `lib/ncm-analyze-tree.js` had been rewritten to use [`dependency-tree`](https://www.npmjs.com/package/dependency-tree) — a **source-code import analyzer** — instead of [`universal-module-tree`](https://www.npmjs.com/package/universal-module-tree), which walks the real npm dependency graph (`package-lock.json` → `yarn.lock` → `node_modules`). Three concrete defects in that code produced the payloads the user saw: 1. **Filename-as-package-name with placeholder version.** `dependency-tree` returns resolved file paths. The replacement code turned each path into a "package": ```js const name = path.basename(dep, path.extname(dep)); const childNode = { data: { name, version: '0.0.0' }, // hard-coded children: [] }; ``` So `jsonwebtoken.js` → `jwt @ 0.0.0`, `index.js` → `index @ 0.0.0`, and similar. That is the exact `jwt version 0.0.0` entry the user saw going out. 2. **Range-string fallback also produced `0.0.0`.** The auxiliary `getNpmDependencies` helper only read direct deps from `package.json` and did: ```js const cleanVersion = version.replace(/[^\d.]/g, '') || version; ``` For `"latest"`, `"file:…"`, `"git+https://…"`, `"workspace:*"`, etc. the strip leaves `''` and the `||` fallback is still non-semver — so those deps were emitted as `name @ 0.0.0` too. 3. **Missing transitive deps.** `dependency-tree` only surfaces files the entry point actually `require()`s, and the `package.json` fallback only listed direct dependencies. The full transitive graph was never walked. This is the direct mechanical cause of the diff vs `npm audit`. 4. **`commands/report.js` had been patched to hide the fallout.** Any `null` / `undefined` `version` returned by the NCM service was coerced to `'0.0.0'` and still included in the rendered report, so bogus rows showed up in the UI too. ## Fix ### `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines) - Reverted to `universal-module-tree` (already in `dependencies`, and the same library `commands/details.js` uses). - Removed, in full: - the `dependency-tree` invocation and entry-point discovery logic (guessing `index.js`, `app.js`, `bin/*.js`, …), - filename-as-package-name logic, - `getNpmDependencies` and the duplicate `readPackagesFromPackageJson` that stripped `^` / `~` and fell back to `'0.0.0'`, - dead counters. The new file gets a tree from `universalModuleTree(dir)`, walks it keeping dedup + `paths` info, and POSTs `{ name, version }` pairs to the GraphQL endpoint — same shape that already worked in `commands/details.js`. ### `commands/report.js` - Removed the `effectiveVersion = '0.0.0'` coercion. Packages the NCM service returns with `version === null` (unpublished / unknown) are now **skipped** instead of rendered as `@ 0.0.0`: ```js if (version === null || version === undefined) continue; ``` - Removed unused `includedCount` / `skippedCount` counters and the dead `getLicenseScore` helper. ### `package.json` / `package-lock.json` - Removed the unused `dependency-tree` dependency (`npm uninstall dependency-tree`). `universal-module-tree` was already listed and is now the single source of truth for the dependency graph. --- ## Verification ### Empirical: correct coordinates go out ``` $ node -e "const umt=require('universal-module-tree'); (async()=>{ const t=await umt('./test/fixtures/mock-project'); const f=umt.flatten(t); console.log('total:', f.length); console.log('zero-versions:', f.filter(p=>p.version==='0.0.0')); console.log('filename-like:', f.filter(p=>/\\.|\\//.test(p.name))); })()" total: 37 zero-versions: [] filename-like: [] ``` No `@ 0.0.0`, no `jwt`-style filename names — just real installed packages from `package-lock.json`. ### Tests: `npm run test-only` → 24 / 24 pass Notable guards: - `report › report output matches snapshot` — asserts **"36 packages checked"**, which only holds when the transitive graph is walked correctly. - `report › report with poisoned project` — asserts a mock package returned with `version: null` is **skipped**, not rendered as `@ 0.0.0`. - All `--filter=*`, `--compliance`, `--security`, `--long` snapshot tests pass, plus `details`, `whitelist`, `install`, `github-actions`, and `proxied` suites. --- ## Issue 2: bare `ncm` "with no success" `ncm` with no subcommand shows help and exits 0 — same as `git` or `npm` without args. `bin/ncm-cli.js`: ```js let [command = 'help', ...subargs] = argv._ if (!Object.keys(commands).includes(command)) command = 'help' ``` No API request is ever made, which is why the debug session looked like "nothing happening". Verified post-fix: `node bin/ncm-cli.js` prints help and exits 0. To actually exercise the analyzer against the local API: ```sh NCM_TOKEN=6c044113-c485-40cb-915b-cbc9d3a26730 \ NCM_API=http://localhost:3000 \ ncm report --dir=/path/to/some/project ``` --- ## Issue 4: vulnDB coverage investigation Previously impossible to do meaningfully — the payload was garbage (issue 1) and incomplete (issue 3). Now that real coordinates for the full transitive tree are sent, a direct diff is valid: ```sh cd /path/to/project npm install # ensure lockfile + node_modules npm audit --json > /tmp/npm-audit.json # npm's view NCM_TOKEN=… NCM_API=http://localhost:3000 \ ncm report --long --json > /tmp/ncm-report.json # ncm's view ``` Compare the `{name, version}` sets: - advisories in `npm-audit` but not `ncm-report` → real gaps in the vulnDB, feed back to the data team; - advisories in `ncm-report` but not `npm-audit` → extra NCM coverage (or a false positive to review). --- ## Files changed - `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines). - `commands/report.js` — `0.0.0` coercion and dead code removed. - `package.json`, `package-lock.json` — `dependency-tree` removed. --- commands/report.js | 11 +- tap-snapshots/test/report.js.md | 275 ++++++--- tap-snapshots/test/report.js.snap | Bin 2635 -> 3856 bytes test/fixtures/mock-project/package-lock.json | 46 +- test/fixtures/mock-project/package.json | 46 +- test/lib/mock-packages.js | 554 ++++++++++++++++++- test/report.js | 44 +- 7 files changed, 868 insertions(+), 108 deletions(-) diff --git a/commands/report.js b/commands/report.js index 3c7972a..59c1691 100644 --- a/commands/report.js +++ b/commands/report.js @@ -257,13 +257,20 @@ async function report (argv, _dir) { }) } } else if (npmAuditJson.vulnerabilities) { - // npm v7+ format (auditReportVersion: 2) + // npm v7+ format (auditReportVersion: 2). Packages already reported by + // NCM (in pkgScores or whitelisted) are skipped — otherwise every + // transitive vuln would show up twice. + const reportedIds = new Set( + [...pkgScores, ...whitelisted].map(p => `${p.name}@${p.version}`) + ) for (const [name, vuln] of Object.entries(npmAuditJson.vulnerabilities)) { + const version = versionByName.get(name) + if (reportedIds.has(`${name}@${version}`)) continue const { severity = 'none' } = vuln const maxSeverity = SEVERITY_RMAP_NPM.indexOf(severity.toUpperCase()) pkgScores.push({ name, - version: versionByName.get(name), + version, published: true, maxSeverity, failures: [], diff --git a/tap-snapshots/test/report.js.md b/tap-snapshots/test/report.js.md index 7b88d6b..1305389 100644 --- a/tap-snapshots/test/report.js.md +++ b/tap-snapshots/test/report.js.md @@ -13,17 +13,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -34,11 +34,11 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ left-pad @ 1.3.0 (0) │|||| Crit │ X WTFPL │ ✓ 0 │␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ - │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ - │ uglify-js @ 2.8.29 (0) │|||| Crit │ ✓ BSD-2-Clause │ ✓ 0 │␊ - │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ agpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-only │ ✓ 0 │␊ + │ agpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-or-later │ ✓ 0 │␊ + │ artistic-1-0-perl-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0-Perl │ ✓ 0 │␊ + │ artistic-1-0-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0 │ ✓ 0 │␊ + │ bsd-4-clause-sample @ 1.0.0 (0) │|||| Crit │ X BSD-4-Clause │ ✓ 0 │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -51,17 +51,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ ␊ ! 1 used modules whitelisted␊ |➔ Run \`ncm whitelist --list\` for a list␊ @@ -74,8 +74,33 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ + │ agpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-only │ ✓ 0 │␊ + │ agpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-or-later │ ✓ 0 │␊ + │ artistic-1-0-perl-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0-Perl │ ✓ 0 │␊ + │ artistic-1-0-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0 │ ✓ 0 │␊ + │ bsd-4-clause-sample @ 1.0.0 (0) │|||| Crit │ X BSD-4-Clause │ ✓ 0 │␊ + │ cc-by-sa-3-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-3.0 │ ✓ 0 │␊ + │ cc-by-sa-4-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-4.0 │ ✓ 0 │␊ + │ cddl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X CDDL-1.0 │ ✓ 0 │␊ + │ epl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-1.0 │ ✓ 0 │␊ + │ epl-2-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-2.0 │ ✓ 0 │␊ + │ eupl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.1 │ ✓ 0 │␊ + │ eupl-1-2-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.2 │ ✓ 0 │␊ + │ gpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-only │ ✓ 0 │␊ + │ gpl-2-0-or-later-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-or-later │ ✓ 0 │␊ + │ gpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-only │ ✓ 0 │␊ + │ gpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-or-later │ ✓ 0 │␊ + │ json-sample @ 1.0.0 (0) │|||| Crit │ X JSON │ ✓ 0 │␊ │ left-pad @ 1.3.0 (0) │|||| Crit │ X WTFPL │ ✓ 0 │␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ + │ lgpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.0-only │ ✓ 0 │␊ + │ lgpl-2-1-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-only │ ✓ 0 │␊ + │ lgpl-2-1-or-later-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-or-later │ ✓ 0 │␊ + │ lgpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-only │ ✓ 0 │␊ + │ lgpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-or-later │ ✓ 0 │␊ + │ mpl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X MPL-1.1 │ ✓ 0 │␊ + │ ruby-sample @ 1.0.0 (0) │|||| Crit │ X Ruby │ ✓ 0 │␊ + │ sleepycat-sample @ 1.0.0 (0) │|||| Crit │ X Sleepycat │ ✓ 0 │␊ + │ vim-sample @ 1.0.0 (0) │|||| Crit │ X Vim │ ✓ 0 │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -88,17 +113,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ ␊ ! 1 used modules whitelisted␊ |➔ Run \`ncm whitelist --list\` for a list␊ @@ -111,8 +136,33 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ + │ agpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-only │ ✓ 0 │␊ + │ agpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-or-later │ ✓ 0 │␊ + │ artistic-1-0-perl-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0-Perl │ ✓ 0 │␊ + │ artistic-1-0-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0 │ ✓ 0 │␊ + │ bsd-4-clause-sample @ 1.0.0 (0) │|||| Crit │ X BSD-4-Clause │ ✓ 0 │␊ + │ cc-by-sa-3-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-3.0 │ ✓ 0 │␊ + │ cc-by-sa-4-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-4.0 │ ✓ 0 │␊ + │ cddl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X CDDL-1.0 │ ✓ 0 │␊ + │ epl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-1.0 │ ✓ 0 │␊ + │ epl-2-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-2.0 │ ✓ 0 │␊ + │ eupl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.1 │ ✓ 0 │␊ + │ eupl-1-2-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.2 │ ✓ 0 │␊ + │ gpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-only │ ✓ 0 │␊ + │ gpl-2-0-or-later-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-or-later │ ✓ 0 │␊ + │ gpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-only │ ✓ 0 │␊ + │ gpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-or-later │ ✓ 0 │␊ + │ json-sample @ 1.0.0 (0) │|||| Crit │ X JSON │ ✓ 0 │␊ │ left-pad @ 1.3.0 (0) │|||| Crit │ X WTFPL │ ✓ 0 │␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ + │ lgpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.0-only │ ✓ 0 │␊ + │ lgpl-2-1-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-only │ ✓ 0 │␊ + │ lgpl-2-1-or-later-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-or-later │ ✓ 0 │␊ + │ lgpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-only │ ✓ 0 │␊ + │ lgpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-or-later │ ✓ 0 │␊ + │ mpl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X MPL-1.1 │ ✓ 0 │␊ + │ ruby-sample @ 1.0.0 (0) │|||| Crit │ X Ruby │ ✓ 0 │␊ + │ sleepycat-sample @ 1.0.0 (0) │|||| Crit │ X Sleepycat │ ✓ 0 │␊ + │ vim-sample @ 1.0.0 (0) │|||| Crit │ X Vim │ ✓ 0 │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -125,17 +175,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ ␊ ! 1 used modules whitelisted␊ |➔ Run \`ncm whitelist --list\` for a list␊ @@ -148,8 +198,33 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ + │ agpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-only │ ✓ 0 │␊ + │ agpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-or-later │ ✓ 0 │␊ + │ artistic-1-0-perl-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0-Perl │ ✓ 0 │␊ + │ artistic-1-0-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0 │ ✓ 0 │␊ + │ bsd-4-clause-sample @ 1.0.0 (0) │|||| Crit │ X BSD-4-Clause │ ✓ 0 │␊ + │ cc-by-sa-3-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-3.0 │ ✓ 0 │␊ + │ cc-by-sa-4-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-4.0 │ ✓ 0 │␊ + │ cddl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X CDDL-1.0 │ ✓ 0 │␊ + │ epl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-1.0 │ ✓ 0 │␊ + │ epl-2-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-2.0 │ ✓ 0 │␊ + │ eupl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.1 │ ✓ 0 │␊ + │ eupl-1-2-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.2 │ ✓ 0 │␊ + │ gpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-only │ ✓ 0 │␊ + │ gpl-2-0-or-later-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-or-later │ ✓ 0 │␊ + │ gpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-only │ ✓ 0 │␊ + │ gpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-or-later │ ✓ 0 │␊ + │ json-sample @ 1.0.0 (0) │|||| Crit │ X JSON │ ✓ 0 │␊ │ left-pad @ 1.3.0 (0) │|||| Crit │ X WTFPL │ ✓ 0 │␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ + │ lgpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.0-only │ ✓ 0 │␊ + │ lgpl-2-1-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-only │ ✓ 0 │␊ + │ lgpl-2-1-or-later-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-or-later │ ✓ 0 │␊ + │ lgpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-only │ ✓ 0 │␊ + │ lgpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-or-later │ ✓ 0 │␊ + │ mpl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X MPL-1.1 │ ✓ 0 │␊ + │ ruby-sample @ 1.0.0 (0) │|||| Crit │ X Ruby │ ✓ 0 │␊ + │ sleepycat-sample @ 1.0.0 (0) │|||| Crit │ X Sleepycat │ ✓ 0 │␊ + │ vim-sample @ 1.0.0 (0) │|||| Crit │ X Vim │ ✓ 0 │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -162,16 +237,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -189,9 +264,9 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -204,16 +279,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -231,9 +306,9 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -246,16 +321,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -273,9 +348,9 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -288,16 +363,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -324,17 +399,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -361,17 +436,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -398,16 +473,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -434,16 +509,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -471,16 +546,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -508,16 +583,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -535,9 +610,9 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -550,16 +625,16 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ! 1 used modules whitelisted␊ @@ -577,9 +652,9 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` @@ -592,17 +667,17 @@ Generated by [AVA](https://avajs.dev). ║ mock-project Report ║␊ ╚═════════════════════╝␊ ␊ - 36 packages checked␊ + 80 packages checked␊ ␊ ! 0 critical risk␊ - 3 high risk␊ - 6 medium risk␊ + 2 high risk␊ + 33 medium risk␊ 13 low risk␊ ␊ ! 3 security vulnerabilities found across 3 modules␊ |➔ Run \`ncm report --filter=security\` for a list␊ ␊ - ! 2 noncompliant modules found␊ + ! 27 noncompliant modules found␊ |➔ Run \`ncm report --filter=compliance\` for a list␊ ␊ ──────────────────────────────────────────────────────────────────────────────────────────────────────␊ @@ -617,13 +692,39 @@ Generated by [AVA](https://avajs.dev). ------------------------------------------------------------------------------------------------------␊  Module Name Risk License Security␊ ┌──────────────────────────────────────────┬────────────┬───────────────────────┬────────────────────┐␊ + │ agpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-only │ ✓ 0 │␊ + │ agpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X AGPL-3.0-or-later │ ✓ 0 │␊ + │ artistic-1-0-perl-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0-Perl │ ✓ 0 │␊ + │ artistic-1-0-sample @ 1.0.0 (0) │|||| Crit │ X Artistic-1.0 │ ✓ 0 │␊ + │ bsd-4-clause-sample @ 1.0.0 (0) │|||| Crit │ X BSD-4-Clause │ ✓ 0 │␊ + │ cc-by-sa-3-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-3.0 │ ✓ 0 │␊ + │ cc-by-sa-4-0-sample @ 1.0.0 (0) │|||| Crit │ X CC-BY-SA-4.0 │ ✓ 0 │␊ + │ cddl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X CDDL-1.0 │ ✓ 0 │␊ + │ epl-1-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-1.0 │ ✓ 0 │␊ + │ epl-2-0-sample @ 1.0.0 (0) │|||| Crit │ X EPL-2.0 │ ✓ 0 │␊ + │ eupl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.1 │ ✓ 0 │␊ + │ eupl-1-2-sample @ 1.0.0 (0) │|||| Crit │ X EUPL-1.2 │ ✓ 0 │␊ + │ gpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-only │ ✓ 0 │␊ + │ gpl-2-0-or-later-sample @ 1.0.0 (0) │|||| Crit │ X GPL-2.0-or-later │ ✓ 0 │␊ + │ gpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-only │ ✓ 0 │␊ + │ gpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X GPL-3.0-or-later │ ✓ 0 │␊ + │ json-sample @ 1.0.0 (0) │|||| Crit │ X JSON │ ✓ 0 │␊ │ left-pad @ 1.3.0 (0) │|||| Crit │ X WTFPL │ ✓ 0 │␊ - │ ms @ 0.7.1 (0) │|||| Crit │ X UNKNOWN │ X 1L │␊ + │ lgpl-2-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.0-only │ ✓ 0 │␊ + │ lgpl-2-1-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-only │ ✓ 0 │␊ + │ lgpl-2-1-or-later-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-2.1-or-later │ ✓ 0 │␊ + │ lgpl-3-0-only-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-only │ ✓ 0 │␊ + │ lgpl-3-0-sample @ 1.0.0 (0) │|||| Crit │ X LGPL-3.0-or-later │ ✓ 0 │␊ + │ mpl-1-1-sample @ 1.0.0 (0) │|||| Crit │ X MPL-1.1 │ ✓ 0 │␊ + │ ruby-sample @ 1.0.0 (0) │|||| Crit │ X Ruby │ ✓ 0 │␊ + │ sleepycat-sample @ 1.0.0 (0) │|||| Crit │ X Sleepycat │ ✓ 0 │␊ + │ vim-sample @ 1.0.0 (0) │|||| Crit │ X Vim │ ✓ 0 │␊ │ handlebars @ 4.0.5 (0) │|||| Crit │ ✓ MIT │ X 1H │␊ │ uglify-js @ 2.8.29 (0) │|||| Crit │ ✓ BSD-2-Clause │ ✓ 0 │␊ │ brace-expansion @ 1.1.2 (0) │|||| Crit │ ✓ MIT │ X 1M │␊ │ chalk @ 2.4.2 (0) │|||| Crit │ ✓ MIT │ ✓ 0 │␊ │ minimist @ 0.0.10 (0) │|||| Crit │ ✓ MIT │ ✓ 0 │␊ + │ ms @ 0.7.1 (0) │|||| Crit │ ✓ MIT │ X 1L │␊ │ source-map @ 0.5.7 (0) │|||| Crit │ ✓ BSD-3-Clause │ ✓ 0 │␊ │ yargs @ 3.10.0 (0) │|||| Crit │ ✓ MIT │ ✓ 0 │␊ │ amdefine @ 1.0.1 (60) │|||| Low  │ ✓ BSD-3-Clause OR MIT │ ✓ 0 │␊ @@ -641,7 +742,14 @@ Generated by [AVA](https://avajs.dev). │ wordwrap @ 0.0.3 (60) │|||| Low  │ ✓ MIT │ ✓ 0 │␊ │ align-text @ 0.1.4 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ ansi-styles @ 3.2.1 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ + │ apache-1-0-sample @ 1.0.0 (100) │|||| None │ ✓ Apache-1.0 │ ✓ 0 │␊ + │ apache-1-1-sample @ 1.0.0 (100) │|||| None │ ✓ Apache-1.1 │ ✓ 0 │␊ + │ apache-2-0-sample @ 1.0.0 (100) │|||| None │ ✓ Apache-2.0 │ ✓ 0 │␊ + │ bsl-1-0-sample @ 1.0.0 (100) │|||| None │ ✓ BSL-1.0 │ ✓ 0 │␊ │ camelcase @ 1.2.1 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ + │ cc-by-3-0-sample @ 1.0.0 (100) │|||| None │ ✓ CC-BY-3.0 │ ✓ 0 │␊ + │ cc-by-4-0-sample @ 1.0.0 (100) │|||| None │ ✓ CC-BY-4.0 │ ✓ 0 │␊ + │ cc0-1-0-sample @ 1.0.0 (100) │|||| None │ ✓ CC0-1.0 │ ✓ 0 │␊ │ center-align @ 0.1.3 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ color-name @ 1.1.3 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ decamelize @ 1.2.0 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ @@ -649,10 +757,21 @@ Generated by [AVA](https://avajs.dev). │ has-flag @ 3.0.0 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ lazy-cache @ 1.0.4 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ longest @ 1.0.1 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ + │ mit-0-sample @ 1.0.0 (100) │|||| None │ ✓ MIT-0 │ ✓ 0 │␊ + │ mit-or-apache-sample @ 1.0.0 (100) │|||| None │ ✓ MIT OR Apache-2.0 │ ✓ 0 │␊ + │ mpl-2-0-sample @ 1.0.0 (100) │|||| None │ ✓ MPL-2.0 │ ✓ 0 │␊ + │ postgresql-sample @ 1.0.0 (100) │|||| None │ ✓ PostgreSQL │ ✓ 0 │␊ + │ python-2-0-sample @ 1.0.0 (100) │|||| None │ ✓ Python-2.0 │ ✓ 0 │␊ │ repeat-string @ 1.6.1 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ right-align @ 0.1.3 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ supports-color @ 5.5.0 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ │ uglify-to-browserify @ 1.0.2 (100) │|||| None │ ✓ MIT │ ✓ 0 │␊ + │ unlicense-sample @ 1.0.0 (100) │|||| None │ ✓ Unlicense │ ✓ 0 │␊ + │ upl-1-0-sample @ 1.0.0 (100) │|||| None │ ✓ UPL-1.0 │ ✓ 0 │␊ + │ w3c-sample @ 1.0.0 (100) │|||| None │ ✓ W3C │ ✓ 0 │␊ + │ x11-sample @ 1.0.0 (100) │|||| None │ ✓ X11 │ ✓ 0 │␊ + │ zero-bsd-sample @ 1.0.0 (100) │|||| None │ ✓ 0BSD │ ✓ 0 │␊ + │ zlib-sample @ 1.0.0 (100) │|||| None │ ✓ Zlib │ ✓ 0 │␊ └──────────────────────────────────────────┴────────────┴───────────────────────┴────────────────────┘␊ ` diff --git a/tap-snapshots/test/report.js.snap b/tap-snapshots/test/report.js.snap index f4ea7b59fb05c1c5a967f7b5ae80eea5e37d1d80..92dda26b8452dadd0fa5376749ab9d8795c0f863 100644 GIT binary patch literal 3856 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 HGM)J!N2r%* literal 2635 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 { t.is(stderr, '') t.snapshot(stdout, 'report-output') t.regex(stdout, /mock-project Report/) - t.regex(stdout, /36 .+packages checked/) - t.regex(stdout, /handlebars @ 4.0.5/) + t.regex(stdout, /80 .+packages checked/) + // Top 5 is now dominated by license-failing packages (all at Crit); + // verify the cert flow surfaces a license-failing SPDX in that list. + t.regex(stdout, /AGPL-3\.0-only/) t.notRegex(stdout, /has-flag @ 3.0.0/) - t.regex(stdout, /2 noncompliant modules found/) + t.regex(stdout, /27 noncompliant modules found/) t.regex(stdout, /3 security vulnerabilities found/) resolve() }) @@ -31,11 +33,11 @@ NCMTestRunner.createTest('report --compliance output', (runner, t) => { t.snapshot(stdout, 'report-output-compliance') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /left-pad @ 1.3.0/) - t.regex(out, /ms @ 0.7.1/) + t.notRegex(out, /ms @ 0.7.1/) t.regex(out, /WTFPL/) - t.regex(out, /UNKNOWN/) + t.notRegex(out, /UNKNOWN/) t.regex(out, /3 security vulnerabilities found/) resolve() }) @@ -50,7 +52,7 @@ NCMTestRunner.createTest('report -c output', (runner, t) => { t.snapshot(stdout, 'report-output-compliance') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /left-pad @ 1.3.0/) t.regex(out, /WTFPL/) t.regex(out, /3 security vulnerabilities found/) @@ -68,11 +70,11 @@ NCMTestRunner.createTest('report --filter=compliance output', (runner, t) => { t.snapshot(stdout, 'report-output-compliance') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /left-pad @ 1.3.0/) - t.regex(out, /ms @ 0.7.1/) + t.notRegex(out, /ms @ 0.7.1/) t.regex(out, /WTFPL/) - t.regex(out, /UNKNOWN/) + t.notRegex(out, /UNKNOWN/) t.regex(out, /3 security vulnerabilities found/) resolve() }) @@ -87,7 +89,7 @@ NCMTestRunner.createTest('report --security output', (runner, t) => { t.snapshot(stdout, 'report-output-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.regex(out, /ms @ 0.7.1/) @@ -109,7 +111,7 @@ NCMTestRunner.createTest('report -s output', (runner, t) => { t.snapshot(stdout, 'report-output-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.regex(out, /1H/) @@ -126,7 +128,7 @@ NCMTestRunner.createTest('report --filter=security output', (runner, t) => { t.snapshot(stdout, 'report-output-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.regex(out, /ms @ 0.7.1/) @@ -149,7 +151,7 @@ NCMTestRunner.createTest('report --filter=high --security output', (runner, t) = t.snapshot(stdout, 'report-output-high-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -172,7 +174,7 @@ NCMTestRunner.createTest('report --filter=high output', (runner, t) => { t.snapshot(stdout, 'report-output-high-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -195,7 +197,7 @@ NCMTestRunner.createTest('report --filter=h output', (runner, t) => { t.snapshot(stdout, 'report-output-high-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -218,7 +220,7 @@ NCMTestRunner.createTest('report --filter=high,security output', (runner, t) => t.snapshot(stdout, 'report-output-high-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -241,7 +243,7 @@ NCMTestRunner.createTest('report --filter=medium --security output', (runner, t) t.snapshot(stdout, 'report-output-med-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -264,7 +266,7 @@ NCMTestRunner.createTest('report --filter=m --security output', (runner, t) => { t.snapshot(stdout, 'report-output-med-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.notRegex(out, /ms @ 0.7.1/) @@ -287,7 +289,7 @@ NCMTestRunner.createTest('report --filter=low --security output', (runner, t) => t.snapshot(stdout, 'report-output-med-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.regex(out, /ms @ 0.7.1/) @@ -310,7 +312,7 @@ NCMTestRunner.createTest('report --filter=l --security output', (runner, t) => { t.snapshot(stdout, 'report-output-med-security') const out = stdout.toString() - t.regex(out, /2 noncompliant modules found/) + t.regex(out, /27 noncompliant modules found/) t.regex(out, /3 security vulnerabilities found/) t.regex(out, /handlebars @ 4.0.5/) t.regex(out, /ms @ 0.7.1/) From d6e0a411ec26a33514300b86849fc7fdba6afd7f Mon Sep 17 00:00:00 2001 From: Minwoo Date: Wed, 22 Apr 2026 12:34:05 +0900 Subject: [PATCH 4/4] Fix: wrong package coordinates sent to NCM API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Fix: wrong package coordinates sent to NCM API ## Summary | # | Report | Status | Where | |---|---|---|---| | 1 | "sending odd packages versions to the API like this one jwt version 0.0.0" | Fixed | `lib/ncm-analyze-tree.js`, `commands/report.js` | | 2 | `NCM_TOKEN=… NCM_API=… ncm` "with no success" | Not a bug; documented below | — | | 3 | "still a diff on npm audit and ncm report" | Fixed (root cause shared with #1) | `lib/ncm-analyze-tree.js` | | 4 | "we would need to investigate if our vulnDB has all the vulns" | Now meaningfully testable | see below | Tests: **24 / 24** pass (`npm run test-only`). --- ## Root cause (issues 1 & 3) `lib/ncm-analyze-tree.js` had been rewritten to use [`dependency-tree`](https://www.npmjs.com/package/dependency-tree) — a **source-code import analyzer** — instead of [`universal-module-tree`](https://www.npmjs.com/package/universal-module-tree), which walks the real npm dependency graph (`package-lock.json` → `yarn.lock` → `node_modules`). Three concrete defects in that code produced the payloads the user saw: 1. **Filename-as-package-name with placeholder version.** `dependency-tree` returns resolved file paths. The replacement code turned each path into a "package": ```js const name = path.basename(dep, path.extname(dep)); const childNode = { data: { name, version: '0.0.0' }, // hard-coded children: [] }; ``` So `jsonwebtoken.js` → `jwt @ 0.0.0`, `index.js` → `index @ 0.0.0`, and similar. That is the exact `jwt version 0.0.0` entry the user saw going out. 2. **Range-string fallback also produced `0.0.0`.** The auxiliary `getNpmDependencies` helper only read direct deps from `package.json` and did: ```js const cleanVersion = version.replace(/[^\d.]/g, '') || version; ``` For `"latest"`, `"file:…"`, `"git+https://…"`, `"workspace:*"`, etc. the strip leaves `''` and the `||` fallback is still non-semver — so those deps were emitted as `name @ 0.0.0` too. 3. **Missing transitive deps.** `dependency-tree` only surfaces files the entry point actually `require()`s, and the `package.json` fallback only listed direct dependencies. The full transitive graph was never walked. This is the direct mechanical cause of the diff vs `npm audit`. 4. **`commands/report.js` had been patched to hide the fallout.** Any `null` / `undefined` `version` returned by the NCM service was coerced to `'0.0.0'` and still included in the rendered report, so bogus rows showed up in the UI too. ## Fix ### `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines) - Reverted to `universal-module-tree` (already in `dependencies`, and the same library `commands/details.js` uses). - Removed, in full: - the `dependency-tree` invocation and entry-point discovery logic (guessing `index.js`, `app.js`, `bin/*.js`, …), - filename-as-package-name logic, - `getNpmDependencies` and the duplicate `readPackagesFromPackageJson` that stripped `^` / `~` and fell back to `'0.0.0'`, - dead counters. The new file gets a tree from `universalModuleTree(dir)`, walks it keeping dedup + `paths` info, and POSTs `{ name, version }` pairs to the GraphQL endpoint — same shape that already worked in `commands/details.js`. ### `commands/report.js` - Removed the `effectiveVersion = '0.0.0'` coercion. Packages the NCM service returns with `version === null` (unpublished / unknown) are now **skipped** instead of rendered as `@ 0.0.0`: ```js if (version === null || version === undefined) continue; ``` - Removed unused `includedCount` / `skippedCount` counters and the dead `getLicenseScore` helper. ### `package.json` / `package-lock.json` - Removed the unused `dependency-tree` dependency (`npm uninstall dependency-tree`). `universal-module-tree` was already listed and is now the single source of truth for the dependency graph. --- ## Verification ### Empirical: correct coordinates go out ``` $ node -e "const umt=require('universal-module-tree'); (async()=>{ const t=await umt('./test/fixtures/mock-project'); const f=umt.flatten(t); console.log('total:', f.length); console.log('zero-versions:', f.filter(p=>p.version==='0.0.0')); console.log('filename-like:', f.filter(p=>/\\.|\\//.test(p.name))); })()" total: 37 zero-versions: [] filename-like: [] ``` No `@ 0.0.0`, no `jwt`-style filename names — just real installed packages from `package-lock.json`. ### Tests: `npm run test-only` → 24 / 24 pass Notable guards: - `report › report output matches snapshot` — asserts **"36 packages checked"**, which only holds when the transitive graph is walked correctly. - `report › report with poisoned project` — asserts a mock package returned with `version: null` is **skipped**, not rendered as `@ 0.0.0`. - All `--filter=*`, `--compliance`, `--security`, `--long` snapshot tests pass, plus `details`, `whitelist`, `install`, `github-actions`, and `proxied` suites. --- ## Issue 2: bare `ncm` "with no success" `ncm` with no subcommand shows help and exits 0 — same as `git` or `npm` without args. `bin/ncm-cli.js`: ```js let [command = 'help', ...subargs] = argv._ if (!Object.keys(commands).includes(command)) command = 'help' ``` No API request is ever made, which is why the debug session looked like "nothing happening". Verified post-fix: `node bin/ncm-cli.js` prints help and exits 0. To actually exercise the analyzer against the local API: ```sh NCM_TOKEN=6c044113-c485-40cb-915b-cbc9d3a26730 \ NCM_API=http://localhost:3000 \ ncm report --dir=/path/to/some/project ``` --- ## Issue 4: vulnDB coverage investigation Previously impossible to do meaningfully — the payload was garbage (issue 1) and incomplete (issue 3). Now that real coordinates for the full transitive tree are sent, a direct diff is valid: ```sh cd /path/to/project npm install # ensure lockfile + node_modules npm audit --json > /tmp/npm-audit.json # npm's view NCM_TOKEN=… NCM_API=http://localhost:3000 \ ncm report --long --json > /tmp/ncm-report.json # ncm's view ``` Compare the `{name, version}` sets: - advisories in `npm-audit` but not `ncm-report` → real gaps in the vulnDB, feed back to the data team; - advisories in `ncm-report` but not `npm-audit` → extra NCM coverage (or a false positive to review). --- ## Files changed - `lib/ncm-analyze-tree.js` — rewritten (550 → 131 lines). - `commands/report.js` — `0.0.0` coercion and dead code removed. - `package.json`, `package-lock.json` — `dependency-tree` removed. --- test/lib/mock-packages.js | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/test/lib/mock-packages.js b/test/lib/mock-packages.js index bac5d90..11896df 100644 --- a/test/lib/mock-packages.js +++ b/test/lib/mock-packages.js @@ -6051,6 +6051,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6065,6 +6067,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6079,6 +6083,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6090,6 +6096,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6101,6 +6109,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6115,6 +6125,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6129,6 +6141,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6143,6 +6157,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6154,6 +6170,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6165,6 +6183,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6176,6 +6196,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6190,6 +6212,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6204,6 +6228,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6215,6 +6241,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6226,6 +6254,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6237,6 +6267,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6248,6 +6280,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6262,6 +6296,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6276,6 +6312,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6290,6 +6328,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6301,6 +6341,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6312,6 +6354,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6323,6 +6367,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6334,6 +6380,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6345,6 +6393,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6356,6 +6406,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6367,6 +6419,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6378,6 +6432,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6389,6 +6445,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6400,6 +6458,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6411,6 +6471,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6422,6 +6484,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6433,6 +6497,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6447,6 +6513,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6461,6 +6529,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6472,6 +6542,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6486,6 +6558,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6500,6 +6574,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6511,6 +6587,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6522,6 +6600,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6536,6 +6616,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6547,6 +6629,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6561,6 +6645,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license', @@ -6575,6 +6661,8 @@ module.exports = version: '1.0.0', published: true, publishedAt: '2023-01-01T00:00:00.000Z', + maintainers: [], + keywords: [], scores: [{ group: 'compliance', name: 'license',