Skip to content

Commit 34ba0af

Browse files
authored
feat(contacts): highlight contacts (#504)
1 parent 1f2664e commit 34ba0af

File tree

15 files changed

+202
-36
lines changed

15 files changed

+202
-36
lines changed

bin/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ function defaultScannerCommand(name, options = {}) {
135135

136136
const cmd = prog.command(name)
137137
.option("-d, --depth", i18n.getTokenSync("cli.commands.option_depth"), Infinity)
138-
.option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false);
138+
.option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false)
139+
.option("-c, --contacts", i18n.getTokenSync("cli.commands.option_contacts"), []);
139140

140141
if (includeOutput) {
141142
cmd.option("-o, --output", i18n.getTokenSync("cli.commands.option_output"), "nsecure-result");

docs/cli/auto.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ $ nsecure auto --keep
2020

2121
## ⚙️ Available Options
2222

23-
| Name | Shortcut | Default Value | Description |
24-
|---|---|---|--|
25-
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
26-
| `--silent` | | `false` | Suppress console output, making execution silent. |
27-
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
28-
| `--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)). |
29-
| `--keep` | `-k` | `false` | Preserve JSON payload after execution. |
30-
| `--developer` | `-d` | `false` | Launch the server in developer mode, enabling automatic HTML component refresh. |
23+
| Name | Shortcut | Default Value | Description |
24+
| ------------------------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
25+
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
26+
| `--silent` | | `false` | Suppress console output, making execution silent. |
27+
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
28+
| `--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)). |
29+
| `--keep` | `-k` | `false` | Preserve JSON payload after execution. |
30+
| `--developer` | `-d` | `false` | Launch the server in developer mode, enabling automatic HTML component refresh. |
31+
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |

docs/cli/cwd.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ $ nsecure cwd [options]
1010

1111
## ⚙️ Available Options
1212

13-
| Name | Shortcut | Default Value | Description |
14-
|---|---|---|---|
15-
| `--nolock` | `-n` | `false` | Do not use a lock file (package-lock.json or yarn.lock) for the analysis. |
16-
| `--full` | `-f` | `false` | Perform a full analysis of the project, including all dependencies. |
17-
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
18-
| `--silent` | | `false` | Suppress console output, making execution silent. |
19-
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
20-
| `--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)). |
13+
| Name | Shortcut | Default Value | Description |
14+
| ------------------------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15+
| `--nolock` | `-n` | `false` | Do not use a lock file (package-lock.json or yarn.lock) for the analysis. |
16+
| `--full` | `-f` | `false` | Perform a full analysis of the project, including all dependencies. |
17+
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
18+
| `--silent` | | `false` | Suppress console output, making execution silent. |
19+
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
20+
| `--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)). |
21+
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |

docs/cli/from.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ $ nsecure from [email protected] -o express-report
1818

1919
## ⚙️ Available Options
2020

21-
| Name | Shortcut | Default Value | Description |
22-
|---|---|---|---|
23-
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
24-
| `--silent` | | `false` | Suppress console output, making execution silent. |
25-
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
26-
| `--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)). |
21+
| Name | Shortcut | Default Value | Description |
22+
| ------------------------- | -------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23+
| `--depth` | `-d` | `Infinity` | Maximum tree depth to scan. |
24+
| `--silent` | | `false` | Suppress console output, making execution silent. |
25+
| `--output` | `-o` | `nsecure-result` | Specify the output file for the results. |
26+
| `--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)). |
27+
| `--contacts` | `-c` | `[]` | List of contacts to highlight. |

i18n/english.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const cli = {
1414
option_depth: "Maximum dependencies depth to fetch",
1515
option_output: "Json file output name",
1616
option_silent: "enable silent mode which disable CLI spinners",
17+
option_contacts: "List of contacts to hightlight",
1718
strategy: "Vulnerabilities source to use",
1819
cwd: {
1920
desc: "Run security analysis on the current working dir",
@@ -80,7 +81,7 @@ const cli = {
8081
startHttp: {
8182
invalidScannerVersion: tS`the payload has been scanned with version '${0}' and do not satisfies the required CLI range '${1}'`,
8283
regenerate: "please re-generate a new JSON payload using the CLI"
83-
}
84+
},
8485
};
8586

8687
const ui = {

i18n/french.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const cli = {
1414
option_depth: "Niveau de profondeur de dépendances maximum à aller chercher",
1515
option_output: "Nom de sortie du fichier json",
1616
option_silent: "Activer le mode silencieux qui désactive les spinners du CLI",
17+
option_contacts: "Liste des contacts à mettre en évidence",
1718
strategy: "Source de vulnérabilités à utiliser",
1819
cwd: {
1920
desc: "Démarre une analyse de sécurité sur le dossier courant",
@@ -80,7 +81,7 @@ const cli = {
8081
startHttp: {
8182
invalidScannerVersion: tS`le fichier d'analyse correspond à la version '${0}' du scanner et ne satisfait pas la range '${1}' attendu par la CLI`,
8283
regenerate: "veuillez re-générer un nouveau fichier d'analyse JSON en utilisant votre CLI"
83-
}
84+
},
8485
};
8586

8687
const ui = {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"@nodesecure/ossf-scorecard-sdk": "^3.2.1",
8888
"@nodesecure/rc": "^4.0.1",
8989
"@nodesecure/report": "^3.0.0",
90-
"@nodesecure/scanner": "^6.4.0",
90+
"@nodesecure/scanner": "^6.6.0",
9191
"@nodesecure/utils": "^2.2.0",
9292
"@nodesecure/vulnera": "^2.0.1",
9393
"@openally/result": "^1.3.0",

public/components/views/home/maintainers/maintainers.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ body.dark .home--maintainers>.person {
2626
background: var(--dark-theme-primary-color);
2727
}
2828

29+
.home--maintainers> .highlighted{
30+
background: linear-gradient(to bottom, rgb(230, 240, 250) 0%, rgb(220, 235, 245) 100%);
31+
}
32+
33+
body.dark .home--maintainers > .highlighted {
34+
background: linear-gradient(to right, rgb(11, 3, 31) 0%, rgba(46, 10, 10, 0.8) 100%);
35+
}
36+
2937
.home--maintainers>.person:hover {
3038
border-color: var(--secondary-darker);
3139
cursor: pointer;

public/components/views/home/maintainers/maintainers.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,23 @@ export class Maintainers {
2626
}
2727

2828
render() {
29-
const authors = [...this.secureDataSet.authors.entries()]
30-
.sort((left, right) => right[1].packages.size - left[1].packages.size);
29+
const authors = this.#highlightContacts([...this.secureDataSet.authors.entries()]
30+
.sort((left, right) => right[1].packages.size - left[1].packages.size));
3131

3232
document.getElementById("authors-count").innerHTML = authors.length;
3333
document.querySelector(".home--maintainers")
3434
.appendChild(this.generate(authors));
3535
}
3636

37+
#highlightContacts(authors) {
38+
const highlightedAuthors = authors
39+
.filter(([_, contact]) => this.secureDataSet.isHighlighted(contact));
40+
41+
const authorsRest = authors.filter(([_, contact]) => !this.secureDataSet.isHighlighted(contact));
42+
43+
return [...highlightedAuthors, ...authorsRest];
44+
}
45+
3746
generate(authors) {
3847
const fragment = document.createDocumentFragment();
3948
const hideItems = authors.length > this.maximumMaintainers;
@@ -61,6 +70,9 @@ export class Maintainers {
6170
})
6271
]
6372
});
73+
if (this.secureDataSet.isHighlighted(data)) {
74+
person.classList.add("highlighted");
75+
}
6476
if (hideItems && id >= this.maximumMaintainers) {
6577
person.classList.add("hidden");
6678
}

src/commands/parsers/contacts.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// CONSTANTS
2+
const kEmailRegex = "[^\\.\\s@:](?:[^\\s@:]*[^\\s@:\\.])?@[^\\.\\s@]+(?:\\.[^\\.\\s@]+)*";
3+
4+
export function parseContacts(input) {
5+
return Array.isArray(input) ? input.map(parseContact) : [parseContact(input)];
6+
}
7+
8+
function parseContact(str) {
9+
const emailMatch = str.match(emailRegex());
10+
if (!emailMatch) {
11+
return { name: str.trim() };
12+
}
13+
const email = emailMatch[0];
14+
const name = str.replace(email, "").trim();
15+
if (name) {
16+
return { name, email };
17+
}
18+
19+
return { email };
20+
}
21+
22+
function emailRegex() {
23+
return new RegExp(kEmailRegex, "g");
24+
}

src/commands/scanner.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ import * as Scanner from "@nodesecure/scanner";
1414
// Import Internal Dependencies
1515
import * as http from "./http.js";
1616
import { appCache } from "../cache.js";
17+
import { parseContacts } from "./parsers/contacts.js";
1718

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

22+
const optionsWithContacts = {
23+
...commandOptions,
24+
highlight: {
25+
contacts: parseContacts(options.contacts)
26+
}
27+
};
28+
2129
const payloadFile = await (
2230
typeof spec === "string" ?
23-
from(spec, commandOptions) :
24-
cwd(commandOptions)
31+
from(spec, optionsWithContacts) :
32+
cwd(optionsWithContacts)
2533
);
2634
try {
2735
if (payloadFile !== null) {
@@ -55,24 +63,31 @@ export async function cwd(options) {
5563
nolock,
5664
full,
5765
vulnerabilityStrategy,
58-
silent
66+
silent,
67+
contacts
5968
} = options;
6069

6170
const payload = await Scanner.cwd(
6271
process.cwd(),
63-
{ maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy },
72+
{ maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy, highlight:
73+
{ contacts: parseContacts(contacts) } },
6474
initLogger(void 0, !silent)
6575
);
6676

6777
return await logAndWrite(payload, output, { local: true });
6878
}
6979

7080
export async function from(spec, options) {
71-
const { depth: maxDepth = Infinity, output, silent } = options;
81+
const { depth: maxDepth = Infinity, output, silent, contacts } = options;
7282

7383
const payload = await Scanner.from(
7484
spec,
75-
{ maxDepth },
85+
{
86+
maxDepth,
87+
highlight: {
88+
contacts: parseContacts(contacts)
89+
}
90+
},
7691
initLogger(spec, !silent)
7792
);
7893

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Import Node.js Dependencies
2+
import { describe, it } from "node:test";
3+
import assert from "node:assert/strict";
4+
5+
// Import Internal Dependencies
6+
import { parseContacts } from "../../../src/commands/parsers/contacts.js";
7+
8+
describe("contacts parser", () => {
9+
it("should have no contacts", () => {
10+
assert.deepEqual(parseContacts([]), []);
11+
});
12+
13+
it("should have a contact with a name", () => {
14+
assert.deepEqual(parseContacts("sindre"), [{ name: "sindre" }]);
15+
});
16+
17+
it("should trim names", () => {
18+
assert.deepEqual(parseContacts(" matteo "), [{ name: "matteo" }]);
19+
});
20+
21+
it("should have a contact with an email", () => {
22+
assert.deepEqual(parseContacts("[email protected]"), [{ email: "[email protected]" }]);
23+
});
24+
25+
it("should trim emails", () => {
26+
assert.deepEqual(parseContacts(" [email protected] "), [{ email: "[email protected]" }]);
27+
});
28+
29+
it("should parse names and emails", () => {
30+
assert.deepEqual(parseContacts("sindre [email protected]"), [{ name: "sindre", email: "[email protected]" }]);
31+
});
32+
33+
it("should parse multiples contacts", () => {
34+
assert.deepEqual(parseContacts(["sindre [email protected]", "matteo"]),
35+
[{ name: "sindre", email: "[email protected]" }, { name: "matteo" }]);
36+
});
37+
});
38+

workspaces/vis-network/src/dataset.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ export default class NodeSecureDataSet extends EventTarget {
1313
* @param {string[]} [options.warningsToIgnore=[]]
1414
* @param {"light"|"dark"} [options.theme]
1515
*/
16+
17+
#highligthedContacts;
18+
1619
constructor(options = {}) {
1720
super();
1821
const {
1922
flagsToIgnore = [],
2023
warningsToIgnore = [],
2124
theme = "light"
2225
} = options;
23-
2426
this.flagsToIgnore = new Set(flagsToIgnore);
2527
this.warningsToIgnore = new Set(warningsToIgnore);
2628
this.theme = theme;
@@ -76,6 +78,18 @@ export default class NodeSecureDataSet extends EventTarget {
7678

7779
this.warnings = data.warnings;
7880

81+
this.#highligthedContacts = data.highlighted.contacts
82+
.reduce((acc, { name, email }) => {
83+
if (name) {
84+
acc.names.add(name);
85+
}
86+
if (email) {
87+
acc.emails.add(email);
88+
}
89+
90+
return acc;
91+
}, { names: new Set(), emails: new Set() });
92+
7993
const dataEntries = Object.entries(data.dependencies);
8094
this.dependenciesCount = dataEntries.length;
8195

@@ -210,4 +224,8 @@ export default class NodeSecureDataSet extends EventTarget {
210224

211225
return { nodes, edges };
212226
}
227+
228+
isHighlighted(contact) {
229+
return this.#highligthedContacts.names.has(contact.name) || this.#highligthedContacts.emails.has(contact.email);
230+
}
213231
}

workspaces/vis-network/test/dataset-payload.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
11
{
22
"id": "abcde",
33
"rootDepencyName": "pkg1",
4+
"highlighted": {
5+
"contacts": [
6+
{
7+
"email": "[email protected]",
8+
"dependencies": [
9+
"frequency-set",
10+
"sec-literal",
11+
"js-x-ray"
12+
]
13+
},
14+
{
15+
"name": "Rich Harris",
16+
"email": "[email protected]",
17+
"dependencies": [
18+
"frequency-set",
19+
"sec-literal",
20+
"js-x-ray"
21+
]
22+
},
23+
{
24+
"name": "Sindre Sorhus",
25+
"dependencies": [
26+
"is-svg",
27+
"ansi-regex",
28+
"strip-ansi",
29+
"is-fullwidth-code-point",
30+
"string-width"
31+
]
32+
}
33+
]
34+
},
435
"dependencies": {
536
"pkg2": {
637
"versions": {

0 commit comments

Comments
 (0)