Skip to content

Commit 979c469

Browse files
authored
refactor(scanner)!: improve global warning implementation (#509)
1 parent 07f9f59 commit 979c469

15 files changed

+126
-93
lines changed

.changeset/clean-sites-find.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/scanner": major
3+
---
4+
5+
Extend global warning with multi-properties objects

workspaces/scanner/src/comparePayloads.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import type {
1111
Publisher,
1212
Maintainer,
1313
Repository,
14-
DependencyLinks
14+
DependencyLinks,
15+
GlobalWarning
1516
} from "./types.js";
1617

1718
export interface PayloadComparison {
1819
title: string;
19-
warnings: ArrayDiff<string>;
20+
warnings: ArrayDiff<GlobalWarning>;
2021
scannerVersion: ValueComparison<string>;
2122
vulnerabilityStrategy: ValueComparison<string>;
2223
dependencies: DependenciesComparison;

workspaces/scanner/src/depWalker.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Logger, ScannerLoggerEvents } from "./class/logger.class.js";
2727
import type {
2828
Dependency,
2929
DependencyVersion,
30+
GlobalWarning,
3031
Options,
3132
Payload
3233
} from "./types.js";
@@ -213,7 +214,7 @@ export async function depWalker(
213214

214215
// We do this because it "seem" impossible to link all dependencies in the first walk.
215216
// Because we are dealing with package only one time it may happen sometimes.
216-
const globalWarnings: string[] = [];
217+
const globalWarnings: GlobalWarning[] = [];
217218
for (const [packageName, dependency] of dependencies) {
218219
const metadataIntegrities = dependency.metadata?.integrity ?? {};
219220

@@ -222,15 +223,21 @@ export async function depWalker(
222223

223224
const isEmptyPackage = dependencyVer.warnings.some((warning) => warning.kind === "empty-package");
224225
if (isEmptyPackage) {
225-
globalWarnings.push(`${packageName}@${version} only contain a package.json file!`);
226+
globalWarnings.push({
227+
type: "empty-package",
228+
message: `${packageName}@${version} only contain a package.json file!`
229+
});
226230
}
227231

228232
if (!("integrity" in dependencyVer) || dependencyVer.flags.includes("isGit")) {
229233
continue;
230234
}
231235

232236
if (dependencyVer.integrity !== integrity) {
233-
globalWarnings.push(`${packageName}@${version} manifest & tarball integrity doesn't match!`);
237+
globalWarnings.push({
238+
type: "integrity-mismatch",
239+
message: `${packageName}@${version} manifest & tarball integrity doesn't match!`
240+
});
234241
}
235242
}
236243
for (const version of Object.entries(dependency.versions)) {

workspaces/scanner/src/types.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,30 @@ export interface Dependency {
157157

158158
export type Dependencies = Record<string, Dependency>;
159159

160+
export type GlobalWarning = { message: string; } & (
161+
{
162+
type:
163+
| "dangerous-dependency"
164+
| "integrity-mismatch"
165+
| "empty-package";
166+
metadata?: Record<string, unknown>;
167+
} |
168+
{
169+
type: "typo-squatting";
170+
metadata: {
171+
name: string;
172+
similar: string[];
173+
};
174+
}
175+
);
176+
160177
export interface Payload {
161178
/** Payload unique id */
162179
id: string;
163180
/** Name of the analyzed package */
164181
rootDependencyName: string;
165182
/** Global warnings list */
166-
warnings: string[];
183+
warnings: GlobalWarning[];
167184
highlighted: {
168185
contacts: IlluminatedContact[];
169186
};

workspaces/scanner/src/utils/warnings.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { Contact } from "@nodesecure/npm-types";
1414
// Import Internal Dependencies
1515
import { getDirNameFromUrl } from "./dirname.js";
1616
import { TopPackages } from "../class/TopPackages.class.js";
17-
import type { Dependency } from "../types.js";
17+
import type { Dependency, GlobalWarning } from "../types.js";
1818

1919
await i18n.extendFromSystemPath(
2020
path.join(getDirNameFromUrl(import.meta.url), "..", "i18n")
@@ -35,7 +35,7 @@ const kDependencyWarnMessage = {
3535
} as const;
3636

3737
export interface GetWarningsResult {
38-
warnings: string[];
38+
warnings: GlobalWarning[];
3939
illuminated: IlluminatedContact[];
4040
}
4141

@@ -49,8 +49,17 @@ export async function getDependenciesWarnings(
4949
const topPackages = new TopPackages();
5050
await topPackages.loadJSON();
5151

52-
const warnings = vulnerableDependencyNames
53-
.flatMap((name) => (dependenciesMap.has(name) ? `${kDetectedDep(name)} ${kDependencyWarnMessage[name]}` : []));
52+
const warnings: GlobalWarning[] = vulnerableDependencyNames
53+
.flatMap((name) => {
54+
if (!dependenciesMap.has(name)) {
55+
return [];
56+
}
57+
58+
return {
59+
type: "dangerous-dependency",
60+
message: `${kDetectedDep(name)} ${kDependencyWarnMessage[name]}`
61+
};
62+
});
5463

5564
const dependencies: Record<string, ContactExtractorPackageMetadata> = Object.create(null);
5665
for (const [packageName, dependency] of dependenciesMap) {
@@ -62,7 +71,14 @@ export async function getDependenciesWarnings(
6271
packageName,
6372
similarPackages.join(", ")
6473
);
65-
warnings.push(warningMessage);
74+
warnings.push({
75+
type: "typo-squatting",
76+
message: warningMessage,
77+
metadata: {
78+
name: packageName,
79+
similar: similarPackages
80+
}
81+
});
6682
}
6783

6884
dependencies[packageName] = {

workspaces/scanner/test/comparePayloads.spec.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,23 @@ it("should throw an error if compared payloads are not from the same package", (
2828
});
2929

3030
it("should detect warnings diff", () => {
31-
const { warnings: { added, removed }, dependencies: { compared } } = compareTo("warningChangedPayload");
32-
33-
assert.strictEqual(added.length, 1);
34-
assert.deepStrictEqual(added[0], "encoded-literal");
35-
36-
assert.strictEqual(removed.length, 1);
37-
assert.deepStrictEqual(removed[0], "unsafe-regex");
31+
const {
32+
warnings: { added, removed },
33+
dependencies: { compared }
34+
} = compareTo("warningChangedPayload");
35+
36+
assert.deepEqual(added, [
37+
{
38+
type: "empty-package",
39+
message: "..."
40+
}
41+
]);
42+
assert.deepEqual(removed, [
43+
{
44+
type: "dangerous-dependency",
45+
message: "..."
46+
}
47+
]);
3848

3949
const deepWarnings = compared.get("foo")!.versions.compared.get("2.0.0")!.warnings;
4050
assert.strictEqual(deepWarnings.added.length, 1);

workspaces/scanner/test/depWalker.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,14 @@ test("execute depWalker on typo-squatting", async() => {
139139
registry: getLocalRegistryURL()
140140
});
141141

142-
assert.deepEqual(result.warnings, [
142+
assert.ok(result.warnings.length > 0);
143+
const warning = result.warnings[0];
144+
145+
assert.equal(warning.type, "typo-squatting");
146+
assert.strictEqual(
147+
result.warnings[0].message,
143148
"The package 'mecha' is similar to the following popular packages: fecha, mocha"
144-
]);
149+
);
145150
});
146151

147152
test("fetch payload of pacote on the npm registry", async() => {

workspaces/scanner/test/fixtures/scannerPayloads/nullAuthor.json

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
{
22
"id": "YtK0Cx",
33
"rootDependencyName": "foo",
4-
"warnings": [
5-
"unsafe-import",
6-
"unsafe-regex"
7-
],
8-
"highlighted": {
9-
"contacts": [
10-
{
11-
"name": "sindresorhus",
12-
"email": "[email protected]"
13-
},
14-
{
15-
"name": "jack",
16-
"email": "[email protected]"
17-
}
18-
]
19-
},
4+
"warnings": [],
5+
"highlighted": {},
206
"vulnerabilityStrategy": "npm",
217
"scannerVersion": "1.0.0",
228
"dependencies": {

workspaces/scanner/test/fixtures/scannerPayloads/payload.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"id": "YuK0CL",
33
"rootDependencyName": "foo",
44
"warnings": [
5-
"unsafe-import",
6-
"unsafe-regex"
5+
{
6+
"type": "dangerous-dependency",
7+
"message": "..."
8+
}
79
],
810
"highlighted": {
911
"contacts": [

workspaces/scanner/test/fixtures/scannerPayloads/sameIdPayload.json

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
{
22
"id": "YuK0CL",
33
"rootDependencyName": "foo",
4-
"warnings": [
5-
"unsafe-import",
6-
"unsafe-regex"
7-
],
8-
"highlighted": {
9-
"contacts": [
10-
{
11-
"name": "sindresorhus",
12-
"email": "[email protected]"
13-
},
14-
{
15-
"name": "jack",
16-
"email": "[email protected]"
17-
}
18-
]
19-
},
4+
"warnings": [],
5+
"highlighted": {},
206
"vulnerabilityStrategy": "npm",
217
"scannerVersion": "1.0.0",
228
"dependencies": {

0 commit comments

Comments
 (0)