agentleFS
carlkiblercarlkibler/agent-skills/skills/dependency-pinning/SKILL.md

dependency-pinning

Skill6 starsChanged 4 months agoMITView on GitHub

Audit Docker, CI, and language dependencies for SHA/digest pinning and cooldowns.

  • Installs packages

The file

Show its rules and text

We split this skill into the rules it gives an agent

14 rules. About 0 are common, 8 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈8

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Ask what to scan if unclear: a single project, a list of repos, or "everything under a directory the user names".

    line 29
  2. Distinctive:

    Cooldown: no native support — enforce by choosing a digest for an old-enough tag.

    line 65
  3. Very specific:

    Bad: no lockfile committed; relying on ^/~/latest without a lock.

    line 79
  4. Very specific:

    Cooldown: no native NuGet cooldown — use Renovate minimumReleaseAge (NuGet supported).

    line 108
  5. Very specific:

    Rust/Cargo: commit Cargo.lock (carries crates.io sha256 checksums).

    line 112
  6. Very specific:

    No native cooldown → Renovate minimumReleaseAge (cargo datasource).

    line 113
  7. Very specific:

    Ruby/Bundler: commit Gemfile.lock; bundle config set frozen true.

    line 120
  8. Distinctive:

    Don't stop at the languages listed here.

    line 126
  9. Distinctive:

    Default to read-only. Do not edit files, run installers, or commit until the user approves.

    line 170
  10. Distinctive:

    When approved, change one ecosystem at a time.

    line 171
  11. Distinctive:

    Every pin bump names the new SHA/digest and reminds the user to do a supply-chain check and respect the cooldown (no artifact newer than N …

    line 172
  12. Very specific:

    Never weaken an existing pin.

    line 174
  13. Distinctive:

    Keep the human-readable version in a trailing comment beside every SHA pin.

    line 175
  14. Distinctive:

    Commit lockfiles — never gitignore them.

    line 176

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Dependency Pinning

A dependency safety scan — not a CVE scan. It checks two things across every ecosystem in scope:

  1. Pinning — dependencies pinned to an immutable content SHA / digest, not a movable tag. Tags can be silently re-pointed to altered or malicious artifacts (the tj-actions class of supply-chain attacks). A digest can't.
  2. Cooldown — a project never adopts an artifact newer than N days (default 7). A fresh release is where a compromise lands first; the cooldown buys time for it to be caught before it reaches the build.

Report first. Mutate only on explicit approval. Bumping a pin is itself risky, so every proposed change names the new SHA and flags it for a supply-chain check.

Scope

Ask what to scan if unclear: a single project, a list of repos, or "everything under a directory the user names". Don't scan outside what the user named. Then discover dependency manifests (this prunes bin/obj/packages directories, so say if a .NET or other repo keeps manifests there):

ROOT="${1:-.}"
/usr/bin/find "$ROOT" -type d \( -name node_modules -o -name .git -o -name .terraform \
    -o -name vendor -o -name packages -o -name bin -o -name obj -o -path '*/assets/libs' \) -prune -o \
  -type f \( \
    -iname 'Dockerfile*' -o -name 'docker-compose*.y*ml' -o -name 'compose*.y*ml' \
    -o -path '*/.github/workflows/*.y*ml' \
    -o -name 'package.json' -o -name 'deno.json*' \
    -o -name 'pyproject.toml' -o -name 'requirements*.txt' \
    -o -name '*.csproj' -o -name 'packages.config' -o -name 'Directory.Packages.props' \
    -o -name 'Cargo.toml' -o -name 'go.mod' \
    -o -name 'pom.xml' -o -name 'build.gradle' -o -name 'build.gradle.kts' \
    -o -name 'Gemfile' -o -name '*.gemspec' \
    -o -name '*.tf' \
  \) -print 2>/dev/null
# Also catch images pulled from scripts/CI, not just Dockerfiles/compose:
grep -rEn 'docker (run|pull|build)[^|]*[a-z0-9./-]+:[a-z0-9._-]+' "$ROOT" \
  --include='*.sh' --include='*.y*ml' --include='Makefile' 2>/dev/null

Ignore vendored/minified asset trees (*/assets/**, *.min.*) — those package.json files are bundled libraries, not your declared dependencies.

What "good" looks like, how to detect violations, how to fix

Docker / Compose (pinning: required · cooldown: manual)

  • Good: FROM repo/img@sha256:<digest> · image: repo/img@sha256:<digest>
  • Bad: :latest, floating tags, no digest.
  • Detect: grep -rEn '^\s*FROM |image:\s' <files> → flag any ref without @sha256:. Also scan scripts/CI/Makefiles for docker run|pull|build ... img:tag — images pulled outside Dockerfiles/compose are easy to miss and just as swappable.
  • Fix: resolve the digest of a tag that is ≥ N days old: docker buildx imagetools inspect repo/img:<ver> --format '{{.Manifest.Digest}}' then write repo/img@sha256:<digest> # <ver>.
  • Cooldown: no native support — enforce by choosing a digest for an old-enough tag.

GitHub Actions (pinning: required · cooldown: via tooling)

  • Good: uses: owner/repo@<40-hex-sha> # vX.Y.Z
  • Bad: @v4, @main, @<branch>.
  • Detect: grep -rEn 'uses:\s' .github/workflows → flag any ref not matching a 40-char hex SHA.
  • Fix: git ls-remote https://github.com/<owner>/<repo> refs/tags/<ver> → SHA; pin with the version in a trailing comment.
  • Cooldown / automation: ratchet (sethvargo/ratchet) or pinact pin+update actions by SHA; Renovate/Dependabot minimumReleaseAge / cooldown gates the version that gets pinned.

JavaScript — npm / yarn / pnpm / bun / deno (pinning: lockfile · cooldown: tooling)

  • Good: lockfile committed (package-lock.json / yarn.lock / pnpm-lock.yaml / bun.lock / deno.lock) with integrity hashes, and CI installs frozen (npm ci, pnpm i --frozen-lockfile, yarn --immutable, deno install --frozen).
  • Bad: no lockfile committed; relying on ^/~/latest without a lock; npm install in CI.
  • Detect: manifest present but lockfile missing/gitignored; CI using non-frozen installs.
  • Fix: generate + commit the lockfile; switch CI to the frozen install command.
  • Cooldown: pnpm has native minimumReleaseAge (config/.npmrc). For npm/yarn/bun, add Renovate minimumReleaseAge: "7 days" (or Dependabot cooldown). Deno: pin exact versions in import map / deno.json; deno.lock carries integrity.

Python — uv / pdm / poetry / pip (pinning: lockfile+hashes · cooldown: uv native / tooling)

  • Good: lockfile committed with hashes (uv.lock, pdm.lock, poetry.lock) and installed locked (uv sync --locked, pdm sync, poetry install); for bare pip, hashed pins via pip-compile --generate-hashes + pip install --require-hashes.
  • Bad: unpinned/>=/* requirements, no lockfile, no hashes.
  • Detect: pyproject.toml/requirements.txt present but no lockfile/hashes; ranges without a lock.
  • Fix: adopt a lock workflow or generate hashed requirements; commit the lock.
  • Cooldown: uv has native exclude-newer / UV_EXCLUDE_NEWER (resolve as of a date — a true cooldown). Otherwise Renovate minimumReleaseAge.

.NET — NuGet (pinning: exact versions + lockfile · cooldown: tooling)

  • Good: exact versions (packages.config version="x.y.z", or <PackageReference Version="x.y.z"/> with no range/*) plus a committed packages.lock.json (enable <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>, restore with dotnet restore --locked-mode). The lockfile carries contentHash per package.
  • Bad: floating versions (Version="*", Version="[1.0,)"), no lockfile, packages restored from a non-pinned feed.
  • Detect: grep -rEn 'Version="\*"|Version="\[' **/*.csproj; .csproj/packages.config present but no packages.lock.json beside it; check Directory.Packages.props for central versions if used.
  • Fix: pin exact versions; enable + commit packages.lock.json; pin the feed in nuget.config.
  • Cooldown: no native NuGet cooldown — use Renovate minimumReleaseAge (NuGet supported).

Rust — Cargo · Go · Java (Maven/Gradle) · Ruby (Bundler)

Terser, but the same two questions apply (immutable pin? cooldown?):

  • Rust/Cargo: commit Cargo.lock (carries crates.io sha256 checksums); cargo --locked. No native cooldown → Renovate minimumReleaseAge (cargo datasource). cargo audit/cargo vet adjacent.
  • Go modules: go.mod + go.sum (cryptographic hashes, verified vs the checksum DB); go mod verify, build with -mod=readonly. No native cooldown → Renovate (gomod).
  • Java/Maven: pin exact versions in pom.xml — ban LATEST/RELEASE/version ranges (enforce with maven-enforcer requireReleaseDeps / ban-dynamic-versions). No lockfile natively.
  • Java/Gradle: enable dependency locking (gradle.lockfile) + verification metadata (gradle --write-verification-metadata sha256 → verification-metadata.xml with checksums/signatures).
  • Ruby/Bundler: commit Gemfile.lock; bundle config set frozen true; add checksums with bundle lock --add-checksums (Bundler 2.5+). No native cooldown → Renovate (bundler).
  • Cooldown across all of the above is best delivered by Renovate minimumReleaseAge / Dependabot cooldown, which support these datasources.

Any other ecosystem you encounter (general rule)

Don't stop at the languages listed here. For any package manager in the target:

  1. Identify the manifest + its lockfile; confirm the lockfile carries integrity hashes and is committed, and that CI installs frozen/locked.
  2. Flag floating/range/latest version specifiers in the manifest.
  3. Research whether that manager supports a cooldown — native settings first (names vary: minimumReleaseAge, exclude-newer, --as-of, min-age, quarantine), then a meta-tool (Renovate minimumReleaseAge / Dependabot cooldown) for that datasource. If unsure, check the tool's current docs rather than guessing.
  4. Report findings the same way and propose the concrete setting to add.

Terraform (pinning: lock + version constraints)

  • Good: .terraform.lock.hcl committed with multi-platform hashes (terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 ...); providers pinned in required_providers; modules pinned by version or git commit SHA; AMIs/data sources pinned by ID (no most_recent = true).
  • Bad: lock gitignored, missing platforms, most_recent = true, modules on main.
  • Detect: lock in .gitignore; grep -rn 'most_recent\s*=\s*true' *.tf; module sources on a branch.
  • Fix: un-ignore + commit the lock (multi-platform); pin module/AMI refs.

Adding cooldowns where supported (offer these)

  • Renovate (cross-ecosystem): "minimumReleaseAge": "7 days" in renovate.json — the single best lever; gates npm/pip/docker/actions/etc. behind the cooldown.
  • Dependabot: cooldown: block in .github/dependabot.yml.
  • pnpm: minimumReleaseAge · uv: exclude-newer · GH Actions: ratchet/pinact.

Report format

DEPENDENCY SAFETY REPORT  (cooldown target: 7 days)
===================================================
<repo/path>
  Docker        : 2 images unpinned (FROM node:20, postgres:16) — PIN MISSING
  GH Actions    : 3 actions on tags (@v4) — PIN MISSING
  JS (pnpm)     : lockfile committed ✓ · no cooldown — COOLDOWN MISSING
  Python (uv)   : uv.lock ✓ · exclude-newer not set — COOLDOWN MISSING
  Terraform     : .terraform.lock.hcl gitignored — LOCK NOT COMMITTED
  ...
SUMMARY: 4 pin gaps, 3 cooldown gaps, 1 uncommitted lock across N repos.

Classify each as: OK · PIN MISSING · COOLDOWN MISSING · LOCK NOT COMMITTED. Then list exact fixes (the resolved SHA/digest, the config snippet) per item, ordered by risk: unpinned CI actions and images first, cooldown settings last. Stop after the report and ask which ecosystems to change.

Action rules

  • Default to read-only. Do not edit files, run installers, or commit until the user approves.
  • When approved, change one ecosystem at a time; show the diff.
  • Every pin bump names the new SHA/digest and reminds the user to do a supply-chain check and respect the cooldown (no artifact newer than N days).
  • Never weaken an existing pin. Never add a :latest/floating ref as a "fix".
  • Keep the human-readable version in a trailing comment beside every SHA pin.
  • Commit lockfiles — never gitignore them.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.