Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function defaultScannerCommand(name, options = {}) {
.option("-d, --depth", i18n.getTokenSync("cli.commands.option_depth"), Infinity)
.option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false)
.option("-c, --contacts", i18n.getTokenSync("cli.commands.option_contacts"), [])
.option("-p, --packages", i18n.getTokenSync("cli.commands.option_packages"), [])
.option("--verbose", i18n.getTokenSync("cli.commands.option_verbose"), false);

if (includeOutput) {
Expand Down
4 changes: 3 additions & 1 deletion docs/cli/auto.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ $ nsecure auto --keep
| `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). |
| `--keep` | `-k` | `false` | Preserve JSON payload after execution. |
| `--developer` | | `false` | Launch the server in developer mode, enabling automatic refresh on HTML/CSS/JS changes. |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |
| `--packages` | `-p` | `[]` | List of packages to highlight. |
`--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
4 changes: 3 additions & 1 deletion docs/cli/cwd.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ $ nsecure cwd [options]
| `--silent` | | `false` | Suppress console output, making execution silent. |
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
| `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |
| `--packages` | `-p` | `[]` | List of packages to highlight. |
`--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
4 changes: 3 additions & 1 deletion docs/cli/from.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ $ nsecure from express@3.0.0 -o express-report
| `--silent` | | `false` | Suppress console output, making execution silent. |
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
| `--vulnerabilityStrategy` | `-s` | github-advisory | Strategy used to fetch package vulnerabilities (see Vulnera [available strategy](https://github.com/NodeSecure/vulnera?tab=readme-ov-file#available-strategy)). |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. | `--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |
| `--packages` | `-p` | `[]` | List of packages to highlight. |
`--verbose` | | `false` | Sets cli log level to verbose, causing the CLI to output more detailed logs. |
1 change: 1 addition & 0 deletions i18n/arabic.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const cli = {
option_output: "اسم ملف JSON الناتج",
option_silent: "تفعيل الوضع الصامت الذي يعطل مؤشرات CLI",
option_contacts: "قائمة جهات الاتصال للتمييز",
option_packages: "قائمة الحزم للتمييز",
option_verbose: "ضبط مستوى الـ log الخاص بالـ CLI على verbose، مما يجعل الـ CLI يولّد logs أكثر تفصيلاً.",
strategy: "مصدر الثغرات للاستخدام",
cwd: {
Expand Down
1 change: 1 addition & 0 deletions i18n/english.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const cli = {
option_output: "Json file output name",
option_silent: "enable silent mode which disable CLI spinners",
option_contacts: "List of contacts to hightlight",
option_packages: "List of packages to highlight",
option_verbose: "Sets cli log level to verbose, causing the CLI to output more detailed logs.",
strategy: "Vulnerabilities source to use",
cwd: {
Expand Down
1 change: 1 addition & 0 deletions i18n/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const cli = {
option_output: "Nom de sortie du fichier json",
option_silent: "Activer le mode silencieux qui désactive les spinners du CLI",
option_contacts: "Liste des contacts à mettre en évidence",
option_packages: "Liste des packages à mettre en évidence",
option_verbose: "Définir le niveau de log CLI à verbeux, ce qui amènera la CLI à générer des logs plus détaillés.",
strategy: "Source de vulnérabilités à utiliser",
cwd: {
Expand Down
1 change: 1 addition & 0 deletions i18n/turkish.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const cli = {
option_output: "JSON dosyası çıktı adı",
option_silent: "CLI döndürücülerini devre dışı bırakan sessiz modu etkinleştir",
option_contacts: "Vurgulanacak kişilerin listesi",
option_packages: "Vurgulanacak paketlerin listesi",
option_verbose: "CLI'nin log seviyesini verbose olarak ayarlar, bu da CLI'nin daha ayrıntılı loglar üretmesine neden olur.",
strategy: "Kullanılacak güvenlik açığı kaynağı",
cwd: {
Expand Down
34 changes: 34 additions & 0 deletions src/commands/parsers/packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Import Third-party Dependencies
import { parseNpmSpec } from "@nodesecure/mama";

/**
* Parse a list of CLI package strings into the expected HighlightPackages format expected
* by @nodesecure/scanner: `string[] | Record<string, string[] | SemverRange>`.
*
* Each input string can be:
* - "lodash" → plain name, no version constraint
* - "lodash@^4.0.0" → name with a semver range
* - "@scope/pkg" → scoped package, no version constraint
* - "@scope/pkg@^1.0.0" → scoped package with a semver range
*
* When none of the entries carry a version constraint the function returns a plain `string[]`.
* If at least one entry has a version constraint the function returns a `Record`;
* Entries without a constraint are mapped to '*'
*
* @param {string | string[]} input
* @returns {string[] | Record<string, string[] | string>}
*/
export function parsePackages(input) {
const items = Array.isArray(input) ? input : [input];
const parsed = items.map(parseNpmSpec);

const hasVersionConstraints = parsed.some(({ semver }) => semver !== null);

if (hasVersionConstraints) {
return Object.fromEntries(
parsed.map(({ name, semver }) => [name, semver ?? "*"])
);
}

return parsed.map(({ name }) => name);
}
18 changes: 12 additions & 6 deletions src/commands/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import {
formatMs
} from "./loggers/logger.js";
import { parseContacts } from "./parsers/contacts.js";
import { parsePackages } from "./parsers/packages.js";

export async function auto(spec, options) {
const { keep, ...commandOptions } = options;

const optionsWithContacts = {
const optionsWithHighlight = {
...commandOptions,
highlight: {
contacts: parseContacts(options.contacts)
contacts: parseContacts(options.contacts),
packages: parsePackages(options.packages ?? [])
}
};

Expand All @@ -36,8 +38,8 @@ export async function auto(spec, options) {

const payloadFile = await (
typeof spec === "string" ?
from(spec, optionsWithContacts, cache) :
cwd(optionsWithContacts, cache)
from(spec, optionsWithHighlight, cache) :
cwd(optionsWithHighlight, cache)
);
try {
if (payloadFile !== null) {
Expand Down Expand Up @@ -75,6 +77,7 @@ export async function cwd(options, cache) {
vulnerabilityStrategy,
silent,
contacts,
packages,
verbose
} = options;

Expand All @@ -86,7 +89,8 @@ export async function cwd(options, cache) {
fullLockMode: full,
vulnerabilityStrategy,
highlight: {
contacts: parseContacts(contacts)
contacts: parseContacts(contacts),
packages: parsePackages(packages)
},
isVerbose: verbose,
workers: true,
Expand Down Expand Up @@ -118,6 +122,7 @@ export async function from(spec, options, cache) {
output,
silent,
contacts,
packages,
vulnerabilityStrategy,
verbose
} = options;
Expand All @@ -128,7 +133,8 @@ export async function from(spec, options, cache) {
maxDepth,
vulnerabilityStrategy,
highlight: {
contacts: parseContacts(contacts)
contacts: parseContacts(contacts),
packages: parsePackages(packages)
},
isVerbose: verbose,
workers: true,
Expand Down
56 changes: 56 additions & 0 deletions test/commands/parsers/packages.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Import Node.js Dependencies
import { describe, it } from "node:test";
import assert from "node:assert/strict";

// Import Internal Dependencies
import { parsePackages } from "../../../src/commands/parsers/packages.js";

describe("packages parser", () => {
describe("returns string[] when no version constraints", () => {
it("should parse a single plain package name", () => {
assert.deepEqual(parsePackages("lodash"), ["lodash"]);
});

it("should parse a single scoped package with no version", () => {
assert.deepEqual(parsePackages("@scope/pkg"), ["@scope/pkg"]);
});

it("should parse multiple plain packages with no versions", () => {
assert.deepEqual(parsePackages(["lodash", "express"]), ["lodash", "express"]);
});
});

describe("returns Record when at least one entry has a version constraint", () => {
it("should parse a package with a semver range", () => {
assert.deepEqual(parsePackages("lodash@^4.0.0"), { lodash: "^4.0.0" });
});

it("should parse a scoped package with a semver range", () => {
assert.deepEqual(parsePackages("@scope/pkg@^1.0.0"), { "@scope/pkg": "^1.0.0" });
});

it("should map entries without a version to '*' when mixed with versioned entries", () => {
assert.deepEqual(
parsePackages(["lodash", "express@^4.0.0"]),
{ lodash: "*", express: "^4.0.0" }
);
});

it("should parse multiple packages all with version constraints", () => {
assert.deepEqual(
parsePackages(["lodash@^4.0.0", "express@^4.18.0"]),
{ lodash: "^4.0.0", express: "^4.18.0" }
);
});
});

describe("edge cases", () => {
it("should return an empty array for an empty array input", () => {
assert.deepEqual(parsePackages([]), []);
});

it("should treat a scoped name with no slash and no version as a plain package name", () => {
assert.deepEqual(parsePackages("@scope"), ["@scope"]);
});
});
});
Loading