1- import events from 'node:events'
2- import https from 'node:https'
3- import readline from 'node:readline'
4-
51import constants from '../../constants'
6- import { getPublicToken } from '../sdk'
72
83import type { Remap } from '@socketsecurity/registry/lib/objects'
9- import type { IncomingMessage } from 'node:http'
10-
11- export type CveAlertType = 'cve' | 'mediumCVE' | 'mildCVE' | 'criticalCVE'
12-
13- export type SocketArtifactAlert = {
14- key : string
15- type : string
16- severity : string
17- category : string
18- action ?: string | undefined
19- actionPolicyIndex ?: number | undefined
20- file ?: string | undefined
21- props ?: any | undefined
22- start ?: number | undefined
23- end ?: number | undefined
24- }
25-
26- export type SocketArtifact = {
27- type : string
28- name : string
29- namespace ?: string | undefined
30- version ?: string | undefined
31- subpath ?: string | undefined
32- release ?: string | undefined
33- id ?: string | undefined
34- author ?: string [ ]
35- license ?: string | undefined
36- licenseDetails ?: Array < {
37- spdxDisj : string
38- provenance : string
39- filepath : string
40- match_strength : number
41- } >
42- licenseAttrib ?: Array < {
43- attribText : string
44- attribData : Array < {
45- purl : string
46- foundInFilepath : string
47- spdxExpr : string
48- foundAuthors : string [ ]
49- } >
50- } >
51- score ?: {
52- supplyChain : number
53- quality : number
54- maintenance : number
55- vulnerability : number
56- license : number
57- overall : number
58- }
59- alerts ?: SocketArtifactAlert [ ]
60- size ?: number | undefined
61- batchIndex ?: number | undefined
62- }
63-
64- export type CompactSocketArtifactAlert = Remap <
65- Omit <
66- SocketArtifactAlert ,
67- 'action' | 'actionPolicyIndex' | 'category' | 'end' | 'file' | 'start'
68- >
69- >
70-
71- export type CompactSocketArtifact = Remap <
72- Omit < SocketArtifact , 'alerts' | 'batchIndex' | 'size' > & {
73- alerts : CompactSocketArtifactAlert [ ]
74- }
75- >
4+ import type { components } from '@socketsecurity/sdk/types/api'
765
776export type ArtifactAlertCve = Remap <
787 Omit < CompactSocketArtifactAlert , 'type' > & {
@@ -97,136 +26,39 @@ export type ArtifactAlertUpgrade = Remap<
9726 }
9827>
9928
29+ export type CveAlertType = 'cve' | 'mediumCVE' | 'mildCVE' | 'criticalCVE'
30+
31+ export type CompactSocketArtifactAlert = Remap <
32+ Omit <
33+ SocketArtifactAlert ,
34+ 'action' | 'actionPolicyIndex' | 'category' | 'end' | 'file' | 'start'
35+ >
36+ >
37+
38+ export type CompactSocketArtifact = Remap <
39+ Omit < SocketArtifact , 'alerts' | 'batchIndex' | 'size' > & {
40+ alerts : CompactSocketArtifactAlert [ ]
41+ }
42+ >
43+
44+ export type SocketArtifact = components [ 'schemas' ] [ 'SocketArtifact' ]
45+
46+ export type SocketArtifactAlert = Remap <
47+ Omit < components [ 'schemas' ] [ 'SocketAlert' ] , 'props' > & {
48+ props ?: any | undefined
49+ }
50+ >
51+
10052const {
10153 ALERT_TYPE_CRITICAL_CVE ,
10254 ALERT_TYPE_CVE ,
10355 ALERT_TYPE_MEDIUM_CVE ,
10456 ALERT_TYPE_MILD_CVE ,
10557 ALERT_TYPE_SOCKET_UPGRADE_AVAILABLE ,
106- API_V0_URL ,
10758 CVE_ALERT_PROPS_FIRST_PATCHED_VERSION_IDENTIFIER ,
108- CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE ,
109- abortSignal
59+ CVE_ALERT_PROPS_VULNERABLE_VERSION_RANGE
11060} = constants
11161
112- async function * createBatchGenerator (
113- chunk : string [ ]
114- ) : AsyncGenerator < CompactSocketArtifact > {
115- // Adds the first 'abort' listener to abortSignal.
116- const req = https
117- . request (
118- `${ API_V0_URL } purl?${ new URLSearchParams ( [
119- [ 'alerts' , 'true' ] ,
120- [ 'compact' , 'true' ]
121- ] ) } `,
122- {
123- method : 'POST' ,
124- headers : {
125- Authorization : `Basic ${ btoa ( `${ getPublicToken ( ) } :` ) } `
126- }
127- // TODO: Fix to not abort process on network abort.
128- // signal: abortSignal
129- }
130- )
131- . end (
132- JSON . stringify ( {
133- components : chunk . map ( id => ( { purl : `pkg:npm/${ id } ` } ) )
134- } )
135- )
136- // Adds the second 'abort' listener to abortSignal.
137- const { 0 : res } = ( await events . once ( req , 'response' , {
138- signal : abortSignal
139- } ) ) as [ IncomingMessage ]
140- const ok = res . statusCode ! >= 200 && res . statusCode ! <= 299
141- if ( ! ok ) {
142- throw new Error ( `Socket API Error: ${ res . statusCode } ` )
143- }
144- const rli = readline . createInterface ( {
145- input : res ,
146- crlfDelay : Infinity ,
147- signal : abortSignal
148- } )
149- for await ( const line of rli ) {
150- yield JSON . parse ( line ) as CompactSocketArtifact
151- }
152- }
153-
154- export async function * batchScan (
155- pkgIds : string [ ] ,
156- concurrencyLimit = 50
157- ) : AsyncGenerator < CompactSocketArtifact > {
158- type GeneratorStep = {
159- generator : AsyncGenerator < CompactSocketArtifact >
160- iteratorResult : IteratorResult < CompactSocketArtifact >
161- }
162- type GeneratorEntry = {
163- generator : AsyncGenerator < CompactSocketArtifact >
164- promise : Promise < GeneratorStep >
165- }
166- type ResolveFn = ( value : GeneratorStep ) => void
167-
168- // The createBatchGenerator method will add 2 'abort' event listeners to
169- // abortSignal so we multiply the concurrencyLimit by 2.
170- const neededMaxListeners = concurrencyLimit * 2
171- // Increase abortSignal max listeners count to avoid Node's MaxListenersExceededWarning.
172- const oldAbortSignalMaxListeners = events . getMaxListeners ( abortSignal )
173- let abortSignalMaxListeners = oldAbortSignalMaxListeners
174- if ( oldAbortSignalMaxListeners < neededMaxListeners ) {
175- abortSignalMaxListeners = oldAbortSignalMaxListeners + neededMaxListeners
176- events . setMaxListeners ( abortSignalMaxListeners , abortSignal )
177- }
178- const { length : pkgIdsCount } = pkgIds
179- const running : GeneratorEntry [ ] = [ ]
180- let index = 0
181- const enqueueGen = ( ) => {
182- if ( index >= pkgIdsCount ) {
183- // No more work to do.
184- return
185- }
186- const chunk = pkgIds . slice ( index , index + 25 )
187- index += 25
188- const generator = createBatchGenerator ( chunk )
189- continueGen ( generator )
190- }
191- const continueGen = ( generator : AsyncGenerator < CompactSocketArtifact > ) => {
192- let resolveFn : ResolveFn
193- running . push ( {
194- generator,
195- promise : new Promise < GeneratorStep > ( resolve => ( resolveFn = resolve ) )
196- } )
197- void generator
198- . next ( )
199- . then ( res => resolveFn ! ( { generator, iteratorResult : res } ) )
200- }
201- // Start initial batch of generators.
202- while ( running . length < concurrencyLimit && index < pkgIdsCount ) {
203- enqueueGen ( )
204- }
205- while ( running . length > 0 ) {
206- // eslint-disable-next-line no-await-in-loop
207- const { generator, iteratorResult } = await Promise . race (
208- running . map ( entry => entry . promise )
209- )
210- // Remove generator.
211- running . splice (
212- running . findIndex ( entry => entry . generator === generator ) ,
213- 1
214- )
215- if ( iteratorResult . done ) {
216- // Start a new generator if available.
217- enqueueGen ( )
218- } else {
219- yield iteratorResult . value
220- // Keep fetching values from this generator.
221- continueGen ( generator )
222- }
223- }
224- // Reset abortSignal max listeners count.
225- if ( abortSignalMaxListeners > oldAbortSignalMaxListeners ) {
226- events . setMaxListeners ( oldAbortSignalMaxListeners , abortSignal )
227- }
228- }
229-
23062export function isArtifactAlertCve (
23163 alert : CompactSocketArtifactAlert
23264) : alert is ArtifactAlertCve {
0 commit comments