Skip to content

Commit 45eb26a

Browse files
authored
feat(tarball): exclude files and glob patterns from NpmTarball.scanFiles (#646)
1 parent a8a6e9c commit 45eb26a

9 files changed

Lines changed: 103 additions & 12 deletions

File tree

.changeset/cyan-hats-obey.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nodesecure/tarball": minor
3+
---
4+
5+
Allow to exclude files and patterns in NpmTarball scanFiles function

workspaces/tarball/docs/NpmTarball.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,26 @@ Create a new NpmTarball instance.
2727
> [!CAUTION]
2828
> ManifestManager instance must have a location defined
2929
30-
### scanFiles(): Promise< ScannedFilesResult >
30+
### scanFiles(astAnalyserOptions?: AstAnalyserOptions, options?: NpmTarballScanFilesOptions): Promise< ScannedFilesResult >
3131

3232
Scan all the files contained in the tarball and obtain a complete report, including detection of JavaScript threats.
3333

34+
```ts
35+
interface NpmTarballScanFilesOptions {
36+
/**
37+
* List of files and directories to exclude from the scan.
38+
* Support glob patterns (e.g., "node_modules/**", "dist/**")
39+
*/
40+
exclude?: string[];
41+
}
42+
```
43+
44+
The function return the following object as response:
45+
3446
```ts
3547
interface ScannedFilesResult {
3648
composition: TarballComposition;
37-
conformance: SpdxExtractedResult;
49+
conformance: conformance.SpdxExtractedResult;
3850
code: SourceCodeReport;
3951
}
4052
```

workspaces/tarball/src/class/NpmTarball.class.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
AstAnalyser,
1212
DefaultCollectableSet,
1313
warnings,
14+
TsSourceParser,
1415
type AstAnalyserOptions
1516
} from "@nodesecure/js-x-ray";
1617

@@ -31,6 +32,14 @@ export interface ScannedFilesResult {
3132
code: SourceCodeReport;
3233
}
3334

35+
export interface NpmTarballScanFilesOptions {
36+
/**
37+
* List of files and directories to exclude from the scan.
38+
* Support glob patterns (e.g., "node_modules/**", "dist/**")
39+
*/
40+
exclude?: string[];
41+
}
42+
3443
export type NpmTarballOptions = {
3544
resolver?: Resolver;
3645
};
@@ -58,8 +67,11 @@ export class NpmTarball {
5867
}
5968

6069
async scanFiles(
61-
astAnalyserOptions?: AstAnalyserOptions
70+
astAnalyserOptions?: AstAnalyserOptions,
71+
options: NpmTarballScanFilesOptions = {}
6272
): Promise<ScannedFilesResult> {
73+
const { exclude = [] } = options;
74+
6375
const location = this.manifest.location;
6476
const [
6577
composition,
@@ -74,17 +86,21 @@ export class NpmTarball {
7486
code = new SourceCodeReport();
7587
}
7688
else {
77-
const options = this.#optionsWithHostnameSet(astAnalyserOptions ?? {});
89+
const options = this.#optionsWithHostnameSet(
90+
astAnalyserOptions ?? {}
91+
);
7892

79-
const hostNameSet = options?.collectables?.find((collectable) => collectable.type === "hostname")!;
93+
const hostNameSet = options?.collectables?.find(
94+
(collectable) => collectable.type === "hostname"
95+
)!;
8096

8197
const astAnalyser = new AstAnalyser(options);
8298

8399
code = await new SourceCodeScanner(this.manifest, { astAnalyser }).iterate({
84100
manifest: [...this.manifest.getEntryFiles()]
85-
.flatMap(filterJavaScriptFiles()),
101+
.flatMap(filterJavaScriptFiles(exclude)),
86102
javascript: composition.files
87-
.flatMap(filterJavaScriptFiles())
103+
.flatMap(filterJavaScriptFiles(exclude))
88104
});
89105

90106
if (hostNameSet instanceof DefaultCollectableSet) {
@@ -117,19 +133,46 @@ export class NpmTarball {
117133
};
118134
}
119135

120-
#optionsWithHostnameSet(options: AstAnalyserOptions): AstAnalyserOptions {
121-
const hasHostnameSet = options?.collectables?.some((collectable) => collectable.type === "hostname");
136+
#optionsWithHostnameSet(
137+
options: AstAnalyserOptions
138+
): AstAnalyserOptions {
139+
const hasHostnameSet = options?.collectables?.some(
140+
(collectable) => collectable.type === "hostname"
141+
);
122142
if (hasHostnameSet) {
123143
return options;
124144
}
125145

126-
return { ...options, collectables: [...options.collectables ?? [], new DefaultCollectableSet("hostname")] };
146+
return {
147+
...options,
148+
collectables: [
149+
...options.collectables ?? [],
150+
new DefaultCollectableSet("hostname")
151+
]
152+
};
127153
}
128154
}
129155

130-
function filterJavaScriptFiles() {
156+
function filterJavaScriptFiles(
157+
exclude: string[] = []
158+
) {
131159
return (file: string) => {
132-
if (NpmTarball.JS_EXTENSIONS.has(path.extname(file))) {
160+
// Exclude .d.ts files
161+
if (file.includes("d.ts")) {
162+
return [];
163+
}
164+
165+
// Exclude files matching any glob pattern
166+
if (exclude.some((pattern) => path.matchesGlob(file, pattern))) {
167+
return [];
168+
}
169+
170+
const fileExt = path.extname(file);
171+
172+
if (NpmTarball.JS_EXTENSIONS.has(fileExt)) {
173+
return file;
174+
}
175+
if (TsSourceParser.FileExtensions.has(fileExt)) {
133176
return file;
134177
}
135178

workspaces/tarball/test/NpmTarball.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ type Metadata = {
2323
};
2424

2525
describe("NpmTarball", () => {
26+
test("it should exclude files matching glob patterns via the exclude option", async() => {
27+
const fixturePath = path.join(__dirname, "fixtures", "exclude-test");
28+
const mama = await ManifestManager.fromPackageJSON(
29+
path.join(fixturePath, "package.json")
30+
);
31+
const npmTarball = new NpmTarball(mama);
32+
33+
const exclude: string[] = [
34+
"ignoreme.js",
35+
"subdir/**"
36+
];
37+
const result = await npmTarball.scanFiles(
38+
undefined,
39+
{ exclude }
40+
);
41+
42+
assert.ok(result.code.warnings.length === 0);
43+
});
44+
2645
test("it should have a shady-link warning when a hostname resolve a private ip address with collectables", async() => {
2746
const mama = await ManifestManager.fromPackageJSON(path.join(kFixturePath, "shady-link", "package.json"));
2847
const npmTarball = new NpmTarball(mama);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('ignoreme.js should be excluded');
2+
const privateIp = "https://10.0.0.1.sslip.io/path";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('index.js included');
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "exclude-test",
3+
"version": "1.0.0"
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
console.log('shouldBeIgnored.js should be excluded');
2+
const privateIp = "https://10.0.0.1.sslip.io/path";
3+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log('shouldBeIncluded.js should be included');
2+
const privateIp = "https://10.0.0.1.sslip.io/path";

0 commit comments

Comments
 (0)