Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,34 @@ github:

## Scenario overview

Modern software development relies heavily on dependencies - third-party packages, libraries, and modules that accelerate development but also introduce security risks. Supply chain attacks targeting dependencies have become increasingly sophisticated, with malicious actors using techniques like typosquatting, package hijacking, account takeovers, and injecting malicious code into legitimate packages.
Modern software development relies heavily on dependencies - third-party packages, libraries, and modules that accelerate development but also introduce security risks. Supply chain attacks targeting dependencies have become increasingly sophisticated, with malicious actors using techniques like typosquatting, package confusion, package hijacking, account takeovers, and injecting malicious code into legitimate packages.

One example is [Shai-Hulud](https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages), a self-replicating worm that infiltrated the npm ecosystem via compromised maintainer accounts, injecting malicious post-install scripts into popular JavaScript packages. The malware also stole credentials, injected backdoors, and attempted to destroy the user's home directory.

Managing dependency threats requires a defense-in-depth strategy. No single control is perfect, so layering multiple defenses creates a more robust security posture. This article provides opinionated guidance on implementing practical security measures that have been field-tested against real-world attacks like Shai-Hulud.
Managing dependency threats requires a defense-in-depth strategy. No single control is perfect. Even registry-level protections like package cooldown periods (waiting periods after unpublishing before names can be re-registered) aren't sufficient against determined attackers. Layering multiple defenses creates a more robust security posture. This article provides opinionated guidance on implementing practical security measures that have been field-tested against real-world attacks like Shai-Hulud.

## Key design strategies

Layered defenses reduce single points of failure by using multiple controls that reinforce each other - each covers gaps the others leave, so together they form a complete defense. Implement these core strategies:

1. **Disable package lifecycle scripts by default**: Package managers execute scripts automatically during installation (`preinstall`, `postinstall`, etc.). These scripts can run arbitrary code on your system before you've reviewed the package contents. Disabling them by default prevents the most common attack vector.
2. **Use dev containers for isolation**: Development containers provide strong isolation between host machine and project environment. Even if malicious code executes, it can't access the actual home directory, SSH keys, or cloud credentials. GitHub Codespaces and Microsoft Dev Box provide managed environments with this isolation built in.
2. **Use dev containers for isolation**: Development containers provide strong isolation between host machine and project environment. Even if malicious code executes, it can't access the actual home directory, SSH keys, or cloud credentials. GitHub Codespaces and Windows 365 provide managed environments with isolation built in.
3. **Require signed commits with user interaction**: Cryptographic commit signing with biometric or password authentication prevents malicious scripts from creating commits without your knowledge. This blocks multi-stage attacks that modify your codebase.
4. **Enforce repository rulesets**: Require pull requests and status checks for all changes to protected branches. This creates a checkpoint where automated security scans catch malicious code before it reaches your main branch.
4. **Enforce repository rulesets**: Require pull requests and status checks for all changes to protected branches. This creates a checkpoint where automated security scans catch malicious code before it reaches your default branch.
5. **Establish trusted publishing and verification**: Implement OIDC-based trusted publishing to eliminate long-lived tokens, and validate package attestations when consuming dependencies to verify they come from trusted sources.
6. **Monitor and respond continuously**: Automate vulnerability detection with Dependabot, dependency review, code scanning, and secret scanning. Tune alerts to reduce fatigue and establish response runbooks.

**Implementation checklist:**

- [ ] Configure package managers to disable lifecycle scripts by default
- [ ] Always use lockfiles for ecosystems that support them to ensure deterministic builds
- [ ] Use dev containers for development, especially for untrusted code
- [ ] Enable commit signing with user interaction (passphrase, biometric, or hardware key)
- [ ] Configure repository rulesets requiring pull requests and status checks
- [ ] Enable Dependabot alerts and security updates for all repositories
- [ ] Configure dependency review action as a required status check on pull requests
- [ ] Enable code scanning with CodeQL or third-party tools, blocking high-severity issues
- [ ] Enable secret scanning with push protection active
- [ ] Enable code scanning with CodeQL or third-party tools; block merges on high-severity findings using rulesets
- [ ] Enable secret scanning with push protection; verify that partner patterns are enabled and custom patterns are defined for organization-specific secrets
- [ ] Use trusted publishing with OIDC for any packages you maintain
- [ ] Publish packages with provenance attestations (e.g., npm publish --provenance)
- [ ] Verify package attestations for dependencies using npm audit signatures in CI/CD
Expand All @@ -103,9 +104,9 @@ Layered defenses reduce single points of failure by using multiple controls that

This guidance assumes:

- **Package ecosystem**: You're using npm or Yarn for JavaScript/TypeScript projects. Similar principles apply to other ecosystems (pip, Maven, NuGet), but specific configurations differ.
- **Package ecosystem**: This guidance uses npm or Yarn for JavaScript/TypeScript projects as examples. Similar principles apply to other ecosystems (pip, Maven, NuGet), but specific configurations differ.
- **GitHub repository**: You have administrative access to configure repository settings, rulesets, and GitHub Actions workflows.
- **GitHub Advanced Security**: For organizations using GitHub Enterprise Cloud, GitHub Advanced Security features (Dependabot, dependency review, code scanning) are available. Some features are also available on public repositories.
- **Dependabot and GitHub Advanced Security features**: This guidance uses Dependabot, dependency review, code scanning, and secret protection. Feature availability varies by GitHub plan.
- **Development environment**: You can configure your local development environment or are using GitHub Codespaces/dev containers.
- **CI/CD with GitHub Actions**: Your build and deployment pipelines use GitHub Actions. Adapt the workflow examples if using other CI/CD systems.

Expand All @@ -130,6 +131,10 @@ save-exact=true

The `ignore-scripts=true` setting tells npm to skip all lifecycle scripts during installation. The `save-exact=true` setting ensures npm saves exact versions (not version ranges) in your `package.json`, preventing unexpected updates to newer versions that might contain malicious code.

{{< callout type="info" >}}
**Project root vs. home directory**: Placing `.npmrc` in the project root (committed to the repository) ensures all contributors and CI systems use the same secure configuration. This provides consistent protection across the team without relying on individual developer setups.
{{< /callout >}}

The `.npmrc` approach is recommended because it applies automatically, so you can't forget to add the flag. For one-off commands or environments where you can't modify the config, pass `--ignore-scripts` directly:

```bash
Expand Down Expand Up @@ -199,9 +204,9 @@ Some legitimate packages require lifecycle scripts (such as `node-gyp` for nativ

### Layer 2: Use dev containers for isolation

Even with package scripts disabled, dev containers provide an additional security boundary. You can run dev containers locally with Docker, or use managed environments like [GitHub Codespaces](https://docs.github.com/enterprise-cloud@latest/codespaces) or [Microsoft Dev Box](https://azure.microsoft.com/products/dev-box) that provide container isolation without local setup.
Even with package scripts disabled, [dev containers](https://containers.dev) provide an additional security boundary. You can run dev containers locally with Docker, or use [GitHub Codespaces](https://docs.github.com/enterprise-cloud@latest/codespaces) for a fully managed dev container environment. Cloud PCs like [Windows 365](https://www.microsoft.com/en-us/windows-365) can also provide isolation from your primary machine, though they require manual Docker and dev container setup.

By isolating development in a container, malicious scripts can only access the container's ephemeral home directory, not your actual files, credentials, or SSH keys. This is exactly the kind of damage the Shai-Hulud attack attempted, destroying home directories when secrets couldn't be found.
By isolating development in a separate environment, malicious scripts can only access the environment's ephemeral home directory, not your actual files, credentials, or SSH keys. This is exactly the kind of damage the Shai-Hulud attack attempted, destroying home directories when secrets couldn't be found.

#### Dev container security benefits

Expand Down Expand Up @@ -229,7 +234,7 @@ One subtle risk in supply chain attacks is that malicious code might commit chan

#### Configure commit signing

Configure commit signing using a GPG, SSH, or S/MIME key. For maximum protection against automated attacks, use a signing method that requires user interaction. Examples include using a passphrase-protected key, biometric authentication, or a hardware security key.
[Configure commit signing using a GPG, SSH, or S/MIME key](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). For maximum protection against automated attacks, use a signing method that requires user interaction. Examples include using a passphrase-protected key, biometric authentication, or a hardware security key.

{{< callout type="info" >}}
**Why user interaction matters:** The key defense here isn't just the cryptographic signature - it's the human verification step. A malicious script running on your machine can access your signing key, but it can't press your fingerprint to the sensor or type your passphrase. This human-in-the-loop requirement is what blocks automated attacks from creating commits on your behalf.
Expand All @@ -241,15 +246,15 @@ GitHub provides [additional documentation on configuring commit signing](https:/

### Layer 4: Enforce repository rulesets

This defense layer happens at the repository level. Rulesets ensure that no code reaches your main branch without going through review and automated security checks.
This defense layer happens at the repository level. Rulesets ensure that no code reaches your default branch without going through review and automated security checks.

#### Configure rulesets

Create a ruleset for your main branch:
Create a ruleset for your default branch:

1. Navigate to **Settings** → **Rules** → **Rulesets**
2. Add a new ruleset targeting your default branch
3. Examples of protections to configure:
3. Examples of [protection rules](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets) to configure:
- Require pull requests before merging
- Require signed commits
- Require status checks to pass (including security scans)
Expand All @@ -265,22 +270,22 @@ Build trust across the supply chain by establishing cryptographic provenance for

#### For package publishers: Configure trusted publishing

[Trusted publishing](https://docs.npmjs.com/trusted-publishers) removes the need to manage long-lived API tokens in your build systems. Instead, your CI/CD pipeline uses OIDC to authenticate directly with package registries. This eliminates the risk of stolen tokens being used to publish malicious versions.
[Trusted publishing](https://docs.npmjs.com/trusted-publishers) removes the need to manage long-lived API tokens in your build systems. Instead, your CI/CD pipeline attests the build artifacts and uses OIDC to authenticate directly with package registries. This achieves [SLSA Build Level 3](https://slsa.dev/spec/v1.0/levels#build-l3) security and enables code-to-cloud traceability, and eliminates the risk of stolen tokens being used to publish malicious versions.

For packages you maintain:

1. Link your GitHub repository as a trusted publisher in your package registry settings (npm, PyPI, RubyGems, etc.)
2. Update your release workflow to use OIDC authentication instead of long-lived tokens
3. Publish with provenance attestations (e.g., `npm publish --provenance`)
4. This creates cryptographic proof that the package came from your specific repository at a specific commit
2. Update your release workflow to use [OIDC authentication](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments) instead of long-lived tokens
3. Publish with provenance attestations (e.g., `npm publish --provenance`) to create cryptographic proof on the specific commit of the source repository
4. Create [linked artifact storage records](https://docs.github.com/enterprise-cloud@latest/code-security/concepts/supply-chain-security/linked-artifacts) with the [`actions/attest`](https://github.com/actions/attest) action

#### For package consumers: Validate package attestations

[Package attestations](https://docs.npmjs.com/viewing-package-provenance) provide cryptographic proof of a package's provenance, verifying it was built from specific source code through a verified build process. When consuming packages, validate that they come from trusted publishers.

For packages you depend on:

1. Use `npm audit signatures` to verify packages were built through GitHub Actions and identify the source repository and commit
1. Use `npm audit signatures` to verify packages have valid attestations and signatures, identifying the source repository and commit
2. Integrate attestation validation into your CI/CD pipeline for continuous verification
3. Prioritize dependencies published with attestations in your dependency selection decisions

Expand All @@ -298,16 +303,20 @@ Build a comprehensive automated detection system that catches vulnerabilities at

**Dependency vulnerabilities:**

Enable [Dependabot](https://docs.github.com/enterprise-cloud@latest/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) to automatically detect vulnerabilities and create pull requests for updates. Consider grouping patch updates for expedited review, assigning security team reviewers, and scheduling daily scans. Use [auto-triage rules](https://docs.github.com/enterprise-cloud@latest/code-security/dependabot/dependabot-auto-triage-rules/about-dependabot-auto-triage-rules) to reduce alert fatigue by automatically dismissing low-risk alerts or alerts for dependencies that don't affect your usage. For comprehensive guidance on managing security alerts at scale, see [Prioritizing security alert remediation](../prioritizing-alerts/).
Enable [Dependabot security updates](https://docs.github.com/enterprise-cloud@latest/code-security/dependabot/dependabot-security-updates/about-dependabot-security-updates) to automatically detect vulnerabilities and create pull requests for updates. Consider grouping patch updates for expedited review, assigning security team reviewers, and scheduling daily scans. Use [auto-triage rules](https://docs.github.com/enterprise-cloud@latest/code-security/dependabot/dependabot-auto-triage-rules/about-dependabot-auto-triage-rules) to reduce alert fatigue by automatically dismissing low-risk alerts or alerts for dependencies that don't affect your usage. For comprehensive guidance on managing security alerts at scale, see [Prioritizing security alert remediation](../prioritizing-alerts/).

Add the [dependency review action](https://github.com/actions/dependency-review-action) to your pull request workflows and require it as a status check. Configure it to fail on high-severity vulnerabilities, block problematic licenses, and warn on low [OpenSSF Scorecard](https://securityscorecards.dev/) scores.
Add the [dependency review action](https://github.com/actions/dependency-review-action) to your pull request workflows and require it as a status check to prevent potential vulnerabilities from being introduced. Configure it to fail on high-severity vulnerabilities, block problematic licenses, and warn on low [OpenSSF Scorecard](https://securityscorecards.dev/) scores.

**Code vulnerabilities and secrets:**

Enable [code scanning](https://docs.github.com/enterprise-cloud@latest/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning) to detect vulnerabilities and coding errors in your source code. Configure CodeQL or third-party tools to run on pull requests and block merges when issues are found.

Enable [secret scanning](https://docs.github.com/enterprise-cloud@latest/code-security/secret-scanning/introduction/about-secret-scanning) to detect accidentally committed credentials. Configure push protection to prevent secrets from being pushed in the first place, and establish a response runbook for when alerts are triggered.

**Prioritize alerts with production context:**

If you track your builds as [linked artifacts](https://docs.github.com/enterprise-cloud@latest/code-security/concepts/supply-chain-security/linked-artifacts) with deployment records, you can filter Dependabot and code scanning alerts based on what's actually deployed in production. Use filters like `has:deployment` and `runtime-risk` alongside EPSS and CVSS scores to [focus remediation on vulnerabilities that pose real risk](https://docs.github.com/enterprise-cloud@latest/code-security/tutorials/secure-your-organization/prioritize-alerts-in-production-code) to your running systems.

#### Review dependency updates systematically

Beyond automated tooling, establish a human review process for evaluating dependency changes:
Expand Down Expand Up @@ -370,6 +379,7 @@ Specifically, you may find the following links helpful:
- [About rulesets](https://docs.github.com/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets)
- [npm trusted publishers](https://docs.npmjs.com/trusted-publishers)
- [Verifying npm package provenance](https://docs.npmjs.com/viewing-package-provenance)
- [Using artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations)
- [About supply chain security](https://docs.github.com/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security)
- [Our plan for a more secure npm supply chain](https://github.blog/security/supply-chain-security/our-plan-for-a-more-secure-npm-supply-chain/) - GitHub's response to the Shai-Hulud attack
- [The second half of software supply chain security on GitHub](https://github.blog/security/supply-chain-security/the-second-half-of-software-supply-chain-security-on-github/) - Build provenance and artifact attestations
Expand Down
Loading