1
1
// Import Node.js Dependencies
2
2
import path from "node:path" ;
3
- import os from "node:os" ;
4
3
5
4
// Import Third-party Dependencies
6
5
import {
7
- AstAnalyser ,
8
6
type Warning ,
9
7
type Dependency
10
8
} from "@nodesecure/js-x-ray" ;
11
- import pacote from "pacote" ;
12
9
import * as conformance from "@nodesecure/conformance" ;
13
- import { ManifestManager } from "@nodesecure/mama" ;
14
10
15
11
// Import Internal Dependencies
16
12
import {
17
- getTarballComposition ,
18
13
isSensitiveFile ,
19
14
analyzeDependencies ,
15
+ filterDependencyKind ,
20
16
booleanToFlags
21
17
} from "./utils/index.js" ;
18
+ import { TarballExtractor } from "./class/TarballExtractor.class.js" ;
22
19
import * as warnings from "./warnings.js" ;
23
- import * as sast from "./sast/index.js" ;
24
20
25
21
export interface DependencyRef {
26
22
id : number ;
@@ -53,12 +49,7 @@ export interface DependencyRef {
53
49
}
54
50
55
51
// CONSTANTS
56
- const NPM_TOKEN = typeof process . env . NODE_SECURE_TOKEN === "string" ?
57
- { token : process . env . NODE_SECURE_TOKEN } :
58
- { } ;
59
-
60
52
const kNativeCodeExtensions = new Set ( [ ".gyp" , ".c" , ".cpp" , ".node" , ".so" , ".h" ] ) ;
61
- const kJsExtname = new Set ( [ ".js" , ".mjs" , ".cjs" ] ) ;
62
53
63
54
export interface scanDirOrArchiveOptions {
64
55
ref : DependencyRef ;
@@ -73,33 +64,22 @@ export async function scanDirOrArchive(
73
64
options : scanDirOrArchiveOptions
74
65
) {
75
66
const { ref, location = process . cwd ( ) , tmpLocation = null , registry } = options ;
67
+ const spec = `${ name } @${ version } ` ;
76
68
77
- const isNpmTarball = ! ( tmpLocation === null ) ;
78
- const dest = isNpmTarball ? path . join ( tmpLocation , `${ name } @${ version } ` ) : location ;
79
-
80
- // If this is an NPM tarball then we extract it on the disk with pacote.
81
- if ( isNpmTarball ) {
82
- await pacote . extract (
83
- ref . flags . includes ( "isGit" ) ? ref . gitUrl ! : `${ name } @${ version } ` ,
84
- dest ,
85
- {
86
- ...NPM_TOKEN ,
87
- registry,
88
- cache : `${ os . homedir ( ) } /.npm`
89
- }
90
- ) ;
69
+ let tarex : TarballExtractor ;
70
+ if ( typeof tmpLocation === "string" ) {
71
+ const location = path . join ( tmpLocation , spec ) ;
72
+
73
+ tarex = ref . flags . includes ( "isGit" ) ?
74
+ await TarballExtractor . fromGit ( location , ref . gitUrl ! , { registry } ) :
75
+ await TarballExtractor . fromNpm ( location , spec , { registry } ) ;
91
76
}
77
+ else {
78
+ tarex = await TarballExtractor . fromFileSystem ( location ) ;
79
+ }
80
+ const mama = tarex . manifest ;
92
81
93
- // Read the package.json at the root of the directory or archive.
94
- const [
95
- mama ,
96
- composition ,
97
- spdx
98
- ] = await Promise . all ( [
99
- ManifestManager . fromPackageJSON ( dest ) ,
100
- getTarballComposition ( dest ) ,
101
- conformance . extractLicenses ( dest )
102
- ] ) ;
82
+ const { composition, spdx } = await tarex . scan ( ) ;
103
83
104
84
{
105
85
const { description, engines, repository, scripts } = mama . document ;
@@ -119,17 +99,34 @@ export async function scanDirOrArchive(
119
99
120
100
// Search for minified and runtime dependencies
121
101
// Run a JS-X-Ray analysis on each JavaScript files of the project!
122
- const scannedFiles = await sast . scanManyFiles ( composition . files , dest , name ) ;
102
+ const scannedFiles = await tarex . runJavaScriptSast (
103
+ composition . files . filter (
104
+ ( name ) => TarballExtractor . JS_EXTENSIONS . has ( path . extname ( name ) )
105
+ )
106
+ ) ;
123
107
124
- ref . warnings . push ( ...scannedFiles . flatMap ( ( row ) => row . warnings ) ) ;
125
- if ( / ^ 0 ( \. \d + ) * $ / . test ( version ) ) {
108
+ ref . warnings . push ( ...scannedFiles . warnings ) ;
109
+ if ( mama . hasZeroSemver ) {
126
110
ref . warnings . push ( warnings . getSemVerWarning ( version ) ) ;
127
111
}
128
112
129
- const dependencies = [ ...new Set ( scannedFiles . flatMap ( ( row ) => row . dependencies ) ) ] ;
130
- const filesDependencies = [ ...new Set ( scannedFiles . flatMap ( ( row ) => row . filesDependencies ) ) ] ;
131
- const tryDependencies = new Set ( scannedFiles . flatMap ( ( row ) => row . tryDependencies ) ) ;
132
- const minifiedFiles = scannedFiles . filter ( ( row ) => row . isMinified ) . flatMap ( ( row ) => row . file ) ;
113
+ const files = new Set < string > ( ) ;
114
+ const dependencies = new Set < string > ( ) ;
115
+ const dependenciesInTryBlock = new Set < string > ( ) ;
116
+
117
+ for ( const [ file , fileDeps ] of Object . entries ( scannedFiles . dependencies ) ) {
118
+ const filtered = filterDependencyKind (
119
+ [ ...Object . keys ( fileDeps ) ] ,
120
+ path . dirname ( file )
121
+ ) ;
122
+
123
+ [ ...Object . entries ( fileDeps ) ]
124
+ . flatMap ( ( [ name , dependency ] ) => ( dependency . inTry ? [ name ] : [ ] ) )
125
+ . forEach ( ( name ) => dependenciesInTryBlock . add ( name ) ) ;
126
+
127
+ filtered . packages . forEach ( ( name ) => dependencies . add ( name ) ) ;
128
+ filtered . files . forEach ( ( file ) => files . add ( file ) ) ;
129
+ }
133
130
134
131
const {
135
132
nodeDependencies,
@@ -139,8 +136,8 @@ export async function scanDirOrArchive(
139
136
unusedDependencies,
140
137
flags
141
138
} = analyzeDependencies (
142
- dependencies ,
143
- { mama, tryDependencies }
139
+ [ ... dependencies ] ,
140
+ { mama, tryDependencies : dependenciesInTryBlock }
144
141
) ;
145
142
146
143
ref . size = composition . size ;
@@ -150,15 +147,15 @@ export async function scanDirOrArchive(
150
147
ref . composition . required_subpath = subpathImportsDependencies ;
151
148
ref . composition . unused . push ( ...unusedDependencies ) ;
152
149
ref . composition . missing . push ( ...missingDependencies ) ;
153
- ref . composition . required_files = filesDependencies ;
150
+ ref . composition . required_files = [ ... files ] ;
154
151
ref . composition . required_nodejs = nodeDependencies ;
155
- ref . composition . minified = minifiedFiles ;
152
+ ref . composition . minified = scannedFiles . minified ;
156
153
157
154
ref . flags . push ( ...booleanToFlags ( {
158
155
...flags ,
159
156
hasNoLicense : spdx . uniqueLicenseIds . length === 0 ,
160
157
hasMultipleLicenses : spdx . uniqueLicenseIds . length > 1 ,
161
- hasMinifiedCode : minifiedFiles . length > 0 ,
158
+ hasMinifiedCode : scannedFiles . minified . length > 0 ,
162
159
hasWarnings : ref . warnings . length > 0 && ! ref . flags . includes ( "hasWarnings" ) ,
163
160
hasBannedFile : composition . files . some ( ( path ) => isSensitiveFile ( path ) ) ,
164
161
hasNativeCode : mama . flags . isNative ||
@@ -189,46 +186,24 @@ export interface ScannedPackageResult {
189
186
}
190
187
191
188
export async function scanPackage (
192
- dest : string ,
193
- packageName ?: string
189
+ dest : string
194
190
) : Promise < ScannedPackageResult > {
195
- const [
196
- mama ,
191
+ const extractor = await TarballExtractor . fromFileSystem ( dest ) ;
192
+
193
+ const {
197
194
composition,
198
195
spdx
199
- ] = await Promise . all ( [
200
- ManifestManager . fromPackageJSON ( dest ) ,
201
- getTarballComposition ( dest ) ,
202
- conformance . extractLicenses ( dest )
203
- ] ) ;
204
- const { type = "script" } = mama . document ;
205
-
206
- // Search for runtime dependencies
207
- const dependencies : Record < string , Record < string , Dependency > > = Object . create ( null ) ;
208
- const minified : string [ ] = [ ] ;
209
- const warnings : Warning [ ] = [ ] ;
210
-
211
- const JSFiles = composition . files
212
- . filter ( ( name ) => kJsExtname . has ( path . extname ( name ) ) ) ;
213
- for ( const file of JSFiles ) {
214
- const result = await new AstAnalyser ( ) . analyseFile (
215
- path . join ( dest , file ) ,
216
- {
217
- packageName : packageName ?? mama . document . name ,
218
- module : type === "module"
219
- }
220
- ) ;
196
+ } = await extractor . scan ( ) ;
221
197
222
- warnings . push (
223
- ...result . warnings . map ( ( curr ) => Object . assign ( { } , curr , { file } ) )
224
- ) ;
225
- if ( result . ok ) {
226
- dependencies [ file ] = Object . fromEntries ( result . dependencies ) ;
227
- if ( result . isMinified ) {
228
- minified . push ( file ) ;
229
- }
230
- }
231
- }
198
+ const {
199
+ dependencies,
200
+ warnings,
201
+ minified
202
+ } = await extractor . runJavaScriptSast (
203
+ composition . files . filter (
204
+ ( name ) => TarballExtractor . JS_EXTENSIONS . has ( path . extname ( name ) )
205
+ )
206
+ ) ;
232
207
233
208
return {
234
209
files : {
0 commit comments