Skip to content

Commit

Permalink
Merge pull request #115 from SocketDev/jdalton/sbom-stuff
Browse files Browse the repository at this point in the history
Add cyclonedx command
  • Loading branch information
jdalton authored Jun 17, 2024
2 parents 8ce1b8c + 60912a3 commit e412204
Show file tree
Hide file tree
Showing 24 changed files with 8,339 additions and 2,564 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ jobs:
with:
no-lockfile: true
npm-test-script: 'test-ci'
# Node 16 has a SegFault from C8 when using --test
node-versions: '18,19'
node-versions: '20'
# We currently have some issues on Windows that will have to wait to be fixed
# os: 'ubuntu-latest,windows-latest'
os: 'ubuntu-latest'
2 changes: 1 addition & 1 deletion .github/workflows/provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '20'
registry-url: 'https://registry.npmjs.org'
cache: npm
- run: npm install -g npm@latest
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ jobs:
uses: SocketDev/workflows/.github/workflows/type-check.yml@master
with:
no-lockfile: true
ts-versions: ${{ github.event.schedule && 'next' || '4.9,next' }}
ts-libs: 'es2020;esnext'
ts-versions: ${{ github.event.schedule && 'next' || '5.4,next' }}
ts-libs: 'esnext'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/coverage
/coverage-ts
/node_modules
/.DS_Store
/.env
/.nyc_output
/.vscode
Expand Down
211 changes: 211 additions & 0 deletions lib/commands/cyclonedx/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/* eslint-disable no-console */

import { existsSync, promises as fs } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import chalk from 'chalk'
import { $ } from 'execa'
import yargsParse from 'yargs-parser'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

const {
SBOM_SIGN_ALGORITHM, // Algorithm. Example: RS512
SBOM_SIGN_PRIVATE_KEY, // Location to the RSA private key
SBOM_SIGN_PUBLIC_KEY // Optional. Location to the RSA public key
} = process.env

const toLower = (/** @type {string} */ arg) => arg.toLowerCase()
const arrayToLower = (/** @type {string[]} */ arg) => arg.map(toLower)

const execaConfig = {
env: { NODE_ENV: '' },
localDir: path.join(__dirname, 'node_modules'),
}

const nodejsPlatformTypes = [
'javascript',
'js',
'nodejs',
'npm',
'pnpm',
'ts',
'tsx',
'typescript'
]

const yargsConfig = {
configuration: {
'camel-case-expansion': false,
'strip-aliased': true,
'parse-numbers': false,
'populate--': true,
'unknown-options-as-args': true
},
coerce: {
author: arrayToLower,
filter: arrayToLower,
only: arrayToLower,
profile: toLower,
standard: arrayToLower,
type: toLower
},
default: {
//author: ['OWASP Foundation'],
//'auto-compositions': true,
//babel: true,
//evidence: false,
//'include-crypto': false,
//'include-formulation': false,
//'install-deps': true,
//output: 'bom.json',
//profile: 'generic',
//'project-version': '',
//recurse: true,
//'server-host': '127.0.0.1',
//'server-port': '9090',
//'spec-version': '1.5',
type: 'js',
//validate: true,
},
alias: {
help: ['h'],
output: ['o'],
print: ['p'],
recurse: ['r'],
'resolve-class': ['c'],
type: ['t'],
version: ['v'],
},
array: [
{ key: 'author', type: 'string' },
{ key: 'exclude', type: 'string' },
{ key: 'filter', type: 'string' },
{ key: 'only', type: 'string' },
{ key: 'standard', type: 'string' }
],
boolean: [
'auto-compositions',
'babel',
'deep',
'evidence',
'fail-on-error',
'generate-key-and-sign',
'help',
'include-formulation',
'include-crypto',
'install-deps',
'print',
'required-only',
'server',
'validate',
'version',
],
string: [
'api-key',
'output',
'parent-project-id',
'profile',
'project-group',
'project-name',
'project-version',
'project-id',
'server-host',
'server-port',
'server-url',
'spec-version',
]
}

/**
*
* @param {{ [key: string]: boolean | null | number | string | (string | number)[]}} argv
* @returns {string[]}
*/
function argvToArray (/** @type {any} */ argv) {
if (argv['help']) return ['--help']
const result = []
for (const { 0: key, 1: value } of Object.entries(argv)) {
if (key === '_' || key === '--') continue
if (key === 'babel' || key === 'install-deps' || key === 'validate') {
// cdxgen documents no-babel, no-install-deps, and no-validate flags so
// use them when relevant.
result.push(`--${value ? key : `no-${key}`}`)
} else if (value === true) {
result.push(`--${key}`)
} else if (typeof value === 'string') {
result.push(`--${key}=${value}`)
} else if (Array.isArray(value)) {
result.push(`--${key}`, ...value.map(String))
}
}
if (argv['--']) {
result.push('--', ...argv['--'])
}
return result
}

/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const cyclonedx = {
description: 'Create an SBOM with CycloneDX generator (cdxgen)',
async run (argv_) {
const /** @type {any} */ yargv = {
__proto__: null,
// @ts-ignore
...yargsParse(argv_, yargsConfig)
}

const /** @type {string[]} */ unknown = yargv._
const { length: unknownLength } = unknown
if (unknownLength) {
console.error(`Unknown argument${unknownLength > 1 ? 's' : ''}: ${yargv._.join(', ')}`)
process.exitCode = 1
return
}

let cleanupPackageLock = false
if (
yargv.type !== 'yarn' &&
nodejsPlatformTypes.includes(yargv.type) &&
existsSync('./yarn.lock')
) {
if (existsSync('./package-lock.json')) {
yargv.type = 'npm'
} else {
// Use synp to create a package-lock.json from the yarn.lock,
// based on the node_modules folder, for a more accurate SBOM.
try {
await $(execaConfig)`synp --source-file ./yarn.lock`
yargv.type = 'npm'
cleanupPackageLock = true
} catch {}
}
}

if (yargv.output === undefined) {
yargv.output = 'socket-cyclonedx.json'
}

await $({
...execaConfig,
env: {
NODE_ENV: '',
SBOM_SIGN_ALGORITHM,
SBOM_SIGN_PRIVATE_KEY,
SBOM_SIGN_PUBLIC_KEY
},
stdout: 'inherit'
})`cdxgen ${argvToArray(yargv)}`

if (cleanupPackageLock) {
try {
await fs.unlink('./package-lock.json')
} catch {}
}
const fullOutputPath = path.join(process.cwd(), yargv.output)
if (existsSync(fullOutputPath)) {
console.log(chalk.cyanBright(`${yargv.output} created!`))
}
}
}
9 changes: 5 additions & 4 deletions lib/commands/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export * from './cyclonedx/index.js'
export * from './info/index.js'
export * from './report/index.js'
export * from './npm/index.js'
export * from './npx/index.js'
export * from './login/index.js'
export * from './logout/index.js'
export * from './wrapper/index.js'
export * from './npm/index.js'
export * from './npx/index.js'
export * from './raw-npm/index.js'
export * from './raw-npx/index.js'
export * from './report/index.js'
export * from './wrapper/index.js'
4 changes: 2 additions & 2 deletions lib/commands/info/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { printFlagList } from '../../utils/formatting.js'
import { objectSome } from '../../utils/misc.js'
import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const info = {
description: 'Look up info regarding a package',
async run (argv, importMeta, { parentName }) {
Expand Down Expand Up @@ -109,7 +109,7 @@ function setupCommand (name, description, argv, importMeta) {
/**
* @typedef PackageData
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data
* @property {Record<import('../../utils/format-issues').SocketIssue['severity'], number>} severityCount
* @property {Record<import('../../utils/format-issues.js').SocketIssue['severity'], number>} severityCount
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getScoreByNPMPackage'>["data"]} score
*/

Expand Down
2 changes: 1 addition & 1 deletion lib/commands/login/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getSetting, updateSetting } from '../../utils/settings.js'

const description = 'Socket API login'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const login = {
description,
run: async (argv, importMeta, { parentName }) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/logout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { updateSetting } from '../../utils/settings.js'

const description = 'Socket API logout'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const logout = {
description,
run: async (argv, importMeta, { parentName }) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/npm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fileURLToPath } from 'url'

const description = 'npm wrapper functionality'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const npm = {
description,
run: async (argv, _importMeta, _ctx) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/npx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fileURLToPath } from 'url'

const description = 'npx wrapper functionality'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const npx = {
description,
run: async (argv, _importMeta, _ctx) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/report/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { createDebugLogger } from '../../utils/misc.js'
import { getPackageFiles } from '../../utils/path-resolve.js'
import { setupSdk } from '../../utils/sdk.js'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const create = {
description: 'Create a project report',
async run (argv, importMeta, { parentName }) {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/report/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'

const description = 'Project report related commands'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const report = {
description,
run: async (argv, importMeta, { parentName }) => {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/report/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getSeverityCount, formatSeverityCount } from '../../utils/format-issues
import { printFlagList } from '../../utils/formatting.js'
import { setupSdk } from '../../utils/sdk.js'

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const view = {
description: 'View a project report',
async run (argv, importMeta, { parentName }) {
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/wrapper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { printFlagList } from '../../utils/formatting.js'
const BASH_FILE = `${homedir.homedir()}/.bashrc`
const ZSH_BASH_FILE = `${homedir.homedir()}/.zshrc`

/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
/** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
export const wrapper = {
description: 'Enable or disable the Socket npm/npx wrapper',
async run (argv, importMeta, { parentName }) {
Expand Down
3 changes: 1 addition & 2 deletions lib/utils/meow-with-subcommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import { printFlagList, printHelpList } from './formatting.js'
*/

/**
* @template {import('meow').AnyFlags} Flags
* @param {Record<string, CliSubcommand>} subcommands
* @param {import('meow').Options<Flags> & { aliases?: CliAliases, argv: readonly string[], name: string }} options
* @param {import('meow').Options<any> & { aliases?: CliAliases, argv: readonly string[], name: string }} options
* @returns {Promise<void>}
*/
export async function meowWithSubcommands (subcommands, options) {
Expand Down
1 change: 0 additions & 1 deletion lib/utils/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export function stringJoinWithSeparateFinalSeparator (list, separator = ' and ')

/**
* Returns a new object with only the specified keys from the input object
*
* @template {Record<string,any>} T
* @template {keyof T} K
* @param {T} input
Expand Down
Loading

0 comments on commit e412204

Please sign in to comment.