Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/audit-log/output-audit-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async function outputAsJson(
logger.log(json)
}

async function outputAsMarkdown(
export async function outputAsMarkdown(
auditLogs: SocketSdkReturnType<'getAuditLogEvents'>['data']['results'],
orgSlug: string,
logType: string,
Expand Down
110 changes: 110 additions & 0 deletions src/commands/package/cmd-package-score.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import path from 'node:path'

import { describe, expect } from 'vitest'

import constants from '../../../dist/constants.js'
import { cmdit, invokeNpm } from '../../../test/utils'

const { CLI } = constants

describe('socket package score', async () => {
// Lazily access constants.rootBinPath.
const entryPath = path.join(constants.rootBinPath, `${CLI}.js`)

cmdit(['package', 'score', '--help'], 'should support --help', async cmd => {
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
expect(stdout).toMatchInlineSnapshot(
`
"Look up score for one package which reflects all of its transitive dependencies as well

Usage
$ socket package score <<ecosystem> <name> | <purl>>

Options
--dryRun Do input validation for a command and exit 0 when input is ok
--help Print this help.
--json Output result as json
--markdown Output result as markdown

Requirements
- quota: 100
- scope: \`packages:list\`

Show deep scoring details for one package. The score will reflect the package
itself, any of its dependencies, and any of its transitive dependencies.

When you want to know whether to trust a package, this is the command to run.

See also the \`socket package shallow\` command, which returns the shallow
score for any number of packages. That will not reflect the dependency scores.

Only a few ecosystems are supported like npm, golang, and maven.

A "purl" is a standard package name formatting: \`pkg:eco/name@version\`
This command will automatically prepend "pkg:" when not present.

The version is optional but when given should be a direct match.

Examples
$ socket package score npm babel-cli
$ socket package score npm babel-cli@1.9.1
$ socket package score npm/babel-cli@1.9.1
$ socket package score pkg:npm/babel-cli@1.9.1"
`
)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>"
`)

expect(code, 'help should exit with code 2').toBe(2)
expect(stderr, 'header should include command (without params)').toContain(
'`socket package score`'
)
})

cmdit(
['package', 'score', '--dry-run'],
'should require args with just dry-run',
async cmd => {
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
expect(stdout).toMatchInlineSnapshot(`""`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>

\\x1b[31m\\xd7\\x1b[39m \\x1b[41m\\x1b[37mInput error\\x1b[39m\\x1b[49m: Please provide the required fields:

- First parameter should be an ecosystem or the arg must be a purl \\x1b[31m(bad!)\\x1b[39m

- Expecting the package to check \\x1b[31m(missing!)\\x1b[39m"
`)

expect(code, 'dry-run should exit with code 2 if missing input').toBe(2)
}
)

cmdit(
['package', 'score', 'npm', 'babel', '--dry-run'],
'should require args with just dry-run',
async cmd => {
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd)
expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`)
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
"
_____ _ _ /---------------
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
|_____|___|___|_,_|___|_|.dev | Command: \`socket package score\`, cwd: <redacted>"
`)

expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
}
)
})
103 changes: 103 additions & 0 deletions src/commands/package/cmd-package-score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import colors from 'yoctocolors-cjs'

import { logger } from '@socketsecurity/registry/lib/logger'

import { handlePurlDeepScore } from './handle-purl-deep-score'
import { parsePackageSpecifiers } from './parse-package-specifiers'
import constants from '../../constants'
import { commonFlags, outputFlags } from '../../flags'
import { meowOrExit } from '../../utils/meow-with-subcommands'
import { getFlagListOutput } from '../../utils/output-formatting'

import type { CliCommandConfig } from '../../utils/meow-with-subcommands'

const { DRY_RUN_BAIL_TEXT } = constants

const config: CliCommandConfig = {
commandName: 'score',
description:
'Look up score for one package which reflects all of its transitive dependencies as well',
hidden: true,
flags: {
...commonFlags,
...outputFlags
},
help: (command, config) => `
Usage
$ ${command} <<ecosystem> <name> | <purl>>

Options
${getFlagListOutput(config.flags, 6)}

Requirements
- quota: 100
- scope: \`packages:list\`

Show deep scoring details for one package. The score will reflect the package
itself, any of its dependencies, and any of its transitive dependencies.

When you want to know whether to trust a package, this is the command to run.

See also the \`socket package shallow\` command, which returns the shallow
score for any number of packages. That will not reflect the dependency scores.

Only a few ecosystems are supported like npm, golang, and maven.

A "purl" is a standard package name formatting: \`pkg:eco/name@version\`
This command will automatically prepend "pkg:" when not present.

The version is optional but when given should be a direct match.

Examples
$ ${command} npm babel-cli
$ ${command} npm babel-cli@1.9.1
$ ${command} npm/babel-cli@1.9.1
$ ${command} pkg:npm/babel-cli@1.9.1
`
}

export const cmdPackageScore = {
description: config.description,
hidden: config.hidden,
run
}

async function run(
argv: string[] | readonly string[],
importMeta: ImportMeta,
{ parentName }: { parentName: string }
): Promise<void> {
const cli = meowOrExit({
argv,
config,
importMeta,
parentName
})

const { json, markdown } = cli.flags
const [ecosystem = '', purl] = cli.input

const { purls, valid } = parsePackageSpecifiers(ecosystem, purl ? [purl] : [])

if (!valid || !purls.length) {
// Use exit status of 2 to indicate incorrect usage, generally invalid
// options or missing arguments.
// https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html
process.exitCode = 2
logger.fail(`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields:\n
- First parameter should be an ecosystem or the arg must be a purl ${!valid ? colors.red('(bad!)') : colors.green('(ok)')}\n
- Expecting the package to check ${!purls.length ? colors.red('(missing!)') : colors.green('(ok)')}\n
`)
return
}

if (cli.flags['dryRun']) {
logger.log(DRY_RUN_BAIL_TEXT)
return
}

await handlePurlDeepScore(
purls[0] || '',
json ? 'json' : markdown ? 'markdown' : 'text'
)
}
6 changes: 4 additions & 2 deletions src/commands/package/cmd-package.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cmdPackageScore } from './cmd-package-score'
import { cmdPackageShallow } from './cmd-package-shallow'
import { meowWithSubcommands } from '../../utils/meow-with-subcommands'

Expand All @@ -11,14 +12,15 @@ export const cmdPackage: CliSubcommand = {
async run(argv, importMeta, { parentName }) {
await meowWithSubcommands(
{
score: cmdPackageScore,
shallow: cmdPackageShallow
},
{
aliases: {
pkg: {
deep: {
description,
hidden: true,
argv: []
argv: ['score']
}
},
argv,
Expand Down
60 changes: 60 additions & 0 deletions src/commands/package/fetch-purl-deep-score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import colors from 'yoctocolors-cjs'

import { logger } from '@socketsecurity/registry/lib/logger'

import constants from '../../constants'
import { handleApiCall, handleApiError, queryApi } from '../../utils/api'
import { AuthError } from '../../utils/errors'
import { getDefaultToken } from '../../utils/sdk'

export async function fetchPurlDeepScore(purl: string) {
// Lazily access constants.spinner.
const { spinner } = constants

const apiToken = getDefaultToken()
if (!apiToken) {
throw new AuthError(
'User must be authenticated to run this command. To log in, run the command `socket login` and enter your API key.'
)
}

spinner.start('Getting deep package score...')
let result
try {
result = await queryApi(`purl/score/${encodeURIComponent(purl)}`, apiToken)
spinner?.successAndStop('Received deep package score response.')
} catch (e) {
spinner?.failAndStop('The request was unsuccessful.')
const msg = (e as undefined | { message: string })?.message
if (msg) {
logger.fail(msg)
logger.log(
'Please report this if the error persists or use the cli version that includes error reporting to automate that'
)
} else {
logger.log(
'An error happened but no reason was given. If this persists please let us know about it and what you were trying to achieve. Thank you.'
)
}
return
}

if (!result.ok) {
const err = await handleApiError(result.status)
logger.fail(
`${colors.bgRed(colors.bold(colors.white(' ' + result.statusText + ' ')))}: ${err}`
)
process.exitCode = 1
return
}

const data = await handleApiCall(await result.text(), 'Reading text')

try {
return JSON.parse(data)
} catch (e) {
throw new Error(
'Was unable to JSON parse the input from the server. It may not have been a proper JSON response. Please report this problem.'
)
}
}
14 changes: 14 additions & 0 deletions src/commands/package/handle-purl-deep-score.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { fetchPurlDeepScore } from './fetch-purl-deep-score'
import { outputPurlScore } from './output-purl-score'

export async function handlePurlDeepScore(
purl: string,
outputKind: 'json' | 'markdown' | 'text'
) {
const data = await fetchPurlDeepScore(purl)
if (!data) {
return
}

await outputPurlScore(purl, data, outputKind)
}
Loading