Baseline
Applies when: Adding a dependency to a manifest or running an install command, referencing a third-party CI action, building a Dockerfile/compose that pulls images or binaries, piping curl/wget output into a shell, or installing an agent skill, MCP server, or IDE extension.
Always:
- Install from a lockfile (
npm ci,pip install --require-hashes,poetry install --no-update,bundle install --frozen,cargo build --locked), never a bare install that re-resolves. - Pin third-party CI references (actions, orbs, plugins, reusable workflows) to a full commit SHA, not a mutable tag.
- Before adding a dependency, verify its spelling against the canonical upstream name, its publisher/namespace, its provenance/attestation, and its download/maintenance history.
- Replace
curl | bash-style pipe-to-shell installs with download, hash-verify, inspect, then run; prefer the project's package-manager distribution. - Review a package's install/build-hook scripts before installing; refuse ones that download external artifacts or touch
~/.ssh,~/.aws, or similar credential paths. - Vet any third-party agent skill, MCP server, or IDE extension as code: read the source, check the permission scope, and reject obfuscated or "ClickFix" install instructions.
Never:
npm install/yarn add/pip install <pkg>(unpinned, no lockfile) in CI.- A GitHub Actions third-party reference pinned to a tag or branch (
@main,@v1) instead of a commit SHA. - Silently substituting a similar-looking package when a typosquat or namespace check fails.
curl ... | bash,wget -qO- ... | sh, oriwr ... | iexpiped installs.- Installing an agent skill or extension with a "ClickFix" instruction, an obfuscated/base64 payload, or a typosquatted publisher name.
Open the full component before acting: it has the examples, the remediation steps, and the detection patterns.
Why
Modern applications execute orders of magnitude more code from
external publishers than from the developer who wrote the project.
A typical Node service pulls in 1,000+ transitive npm packages on
first install; a typical Python service depends on a wheel built
by a maintainer the developer has never met; a typical CI pipeline
executes third-party GitHub Actions that have access to repository
secrets and GITHUB_TOKEN.
The failures this skill blocks share a common shape: the agent adds, recommends, or trusts code from a publisher that hasn't been verified, pinned, or scoped.
- A typosquatted package (
requetsinstead ofrequests,1odashinstead oflodash) ships a working API surface and a payload that runs at install time. - A floating GitHub Action tag (
uses: some-org/action@main) silently changes its source code overnight; the next CI run executes whatever the maintainer pushed last, with access to every secret the workflow can read. - A
curl ... | bashinstall pipes a script the developer never read into the shell. The script can do anything the shell can. - An unverified agent skill, MCP server, or VS Code extension runs with the IDE's full permissions — access to the file tree, the shell, paired credentials, the developer's chat history with the agent itself. In February 2026, 341 malicious skills were found on ClawHub alone.
- A
postinstallscript from a recently-published package executes duringnpm install— before any code review, before any test, before the developer has even read what the package does.
This skill applies the same query-then-modify discipline used elsewhere in the bundle: inspect first, pin always, prefer the verifiable source.
When to apply
Apply this skill before the agent runs, recommends, or commits any of the following:
- A new entry in
package.json,requirements.txt,pyproject.toml,Pipfile,Gemfile,go.mod,Cargo.toml,pom.xml,build.gradle,composer.json, or any other dependency manifest. - An install command —
npm install,pnpm add,yarn add,pip install,poetry add,bundle add,gem install,go install,cargo install,brew install,apt-get install,apk add. - A GitHub Actions workflow that consumes a third-party action
(
uses: <owner>/<action>@<ref>), or any CI provider equivalent (GitLabinclude:, CircleCI orbs, Buildkite plugins, Azure Pipelines tasks). - A
Dockerfileordocker-compose.ymlthat pulls a base image, installs OS packages, or downloads binaries inside the build (cross-referencesdocker-safety). - A
curl,wget, orInvoke-WebRequestcommand that pipes its output into a shell, language runtime, or installer. - Any agent skill, MCP server, IDE extension, OpenClaw skill, ClawHub install, or similar published-by-someone-else artifact the agent is about to install or run.
- Any post-install script (
postinstall,prepare,prepublish, pipsetup.py install-time code,pyproject.toml[tool.*]build hooks).
This skill is the publisher-trust counterpart to docker-safety
(image build), secrets-management (CI logs), and
local-cli-safety (curl-pipe-bash). All four apply together.
Rules
Rule 1 — Dependencies are pinned and locked
Every dependency manifest must be accompanied by a lockfile, and installs must consult the lockfile rather than re-resolving from the manifest:
| Manifest | Lockfile | Install command |
|---|---|---|
package.json |
package-lock.json / pnpm-lock.yaml / yarn.lock |
npm ci / pnpm install --frozen-lockfile / yarn install --immutable |
requirements.txt |
requirements.txt with pinned versions and --hash= entries |
pip install --require-hashes -r requirements.txt |
pyproject.toml |
poetry.lock / uv.lock / pdm.lock |
poetry install --no-update / uv sync --frozen |
Gemfile |
Gemfile.lock |
bundle install --frozen |
go.mod |
go.sum |
go mod download |
Cargo.toml |
Cargo.lock |
cargo build --locked |
npm install, pip install <package> without a lockfile, and yarn add in CI are prohibited because they re-resolve and may pull a
newer version than the developer tested with.
Rule 2 — GitHub Actions and other CI references are SHA-pinned
Third-party CI references must pin to a full commit SHA, not a tag. Tags are mutable; SHAs are immutable.
# ✅ SHA-pinned with the tag in a comment for human readability
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.0
The same applies to:
- CircleCI orbs (use the SHA-pinned form, not
orb: foo@1.0). - Buildkite plugins (
plugins: foo#sha). - Reusable workflows (
uses: org/repo/.github/workflows/x.yml@<sha>). - Docker base images (cross-references
docker-safety).
GitHub-owned actions (actions/*, github/*) and other
organization-trusted publishers may use tag pinning in
non-production repositories; production workflows pin everything to
SHAs.
Rule 3 — Package identity is verified before install
Before adding a dependency, the agent verifies:
- Spelling. Compare character-by-character against the
upstream canonical name.
requestsvsrequets,lodashvs1odash,colorsvscoIors(capitali). - Publisher / namespace.
@types/*,@stripe/*, etc. are scoped to known publishers; their unscoped lookalikes (stripe-helper,types-react) are suspect. - Provenance. Prefer packages with npm provenance attestations,
Sigstore signatures, or other verifiable build origin.
npm view <pkg> --jsonexposes the publish source; GitHub attestations are surfaced viagh attestation verify. - History. Reject packages with
< 100weekly downloads when there is a high-volume alternative; reject packages with no releases in> 2years when an actively maintained alternative exists. - Maintainer reputation. A single author publishing 50+ packages across unrelated categories (crypto, finance, social tools, scraping) is a red flag pattern for mass-malware distribution.
If any check fails, the agent does not silently substitute a similar-looking package. It surfaces the concern and waits for the user to confirm or redirect.
Rule 4 — curl | bash and equivalent pipe-to-shell installs are prohibited
The following patterns are prohibited and must be flagged whenever the agent encounters them in a script, Dockerfile, README copy-paste, or AI-generated install instruction:
# ❌ Any of these
curl -fsSL https://example.com/install.sh | bash
curl ... | sh
wget -qO- https://example.com/setup | bash
iwr -useb https://example.com/install.ps1 | iex # PowerShell
The agent replaces them with: download, inspect, hash-verify, then
run. Where the project publishes an installer, prefer the project's
package-manager distribution (brew, apt, dnf, the
language-native package manager) over the upstream install script.
The same rule applies to language-runtime installs that pipe to
shell (nvm install from a piped script, rustup if invoked via
pipe, oh-my-zsh if invoked via pipe). Pre-download, read, verify,
then execute.
Rule 5 — Post-install scripts and build hooks are reviewed
Packages that run code at install time (postinstall in
package.json, setup.py cmdclass, pyproject.toml
[build-system] with custom hooks, gem install native extension
builds) are reviewed before the install runs.
The agent:
- Surfaces the package's install scripts when adding a new
dependency.
npm view <pkg> scriptsshows the script block; pip surfaces install hooks viapip install --dry-run -v. - Refuses to add a package with an install script that downloads
external artifacts, modifies files outside the package's own
directory, or executes against
~/.ssh,~/.aws,~/.openclaw, or any path matched bylocal-cli-safetyRule 5. - Where install scripts are non-malicious but inconvenient,
prefers running installs with the script disabled
(
npm install --ignore-scripts; pip has no equivalent, and--no-build-isolationstill runssetup.pyand the build backend, so preferpip install --only-binary=:all:, which installs wheels and never runs package build code) unless the project documentation requires the hook.
Rule 6 — Agent skills, MCP servers, and IDE extensions are vetted as code
Anything the agent installs or runs that is published by a third party — agent skills (ClawHub, OpenClaw, MCP registries), IDE extensions (VS Code Marketplace, JetBrains Marketplace), CLI tools distributed via brew taps, language-runtime plugins (Vim, Emacs, shell prompts) — is reviewed as code, not as a feature.
The vetting checklist:
- Read the source. The README is marketing. The code is the contract.
- Look at the manifest for permission scope. Skills that request access beyond their stated purpose are rejected.
- Reject "ClickFix" install patterns. Any skill whose prerequisites say "run this command to fix installation," "download this ZIP and extract before installing," or "execute this base64 string" is rejected without further review.
- Reject obfuscated payloads. Base64-encoded scripts, minified install code, packed binaries — all are reasons to refuse.
- Compare the package name character-by-character against the
publisher the user named.
clawhubb,cllawhub,clawhub-cli,clawhub-toolsare typosquats. - Prefer registry-managed installs.
npx skills add <verified-publisher>/<skill>(which validates source against the publisher's GitHub) is preferred over manual file drops.
For production-critical or organization-wide skills, the install goes through the team's documented review process even when the above checks pass.
Negative examples
# ❌ GitHub Actions: floating tag from a non-canonical publisher
- uses: some-org/build-action@main
- uses: random-user/upload-artifact@v1
# ❌ Production workflow with write-all permissions
permissions: write-all
# ❌ npm install in CI — re-resolves from manifest, ignores lockfile drift
npm install
yarn add some-package # adds without freezing
pip install requests # unpinned, no lockfile
// ❌ Unpinned versions in package.json — `^` allows minor bumps on install
{
"dependencies": {
"express": "^4.17.0",
"lodash": "*"
}
}
# ❌ Pipe-to-shell installer
curl -fsSL https://get.example.com | bash
wget -qO- https://install.example.com/setup.sh | sh
iwr https://get.example.com/install.ps1 | iex
# ❌ Typosquat suggestions
npm install requets # should be `requests` (and that's pypi anyway)
pip install python-discord # should be `discord.py` from the correct publisher
npm install coIors # capital "i", should be `colors`
# ❌ Installing a skill without source review
npx skills add random-user/some-new-skill
code --install-extension untrusted-publisher.fake-claude
# ❌ Random registry pull during image build (cross-ref docker-safety)
FROM random-registry.com/some-image:latest
RUN curl -fsSL https://install.example.com | bash
# ❌ pyproject.toml with no hash pinning
[project]
dependencies = [
"requests",
"django>=4.0"
]
Remediation
npm with full lockfile install
# ✅ Lockfile-respecting install (also faster in CI)
npm ci
# ✅ Adding a new dep: check provenance first
npm view express --json | jq '{name, version, maintainers, repository, dist}'
# Then add
npm install express@4.18.2 --save-exact
git add package.json package-lock.json
Python with pip-tools or uv (hash-locked)
# ✅ pip-tools workflow
pip-compile --generate-hashes requirements.in -o requirements.txt
pip install --require-hashes -r requirements.txt
# ✅ uv workflow
uv add requests
uv sync --frozen
GitHub Actions — SHA-pinned third-party
# ✅ Action pinned to SHA, tag in comment, minimal perms
jobs:
build:
permissions:
contents: read
id-token: write # for OIDC
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123:role/github-actions
aws-region: us-east-1
Pre-download + verify replacement for curl | bash
# ✅ Download, inspect, hash-verify, then execute
curl -fsSL -o /tmp/install.sh https://example.com/install.sh
EXPECTED="abc123...sha256-from-vendor-docs"
ACTUAL=$(sha256sum /tmp/install.sh | awk '{print $1}')
[[ "$EXPECTED" == "$ACTUAL" ]] || { echo "hash mismatch" >&2; exit 1; }
less /tmp/install.sh # human review
bash /tmp/install.sh
Verifying an npm package's provenance attestation
# ✅ Check that the package was built by the claimed publisher's CI
npm view <pkg> --json | jq '.dist.attestations'
# ✅ Or via sigstore
cosign verify-attestation \
--certificate-identity-regexp 'https://github.com/<owner>/<repo>/.github/workflows/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
<pkg-tarball>
Reviewing an agent skill before install
# ✅ Inspect the skill before installing
npx skills add <publisher>/<skill> --list # preview
git clone https://github.com/<publisher>/<repo> /tmp/skill-review
cd /tmp/skill-review
# Read SKILL.md, scripts/, manifest. Look for:
# - ClickFix install instructions
# - Base64 / minified / obfuscated content
# - External-download steps
# - Permission scope beyond stated purpose
grep -rE "(eval|exec|base64|curl.*\|.*sh|wget.*\|.*sh)" .
# If clean, install with the official CLI
npx skills add <publisher>/<skill> --agent claude-code -g -y --copy
Dockerfile install — minimal, verified, locked
# ✅ Cross-references docker-safety + supply-chain
# syntax=docker/dockerfile:1.7
FROM node:20.11.1-alpine3.19@sha256:f1fe1d...
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY package.json package-lock.json ./
# Lockfile install, no scripts, deterministic
RUN npm ci --omit=dev --ignore-scripts
COPY --chown=app:app . .
USER app
CMD ["node", "index.js"]
Production detection heuristics
Treat the supply-chain context as production-class (escalating the protocol) when any of these match:
- The change is in a workflow or branch that publishes a release, pushes to a registry, or deploys to a production environment.
- The repository is the source of an image, library, or skill other organizations consume.
- The dependency manifest belongs to a service that handles cardholder data, PHI, or regulated material.
- A separate
dev/stagingworkflow exists with looser rules — this is the prod workflow.
If any signal matches, all six rules apply without exception. In particular, Rule 2 (SHA pinning) and Rule 1 (lockfile-respecting installs) become hard requirements with no tag-pinning exception for the GitHub-owned actions.
References
- SLSA framework — https://slsa.dev/spec/v1.0/levels
- GitHub Actions security hardening — https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
- npm provenance — https://docs.npmjs.com/generating-provenance-statements
- Sigstore — https://www.sigstore.dev/
- OWASP Top 10 CI/CD Security Risks — https://owasp.org/www-project-top-10-ci-cd-security-risks/
- OpenSSF Scorecard — https://github.com/ossf/scorecard
- Socket.dev (npm supply-chain analysis) — https://socket.dev/
- pip-audit — https://github.com/pypa/pip-audit
- govulncheck — https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck