1- /** @fileoverview CycloneDX cdxgen runner for Socket CLI. Executes cdxgen SBOM generator via npx. Converts yarn.lock to package-lock.json using synp for better accuracy. */
1+ /** @fileoverview CycloneDX cdxgen runner for Socket CLI. Executes cdxgen SBOM generator via npx. Converts yarn.lock to package-lock.json using synp for better accuracy. Applies Socket secure defaults for lifecycle and output. */
22
33import { existsSync } from 'node:fs'
44import path from 'node:path'
55
6+ import terminalLink from 'terminal-link'
67import colors from 'yoctocolors-cjs'
78
89import { removeSync } from '@socketsecurity/registry/lib/fs'
@@ -28,44 +29,28 @@ const nodejsPlatformTypes = new Set([
2829 'typescript' ,
2930] )
3031
31- export type ArgvObject = {
32- [ key : string ] : boolean | null | number | string | Array < string | number >
32+ function hasArg ( args : readonly string [ ] , ... flags : string [ ] ) : boolean {
33+ return flags . some ( flag => args . includes ( flag ) )
3334}
3435
35- function argvObjectToArray ( argvObj : ArgvObject ) : string [ ] {
36- if ( argvObj [ 'help' ] ) {
37- return [ FLAG_HELP ]
36+ function getArgValue (
37+ args : readonly string [ ] ,
38+ flag : string ,
39+ ) : string | undefined {
40+ const idx = args . indexOf ( flag )
41+ if ( idx !== - 1 && idx + 1 < args . length ) {
42+ return args [ idx + 1 ]
3843 }
39- const result = [ ]
40- for ( const { 0 : key , 1 : value } of Object . entries ( argvObj ) ) {
41- if ( key === '_' || key === '--' ) {
42- continue
43- }
44- if ( key === 'babel' || key === 'install-deps' || key === 'validate' ) {
45- // cdxgen documents no-babel, no-install-deps, and no-validate flags so
46- // use them when relevant.
47- result . push ( `--${ value ? key : `no-${ key } ` } ` )
48- } else if ( value === true ) {
49- result . push ( `--${ key } ` )
50- } else if ( typeof value === 'string' ) {
51- result . push ( `--${ key } ` , String ( value ) )
52- } else if ( Array . isArray ( value ) ) {
53- result . push ( `--${ key } ` , ...value . map ( String ) )
54- }
55- }
56- const pathArgs = argvObj [ '_' ] as string [ ]
57- if ( Array . isArray ( pathArgs ) ) {
58- result . push ( ...pathArgs )
59- }
60- const argsAfterDoubleHyphen = argvObj [ '--' ] as string [ ]
61- if ( Array . isArray ( argsAfterDoubleHyphen ) ) {
62- result . push ( '--' , ...argsAfterDoubleHyphen )
63- }
64- return result
44+ // Check for --flag=value format.
45+ const prefix = `${ flag } =`
46+ const arg = args . find ( a => a . startsWith ( prefix ) )
47+ return arg ? arg . slice ( prefix . length ) : undefined
6548}
6649
67- export async function runCdxgen ( argvObj : ArgvObject ) : Promise < ShadowBinResult > {
68- const argvMutable = { __proto__ : null , ...argvObj } as ArgvObject
50+ export async function runCdxgen (
51+ args : readonly string [ ] ,
52+ ) : Promise < ShadowBinResult > {
53+ const argsMutable = [ ...args ]
6954
7055 // Detect lockfiles for synp conversion.
7156 const npmLockPath = await findUp ( PACKAGE_LOCK_JSON , { onlyFiles : true } )
@@ -74,15 +59,16 @@ export async function runCdxgen(argvObj: ArgvObject): Promise<ShadowBinResult> {
7459 ? undefined
7560 : await findUp ( YARN_LOCK , { onlyFiles : true } )
7661
62+ const typeValue =
63+ getArgValue ( argsMutable , '--type' ) || getArgValue ( argsMutable , '-t' )
64+
7765 let cleanupPackageLock = false
7866 if (
7967 yarnLockPath &&
80- argvMutable [ 'type' ] !== YARN &&
81- nodejsPlatformTypes . has ( argvMutable [ 'type' ] as string )
68+ typeValue !== YARN &&
69+ ( ! typeValue || nodejsPlatformTypes . has ( typeValue ) )
8270 ) {
83- if ( npmLockPath ) {
84- argvMutable [ 'type' ] = 'npm'
85- } else {
71+ if ( ! npmLockPath ) {
8672 // Use synp to create a package-lock.json from the yarn.lock,
8773 // based on the node_modules folder, for a more accurate SBOM.
8874 try {
@@ -103,22 +89,39 @@ export async function runCdxgen(argvObj: ArgvObject): Promise<ShadowBinResult> {
10389 } ,
10490 )
10591
106- argvMutable [ 'type' ] = 'npm'
10792 cleanupPackageLock = true
10893 } catch { }
10994 }
11095 }
11196
97+ // Apply Socket secure defaults when not requesting help/version.
98+ const isHelpRequest = hasArg ( argsMutable , FLAG_HELP , '-h' , '--version' , '-v' )
99+ if ( ! isHelpRequest ) {
100+ // Set lifecycle to 'pre-build' to avoid arbitrary code execution.
101+ // https://github.com/CycloneDX/cdxgen/issues/1328
102+ if ( ! hasArg ( argsMutable , '--lifecycle' ) ) {
103+ argsMutable . push ( '--lifecycle' , 'pre-build' )
104+ argsMutable . push ( '--no-install-deps' )
105+ logger . info (
106+ `Setting cdxgen --lifecycle to "pre-build" to avoid arbitrary code execution on this scan.\n Pass "--lifecycle build" to generate a BOM consisting of information obtained during the build process.\n See cdxgen ${ terminalLink (
107+ 'BOM lifecycles documentation' ,
108+ 'https://cyclonedx.github.io/cdxgen/#/ADVANCED?id=bom-lifecycles' ,
109+ ) } for more details.\n`,
110+ )
111+ }
112+
113+ // Set default output filename.
114+ if ( ! hasArg ( argsMutable , '--output' , '-o' ) ) {
115+ argsMutable . push ( '--output' , 'socket-cdx.json' )
116+ }
117+ }
118+
112119 // Run cdxgen via npx.
113120 const cdxgenVersion =
114121 constants . ENV [ 'INLINED_SOCKET_CLI_CYCLONEDX_CDXGEN_VERSION' ]
115122 const cdxgenPackageSpec = `@cyclonedx/cdxgen@${ cdxgenVersion } `
116- const cdxgenArgs = argvObjectToArray ( argvMutable )
117-
118- // Check if this is a help/version request.
119- const isHelpRequest = argvMutable [ 'help' ] || argvMutable [ 'version' ]
120123
121- const result = await runShadowCommand ( cdxgenPackageSpec , cdxgenArgs , {
124+ const result = await runShadowCommand ( cdxgenPackageSpec , argsMutable , {
122125 ipc : {
123126 [ constants . SOCKET_CLI_SHADOW_ACCEPT_RISKS ] : true ,
124127 [ constants . SOCKET_CLI_SHADOW_API_TOKEN ] :
@@ -166,7 +169,8 @@ export async function runCdxgen(argvObj: ArgvObject): Promise<ShadowBinResult> {
166169 } catch { }
167170 }
168171
169- const outputPath = argvMutable [ 'output' ] as string
172+ const outputPath =
173+ getArgValue ( argsMutable , '--output' ) || getArgValue ( argsMutable , '-o' )
170174 if ( outputPath ) {
171175 const fullOutputPath = path . join ( process . cwd ( ) , outputPath )
172176 if ( existsSync ( fullOutputPath ) ) {
0 commit comments