Skip to content

Commit

Permalink
Continue to move alerts together
Browse files Browse the repository at this point in the history
  • Loading branch information
jdalton committed Jan 22, 2025
1 parent e085e37 commit 0a2c825
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 134 deletions.
4 changes: 2 additions & 2 deletions src/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
getSocketDevPackageOverviewUrl
} from '../utils/socket-url'

import type { SocketAlert } from '../utils/alert/severity'
import type { SocketSdkAlert } from '../utils/alert/severity'
import type { CliSubcommand } from '../utils/meow-with-subcommands'
import type { SocketSdkReturnType } from '@socketsecurity/sdk'

Expand Down Expand Up @@ -129,7 +129,7 @@ function setupCommand(

interface PackageData {
data: SocketSdkReturnType<'getIssuesByNPMPackage'>['data']
severityCount: Record<SocketAlert['severity'], number>
severityCount: Record<SocketSdkAlert['severity'], number>
score: SocketSdkReturnType<'getScoreByNPMPackage'>['data']
}

Expand Down
26 changes: 15 additions & 11 deletions src/shadow/arborist/lib/arborist/reify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,26 @@ import {
import { confirm } from '@socketsecurity/registry/lib/prompts'
import { Spinner } from '@socketsecurity/registry/lib/spinner'

import { batchScan, isAlertFixable, isAlertFixableCve, walk } from './alerts'
import { kCtorArgs, kRiskyReify } from './index'
import { walk } from './walk'
import constants from '../../../../constants'
import { uxLookup } from '../../../../utils/alert/rules'
import {
batchScan,
isAlertFixable,
isAlertFixableCve
} from '../../../../utils/alert/scan'
import { ColorOrMarkdown } from '../../../../utils/color-or-markdown'
import { debugLog } from '../../../../utils/debug'
import { getSocketDevPackageOverviewUrl } from '../../../../utils/socket-url'
import { pacotePath } from '../../../npm-paths'
import { Edge, SafeEdge } from '../edge'

import type { InstallEffect, SocketArtifact } from './alerts'
import type { ArboristClass, AuditAdvisory, SafeArborist } from './index'
import type {
InstallEffect,
SocketScanArtifact
} from '../../../../utils/alert/scan'
import type { SafeNode } from '../node'
import type { Writable } from 'node:stream'

Expand Down Expand Up @@ -163,7 +171,7 @@ async function getPackagesAlerts(
p.existing?.startsWith(`${name}@`)
)?.existing
if (existing) {
const oldArtifact: SocketArtifact | undefined =
const oldArtifact: SocketScanArtifact | undefined =
// eslint-disable-next-line no-await-in-loop
(await batchScan([existing]).next()).value
if (oldArtifact?.alerts?.length) {
Expand Down Expand Up @@ -364,7 +372,7 @@ export async function reify(
this: SafeArborist,
...args: Parameters<InstanceType<ArboristClass>['reify']>
): Promise<SafeNode> {
const needInfoOn = await walk(this.diff)
const needInfoOn = walk(this.diff)
if (
!needInfoOn.length ||
needInfoOn.findIndex(c => c.repository_url === NPM_REGISTRY_URL) === -1
Expand Down Expand Up @@ -421,13 +429,9 @@ export async function reify(
ret = await this[kRiskyReify](...args)
await this.loadActual()
await this.buildIdealTree()
alerts = await getPackagesAlerts(
this,
await walk(this.diff, { fix: true }),
{
fixable: true
}
)
alerts = await getPackagesAlerts(this, walk(this.diff, { fix: true }), {
fixable: true
})
alerts = alerts.filter(a => {
const { key } = a
if (prev.has(key)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,118 +1,9 @@
import events from 'node:events'
import https from 'node:https'
import rl from 'node:readline'

import constants from '../../../../constants'
import { getPublicToken } from '../../../../utils/sdk'

import type { SafeNode } from '../node'
import type { Diff } from '@npmcli/arborist'

export type InstallEffect = {
pkgid: SafeNode['pkgid']
repository_url: string
existing?: SafeNode['pkgid'] | undefined
}

export type SocketAlert = {
key: string
type: string
severity: string
category: string
action?: string
actionPolicyIndex?: number
file?: string
props?: any
start?: number
end?: number
}

export type SocketArtifact = {
type: string
namespace?: string
name?: string
version?: string
subpath?: string
release?: string
id?: string
author?: string[]
license?: string
licenseDetails?: {
spdxDisj: string
provenance: string
filepath: string
match_strength: number
}[]
licenseAttrib?: {
attribText: string
attribData: {
purl: string
foundInFilepath: string
spdxExpr: string
foundAuthors: string[]
}[]
}[]
score?: {
supplyChain: number
quality: number
maintenance: number
vulnerability: number
license: number
overall: number
}
alerts?: SocketAlert[]
size?: number
batchIndex?: number
}

const {
API_V0_URL,
LOOP_SENTINEL,
SOCKET_CLI_FIX_PACKAGE_LOCK_FILE,
abortSignal
} = constants

export async function* batchScan(
pkgIds: string[]
): AsyncGenerator<SocketArtifact> {
const req = https
.request(`${API_V0_URL}/purl?alerts=true`, {
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(`${getPublicToken()}:`).toString('base64url')}`
},
signal: abortSignal
})
.end(
JSON.stringify({
components: pkgIds.map(id => ({ purl: `pkg:npm/${id}` }))
})
)
const { 0: res } = await events.once(req, 'response')
const ok = res.statusCode >= 200 && res.statusCode <= 299
if (!ok) {
throw new Error(`Socket API Error: ${res.statusCode}`)
}
const rli = rl.createInterface(res)
for await (const line of rli) {
yield JSON.parse(line)
}
}

export function isAlertFixable(alert: SocketAlert): boolean {
return alert.type === 'socketUpgradeAvailable' || isAlertFixableCve(alert)
}

export function isAlertFixableCve(alert: SocketAlert): boolean {
const { type } = alert
return (
(type === 'cve' ||
type === 'mediumCVE' ||
type === 'mildCVE' ||
type === 'criticalCVE') &&
!!alert.props?.['firstPatchedVersionIdentifier']
)
}
const { LOOP_SENTINEL, SOCKET_CLI_FIX_PACKAGE_LOCK_FILE } = constants

function toRepoUrl(resolved: string): string {
try {
Expand All @@ -121,6 +12,12 @@ function toRepoUrl(resolved: string): string {
return ''
}

export type InstallEffect = {
pkgid: SafeNode['pkgid']
repository_url: string
existing?: SafeNode['pkgid'] | undefined
}

export type WalkOptions = { fix?: boolean }

export function walk(
Expand Down
109 changes: 109 additions & 0 deletions src/utils/alert/scan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import events from 'node:events'
import https from 'node:https'
import rl from 'node:readline'

import constants from '../../constants'
import { getPublicToken } from '../sdk'

import type { SafeNode } from '../../shadow/arborist/lib/node'

export type InstallEffect = {
pkgid: SafeNode['pkgid']
repository_url: string
existing?: SafeNode['pkgid'] | undefined
}

export type SocketScanArtifactAlert = {
key: string
type: string
severity: string
category: string
action?: string
actionPolicyIndex?: number
file?: string
props?: any
start?: number
end?: number
}

export type SocketScanArtifact = {
type: string
namespace?: string
name?: string
version?: string
subpath?: string
release?: string
id?: string
author?: string[]
license?: string
licenseDetails?: {
spdxDisj: string
provenance: string
filepath: string
match_strength: number
}[]
licenseAttrib?: {
attribText: string
attribData: {
purl: string
foundInFilepath: string
spdxExpr: string
foundAuthors: string[]
}[]
}[]
score?: {
supplyChain: number
quality: number
maintenance: number
vulnerability: number
license: number
overall: number
}
alerts?: SocketScanArtifactAlert[]
size?: number
batchIndex?: number
}

const { API_V0_URL, abortSignal } = constants

export async function* batchScan(
pkgIds: string[]
): AsyncGenerator<SocketScanArtifact> {
const req = https
.request(`${API_V0_URL}/purl?alerts=true`, {
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(`${getPublicToken()}:`).toString('base64url')}`
},
signal: abortSignal
})
.end(
JSON.stringify({
components: pkgIds.map(id => ({ purl: `pkg:npm/${id}` }))
})
)
const { 0: res } = await events.once(req, 'response')
const ok = res.statusCode >= 200 && res.statusCode <= 299
if (!ok) {
throw new Error(`Socket API Error: ${res.statusCode}`)
}
const rli = rl.createInterface(res)
for await (const line of rli) {
yield JSON.parse(line)
}
}

export function isAlertFixable(alert: SocketScanArtifactAlert): boolean {
return alert.type === 'socketUpgradeAvailable' || isAlertFixableCve(alert)
}

export function isAlertFixableCve(alert: SocketScanArtifactAlert): boolean {
const { type } = alert
return (
(type === 'cve' ||
type === 'mediumCVE' ||
type === 'mildCVE' ||
type === 'criticalCVE') &&
!!alert.props?.['firstPatchedVersionIdentifier']
)
}
23 changes: 12 additions & 11 deletions src/utils/alert/severity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@ import { stringJoinWithSeparateFinalSeparator } from '../strings'

import type { SocketSdkReturnType } from '@socketsecurity/sdk'

type SocketAlertList = SocketSdkReturnType<'getIssuesByNPMPackage'>['data']
export type SocketSdkAlertList =
SocketSdkReturnType<'getIssuesByNPMPackage'>['data']

export type SocketAlert = SocketAlertList[number]['value'] extends
export type SocketSdkAlert = SocketSdkAlertList[number]['value'] extends
| infer U
| undefined
? U
: never

// Ordered from most severe to least.
const SEVERITIES_BY_ORDER: SocketAlert['severity'][] = [
const SEVERITIES_BY_ORDER: SocketSdkAlert['severity'][] = [
'critical',
'high',
'middle',
'low'
]

function getDesiredSeverities(
lowestToInclude: SocketAlert['severity'] | undefined
): SocketAlert['severity'][] {
const result: SocketAlert['severity'][] = []
lowestToInclude: SocketSdkAlert['severity'] | undefined
): SocketSdkAlert['severity'][] {
const result: SocketSdkAlert['severity'][] = []
for (const severity of SEVERITIES_BY_ORDER) {
result.push(severity)
if (severity === lowestToInclude) {
Expand All @@ -33,7 +34,7 @@ function getDesiredSeverities(
}

export function formatSeverityCount(
severityCount: Record<SocketAlert['severity'], number>
severityCount: Record<SocketSdkAlert['severity'], number>
): string {
const summary: string[] = []
for (const severity of SEVERITIES_BY_ORDER) {
Expand All @@ -45,13 +46,13 @@ export function formatSeverityCount(
}

export function getSeverityCount(
issues: SocketAlertList,
lowestToInclude: SocketAlert['severity'] | undefined
): Record<SocketAlert['severity'], number> {
issues: SocketSdkAlertList,
lowestToInclude: SocketSdkAlert['severity'] | undefined
): Record<SocketSdkAlert['severity'], number> {
const severityCount = pick(
{ low: 0, middle: 0, high: 0, critical: 0 },
getDesiredSeverities(lowestToInclude)
) as Record<SocketAlert['severity'], number>
) as Record<SocketSdkAlert['severity'], number>

for (const issue of issues) {
const { value } = issue
Expand Down

0 comments on commit 0a2c825

Please sign in to comment.