# .github/workflows/publish.yml
permissions: contents: read id-token: write # Required for OIDC provenance jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: -weight: 500;">npm ci - run: -weight: 500;">npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # Use OIDC, not this secret
# .github/workflows/publish.yml
permissions: contents: read id-token: write # Required for OIDC provenance jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: -weight: 500;">npm ci - run: -weight: 500;">npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # Use OIDC, not this secret
# .github/workflows/publish.yml
permissions: contents: read id-token: write # Required for OIDC provenance jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - run: -weight: 500;">npm ci - run: -weight: 500;">npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # Use OIDC, not this secret
-weight: 500;">npm audit signatures
-weight: 500;">npm audit signatures
-weight: 500;">npm audit signatures
-weight: 500;">npm ci --ignore-scripts
-weight: 500;">npm ci --ignore-scripts
-weight: 500;">npm ci --ignore-scripts
# Vulnerable: uses a mutable tag
- uses: actions/checkout@v4 # Hardened: pinned to specific commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Vulnerable: uses a mutable tag
- uses: actions/checkout@v4 # Hardened: pinned to specific commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Vulnerable: uses a mutable tag
- uses: actions/checkout@v4 # Hardened: pinned to specific commit SHA
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# .github/dependabot.yml
version: 2
updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"
# .github/dependabot.yml
version: 2
updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"
# .github/dependabot.yml
version: 2
updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"
# At the workflow level, deny all permissions by default
permissions: {} jobs: publish: permissions: contents: read id-token: write # Only what this job actually needs
# At the workflow level, deny all permissions by default
permissions: {} jobs: publish: permissions: contents: read id-token: write # Only what this job actually needs
# At the workflow level, deny all permissions by default
permissions: {} jobs: publish: permissions: contents: read id-token: write # Only what this job actually needs
# Check provenance attestation for a specific package
-weight: 500;">npm audit signatures --json | jq '.[] | select(.name == "axios")' # View attestation metadata directly
-weight: 500;">npm view [email protected] --json | jq '.dist.attestations'
# For the malicious versions: this would return null or an object missing the SLSA predicate
# Check provenance attestation for a specific package
-weight: 500;">npm audit signatures --json | jq '.[] | select(.name == "axios")' # View attestation metadata directly
-weight: 500;">npm view [email protected] --json | jq '.dist.attestations'
# For the malicious versions: this would return null or an object missing the SLSA predicate
# Check provenance attestation for a specific package
-weight: 500;">npm audit signatures --json | jq '.[] | select(.name == "axios")' # View attestation metadata directly
-weight: 500;">npm view [email protected] --json | jq '.dist.attestations'
# For the malicious versions: this would return null or an object missing the SLSA predicate
# List all active -weight: 500;">npm tokens
-weight: 500;">npm token list # Revoke specific tokens
-weight: 500;">npm token revoke <token-id> # Check for NPM_TOKEN in GitHub Actions secrets
# Go to: github.com/{org}/{repo}/settings/secrets/actions
# List all active -weight: 500;">npm tokens
-weight: 500;">npm token list # Revoke specific tokens
-weight: 500;">npm token revoke <token-id> # Check for NPM_TOKEN in GitHub Actions secrets
# Go to: github.com/{org}/{repo}/settings/secrets/actions
# List all active -weight: 500;">npm tokens
-weight: 500;">npm token list # Revoke specific tokens
-weight: 500;">npm token revoke <token-id> # Check for NPM_TOKEN in GitHub Actions secrets
# Go to: github.com/{org}/{repo}/settings/secrets/actions - SLSA 0: No guarantees. Most -weight: 500;">npm packages today.
- SLSA 1: Build process documented and scripted. Provenance exists but not authenticated.
- SLSA 2: Build on hosted CI with authenticated provenance. -weight: 500;">npm registry supports this natively. This is where you need to be.
- SLSA 3: Build platform itself is hardened. Achievable but requires deliberate effort. - Provenance verification: Does this package version have a valid SLSA attestation from a trusted CI environment?
- Behavioral analysis: Are there new network calls, filesystem access patterns, or postinstall scripts compared to the previous version?
- Dependency graph diffing: What changed in the full transitive dependency tree between your current and proposed lockfile? - Verify OIDC Trusted Publishing is configured in npmjs.com package settings for all packages you publish
- Remove NPM_TOKEN GitHub Actions secrets from all repositories
- Rotate any tokens that were used in CI in the last 90 days
- Enable -weight: 500;">npm 2FA enforcement at the organization level (npmjs.com/org/{org}/settings)
- Review the list of users with publish access to each package