Platform EngineeringInfrastructurePythonAgentic AISecurityRust

Supply Chain Security: The Seven-Day Delay That Protects Your Production Systems

How I protect 15+ projects across Python, JavaScript, and Rust from supply chain attacks using a three-layer defence: registry-level delays, automated PR scheduling, and lockfile discipline.

1 April 2026 · 14 min read

Most supply chain attacks on PyPI, npm, and crates.io are detected within 24-72 hours. If you can delay accepting new package versions by seven days, you’ve eliminated the vast majority of the attack surface. Here’s how I enforce that across 15+ projects spanning three ecosystems.

This Isn’t Theoretical

In March 2026, two high-profile supply chain attacks hit within days of each other. LiteLLM (PyPI) — A malicious payload was injected into LiteLLM version 1.82.8, a package with approximately 3 million daily downloads, where attacker payload could extract SSL/SSH keys, and Axios (npm)suffered a compromise when attackers obtained a long-lived npm access token for a maintainer, and introduced a fake dependency whose post-install script delivered platform-specific remote access trojans.

The Agentic AI Angle — What makes these attacks particularly dangerous in 2026 is the rise of agentic AI development. When Devin (Cognition’s AI coding agent) encountered the Axios attack, it flagged the Axios anomaly flagged within 45 minutes. That’s impressive for detection — but in an agentic workflow where AI agents autonomously install dependencies, run code, and deploy to infrastructure, 45 minutes might be 44 minutes too late. An AI agent that runs pip install litellm as part of an automated pipeline doesn’t pause to notice CPU spikes. It installs, executes, and moves on — potentially exfiltrating credentials to your cloud infrastructure before any human is aware. This is why gate checks in agentic AI development aren’t optional. If your agents can install packages, they need the same supply chain protections as your CI pipeline — or stronger.

In both examples above and potentially others introducing a seven-day delay would have prevented both attacks entirely. The compromised versions would never have been resolved by any system in my infrastructure. This number seven can be anything that your organization is ready to enforce, but it needs to be long enough to outlast the typical detection window for supply chain attacks.

The Problem

Modern platform engineering means managing dependencies across multiple languages and package registries. A single project might pull in hundreds of transitive dependencies from PyPI, npm, and crates.io — each one a potential vector for supply chain compromise.

The attacks above aren’t anomalies — they’re the new normal. Typosquatting, maintainer account takeover, malicious post-install scripts, dependency confusion. What they all have in common is a time window — the malicious package version exists on the registry for hours or days before it’s detected and yanked.

The question isn’t whether your dependencies will be targeted. It’s whether you’ll resolve the malicious version before the community catches it. The blog below shares one way of addressing it and there could be many other approaches. The key is to have a deliberate strategy that fits your team’s workflow and risk tolerance.

The Architecture: Three Independent Layers

My approach uses defence in depth — three independent blocking layers, any one of which prevents a compromised package from reaching production.

Layer 1: Registry-Level Delay (Automatic Rejection)

The first layer rejects any package version published less than seven days ago, at the package manager level. No CI pipeline, no human review required — the tool itself refuses to resolve young packages.

Python (UV)

UV has built-in quarantine support via UV_EXCLUDE_NEWER. I set it dynamically in ~/.bashrc:

export UV_EXCLUDE_NEWER=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)

This computes a timestamp seven days in the past every time a shell starts. Every uv lock, uv sync, and uv pip install respects this cutoff. It covers all nine Python projects in the ecosystem without touching any project configuration.

The same variable is set in CI:

- name: Set UV_EXCLUDE_NEWER
  run: echo "UV_EXCLUDE_NEWER=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV

And the deployment script refuses to run if it’s not set:

if [ -z "$UV_EXCLUDE_NEWER" ]; then
    echo "ERROR: UV_EXCLUDE_NEWER is not set."
    echo "This is required for supply chain security."
    exit 1
fi

JavaScript (pnpm)

pnpm supports minimumReleaseAge in .npmrc:

# Reject packages published less than 7 days ago
minimum-release-age=10080

# Don't run install scripts by default
ignore-scripts=true

10,080 minutes is exactly seven days. The ignore-scripts=true is equally important — post-install scripts are the primary attack vector for npm packages. I whitelist only the specific packages that genuinely need build scripts:

onlyBuiltDependencies[]= esbuild

Rust (Cargo)

Cargo has no native delay support. The strategy shifts to preventing accidental updates:

  1. Committed Cargo.lockcargo build never resolves new versions
  2. Shell aliases block cargo update — you have to explicitly use the force variant
alias cargo-update='echo "Use Renovate PRs for dep updates." && false'
alias cargo-update-force='command cargo update'

Layer 2: Automated PR Delay (Renovate)

Renovate creates pull requests for dependency updates on a schedule, with its own delay enforcement:

{
  "schedule": ["every weekend"],
  "minimumReleaseAge": "7 days",
  "packageRules": [
    {
      "matchUpdateTypes": ["major"],
      "minimumReleaseAge": "14 days",
      "automerge": false
    },
    {
      "matchUpdateTypes": ["pin", "digest"],
      "minimumReleaseAge": "0 days"
    }
  ],
  "vulnerabilityAlerts": {
    "minimumReleaseAge": "0 days",
    "labels": ["security"]
  }
}

The key design decisions:

  • Seven days for minor/patch — consistent with registry-level delay
  • Fourteen days for majors — breaking changes need extra soak time
  • Zero days for pins and digests — hash-pinned updates are safe by definition
  • Zero days for vulnerability alerts — security fixes bypass all delays

All projects extend a single shared preset. Adding a new project takes one line:

{ "extends": ["local>jagatsingh/tradingutils"] }

Layer 3: Lockfile Discipline

The final layer ensures that even if a malicious version passes the first two layers, it can’t silently enter the build:

Frozen installs everywhere:

# CI
uv sync --frozen
pnpm install --frozen-lockfile

# Docker
uv sync --frozen --no-dev

If the lockfile doesn’t match pyproject.toml or package.json, the build fails immediately. No silent resolution of new packages.

Docker base images pinned by SHA256 digest:

FROM python:3.12-bookworm@sha256:b7382e8b26dd8c9f765a373d728a326c6f463433f65e3c30b0e386b7fd2da024

Mutable tags like python:3.12-slim can change without notice. Digests are immutable content hashes — the only guarantee of reproducible builds. Renovate proposes digest updates through its normal PR workflow with the seven-day delay.

Security Vulnerabilities Bypass Everything

The entire delay system has one exception: actual security vulnerabilities. Multiple tools provide immediate detection:

ToolEcosystemWhen
Renovate vulnerability alertsAllReal-time (zero-day delay)
uv-securePythonEvery commit (pre-commit hook)
cargo-auditRustEvery commit (pre-commit hook)
pnpm auditJavaScriptCI pipeline
GitleaksSecretsEvery commit (pre-commit hook)

If a CVE is published against one of your dependencies, Renovate creates a PR immediately — no seven-day wait. The pre-commit hooks catch known vulnerabilities even if Renovate hasn’t run yet.

Pre-Commit Hooks: The Last Line of Defence

Every project runs security checks on commit:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: check-yaml
      - id: check-added-large-files
      - id: detect-private-key

  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.27.2
    hooks:
      - id: gitleaks

Python projects add uv-secure for PyPI vulnerability scanning. Rust projects add cargo-audit. These run locally before code reaches the remote — catching issues before they enter the commit history.

Developer Workflow

Shell aliases prevent accidental bypasses:

alias cargo-update='echo "Use Renovate PRs." && false'
alias npm-update='echo "Use Renovate PRs." && false'
alias pnpm-update='echo "Use Renovate PRs." && false'
alias pre-commit-autoupdate='echo "Use Renovate PRs." && false'

All dependency updates flow through Renovate PRs. This creates an audit trail — every version change is a reviewed, mergeable pull request with a changelog diff.

Emergency bypasses exist but require explicit intent:

UV_EXCLUDE_NEWER="" uv lock          # Python emergency
cargo-update-force                    # Rust emergency

Why Seven Days?

The number isn’t arbitrary:

  1. Detection window — most supply chain attacks are caught within 24-72 hours by security researchers, registry maintainers, and automated scanners
  2. Safety margin — seven days is 2-3x beyond the typical detection window
  3. Practical balance — long enough to be safe, short enough that dependencies don’t go stale
  4. Ecosystem consistency — the same delay across Python, JavaScript, and Rust simplifies the mental model

Verification

Quick commands to verify the setup is working:

# Check UV delay is active
echo $UV_EXCLUDE_NEWER
# → 2026-03-25T14:22:15Z (7 days ago)

# Check pnpm delay
grep minimumReleaseAge .npmrc
# → 10080

# Verify all Docker images are pinned
grep -r "^FROM " ~/dev/code/*/Dockerfile | grep -v "@sha256:"
# → Empty (all pinned)

# Verify Renovate config extends shared preset
cat renovate.json5
# → "extends": ["local>jagatsingh/tradingutils"]

What This Costs

The seven-day delay means you’re never on the bleeding edge of dependency releases. In practice, I’ve never noticed — the delay is invisible during normal development. The only time it matters is when you need a specific new feature from a package that was just released, and even then, the emergency bypass takes one command.

The pre-commit hooks add a few seconds to each commit. Renovate PRs arrive on weekends and auto-merge if CI passes. The ongoing maintenance cost is effectively zero.

What you get in return: the confidence that a supply chain attack on any of the three major registries won’t compromise your production systems. For platform engineering work where you’re running infrastructure that other teams depend on, that confidence is worth the seven-day wait.