Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Created December 16, 2025 00:06
Show Gist options
  • Select an option

  • Save ericboehs/90aa838888b241e18f3de4caee2c621c to your computer and use it in GitHub Desktop.

Select an option

Save ericboehs/90aa838888b241e18f3de4caee2c621c to your computer and use it in GitHub Desktop.
Shai-Hulud PowerShell Detector

Shai-Hulud Detector - PowerShell Edition

A PowerShell-based security scanner that detects indicators of compromise (IOCs) from the Shai-Hulud npm supply chain attacks that occurred in September 2025 and November 2025. This tool specifically targets the "Shai-Hulud: The Second Coming" attack that used fake Bun runtime installations to harvest credentials.

📋 Table of Contents

Overview

The Shai-Hulud detector consists of two specialized scripts:

  1. Repository Scanner (shai-hulud.ps1) - Scans files, packages, and repositories for IOCs
  2. Active Malware Detector (shai-hulud-detection.ps1) - Detects active running malware processes

Scripts

Repository Scanner (shai-hulud.ps1)

Scans project directories and repositories for:

  • Malicious workflow files (shai-hulud-workflow.yml)
  • Fake Bun runtime attacks (setup_bun.js, bun_environment.js)
  • Compromised npm packages (known malicious package versions)
  • Suspicious postinstall hooks that execute malicious code
  • Cryptocurrency theft patterns from the Chalk/Debug attack
  • Trufflehog credential harvesting activity
  • GitHub Actions runners installed by malware
  • Destructive payload patterns that can delete user data
  • Network exfiltration patterns (in paranoid mode)
  • Typosquatting attacks (in paranoid mode)

Active Malware Detector (shai-hulud-detection.ps1)

Detects active running malware processes:

  • Suspicious running processes (fake Bun runtime, Trufflehog, etc.)
  • Malicious process command lines with attack patterns
  • Processes running from suspicious locations (temp directories, node_modules)
  • Credential exposure in process command lines

Requirements

Minimum Requirements

  • PowerShell 7+ (PowerShell Core) - Required for both scripts
  • Windows 10/11, Linux, or macOS (cross-platform support)

Optional Dependencies

  • Git (for git repository scanning in repository scanner)
  • Node.js/npm (not required, but useful for understanding findings)

Check Your PowerShell Version

$PSVersionTable.PSVersion

If you see version 7.0.0 or higher, you're good to go!

Installation

Method 1: Direct Download

  1. Download both scripts:
    • shai-hulud.ps1 (Repository Scanner)
    • shai-hulud-detection.ps1 (Active Malware Detector)
  2. Optionally download infected-packages.txt (if available) to the same directory
  3. Open PowerShell and navigate to the script directory

Method 2: Clone Repository

git clone https://github.com/your-repo/shai-hulud-detect.git
cd shai-hulud-detect

Execution Policy

If you encounter execution policy errors, you may need to allow script execution:

# Check current execution policy
Get-ExecutionPolicy

# For current session only (recommended)
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

# Or run with bypass flag
pwsh -ExecutionPolicy Bypass -File .\shai-hulud.ps1 -ScanDir "C:\path\to\project"

Quick Start

Repository Scanner

# Basic scan
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-project"

# Scan with paranoid mode (includes additional security checks)
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-project" -Paranoid

# Quiet mode (minimal output)
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-project" -Quiet

# JSON output for automation
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-project" -Json

Active Malware Detector

# Basic scan (no directory needed - scans running processes)
.\shai-hulud-detection.ps1

# Quiet mode
.\shai-hulud-detection.ps1 -Quiet

# JSON output
.\shai-hulud-detection.ps1 -Json

Repository Scanner Usage

Basic Syntax

.\shai-hulud.ps1 -ScanDir <directory_path> [OPTIONS]

Parameters

Parameter Type Required Default Description
-ScanDir String Yes - Directory path to scan for IOCs
-Paranoid Switch No $false Enable additional security checks (typosquatting, network patterns)
-SaveLog String No - Save all detected file paths to specified file, grouped by severity
-Parallelism Integer No 4 Number of parallel threads for hash computation (max = CPU count)
-Quiet Switch No $false Minimal output (only critical findings)
-Verbose Switch No $false Detailed output with all stage progress
-Json Switch No $false Output results as JSON (for automation)

Parameter Details

-ScanDir (Required)

The directory path to scan. Can be absolute or relative.

# Absolute path
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-project"

# Relative path
.\shai-hulud.ps1 -ScanDir ".\my-project"

# Path with spaces (use quotes)
.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\My Projects\project-name"

-Paranoid (Optional)

Enables additional security checks that are general security features, not specific to Shai-Hulud:

  • Typosquatting detection: Identifies packages with Unicode/homoglyph characters
  • Network exfiltration patterns: Detects suspicious domains, hardcoded IPs, and data exfiltration attempts
.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Paranoid

Note: Paranoid mode may produce more false positives from legitimate code.

-SaveLog (Optional)

Saves all detected file paths to a log file, grouped by severity (HIGH, MEDIUM, LOW).

.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -SaveLog "scan-results.log"

Log File Format:

# HIGH
C:\path\to\malicious-file.js
C:\path\to\compromised-package.json

# MEDIUM
C:\path\to\suspicious-file.js

# LOW
C:\path\to\low-risk-file.js

-Parallelism (Optional)

Controls the number of parallel threads used for hash computation. Automatically capped at your CPU count.

# Use 8 threads
.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Parallelism 8

# Use all available CPU cores
.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Parallelism 16

Note: Higher parallelism uses more CPU and memory but speeds up hash computation on large projects.

Active Malware Detector Usage

Basic Syntax

.\shai-hulud-detection.ps1 [OPTIONS]

Parameters

Parameter Type Required Default Description
-Quiet Switch No $false Minimal output (only critical findings)
-Verbose Switch No $false Detailed output with all stage progress
-Json Switch No $false Output results as JSON (for automation)
-SaveLog String No - Save results to specified log file

Note: The active malware detector does not require a directory to scan - it automatically checks all running processes on the system.

Output Modes

Both scripts support multiple output modes for different use cases:

Normal Mode (Default)

Standard color-coded output with all findings and progress information.

.\shai-hulud.ps1 -ScanDir "C:\path\to\project"

Quiet Mode (-Quiet)

Minimal output showing only critical findings and summary. Useful for automated scans or when you only want to see issues.

.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Quiet

Verbose Mode (-Verbose)

Detailed output including all stage progress messages and timing information.

.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Verbose

JSON Mode (-Json)

Machine-readable JSON output for automation, CI/CD pipelines, or programmatic processing. Suppresses all console output.

.\shai-hulud.ps1 -ScanDir "C:\path\to\project" -Json | ConvertFrom-Json

JSON Output Structure:

{
  "timestamp": "2025-12-15T12:00:00Z",
  "scan_directory": "C:\\path\\to\\project",
  "findings": {
    "high_risk": [...],
    "medium_risk": [...]
  },
  "summary": {
    "high_risk_count": 2,
    "medium_risk_count": 1,
    "total_issues": 3,
    "exit_code": 1
  },
  "credential_inventory": [...]
}

Credential Inventory

Both scripts automatically collect and report a credential inventory when credentials are detected. This feature provides:

  • Credential Type Identification: GitHub tokens, npm tokens, AWS keys, Slack tokens, etc.
  • Severity Levels: CRITICAL, HIGH, or MEDIUM based on credential type
  • Rotation URLs: Direct links to rotate each type of credential
  • Location Information: Where credentials were found (files or processes)
  • Actionable Steps: Clear instructions on what to do next

Example Credential Inventory Output

==============================================
CREDENTIAL INVENTORY - ACTION REQUIRED
==============================================

⚠️  The following credentials may have been exposed:

🔑 GITHUB_TOKEN: GitHub Personal Access Token or GitHub Actions Token
   Severity: HIGH
   Rotation URL: https://github.com/settings/tokens
   Found in:
     - C:\path\to\malicious-file.js

⚠️  IMMEDIATE ACTION REQUIRED:
   1. Rotate all listed credentials immediately
   2. Review access logs for unauthorized usage
   3. Enable MFA/2FA where available
   4. Review and revoke any suspicious access

The credential inventory is always displayed (even in quiet mode) because it contains critical security information.

What It Detects

HIGH RISK Indicators

These findings indicate likely compromise and require immediate action:

  1. Malicious Workflow Files

    • shai-hulud-workflow.yml files
    • formatter_*.yml workflow files (November 2025 attack)
  2. Known Malicious File Hashes

    • Files matching SHA256 hashes from security incident reports
    • Confirmed malicious payloads
  3. Bun Attack Files (November 2025)

    • setup_bun.js - Fake Bun runtime installation
    • bun_environment.js - 10MB+ obfuscated credential harvesting payload
    • actionsSecrets.json - Double Base64 encoded secrets
  4. Compromised Packages

    • Known malicious package versions (e.g., @ctrl/tinycolor:4.1.0)
    • Packages from compromised namespaces
  5. Suspicious Postinstall Hooks

    • Postinstall scripts using curl, wget, node -e, or eval
  6. Destructive Patterns

    • Code that deletes user home directories when credential theft fails
    • Shai-Hulud 2.0 wiper patterns using Bun.spawnSync, shred, cipher
  7. Trufflehog Activity

    • Dynamic TruffleHog downloads via curl/wget/Bun
    • Credential scanning patterns
    • Credential exfiltration indicators
  8. GitHub Actions Runners

    • Self-hosted runners installed by malware
    • .dev-env directories (Koi.ai IOC)
  9. Cryptocurrency Theft

    • Known attacker wallet addresses
    • XMLHttpRequest hijacking with crypto patterns
    • Phishing domains (npmjs.help)
  10. Active Malware Processes (Active Detector only)

    • Fake Bun runtime processes
    • Trufflehog processes
    • Processes with suspicious command lines

MEDIUM RISK Indicators

These findings require manual investigation:

  1. Suspicious Content Patterns

    • webhook.site references
    • Malicious webhook endpoints
  2. Package Integrity Issues

    • Compromised packages in lockfiles
    • @ctrl packages (potential worm activity)
  3. Git Branches

    • Branches named shai-hulud or similar
  4. Shai-Hulud Repositories

    • Repository names containing "shai-hulud"
    • Migration pattern repositories
    • Suspicious data.json files
  5. Crypto Patterns (context-dependent)

    • Ethereum wallet addresses in crypto-related code
    • XMLHttpRequest modifications without crypto context

LOW RISK Indicators (Informational)

These are likely false positives from legitimate code:

  1. Namespace Warnings

    • Packages from compromised namespaces (may be legitimate)
  2. Framework Code

    • XMLHttpRequest modifications in React Native/Next.js

Output Format

The scripts provide color-coded output with clear risk stratification:

==============================================
      SHAI-HULUD DETECTION REPORT
==============================================

🚨 HIGH RISK: Malicious workflow files detected:
   - C:\path\to\shai-hulud-workflow.yml

🚨 HIGH RISK: Compromised package versions detected:
   - Package: @ctrl/tinycolor@4.1.0
     Found in: C:\path\to\package.json

⚠️  MEDIUM RISK: Suspicious content patterns:
   - Pattern: webhook.site reference
     Found in: C:\path\to\suspicious-file.js

==============================================
CREDENTIAL INVENTORY - ACTION REQUIRED
==============================================

🔑 GITHUB_TOKEN: GitHub Personal Access Token
   Rotation URL: https://github.com/settings/tokens

==============================================
   SUMMARY:
   High Risk Issues: 2
   Medium Risk Issues: 1
   Total Critical Issues: 3
==============================================

Color Coding

  • 🔴 RED: High risk findings requiring immediate action
  • 🟡 YELLOW: Medium risk findings requiring investigation
  • 🟢 GREEN: Clean scan (no findings)
  • 🔵 BLUE: Informational messages and stage progress
  • 🟠 ORANGE: Stage headers and credential inventory

Exit Codes

Both scripts use consistent exit codes to indicate scan results:

Exit Code Meaning Description
0 Clean No significant findings detected
1 High Risk High risk findings detected - immediate action required
2 Medium Risk Medium risk findings detected - investigation recommended

Using Exit Codes in Scripts

# Repository Scanner
.\shai-hulud.ps1 -ScanDir "C:\path\to\project"
$exitCode = $LASTEXITCODE

if ($exitCode -eq 0) {
    Write-Host "Scan clean!"
} elseif ($exitCode -eq 1) {
    Write-Host "HIGH RISK - Immediate action required!"
    # Send alert, block deployment, etc.
} elseif ($exitCode -eq 2) {
    Write-Host "Medium risk - Review recommended"
}

# Active Malware Detector
.\shai-hulud-detection.ps1
if ($LASTEXITCODE -eq 1) {
    Write-Host "Active malware detected - investigate immediately!"
}

Performance

Repository Scanner

Typical Scan Times

  • Small project (< 1,000 files): 5-15 seconds
  • Medium project (1,000-10,000 files): 15-60 seconds
  • Large project (10,000+ files): 1-5 minutes

Performance Tips

  1. Increase Parallelism: Use -Parallelism to match your CPU count for faster hash computation
  2. Exclude node_modules: The script automatically filters node_modules for hash checking, but you can exclude it entirely by scanning a subdirectory
  3. Use SSD: Faster disk I/O significantly improves scan performance

Resource Usage

  • CPU: Moderate during hash computation (scales with -Parallelism)
  • Memory: ~100-500 MB depending on project size
  • Disk I/O: Moderate (reads all relevant files)

Active Malware Detector

Typical Scan Times

  • Process enumeration: 1-5 seconds
  • Command line analysis: 2-10 seconds
  • Total scan time: 3-15 seconds

Resource Usage

  • CPU: Low (process enumeration is fast)
  • Memory: ~50-100 MB
  • Disk I/O: Minimal (only reads process information)

Troubleshooting

Common Issues

Execution Policy Error

.\shai-hulud.ps1 : File cannot be loaded because running scripts is disabled on this system.

Solution:

# For current session only
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

# Or run with bypass flag
pwsh -ExecutionPolicy Bypass -File .\shai-hulud.ps1 -ScanDir "C:\path"

"Directory does not exist" Error

Error: Directory 'C:\path\to\project' does not exist.

Solution:

  • Verify the path is correct
  • Use quotes for paths with spaces
  • Use absolute paths if relative paths don't work

Slow Performance (Repository Scanner)

Solutions:

  • Increase -Parallelism (up to CPU count)
  • Exclude large directories like node_modules by scanning subdirectories
  • Run on SSD instead of HDD

False Positives

Some findings may be false positives:

  • Framework code: React Native, Next.js may trigger XMLHttpRequest warnings
  • Legitimate tools: Security scanners may trigger Trufflehog warnings
  • Development code: Test files may contain suspicious patterns

Solution: Review each finding manually. The script provides file paths and context to help determine legitimacy.

Git Command Errors

If git repository scanning fails:

  • Ensure Git is installed and in PATH
  • Some repositories may have corrupted .git directories (script will skip them)

Process Enumeration Errors (Active Detector)

If process enumeration fails:

  • Ensure you have appropriate permissions to enumerate processes
  • On Linux/macOS, may need elevated permissions for command line access
  • Some processes may be protected (script will skip them)

Examples

Example 1: Basic Repository Scan

.\shai-hulud.ps1 -ScanDir "C:\Users\YourName\Projects\my-app"

Example 2: Comprehensive Repository Scan with Logging

.\shai-hulud.ps1 `
    -ScanDir "C:\Users\YourName\Projects\my-app" `
    -Paranoid `
    -SaveLog "security-scan-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" `
    -Parallelism 8

Example 3: CI/CD Integration (Repository Scanner)

# In Azure DevOps, GitHub Actions, etc.
$scanResult = .\shai-hulud.ps1 -ScanDir $env:BUILD_SOURCESDIRECTORY -Json | ConvertFrom-Json
if ($scanResult.summary.exit_code -ne 0) {
    Write-Error "Security scan failed! Review findings above."
    exit 1
}

Example 4: Scheduled Daily Repository Scan

# Create scheduled task
$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
    -Argument "-ExecutionPolicy Bypass -File C:\Scripts\shai-hulud.ps1 -ScanDir C:\Projects -SaveLog C:\Logs\scan-$(Get-Date -Format 'yyyyMMdd').log"

$trigger = New-ScheduledTaskTrigger -Daily -At 2am
Register-ScheduledTask -TaskName "Shai-Hulud Daily Scan" -Action $action -Trigger $trigger

Example 5: Scan Multiple Projects

$projects = @(
    "C:\Projects\project1",
    "C:\Projects\project2",
    "C:\Projects\project3"
)

foreach ($project in $projects) {
    Write-Host "Scanning $project..." -ForegroundColor Cyan
    .\shai-hulud.ps1 -ScanDir $project -SaveLog "$project-scan.log" -Quiet
    Write-Host ""
}

Example 6: Active Malware Detection

# Basic active malware scan
.\shai-hulud-detection.ps1

# Quiet mode for automated monitoring
.\shai-hulud-detection.ps1 -Quiet

# JSON output for integration
.\shai-hulud-detection.ps1 -Json | ConvertFrom-Json

Example 7: Combined Scanning

# Scan repository first
.\shai-hulud.ps1 -ScanDir "C:\Projects\my-app" -Quiet
$repoExitCode = $LASTEXITCODE

# Then check for active malware
.\shai-hulud-detection.ps1 -Quiet
$activeExitCode = $LASTEXITCODE

# Take action based on results
if ($repoExitCode -eq 1 -or $activeExitCode -eq 1) {
    Write-Host "CRITICAL: High risk detected!" -ForegroundColor Red
    # Send alert, block deployment, etc.
}

Comparison with Bash Version

Similarities

  • Same detection logic and IOCs
  • Same exit codes
  • Same risk stratification
  • Compatible log file format

Differences

Feature PowerShell Bash
Platform Windows, Linux, macOS (PS 7+) Linux, macOS, WSL
Hash Tool Get-FileHash sha256sum / shasum
JSON Parsing ConvertFrom-Json awk / jq
Pattern Matching Select-String grep / ripgrep / git-grep
Parallel Processing ForEach-Object -Parallel (PS 7+) xargs -P
Path Handling Native Windows paths Unix-style paths
Active Detection Separate script Integrated

Performance Comparison

  • PowerShell: Generally faster on Windows due to native .NET integration
  • Bash: Generally faster on Linux/macOS due to optimized Unix tools
  • Cross-platform: PowerShell 7+ provides good performance on all platforms

Additional Resources

Related Files

  • shai-hulud.ps1 - Repository scanner script
  • shai-hulud-detection.ps1 - Active malware detector script
  • infected-packages.txt - Database of known infected package versions (optional)

Security References

Contributing

Found a bug or want to improve detection? Contributions welcome!

  1. Test your changes thoroughly
  2. Ensure compatibility with PowerShell 7+
  3. Update this README if adding new features

License

[Add your license information here]

Support

For issues, questions, or contributions:

  • GitHub Issues: [Your repository URL]
  • Security Disclosures: [Your security contact]

⚠️ Important: These tools detect known IOCs from specific attacks. They do not guarantee complete security. Always practice defense-in-depth and use multiple security tools.

Last Updated: 2025-12-15

02-echo:0.0.7
@accordproject/concerto-analysis:3.24.1
@accordproject/concerto-linter-default-ruleset:3.24.1
@accordproject/concerto-linter:3.24.1
@accordproject/concerto-metamodel:3.12.5
@accordproject/concerto-types:3.24.1
@accordproject/markdown-it-cicero:0.16.26
@accordproject/template-engine:2.7.2
@actbase/css-to-react-native-transform:1.0.3
@actbase/native:0.1.32
@actbase/node-server:1.1.19
@actbase/react-absolute:0.8.3
@actbase/react-daum-postcode:1.0.5
@actbase/react-kakaosdk:0.9.27
@actbase/react-native-actionsheet:1.0.3
@actbase/react-native-devtools:0.1.3
@actbase/react-native-fast-image:8.5.13
@actbase/react-native-kakao-channel:1.0.2
@actbase/react-native-kakao-navi:2.0.4
@actbase/react-native-less-transformer:1.0.6
@actbase/react-native-naver-login:1.0.1
@actbase/react-native-simple-video:1.0.13
@actbase/react-native-tiktok:1.1.3
@afetcan/api:0.0.13
@afetcan/storage:0.0.27
@ahmedhfarag/ngx-perfect-scrollbar:20.0.20
@ahmedhfarag/ngx-virtual-scroller:4.0.4
@alaan/s2s-auth:2.0.3
@alexadark/amadeus-api:1.0.4
@alexadark/gatsby-theme-events:1.0.1
@alexadark/gatsby-theme-wordpress-blog:2.0.1
@alexadark/reusable-functions:1.5.1
@alexcolls/nuxt-socket.io:0.0.7
@alexcolls/nuxt-socket.io:0.0.8
@alexcolls/nuxt-ux:0.6.1
@alexcolls/nuxt-ux:0.6.2
@antstackio/eslint-config-antstack:0.0.3
@antstackio/express-graphql-proxy:0.2.8
@antstackio/graphql-body-parser:0.1.1
@antstackio/json-to-graphql:1.0.3
@antstackio/shelbysam:1.1.7
@art-ws/common:2.0.22
@art-ws/common:2.0.28
@art-ws/config-eslint:2.0.4
@art-ws/config-eslint:2.0.5
@art-ws/config-ts:2.0.7
@art-ws/config-ts:2.0.8
@art-ws/db-context:2.0.21
@art-ws/db-context:2.0.24
@art-ws/di-node:2.0.13
@art-ws/di:2.0.28
@art-ws/di:2.0.32
@art-ws/eslint:1.0.5
@art-ws/eslint:1.0.6
@art-ws/fastify-http-server:2.0.24
@art-ws/fastify-http-server:2.0.27
@art-ws/http-server:2.0.21
@art-ws/http-server:2.0.25
@art-ws/openapi:0.1.12
@art-ws/openapi:0.1.9
@art-ws/package-base:1.0.5
@art-ws/package-base:1.0.6
@art-ws/prettier:1.0.5
@art-ws/prettier:1.0.6
@art-ws/slf:2.0.15
@art-ws/slf:2.0.22
@art-ws/ssl-info:1.0.10
@art-ws/ssl-info:1.0.9
@art-ws/web-app:1.0.3
@art-ws/web-app:1.0.4
@aryanhussain/my-angular-lib:0.0.23
@asyncapi/avro-schema-parser:3.0.25
@asyncapi/avro-schema-parser:3.0.26
@asyncapi/bundler:0.6.5
@asyncapi/bundler:0.6.6
@asyncapi/cli:4.1.2
@asyncapi/cli:4.1.3
@asyncapi/converter:1.6.3
@asyncapi/converter:1.6.4
@asyncapi/diff:0.5.1
@asyncapi/diff:0.5.2
@asyncapi/dotnet-rabbitmq-template:1.0.1
@asyncapi/dotnet-rabbitmq-template:1.0.2
@asyncapi/edavisualiser:1.2.1
@asyncapi/edavisualiser:1.2.2
@asyncapi/generator-components:0.3.2
@asyncapi/generator-components:0.3.3
@asyncapi/generator-helpers:0.2.1
@asyncapi/generator-helpers:0.2.2
@asyncapi/generator-react-sdk:1.1.4
@asyncapi/generator-react-sdk:1.1.5
@asyncapi/generator:2.8.5
@asyncapi/generator:2.8.6
@asyncapi/go-watermill-template:0.2.76
@asyncapi/go-watermill-template:0.2.77
@asyncapi/html-template:3.3.2
@asyncapi/html-template:3.3.3
@asyncapi/java-spring-cloud-stream-template:0.13.5
@asyncapi/java-spring-cloud-stream-template:0.13.6
@asyncapi/java-spring-template:1.6.1
@asyncapi/java-spring-template:1.6.2
@asyncapi/java-template:0.3.5
@asyncapi/java-template:0.3.6
@asyncapi/keeper:0.0.2
@asyncapi/keeper:0.0.3
@asyncapi/markdown-template:1.6.8
@asyncapi/markdown-template:1.6.9
@asyncapi/modelina-cli:5.10.2
@asyncapi/modelina-cli:5.10.3
@asyncapi/modelina:5.10.2
@asyncapi/modelina:5.10.3
@asyncapi/multi-parser:2.2.1
@asyncapi/multi-parser:2.2.2
@asyncapi/nodejs-template:3.0.5
@asyncapi/nodejs-template:3.0.6
@asyncapi/nodejs-ws-template:0.10.1
@asyncapi/nodejs-ws-template:0.10.2
@asyncapi/nunjucks-filters:2.1.1
@asyncapi/nunjucks-filters:2.1.2
@asyncapi/openapi-schema-parser:3.0.25
@asyncapi/openapi-schema-parser:3.0.26
@asyncapi/optimizer:1.0.5
@asyncapi/optimizer:1.0.6
@asyncapi/parser:3.4.1
@asyncapi/parser:3.4.2
@asyncapi/php-template:0.1.1
@asyncapi/php-template:0.1.2
@asyncapi/problem:1.0.1
@asyncapi/problem:1.0.2
@asyncapi/protobuf-schema-parser:3.5.2
@asyncapi/protobuf-schema-parser:3.5.3
@asyncapi/protobuf-schema-parser:3.6.1
@asyncapi/python-paho-template:0.2.14
@asyncapi/python-paho-template:0.2.15
@asyncapi/react-component:2.6.6
@asyncapi/react-component:2.6.7
@asyncapi/server-api:0.16.24
@asyncapi/server-api:0.16.25
@asyncapi/specs:6.10.1
@asyncapi/specs:6.8.2
@asyncapi/specs:6.8.3
@asyncapi/specs:6.9.1
@asyncapi/studio:1.0.2
@asyncapi/studio:1.0.3
@asyncapi/web-component:2.6.6
@asyncapi/web-component:2.6.7
@basic-ui-components-stc/basic-ui-components:1.0.5
@bdkinc/knex-ibmi:0.5.7
@browserbasehq/bb9:1.2.21
@browserbasehq/director-ai:1.0.3
@browserbasehq/mcp-server-browserbase:2.4.2
@browserbasehq/mcp:2.1.1
@browserbasehq/sdk-functions:0.0.4
@browserbasehq/stagehand-docs:1.0.1
@browserbasehq/stagehand:3.0.4
@caretive/caret-cli:0.0.2
@chtijs/eslint-config:1.0.1
@clausehq/flows-step-httprequest:0.1.14
@clausehq/flows-step-jsontoxml:0.1.14
@clausehq/flows-step-mqtt:0.1.14
@clausehq/flows-step-sendgridemail:0.1.14
@clausehq/flows-step-taskscreateurl:0.1.14
@cllbk/ghl:1.3.1
@commute/bloom:1.0.3
@commute/market-data-chartjs:2.3.1
@commute/market-data:1.0.2
@coveops/abi:2.0.1
@crowdstrike/commitlint:8.1.1
@crowdstrike/commitlint:8.1.2
@crowdstrike/falcon-shoelace:0.4.1
@crowdstrike/falcon-shoelace:0.4.2
@crowdstrike/foundry-js:0.19.1
@crowdstrike/foundry-js:0.19.2
@crowdstrike/glide-core:0.34.2
@crowdstrike/glide-core:0.34.3
@crowdstrike/logscale-dashboard:1.205.1
@crowdstrike/logscale-dashboard:1.205.2
@crowdstrike/logscale-file-editor:1.205.1
@crowdstrike/logscale-file-editor:1.205.2
@crowdstrike/logscale-parser-edit:1.205.1
@crowdstrike/logscale-parser-edit:1.205.2
@crowdstrike/logscale-search:1.205.1
@crowdstrike/logscale-search:1.205.2
@crowdstrike/tailwind-toucan-base:5.0.1
@crowdstrike/tailwind-toucan-base:5.0.2
@ctrl/deluge:1.2.0
@ctrl/deluge:7.2.1
@ctrl/deluge:7.2.2
@ctrl/golang-template:1.4.2
@ctrl/golang-template:1.4.3
@ctrl/magnet-link:4.0.3
@ctrl/magnet-link:4.0.4
@ctrl/ngx-codemirror:7.0.1
@ctrl/ngx-codemirror:7.0.2
@ctrl/ngx-csv:6.0.1
@ctrl/ngx-csv:6.0.2
@ctrl/ngx-emoji-mart:9.2.1
@ctrl/ngx-emoji-mart:9.2.2
@ctrl/ngx-rightclick:4.0.1
@ctrl/ngx-rightclick:4.0.2
@ctrl/qbittorrent:9.7.1
@ctrl/qbittorrent:9.7.2
@ctrl/react-adsense:2.0.1
@ctrl/react-adsense:2.0.2
@ctrl/shared-torrent:6.3.1
@ctrl/shared-torrent:6.3.2
@ctrl/tinycolor:4.1.1
@ctrl/tinycolor:4.1.2
@ctrl/torrent-file:4.1.1
@ctrl/torrent-file:4.1.2
@ctrl/transmission:7.3.1
@ctrl/ts-base32:4.0.1
@ctrl/ts-base32:4.0.2
@dev-blinq/ai-qa-logic:1.0.19
@dev-blinq/blinqioclient:1.0.21
@dev-blinq/cucumber-js:1.0.131
@dev-blinq/cucumber_client:1.0.738
@dev-blinq/ui-systems:1.0.93
@duckdb/duckdb-wasm:1.29.2
@duckdb/node-api:1.3.3
@duckdb/node-bindings:1.3.3
@ensdomains/address-encoder:1.1.5
@ensdomains/blacklist:1.0.1
@ensdomains/buffer:0.1.2
@ensdomains/ccip-read-cf-worker:0.0.4
@ensdomains/ccip-read-dns-gateway:0.1.1
@ensdomains/ccip-read-router:0.0.7
@ensdomains/ccip-read-worker-viem:0.0.4
@ensdomains/content-hash:3.0.1
@ensdomains/curvearithmetics:1.0.1
@ensdomains/cypress-metamask:1.2.1
@ensdomains/dnsprovejs:0.5.3
@ensdomains/dnssec-oracle-anchors:0.0.2
@ensdomains/dnssecoraclejs:0.2.9
@ensdomains/durin-middleware:0.0.2
@ensdomains/durin:0.1.2
@ensdomains/ens-archived-contracts:0.0.3
@ensdomains/ens-avatar:1.0.4
@ensdomains/ens-contracts:1.6.1
@ensdomains/ens-test-env:1.0.2
@ensdomains/ens-validation:0.1.1
@ensdomains/ensjs-react:0.0.5
@ensdomains/ensjs:4.0.3
@ensdomains/eth-ens-namehash:2.0.16
@ensdomains/hackathon-registrar:1.0.5
@ensdomains/hardhat-chai-matchers-viem:0.1.15
@ensdomains/hardhat-toolbox-viem-extended:0.0.6
@ensdomains/mock:2.1.52
@ensdomains/name-wrapper:1.0.1
@ensdomains/offchain-resolver-contracts:0.2.2
@ensdomains/op-resolver-contracts:0.0.2
@ensdomains/react-ens-address:0.0.32
@ensdomains/renewal-widget:0.1.10
@ensdomains/renewal:0.0.13
@ensdomains/reverse-records:1.0.1
@ensdomains/server-analytics:0.0.2
@ensdomains/solsha1:0.0.4
@ensdomains/subdomain-registrar:0.2.4
@ensdomains/test-utils:1.3.1
@ensdomains/thorin:0.6.51
@ensdomains/ui:3.4.6
@ensdomains/unicode-confusables:0.1.1
@ensdomains/unruggable-gateways:0.0.3
@ensdomains/vite-plugin-i18next-loader:4.0.4
@ensdomains/web3modal:1.10.2
@everreal/react-charts:2.0.2
@everreal/validate-esmoduleinterop-imports:1.4.4
@everreal/validate-esmoduleinterop-imports:1.4.5
@everreal/web-analytics:0.0.2
@faq-component/core:0.0.4
@faq-component/react:1.0.1
@fishingbooker/browser-sync-plugin:1.0.5
@fishingbooker/react-loader:1.0.7
@fishingbooker/react-pagination:2.0.6
@fishingbooker/react-raty:2.0.1
@fishingbooker/react-swiper:0.1.5
@hapheus/n8n-nodes-pgp:1.5.1
@hestjs/core:0.2.1
@hestjs/core:1.0.1
@hestjs/core:1.0.2
@hestjs/cqrs:0.1.6
@hestjs/demo:0.1.2
@hestjs/eslint-config:0.1.2
@hestjs/logger:0.1.6
@hestjs/scalar:0.1.7
@hestjs/validation:0.1.6
@hover-design/core:0.0.1
@hover-design/react:0.2.1
@huntersofbook/auth-vue:0.4.2
@huntersofbook/core-nuxt:0.4.2
@huntersofbook/core:0.5.1
@huntersofbook/form-naiveui:0.5.1
@huntersofbook/i18n:0.8.2
@huntersofbook/ui:0.5.1
@hyperlook/telemetry-sdk:1.0.19
@ifelsedeveloper/protocol-contracts-svm-idl:0.1.2
@ifelsedeveloper/protocol-contracts-svm-idl:0.1.3
@ifings/design-system:4.9.2
@ifings/metatron3:0.1.5
@jayeshsadhwani/telemetry-sdk:1.0.14
@kvytech/cli:0.0.7
@kvytech/components:0.0.2
@kvytech/habbit-e2e-test:0.0.2
@kvytech/medusa-plugin-announcement:0.0.8
@kvytech/medusa-plugin-management:0.0.5
@kvytech/medusa-plugin-newsletter:0.0.5
@kvytech/medusa-plugin-product-reviews:0.0.9
@kvytech/medusa-plugin-promotion:0.0.2
@kvytech/web:0.0.2
@lessondesk/api-client:9.12.2
@lessondesk/api-client:9.12.3
@lessondesk/babel-preset:1.0.1
@lessondesk/electron-group-api-client:1.0.3
@lessondesk/eslint-config:1.4.2
@lessondesk/material-icons:1.0.3
@lessondesk/react-table-context:2.0.4
@lessondesk/schoolbus:5.2.2
@lessondesk/schoolbus:5.2.3
@livecms/live-edit:0.0.32
@livecms/nuxt-live-edit:1.9.2
@lokeswari-satyanarayanan/rn-zustand-expo-template:1.0.9
@louisle2/core:1.0.1
@louisle2/cortex-js:0.1.6
@lpdjs/firestore-repo-service:1.0.1
@lui-ui/lui-nuxt:0.1.1
@lui-ui/lui-tailwindcss:0.1.2
@lui-ui/lui-vue:1.0.13
@markvivanco/app-version-checker:1.0.1
@markvivanco/app-version-checker:1.0.2
@mcp-use/cli:2.2.6
@mcp-use/cli:2.2.7
@mcp-use/inspector:0.6.2
@mcp-use/inspector:0.6.3
@mcp-use/mcp-use:1.0.1
@mcp-use/mcp-use:1.0.2
@micado-digital/stadtmarketing-kufstein-external:1.9.1
@mizzle-dev/orm:0.0.2
@mparpaillon/connector-parse:1.0.1
@mparpaillon/imagesloaded:4.1.2
@mparpaillon/page:1.0.1
@nativescript-community/arraybuffers:1.1.6
@nativescript-community/arraybuffers:1.1.7
@nativescript-community/arraybuffers:1.1.8
@nativescript-community/gesturehandler:2.0.35
@nativescript-community/perms:3.0.5
@nativescript-community/perms:3.0.6
@nativescript-community/perms:3.0.7
@nativescript-community/perms:3.0.8
@nativescript-community/perms:3.0.9
@nativescript-community/push:1.0.0
@nativescript-community/sentry:4.6.43
@nativescript-community/sqlite:3.5.2
@nativescript-community/sqlite:3.5.3
@nativescript-community/sqlite:3.5.4
@nativescript-community/sqlite:3.5.5
@nativescript-community/text:1.6.10
@nativescript-community/text:1.6.11
@nativescript-community/text:1.6.12
@nativescript-community/text:1.6.13
@nativescript-community/text:1.6.9
@nativescript-community/typeorm:0.2.30
@nativescript-community/typeorm:0.2.31
@nativescript-community/typeorm:0.2.32
@nativescript-community/typeorm:0.2.33
@nativescript-community/ui-collectionview:6.0.6
@nativescript-community/ui-document-picker:1.1.27
@nativescript-community/ui-document-picker:1.1.28
@nativescript-community/ui-drawer:0.1.30
@nativescript-community/ui-image:4.5.6
@nativescript-community/ui-label:1.3.35
@nativescript-community/ui-label:1.3.36
@nativescript-community/ui-label:1.3.37
@nativescript-community/ui-material-activityindicator:7.2.49
@nativescript-community/ui-material-bottom-navigation:7.2.72
@nativescript-community/ui-material-bottom-navigation:7.2.73
@nativescript-community/ui-material-bottom-navigation:7.2.74
@nativescript-community/ui-material-bottom-navigation:7.2.75
@nativescript-community/ui-material-bottomnavigationbar:7.2.49
@nativescript-community/ui-material-bottomsheet:7.2.49
@nativescript-community/ui-material-bottomsheet:7.2.72
@nativescript-community/ui-material-button:7.2.49
@nativescript-community/ui-material-cardview:7.2.49
@nativescript-community/ui-material-core-tabs:7.2.72
@nativescript-community/ui-material-core-tabs:7.2.73
@nativescript-community/ui-material-core-tabs:7.2.74
@nativescript-community/ui-material-core-tabs:7.2.75
@nativescript-community/ui-material-core-tabs:7.2.76
@nativescript-community/ui-material-core:7.2.49
@nativescript-community/ui-material-core:7.2.72
@nativescript-community/ui-material-core:7.2.73
@nativescript-community/ui-material-core:7.2.74
@nativescript-community/ui-material-core:7.2.75
@nativescript-community/ui-material-core:7.2.76
@nativescript-community/ui-material-dialogs:7.2.49
@nativescript-community/ui-material-floatingactionbutton:7.2.49
@nativescript-community/ui-material-progress:7.2.49
@nativescript-community/ui-material-ripple:7.2.49
@nativescript-community/ui-material-ripple:7.2.72
@nativescript-community/ui-material-ripple:7.2.73
@nativescript-community/ui-material-ripple:7.2.74
@nativescript-community/ui-material-ripple:7.2.75
@nativescript-community/ui-material-slider:7.2.49
@nativescript-community/ui-material-snackbar:7.2.49
@nativescript-community/ui-material-tabs:7.2.49
@nativescript-community/ui-material-tabs:7.2.72
@nativescript-community/ui-material-tabs:7.2.73
@nativescript-community/ui-material-tabs:7.2.74
@nativescript-community/ui-material-tabs:7.2.75
@nativescript-community/ui-material-textfield:7.2.49
@nativescript-community/ui-material-textview:7.2.49
@nativescript-community/ui-pager:14.1.35
@nativescript-community/ui-pager:14.1.36
@nativescript-community/ui-pager:14.1.37
@nativescript-community/ui-pager:14.1.38
@nativescript-community/ui-pulltorefresh:2.5.4
@nativescript-community/ui-pulltorefresh:2.5.5
@nativescript-community/ui-pulltorefresh:2.5.6
@nativescript-community/ui-pulltorefresh:2.5.7
@nexe/config-manager:0.1.1
@nexe/eslint-config:0.1.1
@nexe/logger:0.1.3
@nstudio/angular:20.0.4
@nstudio/angular:20.0.5
@nstudio/angular:20.0.6
@nstudio/focus:20.0.4
@nstudio/focus:20.0.5
@nstudio/focus:20.0.6
@nstudio/nativescript-checkbox:2.0.6
@nstudio/nativescript-checkbox:2.0.7
@nstudio/nativescript-checkbox:2.0.8
@nstudio/nativescript-checkbox:2.0.9
@nstudio/nativescript-loading-indicator:5.0.1
@nstudio/nativescript-loading-indicator:5.0.2
@nstudio/nativescript-loading-indicator:5.0.3
@nstudio/nativescript-loading-indicator:5.0.4
@nstudio/ui-collectionview:5.1.11
@nstudio/ui-collectionview:5.1.12
@nstudio/ui-collectionview:5.1.13
@nstudio/ui-collectionview:5.1.14
@nstudio/web-angular:20.0.4
@nstudio/web:20.0.4
@nstudio/xplat-utils:20.0.4
@nstudio/xplat-utils:20.0.5
@nstudio/xplat-utils:20.0.6
@nstudio/xplat-utils:20.0.7
@nstudio/xplat:20.0.4
@nstudio/xplat:20.0.5
@nstudio/xplat:20.0.6
@nstudio/xplat:20.0.7
@ntnx/passport-wso2:0.0.3
@ntnx/t:0.0.101
@oku-ui/accordion:0.6.2
@oku-ui/alert-dialog:0.6.2
@oku-ui/arrow:0.6.2
@oku-ui/aspect-ratio:0.6.2
@oku-ui/avatar:0.6.2
@oku-ui/checkbox:0.6.3
@oku-ui/collapsible:0.6.2
@oku-ui/collection:0.6.2
@oku-ui/dialog:0.6.2
@oku-ui/direction:0.6.2
@oku-ui/dismissable-layer:0.6.2
@oku-ui/focus-guards:0.6.2
@oku-ui/focus-scope:0.6.2
@oku-ui/hover-card:0.6.2
@oku-ui/label:0.6.2
@oku-ui/menu:0.6.2
@oku-ui/motion-nuxt:0.2.2
@oku-ui/motion:0.4.4
@oku-ui/popover:0.6.2
@oku-ui/popper:0.6.2
@oku-ui/portal:0.6.2
@oku-ui/presence:0.6.2
@oku-ui/primitive:0.6.2
@oku-ui/primitives-nuxt:0.3.1
@oku-ui/primitives:0.7.9
@oku-ui/progress:0.6.2
@oku-ui/provide:0.6.2
@oku-ui/radio-group:0.6.2
@oku-ui/roving-focus:0.6.2
@oku-ui/scroll-area:0.6.2
@oku-ui/separator:0.6.2
@oku-ui/slider:0.6.2
@oku-ui/slot:0.6.2
@oku-ui/switch:0.6.2
@oku-ui/tabs:0.6.2
@oku-ui/toast:0.6.2
@oku-ui/toggle-group:0.6.2
@oku-ui/toggle:0.6.2
@oku-ui/toolbar:0.6.2
@oku-ui/tooltip:0.6.2
@oku-ui/use-composable:0.6.2
@oku-ui/utils:0.6.2
@oku-ui/visually-hidden:0.6.2
@operato/board:9.0.35
@operato/board:9.0.36
@operato/board:9.0.37
@operato/board:9.0.38
@operato/board:9.0.39
@operato/board:9.0.40
@operato/board:9.0.41
@operato/board:9.0.42
@operato/board:9.0.43
@operato/board:9.0.44
@operato/board:9.0.45
@operato/board:9.0.46
@operato/board:9.0.47
@operato/board:9.0.48
@operato/board:9.0.49
@operato/board:9.0.50
@operato/board:9.0.51
@operato/data-grist:9.0.29
@operato/data-grist:9.0.35
@operato/data-grist:9.0.36
@operato/data-grist:9.0.37
@operato/graphql:9.0.22
@operato/graphql:9.0.35
@operato/graphql:9.0.36
@operato/graphql:9.0.37
@operato/graphql:9.0.38
@operato/graphql:9.0.39
@operato/graphql:9.0.40
@operato/graphql:9.0.41
@operato/graphql:9.0.42
@operato/graphql:9.0.43
@operato/graphql:9.0.44
@operato/graphql:9.0.45
@operato/graphql:9.0.46
@operato/graphql:9.0.47
@operato/graphql:9.0.48
@operato/graphql:9.0.49
@operato/graphql:9.0.50
@operato/graphql:9.0.51
@operato/headroom:9.0.2
@operato/headroom:9.0.35
@operato/headroom:9.0.36
@operato/headroom:9.0.37
@operato/help:9.0.35
@operato/help:9.0.36
@operato/help:9.0.37
@operato/help:9.0.38
@operato/help:9.0.39
@operato/help:9.0.40
@operato/help:9.0.41
@operato/help:9.0.42
@operato/help:9.0.43
@operato/help:9.0.44
@operato/help:9.0.45
@operato/help:9.0.46
@operato/help:9.0.47
@operato/help:9.0.48
@operato/help:9.0.49
@operato/help:9.0.50
@operato/help:9.0.51
@operato/i18n:9.0.35
@operato/i18n:9.0.36
@operato/i18n:9.0.37
@operato/input:9.0.27
@operato/input:9.0.35
@operato/input:9.0.36
@operato/input:9.0.37
@operato/input:9.0.38
@operato/input:9.0.39
@operato/input:9.0.40
@operato/input:9.0.41
@operato/input:9.0.42
@operato/input:9.0.43
@operato/input:9.0.44
@operato/input:9.0.45
@operato/input:9.0.46
@operato/input:9.0.47
@operato/input:9.0.48
@operato/layout:9.0.35
@operato/layout:9.0.36
@operato/layout:9.0.37
@operato/popup:9.0.22
@operato/popup:9.0.35
@operato/popup:9.0.36
@operato/popup:9.0.37
@operato/popup:9.0.38
@operato/popup:9.0.39
@operato/popup:9.0.40
@operato/popup:9.0.41
@operato/popup:9.0.42
@operato/popup:9.0.43
@operato/popup:9.0.44
@operato/popup:9.0.45
@operato/popup:9.0.46
@operato/popup:9.0.47
@operato/popup:9.0.48
@operato/popup:9.0.49
@operato/popup:9.0.50
@operato/popup:9.0.51
@operato/pull-to-refresh:9.0.35
@operato/pull-to-refresh:9.0.36
@operato/pull-to-refresh:9.0.37
@operato/pull-to-refresh:9.0.38
@operato/pull-to-refresh:9.0.39
@operato/pull-to-refresh:9.0.40
@operato/pull-to-refresh:9.0.41
@operato/pull-to-refresh:9.0.42
@operato/pull-to-refresh:9.0.43
@operato/pull-to-refresh:9.0.44
@operato/pull-to-refresh:9.0.45
@operato/pull-to-refresh:9.0.46
@operato/pull-to-refresh:9.0.47
@operato/shell:9.0.22
@operato/shell:9.0.35
@operato/shell:9.0.36
@operato/shell:9.0.37
@operato/shell:9.0.38
@operato/shell:9.0.39
@operato/styles:9.0.2
@operato/styles:9.0.35
@operato/styles:9.0.36
@operato/styles:9.0.37
@operato/utils:9.0.22
@operato/utils:9.0.35
@operato/utils:9.0.36
@operato/utils:9.0.37
@operato/utils:9.0.38
@operato/utils:9.0.39
@operato/utils:9.0.40
@operato/utils:9.0.41
@operato/utils:9.0.42
@operato/utils:9.0.43
@operato/utils:9.0.44
@operato/utils:9.0.45
@operato/utils:9.0.46
@operato/utils:9.0.47
@operato/utils:9.0.48
@operato/utils:9.0.49
@operato/utils:9.0.50
@operato/utils:9.0.51
@orbitgtbelgium/mapbox-gl-draw-cut-polygon-mode:2.0.5
@orbitgtbelgium/mapbox-gl-draw-scale-rotate-mode:1.1.1
@orbitgtbelgium/orbit-components:1.2.9
@orbitgtbelgium/time-slider:1.0.187
@osmanekrem/bmad:1.0.6
@osmanekrem/error-handler:1.2.2
@pergel/cli:0.11.1
@pergel/module-box:0.6.1
@pergel/module-graphql:0.6.1
@pergel/module-ui:0.0.9
@pergel/nuxt:0.25.5
@posthog/agent:1.24.1
@posthog/ai:7.1.2
@posthog/automatic-cohorts-plugin:0.0.8
@posthog/bitbucket-release-tracker:0.0.8
@posthog/cli:0.5.15
@posthog/clickhouse:1.7.1
@posthog/core:1.5.6
@posthog/currency-normalization-plugin:0.0.8
@posthog/customerio-plugin:0.0.8
@posthog/databricks-plugin:0.0.8
@posthog/drop-events-on-property-plugin:0.0.8
@posthog/event-sequence-timer-plugin:0.0.8
@posthog/filter-out-plugin:0.0.8
@posthog/first-time-event-tracker:0.0.8
@posthog/geoip-plugin:0.0.8
@posthog/github-release-tracking-plugin:0.0.8
@posthog/gitub-star-sync-plugin:0.0.8
@posthog/heartbeat-plugin:0.0.8
@posthog/hedgehog-mode:0.0.42
@posthog/icons:0.36.1
@posthog/ingestion-alert-plugin:0.0.8
@posthog/intercom-plugin:0.0.8
@posthog/kinesis-plugin:0.0.8
@posthog/laudspeaker-plugin:0.0.8
@posthog/lemon-ui:0.0.1
@posthog/maxmind-plugin:0.1.6
@posthog/migrator3000-plugin:0.0.8
@posthog/netdata-event-processing:0.0.8
@posthog/nextjs-config:1.5.1
@posthog/nextjs:0.0.3
@posthog/nuxt:1.2.9
@posthog/pagerduty-plugin:0.0.8
@posthog/piscina:3.2.1
@posthog/plugin-contrib:0.0.6
@posthog/plugin-server:1.10.8
@posthog/plugin-unduplicates:0.0.8
@posthog/postgres-plugin:0.0.8
@posthog/react-rrweb-player:1.1.4
@posthog/rrdom:0.0.31
@posthog/rrweb-player:0.0.31
@posthog/rrweb-record:0.0.31
@posthog/rrweb-replay:0.0.19
@posthog/rrweb-snapshot:0.0.31
@posthog/rrweb-utils:0.0.31
@posthog/rrweb:0.0.31
@posthog/sendgrid-plugin:0.0.8
@posthog/siphash:1.1.2
@posthog/snowflake-export-plugin:0.0.8
@posthog/taxonomy-plugin:0.0.8
@posthog/twilio-plugin:0.0.8
@posthog/twitter-followers-plugin:0.0.8
@posthog/url-normalizer-plugin:0.0.8
@posthog/variance-plugin:0.0.8
@posthog/web-dev-server:1.0.5
@posthog/wizard:1.18.1
@posthog/zendesk-plugin:0.0.8
@postman/aether-icons:2.23.2
@postman/aether-icons:2.23.4
@postman/csv-parse:4.0.3
@postman/csv-parse:4.0.4
@postman/csv-parse:4.0.5
@postman/final-node-keytar:7.9.1
@postman/final-node-keytar:7.9.2
@postman/final-node-keytar:7.9.3
@postman/mcp-ui-client:5.5.1
@postman/mcp-ui-client:5.5.2
@postman/mcp-ui-client:5.5.3
@postman/node-keytar:7.9.4
@postman/node-keytar:7.9.5
@postman/node-keytar:7.9.6
@postman/pm-bin-linux-x64:1.24.3
@postman/pm-bin-linux-x64:1.24.4
@postman/pm-bin-linux-x64:1.24.5
@postman/pm-bin-macos-arm64:1.24.3
@postman/pm-bin-macos-arm64:1.24.4
@postman/pm-bin-macos-arm64:1.24.5
@postman/pm-bin-macos-x64:1.24.3
@postman/pm-bin-macos-x64:1.24.5
@postman/pm-bin-windows-x64:1.24.3
@postman/pm-bin-windows-x64:1.24.4
@postman/pm-bin-windows-x64:1.24.5
@postman/postman-collection-fork:4.3.3
@postman/postman-collection-fork:4.3.4
@postman/postman-collection-fork:4.3.5
@postman/postman-mcp-cli:1.0.3
@postman/postman-mcp-cli:1.0.4
@postman/postman-mcp-cli:1.0.5
@postman/postman-mcp-server:2.4.10
@postman/postman-mcp-server:2.4.11
@postman/postman-mcp-server:2.4.12
@postman/pretty-ms:6.1.1
@postman/pretty-ms:6.1.2
@postman/pretty-ms:6.1.3
@postman/secret-scanner-wasm:2.1.2
@postman/secret-scanner-wasm:2.1.3
@postman/secret-scanner-wasm:2.1.4
@postman/tunnel-agent:0.6.5
@postman/tunnel-agent:0.6.6
@postman/tunnel-agent:0.6.7
@postman/wdio-allure-reporter:0.0.7
@postman/wdio-allure-reporter:0.0.9
@postman/wdio-junit-reporter:0.0.4
@postman/wdio-junit-reporter:0.0.5
@postman/wdio-junit-reporter:0.0.6
@pradhumngautam/common-app:1.0.2
@productdevbook/animejs-vue:0.2.1
@productdevbook/auth:0.2.2
@productdevbook/chatwoot:2.0.1
@productdevbook/motion:1.0.4
@productdevbook/ts-i18n:1.4.2
@pruthvi21/use-debounce:1.0.3
@quick-start-soft/quick-document-translator:1.4.2511142126
@quick-start-soft/quick-git-clean-markdown:1.4.2511142126
@quick-start-soft/quick-markdown-compose:1.4.2506300029
@quick-start-soft/quick-markdown-image:1.4.2511142126
@quick-start-soft/quick-markdown-print:1.4.2511142126
@quick-start-soft/quick-markdown-translator:1.4.2509202331
@quick-start-soft/quick-markdown:1.4.2511142126
@quick-start-soft/quick-remove-image-background:1.4.2511142126
@quick-start-soft/quick-task-refine:1.4.2511142126
@relyt/claude-context-core:0.1.1
@relyt/claude-context-mcp:0.1.1
@relyt/mcp-server-relytone:0.0.3
@rxap/ngx-bootstrap:19.0.3
@rxap/ngx-bootstrap:19.0.4
@sameepsi/sor2:2.0.2
@sameepsi/sor:1.0.3
@seezo/sdr-mcp-server:0.0.5
@seung-ju/next:0.0.2
@seung-ju/openapi-generator:0.0.4
@seung-ju/react-hooks:0.0.2
@seung-ju/react-native-action-sheet:0.2.1
@silgi/better-auth:0.8.1
@silgi/drizzle:0.8.4
@silgi/ecosystem:0.7.6
@silgi/graphql:0.7.15
@silgi/module-builder:0.8.8
@silgi/openapi:0.7.4
@silgi/permission:0.6.8
@silgi/ratelimit:0.2.1
@silgi/scalar:0.6.2
@silgi/yoga:0.7.1
@sme-ui/aoma-vevasound-metadata-lib:0.1.3
@strapbuild/react-native-date-time-picker:2.0.4
@strapbuild/react-native-perspective-image-cropper-2:0.4.7
@strapbuild/react-native-perspective-image-cropper-poojan31:0.4.6
@strapbuild/react-native-perspective-image-cropper:0.4.15
@suraj_h/medium-common:1.0.5
@teselagen/bio-parsers:0.4.29
@teselagen/bio-parsers:0.4.30
@teselagen/bounce-loader:0.3.16
@teselagen/bounce-loader:0.3.17
@teselagen/file-utils:0.3.21
@teselagen/file-utils:0.3.22
@teselagen/liquibase-tools:0.4.1
@teselagen/ove:0.7.39
@teselagen/ove:0.7.40
@teselagen/range-utils:0.3.14
@teselagen/range-utils:0.3.15
@teselagen/react-list:0.8.19
@teselagen/react-list:0.8.20
@teselagen/react-table:6.10.19
@teselagen/react-table:6.10.20
@teselagen/react-table:6.10.21
@teselagen/react-table:6.10.22
@teselagen/sequence-utils:0.3.33
@teselagen/sequence-utils:0.3.34
@teselagen/ui:0.9.10
@teselagen/ui:0.9.9
@thangved/callback-window:1.1.4
@thedelta/eslint-config:1.0.2
@things-factory/attachment-base:9.0.42
@things-factory/attachment-base:9.0.43
@things-factory/attachment-base:9.0.44
@things-factory/attachment-base:9.0.45
@things-factory/attachment-base:9.0.46
@things-factory/attachment-base:9.0.47
@things-factory/attachment-base:9.0.48
@things-factory/attachment-base:9.0.49
@things-factory/attachment-base:9.0.50
@things-factory/attachment-base:9.0.51
@things-factory/attachment-base:9.0.52
@things-factory/attachment-base:9.0.53
@things-factory/attachment-base:9.0.54
@things-factory/attachment-base:9.0.55
@things-factory/auth-base:9.0.42
@things-factory/auth-base:9.0.43
@things-factory/auth-base:9.0.44
@things-factory/auth-base:9.0.45
@things-factory/email-base:9.0.42
@things-factory/email-base:9.0.43
@things-factory/email-base:9.0.44
@things-factory/email-base:9.0.45
@things-factory/email-base:9.0.46
@things-factory/email-base:9.0.47
@things-factory/email-base:9.0.48
@things-factory/email-base:9.0.49
@things-factory/email-base:9.0.50
@things-factory/email-base:9.0.51
@things-factory/email-base:9.0.52
@things-factory/email-base:9.0.53
@things-factory/email-base:9.0.54
@things-factory/email-base:9.0.55
@things-factory/email-base:9.0.56
@things-factory/email-base:9.0.57
@things-factory/email-base:9.0.58
@things-factory/email-base:9.0.59
@things-factory/env:9.0.42
@things-factory/env:9.0.43
@things-factory/env:9.0.44
@things-factory/env:9.0.45
@things-factory/integration-base:9.0.42
@things-factory/integration-base:9.0.43
@things-factory/integration-base:9.0.44
@things-factory/integration-base:9.0.45
@things-factory/integration-marketplace:9.0.42
@things-factory/integration-marketplace:9.0.43
@things-factory/integration-marketplace:9.0.44
@things-factory/integration-marketplace:9.0.45
@things-factory/shell:9.0.42
@things-factory/shell:9.0.43
@things-factory/shell:9.0.44
@things-factory/shell:9.0.45
@tiaanduplessis/json:2.0.2
@tiaanduplessis/json:2.0.3
@tiaanduplessis/react-progressbar:1.0.1
@tiaanduplessis/react-progressbar:1.0.2
@tnf-dev/api:1.0.8
@tnf-dev/core:1.0.8
@tnf-dev/js:1.0.8
@tnf-dev/mui:1.0.8
@tnf-dev/react:1.0.8
@trackstar/angular-trackstar-link:1.0.2
@trackstar/react-trackstar-link-upgrade:1.1.10
@trackstar/react-trackstar-link:2.0.21
@trackstar/test-angular-package:0.0.9
@trackstar/test-package:1.1.5
@trefox/sleekshop-js:0.1.6
@trigo/atrix-acl:4.0.2
@trigo/atrix-elasticsearch:2.0.1
@trigo/atrix-mongoose:1.0.2
@trigo/atrix-orientdb:1.0.2
@trigo/atrix-postgres:1.0.3
@trigo/atrix-pubsub:4.0.3
@trigo/atrix-redis:1.0.2
@trigo/atrix-soap:1.0.2
@trigo/atrix-swagger:3.0.1
@trigo/atrix:7.0.1
@trigo/bool-expressions:4.1.3
@trigo/eslint-config-trigo:3.3.1
@trigo/fsm:3.4.2
@trigo/hapi-auth-signedlink:1.3.1
@trigo/jsdt:0.2.1
@trigo/keycloak-api:1.3.1
@trigo/node-soap:0.5.4
@trigo/pathfinder-ui-css:0.1.1
@trigo/trigo-hapijs:5.0.1
@trpc-rate-limiter/cloudflare:0.1.4
@trpc-rate-limiter/hono:0.1.4
@ui-ux-gang/devextreme-angular-rpk:24.1.7
@ui-ux-gang/devextreme-rpk:24.1.7
@varsityvibe/api-client:1.3.36
@varsityvibe/api-client:1.3.37
@varsityvibe/utils:5.0.6
@varsityvibe/validation-schemas:0.6.7
@varsityvibe/validation-schemas:0.6.8
@viapip/eslint-config:0.2.4
@vishadtyagi/full-year-calendar:0.1.11
@voiceflow/alexa-types:2.15.61
@voiceflow/anthropic:0.4.4
@voiceflow/anthropic:0.4.5
@voiceflow/api-sdk:3.28.59
@voiceflow/backend-utils:5.0.1
@voiceflow/backend-utils:5.0.2
@voiceflow/base-types:2.136.2
@voiceflow/base-types:2.136.3
@voiceflow/body-parser:1.21.2
@voiceflow/body-parser:1.21.3
@voiceflow/chat-types:2.14.58
@voiceflow/chat-types:2.14.59
@voiceflow/circleci-config-sdk-orb-import:0.2.1
@voiceflow/circleci-config-sdk-orb-import:0.2.2
@voiceflow/commitlint-config:2.6.1
@voiceflow/commitlint-config:2.6.2
@voiceflow/common:8.9.1
@voiceflow/common:8.9.2
@voiceflow/default-prompt-wrappers:1.7.3
@voiceflow/default-prompt-wrappers:1.7.4
@voiceflow/dependency-cruiser-config:1.8.11
@voiceflow/dependency-cruiser-config:1.8.12
@voiceflow/dtos-interact:1.40.1
@voiceflow/dtos-interact:1.40.2
@voiceflow/encryption:0.3.2
@voiceflow/encryption:0.3.3
@voiceflow/eslint-config:7.16.4
@voiceflow/eslint-config:7.16.5
@voiceflow/eslint-plugin:1.6.1
@voiceflow/eslint-plugin:1.6.2
@voiceflow/exception:1.10.1
@voiceflow/exception:1.10.2
@voiceflow/fetch:1.11.1
@voiceflow/fetch:1.11.2
@voiceflow/general-types:3.2.22
@voiceflow/general-types:3.2.23
@voiceflow/git-branch-check:1.4.3
@voiceflow/git-branch-check:1.4.4
@voiceflow/google-dfes-types:2.17.12
@voiceflow/google-dfes-types:2.17.13
@voiceflow/google-types:2.21.13
@voiceflow/husky-config:1.3.1
@voiceflow/husky-config:1.3.2
@voiceflow/logger:2.4.2
@voiceflow/logger:2.4.3
@voiceflow/metrics:1.5.1
@voiceflow/metrics:1.5.2
@voiceflow/natural-language-commander:0.5.2
@voiceflow/natural-language-commander:0.5.3
@voiceflow/nestjs-common:2.75.2
@voiceflow/nestjs-common:2.75.3
@voiceflow/nestjs-mongodb:1.3.1
@voiceflow/nestjs-mongodb:1.3.2
@voiceflow/nestjs-rate-limit:1.3.2
@voiceflow/nestjs-rate-limit:1.3.3
@voiceflow/nestjs-redis:1.3.1
@voiceflow/nestjs-redis:1.3.2
@voiceflow/nestjs-timeout:1.3.1
@voiceflow/nestjs-timeout:1.3.2
@voiceflow/npm-package-json-lint-config:1.1.1
@voiceflow/openai:3.2.2
@voiceflow/openai:3.2.3
@voiceflow/pino-pretty:4.4.1
@voiceflow/pino-pretty:4.4.2
@voiceflow/pino:6.11.3
@voiceflow/pino:6.11.4
@voiceflow/prettier-config:1.10.1
@voiceflow/prettier-config:1.10.2
@voiceflow/react-chat:1.65.4
@voiceflow/runtime-client-js:1.17.2
@voiceflow/runtime-client-js:1.17.3
@voiceflow/runtime:1.29.1
@voiceflow/runtime:1.29.2
@voiceflow/sdk-runtime:1.43.1
@voiceflow/sdk-runtime:1.43.2
@voiceflow/secrets-provider:1.9.2
@voiceflow/secrets-provider:1.9.3
@voiceflow/semantic-release-config:1.4.1
@voiceflow/semantic-release-config:1.4.2
@voiceflow/serverless-plugin-typescript:2.1.7
@voiceflow/serverless-plugin-typescript:2.1.8
@voiceflow/slate-serializer:1.7.3
@voiceflow/slate-serializer:1.7.4
@voiceflow/stitches-react:2.3.2
@voiceflow/stitches-react:2.3.3
@voiceflow/storybook-config:1.2.2
@voiceflow/storybook-config:1.2.3
@voiceflow/stylelint-config:1.1.1
@voiceflow/stylelint-config:1.1.2
@voiceflow/test-common:2.1.1
@voiceflow/test-common:2.1.2
@voiceflow/tsconfig-paths:1.1.4
@voiceflow/tsconfig-paths:1.1.5
@voiceflow/tsconfig:1.12.1
@voiceflow/tsconfig:1.12.2
@voiceflow/utils-designer:1.74.20
@voiceflow/verror:1.1.4
@voiceflow/verror:1.1.5
@voiceflow/vite-config:2.6.2
@voiceflow/vite-config:2.6.3
@voiceflow/vitest-config:1.10.2
@voiceflow/vitest-config:1.10.3
@voiceflow/voice-types:2.10.58
@voiceflow/voice-types:2.10.59
@voiceflow/voiceflow-types:3.32.45
@voiceflow/voiceflow-types:3.32.46
@voiceflow/widget:1.7.18
@voiceflow/widget:1.7.19
@vucod/email:0.0.3
@yoobic/design-system:6.5.17
@yoobic/jpeg-camera-es6:1.0.13
@yoobic/yobi:8.7.53
@zapier/ai-actions-react:0.1.12
@zapier/ai-actions-react:0.1.13
@zapier/ai-actions-react:0.1.14
@zapier/ai-actions:0.1.18
@zapier/ai-actions:0.1.19
@zapier/ai-actions:0.1.20
@zapier/babel-preset-zapier:6.4.1
@zapier/babel-preset-zapier:6.4.2
@zapier/babel-preset-zapier:6.4.3
@zapier/browserslist-config-zapier:1.0.3
@zapier/browserslist-config-zapier:1.0.4
@zapier/browserslist-config-zapier:1.0.5
@zapier/eslint-plugin-zapier:11.0.3
@zapier/eslint-plugin-zapier:11.0.4
@zapier/eslint-plugin-zapier:11.0.5
@zapier/mcp-integration:3.0.1
@zapier/mcp-integration:3.0.2
@zapier/mcp-integration:3.0.3
@zapier/secret-scrubber:1.1.3
@zapier/secret-scrubber:1.1.4
@zapier/secret-scrubber:1.1.5
@zapier/spectral-api-ruleset:1.9.1
@zapier/spectral-api-ruleset:1.9.2
@zapier/spectral-api-ruleset:1.9.3
@zapier/stubtree:0.1.2
@zapier/stubtree:0.1.3
@zapier/stubtree:0.1.4
@zapier/zapier-sdk:0.15.5
@zapier/zapier-sdk:0.15.6
@zapier/zapier-sdk:0.15.7
ace-colorpicker-rpk:0.0.14
ai-crowl-shield:1.0.7
airchief:0.3.1
airpilot:0.8.8
angulartics2:14.1.1
angulartics2:14.1.2
another-shai:1.0.1
ansi-regex:6.2.1
ansi-styles:6.2.2
arc-cli-fc:1.0.1
asciitranslator:1.0.3
asyncapi-preview:1.0.1
asyncapi-preview:1.0.2
atrix-mongoose:1.0.1
atrix:1.0.1
automation_model:1.0.491
avvvatars-vue:1.1.2
axios-builder:1.2.1
axios-cancelable:1.0.1
axios-cancelable:1.0.2
axios-timed:1.0.1
axios-timed:1.0.2
babel-preset-kinvey-flex-service:0.1.1
backslash:0.2.1
barebones-css:1.1.3
barebones-css:1.1.4
benmostyn-frame-print:1.0.1
best_gpio_controller:1.0.10
better-auth-nuxt:0.0.10
better-queue-nedb:0.1.5
bidirectional-adapter:1.2.2
bidirectional-adapter:1.2.3
bidirectional-adapter:1.2.4
bidirectional-adapter:1.2.5
blinqio-executions-cli:1.0.41
blob-to-base64:1.0.3
bool-expressions:0.1.2
browser-webdriver-downloader:3.0.8
buffered-interpolation-babylon6:0.2.8
bun-plugin-httpfile:0.1.1
bytecode-checker-cli:1.0.10
bytecode-checker-cli:1.0.11
bytecode-checker-cli:1.0.8
bytecode-checker-cli:1.0.9
bytes-to-x:1.0.1
calc-loan-interest:1.0.4
capacitor-notificationhandler:0.0.2
capacitor-notificationhandler:0.0.3
capacitor-plugin-apptrackingios:0.0.21
capacitor-plugin-healthapp:0.0.2
capacitor-plugin-healthapp:0.0.3
capacitor-plugin-ihealth:1.1.8
capacitor-plugin-ihealth:1.1.9
capacitor-plugin-purchase:0.1.1
capacitor-plugin-scgssigninwithgoogle:0.0.5
capacitor-plugin-vonage:1.0.2
capacitor-plugin-vonage:1.0.3
capacitor-purchase-history:0.0.10
capacitor-voice-recorder-wav:6.0.3
capacitorandroidpermissions:0.0.4
capacitorandroidpermissions:0.0.5
ceviz:0.0.5
chalk-template:1.1.1
chalk:5.6.1
chrome-extension-downloads:0.0.3
chrome-extension-downloads:0.0.4
claude-token-updater:1.0.3
coinmarketcap-api:3.1.2
coinmarketcap-api:3.1.3
color-convert:3.1.1
color-name:2.0.1
color-string:2.1.1
colors-regex:2.0.1
command-irail:0.5.4
compare-obj:1.1.1
compare-obj:1.1.2
composite-reducer:1.0.2
composite-reducer:1.0.3
composite-reducer:1.0.4
composite-reducer:1.0.5
config-cordova:0.8.5
cordova-plugin-voxeet2:1.0.24
cordova-voxeet:1.0.32
count-it-down:1.0.1
count-it-down:1.0.2
cpu-instructions:0.0.14
create-director-app:0.1.1
create-glee-app:0.2.2
create-glee-app:0.2.3
create-hardhat3-app:1.1.1
create-hardhat3-app:1.1.2
create-hardhat3-app:1.1.3
create-hardhat3-app:1.1.4
create-hest-app:0.1.9
create-kinvey-flex-service:0.2.1
create-mcp-use-app:0.5.3
create-mcp-use-app:0.5.4
create-silgi:0.3.1
crypto-addr-codec:0.1.9
css-dedoupe:0.1.2
csv-tool-cli:1.2.1
dashboard-empty-state:1.0.3
db-evo:1.1.4
db-evo:1.1.5
debug:4.4.2
designstudiouiux:1.0.1
devextreme-angular-rpk:21.2.8
devextreme-rpk:21.2.8
devstart-cli:1.0.6
dialogflow-es:1.1.2
dialogflow-es:1.1.3
dialogflow-es:1.1.4
discord-bot-server:0.1.2
docusaurus-plugin-vanilla-extract:1.0.3
dont-go:1.1.2
dotnet-template:0.0.3
dotnet-template:0.0.4
drop-events-on-property-plugin:0.0.2
duckdb:1.3.3
easypanel-sdk:0.3.2
electron-volt:0.0.2
email-deliverability-tester:1.1.1
ember-browser-services:5.0.2
ember-browser-services:5.0.3
ember-headless-form-yup:1.0.1
ember-headless-form:1.1.2
ember-headless-form:1.1.3
ember-headless-table:2.1.5
ember-headless-table:2.1.6
ember-url-hash-polyfill:1.0.12
ember-url-hash-polyfill:1.0.13
ember-velcro:2.2.1
ember-velcro:2.2.2
encounter-playground:0.0.2
encounter-playground:0.0.3
encounter-playground:0.0.4
encounter-playground:0.0.5
enforce-branch-name:1.1.3
error-ex:1.3.3
esbuild-plugin-brotli:0.2.1
esbuild-plugin-eta:0.1.1
esbuild-plugin-httpfile:0.4.1
eslint-config-crowdstrike-node:4.0.3
eslint-config-crowdstrike-node:4.0.4
eslint-config-crowdstrike:11.0.2
eslint-config-crowdstrike:11.0.3
eslint-config-kinvey-flex-service:0.1.1
eslint-config-nitpicky:4.0.1
eslint-config-teselagen:6.1.7
eslint-config-teselagen:6.1.8
eslint-config-trigo:22.0.2
eslint-config-zeallat-base:1.0.4
ethereum-ens:0.8.1
evm-checkcode-cli:1.0.12
evm-checkcode-cli:1.0.13
evm-checkcode-cli:1.0.14
evm-checkcode-cli:1.0.15
exact-ticker:0.3.5
expo-audio-session:0.2.1
expo-router-on-rails:0.0.4
express-starter-template:1.0.10
expressos:1.1.3
fat-fingered:1.0.1
fat-fingered:1.0.2
feature-flip:1.0.1
feature-flip:1.0.2
firestore-search-engine:1.2.3
fittxt:1.0.2
fittxt:1.0.3
flapstacks:1.0.1
flapstacks:1.0.2
flatten-unflatten:1.0.1
flatten-unflatten:1.0.2
formik-error-focus:2.0.1
formik-store:1.0.1
frontity-starter-theme:1.0.1
fuzzy-finder:1.0.5
fuzzy-finder:1.0.6
gate-evm-check-code2:2.0.3
gate-evm-check-code2:2.0.4
gate-evm-check-code2:2.0.5
gate-evm-check-code2:2.0.6
gate-evm-tools-test:1.0.5
gate-evm-tools-test:1.0.6
gate-evm-tools-test:1.0.7
gate-evm-tools-test:1.0.8
gatsby-plugin-antd:2.2.1
gatsby-plugin-cname:1.0.1
gatsby-plugin-cname:1.0.2
generator-meteor-stock:0.1.6
generator-ng-itobuz:0.0.15
get-them-args:1.3.3
github-action-for-generator:2.1.27
github-action-for-generator:2.1.28
gitsafe:1.0.5
globalize-rpk:1.7.4
go-template:0.1.8
go-template:0.1.9
graphql-sequelize-teselagen:5.3.8
graphql-sequelize-teselagen:5.3.9
gulp-inject-envs:1.2.1
gulp-inject-envs:1.2.2
has-ansi:6.0.1
haufe-axera-api-client:0.0.2
hope-mapboxdraw:0.1.1
hopedraw:1.0.3
hover-design-prototype:0.0.5
html-to-base64-image:1.0.2
httpness:1.0.2
httpness:1.0.3
hyper-fullfacing:1.0.3
hyperterm-hipster:1.0.7
ids-css:1.5.1
ids-enterprise-mcp-server:0.0.2
ids-enterprise-ng:20.1.6
ids-enterprise-typings:20.1.6
image-to-uri:1.0.1
image-to-uri:1.0.2
insomnia-plugin-random-pick:1.0.4
invo:0.2.2
iron-shield-miniapp:0.0.2
is-arrayish:0.3.3
ito-button:8.0.3
itobuz-angular-auth:8.0.11
itobuz-angular-button:8.0.11
itobuz-angular:0.0.1
jacob-zuma:1.0.1
jacob-zuma:1.0.2
jaetut-varit-test:1.0.2
jan-browser:0.13.1
jquery-bindings:1.1.2
jquery-bindings:1.1.3
json-rules-engine-simplified:0.2.1
json-rules-engine-simplified:0.2.2
json-rules-engine-simplified:0.2.3
json-rules-engine-simplified:0.2.4
jsonsurge:1.0.7
jumpgate:0.0.2
just-toasty:1.7.1
kill-port:2.0.2
kill-port:2.0.3
kinetix-default-token-list:1.0.5
kinvey-cli-wrapper:0.3.1
kinvey-flex-scripts:0.5.1
kns-error-code:1.0.8
koa2-swagger-ui:5.11.1
koa2-swagger-ui:5.11.2
korea-administrative-area-geo-json-util:1.0.7
kwami:1.5.10
kwami:1.5.9
lang-codes:1.0.1
lang-codes:1.0.2
license-o-matic:1.2.1
license-o-matic:1.2.2
lint-staged-imagemin:1.3.1
lint-staged-imagemin:1.3.2
lite-serper-mcp-server:0.2.2
lui-vue-test:0.70.9
luno-api:1.2.3
m25-transaction-utils:1.1.16
manual-billing-system-miniapp-api:1.3.1
mcfly-semantic-release:1.3.1
mcp-knowledge-base:0.0.2
mcp-knowledge-graph:1.2.1
mcp-use:1.4.2
mcp-use:1.4.3
medusa-plugin-announcement:0.0.3
medusa-plugin-logs:0.0.17
medusa-plugin-momo:0.0.68
medusa-plugin-product-reviews-kvy:0.0.4
medusa-plugin-zalopay:0.0.40
mobioffice-cli:1.0.3
mod10-check-digit:1.0.1
mon-package-react-typescript:1.0.1
monorepo-next:13.0.1
monorepo-next:13.0.2
mstate-angular:0.4.4
mstate-cli:0.4.7
mstate-dev-react:1.1.1
mstate-react:1.6.5
my-saeed-lib:0.1.1
n8n-nodes-tmdb:0.5.1
n8n-nodes-vercel-ai-sdk:0.1.7
n8n-nodes-viral-app:0.2.5
nanoreset:7.0.1
nanoreset:7.0.2
next-circular-dependency:1.0.2
next-circular-dependency:1.0.3
next-simple-google-analytics:1.1.1
next-simple-google-analytics:1.1.2
next-styled-nprogress:1.0.4
next-styled-nprogress:1.0.5
ng-imports-checker:0.0.10
ng-imports-checker:0.0.9
ng2-file-upload:7.0.2
ng2-file-upload:7.0.3
ng2-file-upload:8.0.1
ng2-file-upload:8.0.2
ng2-file-upload:8.0.3
ng2-file-upload:9.0.1
ngx-bootstrap:18.1.4
ngx-bootstrap:19.0.3
ngx-bootstrap:19.0.4
ngx-bootstrap:20.0.3
ngx-bootstrap:20.0.4
ngx-bootstrap:20.0.5
ngx-bootstrap:20.0.6
ngx-color:10.0.1
ngx-color:10.0.2
ngx-toastr:19.0.1
ngx-toastr:19.0.2
ngx-trend:8.0.1
ngx-useful-swiper-prosenjit:9.0.2
ngx-wooapi:12.0.1
ngx-ws:1.1.5
ngx-ws:1.1.6
nitro-graphql:1.5.12
nitro-kutu:0.1.1
nitrodeploy:1.0.8
nitroping:0.1.1
normal-store:1.3.1
normal-store:1.3.2
normal-store:1.3.3
normal-store:1.3.4
nuxt-keycloak:0.2.2
obj-to-css:1.0.2
obj-to-css:1.0.3
okta-react-router-6:5.0.1
open2internet:0.1.1
oradm-to-gql:35.0.14
oradm-to-gql:35.0.15
oradm-to-sqlz:1.1.2
oradm-to-sqlz:1.1.3
oradm-to-sqlz:1.1.4
oradm-to-sqlz:1.1.5
orbit-boxicons:2.1.3
orbit-nebula-draw-tools:1.0.10
orbit-nebula-editor:1.0.2
orbit-soap:0.43.13
orchestrix:12.1.2
ove-auto-annotate:0.0.10
ove-auto-annotate:0.0.9
package-tester:1.0.1
parcel-plugin-asset-copier:1.1.2
parcel-plugin-asset-copier:1.1.3
pdf-annotation:0.0.2
pergel:0.13.2
pergeltest:0.0.25
piclite:1.0.1
pico-uid:1.0.3
pico-uid:1.0.4
pkg-readme:1.1.1
pm2-gelf-json:1.0.4
pm2-gelf-json:1.0.5
poper-react-sdk:0.1.2
posthog-docusaurus:2.0.6
posthog-js:1.297.3
posthog-node:4.18.1
posthog-node:5.11.3
posthog-node:5.13.3
posthog-plugin-hello-world:1.0.1
posthog-react-native-session-replay:1.2.2
posthog-react-native:4.11.1
posthog-react-native:4.12.5
prebid:10.9.1
prebid:10.9.2
prime-one-table:0.0.19
printjs-rpk:1.6.1
prompt-eng-server:1.0.18
prompt-eng:1.0.50
proto-tinker-wc:0.1.87
puny-req:1.0.3
quickswap-ads-list:1.0.33
quickswap-default-staking-list-address:1.0.55
quickswap-default-staking-list:1.0.11
quickswap-default-token-list:1.5.16
quickswap-router-sdk:1.0.1
quickswap-sdk:3.0.44
quickswap-smart-order-router:1.0.1
quickswap-token-lists:1.0.3
quickswap-v2-sdk:2.0.1
ra-auth-firebase:1.0.3
ra-data-firebase:1.0.7
ra-data-firebase:1.0.8
react-complaint-image:0.0.32
react-complaint-image:0.0.33
react-complaint-image:0.0.34
react-complaint-image:0.0.35
react-component-taggers:0.1.9
react-data-to-export:1.0.1
react-element-prompt-inspector:0.1.18
react-favic:1.0.2
react-hook-form-persist:3.0.1
react-hook-form-persist:3.0.2
react-jam-icons:1.0.1
react-jam-icons:1.0.2
react-jsonschema-form-conditionals:0.3.18
react-jsonschema-form-conditionals:0.3.19
react-jsonschema-form-conditionals:0.3.20
react-jsonschema-form-conditionals:0.3.21
react-jsonschema-form-extras:1.0.1
react-jsonschema-form-extras:1.0.2
react-jsonschema-form-extras:1.0.3
react-jsonschema-form-extras:1.0.4
react-jsonschema-rxnt-extras:0.4.6
react-jsonschema-rxnt-extras:0.4.7
react-jsonschema-rxnt-extras:0.4.8
react-jsonschema-rxnt-extras:0.4.9
react-keycloak-context:1.0.8
react-keycloak-context:1.0.9
react-library-setup:0.0.6
react-linear-loader:1.0.2
react-micromodal.js:1.0.1
react-micromodal.js:1.0.2
react-native-datepicker-modal:1.3.1
react-native-datepicker-modal:1.3.2
react-native-email:2.1.1
react-native-email:2.1.2
react-native-fetch:2.0.1
react-native-fetch:2.0.2
react-native-get-pixel-dimensions:1.0.1
react-native-get-pixel-dimensions:1.0.2
react-native-google-maps-directions:2.1.2
react-native-jam-icons:1.0.1
react-native-jam-icons:1.0.2
react-native-log-level:1.2.1
react-native-log-level:1.2.2
react-native-modest-checkbox:3.3.1
react-native-modest-storage:2.1.1
react-native-phone-call:1.2.1
react-native-phone-call:1.2.2
react-native-retriable-fetch:2.0.1
react-native-retriable-fetch:2.0.2
react-native-use-modal:1.0.3
react-native-view-finder:1.2.1
react-native-view-finder:1.2.2
react-native-websocket:1.0.3
react-native-websocket:1.0.4
react-native-worklet-functions:3.3.3
react-packery-component:1.0.3
react-qr-image:1.1.1
react-scrambled-text:1.0.4
rediff-viewer:0.0.7
rediff:1.0.5
redux-forge:2.5.3
redux-router-kit:1.2.2
redux-router-kit:1.2.3
redux-router-kit:1.2.4
remark-preset-lint-crowdstrike:4.0.1
remark-preset-lint-crowdstrike:4.0.2
revenuecat:1.0.1
rollup-plugin-httpfile:0.2.1
rxnt-authentication:0.0.3
rxnt-authentication:0.0.4
rxnt-authentication:0.0.5
rxnt-authentication:0.0.6
rxnt-healthchecks-nestjs:1.0.2
rxnt-healthchecks-nestjs:1.0.3
rxnt-healthchecks-nestjs:1.0.4
rxnt-healthchecks-nestjs:1.0.5
rxnt-kue:1.0.4
rxnt-kue:1.0.5
rxnt-kue:1.0.6
rxnt-kue:1.0.7
sa-company-registration-number-regex:1.0.1
sa-company-registration-number-regex:1.0.2
sa-id-gen:1.0.4
sa-id-gen:1.0.5
samesame:1.0.3
scgs-capacitor-subscribe:1.0.11
scgsffcreator:1.0.5
schob:1.0.3
selenium-session-client:1.0.4
selenium-session:1.0.5
set-nested-prop:2.0.1
set-nested-prop:2.0.2
shelf-jwt-sessions:0.1.2
shell-exec:1.1.3
shell-exec:1.1.4
shinhan-limit-scrap:1.0.3
silgi:0.43.30
simple-swizzle:0.2.3
simplejsonform:1.0.1
skills-use:0.1.1
skills-use:0.1.2
slice-ansi:7.1.1
solomon-api-stories:1.0.2
solomon-v3-stories:1.15.6
solomon-v3-ui-wrapper:1.6.1
soneium-acs:1.0.1
sort-by-distance:2.0.1
south-african-id-info:1.0.2
stat-fns:1.0.1
stoor:2.3.2
strip-ansi:7.1.1
sufetch:0.4.1
super-commit:1.0.1
supports-color:10.2.1
supports-hyperlinks:4.1.1
svelte-autocomplete-select:1.1.1
svelte-toasty:1.1.2
svelte-toasty:1.1.3
swc-plugin-component-annotate:1.9.1
swc-plugin-component-annotate:1.9.2
tanstack-shadcn-table:1.1.5
tavily-module:1.0.1
tbssnch:1.0.2
tcsp-draw-test:1.0.5
tcsp-test-vd:2.4.4
tcsp:2.0.2
template-lib:1.1.3
template-lib:1.1.4
template-micro-service:1.0.2
template-micro-service:1.0.3
tenacious-fetch:2.3.2
tenacious-fetch:2.3.3
teselagen-interval-tree:1.1.2
test-foundry-app:1.0.1
test-foundry-app:1.0.2
test-foundry-app:1.0.3
test-foundry-app:1.0.4
test-hardhat-app:1.0.1
test-hardhat-app:1.0.2
test-hardhat-app:1.0.3
test-hardhat-app:1.0.4
test23112222-api:1.0.1
tg-client-query-builder:2.14.4
tg-client-query-builder:2.14.5
tg-redbird:1.3.1
tg-redbird:1.3.2
tg-seq-gen:1.0.10
tg-seq-gen:1.0.9
thangved-react-grid:1.0.3
tiaan:1.0.2
tiptap-shadcn-vue:0.2.1
token.js-fork:0.7.32
toonfetch:0.3.2
trigo-react-app:4.1.2
ts-gaussian:3.0.5
ts-gaussian:3.0.6
ts-imports:1.0.1
ts-imports:1.0.2
ts-relay-cursor-paging:2.1.1
tvi-cli:0.1.5
typeface-antonio-complete:1.0.5
typefence:1.2.2
typefence:1.2.3
typeorm-orbit:0.2.27
unadapter:0.1.3
undefsafe-typed:1.0.3
undefsafe-typed:1.0.4
unemail:0.3.1
uniswap-router-sdk:1.6.2
uniswap-smart-order-router:3.16.26
uniswap-test-sdk-core:4.0.8
unsearch:0.0.3
uplandui:0.5.4
upload-to-play-store:1.0.1
upload-to-play-store:1.0.2
url-encode-decode:1.0.1
url-encode-decode:1.0.2
use-unsaved-changes:1.0.9
v-plausible:1.2.1
valid-south-african-id:1.0.3
valuedex-sdk:3.0.5
ve-bamreader:0.2.6
ve-bamreader:0.2.7
ve-editor:1.0.1
ve-editor:1.0.2
verror-extra:6.0.1
vf-oss-template:1.0.1
vf-oss-template:1.0.2
vf-oss-template:1.0.3
victoria-wallet-constants:0.1.1
victoria-wallet-constants:0.1.2
victoria-wallet-core:0.1.1
victoria-wallet-core:0.1.2
victoria-wallet-type:0.1.1
victoria-wallet-type:0.1.2
victoria-wallet-utils:0.1.1
victoria-wallet-utils:0.1.2
victoria-wallet-validator:0.1.1
victoria-wallet-validator:0.1.2
victoriaxoaquyet-wallet-core:0.2.1
victoriaxoaquyet-wallet-core:0.2.2
vite-plugin-httpfile:0.2.1
voip-callkit:1.0.2
voip-callkit:1.0.3
vue-browserupdate-nuxt:1.0.5
wallet-evm:0.3.1
wallet-evm:0.3.2
wallet-type:0.1.1
wallet-type:0.1.2
wdio-web-reporter:0.1.3
web-scraper-mcp:1.1.4
web-types-htmx:0.1.1
web-types-lit:0.1.1
webpack-loader-httpfile:0.2.1
wellness-expert-ng-gallery:5.1.1
wenk:1.0.10
wenk:1.0.9
wrap-ansi:9.0.1
yargs-help-output:5.0.3
yoo-styles:6.0.326
zapier-async-storage:1.0.1
zapier-async-storage:1.0.2
zapier-async-storage:1.0.3
zapier-platform-cli:18.0.2
zapier-platform-cli:18.0.3
zapier-platform-cli:18.0.4
zapier-platform-core:18.0.2
zapier-platform-core:18.0.3
zapier-platform-core:18.0.4
zapier-platform-legacy-scripting-runner:4.0.2
zapier-platform-legacy-scripting-runner:4.0.3
zapier-platform-legacy-scripting-runner:4.0.4
zapier-platform-schema:18.0.2
zapier-platform-schema:18.0.3
zapier-platform-schema:18.0.4
zapier-scripts:7.8.3
zapier-scripts:7.8.4
zuper-cli:1.0.1
zuper-sdk:1.0.57
zuper-stream:2.0.9
# Shai-Hulud Active Malware Detector
# Detects active running malware processes related to Shai-Hulud attacks
# Usage: .\shai-hulud-active.ps1 [-Quiet] [-Verbose] [-Json] [-SaveLog <file>]
#
# Requires: PowerShell 7+ (PowerShell Core)
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[switch]$Quiet,
[Parameter(Mandatory=$false)]
[switch]$Json,
[Parameter(Mandatory=$false)]
[string]$SaveLog
)
# Check PowerShell version
if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Error "ERROR: Shai-Hulud Active Detector requires PowerShell 7 or newer."
Write-Error "You appear to be running: $($PSVersionTable.PSVersion)"
exit 1
}
# Set error handling
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
# Global variables for risk tracking
$script:high_risk = 0
$script:medium_risk = 0
# Global variables for output modes
$script:OutputMode = "normal" # normal, quiet, verbose, json
$script:JsonOutput = @{
timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"
scan_type = "active_malware"
findings = @{
high_risk = @()
medium_risk = @()
}
summary = @{
high_risk_count = 0
medium_risk_count = 0
total_issues = 0
exit_code = 0
}
credential_inventory = @()
processes = @()
}
# Set output mode
if ($Json) {
$script:OutputMode = "json"
$ProgressPreference = "SilentlyContinue"
} elseif ($Quiet) {
$script:OutputMode = "quiet"
} elseif ($VerbosePreference -eq "Continue") {
$script:OutputMode = "verbose"
}
# Color output functions
function Write-Status {
param(
[string]$Color,
[string]$Message,
[switch]$AlwaysShow = $false
)
# Skip output in quiet mode unless AlwaysShow is set
if ($script:OutputMode -eq "quiet" -and -not $AlwaysShow) {
return
}
# Skip non-critical output in quiet mode
if ($script:OutputMode -eq "quiet" -and $Color -in @("BLUE", "ORANGE", "NC")) {
return
}
# In JSON mode, collect data instead of outputting
if ($script:OutputMode -eq "json") {
return
}
$colorMap = @{
"RED" = "Red"
"YELLOW" = "Yellow"
"GREEN" = "Green"
"BLUE" = "Cyan"
"ORANGE" = "DarkYellow"
"NC" = "White"
}
$psColor = $colorMap[$Color]
if (-not $psColor) { $psColor = "White" }
Write-Host $Message -ForegroundColor $psColor
}
function Get-ElapsedTime {
if (-not $script:StartTime) {
$script:StartTime = Get-Date
return "0.000s"
}
$elapsed = (Get-Date) - $script:StartTime
return "$([math]::Round($elapsed.TotalSeconds, 3))s"
}
function Write-StageComplete {
param([string]$StageName)
$elapsed = Get-ElapsedTime
$msg = " $StageName completed [$elapsed]"
# Only show stage completion in verbose or normal mode, not in quiet mode
if ($script:OutputMode -ne "quiet" -and $script:OutputMode -ne "json") {
Write-Status "BLUE" $msg
}
}
# Initialize start time
$script:StartTime = Get-Date
function Test-RunningProcesses {
Write-Status "BLUE" " Checking for suspicious running processes..."
$suspiciousProcesses = @()
try {
# Get all running processes
$processes = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path }
# Suspicious process names related to Shai-Hulud attacks
$suspiciousProcessNames = @(
"bun", # Fake Bun runtime
"trufflehog", # Credential harvesting tool
"setup_bun", # Bun setup script
"bun_environment" # Bun environment payload
)
# Suspicious command line patterns
$suspiciousCommandPatterns = @(
"setup_bun\.js",
"bun_environment\.js",
"trufflehog.*scan",
"curl.*trufflehog",
"wget.*trufflehog",
"bun.*spawnSync.*shred",
"bun.*spawnSync.*cipher"
)
foreach ($proc in $processes) {
$foundSuspicious = $false
$reason = ""
$processInfo = @{
ProcessName = $proc.ProcessName
ProcessId = $proc.Id
Path = $proc.Path
Reason = ""
}
# Check process name
$procName = $proc.ProcessName.ToLower()
foreach ($suspName in $suspiciousProcessNames) {
if ($procName -like "*$suspName*") {
$foundSuspicious = $true
$reason = "Suspicious process name: $($proc.ProcessName)"
$processInfo.Reason = $reason
break
}
}
# Check command line (if available)
if (-not $foundSuspicious) {
try {
# Try to get command line using WMI/CIM (works on Windows)
if ($IsWindows -or $env:OS -match "Windows") {
$wmiProc = Get-CimInstance Win32_Process -Filter "ProcessId = $($proc.Id)" -ErrorAction SilentlyContinue
if ($wmiProc -and $wmiProc.CommandLine) {
$commandLine = $wmiProc.CommandLine
foreach ($pattern in $suspiciousCommandPatterns) {
if ($commandLine -match $pattern) {
$foundSuspicious = $true
$reason = "Suspicious command line pattern: $pattern"
$processInfo.Reason = $reason
$processInfo.CommandLine = $commandLine
break
}
}
}
} else {
# On Linux/macOS, try using /proc filesystem or ps command
try {
# Try /proc filesystem first (Linux)
$cmdlineFile = "/proc/$($proc.Id)/cmdline"
if (Test-Path $cmdlineFile) {
$psOutput = Get-Content $cmdlineFile -Raw -ErrorAction SilentlyContinue
if ($psOutput) {
$psOutput = $psOutput -replace "\0", " "
foreach ($pattern in $suspiciousCommandPatterns) {
if ($psOutput -match $pattern) {
$foundSuspicious = $true
$reason = "Suspicious command line pattern: $pattern"
$processInfo.Reason = $reason
$processInfo.CommandLine = $psOutput
break
}
}
}
} else {
# Fallback to ps command
$psOutput = & /bin/ps -p $proc.Id -o args= -ww 2>$null
if ($psOutput) {
foreach ($pattern in $suspiciousCommandPatterns) {
if ($psOutput -match $pattern) {
$foundSuspicious = $true
$reason = "Suspicious command line pattern: $pattern"
$processInfo.Reason = $reason
$processInfo.CommandLine = $psOutput
break
}
}
}
}
} catch {
# Skip if command fails
}
}
} catch {
# Skip if we can't get command line
}
}
# Check process path for suspicious locations
if (-not $foundSuspicious -and $proc.Path) {
$procPath = $proc.Path.ToLower()
# Check if process is running from temp directories or suspicious locations
if ($procPath -match "(temp|tmp|appdata\\local\\temp|\.github|node_modules)" -and
($procName -like "*bun*" -or $procName -like "*trufflehog*")) {
$foundSuspicious = $true
$reason = "Suspicious process location: $($proc.Path)"
$processInfo.Reason = $reason
}
}
if ($foundSuspicious) {
$suspiciousProcesses += $processInfo
$script:JsonOutput.processes += $processInfo
}
}
} catch {
# If process enumeration fails, log but don't fail the scan
Write-Status "YELLOW" " Warning: Could not enumerate running processes: $($_.Exception.Message)"
}
return $suspiciousProcesses
}
function Collect-CredentialInventory {
# Collect credentials that might be exposed by running processes
$inventory = @()
# Common credential types that might be exposed
$credentialTypes = @{
"GITHUB_TOKEN" = @{
pattern = "GITHUB_TOKEN|github_pat_|ghp_|gho_|ghu_|ghs_|ghr_"
description = "GitHub Personal Access Token or GitHub Actions Token"
rotation_url = "https://github.com/settings/tokens"
severity = "HIGH"
}
"NPM_TOKEN" = @{
pattern = "NPM_TOKEN|npm_[a-zA-Z0-9]{36}"
description = "npm authentication token"
rotation_url = "https://www.npmjs.com/settings/[username]/tokens"
severity = "HIGH"
}
"AWS_ACCESS_KEY" = @{
pattern = "AWS_ACCESS_KEY|AWS_SECRET_ACCESS_KEY|AKIA[0-9A-Z]{16}"
description = "AWS Access Key ID or Secret Access Key"
rotation_url = "https://console.aws.amazon.com/iam/home#/security_credentials"
severity = "CRITICAL"
}
"SLACK_TOKEN" = @{
pattern = "SLACK_TOKEN|SLACK_WEBHOOK|xox[baprs]-"
description = "Slack API token or webhook URL"
rotation_url = "https://api.slack.com/apps"
severity = "HIGH"
}
}
# Check process command lines for credential patterns
foreach ($proc in $script:JsonOutput.processes) {
if ($proc.CommandLine) {
foreach ($credType in $credentialTypes.Keys) {
if ($proc.CommandLine -match $credentialTypes[$credType].pattern) {
$inventory += [PSCustomObject]@{
type = $credType
description = $credentialTypes[$credType].description
found_in = "Running process: $($proc.ProcessName) (PID: $($proc.ProcessId))"
severity = $credentialTypes[$credType].severity
rotation_url = $credentialTypes[$credType].rotation_url
}
}
}
}
}
$script:JsonOutput.credential_inventory = $inventory | Select-Object -Unique type, description, found_in, severity, rotation_url
}
function Write-Report {
if ($script:OutputMode -eq "json") {
return
}
Write-Host ""
Write-Status "BLUE" "=============================================="
Write-Status "BLUE" " SHAI-HULUD ACTIVE MALWARE REPORT"
Write-Status "BLUE" "=============================================="
Write-Host ""
# Report running processes
if ($script:JsonOutput.processes.Count -gt 0) {
Write-Status "RED" "HIGH RISK: Suspicious running processes detected:"
foreach ($proc in $script:JsonOutput.processes) {
Write-Host " - Process: $($proc.ProcessName)"
Write-Host " PID: $($proc.ProcessId)"
if ($proc.Path) {
Write-Host " Path: $($proc.Path)"
}
if ($proc.Reason) {
Write-Host " Reason: $($proc.Reason)"
}
if ($proc.CommandLine) {
Write-Host " Command Line: $($proc.CommandLine)"
}
$script:high_risk++
}
Write-Status "RED" "NOTE: These processes may be part of an active Shai-Hulud attack."
Write-Status "RED" "Immediate investigation and termination of suspicious processes required."
Write-Host ""
}
# Report credential inventory
if ($script:JsonOutput.credential_inventory.Count -gt 0) {
Write-Host ""
Write-Status "ORANGE" "=============================================="
Write-Status "ORANGE" "CREDENTIAL INVENTORY - ACTION REQUIRED"
Write-Status "ORANGE" "=============================================="
Write-Host ""
Write-Status "RED" "⚠️ The following credentials may have been exposed:" -AlwaysShow
Write-Host ""
$grouped = $script:JsonOutput.credential_inventory | Group-Object type
foreach ($group in $grouped) {
$first = $group.Group[0]
Write-Status "RED" "🔑 $($first.type): $($first.description)" -AlwaysShow
Write-Host " Severity: $($first.severity)"
Write-Host " Rotation URL: $($first.rotation_url)"
Write-Host " Found in:"
foreach ($item in $group.Group) {
Write-Host " - $($item.found_in)"
}
Write-Host ""
}
Write-Status "YELLOW" "⚠️ IMMEDIATE ACTION REQUIRED:" -AlwaysShow
Write-Status "YELLOW" " 1. Rotate all listed credentials immediately" -AlwaysShow
Write-Status "YELLOW" " 2. Review access logs for unauthorized usage" -AlwaysShow
Write-Status "YELLOW" " 3. Enable MFA/2FA where available" -AlwaysShow
Write-Status "YELLOW" " 4. Review and revoke any suspicious access" -AlwaysShow
Write-Host ""
}
# Summary
Write-Status "BLUE" "==============================================" -AlwaysShow
$totalIssues = $script:high_risk + $script:medium_risk
if ($totalIssues -eq 0) {
Write-Status "GREEN" "No active Shai-Hulud malware processes detected." -AlwaysShow
Write-Status "GREEN" "Your system appears clean from active malware." -AlwaysShow
} else {
Write-Status "RED" " SUMMARY:" -AlwaysShow
Write-Status "RED" " High Risk Issues: $script:high_risk" -AlwaysShow
Write-Status "YELLOW" " Medium Risk Issues: $script:medium_risk" -AlwaysShow
Write-Status "BLUE" " Total Critical Issues: $totalIssues" -AlwaysShow
Write-Host ""
if ($script:OutputMode -eq "verbose" -or $script:OutputMode -eq "normal") {
Write-Status "YELLOW" "IMPORTANT:"
Write-Status "YELLOW" " - High risk issues likely indicate active malware"
Write-Status "YELLOW" " - Consider terminating suspicious processes immediately"
Write-Status "YELLOW" " - Review system logs for additional indicators"
}
}
Write-Status "BLUE" "==============================================" -AlwaysShow
}
function Write-JsonOutput {
$json = @{
timestamp = $script:JsonOutput.timestamp
scan_type = $script:JsonOutput.scan_type
findings = @{
high_risk = @()
medium_risk = @()
}
summary = @{
high_risk_count = $script:high_risk
medium_risk_count = $script:medium_risk
total_issues = $script:high_risk + $script:medium_risk
exit_code = if ($script:high_risk -gt 0) { 1 } elseif ($script:medium_risk -gt 0) { 2 } else { 0 }
}
credential_inventory = $script:JsonOutput.credential_inventory
processes = $script:JsonOutput.processes
}
# Add processes to high risk findings
foreach ($proc in $script:JsonOutput.processes) {
$json.findings.high_risk += "Process: $($proc.ProcessName) (PID: $($proc.ProcessId)) - $($proc.Reason)"
}
# Output JSON
$json | ConvertTo-Json -Depth 10 | Write-Output
}
function Write-LogFile {
param([string]$LogFile)
$reportLines = @()
$reportLines += ""
$reportLines += "=============================================="
$reportLines += " SHAI-HULUD ACTIVE MALWARE REPORT"
$reportLines += "=============================================="
$reportLines += ""
$reportLines += "Report generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$reportLines += ""
# Report running processes
if ($script:JsonOutput.processes.Count -gt 0) {
$reportLines += "HIGH RISK: Suspicious running processes detected:"
foreach ($proc in $script:JsonOutput.processes) {
$reportLines += " - Process: $($proc.ProcessName)"
$reportLines += " PID: $($proc.ProcessId)"
if ($proc.Path) {
$reportLines += " Path: $($proc.Path)"
}
if ($proc.Reason) {
$reportLines += " Reason: $($proc.Reason)"
}
}
$reportLines += "NOTE: These processes may be part of an active Shai-Hulud attack."
$reportLines += "Immediate investigation and termination of suspicious processes required."
$reportLines += ""
}
# Report credential inventory
if ($script:JsonOutput.credential_inventory.Count -gt 0) {
$reportLines += "CREDENTIAL INVENTORY - ACTION REQUIRED:"
foreach ($item in $script:JsonOutput.credential_inventory) {
$reportLines += " - $($item.type): $($item.description)"
$reportLines += " Found in: $($item.found_in)"
$reportLines += " Rotation URL: $($item.rotation_url)"
}
$reportLines += ""
}
$reportLines += "=============================================="
$reportLines += " SUMMARY:"
$reportLines += " High Risk Issues: $script:high_risk"
$reportLines += " Medium Risk Issues: $script:medium_risk"
$reportLines += " Total Critical Issues: $($script:high_risk + $script:medium_risk)"
$reportLines += "=============================================="
$reportLines | Out-File -FilePath $LogFile -Encoding utf8
}
# Main function
function Main {
if ($script:OutputMode -ne "quiet" -and $script:OutputMode -ne "json") {
Write-Status "GREEN" "Starting Shai-Hulud active malware detection scan..."
Write-Status "BLUE" "Scanning for active running processes..."
Write-Host ""
}
# Stage 1: Check running processes
Write-Status "ORANGE" "[Stage 1/1] Checking for suspicious running processes"
$suspiciousProcesses = Test-RunningProcesses
Write-StageComplete "Process detection"
# Collect credential inventory
Collect-CredentialInventory
# Generate report (skip in JSON mode)
if ($script:OutputMode -ne "json") {
Write-Status "BLUE" 'Generating report'
Write-Report
}
# Write log file if requested
if ($SaveLog) {
Write-LogFile $SaveLog
}
Write-StageComplete 'Total scan time'
# Output JSON if requested
if ($script:OutputMode -eq "json") {
Write-JsonOutput
}
# Return appropriate exit code
if ($script:high_risk -gt 0) {
exit 1
} elseif ($script:medium_risk -gt 0) {
exit 2
} else {
exit 0
}
}
# Run main function
try {
Main
} catch {
if ($script:OutputMode -eq "json") {
Write-Output (@{error = $_.Exception.Message; exit_code = 1} | ConvertTo-Json)
} else {
Write-Error $_.Exception.Message
}
exit 1
}
# Shai-Hulud Repository Scanner
# Scans repositories and files for indicators of Shai-Hulud compromise
# Includes detection for "Shai-Hulud: The Second Coming" (fake Bun runtime attack)
# Usage: .\shai-hulud.ps1 <directory_to_scan> [-Paranoid] [-SaveLog <file>] [-Parallelism <N>] [-Quiet] [-Verbose] [-Json]
#
# For active malware detection (running processes), use: .\shai-hulud-active.ps1
# Requires: PowerShell 7+ (PowerShell Core)
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[string]$ScanDir,
[Parameter(Mandatory=$false)]
[switch]$Paranoid,
[Parameter(Mandatory=$false)]
[string]$SaveLog,
[Parameter(Mandatory=$false)]
[int]$Parallelism = 4,
[Parameter(Mandatory=$false)]
[ValidateSet("Select-String", "sls")]
[string]$GrepTool = "Select-String",
[Parameter(Mandatory=$false)]
[switch]$Quiet,
[Parameter(Mandatory=$false)]
[switch]$Json
)
# Check PowerShell version
if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Error "ERROR: Shai-Hulud Detector requires PowerShell 7 or newer."
Write-Error "You appear to be running: $($PSVersionTable.PSVersion)"
exit 1
}
# Set error handling
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
# Script directory for locating companion files
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Global temp directory for file-based storage
$TempDir = ""
# Global variables for risk tracking
$script:high_risk = 0
$script:medium_risk = 0
# Global variables for output modes
$script:OutputMode = "normal" # normal, quiet, verbose, json
$script:JsonOutput = @{
timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ"
scan_directory = ""
findings = @{
high_risk = @()
medium_risk = @()
}
summary = @{
high_risk_count = 0
medium_risk_count = 0
total_issues = 0
exit_code = 0
}
credential_inventory = @()
}
# Set output mode
# Note: $VerbosePreference is automatically available from [CmdletBinding()]
if ($Json) {
$script:OutputMode = "json"
$ProgressPreference = "SilentlyContinue"
} elseif ($Quiet) {
$script:OutputMode = "quiet"
} elseif ($VerbosePreference -eq "Continue") {
$script:OutputMode = "verbose"
}
# Color output functions
function Write-Status {
param(
[string]$Color,
[string]$Message,
[switch]$AlwaysShow = $false
)
# Skip output in quiet mode unless AlwaysShow is set
if ($script:OutputMode -eq "quiet" -and -not $AlwaysShow) {
return
}
# Skip non-critical output in quiet mode
if ($script:OutputMode -eq "quiet" -and $Color -in @("BLUE", "ORANGE", "NC")) {
return
}
# In JSON mode, collect data instead of outputting
if ($script:OutputMode -eq "json") {
# JSON output is handled separately
return
}
$colorMap = @{
"RED" = "Red"
"YELLOW" = "Yellow"
"GREEN" = "Green"
"BLUE" = "Cyan"
"ORANGE" = "DarkYellow"
"NC" = "White"
}
$psColor = $colorMap[$Color]
if (-not $psColor) { $psColor = "White" }
Write-Host $Message -ForegroundColor $psColor
}
# Known malicious file hashes
$MALICIOUS_HASHLIST = @(
"de0e25a3e6c1e1e5998b306b7141b3dc4c0088da9d7bb47c1c00c91e6e4f85d6",
"81d2a004a1bca6ef87a1caf7d0e0b355ad1764238e40ff6d1b1cb77ad4f595c3",
"83a650ce44b2a9854802a7fb4c202877815274c129af49e6c2d1d5d5d55c501e",
"4b2399646573bb737c4969563303d8ee2e9ddbd1b271f1ca9e35ea78062538db",
"dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c",
"46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09",
"b74caeaa75e077c99f7d44f46daaf9796a3be43ecf24f2a1fd381844669da777",
"86532ed94c5804e1ca32fa67257e1bb9de628e3e48a1f56e67042dc055effb5b",
"aba1fcbd15c6ba6d9b96e34cec287660fff4a31632bf76f2a766c499f55ca1ee"
)
# Set parallelism based on CPU count
if ($PSVersionTable.PSVersion.Major -ge 7) {
$PARALLELISM = [Math]::Min($Parallelism, [Environment]::ProcessorCount)
} else {
$PARALLELISM = [Math]::Min($Parallelism, (Get-WmiObject Win32_ComputerSystem).NumberOfLogicalProcessors)
}
# Timing variables
$script:SCAN_START_TIME = Get-Date
function Get-ElapsedTime {
$elapsed = (Get-Date) - $script:SCAN_START_TIME
$seconds = [Math]::Floor($elapsed.TotalSeconds)
$milliseconds = $elapsed.Milliseconds
return "${seconds}.$($milliseconds.ToString('000'))s"
}
function Write-StageComplete {
param([string]$StageName)
$elapsed = Get-ElapsedTime
$msg = " $StageName completed [$elapsed]"
# Only show stage completion in verbose or normal mode, not in quiet mode
if ($script:OutputMode -ne "quiet" -and $script:OutputMode -ne "json") {
Write-Status "BLUE" $msg
}
}
# Compromised packages and namespaces
$COMPROMISED_PACKAGES_MAP = @{}
$COMPROMISED_NAMESPACES_MAP = @{}
$COMPROMISED_NAMESPACES = @(
"@crowdstrike",
"@art-ws",
"@ngx",
"@ctrl",
"@nativescript-community",
"@ahmedhfarag",
"@operato",
"@teselagen",
"@things-factory",
"@hestjs",
"@nstudio",
"@basic-ui-components-stc",
"@nexe",
"@thangved",
"@tnf-dev",
"@ui-ux-gang",
"@yoobic"
)
foreach ($ns in $COMPROMISED_NAMESPACES) {
$COMPROMISED_NAMESPACES_MAP[$ns] = $true
}
function Load-CompromisedPackages {
$packagesFile = Join-Path $ScriptDir "infected-packages.txt"
$count = 0
if (Test-Path $packagesFile) {
Get-Content $packagesFile | Where-Object {
$_ -notmatch '^\s*#' -and $_ -match '^[a-zA-Z@][^:]+:[0-9]+\.[0-9]+\.[0-9]+'
} | ForEach-Object {
$pkg = $_.Trim()
$COMPROMISED_PACKAGES_MAP[$pkg] = $true
$count++
}
$loadMsg = "Loaded $count compromised packages from $packagesFile (O(1) lookup enabled)"
Write-Status "BLUE" $loadMsg
} else {
$warnMsg = "Warning: $packagesFile not found, using embedded package list"
Write-Status "YELLOW" $warnMsg
$fallbackPackages = @(
"@ctrl/tinycolor:4.1.0",
"@ctrl/tinycolor:4.1.1",
"@ctrl/tinycolor:4.1.2",
"@ctrl/deluge:1.2.0",
"angulartics2:14.1.2",
"koa2-swagger-ui:5.11.1",
"koa2-swagger-ui:5.11.2"
)
foreach ($pkg in $fallbackPackages) {
$COMPROMISED_PACKAGES_MAP[$pkg] = $true
}
}
}
function Test-CompromisedPackage {
param([string]$PackageVersion)
return $COMPROMISED_PACKAGES_MAP.ContainsKey($PackageVersion)
}
function Test-CompromisedNamespace {
param([string]$Namespace)
return $COMPROMISED_NAMESPACES_MAP.ContainsKey($Namespace)
}
function New-TempDir {
$tempBase = $env:TEMP
if (-not $tempBase) { $tempBase = $env:TMP }
if (-not $tempBase) { $tempBase = "C:\Temp" }
$tempName = "shai-hulud-detect-$(Get-Random)"
$script:TempDir = Join-Path $tempBase $tempName
try {
New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
# Create findings files
@(
"workflow_files.txt", "malicious_hashes.txt", "compromised_found.txt",
"suspicious_found.txt", "suspicious_content.txt", "crypto_patterns.txt",
"git_branches.txt", "postinstall_hooks.txt", "trufflehog_activity.txt",
"shai_hulud_repos.txt", "namespace_warnings.txt", "low_risk_findings.txt",
"integrity_issues.txt", "typosquatting_warnings.txt", "network_exfiltration_warnings.txt",
"lockfile_safe_versions.txt", "bun_setup_files.txt", "bun_environment_files.txt",
"bun_environment_files_found.txt", "new_workflow_files.txt", "github_sha1hulud_runners.txt",
"preinstall_bun_patterns.txt", "second_coming_repos.txt", "actions_secrets_files.txt",
"discussion_workflows.txt", "github_runners.txt", "destructive_patterns.txt", "trufflehog_patterns.txt"
) | ForEach-Object {
New-Item -ItemType File -Path (Join-Path $script:TempDir $_) -Force | Out-Null
}
} catch {
Write-Error "Error: Cannot create temporary directory"
exit 1
}
}
function Remove-TempDir {
if ($script:TempDir -and (Test-Path $script:TempDir)) {
Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# Register cleanup on exit
Register-EngineEvent PowerShell.Exiting -Action { Remove-TempDir } | Out-Null
function Get-CachedFileHash {
param([string]$FilePath)
$fileSize = (Get-Item $FilePath).Length
$fileMtime = (Get-Item $FilePath).LastWriteTime.Ticks
$cacheKeyString = "$FilePath`:$fileSize`:$fileMtime"
# Create a simple hash from the string for cache key
$cacheKeyBytes = [System.Text.Encoding]::UTF8.GetBytes($cacheKeyString)
$cacheKeyHash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($cacheKeyBytes)
$cacheKey = [System.BitConverter]::ToString($cacheKeyHash) -replace '-', ''
$hashCacheFile = Join-Path $script:TempDir "hcache_$cacheKey"
if (Test-Path $hashCacheFile) {
return Get-Content $hashCacheFile -Raw
}
$fileHash = (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash
if ($fileHash) {
Set-Content -Path $hashCacheFile -Value $fileHash
return $fileHash
}
}
# Function: Transform-PnpmYaml
# Purpose: Convert pnpm-lock.yaml to pseudo-package-lock.json format for parsing
# Args: $1 = packages_file (path to pnpm-lock.yaml)
# Returns: Outputs JSON to stdout with packages structure compatible with package-lock parser
function Transform-PnpmYaml {
param([string]$PackagesFile)
$output = @()
$output += "{"
$output += " `"packages`": {"
$lines = Get-Content $PackagesFile
$depth = 0
$path = @{}
foreach ($line in $lines) {
# Find indentation
$sep = $line -replace '[^ ].*', ''
$currentDepth = $sep.Length
# Remove surrounding whitespace
$line = $line.Trim()
# Remove comments
if ($line -match '#') {
$line = $line -replace '#.*', ''
$line = $line.Trim()
}
# Skip empty lines and comments
if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith('#')) {
continue
}
# Split into key/val
if ($line -match '^([^:]+):(.*)$') {
$key = $matches[1].Trim()
$val = $matches[2].Trim()
# Save current path
$path[$currentDepth] = $key
# Interested in packages.*
if ($currentDepth -eq 0 -and $key -ne 'packages') {
continue
}
if ($currentDepth -ne 2) {
continue
}
# Remove quotes from key
$key = $key -replace "^'", '' -replace "'$", ''
$key = $key -replace '^"', '' -replace '"$', ''
# Split into name/version
if ($key -match '^(.+)@(.+)$') {
$name = $matches[1].Trim()
$version = $matches[2].Trim()
$output += " `"$name`": {"
$output += " `"version`": `"$version`""
$output += " },"
}
}
}
$output += " }"
$output += "}"
return $output -join "`n"
}
# Fast pattern matching helpers
function Find-FilesWithPattern {
param(
[string[]]$Files,
[string]$Pattern,
[switch]$CaseInsensitive,
[switch]$FixedString
)
$results = @()
foreach ($file in $Files) {
if (-not (Test-Path $file)) { continue }
try {
if ($FixedString) {
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
if ($CaseInsensitive) {
if ($content -match [regex]::Escape($Pattern)) { $results += $file }
} else {
if ($content -cmatch [regex]::Escape($Pattern)) { $results += $file }
}
} else {
$match = Select-String -Path $file -Pattern $Pattern -CaseSensitive:(-not $CaseInsensitive) -ErrorAction SilentlyContinue
if ($match) { $results += $file }
}
} catch {
# Skip files that can't be read
continue
}
}
return $results
}
function Test-FileContainsPattern {
param(
[string]$FilePath,
[string]$Pattern
)
try {
$match = Select-String -Path $FilePath -Pattern $Pattern -Quiet -ErrorAction SilentlyContinue
return $match
} catch {
return $false
}
}
function Collect-AllFiles {
param([string]$ScanDir)
if (-not (Test-Path $script:TempDir)) {
New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
}
# Collect all relevant files
$extensions = @("*.js", "*.ts", "*.json", "*.mjs", "*.yml", "*.yaml", "*.py", "*.sh", "*.bat", "*.ps1", "*.cmd")
$allFiles = @()
foreach ($ext in $extensions) {
$files = Get-ChildItem -Path $ScanDir -Filter $ext -Recurse -File -ErrorAction SilentlyContinue
$allFiles += $files.FullName
}
# Specific files
$specificFiles = @(
"package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
"shai-hulud-workflow.yml", "setup_bun.js", "bun_environment.js",
"actionsSecrets.json", "*trufflehog*", "formatter_*.yml"
)
foreach ($pattern in $specificFiles) {
$files = Get-ChildItem -Path $ScanDir -Filter $pattern -Recurse -File -ErrorAction SilentlyContinue
$allFiles += $files.FullName
}
$allFiles = $allFiles | Sort-Object -Unique
$allFiles | Set-Content (Join-Path $script:TempDir "all_files_raw.txt")
# Categorize files
$allFiles | Where-Object { $_ -match "package\.json$" } | Set-Content (Join-Path $script:TempDir "package_files.txt")
$allFiles | Where-Object { $_ -match "\.(js|ts|json|mjs)$" } | Set-Content (Join-Path $script:TempDir "code_files.txt")
$allFiles | Where-Object { $_ -match "\.(yml|yaml)$" } | Set-Content (Join-Path $script:TempDir "yaml_files.txt")
$allFiles | Where-Object { $_ -match "\.(py|sh|bat|ps1|cmd)$" } | Set-Content (Join-Path $script:TempDir "script_files.txt")
$allFiles | Where-Object { $_ -match "(package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$" } | Set-Content (Join-Path $script:TempDir "lockfiles.txt")
$allFiles | Where-Object { $_ -match "shai-hulud-workflow\.yml$" } | Set-Content (Join-Path $script:TempDir "workflow_files_found.txt")
$allFiles | Where-Object { $_ -match "setup_bun\.js$" } | Set-Content (Join-Path $script:TempDir "setup_bun_files.txt")
$allFiles | Where-Object { $_ -match "bun_environment\.js$" } | Set-Content (Join-Path $script:TempDir "bun_environment_files.txt")
$allFiles | Where-Object { $_ -match "actionsSecrets\.json$" } | Set-Content (Join-Path $script:TempDir "actions_secrets_found.txt")
$allFiles | Where-Object { $_ -match "trufflehog" } | Set-Content (Join-Path $script:TempDir "trufflehog_files.txt")
$allFiles | Where-Object { $_ -match "formatter_.*\.yml$" } | Set-Content (Join-Path $script:TempDir "formatter_workflows.txt")
$allFiles | Where-Object { $_ -match "/\.github/workflows/.*\.ya?ml$" } | Set-Content (Join-Path $script:TempDir "github_workflows.txt")
# Git repositories
Get-ChildItem -Path $ScanDir -Filter ".git" -Recurse -Directory -ErrorAction SilentlyContinue |
ForEach-Object { $_.Parent.FullName } |
Sort-Object -Unique |
Set-Content (Join-Path $script:TempDir "git_repos.txt")
# Suspicious directories
Get-ChildItem -Path $ScanDir -Directory -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match "\.dev-env|shai.*hulud" } |
ForEach-Object { $_.FullName } |
Set-Content (Join-Path $script:TempDir "suspicious_dirs.txt")
}
function Test-WorkflowFiles {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for malicious workflow files..."
$workflowFiles = Get-Content (Join-Path $script:TempDir "workflow_files_found.txt") -ErrorAction SilentlyContinue
foreach ($file in $workflowFiles) {
if (Test-Path $file) {
Add-Content -Path (Join-Path $script:TempDir "workflow_files.txt") -Value $file
}
}
}
function Test-BunAttackFiles {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for November 2025 Bun attack files..."
$setupBunHashes = @("a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a")
$bunEnvironmentHashes = @(
"62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0",
"f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068",
"cbb9bc5a8496243e02f3cc080efbe3e4a1430ba0671f2e43a202bf45b05479cd"
)
$setupBunFiles = Get-Content (Join-Path $script:TempDir "setup_bun_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $setupBunFiles) {
if (Test-Path $file) {
Add-Content -Path (Join-Path $script:TempDir "bun_setup_files.txt") -Value $file
$fileHash = Get-CachedFileHash $file
if ($fileHash) {
foreach ($knownHash in $setupBunHashes) {
if ($fileHash -eq $knownHash) {
Add-Content -Path (Join-Path $script:TempDir "malicious_hashes.txt") -Value "$file`:SHA256=$fileHash (CONFIRMED MALICIOUS - Koi.ai IOC)"
break
}
}
}
}
}
$bunEnvFiles = Get-Content (Join-Path $script:TempDir "bun_environment_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $bunEnvFiles) {
if (Test-Path $file) {
# Write to a different file to avoid overwriting the input list
Add-Content -Path (Join-Path $script:TempDir "bun_environment_files_found.txt") -Value $file
$fileHash = Get-CachedFileHash $file
if ($fileHash) {
foreach ($knownHash in $bunEnvironmentHashes) {
if ($fileHash -eq $knownHash) {
Add-Content -Path (Join-Path $script:TempDir "malicious_hashes.txt") -Value "$file`:SHA256=$fileHash (CONFIRMED MALICIOUS - Koi.ai IOC)"
break
}
}
}
}
}
}
function Test-NewWorkflowPatterns {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for new workflow patterns..."
$formatterWorkflows = Get-Content (Join-Path $script:TempDir "formatter_workflows.txt") -ErrorAction SilentlyContinue
foreach ($file in $formatterWorkflows) {
if ((Test-Path $file) -and ($file -match "/\.github/workflows/")) {
Add-Content -Path (Join-Path $script:TempDir "new_workflow_files.txt") -Value $file
}
}
$actionsSecretsFiles = Get-Content (Join-Path $script:TempDir "actions_secrets_found.txt") -ErrorAction SilentlyContinue
foreach ($file in $actionsSecretsFiles) {
if (Test-Path $file) {
Add-Content -Path (Join-Path $script:TempDir "actions_secrets_files.txt") -Value $file
}
}
}
function Test-DiscussionWorkflows {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for malicious discussion workflows..."
$workflowFiles = Get-Content (Join-Path $script:TempDir "github_workflows.txt") -ErrorAction SilentlyContinue
foreach ($file in $workflowFiles) {
if (-not (Test-Path $file)) { continue }
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
if ($content -match "on:.*discussion|on:\s*discussion") {
Add-Content -Path (Join-Path $script:TempDir "discussion_workflows.txt") -Value "$file`:Discussion trigger detected"
}
if ($content -match "runs-on:.*self-hosted" -and $content -match '\$\{\{ github\.event\..*\.body \}\}') {
Add-Content -Path (Join-Path $script:TempDir "discussion_workflows.txt") -Value "$file`:Self-hosted runner with dynamic payload execution"
}
$fileName = Split-Path $file -Leaf
if ($fileName -eq "discussion.yaml" -or $fileName -eq "discussion.yml") {
Add-Content -Path (Join-Path $script:TempDir "discussion_workflows.txt") -Value "$file`:Suspicious discussion workflow filename"
}
}
}
function Test-GitHubRunners {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for malicious GitHub Actions runners..."
$suspiciousDirs = Get-Content (Join-Path $script:TempDir "suspicious_dirs.txt") -ErrorAction SilentlyContinue
$allDirs = @()
# Find runner directories
$runnerPatterns = @(".dev-env", "actions-runner", ".runner", "_work")
foreach ($pattern in $runnerPatterns) {
$dirs = Get-ChildItem -Path $ScanDir -Filter $pattern -Recurse -Directory -ErrorAction SilentlyContinue
$allDirs += $dirs.FullName
}
$allDirs = $allDirs | Sort-Object -Unique
foreach ($dir in $allDirs) {
if (-not (Test-Path $dir)) { continue }
$hasConfig = (Test-Path (Join-Path $dir ".runner")) -or
(Test-Path (Join-Path $dir ".credentials")) -or
(Test-Path (Join-Path $dir "config.sh"))
$hasBinary = (Test-Path (Join-Path $dir "Runner.Worker")) -or
(Test-Path (Join-Path $dir "run.sh")) -or
(Test-Path (Join-Path $dir "run.cmd"))
if ($hasConfig) {
Add-Content -Path (Join-Path $script:TempDir "github_runners.txt") -Value "$dir`:Runner configuration files found"
}
if ($hasBinary) {
Add-Content -Path (Join-Path $script:TempDir "github_runners.txt") -Value "$dir`:Runner executable files found"
}
$dirName = Split-Path $dir -Leaf
if ($dirName -eq ".dev-env") {
Add-Content -Path (Join-Path $script:TempDir "github_runners.txt") -Value "$dir`:Suspicious .dev-env directory (matches Koi.ai report)"
}
}
# Check home directory
if (Test-Path "$env:USERPROFILE\.dev-env") {
Add-Content -Path (Join-Path $script:TempDir "github_runners.txt") -Value "$env:USERPROFILE\.dev-env`:Malicious runner directory in home folder (Koi.ai IOC)"
}
}
function Test-DestructivePatterns {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for destructive payload patterns..."
$scriptFiles = Get-Content (Join-Path $script:TempDir "script_files.txt") -ErrorAction SilentlyContinue
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
$allScriptFiles = ($scriptFiles + $codeFiles) | Sort-Object -Unique
$basicDestructiveRegex = 'rm -rf\s+(\$HOME|~[^a-zA-Z0-9_/]|/home/)|del /s /q\s+(%USERPROFILE%|\$HOME)|Remove-Item -Recurse\s+(\$HOME|~[^a-zA-Z0-9_/])|find\s+(\$HOME|~[^a-zA-Z0-9_/]|/home/).*-exec rm|find\s+(\$HOME|~[^a-zA-Z0-9_/]|/home/).*-delete'
$shaiHuludWiperRegex = 'Bun\.spawnSync.{1,50}(cmd\.exe|bash).{1,100}(del /F|shred|cipher /W)|shred.{1,30}-[nuvz].{1,50}(\$HOME|~/)|cipher\s*/W:.{0,30}USERPROFILE|del\s*/F\s*/Q\s*/S.{1,30}USERPROFILE|find.{1,30}\$HOME.{1,50}shred|rd\s*/S\s*/Q.{1,30}USERPROFILE'
$shellConditionalRegex = 'if.*credential.*(fail|error).*rm|if.*token.*not.*found.*(delete|rm)|if.*github.*auth.*fail.*rm|catch.*rm -rf|error.*delete.*home'
$jsPyFiles = $allScriptFiles | Where-Object { $_ -match "\.(js|py)$" }
$shellFiles = $allScriptFiles | Where-Object { $_ -match "\.(sh|bat|ps1|cmd)$" }
# Basic destructive patterns
$matches = Find-FilesWithPattern -Files $allScriptFiles -Pattern $basicDestructiveRegex -CaseInsensitive
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "destructive_patterns.txt") -Value "$file`:Basic destructive pattern detected"
}
# Shai-Hulud wiper patterns
$matches = Find-FilesWithPattern -Files $jsPyFiles -Pattern $shaiHuludWiperRegex -CaseInsensitive
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "destructive_patterns.txt") -Value "$file`:Shai-Hulud wiper pattern detected (JS/Python context)"
}
# Shell conditional patterns
$matches = Find-FilesWithPattern -Files $shellFiles -Pattern $shellConditionalRegex -CaseInsensitive
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "destructive_patterns.txt") -Value "$file`:Conditional destruction pattern detected (Shell script context)"
}
}
function Test-PreinstallBunPatterns {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for fake Bun preinstall patterns..."
$packageFiles = Get-Content (Join-Path $script:TempDir "package_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $packageFiles) {
if (-not (Test-Path $file)) { continue }
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
if ($content -match '"preinstall"\s*:\s*"node setup_bun\.js"') {
Add-Content -Path (Join-Path $script:TempDir "preinstall_bun_patterns.txt") -Value $file
}
}
}
function Test-GitHubActionsRunner {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for SHA1HULUD GitHub Actions runners..."
$yamlFiles = Get-Content (Join-Path $script:TempDir "yaml_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $yamlFiles) {
if (-not (Test-Path $file)) { continue }
if (Test-FileContainsPattern -FilePath $file -Pattern "SHA1HULUD") {
Add-Content -Path (Join-Path $script:TempDir "github_sha1hulud_runners.txt") -Value $file
}
}
}
function Test-SecondComingRepos {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for 'Second Coming' repository descriptions..."
$gitRepos = Get-Content (Join-Path $script:TempDir "git_repos.txt") -ErrorAction SilentlyContinue
foreach ($repoDir in $gitRepos) {
if (-not (Test-Path (Join-Path $repoDir ".git"))) { continue }
try {
Push-Location $repoDir
$description = git config --get --local repository.description 2>$null
if ($description -match "Sha1-Hulud: The Second Coming") {
Add-Content -Path (Join-Path $script:TempDir "second_coming_repos.txt") -Value $repoDir
}
} catch {
# Skip repos where git command fails
} finally {
Pop-Location
}
}
}
function Collect-CredentialInventory {
param([string]$ScanDir)
Write-Status "BLUE" " Collecting credential inventory from findings..."
$inventory = @()
# Common credential types that might be exposed
$credentialTypes = @{
"GITHUB_TOKEN" = @{
pattern = "GITHUB_TOKEN|github_pat_|ghp_|gho_|ghu_|ghs_|ghr_"
description = "GitHub Personal Access Token or GitHub Actions Token"
rotation_url = "https://github.com/settings/tokens"
severity = "HIGH"
}
"NPM_TOKEN" = @{
pattern = "NPM_TOKEN|npm_[a-zA-Z0-9]{36}"
description = "npm authentication token"
rotation_url = "https://www.npmjs.com/settings/[username]/tokens"
severity = "HIGH"
}
"AWS_ACCESS_KEY" = @{
pattern = "AWS_ACCESS_KEY|AWS_SECRET_ACCESS_KEY|AKIA[0-9A-Z]{16}"
description = "AWS Access Key ID or Secret Access Key"
rotation_url = "https://console.aws.amazon.com/iam/home#/security_credentials"
severity = "CRITICAL"
}
"SLACK_TOKEN" = @{
pattern = "SLACK_TOKEN|SLACK_WEBHOOK|xox[baprs]-"
description = "Slack API token or webhook URL"
rotation_url = "https://api.slack.com/apps"
severity = "HIGH"
}
"EXPO_TOKEN" = @{
pattern = "EXPO_TOKEN"
description = "Expo authentication token"
rotation_url = "https://expo.dev/accounts/[username]/settings/access-tokens"
severity = "MEDIUM"
}
"CODECOV_TOKEN" = @{
pattern = "CODECOV_TOKEN"
description = "Codecov authentication token"
rotation_url = "https://codecov.io/settings"
severity = "MEDIUM"
}
"WEBFLOW_TOKEN" = @{
pattern = "WEBFLOW_TOKEN"
description = "Webflow API token"
rotation_url = "https://webflow.com/dashboard/account/api"
severity = "MEDIUM"
}
}
# Check trufflehog activity files for credential references
if (Test-Path (Join-Path $script:TempDir "trufflehog_activity.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "trufflehog_activity.txt") -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
foreach ($credType in $credentialTypes.Keys) {
if ($entry -match $credentialTypes[$credType].pattern) {
$parts = $entry -split ":", 3
$file = $parts[0]
$inventory += [PSCustomObject]@{
type = $credType
description = $credentialTypes[$credType].description
found_in = $file
severity = $credentialTypes[$credType].severity
rotation_url = $credentialTypes[$credType].rotation_url
}
}
}
}
}
# Check crypto theft patterns
if (Test-Path (Join-Path $script:TempDir "crypto_patterns.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "crypto_patterns.txt") -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
if ($entry -match "AWS_ACCESS_KEY|AWS_SECRET") {
$parts = $entry -split ":"
$file = $parts[0]
$inventory += [PSCustomObject]@{
type = "AWS_ACCESS_KEY"
description = $credentialTypes["AWS_ACCESS_KEY"].description
found_in = $file
severity = "CRITICAL"
rotation_url = $credentialTypes["AWS_ACCESS_KEY"].rotation_url
}
}
}
}
# Check compromised packages - these may have exposed credentials
if (Test-Path (Join-Path $script:TempDir "compromised_found.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "compromised_found.txt") -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
$parts = $entry -split ":"
$package = $parts[1]
$inventory += [PSCustomObject]@{
type = "COMPROMISED_PACKAGE"
description = "Credentials may have been exposed via compromised package: $package"
found_in = $parts[0]
severity = "HIGH"
rotation_url = "https://www.npmjs.com/package/$($package -replace '@', '')"
}
}
}
# Store inventory
$script:JsonOutput.credential_inventory = $inventory | Select-Object -Unique type, description, found_in, severity, rotation_url
}
function Test-FileHashes {
param([string]$ScanDir)
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
$totalFiles = ($codeFiles | Measure-Object).Count
Write-Status "BLUE" " Filtering files for hash checking..."
# Priority files
$priorityFiles = @()
$priorityFiles += $codeFiles | Where-Object { $_ -match "(setup_bun\.js|bun_environment\.js|actionsSecrets\.json|trufflehog)" }
$priorityFiles += $codeFiles | Where-Object { $_ -notmatch "/node_modules/" }
$priorityFiles = $priorityFiles | Sort-Object -Unique
$filesCount = ($priorityFiles | Measure-Object).Count
$checkMsg = " Checking $filesCount priority files for known malicious content (filtered from $totalFiles total)..."
Write-Status "BLUE" $checkMsg
Write-Status "BLUE" " Computing hashes in parallel..."
# Compute hashes - use parallel processing for PowerShell 7+, sequential for 5.1
$fileHashes = @{}
if ($PSVersionTable.PSVersion.Major -ge 7) {
$priorityFiles | ForEach-Object -Parallel {
$file = $_
if (Test-Path $file) {
try {
$hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash
[PSCustomObject]@{ File = $file; Hash = $hash }
} catch {
$null
}
}
} -ThrottleLimit $PARALLELISM | ForEach-Object {
if ($_.Hash) {
$fileHashes[$_.File] = $_.Hash
}
}
} else {
# PowerShell 5.1 - sequential processing
foreach ($file in $priorityFiles) {
if (Test-Path $file) {
try {
$hash = (Get-FileHash -Path $file -Algorithm SHA256).Hash
if ($hash) {
$fileHashes[$file] = $hash
}
} catch {
# Skip files that can't be hashed
}
}
}
}
Write-Status "BLUE" " Checking against known malicious hashes..."
foreach ($file in $fileHashes.Keys) {
$hash = $fileHashes[$file]
if ($MALICIOUS_HASHLIST -contains $hash) {
Add-Content -Path (Join-Path $script:TempDir "malicious_hashes.txt") -Value "$file`:$hash"
}
}
}
function Test-Packages {
param([string]$ScanDir)
$packageFiles = Get-Content (Join-Path $script:TempDir "package_files.txt") -ErrorAction SilentlyContinue
$filesCount = ($packageFiles | Measure-Object).Count
Write-Status "BLUE" " Checking $filesCount package.json files for compromised packages..."
Write-Status "BLUE" " Extracting dependencies from all package.json files..."
$allDeps = @()
foreach ($file in $packageFiles) {
if (-not (Test-Path $file)) { continue }
try {
$packageJson = Get-Content $file -Raw | ConvertFrom-Json
$deps = @{}
if ($packageJson.dependencies) {
foreach ($key in $packageJson.dependencies.PSObject.Properties.Name) {
$deps[$key] = $packageJson.dependencies.$key
}
}
if ($packageJson.devDependencies) {
foreach ($key in $packageJson.devDependencies.PSObject.Properties.Name) {
$deps[$key] = $packageJson.devDependencies.$key
}
}
foreach ($pkgName in $deps.Keys) {
$version = $deps[$pkgName]
# Try exact match first (with version prefix)
$pkgVersionFull = "$pkgName`:$version"
# Also try without prefix for matching
$versionClean = $version -replace '^[\^~]', ''
$pkgVersionClean = "$pkgName`:$versionClean"
# Store both versions for matching
$allDeps += [PSCustomObject]@{
File = $file
Package = $pkgVersionClean
PackageFull = $pkgVersionFull
PackageName = $pkgName
Version = $versionClean
}
}
} catch {
# Skip invalid JSON
continue
}
}
Write-Status "BLUE" " Checking dependencies against compromised list..."
$depCount = ($allDeps | Measure-Object).Count
Write-Status "BLUE" " Found $depCount total dependencies to check"
# Check for compromised packages - exact match on package:version
foreach ($dep in $allDeps) {
if (Test-CompromisedPackage $dep.Package) {
Add-Content -Path (Join-Path $script:TempDir "compromised_found.txt") -Value "$($dep.File):$($dep.Package -replace ':', '@')"
}
}
# Check for compromised namespaces
Write-Status "BLUE" " Checking for compromised namespaces..."
foreach ($namespace in $COMPROMISED_NAMESPACES) {
$matchingDeps = $allDeps | Where-Object { $_.Package -like "$namespace/*" }
foreach ($dep in $matchingDeps) {
Add-Content -Path (Join-Path $script:TempDir "namespace_warnings.txt") -Value "$($dep.File):Contains packages from compromised namespace: $namespace"
}
}
}
function Test-PostinstallHooks {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for suspicious postinstall hooks..."
$packageFiles = Get-Content (Join-Path $script:TempDir "package_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $packageFiles) {
if (-not (Test-Path $file)) { continue }
try {
$packageJson = Get-Content $file -Raw | ConvertFrom-Json
if ($packageJson.scripts -and $packageJson.scripts.postinstall) {
$postinstallCmd = $packageJson.scripts.postinstall
if ($postinstallCmd -match "curl|wget|node -e|eval") {
Add-Content -Path (Join-Path $script:TempDir "postinstall_hooks.txt") -Value "$file`:Suspicious postinstall: $postinstallCmd"
}
}
} catch {
continue
}
}
}
function Test-Content {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for suspicious content patterns..."
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
$yamlFiles = Get-Content (Join-Path $script:TempDir "yaml_files.txt") -ErrorAction SilentlyContinue
$allFiles = ($codeFiles + $yamlFiles) | Sort-Object -Unique
$webhookMatches = Find-FilesWithPattern -Files $allFiles -Pattern "webhook\.site" -FixedString
foreach ($file in $webhookMatches) {
Add-Content -Path (Join-Path $script:TempDir "suspicious_content.txt") -Value "$file`:webhook.site reference"
}
$endpointMatches = Find-FilesWithPattern -Files $allFiles -Pattern "bb8ca5f6-4175-45d2-b042-fc9ebb8170b7" -FixedString
foreach ($file in $endpointMatches) {
Add-Content -Path (Join-Path $script:TempDir "suspicious_content.txt") -Value "$file`:malicious webhook endpoint"
}
}
function Test-CryptoTheftPatterns {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for cryptocurrency theft patterns..."
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
# Known crypto theft function names
$cryptoFunctions = "checkethereumw|runmask|newdlocal|_0x19ca67"
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern $cryptoFunctions
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:Known crypto theft function names detected"
}
# Known attacker wallets
$walletAddresses = "0xFc4a4858bafef54D1b1d7697bfb5c52F4c166976|1H13VnQJKtT4HjD5ZFKaaiZEetMbG7nDHx|TB9emsCq6fQw6wRk4HBxxNnU6Hwt1DnV67"
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern $walletAddresses
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:Known attacker wallet address detected - HIGH RISK"
}
# npmjs.help phishing domain
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern "npmjs\.help" -FixedString
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:Phishing domain npmjs.help detected"
}
# XMLHttpRequest hijacking
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern "XMLHttpRequest\.prototype\.send" -FixedString
foreach ($file in $matches) {
$isFramework = $file -match "/react-native/Libraries/Network/|/next/dist/compiled/"
$hasCrypto = Test-FileContainsPattern -FilePath $file -Pattern "0x[a-fA-F0-9]{40}|checkethereumw|runmask|webhook\.site|npmjs\.help"
if ($hasCrypto) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:XMLHttpRequest prototype modification with crypto patterns detected - HIGH RISK"
} elseif ($isFramework) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:XMLHttpRequest prototype modification detected in framework code - LOW RISK"
} else {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:XMLHttpRequest prototype modification detected - MEDIUM RISK"
}
}
# JavaScript obfuscation
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern "javascript-obfuscator" -FixedString
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:JavaScript obfuscation detected"
}
# Ethereum wallet address patterns
$matches = Find-FilesWithPattern -Files $codeFiles -Pattern "0x[a-fA-F0-9]{40}"
foreach ($file in $matches) {
if (Test-FileContainsPattern -FilePath $file -Pattern "ethereum|wallet|address|crypto") {
Add-Content -Path (Join-Path $script:TempDir "crypto_patterns.txt") -Value "$file`:Ethereum wallet address patterns detected"
}
}
}
function Test-GitBranches {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for suspicious git branches..."
$gitRepos = Get-Content (Join-Path $script:TempDir "git_repos.txt") -ErrorAction SilentlyContinue
foreach ($repoDir in $gitRepos) {
$refsDir = Join-Path $repoDir ".git\refs\heads"
if (-not (Test-Path $refsDir)) { continue }
$branches = Get-ChildItem -Path $refsDir -File -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match "shai.*hulud" }
foreach ($branch in $branches) {
$branchName = $branch.Name
$commitHash = Get-Content $branch.FullName -Raw -ErrorAction SilentlyContinue
$shortHash = if ($commitHash) { $commitHash.Substring(0, [Math]::Min(8, $commitHash.Length)) } else { "unknown" }
Add-Content -Path (Join-Path $script:TempDir "git_branches.txt") -Value "$repoDir`:Branch '$branchName' (commit: ${shortHash}...)"
}
}
}
function Test-TrufflehogActivity {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for Trufflehog activity and secret scanning..."
$trufflehogFiles = Get-Content (Join-Path $script:TempDir "trufflehog_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $trufflehogFiles) {
if (Test-Path $file) {
Add-Content -Path (Join-Path $script:TempDir "trufflehog_activity.txt") -Value "$file`:HIGH:Trufflehog binary found"
}
}
$scriptFiles = Get-Content (Join-Path $script:TempDir "script_files.txt") -ErrorAction SilentlyContinue
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
$scanFiles = ($scriptFiles + $codeFiles) | Sort-Object -Unique
# Dynamic TruffleHog download patterns
$downloadPattern = "curl.*trufflehog|wget.*trufflehog|bunExecutable.*trufflehog|download.*trufflehog"
$matches = Find-FilesWithPattern -Files $scanFiles -Pattern $downloadPattern -CaseInsensitive
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "trufflehog_activity.txt") -Value "$file`:HIGH:November 2025 pattern - Dynamic TruffleHog download via curl/wget/Bun"
}
# Credential harvesting patterns
$credPattern = "TruffleHog.*scan.*credential|trufflehog.*env|trufflehog.*AWS|trufflehog.*NPM_TOKEN"
$matches = Find-FilesWithPattern -Files $scanFiles -Pattern $credPattern -CaseInsensitive
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "trufflehog_activity.txt") -Value "$file`:HIGH:TruffleHog credential scanning pattern detected"
}
# Credential patterns with exfiltration
$exfilPattern = '(AWS_ACCESS_KEY|GITHUB_TOKEN|NPM_TOKEN).*(webhook\.site|curl|https\.request)'
$matches = Find-FilesWithPattern -Files $scanFiles -Pattern $exfilPattern
$matches = $matches | Where-Object { $_ -notmatch "/node_modules/|\.d\.ts$" }
foreach ($file in $matches) {
Add-Content -Path (Join-Path $script:TempDir "trufflehog_activity.txt") -Value "$file`:HIGH:Credential patterns with potential exfiltration"
}
# Medium priority: Trufflehog references
$refPattern = "trufflehog|TruffleHog"
$matches = Find-FilesWithPattern -Files $scanFiles -Pattern $refPattern -CaseInsensitive
$matches = $matches | Where-Object { $_ -notmatch "/node_modules/|\.md$|/docs/|\.d\.ts$" }
foreach ($file in $matches) {
if (-not (Test-FileContainsPattern -FilePath (Join-Path $script:TempDir "trufflehog_activity.txt") -Pattern "^$([regex]::Escape($file)):")) {
Add-Content -Path (Join-Path $script:TempDir "trufflehog_activity.txt") -Value "$file`:MEDIUM:Contains trufflehog references in source code"
}
}
}
function Test-ShaiHuludRepos {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for Shai-Hulud repositories and migration patterns..."
$gitRepos = Get-Content (Join-Path $script:TempDir "git_repos.txt") -ErrorAction SilentlyContinue
foreach ($repoDir in $gitRepos) {
$repoName = Split-Path $repoDir -Leaf
if ($repoName -match "shai.*hulud") {
Add-Content -Path (Join-Path $script:TempDir "shai_hulud_repos.txt") -Value "$repoDir`:Repository name contains 'Shai-Hulud'"
}
if ($repoName -match "-migration") {
Add-Content -Path (Join-Path $script:TempDir "shai_hulud_repos.txt") -Value "$repoDir`:Repository name contains migration pattern"
}
$gitConfig = Join-Path $repoDir ".git\config"
if (Test-Path $gitConfig) {
$configContent = Get-Content $gitConfig -Raw -ErrorAction SilentlyContinue
if ($configContent -match "shai.*hulud") {
Add-Content -Path (Join-Path $script:TempDir "shai_hulud_repos.txt") -Value "$repoDir`:Git remote contains 'Shai-Hulud'"
}
}
$dataJson = Join-Path $repoDir "data.json"
if (Test-Path $dataJson) {
$content = Get-Content $dataJson -TotalCount 5 -ErrorAction SilentlyContinue
if ($content -match "eyJ" -and $content -match "==") {
Add-Content -Path (Join-Path $script:TempDir "shai_hulud_repos.txt") -Value "$repoDir`:Contains suspicious data.json (possible base64-encoded credentials)"
}
}
}
}
function Test-PackageIntegrity {
param([string]$ScanDir)
Write-Status "BLUE" " Checking package lock files for integrity issues..."
$lockfiles = Get-Content (Join-Path $script:TempDir "lockfiles.txt") -ErrorAction SilentlyContinue
foreach ($lockfile in $lockfiles) {
if (-not (Test-Path $lockfile)) { continue }
$orgFile = $lockfile
$tempLockfile = $null
try {
# Transform pnpm-lock.yaml into pseudo-package-lock.json
if ((Split-Path $orgFile -Leaf) -eq "pnpm-lock.yaml") {
$tempLockfile = Join-Path $script:TempDir "pnpm_temp_$(Get-Random).json"
$transformed = Transform-PnpmYaml $orgFile
Set-Content -Path $tempLockfile -Value $transformed
$lockfile = $tempLockfile
}
if ($lockfile -match "package-lock\.json$|pnpm.*\.json$") {
$lockContent = Get-Content $lockfile -Raw | ConvertFrom-Json
# Check packages section (newer format)
if ($lockContent.packages) {
foreach ($pkgPath in $lockContent.packages.PSObject.Properties.Name) {
$pkg = $lockContent.packages.$pkgPath
if ($pkg.version) {
$pkgName = $pkgPath -replace "^node_modules/", ""
$pkgVersion = "$pkgName`:$($pkg.version)"
if (Test-CompromisedPackage $pkgVersion) {
Add-Content -Path (Join-Path $script:TempDir "integrity_issues.txt") -Value "$orgFile`:Compromised package in lockfile: $pkgVersion"
}
}
}
}
# Check dependencies section (older format)
if ($lockContent.dependencies) {
foreach ($pkgName in $lockContent.dependencies.PSObject.Properties.Name) {
$pkg = $lockContent.dependencies.$pkgName
if ($pkg.version) {
$pkgVersion = "$pkgName`:$($pkg.version)"
if (Test-CompromisedPackage $pkgVersion) {
Add-Content -Path (Join-Path $script:TempDir "integrity_issues.txt") -Value "$orgFile`:Compromised package in lockfile: $pkgVersion"
}
}
}
}
} elseif ($orgFile -match "yarn\.lock$") {
$lockContent = Get-Content $orgFile -Raw
# Simple yarn.lock parsing
$lines = $lockContent -split "`n"
foreach ($line in $lines) {
# Parse yarn.lock format: "package-name@version":
if ($line -match '^"(.+)@(.+)"') {
$pkgName = $Matches[1]
$pkgVersion = $Matches[2]
$pkgVersionFull = "$pkgName`:$pkgVersion"
if (Test-CompromisedPackage $pkgVersionFull) {
Add-Content -Path (Join-Path $script:TempDir "integrity_issues.txt") -Value "$orgFile`:Compromised package in lockfile: $pkgVersionFull"
}
}
}
}
# Check for @ctrl packages (potential worm activity)
$content = Get-Content $orgFile -Raw -ErrorAction SilentlyContinue
if ($content -match "@ctrl") {
Add-Content -Path (Join-Path $script:TempDir "integrity_issues.txt") -Value "$orgFile`:Lockfile contains @ctrl packages (potential worm activity)"
}
} catch {
# Skip invalid lockfiles
continue
} finally {
# Cleanup temp lockfile for pnpm
if ($tempLockfile -and (Test-Path $tempLockfile)) {
Remove-Item $tempLockfile -ErrorAction SilentlyContinue
}
}
}
}
function Test-Typosquatting {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for typosquatting patterns..."
$packageFiles = Get-Content (Join-Path $script:TempDir "package_files.txt") -ErrorAction SilentlyContinue
$warnedPackages = @{}
foreach ($file in $packageFiles) {
if (-not (Test-Path $file)) { continue }
try {
$packageJson = Get-Content $file -Raw | ConvertFrom-Json
$deps = @{}
if ($packageJson.dependencies) {
foreach ($key in $packageJson.dependencies.PSObject.Properties.Name) {
$deps[$key] = $packageJson.dependencies.$key
}
}
if ($packageJson.devDependencies) {
foreach ($key in $packageJson.devDependencies.PSObject.Properties.Name) {
$deps[$key] = $packageJson.devDependencies.$key
}
}
foreach ($pkgName in $deps.Keys) {
$key = "$file`:$pkgName"
if ($warnedPackages.ContainsKey($key)) { continue }
# Check for non-ASCII characters
$hasUnicode = $pkgName -notmatch '^[a-zA-Z0-9@/._-]*$'
if ($hasUnicode) {
Add-Content -Path (Join-Path $script:TempDir "typosquatting_warnings.txt") -Value "$file`:Potential Unicode/homoglyph characters in package: $pkgName"
$warnedPackages[$key] = $true
}
}
} catch {
continue
}
}
}
function Test-NetworkExfiltration {
param([string]$ScanDir)
Write-Status "BLUE" " Checking for network exfiltration patterns..."
$suspiciousDomains = @(
"pastebin.com", "hastebin.com", "ix.io", "0x0.st", "transfer.sh",
"file.io", "anonfiles.com", "mega.nz", "dropbox.com/s/",
"discord.com/api/webhooks", "telegram.org", "t.me",
"ngrok.io", "localtunnel.me", "serveo.net",
"requestbin.com", "webhook.site", "beeceptor.com",
"pipedream.com", "zapier.com/hooks"
)
$codeFiles = Get-Content (Join-Path $script:TempDir "code_files.txt") -ErrorAction SilentlyContinue
foreach ($file in $codeFiles) {
if (-not (Test-Path $file)) { continue }
if ($file -match "/vendor/|/node_modules/") { continue }
$content = Get-Content $file -Raw -ErrorAction SilentlyContinue
if (-not $content) { continue }
# Check for hardcoded IPs
if ($content -match '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b') {
$ips = [regex]::Matches($content, '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b') |
Select-Object -First 3 -ExpandProperty Value
$ipsStr = $ips -join ' '
if ($ipsStr -notmatch "127\.0\.0\.1|0\.0\.0\.0") {
Add-Content -Path (Join-Path $script:TempDir "network_exfiltration_warnings.txt") -Value "$file`:Hardcoded IP addresses found: $ipsStr"
}
}
# Check for suspicious domains
if ($file -notmatch "package-lock\.json|yarn\.lock|/vendor/|/node_modules/") {
foreach ($domain in $suspiciousDomains) {
$domainPattern = [regex]::Escape($domain)
# Check for URL pattern or domain in content
$urlPattern = "https?://.*$domainPattern"
# Simple check: domain appears in content (not just in comments)
$hasDomain = $content -match $domainPattern
if ($content -match $urlPattern -or $hasDomain) {
Add-Content -Path (Join-Path $script:TempDir "network_exfiltration_warnings.txt") -Value "$file`:Suspicious domain found: $domain"
}
}
}
}
}
function Write-LogFile {
param([string]$LogFile, [bool]$ParanoidMode)
$reportLines = @()
# Header
$reportLines += ""
$reportLines += "=============================================="
if ($ParanoidMode) {
$reportLines += " SHAI-HULUD + PARANOID SECURITY REPORT"
} else {
$reportLines += " SHAI-HULUD DETECTION REPORT"
}
$reportLines += "=============================================="
$reportLines += ""
$reportLines += "Report generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$reportLines += ""
$highRiskCount = 0
$mediumRiskCount = 0
# Report malicious workflow files
if (Test-Path (Join-Path $script:TempDir "workflow_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "workflow_files.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: Malicious workflow files detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
# Report malicious file hashes
if (Test-Path (Join-Path $script:TempDir "malicious_hashes.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "malicious_hashes.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Files with known malicious hashes:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " Hash: $($parts[1])"
}
$highRiskCount++
}
$reportLines += ""
}
}
# Report November 2025 Bun attack files
if (Test-Path (Join-Path $script:TempDir "bun_setup_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "bun_setup_files.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: November 2025 Bun attack setup files detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
if (Test-Path (Join-Path $script:TempDir "bun_environment_files_found.txt")) {
$files = Get-Content (Join-Path $script:TempDir "bun_environment_files_found.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: November 2025 Bun environment payload detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
if (Test-Path (Join-Path $script:TempDir "new_workflow_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "new_workflow_files.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: November 2025 malicious workflow files detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
if (Test-Path (Join-Path $script:TempDir "actions_secrets_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "actions_secrets_files.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: Actions secrets exfiltration files detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
# Report discussion workflows
if (Test-Path (Join-Path $script:TempDir "discussion_workflows.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "discussion_workflows.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Malicious discussion-triggered workflows detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " Reason: $($parts[1])"
}
$highRiskCount++
}
$reportLines += ""
}
}
# Report GitHub runners
if (Test-Path (Join-Path $script:TempDir "github_runners.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "github_runners.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Malicious GitHub Actions runners detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " Reason: $($parts[1])"
}
$highRiskCount++
}
$reportLines += ""
}
}
# Report destructive patterns
if (Test-Path (Join-Path $script:TempDir "destructive_patterns.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "destructive_patterns.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "CRITICAL: Destructive payload patterns detected:"
$reportLines += " WARNING: These patterns can cause permanent data loss!"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " Pattern: $($parts[1])"
}
$highRiskCount++
}
$reportLines += " IMMEDIATE ACTION REQUIRED: Quarantine these files and review for data destruction capabilities"
$reportLines += ""
}
}
# Report preinstall Bun patterns
if (Test-Path (Join-Path $script:TempDir "preinstall_bun_patterns.txt")) {
$files = Get-Content (Join-Path $script:TempDir "preinstall_bun_patterns.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: Fake Bun preinstall patterns detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
# Report SHA1HULUD runners
if (Test-Path (Join-Path $script:TempDir "github_sha1hulud_runners.txt")) {
$files = Get-Content (Join-Path $script:TempDir "github_sha1hulud_runners.txt") -ErrorAction SilentlyContinue
if ($files) {
$reportLines += "HIGH RISK: SHA1HULUD GitHub Actions runners detected:"
foreach ($file in $files) {
$reportLines += " - $file"
$highRiskCount++
}
$reportLines += ""
}
}
# Report second coming repos
if (Test-Path (Join-Path $script:TempDir "second_coming_repos.txt")) {
$repos = Get-Content (Join-Path $script:TempDir "second_coming_repos.txt") -ErrorAction SilentlyContinue
if ($repos) {
$reportLines += "HIGH RISK: 'Shai-Hulud: The Second Coming' repositories detected:"
foreach ($repo in $repos) {
$reportLines += " - $repo"
$reportLines += " Repository description: Sha1-Hulud: The Second Coming."
$highRiskCount++
}
$reportLines += ""
}
}
# Report compromised packages
if (Test-Path (Join-Path $script:TempDir "compromised_found.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "compromised_found.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Compromised package versions detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Package: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$highRiskCount++
}
$reportLines += "NOTE: These specific package versions are known to be compromised."
$reportLines += "You should immediately update or remove these packages."
$reportLines += ""
}
}
# Report postinstall hooks
if (Test-Path (Join-Path $script:TempDir "postinstall_hooks.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "postinstall_hooks.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Suspicious postinstall hooks detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Hook: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$highRiskCount++
}
$reportLines += "NOTE: Postinstall hooks can execute arbitrary code during package installation."
$reportLines += "Review these hooks carefully for malicious behavior."
$reportLines += ""
}
}
# Report Trufflehog activity
if (Test-Path (Join-Path $script:TempDir "trufflehog_activity.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "trufflehog_activity.txt") -ErrorAction SilentlyContinue
if ($entries) {
$highTrufflehog = @()
$mediumTrufflehog = @()
foreach ($entry in $entries) {
$parts = $entry -split ":", 3
if ($parts.Length -ge 2) {
if ($parts[1] -eq "HIGH") {
$highTrufflehog += $entry
} elseif ($parts[1] -eq "MEDIUM") {
$mediumTrufflehog += $entry
}
}
}
if ($highTrufflehog) {
$reportLines += "HIGH RISK: Trufflehog/secret scanning activity detected:"
foreach ($entry in $highTrufflehog) {
$parts = $entry -split ":", 3
$reportLines += " - Activity: $($parts[2])"
$reportLines += " Found in: $($parts[0])"
$highRiskCount++
}
$reportLines += "NOTE: These patterns indicate likely malicious credential harvesting."
$reportLines += "Immediate investigation and remediation required."
$reportLines += ""
}
if ($mediumTrufflehog) {
$reportLines += "MEDIUM RISK: Potentially suspicious secret scanning patterns:"
foreach ($entry in $mediumTrufflehog) {
$parts = $entry -split ":", 3
$reportLines += " - Pattern: $($parts[2])"
$reportLines += " Found in: $($parts[0])"
$mediumRiskCount++
}
$reportLines += "NOTE: These may be legitimate security tools or framework code."
$reportLines += "Manual review recommended to determine if they are malicious."
$reportLines += ""
}
}
}
# Report crypto patterns
if (Test-Path (Join-Path $script:TempDir "crypto_patterns.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "crypto_patterns.txt") -ErrorAction SilentlyContinue
if ($entries) {
$highCrypto = @()
$mediumCrypto = @()
foreach ($entry in $entries) {
if ($entry -match "HIGH RISK|Known attacker wallet") {
$highCrypto += $entry
} elseif ($entry -notmatch "LOW RISK") {
$mediumCrypto += $entry
}
}
if ($highCrypto) {
$reportLines += "HIGH RISK: Cryptocurrency theft patterns detected:"
foreach ($entry in $highCrypto) {
$reportLines += " - $entry"
$highRiskCount++
}
$reportLines += "NOTE: These patterns strongly indicate crypto theft malware from the September 8 attack."
$reportLines += "Immediate investigation and remediation required."
$reportLines += ""
}
if ($mediumCrypto) {
$reportLines += "MEDIUM RISK: Potential cryptocurrency manipulation patterns:"
foreach ($entry in $mediumCrypto) {
$reportLines += " - $entry"
$mediumRiskCount++
}
$reportLines += "NOTE: These may be legitimate crypto tools or framework code."
$reportLines += "Manual review recommended to determine if they are malicious."
$reportLines += ""
}
}
}
# Report Shai-Hulud repos
if (Test-Path (Join-Path $script:TempDir "shai_hulud_repos.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "shai_hulud_repos.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "HIGH RISK: Shai-Hulud repositories detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Repository: $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " $($parts[1])"
}
$highRiskCount++
}
$reportLines += "NOTE: 'Shai-Hulud' repositories are created by the malware for exfiltration."
$reportLines += "These should be deleted immediately after investigation."
$reportLines += ""
}
}
# Report git branches
if (Test-Path (Join-Path $script:TempDir "git_branches.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "git_branches.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "MEDIUM RISK: Suspicious git branches:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Repository: $($parts[0])"
if ($parts.Length -gt 1) {
$reportLines += " $($parts[1])"
}
$mediumRiskCount++
}
$reportLines += "NOTE: 'shai-hulud' branches may indicate compromise."
$reportLines += ""
}
}
# Report suspicious content
if (Test-Path (Join-Path $script:TempDir "suspicious_content.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "suspicious_content.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "MEDIUM RISK: Suspicious content patterns:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Pattern: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$mediumRiskCount++
}
$reportLines += "NOTE: Manual review required to determine if these are malicious."
$reportLines += ""
}
}
# Report integrity issues
if (Test-Path (Join-Path $script:TempDir "integrity_issues.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "integrity_issues.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "MEDIUM RISK: Package integrity issues detected:"
foreach ($entry in $entries) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Issue: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$mediumRiskCount++
}
$reportLines += "NOTE: These issues may indicate tampering with package dependencies."
$reportLines += "Verify package versions and regenerate lockfiles if necessary."
$reportLines += ""
}
}
# Report typosquatting (paranoid mode only)
if ($ParanoidMode -and (Test-Path (Join-Path $script:TempDir "typosquatting_warnings.txt"))) {
$entries = Get-Content (Join-Path $script:TempDir "typosquatting_warnings.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "MEDIUM RISK (PARANOID): Potential typosquatting/homoglyph attacks detected:"
$count = 0
foreach ($entry in $entries) {
if ($count -lt 5) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Warning: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$mediumRiskCount++
$count++
}
}
if (($entries | Measure-Object).Count -gt 5) {
$reportLines += " - ... and $(($entries | Measure-Object).Count - 5) more typosquatting warnings (truncated for brevity)"
}
$reportLines += "NOTE: These packages may be impersonating legitimate packages."
$reportLines += ""
}
}
# Report network exfiltration (paranoid mode only)
if ($ParanoidMode -and (Test-Path (Join-Path $script:TempDir "network_exfiltration_warnings.txt"))) {
$entries = Get-Content (Join-Path $script:TempDir "network_exfiltration_warnings.txt") -ErrorAction SilentlyContinue
if ($entries) {
$reportLines += "MEDIUM RISK (PARANOID): Network exfiltration patterns detected:"
$count = 0
foreach ($entry in $entries) {
if ($count -lt 5) {
if ($entry -match '^[A-Z]:\\.*:') {
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
$parts = $entry -split ":", 2
}
$reportLines += " - Warning: $($parts[1])"
$reportLines += " Found in: $($parts[0])"
$mediumRiskCount++
$count++
}
}
if (($entries | Measure-Object).Count -gt 5) {
$reportLines += " - ... and $(($entries | Measure-Object).Count - 5) more network warnings (truncated for brevity)"
}
$reportLines += "NOTE: These patterns may indicate data exfiltration or communication with C2 servers."
$reportLines += ""
}
}
# Summary
$reportLines += "=============================================="
$totalIssues = $highRiskCount + $mediumRiskCount
if ($totalIssues -eq 0) {
$reportLines += "No indicators of Shai-Hulud compromise detected."
$reportLines += "Your system appears clean from this specific attack."
} else {
$reportLines += " SUMMARY:"
$reportLines += " High Risk Issues: $highRiskCount"
$reportLines += " Medium Risk Issues: $mediumRiskCount"
$reportLines += " Total Critical Issues: $totalIssues"
$reportLines += ""
$reportLines += "IMPORTANT:"
$reportLines += " - High risk issues likely indicate actual compromise"
$reportLines += " - Medium risk issues require manual investigation"
if ($ParanoidMode) {
$reportLines += " - Issues marked (PARANOID) are general security checks, not Shai-Hulud specific"
}
$reportLines += " - Consider running additional security scans"
}
$reportLines += "=============================================="
# Write all lines to file
$reportLines | Set-Content $LogFile
Write-Status "GREEN" "Comprehensive report saved to: $LogFile"
}
function Write-Report {
param([bool]$ParanoidMode)
Write-Host ""
Write-Status "BLUE" "=============================================="
if ($ParanoidMode) {
Write-Status "BLUE" " SHAI-HULUD + PARANOID SECURITY REPORT"
} else {
Write-Status "BLUE" " SHAI-HULUD DETECTION REPORT"
}
Write-Status "BLUE" "=============================================="
Write-Host ""
$script:high_risk = 0
$script:medium_risk = 0
# Report malicious workflow files
if (Test-Path (Join-Path $script:TempDir "workflow_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "workflow_files.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: Malicious workflow files detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
# Report malicious file hashes
if (Test-Path (Join-Path $script:TempDir "malicious_hashes.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "malicious_hashes.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Files with known malicious hashes:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " Hash: $($parts[1])"
}
$script:high_risk++
}
Write-Host ""
}
}
# Report November 2025 Bun attack files
if (Test-Path (Join-Path $script:TempDir "bun_setup_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "bun_setup_files.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: November 2025 Bun attack setup files detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
if (Test-Path (Join-Path $script:TempDir "bun_environment_files_found.txt")) {
$files = Get-Content (Join-Path $script:TempDir "bun_environment_files_found.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: November 2025 Bun environment payload detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
if (Test-Path (Join-Path $script:TempDir "new_workflow_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "new_workflow_files.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: November 2025 malicious workflow files detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
if (Test-Path (Join-Path $script:TempDir "actions_secrets_files.txt")) {
$files = Get-Content (Join-Path $script:TempDir "actions_secrets_files.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: Actions secrets exfiltration files detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
# Report discussion workflows
if (Test-Path (Join-Path $script:TempDir "discussion_workflows.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "discussion_workflows.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Malicious discussion-triggered workflows detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " Reason: $($parts[1])"
}
$script:high_risk++
}
Write-Host ""
}
}
# Report GitHub runners
if (Test-Path (Join-Path $script:TempDir "github_runners.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "github_runners.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Malicious GitHub Actions runners detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " Reason: $($parts[1])"
}
$script:high_risk++
}
Write-Host ""
}
}
# Report destructive patterns
if (Test-Path (Join-Path $script:TempDir "destructive_patterns.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "destructive_patterns.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "CRITICAL: Destructive payload patterns detected:"
Write-Status "RED" " WARNING: These patterns can cause permanent data loss!"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " Pattern: $($parts[1])"
}
$script:high_risk++
}
Write-Status "RED" " IMMEDIATE ACTION REQUIRED: Quarantine these files and review for data destruction capabilities"
Write-Host ""
}
}
# Report preinstall Bun patterns
if (Test-Path (Join-Path $script:TempDir "preinstall_bun_patterns.txt")) {
$files = Get-Content (Join-Path $script:TempDir "preinstall_bun_patterns.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: Fake Bun preinstall patterns detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
# Report SHA1HULUD runners
if (Test-Path (Join-Path $script:TempDir "github_sha1hulud_runners.txt")) {
$files = Get-Content (Join-Path $script:TempDir "github_sha1hulud_runners.txt") -ErrorAction SilentlyContinue
if ($files) {
Write-Status "RED" "HIGH RISK: SHA1HULUD GitHub Actions runners detected:"
foreach ($file in $files) {
Write-Host " - $file"
$script:high_risk++
}
Write-Host ""
}
}
# Report second coming repos
if (Test-Path (Join-Path $script:TempDir "second_coming_repos.txt")) {
$repos = Get-Content (Join-Path $script:TempDir "second_coming_repos.txt") -ErrorAction SilentlyContinue
if ($repos) {
Write-Status "RED" "HIGH RISK: 'Shai-Hulud: The Second Coming' repositories detected:"
foreach ($repo in $repos) {
Write-Host " - $repo"
Write-Host " Repository description: Sha1-Hulud: The Second Coming."
$script:high_risk++
}
Write-Host ""
}
}
# Report compromised packages
if (Test-Path (Join-Path $script:TempDir "compromised_found.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "compromised_found.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Compromised package versions detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Package: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:high_risk++
}
Write-Status "YELLOW" "NOTE: These specific package versions are known to be compromised."
Write-Status "YELLOW" "You should immediately update or remove these packages."
Write-Host ""
}
}
# Report postinstall hooks
if (Test-Path (Join-Path $script:TempDir "postinstall_hooks.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "postinstall_hooks.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Suspicious postinstall hooks detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Hook: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:high_risk++
}
Write-Status "YELLOW" "NOTE: Postinstall hooks can execute arbitrary code during package installation."
Write-Status "YELLOW" "Review these hooks carefully for malicious behavior."
Write-Host ""
}
}
# Report Trufflehog activity
if (Test-Path (Join-Path $script:TempDir "trufflehog_activity.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "trufflehog_activity.txt") -ErrorAction SilentlyContinue
if ($entries) {
$highTrufflehog = @()
$mediumTrufflehog = @()
foreach ($entry in $entries) {
$parts = $entry -split ":", 3
if ($parts.Length -ge 2) {
if ($parts[1] -eq "HIGH") {
$highTrufflehog += $entry
} elseif ($parts[1] -eq "MEDIUM") {
$mediumTrufflehog += $entry
}
}
}
if ($highTrufflehog) {
Write-Status "RED" "HIGH RISK: Trufflehog/secret scanning activity detected:"
foreach ($entry in $highTrufflehog) {
$parts = $entry -split ":", 3
Write-Host " - Activity: $($parts[2])"
Write-Host " Found in: $($parts[0])"
$script:high_risk++
}
Write-Status "RED" "NOTE: These patterns indicate likely malicious credential harvesting."
Write-Status "RED" "Immediate investigation and remediation required."
Write-Host ""
}
if ($mediumTrufflehog) {
Write-Status "YELLOW" "MEDIUM RISK: Potentially suspicious secret scanning patterns:"
foreach ($entry in $mediumTrufflehog) {
$parts = $entry -split ":", 3
Write-Host " - Pattern: $($parts[2])"
Write-Host " Found in: $($parts[0])"
$script:medium_risk++
}
Write-Status "YELLOW" "NOTE: These may be legitimate security tools or framework code."
Write-Status "YELLOW" "Manual review recommended to determine if they are malicious."
Write-Host ""
}
}
}
# Report crypto patterns
if (Test-Path (Join-Path $script:TempDir "crypto_patterns.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "crypto_patterns.txt") -ErrorAction SilentlyContinue
if ($entries) {
$highCrypto = @()
$mediumCrypto = @()
foreach ($entry in $entries) {
if ($entry -match "HIGH RISK|Known attacker wallet") {
$highCrypto += $entry
} elseif ($entry -notmatch "LOW RISK") {
$mediumCrypto += $entry
}
}
if ($highCrypto) {
Write-Status "RED" "HIGH RISK: Cryptocurrency theft patterns detected:"
foreach ($entry in $highCrypto) {
Write-Host " - $entry"
$script:high_risk++
}
Write-Status "RED" "NOTE: These patterns strongly indicate crypto theft malware from the September 8 attack."
Write-Status "RED" "Immediate investigation and remediation required."
Write-Host ""
}
if ($mediumCrypto) {
Write-Status "YELLOW" "MEDIUM RISK: Potential cryptocurrency manipulation patterns:"
foreach ($entry in $mediumCrypto) {
Write-Host " - $entry"
$script:medium_risk++
}
Write-Status "YELLOW" "NOTE: These may be legitimate crypto tools or framework code."
Write-Status "YELLOW" "Manual review recommended to determine if they are malicious."
Write-Host ""
}
}
}
# Report Shai-Hulud repos
if (Test-Path (Join-Path $script:TempDir "shai_hulud_repos.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "shai_hulud_repos.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "RED" "HIGH RISK: Shai-Hulud repositories detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Repository: $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " $($parts[1])"
}
$script:high_risk++
}
Write-Status "YELLOW" "NOTE: 'Shai-Hulud' repositories are created by the malware for exfiltration."
Write-Status "YELLOW" "These should be deleted immediately after investigation."
Write-Host ""
}
}
# Report git branches
if (Test-Path (Join-Path $script:TempDir "git_branches.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "git_branches.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "YELLOW" "MEDIUM RISK: Suspicious git branches:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Repository: $($parts[0])"
if ($parts.Length -gt 1) {
Write-Host " $($parts[1])"
}
$script:medium_risk++
}
Write-Status "YELLOW" "NOTE: 'shai-hulud' branches may indicate compromise."
Write-Host ""
}
}
# Report suspicious content
if (Test-Path (Join-Path $script:TempDir "suspicious_content.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "suspicious_content.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "YELLOW" "MEDIUM RISK: Suspicious content patterns:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Pattern: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:medium_risk++
}
Write-Status "YELLOW" "NOTE: Manual review required to determine if these are malicious."
Write-Host ""
}
}
# Report integrity issues
if (Test-Path (Join-Path $script:TempDir "integrity_issues.txt")) {
$entries = Get-Content (Join-Path $script:TempDir "integrity_issues.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "YELLOW" "MEDIUM RISK: Package integrity issues detected:"
foreach ($entry in $entries) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Issue: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:medium_risk++
}
Write-Status "YELLOW" "NOTE: These issues may indicate tampering with package dependencies."
Write-Status "YELLOW" "Verify package versions and regenerate lockfiles if necessary."
Write-Host ""
}
}
# Report typosquatting (paranoid mode only)
if ($ParanoidMode -and (Test-Path (Join-Path $script:TempDir "typosquatting_warnings.txt"))) {
$entries = Get-Content (Join-Path $script:TempDir "typosquatting_warnings.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "YELLOW" "MEDIUM RISK (PARANOID): Potential typosquatting/homoglyph attacks detected:"
$count = 0
foreach ($entry in $entries) {
if ($count -lt 5) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Warning: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:medium_risk++
$count++
}
}
if (($entries | Measure-Object).Count -gt 5) {
Write-Host " - ... and $(($entries | Measure-Object).Count - 5) more typosquatting warnings (truncated for brevity)"
}
Write-Status "YELLOW" "NOTE: These packages may be impersonating legitimate packages."
Write-Host ""
}
}
# Report network exfiltration (paranoid mode only)
if ($ParanoidMode -and (Test-Path (Join-Path $script:TempDir "network_exfiltration_warnings.txt"))) {
$entries = Get-Content (Join-Path $script:TempDir "network_exfiltration_warnings.txt") -ErrorAction SilentlyContinue
if ($entries) {
Write-Status "YELLOW" "MEDIUM RISK (PARANOID): Network exfiltration patterns detected:"
$count = 0
foreach ($entry in $entries) {
if ($count -lt 5) {
# Split on colon, handling Windows paths (C:\path\to\file:description)
# For Windows paths, the drive colon is at position 1, so we split on the last colon
if ($entry -match '^[A-Z]:\\.*:') {
# Windows path - split on last colon (after drive letter)
$lastColon = $entry.LastIndexOf(':')
$parts = @($entry.Substring(0, $lastColon), $entry.Substring($lastColon + 1))
} else {
# Unix path or simple format - split on first colon
$parts = $entry -split ":", 2
}
Write-Host " - Warning: $($parts[1])"
Write-Host " Found in: $($parts[0])"
$script:medium_risk++
$count++
}
}
if (($entries | Measure-Object).Count -gt 5) {
Write-Host " - ... and $(($entries | Measure-Object).Count - 5) more network warnings (truncated for brevity)"
}
Write-Status "YELLOW" "NOTE: These patterns may indicate data exfiltration or communication with C2 servers."
Write-Host ""
}
}
# Report credential inventory (always show, even in quiet mode)
if ($script:JsonOutput.credential_inventory.Count -gt 0) {
if ($script:OutputMode -ne "json") {
Write-Host ""
Write-Status "ORANGE" "==============================================" -AlwaysShow
Write-Status "ORANGE" "CREDENTIAL INVENTORY - ACTION REQUIRED" -AlwaysShow
Write-Status "ORANGE" "==============================================" -AlwaysShow
Write-Host ""
Write-Status "RED" "⚠️ The following credentials may have been exposed:" -AlwaysShow
Write-Host ""
$grouped = $script:JsonOutput.credential_inventory | Group-Object type
foreach ($group in $grouped) {
$first = $group.Group[0]
Write-Status "RED" "🔑 $($first.type): $($first.description)"
Write-Host " Severity: $($first.severity)"
Write-Host " Rotation URL: $($first.rotation_url)"
Write-Host " Found in:"
foreach ($item in $group.Group) {
Write-Host " - $($item.found_in)"
}
Write-Host ""
}
Write-Status "YELLOW" "⚠️ IMMEDIATE ACTION REQUIRED:" -AlwaysShow
Write-Status "YELLOW" " 1. Rotate all listed credentials immediately" -AlwaysShow
Write-Status "YELLOW" " 2. Review access logs for unauthorized usage" -AlwaysShow
Write-Status "YELLOW" " 3. Enable MFA/2FA where available" -AlwaysShow
Write-Status "YELLOW" " 4. Review and revoke any suspicious access" -AlwaysShow
Write-Host ""
}
}
# Summary (always show, even in quiet mode)
if ($script:OutputMode -ne "json") {
Write-Status "BLUE" "==============================================" -AlwaysShow
$totalIssues = $script:high_risk + $script:medium_risk
if ($totalIssues -eq 0) {
Write-Status "GREEN" "No indicators of Shai-Hulud compromise detected." -AlwaysShow
Write-Status "GREEN" "Your system appears clean from this specific attack." -AlwaysShow
} else {
Write-Status "RED" " SUMMARY:" -AlwaysShow
Write-Status "RED" " High Risk Issues: $script:high_risk" -AlwaysShow
Write-Status "YELLOW" " Medium Risk Issues: $script:medium_risk" -AlwaysShow
Write-Status "BLUE" " Total Critical Issues: $totalIssues" -AlwaysShow
Write-Host ""
if ($script:OutputMode -eq "verbose" -or $script:OutputMode -eq "normal") {
Write-Status "YELLOW" "IMPORTANT:"
Write-Status "YELLOW" " - High risk issues likely indicate actual compromise"
Write-Status "YELLOW" " - Medium risk issues require manual investigation"
if ($ParanoidMode) {
Write-Status "YELLOW" " - Issues marked (PARANOID) are general security checks, not Shai-Hulud specific"
}
Write-Status "YELLOW" " - Consider running additional security scans"
}
}
Write-Status "BLUE" "==============================================" -AlwaysShow
}
}
function Write-JsonOutput {
# Collect all findings into JSON structure
$json = @{
timestamp = $script:JsonOutput.timestamp
scan_directory = $script:JsonOutput.scan_directory
findings = @{
high_risk = @()
medium_risk = @()
}
summary = @{
high_risk_count = $script:high_risk
medium_risk_count = $script:medium_risk
total_issues = $script:high_risk + $script:medium_risk
exit_code = if ($script:high_risk -gt 0) { 1 } elseif ($script:medium_risk -gt 0) { 2 } else { 0 }
}
credential_inventory = $script:JsonOutput.credential_inventory
}
# Collect high risk findings
$highRiskFiles = @(
(Join-Path $script:TempDir "workflow_files.txt"),
(Join-Path $script:TempDir "malicious_hashes.txt"),
(Join-Path $script:TempDir "compromised_found.txt"),
(Join-Path $script:TempDir "postinstall_hooks.txt"),
(Join-Path $script:TempDir "bun_setup_files.txt"),
(Join-Path $script:TempDir "bun_environment_files_found.txt"),
(Join-Path $script:TempDir "actions_secrets_files.txt"),
(Join-Path $script:TempDir "new_workflow_files.txt"),
(Join-Path $script:TempDir "github_sha1hulud_runners.txt"),
(Join-Path $script:TempDir "preinstall_bun_patterns.txt"),
(Join-Path $script:TempDir "second_coming_repos.txt"),
(Join-Path $script:TempDir "shai_hulud_repos.txt"),
(Join-Path $script:TempDir "github_runners.txt"),
(Join-Path $script:TempDir "destructive_patterns.txt")
)
foreach ($file in $highRiskFiles) {
if (Test-Path $file) {
$entries = Get-Content $file -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
$json.findings.high_risk += $entry
}
}
}
# Collect medium risk findings
$mediumRiskFiles = @(
(Join-Path $script:TempDir "suspicious_content.txt"),
(Join-Path $script:TempDir "git_branches.txt"),
(Join-Path $script:TempDir "namespace_warnings.txt"),
(Join-Path $script:TempDir "integrity_issues.txt"),
(Join-Path $script:TempDir "typosquatting_warnings.txt"),
(Join-Path $script:TempDir "network_exfiltration_warnings.txt")
)
foreach ($file in $mediumRiskFiles) {
if (Test-Path $file) {
$entries = Get-Content $file -ErrorAction SilentlyContinue
foreach ($entry in $entries) {
$json.findings.medium_risk += $entry
}
}
}
# Output JSON
$json | ConvertTo-Json -Depth 10 | Write-Output
}
# Main function
function Main {
# Load compromised packages
Load-CompromisedPackages
# Create temporary directory
New-TempDir
# Convert scan directory to absolute path
if (-not (Test-Path $ScanDir)) {
if ($script:OutputMode -eq "json") {
Write-Output (@{error = "Directory does not exist: $ScanDir"; exit_code = 1} | ConvertTo-Json)
} else {
Write-Error "Error: Directory does not exist: $ScanDir"
}
exit 1
}
$ScanDir = Resolve-Path $ScanDir
$script:JsonOutput.scan_directory = $ScanDir
if ($script:OutputMode -ne "quiet" -and $script:OutputMode -ne "json") {
Write-Status "GREEN" "Starting Shai-Hulud detection scan..."
if ($Paranoid) {
Write-Status "BLUE" "Scanning directory: $ScanDir (with paranoid mode enabled)"
} else {
Write-Status "BLUE" "Scanning directory: $ScanDir"
}
Write-Host ""
}
# Stage 1: Collect files
Write-Status "ORANGE" "[Stage 1/6] Collecting file inventory for analysis"
Collect-AllFiles $ScanDir
$totalFiles = (Get-Content (Join-Path $script:TempDir "all_files_raw.txt") -ErrorAction SilentlyContinue | Measure-Object).Count
Write-StageComplete "File collection: $totalFiles files found"
# Stage 2: Core detection
Write-Status "ORANGE" '[Stage 2/6] Core detection (workflows, hashes, packages, hooks)'
Test-WorkflowFiles $ScanDir
Test-FileHashes $ScanDir
Test-Packages $ScanDir
Test-PostinstallHooks $ScanDir
Write-StageComplete "Core detection"
# Stage 3: Content analysis
Write-Status "ORANGE" '[Stage 3/6] Content analysis (patterns, crypto, trufflehog, git)'
Test-Content $ScanDir
Test-CryptoTheftPatterns $ScanDir
Test-TrufflehogActivity $ScanDir
Test-GitBranches $ScanDir
Write-StageComplete "Content analysis"
# Stage 4: Repository analysis
Write-Status "ORANGE" '[Stage 4/6] Repository analysis (repos, integrity, bun, workflows)'
Test-ShaiHuludRepos $ScanDir
Test-PackageIntegrity $ScanDir
Test-BunAttackFiles $ScanDir
Test-NewWorkflowPatterns $ScanDir
Write-StageComplete "Repository analysis"
# Stage 5: Advanced detection
Write-Status "ORANGE" '[Stage 5/6] Advanced detection (discussions, runners, destructive)'
Test-DiscussionWorkflows $ScanDir
Test-GitHubRunners $ScanDir
Test-DestructivePatterns $ScanDir
Test-PreinstallBunPatterns $ScanDir
Write-StageComplete "Advanced detection"
# Stage 6: Final checks
Write-Status "ORANGE" "[Stage 6/6] Final checks (actions runner, second coming repos)"
Test-GitHubActionsRunner $ScanDir
Test-SecondComingRepos $ScanDir
Write-StageComplete "Final checks"
# Paranoid mode checks
if ($Paranoid) {
Write-Status "BLUE" "[Paranoid] Running extra security checks"
Test-Typosquatting $ScanDir
Test-NetworkExfiltration $ScanDir
Write-StageComplete "Paranoid mode checks"
}
# Collect credential inventory
Collect-CredentialInventory $ScanDir
# Generate report (skip in JSON mode)
if ($script:OutputMode -ne "json") {
Write-Status "BLUE" 'Generating report'
Write-Report $Paranoid
}
# Write log file if requested
if ($SaveLog) {
Write-LogFile $SaveLog $Paranoid
}
Write-StageComplete 'Total scan time'
# Output JSON if requested
if ($script:OutputMode -eq "json") {
Write-JsonOutput
}
# Return appropriate exit code
if ($script:high_risk -gt 0) {
exit 1
} elseif ($script:medium_risk -gt 0) {
exit 2
} else {
exit 0
}
}
# Run main function
try {
Main
} finally {
Remove-TempDir
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment