Note
This page is part of the Windlass organization security policy. Start with SECURITY.md.
All projects must commit dependency lockfiles to version control to ensure reproducible builds and enable security auditing.
Dependency security uses layered controls so each tool covers a different point in the lifecycle:
| Control | Timing | Scope | Primary outcome |
|---|---|---|---|
| Lockfiles | During development and build | Resolved dependency graph | Reproducible builds and reviewable diffs |
| Dependabot | Continuous and scheduled | Existing dependency versions | Update PRs for vulnerable or stale packages |
| Cooldowns | Before version update PRs | Newly published versions | Time for malicious releases to be detected |
| Dependency Review | Pull request | Newly changed dependencies | Blocks vulnerable or non-compliant additions |
| OSV Scanner | Pull request and schedule | Full dependency tree | Finds known vulnerabilities across projects |
| Ecosystem | Lockfile | Commit Required |
|---|---|---|
| Rust/Cargo | Cargo.lock |
✅ Yes |
| Node.js/npm | package-lock.json |
✅ Yes |
| Node.js/Yarn | yarn.lock |
✅ Yes |
| Node.js/pnpm | pnpm-lock.yaml |
✅ Yes |
| Bun | bun.lock |
✅ Yes |
| Python/pip | requirements.txt (pinned) or poetry.lock |
✅ Yes |
| Go | go.sum |
✅ Yes |
| Java/Maven | pom.xml (with versions) |
✅ Yes |
| Java/Gradle | gradle.lockfile |
✅ Yes |
| Ruby | Gemfile.lock |
✅ Yes |
- Use Dependabot or Renovate to automatically update dependencies
- Security updates should be applied immediately
- Major version updates require review and testing
Cooldown periods delay version update PRs to allow community vetting of new releases. This mitigates supply chain attacks by providing time for malicious packages to be detected and yanked before adoption.
Note
Security updates bypass cooldowns and are created immediately. Cooldowns apply only to version updates.
| Ecosystem | Patch | Minor | Major | Rationale |
|---|---|---|---|---|
| GitHub Actions | N/A | N/A | N/A | Use default-days only; SemVer cooldown not supported. SHA-pinning mitigates risk. |
| NPM/Bun (Production) | 3-7 days | 7-14 days | 21-30 days | Balance security with npm min-release-age; align cooldown with install restrictions |
| NPM/Bun (Development) | 1-3 days | 3-7 days | 14-21 days | Lower risk tolerance for dev dependencies |
| Rust/Cargo | 3-7 days | 14-21 days | 30-60 days | Stricter SemVer compliance allows longer vetting; Cargo.lock provides safety net |
| Python/pip | 7-14 days | 14-21 days | 21-30 days | PyPI has slower yank response; conservative approach recommended |
| Go | 3-7 days | 7-14 days | 14-21 days | Go modules are immutable; supply chain attacks less common but possible |
Important
SemVer-based cooldown (semver-major-days, semver-minor-days, semver-patch-days) is only supported by package ecosystems that use Semantic Versioning. Ecosystems like GitHub Actions, Docker, Terraform, and Bazel only support default-days.
| Support | Ecosystems |
|---|---|
| SemVer + default-days | bundler, bun, cargo, composer, dotnet_sdk, elm, gomod, gradle, hex, julia, maven, npm, nuget, opentofu, pip, pub, swift, uv |
| default-days only | bazel, devcontainers, docker, docker_compose, github-actions, gitsubmodule, helm, terraform |
Why cooldowns matter:
- Most malicious packages are detected within 24-72 hours of publication
- 7-day cooldown provides comfortable safety margin for community detection
- Organizations with cooldowns were not exposed during major incidents (e.g., Nx, xz utils backdoor)
SemVer-specific delays:
- Patch (z in x.y.z): Bug fixes only; shortest cooldown acceptable
- Minor (y in x.y.z): New features; moderate cooldown for API stability verification
- Major (x in x.y.z): Breaking changes; longest cooldown for migration planning and community feedback
The Rust ecosystem has unique characteristics affecting cooldown strategy:
-
Strict SemVer Compliance: Cargo enforces SemVer strictly. Minor updates are generally safer than in npm, but the community tends toward conservatism.
-
Cargo Yank Mechanism: Yanked crates remain available for existing
Cargo.lockbut prevent new adoption. Cooldowns work synergistically—malicious versions can be yanked before PRs are created. -
Recent Attack Patterns (March 2026): Five malicious crates (
chrono_anchor,dnp3times, etc.) targeted CI/CD environments to exfiltrate.envfiles. Cooldowns provide critical detection window. -
Cargo.lock Pinning: Unlike npm, Cargo.lock should always be committed. This creates natural review gates that complement cooldowns.
# dependabot.yml - Production-grade Rust project
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
day: "tuesday"
cooldown:
default-days: 7
semver-major-days: 30
semver-minor-days: 14
semver-patch-days: 5
groups:
patch-updates:
patterns: ["*"]
update-types: ["patch"]
reviewers:
- "windlass-tech/security-team"Combine cooldowns with:
cargo-audit: Scan for known vulnerabilities inCargo.lockcargo-vet: Establish trust relationships for dependencies- Dependency review workflows: Require human review for new dependencies
- Lockfile verification: Always review
Cargo.lock/package-lock.jsondiffs in PRs
The Dependency Review Action scans pull requests for vulnerable or non-compliant dependencies before they enter the codebase. It performs diff-based analysis comparing dependencies in the PR head against the base, identifying changes that introduce known vulnerabilities or license violations.
Note
Dependency Review is available for all public repositories. For private repositories, GitHub Advanced Security is required.
All repositories must implement Dependency Review with this hardened configuration:
name: "Dependency Review"
permissions:
contents: read
on:
pull_request:
paths-ignore:
- "**.md"
- "docs/**"
merge_group:
paths-ignore:
- "**.md"
- "docs/**"
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: "Dependency Review"
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
config-file: "windlasstech/.github/.github/dependency-review-config.yml@main"| Option | Type | Description | Recommended |
|---|---|---|---|
fail-on-severity |
string | Minimum severity to block PR: low, moderate, high, critical |
moderate |
fail-on-scopes |
string | Scopes to check: runtime, development, unknown |
development, runtime |
allow-licenses |
string | Comma-separated allowed SPDX license IDs | See license policy below |
deny-licenses |
string | Comma-separated forbidden SPDX license IDs | Mutually exclusive with allow-licenses |
allow-ghsas |
string | GitHub Advisory IDs to temporarily allow | Document with justification |
comment-summary-in-pr |
string | PR comment mode: always, on-failure, never |
on-failure |
warn-only |
boolean | Log warnings without failing | Use for phased rollout only |
Use an allow-list approach for license compliance. Reference a centralized configuration file:
# In repository workflow
- name: "Dependency Review"
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
config-file: "windlasstech/.github/.github/dependency-review-config.yml@main"Standard allow-licenses list:
allow-licenses:
# Permissive licenses with attribution requirements
- MIT # MIT License - permissive, requires attribution
- Apache-2.0 # Apache License 2.0 - permissive, patent protection
- BSD-2-Clause # BSD 2-Clause "Simplified" - permissive
- BSD-3-Clause # BSD 3-Clause "New" - permissive, no endorsement clause
- ISC # ISC License - permissive, functionally equivalent to MIT
- 0BSD # Zero-Clause BSD - permissive, no attribution required
# Public domain dedications (no attribution required)
- CC0-1.0 # Creative Commons Zero - public domain dedication
- Unlicense # The Unlicense - public domain dedication (not "unlicensed")
# Language-specific permissive licenses
- Python-2.0 # Python Software Foundation License - permissiveNote
License identifiers must be SPDX-compliant. For licenses detected as OTHER, use LicenseRef-clearlydefined-OTHER in allow/deny lists.
| Use Case | Required Permissions |
|---|---|
| Basic vulnerability scan | contents: read |
| PR comments enabled | contents: read, pull-requests: write |
| External config file | contents: read + external-repo-token |
| Level | Description | Recommended Action |
|---|---|---|
| Critical | Active exploitation or severe impact | Always fail |
| High | Significant security impact | Fail in production |
| Moderate | Limited impact | Consider failing |
| Low | Minimal security risk | Warn only |
Enforce Dependency Review across the organization using repository rulesets:
- Navigate to Organization Settings > Rules > Rulesets
- Create ruleset targeting all repositories
- Add requirement: Require status checks to pass
- Search for and select the
dependency-reviewworkflow
This ensures all PRs must pass dependency review before merging, regardless of individual repository configuration.
| Pitfall | Solution |
|---|---|
Missing pull-requests: write with comment-summary-in-pr |
Add permission to workflow job |
| Internal packages fail license check | Add to allow-dependencies-licenses with purl format |
Both allow-licenses and deny-licenses set |
Choose one approach; they are mutually exclusive |
| External config in private repo fails | Provide external-repo-token with read access |
| Action fails before dependency submission completes | Use retry-on-snapshot-warnings: true |
The OSV Scanner workflow detects known vulnerabilities in project dependencies using the Open Source Vulnerabilities database. It complements Dependency Review by scanning the entire dependency tree, not just changed dependencies.
upload-sarif: true— upload results to the repository's code scanning dashboardfail-on-vuln: true— fail the workflow when vulnerabilities are foundscan-argsdefaulting to--recursive ./for automatic lockfile detection
Repository-specific overrides are supported:
- Set
upload-sarif: falsewhen SARIF upload should not be used for that repository - Override
scan-argsfor explicit lockfiles, generated dependency artifacts, or excluded paths - Use
fail-on-vuln: falseonly for short initial calibration, then restore the standard default oftrue
name: OSV Scanner PR
on:
pull_request:
merge_group:
permissions:
contents: read
jobs:
osv:
permissions:
actions: read
contents: read
security-events: write
uses: windlasstech/.github/.github/workflows/osv-scanner-pr-reusable.yml@<pin-sha>- Dependency Review Action Repository
- GitHub Dependency Review Documentation
- SPDX License List
- GitHub Advisory Database