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 |
npx claudepluginhub khaym/claude-code-plugins --plugin hardening-dev-environmentGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.