Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/calm-dingos-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nodesecure/tree-walker": major
"@nodesecure/scanner": major
---

feat(scanner): add manifest integrity of root dependency in payload
6 changes: 6 additions & 0 deletions .changeset/every-hairs-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nodesecure/tree-walker": major
"@nodesecure/scanner": major
---

feat(scanner): add manifest integrity of root dependency in payload
1 change: 1 addition & 0 deletions workspaces/scanner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"frequency-set": "^2.1.0",
"pacote": "^21.0.0",
"semver": "^7.5.4",
"ssri": "10.0.6",
"type-fest": "^5.0.1"
},
"devDependencies": {
Expand Down
9 changes: 8 additions & 1 deletion workspaces/scanner/src/depWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ManifestManager, parseNpmSpec } from "@nodesecure/mama";
import type { ManifestVersion, PackageJSON, WorkspacesPackageJSON } from "@nodesecure/npm-types";
import { getNpmRegistryURL } from "@nodesecure/npm-registry-sdk";
import type Config from "@npmcli/config";
import { fromData } from "ssri";

// Import Internal Dependencies
import {
Expand Down Expand Up @@ -72,6 +73,8 @@ const kDefaultDependencyMetadata: Dependency["metadata"] = {
integrity: {}
};

const kRootDependencyId = 0;

const { version: packageVersion } = JSON.parse(
readFileSync(
new URL(path.join("..", "package.json"), import.meta.url),
Expand Down Expand Up @@ -140,7 +143,7 @@ export async function depWalker(
packageLock
};
for await (const current of npmTreeWalker.walk(manifest, rootDepsOptions)) {
const { name, version, ...currentVersion } = current;
const { name, version, integrity, ...currentVersion } = current;
const dependency: Dependency = {
versions: {
[version]: {
Expand Down Expand Up @@ -176,6 +179,10 @@ export async function depWalker(
dependencies.set(name, dependency);
}

if (current.id === kRootDependencyId) {
payload.integrity = integrity ?? fromData(JSON.stringify(manifest), { algorithms: ["sha512"] }).toString();
}

// If the dependency is a DevDependencies we ignore it.
if (current.isDevDependency || !proceedDependencyScan) {
continue;
Expand Down
3 changes: 3 additions & 0 deletions workspaces/scanner/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ export interface Payload {
scannerVersion: string;
/** Vulnerability strategy name (npm, snyk, node) */
vulnerabilityStrategy: Vulnera.Kind;

/** The integrity of the scanned package */
integrity: string | null;
}

export interface Options {
Expand Down
9 changes: 7 additions & 2 deletions workspaces/scanner/test/depWalker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,10 @@ test("fetch payload of pacote on the npm registry", async() => {
"vulnerabilityStrategy",
"warnings",
"highlighted",
"dependencies"
"dependencies",
"integrity"
]);
assert.strictEqual(typeof result.integrity, "string");
});

test("fetch payload of pacote on the gitlab registry", async() => {
Expand All @@ -191,8 +193,10 @@ test("fetch payload of pacote on the gitlab registry", async() => {
"vulnerabilityStrategy",
"warnings",
"highlighted",
"dependencies"
"dependencies",
"integrity"
]);
assert.strictEqual(typeof result.integrity, "string");
});

test("highlight contacts from a remote package", async() => {
Expand Down Expand Up @@ -238,6 +242,7 @@ describe("scanner.cwd()", () => {
name: "NodeSecure"
});
assert.strictEqual(dep.metadata.homepage, "https://nodesecure.com");
assert.strictEqual(typeof result.integrity, "string");
});

test("should parse local manifest author field without throwing when attempting to highlight contacts", async() => {
Expand Down
5 changes: 4 additions & 1 deletion workspaces/tree-walker/src/Dependency.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface DependencyJSON {
alias: Record<string, string>;
dependencyCount: number;
gitUrl: string | null;
integrity: string | null;
}

export type DependencyOptions = {
Expand All @@ -34,6 +35,7 @@ export class Dependency {
public gitUrl: null | string = null;
public warnings: Warning[] = [];
public alias: Record<string, string> = {};
public integrity: string | null = null;

#flags = new Set<string>();
#parent: null | Dependency = null;
Expand Down Expand Up @@ -111,7 +113,8 @@ export class Dependency {
warnings: this.warnings,
dependencyCount: this.dependencyCount,
gitUrl: this.gitUrl,
alias: this.alias
alias: this.alias,
integrity: this.integrity
};
}
}
3 changes: 2 additions & 1 deletion workspaces/tree-walker/src/npm/walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,11 @@ export class TreeWalker {
);

try {
await this.providers.pacote.manifest(
const { _integrity: integrity } = await this.providers.pacote.manifest(
`${manifest.name}@${manifest.version}`,
this.registryOptions
);
rootDependency.integrity = integrity;
}
catch {
rootDependency.existOnRemoteRegistry = false;
Expand Down
3 changes: 2 additions & 1 deletion workspaces/tree-walker/test/Dependency.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ test("Dependency class should act as expected by assertions", () => {
assert.deepEqual(dep.warnings, []);
assert.deepEqual(dep.alias, {});
assert.strictEqual(dep.gitUrl, null);
assert.strictEqual(Reflect.ownKeys(dep).length, 8);
assert.strictEqual(dep.integrity, null);
assert.strictEqual(Reflect.ownKeys(dep).length, 9);

const flagOne = dep.flags;
const flagTwo = dep.flags;
Expand Down
11 changes: 7 additions & 4 deletions workspaces/tree-walker/test/npm/TreeWalker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe("npm.TreeWalker", () => {
test("Given a fixed '@nodesecure/fs-walk' manifest then it must extract one root dependency", async() => {
const spec = "@nodesecure/fs-walk@2.0.0";
const manifest = await pacote.manifest(spec) as pacote.AbbreviatedManifest;
const manifest = await pacote.manifest(spec);
const expectedIntegrity = manifest._integrity;

const walker = new npm.TreeWalker();

for await (const dependency of walker.walk(manifest)) {
for await (const dependency of walker.walk(manifest as pacote.AbbreviatedManifest)) {
assert.deepEqual(
dependency,
{
Expand All @@ -36,7 +37,8 @@ describe("npm.TreeWalker", () => {
warnings: [],
dependencyCount: 0,
gitUrl: null,
alias: {}
alias: {},
integrity: expectedIntegrity
}
);
}
Expand Down Expand Up @@ -73,7 +75,8 @@ describe("npm.TreeWalker", () => {
warnings: [],
dependencyCount: 0,
gitUrl: null,
alias: {}
alias: {},
integrity: null
}
);
}
Expand Down