@socketsecurity/cli 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -20,6 +20,8 @@ try {
20
20
  entry[0] = 'raw-npm'
21
21
  } else if (entry[0] === 'rawNpx') {
22
22
  entry[0] = 'raw-npx'
23
+ } else if (entry[0] === 'auditlog') {
24
+ entry[0] = 'audit-log'
23
25
  }
24
26
  return entry
25
27
  }))
@@ -0,0 +1,162 @@
1
+ /* eslint-disable no-console */
2
+ import { Separator } from '@inquirer/select'
3
+ import chalk from 'chalk'
4
+ import inquirer from 'inquirer'
5
+ import meow from 'meow'
6
+ import ora from 'ora'
7
+
8
+ import { outputFlags } from '../../flags/index.js'
9
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
10
+ import { prepareFlags } from '../../utils/flags.js'
11
+ import { printFlagList } from '../../utils/formatting.js'
12
+ import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'
13
+
14
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
15
+ export const auditlog = {
16
+ description: 'Look up the audit log for an organization',
17
+ async run (argv, importMeta, { parentName }) {
18
+ const name = parentName + ' audit-log'
19
+
20
+ const input = setupCommand(name, auditlog.description, argv, importMeta)
21
+ if (input) {
22
+ const spinner = ora(`Looking up audit log for ${input.orgSlug}\n`).start()
23
+ await fetchOrgAuditLog(input.orgSlug, input, spinner)
24
+ }
25
+ }
26
+ }
27
+
28
+ const auditLogFlags = prepareFlags({
29
+ type: {
30
+ type: 'string',
31
+ shortFlag: 't',
32
+ default: '',
33
+ description: 'Type of log event',
34
+ },
35
+ perPage: {
36
+ type: 'number',
37
+ shortFlag: 'pp',
38
+ default: 30,
39
+ description: 'Results per page - default is 30',
40
+ },
41
+ page: {
42
+ type: 'number',
43
+ shortFlag: 'p',
44
+ default: 1,
45
+ description: 'Page number - default is 1',
46
+ }
47
+ })
48
+
49
+ // Internal functions
50
+
51
+ /**
52
+ * @typedef CommandInput
53
+ * @property {boolean} outputJson
54
+ * @property {boolean} outputMarkdown
55
+ * @property {string} orgSlug
56
+ * @property {string} type
57
+ * @property {number} page
58
+ * @property {number} per_page
59
+ */
60
+
61
+ /**
62
+ * @param {string} name
63
+ * @param {string} description
64
+ * @param {readonly string[]} argv
65
+ * @param {ImportMeta} importMeta
66
+ * @returns {void|CommandInput}
67
+ */
68
+ function setupCommand (name, description, argv, importMeta) {
69
+ const flags = {
70
+ ...auditLogFlags,
71
+ ...outputFlags
72
+ }
73
+
74
+ const cli = meow(`
75
+ Usage
76
+ $ ${name} <org slug>
77
+
78
+ Options
79
+ ${printFlagList(flags, 6)}
80
+
81
+ Examples
82
+ $ ${name} FakeOrg
83
+ `, {
84
+ argv,
85
+ description,
86
+ importMeta,
87
+ flags
88
+ })
89
+
90
+ const {
91
+ json: outputJson,
92
+ markdown: outputMarkdown,
93
+ type,
94
+ page,
95
+ perPage
96
+ } = cli.flags
97
+
98
+ if (cli.input.length < 1) {
99
+ console.error(`${chalk.bgRed('Input error')}: Please provide an organization slug \n`)
100
+ cli.showHelp()
101
+ return
102
+ }
103
+ const [orgSlug = ''] = cli.input
104
+
105
+ return {
106
+ outputJson,
107
+ outputMarkdown,
108
+ orgSlug,
109
+ type: type && type.charAt(0).toUpperCase() + type.slice(1),
110
+ page,
111
+ per_page: perPage
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @typedef AuditLogData
117
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getAuditLogEvents'>["data"]} data
118
+ */
119
+
120
+ /**
121
+ * @param {string} orgSlug
122
+ * @param {CommandInput} input
123
+ * @param {import('ora').Ora} spinner
124
+ * @returns {Promise<void|AuditLogData>}
125
+ */
126
+ async function fetchOrgAuditLog (orgSlug, input, spinner) {
127
+ const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
128
+ const result = await handleApiCall(socketSdk.getAuditLogEvents(orgSlug, input), `Looking up audit log for ${orgSlug}\n`)
129
+
130
+ if (!result.success) {
131
+ return handleUnsuccessfulApiResponse('getAuditLogEvents', result, spinner)
132
+ }
133
+ spinner.stop()
134
+
135
+ const /** @type {({name: string} | Separator)[]} */ data = []
136
+ const /** @type {{[key: string]: string}} */ logDetails = {}
137
+
138
+ result.data.results.map(d => {
139
+ data.push({
140
+ name: `${d.created_at && new Date(d.created_at).toLocaleDateString('en-us', { year: 'numeric', month: 'numeric', day: 'numeric' })} - ${d.user_email} - ${d.type} - ${d.ip_address} - ${d.user_agent}`
141
+ }, new Separator())
142
+
143
+ logDetails[`${d.created_at && new Date(d.created_at).toLocaleDateString('en-us', { year: 'numeric', month: 'numeric', day: 'numeric' })} - ${d.user_email} - ${d.type} - ${d.ip_address} - ${d.user_agent}`] = JSON.stringify(d.payload)
144
+ return data
145
+ })
146
+
147
+ inquirer
148
+ .prompt(
149
+ {
150
+ type: 'list',
151
+ name: 'log',
152
+ message: input.type ? `\n Audit log for: ${orgSlug} with type: ${input.type} \n` : `\n Audit log for: ${orgSlug} \n`,
153
+ choices: data,
154
+ pageSize: 30
155
+ }
156
+ )
157
+ .then((/** @type {{log: string}} */ answers) => console.log(logDetails[answers.log]))
158
+
159
+ return {
160
+ data: result.data
161
+ }
162
+ }
@@ -0,0 +1,211 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { existsSync, promises as fs } from 'node:fs'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import chalk from 'chalk'
8
+ import { $ } from 'execa'
9
+ import yargsParse from 'yargs-parser'
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
12
+
13
+ const {
14
+ SBOM_SIGN_ALGORITHM, // Algorithm. Example: RS512
15
+ SBOM_SIGN_PRIVATE_KEY, // Location to the RSA private key
16
+ SBOM_SIGN_PUBLIC_KEY // Optional. Location to the RSA public key
17
+ } = process.env
18
+
19
+ const toLower = (/** @type {string} */ arg) => arg.toLowerCase()
20
+ const arrayToLower = (/** @type {string[]} */ arg) => arg.map(toLower)
21
+
22
+ const execaConfig = {
23
+ env: { NODE_ENV: '' },
24
+ localDir: path.join(__dirname, 'node_modules'),
25
+ }
26
+
27
+ const nodejsPlatformTypes = [
28
+ 'javascript',
29
+ 'js',
30
+ 'nodejs',
31
+ 'npm',
32
+ 'pnpm',
33
+ 'ts',
34
+ 'tsx',
35
+ 'typescript'
36
+ ]
37
+
38
+ const yargsConfig = {
39
+ configuration: {
40
+ 'camel-case-expansion': false,
41
+ 'strip-aliased': true,
42
+ 'parse-numbers': false,
43
+ 'populate--': true,
44
+ 'unknown-options-as-args': true
45
+ },
46
+ coerce: {
47
+ author: arrayToLower,
48
+ filter: arrayToLower,
49
+ only: arrayToLower,
50
+ profile: toLower,
51
+ standard: arrayToLower,
52
+ type: toLower
53
+ },
54
+ default: {
55
+ //author: ['OWASP Foundation'],
56
+ //'auto-compositions': true,
57
+ //babel: true,
58
+ //evidence: false,
59
+ //'include-crypto': false,
60
+ //'include-formulation': false,
61
+ //'install-deps': true,
62
+ //output: 'bom.json',
63
+ //profile: 'generic',
64
+ //'project-version': '',
65
+ //recurse: true,
66
+ //'server-host': '127.0.0.1',
67
+ //'server-port': '9090',
68
+ //'spec-version': '1.5',
69
+ type: 'js',
70
+ //validate: true,
71
+ },
72
+ alias: {
73
+ help: ['h'],
74
+ output: ['o'],
75
+ print: ['p'],
76
+ recurse: ['r'],
77
+ 'resolve-class': ['c'],
78
+ type: ['t'],
79
+ version: ['v'],
80
+ },
81
+ array: [
82
+ { key: 'author', type: 'string' },
83
+ { key: 'exclude', type: 'string' },
84
+ { key: 'filter', type: 'string' },
85
+ { key: 'only', type: 'string' },
86
+ { key: 'standard', type: 'string' }
87
+ ],
88
+ boolean: [
89
+ 'auto-compositions',
90
+ 'babel',
91
+ 'deep',
92
+ 'evidence',
93
+ 'fail-on-error',
94
+ 'generate-key-and-sign',
95
+ 'help',
96
+ 'include-formulation',
97
+ 'include-crypto',
98
+ 'install-deps',
99
+ 'print',
100
+ 'required-only',
101
+ 'server',
102
+ 'validate',
103
+ 'version',
104
+ ],
105
+ string: [
106
+ 'api-key',
107
+ 'output',
108
+ 'parent-project-id',
109
+ 'profile',
110
+ 'project-group',
111
+ 'project-name',
112
+ 'project-version',
113
+ 'project-id',
114
+ 'server-host',
115
+ 'server-port',
116
+ 'server-url',
117
+ 'spec-version',
118
+ ]
119
+ }
120
+
121
+ /**
122
+ *
123
+ * @param {{ [key: string]: boolean | null | number | string | (string | number)[]}} argv
124
+ * @returns {string[]}
125
+ */
126
+ function argvToArray (/** @type {any} */ argv) {
127
+ if (argv['help']) return ['--help']
128
+ const result = []
129
+ for (const { 0: key, 1: value } of Object.entries(argv)) {
130
+ if (key === '_' || key === '--') continue
131
+ if (key === 'babel' || key === 'install-deps' || key === 'validate') {
132
+ // cdxgen documents no-babel, no-install-deps, and no-validate flags so
133
+ // use them when relevant.
134
+ result.push(`--${value ? key : `no-${key}`}`)
135
+ } else if (value === true) {
136
+ result.push(`--${key}`)
137
+ } else if (typeof value === 'string') {
138
+ result.push(`--${key}=${value}`)
139
+ } else if (Array.isArray(value)) {
140
+ result.push(`--${key}`, ...value.map(String))
141
+ }
142
+ }
143
+ if (argv['--']) {
144
+ result.push('--', ...argv['--'])
145
+ }
146
+ return result
147
+ }
148
+
149
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
150
+ export const cdxgen = {
151
+ description: 'Create an SBOM with CycloneDX generator (cdxgen)',
152
+ async run (argv_) {
153
+ const /** @type {any} */ yargv = {
154
+ __proto__: null,
155
+ // @ts-ignore
156
+ ...yargsParse(argv_, yargsConfig)
157
+ }
158
+
159
+ const /** @type {string[]} */ unknown = yargv._
160
+ const { length: unknownLength } = unknown
161
+ if (unknownLength) {
162
+ console.error(`Unknown argument${unknownLength > 1 ? 's' : ''}: ${yargv._.join(', ')}`)
163
+ process.exitCode = 1
164
+ return
165
+ }
166
+
167
+ let cleanupPackageLock = false
168
+ if (
169
+ yargv.type !== 'yarn' &&
170
+ nodejsPlatformTypes.includes(yargv.type) &&
171
+ existsSync('./yarn.lock')
172
+ ) {
173
+ if (existsSync('./package-lock.json')) {
174
+ yargv.type = 'npm'
175
+ } else {
176
+ // Use synp to create a package-lock.json from the yarn.lock,
177
+ // based on the node_modules folder, for a more accurate SBOM.
178
+ try {
179
+ await $(execaConfig)`synp --source-file ./yarn.lock`
180
+ yargv.type = 'npm'
181
+ cleanupPackageLock = true
182
+ } catch {}
183
+ }
184
+ }
185
+
186
+ if (yargv.output === undefined) {
187
+ yargv.output = 'socket-cdx.json'
188
+ }
189
+
190
+ await $({
191
+ ...execaConfig,
192
+ env: {
193
+ NODE_ENV: '',
194
+ SBOM_SIGN_ALGORITHM,
195
+ SBOM_SIGN_PRIVATE_KEY,
196
+ SBOM_SIGN_PUBLIC_KEY
197
+ },
198
+ stdout: 'inherit'
199
+ })`cdxgen ${argvToArray(yargv)}`
200
+
201
+ if (cleanupPackageLock) {
202
+ try {
203
+ await fs.unlink('./package-lock.json')
204
+ } catch {}
205
+ }
206
+ const fullOutputPath = path.join(process.cwd(), yargv.output)
207
+ if (existsSync(fullOutputPath)) {
208
+ console.log(chalk.cyanBright(`${yargv.output} created!`))
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,150 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ // @ts-ignore
5
+ import chalkTable from 'chalk-table'
6
+ import meow from 'meow'
7
+ import ora from 'ora'
8
+
9
+ import { outputFlags } from '../../flags/index.js'
10
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
11
+ import { prepareFlags } from '../../utils/flags.js'
12
+ import { printFlagList } from '../../utils/formatting.js'
13
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
14
+
15
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
16
+ export const dependencies = {
17
+ description: 'Search for any dependency that is being used in your organization',
18
+ async run (argv, importMeta, { parentName }) {
19
+ const name = parentName + ' dependencies'
20
+
21
+ const input = setupCommand(name, dependencies.description, argv, importMeta)
22
+ if (input) {
23
+ const spinnerText = 'Searching dependencies...'
24
+ const spinner = ora(spinnerText).start()
25
+ await searchDeps(input, spinner)
26
+ }
27
+ }
28
+ }
29
+
30
+ const dependenciesFlags = prepareFlags({
31
+ limit: {
32
+ type: 'number',
33
+ shortFlag: 'l',
34
+ default: 50,
35
+ description: 'Maximum number of dependencies returned',
36
+ },
37
+ offset: {
38
+ type: 'number',
39
+ shortFlag: 'o',
40
+ default: 0,
41
+ description: 'Page number',
42
+ }
43
+ })
44
+
45
+ // Internal functions
46
+
47
+ /**
48
+ * @typedef Command
49
+ * @property {boolean} outputJson
50
+ * @property {boolean} outputMarkdown
51
+ * @property {number} limit
52
+ * @property {number} offset
53
+ */
54
+
55
+ /**
56
+ * @param {string} name
57
+ * @param {string} description
58
+ * @param {readonly string[]} argv
59
+ * @param {ImportMeta} importMeta
60
+ * @returns {void|Command}
61
+ */
62
+ function setupCommand (name, description, argv, importMeta) {
63
+ const flags = {
64
+ ...outputFlags,
65
+ ...dependenciesFlags
66
+ }
67
+
68
+ const cli = meow(`
69
+ Usage
70
+ $ ${name}
71
+
72
+ Options
73
+ ${printFlagList(flags, 6)}
74
+
75
+ Examples
76
+ $ ${name}
77
+ `, {
78
+ argv,
79
+ description,
80
+ importMeta,
81
+ flags
82
+ })
83
+
84
+ const {
85
+ json: outputJson,
86
+ markdown: outputMarkdown,
87
+ limit,
88
+ offset
89
+ } = cli.flags
90
+
91
+ return {
92
+ outputJson,
93
+ outputMarkdown,
94
+ limit,
95
+ offset
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @typedef DependenciesData
101
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'searchDependencies'>["data"]} data
102
+ */
103
+
104
+ /**
105
+ * @param {Command} input
106
+ * @param {import('ora').Ora} spinner
107
+ * @returns {Promise<void|DependenciesData>}
108
+ */
109
+ async function searchDeps ({ limit, offset, outputJson }, spinner) {
110
+ const socketSdk = await setupSdk(getDefaultKey())
111
+ const result = await handleApiCall(socketSdk.searchDependencies({ limit, offset }), 'Searching dependencies')
112
+
113
+ if (!result.success) {
114
+ return handleUnsuccessfulApiResponse('searchDependencies', result, spinner)
115
+ }
116
+
117
+ spinner.stop()
118
+
119
+ console.log('Organization dependencies: \n')
120
+
121
+ if (outputJson) {
122
+ return console.log(result.data)
123
+ }
124
+
125
+ const options = {
126
+ columns: [
127
+ { field: 'namespace', name: chalk.cyan('Namespace') },
128
+ { field: 'name', name: chalk.cyan('Name') },
129
+ { field: 'version', name: chalk.cyan('Version') },
130
+ { field: 'repository', name: chalk.cyan('Repository') },
131
+ { field: 'branch', name: chalk.cyan('Branch') },
132
+ { field: 'type', name: chalk.cyan('Type') },
133
+ { field: 'direct', name: chalk.cyan('Direct') }
134
+ ]
135
+ }
136
+
137
+ const formattedResults = result.data.rows.map((/** @type {{[key:string]: any}} */ d) => {
138
+ return {
139
+ ...d
140
+ }
141
+ })
142
+
143
+ const table = chalkTable(options, formattedResults)
144
+
145
+ console.log(table, '\n')
146
+
147
+ return {
148
+ data: result.data
149
+ }
150
+ }
@@ -1,9 +1,14 @@
1
+ export * from './cdxgen/index.js'
1
2
  export * from './info/index.js'
2
- export * from './report/index.js'
3
- export * from './npm/index.js'
4
- export * from './npx/index.js'
5
3
  export * from './login/index.js'
6
4
  export * from './logout/index.js'
7
- export * from './wrapper/index.js'
5
+ export * from './npm/index.js'
6
+ export * from './npx/index.js'
8
7
  export * from './raw-npm/index.js'
9
8
  export * from './raw-npx/index.js'
9
+ export * from './report/index.js'
10
+ export * from './wrapper/index.js'
11
+ export * from './scan/index.js'
12
+ export * from './audit-log/index.js'
13
+ export * from './repos/index.js'
14
+ export * from './dependencies/index.js'