This is Part 1 of a 3-part series on protecting Astro websites from npm supply chain attacks. In this article, we’ll cover understanding the threat and immediate response actions. Check out Part 2 for long-term security measures and Part 3 for best practices and monitoring.
High Quality Starter Kits with built-in authentication flow (Auth.js), object uploads (AWS, Clouflare R2, Firebase Storage, Supabase Storage), integrated payments (Stripe, LemonSqueezy), email verification flow (Resend, Postmark, Sendgrid), and much more. Compatible with any database (Redis, Postgres, MongoDB, SQLite, Firestore).Next.js Starter KitSvelteKit Starter KitAstro Starter Kit
In November 2025, the npm ecosystem faced one of its most significant supply chain attacks to date: Shai-Hulud 2.0. This attack compromised over 700 npm packages, including popular libraries from Zapier, PostHog, and Postman, affecting roughly 27% of cloud and code environments. The malware executed during the preinstall phase, stealing credentials from GitHub, AWS, GCP, Azure, and other services, then exfiltrating them to public repositories.
According to Wiz Research, over 25,000 malicious repositories were created containing stolen secrets, with new repos appearing at a rate of ~1,000 every 30 minutes during the attack. If you’re running an Astro website, this guide will help you understand the threat and take immediate action.
Prerequisites
You’ll need the following:
- Node.js 18 or later
- An existing Astro project
- Access to your CI/CD pipeline configuration
- Administrative access to your package manager (npm, pnpm, or yarn)
1. Understanding the Shai-Hulud 2.0 Attack
The Shai-Hulud 2.0 attack leveraged compromised maintainer accounts to publish trojanized versions of legitimate npm packages. This wasn’t a new zero-day vulnerability or a sophisticated exploit-it was a compromise of trusted accounts that allowed attackers to publish malicious code under the guise of legitimate updates.
1.1 What Makes This Attack Different?
Unlike traditional attacks, this variant introduced several concerning characteristics:
1.1.1 Executes During preinstall Phase
The malware runs during the preinstall lifecycle hook, which dramatically widens the blast radius. This means:
- It executes before any package installation completes
- It runs on developer machines during
npm install - It executes in CI/CD pipelines automatically
- It can’t be easily prevented by typical security measures
// Example of how the malicious package.json looked{ "scripts": { "preinstall": "node malicious-script.js" }}1.1.2 Steals Multiple Credential Types
The attack wasn’t limited to a single type of credential. It actively searched for and exfiltrated:
- GitHub Tokens: Personal access tokens, OAuth tokens, and deploy keys
- AWS Credentials: Long-term access keys (AKIA format), secret keys, and session tokens
- GCP Credentials: Service account keys and application credentials
- Azure Credentials: Service principal credentials and storage keys
- Environment Variables: Any secrets stored in
.envfiles or environment
1.1.3 Uses Cross-Victim Exfiltration
One of the most insidious aspects is that your stolen data might not even appear in your own GitHub account:
// Simplified example of the exfiltration logicconst victims = getRandomVictimAccounts()const stolenData = gatherCredentials()
// Upload to ANOTHER victim's accountuploadToGitHub(victims[randomIndex], stolenData)This means:
- Your secrets could be in someone else’s public repository
- Other victims’ secrets might appear in your account
- Detection is significantly more difficult
- The blast radius is much larger than initially visible
1.1.4 Creates Backdoor Workflows
The malware attempted to inject malicious GitHub Actions workflows for persistent access:
# Example of injected .github/workflows/discussion.yamlname: Discussion Updateon: push: schedule: - cron: '0 */6 * * *'jobs: exfiltrate: runs-on: ubuntu-latest steps: - name: Gather More Secrets run: | # Malicious code here1.1.5 Affects High-Prevalence Packages
The attack targeted packages that are widely used across the ecosystem:
@postman/tunnel-agent- Found in 27% of environmentsposthog-node- Found in 25% of environments@asyncapi/specs- Found in 20% of environments- Various Zapier platform packages
This maximized the attack’s reach, potentially affecting thousands of projects instantly.
1.2 Attack Timeline and Impact
Here’s what we know about the attack progression:
-
November 21-23, 2025: Initial compromise and upload of trojanized packages to npm
-
November 24, 2025 (01:22 UTC): Earliest evidence of repositories being created on GitHub with leaked secrets
-
November 24, 2025 (~03:00 UTC): Earliest evidence of malicious package versions on npm
-
November 25, 2025 (22:45 UTC): Potential second phase observed with private repositories being published
-
November 26, 2025: GitHub begins mitigation by revoking tokens and privatizing malicious repositories
Impact Statistics:
- ~700 compromised packages on npm
- 25,000+ malicious repositories created
- ~500 affected GitHub users
- 775+ compromised GitHub access tokens (verified)
- 373+ AWS credentials leaked
- 300+ GCP credentials exposed
- 115+ Azure credentials compromised
The malware creates specific indicator files:
cloud.json # Cloud provider credentialscontents.json # Repository contents and codeenvironment.json # Environment variables and secretstruffleSecrets.json # Secrets detected by TruffleHog patternsbun_environment.js # Bun runtime environment datasetup_bun.js # Setup script for persistence2. Immediate Actions to Take
If you suspect your Astro project might be affected, follow these steps immediately. Time is critical - the longer compromised credentials remain active, the more damage can occur.
2.1 Check Your Dependencies
First, verify if any of the compromised packages are in your dependency tree.
2.1.1 Quick Check Using pnpm Audit
# Run a security auditpnpm audit fix
# Or with npmnpm audit --audit-level=moderate2.1.2 Manual Inspection
Review your package.json and lock files for high-risk packages:
# Check for specific compromised packagesgrep -E "@postman/tunnel-agent|posthog-node|posthog-js|@asyncapi/specs|zapier-platform" package.json pnpm-lock.yaml2.1.3 Known Compromised Packages (Partial List)
Here are some of the most prevalent affected packages:
{ "high-prevalence-packages": [ "@postman/tunnel-agent", "posthog-node", "posthog-js", "@asyncapi/specs", "@asyncapi/openapi-schema-parser", "zapier-platform-cli", "zapier-platform-core", "zapier-platform-schema", "zapier-async-storage", "get-them-args", "shell-exec", "kill-port" ]}2.1.4 Check Transitive Dependencies
The compromised packages might not be direct dependencies. Check your entire dependency tree:
# For pnpmpnpm list --depth=Infinity | grep -E "postman|posthog|zapier"
# For npmnpm list --all | grep -E "postman|posthog|zapier"2.1.5 Review Installation History
Check when packages were last installed to determine exposure window:
# Check git history of lock file changesgit log -p --follow pnpm-lock.yaml | grep -A 5 -B 5 "postman\|posthog\|zapier"2.2. Audit Your Environment
Check for evidence of compromise in your local development environment and CI/CD systems.
2.2.1 Scan for Malicious Files
# Check for known malware indicator filesfind . -type f \( \ -name "cloud.json" -o \ -name "contents.json" -o \ -name "environment.json" -o \ -name "truffleSecrets.json" -o \ -name "bun_environment.js" -o \ -name "setup_bun.js" \\) 2>/dev/nullIf this command returns any results, your environment has been compromised.
2.2.2 Check GitHub Workflows
# List all workflow filesls -la .github/workflows/
# Check for the known malicious workflowcat .github/workflows/discussion.yaml 2>/dev/nullThe malicious workflow typically has:
- Name: “Discussion Update” or similar
- Suspicious cron schedules
- Unusual environment variable access
- Base64 encoded commands
2.2.3 Review Git History for Unauthorized Changes
# Check recent commits for suspicious changesgit log --all --oneline --decorate --graph -n 20
# Look for unexpected workflow additionsgit log --all --full-history -- .github/workflows/2.2.4 Check Your GitHub Repositories
Log into GitHub and check for:
- Unexpected public repositories in your account
- Repositories with names containing leaked data or credentials
- Repository descriptions mentioning “Shai-Hulud” or suspicious content
- Newly created repositories you didn’t create
2.2.5 Verify CI/CD Logs
Review your CI/CD pipeline logs for:
- Unexpected network connections during builds
- Failed authentication attempts
- Unusual package installations
- Build duration increases (malware execution takes time)
# Example: Check GitHub Actions logsgh run list --limit 20gh run view <run-id> --log2.3 Rotate All Credentials
If you’ve identified any compromised packages or suspicious files, immediately rotate all credentials. Don’t wait to confirm the full extent of the compromise.
2.3.1 GitHub Tokens
- Navigate to GitHub Settings → Developer settings → Personal access tokens
- Review all existing tokens:
- Note what each token is used for
- Check last used date
- Revoke ALL tokens (even if they seem safe)
- Create new tokens with minimal required permissions:
# Use the principle of least privilege# Instead of:# - repo (full control)# Use:# - repo:status (read-only)# - public_repo (if possible)- Update tokens in:
- CI/CD secrets (GitHub Actions, CircleCI, etc.)
- Local development environments
- Any automation scripts
- Third-party integrations
2.3.2 AWS Credentials
Rotate AWS access keys immediately:
# 1. List current access keysaws iam list-access-keys --user-name your-username
# 2. Create new access keys FIRSTaws iam create-access-key --user-name your-username# Save the output immediately - you won't see it again
# 3. Update the new keys everywhere they're used:# - CI/CD secrets# - ~/.aws/credentials# - Environment variables# - Application configuration
# 4. Test that new keys workaws sts get-caller-identity
# 5. Delete old access keysaws iam delete-access-key \ --access-key-id AKIA_OLD_KEY_ID \ --user-name your-username2.3.3 Important AWS Security Steps
# Check CloudTrail for unauthorized activityaws cloudtrail lookup-events \ --lookup-attributes AttributeKey=Username,AttributeValue=your-username \ --max-items 100 \ --start-time 2025-11-20T00:00:00Z
# Review recent S3 bucket accessaws s3api get-bucket-logging --bucket your-bucket-name
# Check for unauthorized IAM changesaws iam get-credential-report2.3.4 GCP Credentials
For Google Cloud Platform:
# List service accountsgcloud iam service-accounts list
# Create new key for a service accountgcloud iam service-accounts keys create new-key.json \ --iam-account=sa-name@project-id.iam.gserviceaccount.com
# List all keys for a service accountgcloud iam service-accounts keys list \ --iam-account=sa-name@project-id.iam.gserviceaccount.com
# Delete old keysgcloud iam service-accounts keys delete KEY_ID \ --iam-account=sa-name@project-id.iam.gserviceaccount.com2.3.5 Azure Credentials
For Microsoft Azure:
# List service principalsaz ad sp list --show-mine
# Reset service principal credentialsaz ad sp credential reset \ --id <app-id> \ --append
# Create new client secretaz ad sp credential reset \ --id <app-id> \ --credential-description "New secret after Shai-Hulud"2.3.6 Database Credentials
Don’t forget to rotate database credentials:
# Example for PostgreSQL# Connect to your databasepsql -U admin -d postgres
# Create new user with same privilegesCREATE USER newuser WITH PASSWORD 'strong-new-password';GRANT ALL PRIVILEGES ON DATABASE yourdb TO newuser;
# Update application configuration# Test thoroughly# Then remove old userDROP USER olduser;2.3.7 Environment Variables and Secrets
Update all environment secrets:
import crypto from 'crypto'
const generateSecureSecret = (length: number = 32): string => { return crypto.randomBytes(length).toString('hex')}
console.log('🔐 New secrets generated:\n')console.log(`SESSION_SECRET=${generateSecureSecret(64)}`)console.log(`API_SECRET=${generateSecureSecret(32)}`)console.log(`ENCRYPTION_KEY=${generateSecureSecret(32)}`)console.log(`JWT_SECRET=${generateSecureSecret(64)}`)console.log('\n⚠️ Update these in:')console.log(' - Vercel/Netlify/hosting platform')console.log(' - GitHub repository secrets')console.log(' - Local .env files')console.log(' - Team password manager')Next Steps
You’ve taken critical first steps to secure your Astro project. However, protecting against supply chain attacks requires ongoing vigilance and robust security practices.
In Part 2, we explore long-term prevention (locking dependencies, integrity checks, CI/CD hardening) and in Part 3 we dig deeper into security-dependency isolation, secret management, automated scans, and monitoring.
Conclusion
The Shai-Hulud 2.0 attack is a stark reminder that supply chain security is not optional-it’s a critical component of modern web development. The immediate actions covered in this article are just the first line of defense.
If you have any questions or comments, feel free to reach out to me on Twitter.