Skip to content

Instantly share code, notes, and snippets.

@mcansky
Last active September 18, 2025 07:29
Show Gist options
  • Select an option

  • Save mcansky/5d50b47a86b04008d65eed4456922257 to your computer and use it in GitHub Desktop.

Select an option

Save mcansky/5d50b47a86b04008d65eed4456922257 to your computer and use it in GitHub Desktop.
A set of scripts to check quickly impact of the 2025/09/08 supply chain attack on your project
#!/usr/bin/env ruby
require 'json'
require 'set'
#####################################################################
### Make sure you have a compromised_packages.json file with the list
#####################################################################
class NPMSecurityChecker
def initialize(compromised_json_file = 'compromised_packages.json')
@compromised_json_file = compromised_json_file
@compromised_packages = load_compromised_packages
@findings = []
end
def load_compromised_packages
unless File.exist?(@compromised_json_file)
puts "Error: #{@compromised_json_file} not found!"
puts "Please ensure the JSON file with compromised packages exists."
exit 1
end
begin
data = JSON.parse(File.read(@compromised_json_file))
packages = Set.new
data['compromised_packages'].each do |package_version|
packages.add(package_version)
end
packages
rescue JSON::ParserError => e
puts "Error parsing JSON file: #{e.message}"
exit 1
rescue => e
puts "Error loading compromised packages: #{e.message}"
exit 1
end
end
def parse_package_version(package_version_string)
# Handle scoped packages like @scope/package@version
if package_version_string.start_with?('@')
# Find the last @ which separates version from package name
parts = package_version_string.split('@')
if parts.length >= 3
# @scope/package@version -> ["", "scope/package", "version"]
package_name = "@#{parts[1]}"
version = parts[2]
else
return nil
end
else
# Regular package@version
parts = package_version_string.split('@')
if parts.length == 2
package_name = parts[0]
version = parts[1]
else
return nil
end
end
[package_name, version]
end
def check_package_json(file_path)
return unless File.exist?(file_path)
begin
data = JSON.parse(File.read(file_path))
rescue JSON::ParserError => e
puts "Warning: Could not parse #{file_path}: #{e.message}"
return
end
dependency_types = %w[dependencies devDependencies peerDependencies optionalDependencies]
dependency_types.each do |dep_type|
next unless data[dep_type]
data[dep_type].each do |package_name, version_spec|
# Clean version spec (remove ^, ~, etc.)
clean_version = version_spec.gsub(/^[\^~>=<]/, '').split(' ').first
# Check if this exact package@version is compromised
package_version = "#{package_name}@#{clean_version}"
if @compromised_packages.include?(package_version)
@findings << {
file: file_path,
package: package_name,
version: clean_version,
version_spec: version_spec,
dependency_type: dep_type,
match_type: 'exact',
source: 'package.json'
}
end
# Also check if any compromised package has the same name (version mismatch)
@compromised_packages.each do |compromised|
comp_name, comp_version = parse_package_version(compromised)
next unless comp_name
if comp_name == package_name && comp_version != clean_version
@findings << {
file: file_path,
package: package_name,
version: clean_version,
version_spec: version_spec,
compromised_version: comp_version,
dependency_type: dep_type,
match_type: 'name_only',
source: 'package.json'
}
end
end
end
end
end
def check_package_lock_json(file_path)
return unless File.exist?(file_path)
begin
data = JSON.parse(File.read(file_path))
rescue JSON::ParserError => e
puts "Warning: Could not parse #{file_path}: #{e.message}"
return
end
# Check packages section (npm v7+)
if data['packages']
data['packages'].each do |package_path, package_info|
next if package_path.empty? # Skip root
package_name = package_info['name']
version = package_info['version']
next unless package_name && version
package_version = "#{package_name}@#{version}"
if @compromised_packages.include?(package_version)
@findings << {
file: file_path,
package: package_name,
version: version,
match_type: 'exact',
source: 'package-lock.json',
package_path: package_path
}
end
end
end
# Check dependencies section (npm v6 and earlier)
if data['dependencies']
check_lock_dependencies(data['dependencies'], file_path)
end
end
def check_lock_dependencies(deps, file_path, prefix = '')
deps.each do |package_name, package_info|
version = package_info['version']
next unless version
package_version = "#{package_name}@#{version}"
if @compromised_packages.include?(package_version)
@findings << {
file: file_path,
package: package_name,
version: version,
match_type: 'exact',
source: 'package-lock.json'
}
end
# Recursively check nested dependencies
if package_info['dependencies']
check_lock_dependencies(package_info['dependencies'], file_path, "#{prefix}#{package_name}/")
end
end
end
def check_yarn_lock(file_path)
return unless File.exist?(file_path)
begin
content = File.read(file_path)
# Parse yarn.lock entries
content.scan(/^"?([^"\s]+)@[^"]*"?:\s*\n(?:\s+.*\n)*?\s+version\s+"([^"]+)"/) do |package_name, version|
# Handle scoped packages in yarn.lock format
if package_name.include?('@') && !package_name.start_with?('@')
# Convert "package@^1.0.0" style to just "package"
package_name = package_name.split('@').first
end
package_version = "#{package_name}@#{version}"
if @compromised_packages.include?(package_version)
@findings << {
file: file_path,
package: package_name,
version: version,
match_type: 'exact',
source: 'yarn.lock'
}
end
end
rescue => e
puts "Warning: Could not parse #{file_path}: #{e.message}"
end
end
def scan_directory(dir_path)
Dir.glob(File.join(dir_path, '**/package.json')) do |file|
check_package_json(file)
end
Dir.glob(File.join(dir_path, '**/package-lock.json')) do |file|
check_package_lock_json(file)
end
Dir.glob(File.join(dir_path, '**/yarn.lock')) do |file|
check_yarn_lock(file)
end
end
def scan_file(file_path)
case File.basename(file_path)
when 'package.json'
check_package_json(file_path)
when 'package-lock.json'
check_package_lock_json(file_path)
when 'yarn.lock'
check_yarn_lock(file_path)
else
puts "Unsupported file type: #{file_path}"
end
end
def report_findings
puts "=" * 70
puts "NPM Supply Chain Security Check Results"
puts "=" * 70
puts "Checked against #{@compromised_packages.size} known compromised packages"
puts
if @findings.empty?
puts "✓ No compromised packages detected!"
puts "Your dependencies appear safe from this known supply chain attack."
return true
end
puts "⚠ SECURITY ALERT: Compromised packages detected!"
puts
# Group findings by file
@findings.group_by { |f| f[:file] }.each do |file, findings|
puts "File: #{file}"
findings.each do |finding|
case finding[:match_type]
when 'exact'
puts " 🚨 EXACT MATCH: #{finding[:package]}@#{finding[:version]}"
when 'name_only'
puts " ⚠ PACKAGE NAME MATCH: #{finding[:package]}"
puts " Your version: #{finding[:version]} | Compromised version: #{finding[:compromised_version]}"
end
puts " Source: #{finding[:source]}"
puts " Type: #{finding[:dependency_type]}" if finding[:dependency_type]
puts
end
end
puts "IMMEDIATE ACTIONS REQUIRED:"
puts "1. Do NOT install or update these packages"
puts "2. Remove these packages from your project immediately"
puts "3. Clear package manager cache (npm cache clean --force)"
puts "4. Review your systems for signs of compromise"
puts "5. Consider rotating API keys and secrets"
puts "6. Update your security team if applicable"
false
end
def run(paths)
paths = ['.'] if paths.empty?
paths.each do |path|
if File.directory?(path)
puts "Scanning directory: #{File.expand_path(path)}"
scan_directory(path)
elsif File.file?(path)
puts "Scanning file: #{File.expand_path(path)}"
scan_file(path)
else
puts "Path not found: #{path}"
end
end
safe = report_findings
exit(safe ? 0 : 1)
end
end
# Run the checker
if __FILE__ == $0
checker = NPMSecurityChecker.new
checker.run(ARGV)
end
{
"compromised_packages": [
"@ahmedhfarag/[email protected]",
"@ahmedhfarag/[email protected]",
"[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@art-ws/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@crowdstrike/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@ctrl/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@hestjs/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nativescript-community/[email protected]",
"@nexe/[email protected]",
"@nexe/[email protected]",
"@nexe/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@nstudio/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@operato/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@teselagen/[email protected]",
"@thangved/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@things-factory/[email protected]",
"@tnf-dev/[email protected]",
"@tnf-dev/[email protected]",
"@tnf-dev/[email protected]",
"@tnf-dev/[email protected]",
"@tnf-dev/[email protected]",
"@ui-ux-gang/[email protected]",
"@yoobic/[email protected]",
"@yoobic/[email protected]",
"@yoobic/[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"@rxap/[email protected]",
"@rxap/[email protected]",
"[email protected]"
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment