AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Pin Everything

skill-cboone-agent-harness-plugins-pin-everything · by cboone

>-

— No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-cboone-agent-harness-plugins-pin-everything

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-cboone-agent-harness-plugins-pin-everything)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Pin Everything? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Pin Everything

Pin every version surface in a repository as a one-shot supply-chain hardening pass: SHA pins for actions, integrity hashes for Corepack, exact pins for package-manager dependencies (in application context), version-file refs for language runtimes, and explicit versions for install commands. Optionally generates a weekly drift-audit script and tightens Dependabot.

This skill is a generalization of the canonical hardening pass executed in PR #250 on agent-harness-plugins itself. Adopters who want to repeat that pass on their own repositories run the skill end-to-end. Adopters who only want a subset (for example, SHA-pinning actions) can stop after the relevant step.

Workflow

1. Audit the Repo for Version Surfaces

Scan the working tree for every version surface, then output a categorized table (file paths, count of refs, current pin state).

Surfaces to detect:

| Category | Detection | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GitHub Actions uses: refs | Glob .github/workflows/*.{yml,yaml} and .github/actions/**/action.{yml,yaml}; also grep **/*.md for uses: lines (scaffolded reference docs and templates often embed real refs); grep for uses: | | Reusable workflow refs | Same files plus the same Markdown sweep; grep for uses: lines containing .github/workflows/ | | packageManager field | Read package.json; check .packageManager | | package.json deps | Read package.json; flag ^/~ ranges in dependencies/devDependencies. Skip peerDependencies — see step 6. | | Language version files | Glob .tool-versions, .nvmrc, .node-version, .ruby-version, .python-version, rust-toolchain.toml, build.zig.zon; also grep Gemfile for a ruby directive | | go.mod go directive | Read go.mod; capture the directive line | | Inline language pins in CI | Grep workflows for node-version:, ruby-version:, go-version:, python-version:, zig-version: (without -file suffix) | | go install pins | Grep for go install @ where ` is latest, a vN.Y.Z tag (with optional prerelease), or a pseudo-version (v0.0.0-YYYYMMDDhhmmss-) | | cargo install pins | Grep for cargo install with or without --locked --version | | pip / uv pins | Grep for pip install, uv pip install, uv add, uv tool install, uvx (with or without ==) | | npx pins | Grep for npx (with or without @version) | | Schema URLs | Grep .json and .yaml for $schema URLs containing @latest`. Pinning is per-publisher (see step 7). |

Exclude vendored directories from all greps: node_modules/, .yarn/, vendor/, dist/, target/, .venv/. Markdown templates with uses: refs that the consuming repo distributes downstream (skill scaffolds, README install snippets, etc.) are still in scope: real refs there should be pinned and refreshed against upstream just like CI workflows. The deliberate exclusions for placeholder paths (OWNER/REPO, `, etc.) are documented in step 7 and apply to install commands, not uses:` refs.

Print the output as a Markdown table grouped by category, with one row per file showing the file path, the count of matching refs, and a one-word state (pinned / unpinned / mixed).

2. Confirm Scope with the User

Present the categorized findings. The default is to pin everything in the repo that is not deliberately user-facing. User-facing means scaffolded README install instructions, placeholder paths in skill templates, and similar documentation that downstream users will customize.

Ask the user to confirm or trim the scope. Offer per-category opt-out (e.g., "skip pip pinning", "skip Dependabot config", "actions only"). If the user invokes the skill with --scope , use that list directly; otherwise prompt.

If the user requested --dry-run, or invoked the skill with audit-only phrasing (e.g. "audit version pins", "audit pins", "report unpinned versions", or any other request that asks for findings without changes), perform the audit only and stop here. Treat all audit-shaped trigger phrases the same as --dry-run so the README's "audit version pins" example does not silently fall into the mutating path.

3. SHA-Pin GitHub Actions

For every uses: ref to a third-party or org-owned action:

  1. Resolve the ref to a 40-character commit SHA. Prefer gh api repos//commits/ (always returns the commit SHA directly, even for annotated tags). If that fails, fall back to gh api repos//git/ref/tags/ and recurse through the tag object until a commit SHA is found.
  2. Replace the tag with the SHA and append a # vX.Y.Z comment. Example: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2.
  3. Channel-style refs (e.g. dtolnay/rust-toolchain@stable): pin to the SHA of the named branch's HEAD (gh api repos//commits/) and use # stable (or the branch name) as the comment.
  4. Reusable workflow refs (org/repo/.github/workflows/foo.yml@vN): pin them too. Tag immutability is not a function of repo ownership.
  5. First-party ./ refs (e.g. uses: ./.github/actions/local): leave unpinned; they resolve to the current commit by definition.

Reference: ./references/github-actions.md for full recipes including annotated-vs-lightweight tag handling and how to bump a held major version.

4. Pin Language Runtimes via Version Files

Replace inline pins in scaffolded CI with version-file refs so the version of record is a single file in the repo:

| Inline form | Version-file form | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | node-version: "X" | .tool-versions if it has a nodejs line, else .nvmrc, else .node-version | | ruby-version: "X" | .tool-versions if it has a ruby line, else .ruby-version, else Gemfile (if it has a ruby directive) | | go-version: "stable" or "X.Y" | go-version-file: "go.mod" | | python-version: "X.Y" | astral-sh/setup-uv: omit input (reads pyproject.toml requires-python directly). actions/setup-python: python-version-file: ".python-version"; create .python-version if missing | | zig-version: "X.Y.Z" | Action-direct (mlugg/setup-zig): omit (reads build.zig.zon). Wrapper (cboone/gh-actions/.../run-zig-ci.yml v2.2.0+): zig-version-file: "build.zig.zon" |

Reuse the file that already pins this language. Audit step 1 already enumerates which version files exist in the repo and what each one contains. Mere existence is not enough — a .tool-versions that lists only python and ruby will not configure Node when CI loads it. Verify the file has an entry for the language being configured before pointing the *-version-file input at it. If .nvmrc is present, point node-version-file at it instead of introducing a parallel .tool-versions. The whole purpose of the version-file rewrite is to make CI and local dev agree on a single source of truth; emitting a second file (or pointing at a file that lacks the relevant entry) silently re-creates the drift or breaks the workflow.

If no version file pins this language, either add a line to an existing .tool-versions (preferred when one is already present) or create the language's conventional file (.tool-versions with the language's line, .python-version for Python, rust-toolchain.toml for Rust) with current LTS / stable values. Reference ./references/language-runtimes.md for the LTS / stable lookup commands per language and the per-language fallback order.

Python special case: do not create .python-version for astral-sh/setup-uv workflows. uv reads pyproject.toml's [project] requires-python directly, so pyproject.toml is the version source. Adding a separate .python-version creates a second source that uv ignores and that local tooling and CI can drift apart on; the established convention in this repo (see plugins/set-up-ci/skills/set-up-ci/SKILL.md and plugins/set-up-ci/skills/set-up-ci/references/ci-python.md) is to keep requires-python as the only version source for uv workflows. If requires-python is missing, add it to pyproject.toml; do not create a parallel .python-version. Only create .python-version when the workflow uses actions/setup-python, which has no equivalent automatic source.

Normalize the file's value to an exact X.Y.Z before treating the rewrite as a pin. Existing version files routinely carry moving forms that look pinned but aren't: .nvmrc accepts aliases like lts/*, lts/iron, node, and latest; .ruby-version, .python-version, and .tool-versions entries are often major-only (3.4) or truncated to major+minor (3.13). CI that reads any of those values resolves them at install time, so each fresh runner can pick a different patch release while the workflow still claims to be "pinned to" the file. After selecting (or creating) the file, inspect its current contents and, if the value is anything other than an exact X.Y.Z, rewrite it to the matching exact release within the existing release line: the latest patch in the same major for Node.js (so .nvmrc = 23 becomes the highest 23.x.y, not the current LTS major), and the latest patch in the same X.Y feature line for Ruby and Python (so .ruby-version = 3.4 becomes the highest 3.4.z, not whatever 3.5.x may already be cut). Reach for the global LTS / stable lookup only when no version file existed before this pass and a fresh release line is being chosen. See ./references/language-runtimes.md for both the in-series and the create-from-scratch lookup commands. Only then has the runtime actually been pinned.

5. Pin the Corepack packageManager Field with SHA-512 Integrity

If package.json has "packageManager": "@X.Y.Z":

  1. Compute the SHA-512 integrity hash via Corepack itself. Run corepack use yarn@X.Y.Z for Yarn or corepack use pnpm@X.Y.Z for pnpm — either form downloads the requested release, computes the integrity hash, and writes the suffixed @X.Y.Z+sha512. form into package.json's packageManager field in one command. (corepack prepare ... --activate only prepares and activates the binary globally; it does not touch package.json.) Fall back to fetching the upstream tarball and computing shasum -a 512 if Corepack is unavailable: Yarn lives at https://repo.yarnpkg.com/${X.Y.Z}/packages/yarnpkg-cli/bin/yarn.js; pnpm lives at https://registry.npmjs.org/pnpm/-/pnpm-${X.Y.Z}.tgz. Use shasum -a 512 (portable across macOS and Linux); sha512sum is Linux-only.
  2. Rewrite the field as "@X.Y.Z+sha512.".
  3. Verify with corepack enable && (yarn|pnpm) --version.

npm-managed projects have no equivalent integrity surface in package.json — rely on package-lock.json's per-package integrity hashes plus npm ci in CI. Skip this step when packageManager is absent or names npm.

Reference: ./references/yarn-corepack.md (Yarn-specific .yarnrc.yml hardening covered in step 8 does not apply to pnpm).

6. Exact-Pin Package-Manager Dependencies (Application Context Only)

Strip ^/~ ranges from manifests, replacing each with the exact version locked in the lockfile. Discriminate library from application before touching the manifest — exact-pinning a library's manifest breaks downstream version unification.

| Ecosystem | Application discriminator | Library discriminator | Default if app | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------ | | Node.js | Lockfile committed AND ("private": true OR no published name exposure) | Library packages with name/exports configured for distribution | Pin manifest | | Ruby | No *.gemspec; Gemfile.lock committed | *.gemspec present (gem) | Pin from lockfile | | Python | requirements.txt or uv.lock committed; no library distribution name | pyproject.toml declares a published distribution | Pin to == | | Rust | Cargo.lock committed AND crate has a binary target ([[bin]], src/main.rs, or src/bin/*.rs; publish = false is also a strong app signal) | Crate exposes [lib]; Cargo.lock typically not committed | Exact-pin manifest |

When the discriminator is ambiguous (monorepo workspaces, hybrid crates, gems with bin entrypoints), prompt the user. Always pin the lockfile even for libraries — that is what yarn install --frozen-lockfile and friends consume in CI. Only the manifest changes between app and library treatment.

Never exact-pin Node.js peerDependencies. They express the range of host versions a package is compatible with; rewriting them to ==X.Y.Z overconstrains downstream installers and can break otherwise compatible consumers. Leave the existing range (caret, pessimistic, or >=) intact even when pinning dependencies and devDependencies. The same logic applies to optionalPeerDependencies. The audit in step 1 already excludes `pe

…

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.