11#!/usr/bin/env node
22
33/**
4- * Shai-Hulud 2.0 IOC Scanner for Yarn projects
5- * Scans package.json and yarn. lock against DataDog's consolidated IOC list
4+ * Shai-Hulud 2.0 IOC Scanner
5+ * Scans package.json and pnpm- lock.yaml against DataDog's consolidated IOC list
66 */
77
88const fs = require ( "fs" ) ;
99const path = require ( "path" ) ;
1010
1111const IOC_FILE = "iocs.csv" ;
1212const PACKAGE_JSON_PATHS = [ "package.json" , "api/package.json" , "web/package.json" ] ;
13- const YARN_LOCK_PATH = "yarn. lock" ;
13+ const PNPM_LOCK_PATH = "pnpm- lock.yaml " ;
1414
1515/**
1616 * Parse IOC CSV file
@@ -63,10 +63,11 @@ function extractDependencies(packageJsonPath) {
6363}
6464
6565/**
66- * Parse yarn.lock to get resolved versions
67- * Simplified parser - extracts package@version pairs
66+ * Parse pnpm-lock.yaml to get resolved versions
67+ * Extracts package@version pairs from the packages: section
68+ * Format: ' @scope/name@1.2.3:' or ' name@1.2.3:'
6869 */
69- function parseYarnLock ( lockPath ) {
70+ function parsePnpmLock ( lockPath ) {
7071 if ( ! fs . existsSync ( lockPath ) ) {
7172 console . error ( `ERROR: ${ lockPath } not found` ) ;
7273 process . exit ( 1 ) ;
@@ -76,31 +77,26 @@ function parseYarnLock(lockPath) {
7677 const lines = content . split ( "\n" ) ;
7778 const packages = [ ] ;
7879
79- let currentPackage = null ;
80- let currentVersion = null ;
80+ let inPackagesSection = false ;
8181
8282 for ( const line of lines ) {
83- // Package declaration line (e.g., "package-name@^1.0.0:")
84- if ( line . match ( / ^ [ ^ ] .* : $ / ) ) {
85- const pkgLine = line . slice ( 0 , - 1 ) ; // Remove trailing :
86- // Extract package name (before @)
87- const atIndex = pkgLine . lastIndexOf ( "@" ) ;
88- if ( atIndex > 0 ) {
89- currentPackage = pkgLine . substring ( 0 , atIndex ) ;
90- }
83+ if ( line === "packages:" ) {
84+ inPackagesSection = true ;
85+ continue ;
9186 }
92- // Version line (e.g., " version "1.2.3"")
93- else if ( line . trim ( ) . startsWith ( 'version "' ) ) {
94- const versionMatch = line . match ( / v e r s i o n " ( [ ^ " ] + ) " / ) ;
95- if ( versionMatch && currentPackage ) {
96- currentVersion = versionMatch [ 1 ] ;
97- packages . push ( {
98- name : currentPackage ,
99- version : currentVersion
100- } ) ;
101- currentPackage = null ;
102- currentVersion = null ;
103- }
87+
88+ if ( inPackagesSection && line . length > 0 && ! line . startsWith ( " " ) ) {
89+ break ;
90+ }
91+
92+ if ( ! inPackagesSection ) continue ;
93+
94+ const match = line . match ( / ^ \s { 2 } ' ? ( @ ? [ ^ @ ' ] + ) @ ( [ ^ ' : ] + ) ' ? : / ) ;
95+ if ( match ) {
96+ packages . push ( {
97+ name : match [ 1 ] ,
98+ version : match [ 2 ]
99+ } ) ;
104100 }
105101 }
106102
@@ -153,9 +149,9 @@ function scanDependencies() {
153149 }
154150 }
155151
156- // Parse yarn. lock for resolved versions
157- const resolvedDeps = parseYarnLock ( YARN_LOCK_PATH ) ;
158- console . log ( `✓ Scanned ${ YARN_LOCK_PATH } : ${ resolvedDeps . length } resolved packages\n` ) ;
152+ // Parse pnpm- lock.yaml for resolved versions
153+ const resolvedDeps = parsePnpmLock ( PNPM_LOCK_PATH ) ;
154+ console . log ( `✓ Scanned ${ PNPM_LOCK_PATH } : ${ resolvedDeps . length } resolved packages\n` ) ;
159155
160156 // Check for matches
161157 const matches = [ ] ;
@@ -167,7 +163,7 @@ function scanDependencies() {
167163 matches . push ( {
168164 package : pkg . name ,
169165 version : pkg . version ,
170- source : "yarn. lock" ,
166+ source : "pnpm- lock.yaml " ,
171167 ioc : ioc
172168 } ) ;
173169 }
0 commit comments