{"slug": "safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies", "title": "Safer-dependencies is a security layer for Claude Code that audits dependencies", "summary": "Safer-dependencies, a new security layer for Claude Code, automatically audits and blocks risky dependencies—including CVEs, typosquats, abandoned packages, and version-age issues—across npm, PyPI, RubyGems, Maven, Go, Rust, and PHP (Composer), correcting vulnerable versions on disk before they run. The tool, created by Robert Auger, is source-available under a license that permits free use and modification for internal and commercial products but requires a paid license to monetize the software itself.", "body_md": "When AI coding assistants like Claude add packages to your project, they often pick whatever version sounds right — without checking whether it has known security vulnerabilities, whether the package is still actively maintained, or whether the name is a typo away from a malicious lookalike.\n\nsafer-dependencies is a security layer for Claude Code: it sits between Claude and your manifest files and runs its security checks automatically: vulnerable installs are denied before they run, and a risky version written to a manifest is corrected on disk right after the write. It detects and fixes risky dependencies — CVEs, typosquats, abandoned packages, and version-age issues, plus a cooldown period on brand-new releases — across npm, PyPI, RubyGems, Maven, Go, Rust, and PHP (Composer). See [CAPABILITIES.md](/robert-auger/safer-dependencies/blob/main/CAPABILITIES.md) for exactly what is and isn't covered.\n\nNew here?[GETTING-STARTED.md]takes you from zero to a working install in about five minutes.\n\nSecurity & privacy:see[SECURITY.md](vulnerability disclosure),[PRIVACY.md](data egress, no telemetry), and[CAPABILITIES.md](what the tool defends against and what it doesn't).\n\nLicense (source-available — NOT OSI \"open source\"):Free to use and modify for your own purposes,including for-profit/company internal use and building products you sell. A separate paid license is requiredonlyto monetize the softwareitself— selling it, shipping it inside a product or service that is sold, or offering its functionality to third parties for a fee (including hosted/SaaS/API). Redistribution and derivatives must keep the license and credit this project. See(Section 4 for the commercial restriction); commercial-license requests via[LICENSE][github.com/robert-auger].\n\n[Getting started](#getting-started)— zero to installed in about five minutes[What it does](#what-it-does)[How it works](#how-it-works)[What triggers it](#what-triggers-it)[What's in this repo](#whats-in-this-repo)[Supported ecosystems](#supported-ecosystems)[Install](#install)[Warning levels](#warning-levels)[Audit log](#audit-log)[Requirements](#requirements)[FAQ](#faq)\n\n** GETTING-STARTED.md** takes you from zero to a working install in about five minutes — prerequisites, the interactive install, and verification. For the full install reference (global/project/manual installs, Windows specifics, the\n\n[permissions allowlist](/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist), updating, and uninstalling), see\n\n**.**\n\n[INSTALLATION.md](/robert-auger/safer-dependencies/blob/main/INSTALLATION.md)**Everyday use:** once the hooks are installed, there's nothing to run — safer-dependencies works automatically in the background. As Claude adds or installs packages, it **flags risky dependencies and upgrades vulnerable versions to a safe one in place** — and blocks a known-vulnerable install before it even runs — so unsafe packages are caught and corrected without you having to ask. You can still invoke it directly any time: *\"is axios@1.2.0 safe?\"*,\n\n*\"check safer-dependencies setup\"*, or\n\n*\"show safer-dependencies stats\"*.\n\nWhen Claude is about to add a package to your project, safer-dependencies intercepts and runs 5 checks:\n\n**Provenance**-- official registry, typosquat detection (npm/PyPI/RubyGems/Maven/crates.io), package age** Version age**-- picks the newest stable version published 7+ days ago (cooldown window)** Vulnerability scan**-- OSV API, with ecosystem-native tools (npm audit, pip-audit, bundle audit) when available** Hash-pin integrity**-- for PyPI`requirements.txt`\n\nlines with`--hash=sha256:...`\n\npins, the declared hash is validated against PyPI's published hashes; mismatch emits a WARNING**Abandoned & stale packages**-- known-abandoned packages (e.g.`paperclip`\n\n,`request`\n\n,`pycrypto`\n\n,`github.com/dgrijalva/jwt-go`\n\n) are hard-blocked immediately with a suggested replacement; packages with no stable release in 2+ years get an advisory`STALE:`\n\nwarning. Hard-blocked packages are removed from the manifest and Claude will ask how to proceed; stale-only packages are left in place.\n\nIf issues are found, Claude emits warnings and may step back to a safer version. All checks are logged to `~/.claude/safer-dependencies-audit-YYYY-MM.log`\n\n(one file per calendar month).\n\nThe skill operates in five modes (summarized below; the deepest design rationale lives in `skills/safer-dependencies.md`\n\n):\n\nWhen Claude is about to write an `import`\n\n, add a package to a manifest, or update a lock file, the skill runs inline in your session:\n\n- Queries the package registry for stable versions\n- Auto-selects the newest version published 7+ days ago (deterministic -- no LLM judgment)\n- Checks for known vulnerabilities via ecosystem tools and the OSV API\n- Verifies package signatures where available\n- Emits warnings if issues are found, pins the exact version\n- Logs the result to the audit trail\n\nThe version selection is handled by standalone Python scripts bundled with the skill, not by the LLM interpreting rules. The command outputs `SELECTED: <version>`\n\nand Claude uses that version exactly.\n\nConfigure `.claude/settings.json`\n\nwith a `PostToolUse`\n\nhook to enable automatic, transparent package verification:\n\n- Claude writes a manifest file (e.g.\n`package.json`\n\n) with the originally-requested version — the file lands on disk - The\n`PostToolUse`\n\nhook fires immediately after the write completes and invokes`safer-dependencies-shim.sh`\n\n- The shim reads the file, parses declared packages, and runs all security checks (typosquat, abandoned, CVE, staleness, hash-pin)\n- If corrections are needed, the shim\n**rewrites the manifest in place** with safe versions (or removes entries that have no safe version) - The shim emits signals (\n`UPDATED:`\n\n,`BLOCKED:`\n\n,`WARNING:`\n\n,`STALE:`\n\n,`MAJOR-UPDATE-CONFIRM:`\n\n,`REFACTOR-REQUIRED:`\n\n,`REGRESSION:`\n\n,`TYPOSQUAT-CONFIRM:`\n\n,`VERIFY:`\n\n,`CLEAN:`\n\n) via`hookSpecificOutput.additionalContext`\n\non stdout.`REGRESSION:`\n\nprecedes a`MAJOR-UPDATE-CONFIRM:`\n\nwhen the audit log shows the same (file, package) was previously corrected to the same safe target — that is, a subagent or stale plan has re-introduced a known-vulnerable version, and the orchestrator should restore the previously-approved version rather than re-deciding the major bump. - Claude receives those signals as a system-reminder and performs follow-up work (find affected imports, run tests, refactor for breaking changes)\n\n**Design note — Shape C (post-write corrective):** the hook does NOT block writes. Each vulnerable version lands on disk first and is then auto-corrected within the same tool-use cycle. This is a deliberate choice over a `PreToolUse`\n\nblocking design — see [FAQ.md](/robert-auger/safer-dependencies/blob/main/FAQ.md#why-posttooluse-post-write-corrective-instead-of-pretooluse-pre-write-blocking-for-the-manifest-path) for the tradeoffs.\n\n**Example signal:**\n\n```\nUPDATED: aiohttp 3.8.5 → 3.9.0 (HIGH: 33 CVEs fixed)\n```\n\nThe parent agent uses these signals to identify affected code and refactor as needed.\n\nConfigure `.claude/settings.json`\n\nwith a `PreToolUse:Bash`\n\nhook to enable\npre-flight auditing of package-manager install commands. This complements\n(does not replace) Intercept Mode — together they form a layered defense.\n\n- Claude attempts a Bash tool call (e.g.\n`npm install lodash@4.17.20`\n\n) - The\n`PreToolUse`\n\nhook fires before the call runs and invokes`safer-dependencies-pretooluse-bash.sh`\n\n- A pure-bash early filter short-circuits non-PM commands in ~115 ms\n(no Python invocation), so\n`git status`\n\n/`ls`\n\n/`npm test`\n\npay negligible cost on the hot path - For recognized package-manager installs (\n`npm`\n\n/`pnpm`\n\n/`yarn`\n\n`install`\n\n/`i`\n\n/`add`\n\n), the helper tokenizes via`shlex`\n\n, extracts each`pkg@version`\n\nargument, and POSTs to OSV - Any vulnerable concrete pin → the hook returns\n`permissionDecision: \"deny\"`\n\nwith a per-finding GHSA-id + CVSS + summary, plus a hint to invoke the safer-dependencies skill - The install never runs — no network fetch, no postinstall scripts\n\n**Why this exists in addition to Intercept Mode:** the post-write shim\nis blind to Bash. `npm install lodash@4.17.20`\n\nruns to completion (and\npostinstall scripts execute) before any audit fires; `npm install -g typosquat-pkg`\n\nwrites no project manifest at all. Pre-Install Mode\ncloses those gaps structurally.\n\nPre-Install Mode only sees what the user **typed** (`pkg@version`\n\nargs on\nthe command line). It can't see the transitive tree the resolver will\nactually install. **Post-Install Mode** (below) audits the lockfile once\nthe install completes — the two modes are complementary, not redundant.\n\n**Scope:** the package-manager CLIs covered here span five ecosystems\n(npm/pnpm/yarn/bun/npx/deno, pip/pip3/pipx/pipenv/uv/uvx/poetry, gem/bundle,\ngo, cargo), plus Maven via Intercept Mode (Maven dependencies are typically\ndeclared in `pom.xml`\n\n/`build.gradle`\n\n, not added via a CLI verb).\n\nKnown gap:the Maven CLI does support direct downloads via`mvn dependency:get -Dartifact=group:art:version`\n\nand`mvn dependency:copy`\n\n. This hook does not yet recognize those invocations. If you use them regularly, the existing post-write shim still catches whatever lands in your manifest, but the pre-fetch protection only applies to the ecosystems listed above. Tracked as a follow-up.\n\nPer-ecosystem syntax recognized:\n\n| PM | Verbs | Concrete-pin syntax |\n|---|---|---|\n`npm` , `pnpm` , `yarn` , `bun` |\n`install` , `i` , `add` (plus `yarn` /`pnpm dlx` , `bun x` , `yarn create` ) |\n`pkg@1.2.3` , `@scope/pkg@1.2.3` |\n`npx` |\n(verbless — package is first positional) | `pkg@1.2.3` |\n`deno` |\n`add` , `install` |\n`npm:pkg@1.2.3` (npm-prefixed specs) |\n`pip` , `pip3` , `pipx` , `pipenv` , `uv` , `uvx` , `poetry` |\n`install` (pip/pip3/pipx/pipenv) / `add` (uv/poetry) / verbless (uvx) |\n`pkg==1.2.3` (extras `pkg[extra]==X` also handled) |\n`gem` , `bundle` |\n`install` (gem) / `add` |\n`-v 1.2.3` , `--version 1.2.3` , `--version=1.2.3` (separate flag) |\n`go` |\n`get` , `install` |\n`pkg@v1.2.3` (must include `v` prefix per Go modules) |\n`cargo` |\n`add` , `install` |\n`crate@1.2.3` |\n\nRange pins (npm `^4.17`\n\n, pip `>=`\n\n, poetry `^`\n\n/`~`\n\n, Go `@latest`\n\n) and\nunspecified versions pass through to Intercept Mode after install — the\npost-write shim audits whatever the resolver picks. Auto-rewrite to a\nsafe version is queued as a follow-up.\n\n**Failure mode:** fail-open. Any error (Python missing, network blip,\nmalformed input) exits 0 with no output, allowing bash to proceed.\nIntercept Mode still runs after install, so a failed pre-flight degrades\ngracefully to existing protection.\n\n**Example deny:**\n\n```\nsafer-dependencies pre-flight audit blocked this install.\nVulnerable pinned version(s) detected:\n  - lodash@4.17.20 → GHSA-35jh-r3h4-6jhm (CVSS:7.4): Command Injection in lodash\nRe-run with a patched version, or invoke the safer-dependencies skill\nfor a recommended pin.\n```\n\nConfigure `.claude/settings.json`\n\nwith a `PostToolUse:Bash`\n\nhook to enable\npost-flight auditing after Bash commands. It runs **three independent scans**\nagainst the command's `cwd`\n\n, each closing a gap the other hooks can't address:\n\n**Scan A — lockfiles.** After a successful install verb (`npm install`\n\n,`bundle install`\n\n,`poetry install`\n\n,`uv sync`\n\n,`go mod tidy`\n\n, etc.), audits freshly-modified lockfiles (`package-lock.json`\n\n,`Gemfile.lock`\n\n,`poetry.lock`\n\n,`uv.lock`\n\n,`go.sum`\n\n,`yarn.lock`\n\n,`pnpm-lock.yaml`\n\n,`Pipfile.lock`\n\n). This closes the**transitive-CVE gap** Pre-Install can't see: the user typed`pkg@version`\n\n, but the resolver may have pulled in dozens of transitives no one named.**Scan B — manifests.** After any Bash command*not*on a read-only denylist (`ls`\n\n,`cat`\n\n,`git status`\n\n, …), audits freshly-modified manifests. This is the**only** fallback for manifest edits made via`sed -i`\n\n,`jq`\n\n, or a script — those bypass the`Write`\n\n/`Edit`\n\ntool that Intercept Mode hooks on.**Scan C — resolved environment.** Plain`pip install`\n\n/`pip install -r requirements.txt`\n\nwrites no lockfile, so Scan A never sees the resolved tree. After a pip-shaped install, Scan C re-invokes the same pip with a read-only`list --format=json`\n\nand OSV-checks the full resolved environment (direct + transitive).\n\nHow a scan runs:\n\n- Claude runs a Bash tool call\n- The\n`PostToolUse`\n\nhook fires*after*the command completes and invokes`safer-dependencies-posttooluse-bash.sh`\n\n- A pure-bash early filter short-circuits commands that match no scan gate in\n~115 ms (same fast-path convention as Pre-Install), so\n`ls`\n\n/`git`\n\n/`cat`\n\npay negligible cost - Each scan walks\n`cwd`\n\nwith`find -maxdepth 5`\n\n(covers monorepo layouts; excludes`node_modules`\n\n,`.git`\n\n,`.venv`\n\n,`venv`\n\n) for files modified within the last 60 s — override via`SAFE_DEP_POSTINSTALL_MTIME_WINDOW`\n\n- For each freshly-modified file (Scan A/B), the hook forges a synthetic\n`PostToolUse:Write`\n\npayload and pipes it to the existing shim — the shim's lockfile and manifest auditors run unchanged, no duplicated logic - Per-file signals are concatenated and emitted as one\n`hookSpecificOutput`\n\nJSON to the parent agent\n\n**What it catches that Pre-Install doesn't:** transitive vulnerabilities.\nA clean-looking `bundle install`\n\ncan pull `rack@2.2.23`\n\n(CVE-2025-27610)\nas a transitive of `sinatra`\n\n— the user never typed `rack`\n\n, so\nPre-Install can't see it, but Post-Install reads the resolved\n`Gemfile.lock`\n\nand reports the CVE.\n\n**Scope:** Scan A does not rewrite resolved versions — the auto-correct\ncontract only applies to manifests Claude wrote directly. For transitive CVEs,\nthe fix is typically \"update the direct dep that owns the transitive,\" which\nneeds human judgment. Scan B *does* auto-correct, because it audits manifests\nthrough the same shim path as Intercept Mode. Scan A skips when the\n`transitive`\n\ncheck tier is set to `off`\n\n(`config set checks.transitive off`\n\n).\n\n**Failure mode:** fail-open, same as other hooks. Any error (missing\nshim, malformed payload, Python unavailable) exits 0 silently.\n\n**Example WARNING:**\n\n```\nWARNING: lodash@4.17.10 in lock file has GHSA-29mw-wpgm-hmr9, GHSA-35jh-r3h4-6jhm\n```\n\nThe four modes above only fire for **root-session** tool calls. When the root\nsession dispatches a subagent (via the `Agent`\n\ntool — many skills and slash\ncommands do this internally), the subagent's Write/Edit/Bash calls bypass all\nof them. Post-Agent Mode is the reactive safety net for that gap.\n\n- A\n`PreToolUse:Agent`\n\nhook (`safer-dependencies-pretooluse-agent.sh`\n\n) runs immediately before each Agent dispatch and touches a sentinel file at`/tmp/.safer-deps-agent-<PPID>-<session_id>.sentinel`\n\n(falling back to a PPID-only name when no session id is available) - The subagent runs and may write manifests or lockfiles\n- A\n`PostToolUse:Agent`\n\nhook (`safer-dependencies-posttooluse-agent.sh`\n\n) runs after the Agent call returns,`find`\n\ns every manifest and lockfile newer than the sentinel, and audits each via the same shim path - Findings surface as\n`additionalContext`\n\nto the root session's next turn; the sentinel is removed\n\nNested subagents are covered automatically — the root's `PostToolUse:Agent`\n\nfires only after all of the outer agent's work (including anything *it*\ndispatched) is on disk. The one gap is a global install that writes no\nmanifest or lockfile (`npm install -g …`\n\n): there is nothing to scan. Like the\nother hooks, it fails open — any error (missing sentinel, missing shim,\nunreadable payload) exits 0 silently. Full design rationale lives in\n`skills/safer-dependencies.md`\n\n.\n\nThe skill fires automatically when Claude:\n\n**Manifest / install operations**\n\n- Adds or updates a package in\n`package.json`\n\n,`requirements.txt`\n\n,`Gemfile`\n\n,`pom.xml`\n\n,`build.gradle`\n\n,`Cargo.toml`\n\n,`go.mod`\n\n, or any other supported manifest - Writes an\n`import`\n\n,`require`\n\n, or`use`\n\nfor a package not already declared in the manifest - Generates or updates a lock file (checks only new/changed entries)\n- Runs a package-manager install via Bash (\n`npm install`\n\n,`bundle install`\n\n,`poetry install`\n\n,`uv sync`\n\n,`go mod tidy`\n\n, etc.) — Pre-Install audits the command args, Post-Install audits the resulting lockfile - Writes a\n`Dockerfile`\n\nor CI workflow (`.github/workflows/*.yml`\n\n, etc.) that embeds pinned package-manager install steps\n\n**Selection & recommendation questions**\n\n- Library/framework comparisons: \"should I use axios or node-fetch?\", \"moment vs dayjs?\", \"which is better X or Y?\"\n- Recommendation requests: \"what's a good HTTP client for Python?\", \"recommend a logging library for Go\", \"what package handles CSV in Node?\"\n- Version selection: \"what version of Django should I use?\", \"latest stable Flask?\"\n\n**Intent-to-use expressions (pre-add)**\n\n- \"I want to use FastAPI for this\", \"I'm thinking of adding Celery\", \"we're looking at Prisma as the ORM\", \"let's use Tailwind\"\n\n**Package health and trust questions**\n\n- \"Is moment.js still maintained?\", \"is this gem still active?\", \"is X abandoned?\", \"is X EOL?\", \"can I trust this package?\", \"when was faker last updated?\"\n\n**Scaffolding commands**\n\n`npx create-react-app`\n\n,`npm create vite@latest`\n\n,`django-admin startproject`\n\n,`rails new`\n\n,`cargo new`\n\n+`cargo add`\n\n, \"bootstrap a new FastAPI project\"\n\n**Implicit package adds (feature requests that imply a new dependency)**\n\n- \"Add Redis caching to the app\", \"connect to Postgres\", \"add JWT auth\", \"write code to send emails\" — fires when no package for that capability is already in the manifest\n\n**Migration and porting**\n\n- \"Migrate from requests to httpx\", \"move from CRA to Vite\", \"port from moment to date-fns\" — audits the incoming package\n\nIt does **not** fire for:\n\n- Standard library imports (\n`os`\n\n,`fs`\n\n,`java.util.*`\n\n, etc.) - Already-declared dependencies that aren't being changed\n- Academic discussion of how a package works internally (\"explain React's reconciler\", \"how does webpack's module resolution work?\") — comparison and selection questions do still fire\n- Installing OS-level apps, runtimes, or IDE extensions (Python itself, Docker, Homebrew, VS Code extensions)\n\nThis is a **skill + hook bundle**, not a single skill file. A complete install deploys these pieces:\n\n| File | Role |\n|---|---|\n`skills/safer-dependencies.md` |\nThe skill (`SKILL.md` once installed). Describes audit procedures and includes management mode for installation/stats. |\n`skills/safer-dependencies-shim.sh` |\n`PostToolUse:Write` /`Edit` hook — audits manifest + lockfile writes and auto-corrects vulnerable versions in place (Intercept Mode). |\n`skills/safer-dependencies-pretooluse-bash.sh` |\n`PreToolUse:Bash` hook — pre-flight OSV audit of package-manager install commands; denies vulnerable concrete pins before the install runs (Pre-Install Mode). |\n`skills/safer-dependencies-posttooluse-bash.sh` |\n`PostToolUse:Bash` hook — post-flight audit after Bash commands; catches transitive CVEs in freshly-written lockfiles, manifests edited via `sed` /`jq` /scripts, and the resolved environment of plain `pip install` (Post-Install Mode). |\n`skills/safer-dependencies-pretooluse-agent.sh` + `skills/safer-dependencies-posttooluse-agent.sh` |\n`PreToolUse:Agent` + `PostToolUse:Agent` hook pair — closes the subagent coverage gap. Modes 2–4 only fire for root-session tool calls, so any manifest a subagent writes bypasses them. Post-Agent audits whatever the subagent wrote after each Agent tool-call returns (Post-Agent Mode). |\n`skills/scripts/` |\nShared Python library (`safedep/` ) and standalone resolver scripts used by all hooks. |\n`skills/scripts/safer_dependencies_manager.py` |\nManagement module for interactive installation, usage stats, and setup validation. |\n\nThe skill file alone is not enough — without hooks, automatic invocation depends on Claude deciding to reach for the skill. Install all five pieces for full coverage; many skills and slash commands dispatch subagents internally, so the Post-Agent pair matters even if you never explicitly spawn one. (See [FAQ.md](/robert-auger/safer-dependencies/blob/main/FAQ.md#why-a-skill-alone-is-not-sufficient) for why a skill on its own can't guarantee coverage.)\n\n| Ecosystem | Manifest | Lock file |\n|---|---|---|\n| npm | `package.json` |\n`package-lock.json` , `yarn.lock` , `pnpm-lock.yaml` |\n| PyPI | `requirements.txt` , `pyproject.toml` , `Pipfile` , `setup.py` , `setup.cfg` |\n`Pipfile.lock` , `poetry.lock` , `uv.lock` |\n| RubyGems | `Gemfile` , `*.gemspec` |\n`Gemfile.lock` |\n| Maven | `pom.xml` , `build.gradle` , `libs.versions.toml` |\n-- |\n| Go | `go.mod` |\n`go.sum` |\n| Rust | `Cargo.toml` |\n`Cargo.lock` |\n| PHP (Composer) | `composer.json` |\n`composer.lock` |\n\nNew to the project? Start with ** GETTING-STARTED.md**. The short version:\n\n```\ngit clone https://github.com/robert-auger/safer-dependencies /tmp/safer-dependencies\npython3 /tmp/safer-dependencies/skills/scripts/safer_dependencies_manager.py interactive_install\n```\n\nThe installer prompts for scope (global vs project) and which hooks to enable, then writes `settings.json`\n\nfor you — both the hook entries **and** the permissions allowlist that lets the skill's check commands run without an approval prompt on every audit.\n\nEverything else install-related lives in ** INSTALLATION.md**, the single reference for install mechanics: manual file-by-file installs (global and project-level), Windows specifics, Post-Agent hooks, the\n\n[permissions allowlist](/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist), verifying the setup, updating, pinning to a release tag, and uninstalling.\n\nAfter install, day-to-day management works via natural language to Claude — `install safer-dependencies`\n\n(re-run / change hooks), `show safer-dependencies stats`\n\n, `check safer-dependencies setup`\n\n— or the `/safer-dependencies`\n\nmenu. Updating is in-session too: `/safer-dependencies update`\n\napplies the latest release (`update --check`\n\nfor a dry-run, `update --rollback`\n\nto undo); see [INSTALLATION.md](/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#in-session-self-updater-safer-dependencies-update) for the trust model.\n\nPlatform note:macOS, Linux, and Windows are supported. Windows needs Git for Windows (provides bash) and Python 3 on`PATH`\n\n— no WSL required. Hands-on testing to date has focused onmacOS and Windows; Linux support is exercised by the automated CI matrix.\n\nTwo things are configurable after install:\n\n**Permissions allowlist**— pre-approves the skill's read-only check commands (the exact-form`npm audit`\n\n/`bundle audit`\n\nrules and the skill's own resolver scripts) so audits run without an approval prompt each time;`curl`\n\nis never pre-approved, and`npm view`\n\n/`pip-audit`\n\nare opt-in via the Convenience profile. The interactive installer writes the core entries for you; manual installs add the full block by hand. Full block and rationale:[INSTALLATION.md → Permissions allowlist](/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist).**Security policy**— the release-age cooldown window/mode and a per-check`off`\n\n/`warn`\n\n/`block`\n\ntier for every check type, edited with`/safer-dependencies config`\n\nand stored in`~/.config/safer-dependencies/config.toml`\n\n. Schema and tier semantics:.`skills/references/configuration.md`\n\n| Level | Meaning | Example |\n|---|---|---|\n| CRITICAL | Stop and ask user | Typosquat detected, tampered signature |\n| HIGH | Warn and proceed | Known CVE, package < 30 days old |\n| MEDIUM | Warn and proceed | Version < 7 days old, missing signature |\n| LOW | Warn and proceed | Unsigned Ruby gem (expected) |\n\nEvery check is logged to `~/.claude/safer-dependencies-audit-YYYY-MM.log`\n\n(one file per calendar month, where `YYYY-MM`\n\nis the UTC year-month) as a single JSON line. Override the full path with the `SAFE_DEP_AUDIT_LOG`\n\nenvironment variable (when set, the date suffix is not appended). Files are also size-rotated when they exceed `SAFE_DEP_LOG_MAX_BYTES`\n\n(default 10 MiB; set to `0`\n\nto disable). Set `SAFE_DEP_MODEL`\n\nto override the model value written to `source.model`\n\nin each entry — useful for A/B comparisons between model versions.\n\nAll five modes append to the same file. Each entry carries a ** source block** (schema 2.2) identifying which component wrote it:\n\n`source.component` |\nWritten by | Trigger |\n|---|---|---|\n`shim.posttooluse` |\n`shim.sh` |\nManifest or lockfile write (Intercept Mode, Post-Install dispatch) |\n`shim.install_error` |\n`shim.sh` |\nShim preflight install failure |\n`bash.pretooluse` |\n`pretooluse-bash.sh` |\nBash install command (Pre-Install Mode) |\n`bash.posttooluse` |\n`posttooluse-bash.sh` |\nPost-Install Bash hook itself, when it fail-opens before reaching the shim |\n`agent.pretooluse` |\n`pretooluse-agent.sh` |\nReserved for Pre-Agent fail-open events (the hook itself is currently silent on success) |\n`agent.posttooluse` |\n`posttooluse-agent.sh` |\nPost-Agent hook fail-open events (e.g. shim missing, python_missing) |\n`manual.skill` |\nClaude running Normal Mode | Manual audit invoked inline |\n\n`source.model`\n\nrecords the Claude Code model active in the session (e.g. `\"claude-sonnet-4-6\"`\n\n). Present in schema 2.1+; entries written by older installs omit the field. The stats command degrades gracefully to `\"unknown\"`\n\nwhen it is absent.\n\nFilter by `source.component`\n\nwith `jq`\n\n:\n\n```\njq -r '.source.component' audit.log | sort | uniq -c | sort -rn\njq -c 'select(.source.component == \"bash.pretooluse\")' audit.log\n\n# Surface every silent fail-open across all hooks:\njq -c 'select(.source.mode == \"fail_open\") | {component: .source.component, reason: .fail_open.reason, ts}' audit.log\n```\n\nFor easier analysis, ask Claude for usage statistics instead of parsing logs manually:\n\n```\n\"Show safer-dependencies stats for the last month\"\n```\n\nThis provides human-readable summaries of activity, security impact, and performance metrics extracted from these audit logs.\n\n**Entry shapes (schema 2.2).** Three distinct shapes share the same `ts`\n\n/ `schema`\n\n/ `source`\n\nheader:\n\n| Shape | When it's written | Distinguishing fields |\n|---|---|---|\nAudit entry |\nManifest / lockfile / bash-install audit | `file` , `ecosystem` , `checked` , `findings` , `abandoned` , `stale` , `typosquat` , `unknown` , `signatures` , `notes` , `clean` |\nInstall-error entry |\nShim preflight install-error (component `shim.install_error` ) |\n`install_error` , `shim_dir` , `scripts_dir` |\nFail-open entry |\nAny hook entry-point exits early because of `helper_missing` / `shim_missing` / `python_missing` . `source.mode` is `\"fail_open\"` |\n`fail_open: { reason, detail? }` |\n\nAudit entries: Intercept Mode runs the full pipeline (provenance, version age, OSV, abandoned/stale, typosquat, signatures), so all arrays can populate. Pre-Install Mode runs OSV only today, so `abandoned`\n\n/ `stale`\n\n/ `typosquat`\n\n/ `signatures`\n\nare always empty. Post-Install dispatch (lockfile audit) writes under `shim.posttooluse`\n\nwith `findings`\n\npopulated by `WARNING:`\n\nstrings from the lockfile auditors. The `notes`\n\narray carries informational `NOTE:`\n\nsignals (e.g. manifest-skipped-because-unpinned).\n\nSchema 2.2 added — additively — four fields to **lockfile** audit entries: `lockfile`\n\n, `manifest_ref`\n\n, `relation_summary`\n\n(a direct/transitive/unknown classification of each flagged package against the sibling manifest), and a `policy`\n\nblock recording the `transitive`\n\ntier in force. The bump is backward-compatible: readers of 2.1 entries tolerate the new fields, and the `source.model`\n\nfield remains present from 2.1 onward.\n\n```\n{\n  \"ts\": \"2026-04-19T12:34:56Z\",\n  \"schema\": \"2.2\",\n  \"source\": {\n    \"component\": \"shim.posttooluse\",\n    \"script\": \"shim.sh\",\n    \"hook\": \"PostToolUse:Write\",\n    \"tool\": \"Write\",\n    \"mode\": \"intercept\",\n    \"model\": \"claude-sonnet-4-6\"\n  },\n  \"file\": \"/path/to/project/package.json\",\n  \"ecosystem\": \"npm\",\n  \"checked\": [\"express@4.18.2\", \"lodash@4.17.21\"],\n  \"findings\": [\"UPDATED: express 4.18.2 → 4.22.1 (HIGH: 1 CVE fixed)\"],\n  \"abandoned\": [],\n  \"stale\": [],\n  \"typosquat\": [],\n  \"unknown\": [],\n  \"signatures\": [],\n  \"notes\": [],\n  \"clean\": [\"lodash@4.17.21\"]\n}\n```\n\nPre-Install Mode example (Bash hook, vulnerable pin denied):\n\n```\n{\n  \"ts\": \"2026-04-23T06:56:21Z\",\n  \"schema\": \"2.2\",\n  \"source\": {\n    \"component\": \"bash.pretooluse\",\n    \"script\": \"pretooluse-bash.sh\",\n    \"hook\": \"PreToolUse:Bash\",\n    \"tool\": \"Bash\",\n    \"mode\": \"intercept\",\n    \"model\": \"claude-sonnet-4-6\"\n  },\n  \"file\": \"bash:npm install lodash@4.17.20 ms@2.1.3\",\n  \"ecosystem\": \"npm\",\n  \"checked\": [\"lodash@4.17.20\", \"ms@2.1.3\"],\n  \"findings\": [\n    \"BLOCKED: lodash@4.17.20 GHSA-35jh-r3h4-6jhm (CVSS:3.1/...): Command Injection in lodash\"\n  ],\n  \"abandoned\": [],\n  \"stale\": [],\n  \"typosquat\": [],\n  \"unknown\": [],\n  \"signatures\": [],\n  \"notes\": [],\n  \"clean\": [\"ms@2.1.3\"]\n}\n```\n\nFail-open Mode example (Post-Install Bash hook called with no shim adjacent — broken install):\n\n```\n{\n  \"ts\": \"2026-05-03T07:14:11Z\",\n  \"schema\": \"2.2\",\n  \"source\": {\n    \"component\": \"bash.posttooluse\",\n    \"script\": \"safer-dependencies-posttooluse-bash.sh\",\n    \"hook\": \"PostToolUse\",\n    \"tool\": \"Bash\",\n    \"mode\": \"fail_open\",\n    \"model\": \"claude-sonnet-4-6\"\n  },\n  \"fail_open\": {\n    \"reason\": \"shim_missing\",\n    \"detail\": \"/home/alice/.claude/skills/safer-dependencies\"\n  }\n}\n```\n\nA fail-open entry says: \"this hook fired but exited early without auditing because something prerequisite was missing.\" Use the jq filter above (`select(.source.mode == \"fail_open\")`\n\n) to surface every silent loss-of-protection event in your log.\n\nWhen the shim runs in dry-run mode (`SAFE_DEP_DRY_RUN=1`\n\n), entries also include `\"mode\": \"dry_run\"`\n\nso post-hoc analysis can filter audit-only invocations.\n\n- Python 3.9+ (the hooks probe for this and fail-open on older interpreters)\n`curl`\n\n(for registry API calls and OSV vulnerability checks)- Ecosystem tools (optional, skill falls back to OSV API if missing):\n`npm`\n\nfor npm packages`pip-audit`\n\nfor Python packages`bundle`\n\nfor Ruby packages`dependency-check`\n\nfor Java packages\n\nDesign-decision rationale (why `PostToolUse`\n\ninstead of `PreToolUse`\n\n, why signatures aren't verified, why scripts and shim are duplicated, skill-loading gotchas, etc.) is documented in [ FAQ.md](/robert-auger/safer-dependencies/blob/main/FAQ.md).", "url": "https://wpnews.pro/news/safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies", "canonical_source": "https://github.com/robert-auger/safer-dependencies", "published_at": "2026-08-25 04:39:58+00:00", "updated_at": "2026-08-25 05:13:11.124551+00:00", "lang": "en", "topics": ["ai-tools", "ai-safety", "developer-tools"], "entities": ["Claude Code", "Robert Auger", "npm", "PyPI", "RubyGems", "Maven", "Go", "Rust"], "alternates": {"html": "https://wpnews.pro/news/safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies", "markdown": "https://wpnews.pro/news/safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies.md", "text": "https://wpnews.pro/news/safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies.txt", "jsonld": "https://wpnews.pro/news/safer-dependencies-is-a-security-layer-for-claude-code-that-audits-dependencies.jsonld"}}