Skip to content
Draft
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ Type a package name directly to search, or prefix with a filter name followed by
- `ext` — file extension present in the package (e.g. `.js`, `.ts`).
- `builtin` — Node.js core module used by the package (e.g. `fs`, `path`).
- `size` — size range (see [size-satisfies](https://github.com/NodeSecure/size-satisfies#usage-example), e.g. `>50kb`, `10kb..200kb`).
- `highlighted` — all highlighted packages by default.

## FAQ

Expand Down
1 change: 1 addition & 0 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,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. |
4 changes: 3 additions & 1 deletion 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 Expand Up @@ -232,7 +233,8 @@ const ui = {
legend: {
default: "الحزمة بخير.",
warn: "الحزمة بها تحذيرات.",
friendly: "الحزمة تتم صيانتها بواسطة نفس مؤلفي الحزمة الجذرية."
friendly: "الحزمة تتم صيانتها بواسطة نفس مؤلفي الحزمة الجذرية.",
highlighted: "الحزمة جزء من الحزم المميزة"
},
lockedNavigation: {
next: "التالي",
Expand Down
7 changes: 5 additions & 2 deletions i18n/english.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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 Expand Up @@ -258,13 +259,15 @@ const ui = {
author: "name or email",
ext: "file extension",
builtin: "node.js module",
size: "e.g. >50kb"
size: "e.g. >50kb",
highlighted: "all"
}
},
legend: {
default: "The package is fine.",
warn: "The package has warnings.",
friendly: "The package is maintained by the same authors as the root package."
friendly: "The package is maintained by the same authors as the root package.",
highlighted: "The package is part of highlighted packages"
},
lockedNavigation: {
next: "Next",
Expand Down
7 changes: 5 additions & 2 deletions i18n/french.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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 Expand Up @@ -258,13 +259,15 @@ const ui = {
author: "nom ou email",
ext: "extension de fichier",
builtin: "module node.js",
size: "ex. >50kb"
size: "ex. >50kb",
highlighted: "all"
}
},
legend: {
default: "Rien à signaler.",
warn: "La dépendance contient des menaces.",
friendly: "La dépendance est maintenu par des auteurs du package principal."
friendly: "La dépendance est maintenu par des auteurs du package principal.",
highlighted: "Le package fait partie des packages mis en évidence"
},
lockedNavigation: {
next: "Suivant",
Expand Down
4 changes: 3 additions & 1 deletion 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 Expand Up @@ -234,7 +235,8 @@ const ui = {
legend: {
default: "Paket sorunsuz.",
warn: "Pakette uyarılar var.",
friendly: "Paket, kök paketin yazarlarıyla aynı kişiler tarafından bakılmaktadır."
friendly: "Paket, kök paketin yazarlarıyla aynı kişiler tarafından bakılmaktadır.",
highlighted: "Paket, vurgulanan paketlerin bir parçasıdır"
},
lockedNavigation: {
next: "Sonraki",
Expand Down
3 changes: 2 additions & 1 deletion public/components/legend/legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class Legend extends LitElement {
${this.#createLegendBoxElement(colors.WARN, legend.warn)}
${this.#createLegendBoxElement(colors.FRIENDLY, legend.friendly)}
${this.#createLegendBoxElement(colors.DEFAULT, legend.default)}
${this.#createLegendBoxElement(colors.HIGHLIGHTED, legend.highlighted)}
</div>
`;
}
Expand All @@ -98,7 +99,7 @@ class Legend extends LitElement {
theme,
text
) {
const style = `background-color: ${theme.color}; color: ${theme.font.color};`;
const style = `background-color: ${theme.color}; color: ${(theme.font ?? COLORS.LIGHT.DEFAULT.font).color};`;

return html`
<div class="legend-box" style=${style}>
Expand Down
6 changes: 5 additions & 1 deletion public/components/search-command/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ export const VERSION_PRESETS = [
{ label: "≥ 1.0", value: ">=1.0.0" },
{ label: "< 1.0", value: "<1.0.0" }
];
export const FILTERS_NAME = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size"]);
export const FILTERS_NAME = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size", "highlighted"]);
// Filters that use a searchable text-based list (not a rich visual panel)
export const FILTER_HAS_HELPERS = new Set(["license", "ext", "builtin", "author"]);
// Filters where the mode persists after selection (multi-select)
export const FILTER_MULTI_SELECT = new Set(["flag"]);
// Filters that auto-confirm immediately on selection (no text input needed)
export const FILTER_INSTANT_CONFIRM = new Set(["highlighted"]);

/**
* Returns per-flag package counts across the full linker.
Expand Down Expand Up @@ -236,6 +238,8 @@ function matchesFilter(opt, filterName, inputValue) {
}
case "flag":
return opt.flags.includes(inputValue);
case "highlighted":
return inputValue === "true" ? opt.isHighlighted === true : opt.isHighlighted !== true;
default:
return false;
}
Expand Down
19 changes: 13 additions & 6 deletions public/components/search-command/search-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FILTERS_NAME,
FILTER_HAS_HELPERS,
FILTER_MULTI_SELECT,
FILTER_INSTANT_CONFIRM,
computeMatches,
getHelperValues
} from "./filters.js";
Expand Down Expand Up @@ -59,12 +60,13 @@ class SearchCommand extends LitElement {
#init = ({ detail: { linker, packages, network } }) => {
this.#linker = linker;
this.#network = network;
this.#packages = packages.map(({ id, name, version, flags }) => {
this.#packages = packages.map(({ id, name, version, flags, isHighlighted }) => {
return {
id: String(id),
name,
version,
flags
flags,
isHighlighted
};
});
};
Expand Down Expand Up @@ -280,10 +282,15 @@ class SearchCommand extends LitElement {

#selectHelper(helper) {
if (helper.type === "filter") {
this.inputValue = `${helper.value}:`;
this.activeFilter = helper.value;
this.selectedIndex = -1;
this.results = [];
if (FILTER_INSTANT_CONFIRM.has(helper.value)) {
this.#addQuery(helper.value, "true");
}
else {
this.inputValue = `${helper.value}:`;
this.activeFilter = helper.value;
this.selectedIndex = -1;
this.results = [];
}
}
else {
this.#addQuery(this.activeFilter, helper.value);
Expand Down
7 changes: 1 addition & 6 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,7 @@ function onSettingsSaved(defaultConfig = null) {
window.settings.config.theme = theme;
window.settings.config.disableExternalRequests = config.disableExternalRequests;

if (theme === "dark") {
document.body.classList.add("dark");
}
else {
document.body.classList.remove("dark");
}
document.body.classList.toggle("dark", theme === "dark");

await secureDataSet.init(
secureDataSet.data,
Expand Down
70 changes: 70 additions & 0 deletions src/commands/parsers/packages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* 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
* - "lodash@1.0.0,2.0.0" → name with a list of specific versions
* - "@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(parsePackage);

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

if (!hasVersionConstraints) {
return parsed.map(({ name }) => name);
}

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

/**
* @param {string} str
* @returns {{ name: string, version: string | string[] | null }}
*/
function parsePackage(str) {
// Scoped packages start with "@", so search for a second "@" after index 1.
const versionSeparator = str.startsWith("@") ? str.indexOf("@", 1) : str.indexOf("@");

if (versionSeparator === -1) {
return { name: str.trim(), version: null };
}

const name = str.slice(0, versionSeparator).trim();
const versionStr = str.slice(versionSeparator + 1).trim();

if (versionStr === "") {
return { name, version: null };
}

if (versionStr.includes(",")) {
const versions = versionStr.split(",").map((v) => v.trim()).filter(Boolean);
let version;
if (versions.length === 0) {
version = null;
}
else if (versions.length === 1) {
version = versions[0];
}
else {
version = versions;
}

return { name, version };
}

return { name, version: versionStr };
}
20 changes: 16 additions & 4 deletions src/commands/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ import * as scanner from "@nodesecure/scanner";
import kleur from "../utils/styleText.js";
import * as http from "./http.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 = {
...commandOptions,
highlight: {
contacts: parseContacts(options.contacts)
contacts: parseContacts(options.contacts),
packages: parsePackages(options.packages ?? [])
}
};

Expand Down Expand Up @@ -67,14 +69,15 @@ export async function cwd(options) {
vulnerabilityStrategy,
silent,
contacts,
packages: highlightPackages = [],
verbose
} = options;

const payload = await scanner.workingDir(
process.cwd(),
{
maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy, highlight:
{ contacts: parseContacts(contacts) }, isVerbose: verbose
{ contacts: parseContacts(contacts), packages: parsePackages(highlightPackages) }, isVerbose: verbose
},
initLogger(void 0, !silent)
);
Expand All @@ -83,15 +86,24 @@ export async function cwd(options) {
}

export async function from(spec, options) {
const { depth: maxDepth = Infinity, output, silent, contacts, vulnerabilityStrategy, verbose } = options;
const {
depth: maxDepth = Infinity,
output,
silent,
contacts,
packages: highlightPackages = [],
vulnerabilityStrategy,
verbose
} = options;

const payload = await scanner.from(
spec,
{
maxDepth,
vulnerabilityStrategy,
highlight: {
contacts: parseContacts(contacts)
contacts: parseContacts(contacts),
packages: parsePackages(highlightPackages)
},
isVerbose: verbose
},
Expand Down
Loading