From hardening-dev-environment
Hardens uv-managed Python projects against PyPI supply chain risk. Migrates legacy pip / setup.py projects to a uv-native shape, writes baseline `[tool.uv]` settings into pyproject.toml, and replaces `pip install` / `pipx run` invocations with pinned `uv add` / `uvx`. Use when you hear "harden uv config", "harden python project", "python supply chain hardening", "migrate pip to uv".
How this skill is triggered — by the user, by Claude, or both
Slash command
/hardening-dev-environment:hardening-uv-configThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reduce PyPI supply chain attack surface by applying uv hardening settings to a Python project. Configuration-time hardening stops known attack patterns before any runtime or commit-time check is needed. For the full layered defense picture across `hardening-dev-environment`, see `hardening-overview`.
Reduce PyPI supply chain attack surface by applying uv hardening settings to a Python project. Configuration-time hardening stops known attack patterns before any runtime or commit-time check is needed. For the full layered defense picture across hardening-dev-environment, see hardening-overview.
pyproject.toml, requirements.txt, setup.py)Run these in the project root and read the outputs:
uv --version — verify uv is installed[ -f pyproject.toml ] && cat pyproject.toml || echo "no pyproject.toml"[ -f uv.lock ] && echo "uv.lock present" || echo "no uv.lock"ls requirements*.txt setup.py setup.cfg poetry.lock Pipfile pdm.lock 2>/dev/nullClassify the project shape from the outputs:
| Shape | Markers | Migration needed in Step 2? |
|---|---|---|
| uv-native | pyproject.toml with [tool.uv] or uv.lock | No |
| pyproject (non-uv) | pyproject.toml without [tool.uv], no uv.lock | Light — add [tool.uv] table only |
| requirements-only | requirements*.txt, no pyproject.toml | Yes — initialize pyproject.toml |
| setup.py-only | setup.py / setup.cfg, no pyproject.toml | Yes — initialize pyproject.toml |
| other (poetry / Pipenv / pdm) | poetry.lock, Pipfile, pdm.lock | Stop. See Troubleshooting |
Build a mental model of which target settings are missing, present, or conflicting. Do not write yet.
Skip this step for uv-native projects. For other (poetry / Pipenv / pdm), stop and follow Troubleshooting — automatic migration is out of scope.
For requirements-only and setup.py-only, propose this migration plan to the user and wait for explicit approval before any write:
| Source | Target action |
|---|---|
requirements.txt | Run uv init --bare to create a minimal pyproject.toml, then uv add -r requirements.txt to import deps into [project.dependencies] |
requirements-dev.txt (or similar) | uv add --dev -r requirements-dev.txt |
setup.py with simple install_requires | Read install_requires and propose the equivalent uv add .... If setup.py contains custom build logic, stop and surface the file for manual migration |
Existing setup.py build backend | Leave in place. Adding [tool.uv] does not require removing setup.py |
For pyproject (non-uv), no dependency import is needed — proceed directly to Step 3.
After migration, regenerate the lockfile:
uv lock
Compare the current [tool.uv] against Target Settings. For each key:
For no-build-isolation-package, the safe default is an empty list. Disabling build isolation gives a package's build backend access to the project environment, which is the threat surface this setting controls. Add an entry only if the user identifies a specific package whose build legitimately requires it.
Show one consolidated diff per target file (pyproject.toml, and .python-version if it needs changes). Wait for explicit approval per file before any write.
Edit for in-place modifications to existing filesWrite only when creating a missing fileTrigger a fresh resolve so the new policy is exercised:
rm -rf .venv uv.lock && uv sync
Confirm each of the following:
| Expectation | What to look for |
|---|---|
| pyproject.toml parses cleanly | uv emits no warning lines about [tool.uv] |
exclude-newer is enforced | A package published inside the cooldown window resolves to its previous version, or uv sync reports no candidates if no older version exists |
required-version is enforced | Running uv from a non-matching version fails with a clear error |
index-strategy = "first-index" is in effect | uv sync does not consider later-listed indexes for a package available on the first |
If a step fails, stop and revisit Step 3's plan rather than relaxing the config.
pip install / pipx run invocations to uvpip install and pipx run invocations bypass every config-level setting written above (they shell out to a different tool). Replace them with uv add <pkg> (project-scoped) or uvx <pkg>@<version> (one-shot) so the same policy (exclude-newer, index-strategy) applies. This step is self-contained: detect → classify → resolve versions → confirm → apply.
Scan tracked files for pip/pipx usage; gitignored paths are excluded automatically:
git ls-files | grep -vE '\.(md|markdown|txt|rst)$' | \
xargs grep -nE '\b(pip install|pipx run|pipx install)\b' 2>/dev/null
Documentation files are intentionally excluded — README rewrites are user-driven and out of scope for this skill.
For each match, classify per pip / pipx Migration Rules. Auto-migratable matches proceed to 7.3. Report-only matches are surfaced at the end of the step as file:line: <original command>; the user decides them manually.
For each auto-migratable package, fetch the latest stable version from the registry:
uv pip index versions <pkg> | head -1
Propose the replacement using the resolved version. The user may override the version. Reject @latest as a literal tag — a floating tag reproduces the unpinned-execution risk this step is meant to remove.
Present a per-file diff (one consolidated diff per file with multiple hunks). After explicit approval per file, apply each change with Edit. Re-read each file after writing to verify.
After config is in place, print the install instructions in Recommended External Tools. Do not run the install commands automatically — they are global / per-developer decisions.
| Key | Location | Value | Purpose |
|---|---|---|---|
required-version | [tool.uv] in pyproject.toml | ">=<detected-version>" (e.g. ">=0.5.0") | Pin a uv version floor |
exclude-newer | [tool.uv] | "P3D" (ISO 8601 duration, 72h sliding window) | Reject freshly-published packages |
index-strategy | [tool.uv] | "first-index" | Prevent dependency confusion across multiple indexes |
no-build-isolation-package | [tool.uv] | [] (empty allowlist) | Keep build isolation on for every package by default |
Reference: https://docs.astral.sh/uv/reference/settings/
pyproject.toml [tool.uv] after apply[tool.uv]
required-version = ">=0.5.0"
exclude-newer = "P3D"
index-strategy = "first-index"
no-build-isolation-package = []
no-build = true rejects all source distributions, removing build-time arbitrary code execution entirely. Recommend it only when the project's dependencies all ship wheels for the target Python and platform. Common ML/data stacks (some numpy / pandas build matrices) may break under this setting, so it is opt-in rather than default.
pipx is also denied at the permission layer when hardening-claude-permissions is applied. Migrating tracked-file occurrences here covers documentation and scripts; the permission deny prevents Claude from invoking pipx ad-hoc in interactive sessions. pip install is not permission-denied — see hardening-claude-permissions design notes for the rationale (persistent installers are handled at the [tool.uv] config layer).
| Pattern | Class | Replacement |
|---|---|---|
pip install pkg (project dep) | auto | uv add pkg (resolves a pin into pyproject.toml + uv.lock) |
pip install pkg==X.Y | auto | uv add 'pkg==X.Y' |
pip install -r requirements.txt | auto | uv add -r requirements.txt |
pipx install pkg | auto | uv tool install pkg |
pipx run pkg / pipx run pkg arg… | auto | uvx pkg@<version> |
pip install -e . (editable self-install) | report-only | (project install pattern; user judgment needed) |
pip install --user pkg | report-only | (per-user global install — surface for review) |
pip install pkg --no-deps | report-only | (intentional bypass of resolver — surface for review) |
These complement the config-level prevention. The skill does not install them on behalf of the user.
Audits installed packages or pyproject.toml against the PyPI Advisory Database. Suitable for both pre-commit and CI.
uv tool install pip-audit
pip-audit
Reference: https://github.com/pypa/pip-audit
Wraps npm / pnpm / pip / etc. via shell aliases to block known-malicious installs against the Aikido Intel feed. Best installed per-developer — interactive prompts make it unsuitable for CI. The installer command below uses curl, which hardening-claude-permissions denies for Claude — run it in your own shell.
Use the official one-line installer (Unix/Linux/macOS):
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh
For reproducibility, pin to a specific release (recommended) by replacing latest with vX.Y.Z from the releases page:
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/download/vX.Y.Z/install-safe-chain.sh | sh
Restart the terminal to load aliases.
Reference: https://github.com/AikidoSec/safe-chain
Safe Chain's shell aliases only load in interactive shells. Claude Code's Bash tool runs bash -c non-interactively, so the aliases do not apply to Claude-issued installs by default. To extend coverage, set BASH_ENV in your user-level ~/.claude/settings.json so every Bash invocation sources the Safe Chain init script:
{
"env": {
"BASH_ENV": "${HOME}/.safe-chain/scripts/init-posix.sh"
}
}
Edit this file from your own shell — hardening-claude-permissions denies Claude write access to ~/.claude/settings.json. Keep the entry in user-level settings (not project .claude/settings.json), since Safe Chain is a per-developer install and the path will not exist for collaborators who have not installed it. Bash silently ignores a missing BASH_ENV target, but install Safe Chain first to avoid surprises. Restart Claude Code to pick up the new env, then run pip --version (or pnpm / npm) under the Bash tool to confirm Safe Chain's banner appears.
Scans uv.lock against the OSV.dev CVE database. Suitable for both pre-commit and CI.
osv-scanner -L uv.lock
Reference: https://google.github.io/osv-scanner
If the project uses .githooks/, append the following to .githooks/pre-commit (create the file with chmod +x if absent):
if command -v osv-scanner >/dev/null 2>&1; then
osv-scanner -L uv.lock || exit 1
fi
Activate per clone with git config core.hooksPath .githooks. This is a per-clone setting and cannot be checked into the repository — document the activation step in the project README.
| Situation | Action |
|---|---|
uv not installed | Stop. Recommend curl -LsSf https://astral.sh/uv/install.sh | sh for Unix-like, or pipx install uv if pipx is already trusted on the host. Re-run from Step 1 after install |
pyproject.toml not found and no requirements*.txt / setup.py | Stop. This skill targets Python projects only |
| Project shape is other (poetry / Pipenv / pdm) | Stop. Automatic migration is out of scope. Recommend the user follow uv's official migration guide for their toolchain, then re-run from Step 1. Settings written by this skill apply identically once [tool.uv] is present |
pyproject.toml is malformed TOML | Report the parse error and ask the user to fix manually before retrying |
setup.py contains custom build logic beyond install_requires | Surface the file and ask the user to migrate manually. Do not auto-rewrite — setup.py semantics are not statically analyzable in general |
exclude-newer = "P3D" blocks a needed dependency | Add a per-package override under exclude-newer-package rather than lowering the global value |
uv pip index versions <pkg> fails (offline / restricted registry) | Skip auto-resolution; ask the user to provide each version manually, or defer Step 7 until network is available |
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub khaym/claude-code-plugins --plugin hardening-dev-environment