---
title: "Protecting Astro from Supply Chain Attacks: Part 2 - Long-Term Security Measures"
description: "Implement comprehensive security measures for your Astro project. Learn how to secure your CI/CD pipeline, automate dependency scanning, and prevent future npm supply chain attacks."
source: "https://www.launchfa.st/blog/protect-astro-supply-chain-attacks-part-2"
author: "Rishi Raj Jain"
created_at: 2025-12-01T12:00:00.000Z
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Protecting Astro from Supply Chain Attacks Part 2" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/protect-astro-supply-chain-attacks-2.png" />

> **This is Part 2 of a 2-part series** on protecting Astro websites from npm supply chain attacks. [Part 1](/blog/protect-astro-supply-chain-attacks-part-1) covered understanding the threat and immediate response. This article focuses on long-term security measures, monitoring, and best practices.

In [Part 1](/blog/protect-astro-supply-chain-attacks-part-1), we covered the immediate response to the Shai-Hulud 2.0 attack. Now it's time to implement comprehensive security measures that will protect your Astro project from future supply chain attacks. These aren't temporary fixes-they're essential practices that should become part of your development workflow.

## Prerequisites

You'll need the following:

- Completed [Part 1](/blog/protect-astro-supply-chain-attacks-part-1) of this series

## 1. Implement Package Lock and Verification

The first line of defense against supply chain attacks is ensuring that you always install exactly the versions of packages you've tested and verified.

### 1.1 Use Exact Version Numbers

Never use version ranges in your `package.json` for production dependencies:

```json
// ❌ Bad: Allows automatic updates
{
  "dependencies": {
    "astro": "^4.16.1",    // Can install 4.16.x or 4.x.x
    "sharp": "~0.33.1"     // Can install 0.33.x
  }
}

// ✅ Good: Exact versions only
{
  "dependencies": {
    "astro": "4.16.1",
    "sharp": "0.33.1"
  }
}
```

### 1.2 Configure npm/pnpm for Security

Create an `.npmrc` file in your project root with strict security settings:

```bash
# File: .npmrc

# Always use exact versions when saving packages
save-exact=true

# Ensure package-lock.json is always created and used
package-lock=true

# Run audit on every install
audit=true

# Set minimum audit level (low, moderate, high, critical)
audit-level=moderate

# Fail install if vulnerabilities are found
audit-report=true
```

For pnpm users (recommended), configure additional security features:

```bash
# File: .npmrc (for pnpm)

# Require exact versions
save-exact=true

# Strict peer dependencies
strict-peer-dependencies=true

# Verify package integrity using checksums
package-import-method=copy

# Run security audit on install
audit=true

# Only allow packages from verified registries
registry=https://registry.npmjs.org/

# Verify SSL certificates
strict-ssl=true
```

### 1.3 Always Commit Lock Files

Your lock file (`package-lock.json` or `pnpm-lock.yaml`) is critical for reproducible builds:

```bash
# Ensure lock file is tracked in git
git add pnpm-lock.yaml
git commit -m "chore: commit lock file for security"
```

Update your `.gitignore` to ensure lock files are never ignored:

```bash
# File: .gitignore

# Make sure these are NOT in .gitignore:
# pnpm-lock.yaml
# package-lock.json
# yarn.lock

# But DO ignore
node_modules/
```

## 2. Use Dependency Scanning Tools

Automated scanning tools can detect vulnerabilities before they make it into production. Let's set up a comprehensive scanning workflow.

### 2.1 Snyk Security Scan with GitHub Actions

Snyk is a leading [developer security platform](https://snyk.io/) specializing in scanning your dependencies for vulnerabilities.

Create a GitHub Actions workflow for Snyk scanning:

```yaml
# File: .github/workflows/snyk-security-scan.yml

name: Snyk Security Scan

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:

jobs:
  snyk-scan:
    runs-on: ubuntu-latest

    permissions:
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies (safely)
        run: pnpm install --frozen-lockfile --ignore-scripts

      - name: Run npm audit
        run: pnpm audit --audit-level=moderate
        continue-on-error: true
        id: npm-audit

      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --all-projects
        continue-on-error: true
        id: snyk-scan

      - name: Generate Security Report
        if: always()
        run: |
          echo "# Snyk Security Scan Report" > security-report.md
          echo "Generated: $(date)" >> security-report.md
          echo "" >> security-report.md
          echo "## Scan Results" >> security-report.md
          echo "- npm audit: ${{ steps.npm-audit.outcome }}" >> security-report.md
          echo "- Snyk scan: ${{ steps.snyk-scan.outcome }}" >> security-report.md

      - name: Upload Security Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: snyk-security-report-${{ github.run_number }}
          path: security-report.md
          retention-days: 90
```

Snyk offers continuous monitoring of your dependencies, keeping track of new vulnerabilities even after your code is merged. It provides fix suggestions that identify exactly which updates will resolve known issues. With strong IDE and CI/CD integration, Snyk helps you catch security problems before your dependencies reach production. Additionally, Snyk features a generous free tier for public and open-source repositories, making it accessible to the broader developer community.

### 2.2 Trivy Vulnerability Scanner with GitHub Actions

Trivy is an [open-source vulnerability scanner](https://github.com/aquasecurity/trivy) that provides broader scanning, including infrastructure and container security.

Set up Trivy with GitHub Actions:

```yaml
# File: .github/workflows/trivy-scan.yml

name: Trivy Scan

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
  schedule:
    - cron: '0 4 * * *'
  workflow_dispatch:

jobs:
  trivy-scan:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      security-events: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Install dependencies (safely)
        run: pnpm install --frozen-lockfile --ignore-scripts

      - name: Trivy vulnerability scan (code & dependencies)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'HIGH,CRITICAL'

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'
```

Trivy is an open-source security scanner that offers broad coverage for vulnerabilities across your entire project-not just in `node_modules`, but also inside containers, operating system packages, configuration files, and infrastructure-as-code. Its results can be output in SARIF format, allowing seamless integration with GitHub Security features. Trivy is free, straightforward to automate, and well-suited for both local and CI/CD workflows.

### 2.3 Set Up Local Security Scanning

Don't wait for CI/CD to catch issues. Scan locally before pushing:

```bash
# Install Snyk CLI globally
npm install -g snyk

# Authenticate with Snyk
snyk auth

# Scan your project
snyk test

# Monitor your project (sends snapshot to Snyk for continuous monitoring)
snyk monitor
```

Add a pre-push git hook:

```bash
# Install husky if you haven't already
pnpm add -D husky

# Initialize husky
npx husky init

# Create pre-push hook
cat > .husky/pre-push << 'EOF'
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "🔍 Running security scan before push..."
pnpm audit --audit-level=moderate
EOF

chmod +x .husky/pre-push
```

## 3. Secure Your CI/CD Pipeline

The Shai-Hulud 2.0 attack specifically targeted CI/CD environments where secrets are readily available. Here's how to harden your pipeline.

### 3.1 Use Minimal Permissions

Configure your GitHub Actions with the principle of least privilege:

```yaml
# File: .github/workflows/deploy.yml

name: Deploy to Production

on:
  push:
    branches: [main]

# Set minimal default permissions
permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    # Override only for specific needs
    permissions:
      contents: read
      deployments: write
      id-token: write  # For OIDC authentication
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'pnpm'
          
      - name: Install pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8
          
      # Critical: Disable install scripts in CI
      - name: Install dependencies
        run: pnpm install --frozen-lockfile --ignore-scripts
        
      - name: Build
        env:
          # Use environment-specific secrets
          DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
          API_KEY: ${{ secrets.PROD_API_KEY }}
        run: pnpm run build
        
      - name: Deploy
        run: pnpm run deploy
```

### 3.2 Disable Install Scripts

Install scripts are the primary attack vector. Always disable them in CI/CD:

```yaml
# Always use --ignore-scripts in CI
- name: Install dependencies
  run: pnpm install --frozen-lockfile --ignore-scripts
```

For packages that legitimately need install scripts (like `sharp` or `esbuild`), rebuild them explicitly:

```typescript
// File: scripts/secure-install.ts

import { execSync } from 'child_process'

console.log('🔒 Running secure installation...\n')

try {
  // Install without running any scripts
  console.log('📦 Installing packages without scripts...')
  execSync('pnpm install --frozen-lockfile --ignore-scripts', {
    stdio: 'inherit',
  })
  
  console.log('\n✅ Dependencies installed safely')
  
  // Explicitly rebuild trusted packages that need native bindings
  const TRUSTED_PACKAGES = [
    'sharp',      // Image processing
    'esbuild',    // Bundler
    '@parcel/watcher',  // File watcher
    // Add other trusted packages here
  ]
  
  console.log('\n📦 Rebuilding trusted native packages...')
  for (const pkg of TRUSTED_PACKAGES) {
    try {
      console.log(`  - Rebuilding ${pkg}...`)
      execSync(`pnpm rebuild ${pkg}`, { stdio: 'inherit' })
      console.log(`  ✅ ${pkg} rebuilt successfully`)
    } catch (error) {
      console.error(`  ❌ Failed to rebuild ${pkg}`)
      // Don't fail the whole process - package might not be installed
    }
  }
  
  console.log('\n✅ Secure installation complete!')
} catch (error) {
  console.error('\n❌ Installation failed:', error.message)
  process.exit(1)
}
```

Update your `package.json`:

```json
{
  "scripts": {
    "install:safe": "tsx scripts/secure-install.ts",
    "build": "astro build",
    "dev": "astro dev"
  }
}
```

### 3.3 Use Environment Isolation

Use different secrets and credentials for each environment (dev, staging, production) to contain potential breaches and prevent leaks from spreading across environments.

```yaml
# File: .github/workflows/deploy-staging.yml

name: Deploy to Staging

on:
  push:
    branches: [develop]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging  # GitHub environment
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Build and Deploy
        env:
          # Staging-specific secrets
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
          API_KEY: ${{ secrets.STAGING_API_KEY }}
        run: |
          pnpm install --frozen-lockfile --ignore-scripts
          pnpm run build
          pnpm run deploy:staging
```
---
```yaml
# File: .github/workflows/deploy-production.yml

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment: production  # Different environment
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Build and Deploy
        env:
          # Production-specific secrets
          DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
          API_KEY: ${{ secrets.PROD_API_KEY }}
        run: |
          pnpm install --frozen-lockfile --ignore-scripts
          pnpm run build
          pnpm run deploy:production
```

### 3.4 Use OIDC Instead of Long-Lived Tokens

For cloud deployments, use OpenID Connect (OIDC) instead of storing long-lived credentials:

```yaml
# File: .github/workflows/deploy-aws.yml

name: Deploy to AWS

on:
  push:
    branches: [main]

permissions:
  id-token: write  # Required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      # Use OIDC to get temporary credentials
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: us-east-1
          
      # Now you have temporary AWS credentials
      - name: Deploy to S3
        run: |
          pnpm run build
          aws s3 sync dist/ s3://my-bucket/
```

By adopting OIDC, you eliminate the need to store long-lived cloud credentials in GitHub, significantly reducing the risk if your CI environment is ever compromised. Instead, GitHub Actions can request short-lived, automatically expiring credentials for each workflow run. Every authentication event is tracked and auditable in AWS CloudTrail, ensuring full traceability and security transparency.

## 4. Configure npm Audit Automation

Make security auditing a regular part of your development workflow.

### 4.1 Set Up Pre-Commit Hooks

Setting up pre-commit hooks adds an automated checkpoint in your workflow to prevent you from committing insecure code. Every time you attempt a commit, Husky will trigger the security audit (using `pnpm audit --audit-level=moderate`). If vulnerabilities above the threshold are detected, the commit is blocked until the issues are resolved or bypassed intentionally. This helps ensure only secure code is committed to the repository, maintaining your project's integrity from the outset.

```bash
# File: .husky/pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

echo "🔍 Running pre-commit security checks...\n"

# Run audit
echo "📦 Checking for vulnerabilities..."
pnpm audit --audit-level=moderate

if [ $? -ne 0 ]; then
  echo "\n❌ Security vulnerabilities found!"
  echo "Run 'pnpm audit fix' to resolve issues."
  echo "Or use --no-verify to skip (not recommended)."
  exit 1
fi

echo "✅ Security checks passed!\n"
```

Further, update scripts in package.json to run the audits via the associated package manager.

```json
{
  "scripts": {
    "audit": "pnpm audit --audit-level=moderate",
    "audit:fix": "pnpm audit fix",
    "audit:production": "pnpm audit --production --audit-level=high",
    "precommit": "pnpm run audit",
    "prepush": "pnpm run audit:production"
  }
}
```

### 4.2 Create Automated Security Reports

This section shows how to create automated security reports for your project.

```typescript
// File: scripts/generate-security-report.ts

import { execSync } from 'child_process'
import { writeFileSync } from 'fs'

const generateSecurityReport = () => {
  console.log('📊 Generating security report...\n')
  
  const report = {
    timestamp: new Date().toISOString(),
    auditResults: null as any,
    outdatedPackages: null as any,
    recommendations: [] as string[],
  }
  
  try {
    // Run npm audit
    const auditOutput = execSync('pnpm audit --json', {
      encoding: 'utf-8',
    })
    report.auditResults = JSON.parse(auditOutput)
  } catch (error: any) {
    // Audit exits with non-zero if vulnerabilities found
    if (error.stdout) {
      report.auditResults = JSON.parse(error.stdout)
    }
  }
  
  // Generate recommendations
  const vulnerabilities = report.auditResults?.metadata?.vulnerabilities
  if (vulnerabilities) {
    const { critical, high, moderate } = vulnerabilities
    
    if (critical > 0) {
      report.recommendations.push(
        `🚨 CRITICAL: ${critical} critical vulnerabilities require immediate attention`
      )
    }
    if (high > 0) {
      report.recommendations.push(
        `⚠️  HIGH: ${high} high-severity vulnerabilities should be addressed soon`
      )
    }
    if (moderate > 0) {
      report.recommendations.push(
        `ℹ️  MODERATE: ${moderate} moderate vulnerabilities should be reviewed`
      )
    }
  }
  
  // Write report
  const reportPath = `security-report-${Date.now()}.json`
  writeFileSync(reportPath, JSON.stringify(report, null, 2))
  
  console.log(`✅ Security report generated: ${reportPath}\n`)
  
  // Print summary
  console.log('📊 Summary:')
  report.recommendations.forEach(rec => console.log(rec))
  
  return report
}

generateSecurityReport()
```

- Run a `pnpm audit` command to scan your dependencies for vulnerabilities and capture the results as JSON.
- Collect the discovery timestamp, audit results, and recommendations for remediation.
- Summarize vulnerabilities by severity (critical, high, moderate), and make actionable recommendations if issues are present.
- Write the resulting security report to a file you can review or share with your team.

By integrating this script into your workflow, you ensure clear and regular visibility into your project's security posture. This makes it easier to track and fix vulnerabilities effectively, and supports a policy of continuous security monitoring.

## Conclusion

The steps we’ve covered here set up a reliable defense: always use exact versions and lock files, automate security checks in your CI/CD pipeline, turn off install scripts during production builds, use OIDC instead of static credentials, and make security reviews a standard part of your git process.

If you have any questions or comments, feel free to reach out to me on [Twitter](https://twitter.com/direct_messages/create/rishi_raj_jain_).
