cd /news/ai-tools/safer-dependencies-is-a-security-lay… · home topics ai-tools article
[ARTICLE · art-109681] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Safer-dependencies is a security layer for Claude Code that audits dependencies

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.

read20 min views1 publishedAug 25, 2026
Safer-dependencies is a security layer for Claude Code that audits dependencies
Image: Michielbdejong (auto-discovered)

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.

safer-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 for exactly what is and isn't covered.

New here?[GETTING-STARTED.md]takes you from zero to a working install in about five minutes.

Security & 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).

License (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].

Getting started— zero to installed in about five minutesWhat it doesHow it worksWhat triggers itWhat's in this repoSupported ecosystemsInstallWarning levelsAudit logRequirementsFAQ

** 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

permissions allowlist, updating, and uninstalling), see

.

INSTALLATION.mdEveryday 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?",

"check safer-dependencies setup", or

"show safer-dependencies stats".

When Claude is about to add a package to your project, safer-dependencies intercepts and runs 5 checks:

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 PyPIrequirements.txt

lines with--hash=sha256:...

pins, the declared hash is validated against PyPI's published hashes; mismatch emits a WARNINGAbandoned & stale packages-- known-abandoned packages (e.g.paperclip

,request

,pycrypto

,github.com/dgrijalva/jwt-go

) are hard-blocked immediately with a suggested replacement; packages with no stable release in 2+ years get an advisorySTALE:

warning. Hard-blocked packages are removed from the manifest and Claude will ask how to proceed; stale-only packages are left in place.

If 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

(one file per calendar month).

The skill operates in five modes (summarized below; the deepest design rationale lives in skills/safer-dependencies.md

):

When Claude is about to write an import

, add a package to a manifest, or update a lock file, the skill runs inline in your session:

  • Queries the package registry for stable versions
  • Auto-selects the newest version published 7+ days ago (deterministic -- no LLM judgment)
  • Checks for known vulnerabilities via ecosystem tools and the OSV API
  • Verifies package signatures where available
  • Emits warnings if issues are found, pins the exact version
  • Logs the result to the audit trail

The version selection is handled by standalone Python scripts bundled with the skill, not by the LLM interpreting rules. The command outputs SELECTED: <version>

and Claude uses that version exactly.

Configure .claude/settings.json

with a PostToolUse

hook to enable automatic, transparent package verification:

  • Claude writes a manifest file (e.g. package.json

) with the originally-requested version — the file lands on disk - The PostToolUse

hook fires immediately after the write completes and invokessafer-dependencies-shim.sh

  • The shim reads the file, parses declared packages, and runs all security checks (typosquat, abandoned, CVE, staleness, hash-pin)
  • If corrections are needed, the shim rewrites the manifest in place with safe versions (or removes entries that have no safe version) - The shim emits signals ( UPDATED:

,BLOCKED:

,WARNING:

,STALE:

,MAJOR-UPDATE-CONFIRM:

,REFACTOR-REQUIRED:

,REGRESSION:

,TYPOSQUAT-CONFIRM:

,VERIFY:

,CLEAN:

) viahookSpecificOutput.additionalContext

on stdout.REGRESSION:

precedes aMAJOR-UPDATE-CONFIRM:

when 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)

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

blocking design — see FAQ.md for the tradeoffs.

Example signal:

UPDATED: aiohttp 3.8.5 → 3.9.0 (HIGH: 33 CVEs fixed)

The parent agent uses these signals to identify affected code and refactor as needed.

Configure .claude/settings.json

with a PreToolUse:Bash

hook to enable pre-flight auditing of package-manager install commands. This complements (does not replace) Intercept Mode — together they form a layered defense.

  • Claude attempts a Bash tool call (e.g. npm install lodash@4.17.20

) - The PreToolUse

hook fires before the call runs and invokessafer-dependencies-pretooluse-bash.sh

  • A pure-bash early filter short-circuits non-PM commands in ~115 ms (no Python invocation), so git status

/ls

/npm test

pay negligible cost on the hot path - For recognized package-manager installs ( npm

/pnpm

/yarn

install

/i

/add

), the helper tokenizes viashlex

, extracts eachpkg@version

argument, and POSTs to OSV - Any vulnerable concrete pin → the hook returns permissionDecision: "deny"

with 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

Why this exists in addition to Intercept Mode: the post-write shim is blind to Bash. npm install lodash@4.17.20

runs to completion (and postinstall scripts execute) before any audit fires; npm install -g typosquat-pkg

writes no project manifest at all. Pre-Install Mode closes those gaps structurally.

Pre-Install Mode only sees what the user typed (pkg@version

args on the command line). It can't see the transitive tree the resolver will actually install. Post-Install Mode (below) audits the lockfile once the install completes — the two modes are complementary, not redundant.

Scope: the package-manager CLIs covered here span five ecosystems (npm/pnpm/yarn/bun/npx/deno, pip/pip3/pipx/pipenv/uv/uvx/poetry, gem/bundle, go, cargo), plus Maven via Intercept Mode (Maven dependencies are typically declared in pom.xml

/build.gradle

, not added via a CLI verb).

Known gap:the Maven CLI does support direct downloads viamvn dependency:get -Dartifact=group:art:version

andmvn dependency:copy

. 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.

Per-ecosystem syntax recognized:

PM Verbs Concrete-pin syntax
npm , pnpm , yarn , bun
install , i , add (plus yarn /pnpm dlx , bun x , yarn create )
pkg@1.2.3 , @scope/pkg@1.2.3
npx
(verbless — package is first positional) pkg@1.2.3
deno
add , install
npm:pkg@1.2.3 (npm-prefixed specs)
pip , pip3 , pipx , pipenv , uv , uvx , poetry
install (pip/pip3/pipx/pipenv) / add (uv/poetry) / verbless (uvx)
pkg==1.2.3 (extras pkg[extra]==X also handled)
gem , bundle
install (gem) / add
-v 1.2.3 , --version 1.2.3 , --version=1.2.3 (separate flag)
go
get , install
pkg@v1.2.3 (must include v prefix per Go modules)
cargo
add , install
crate@1.2.3

Range pins (npm ^4.17

, pip >=

, poetry ^

/~

, Go @latest

) and unspecified versions pass through to Intercept Mode after install — the post-write shim audits whatever the resolver picks. Auto-rewrite to a safe version is queued as a follow-up.

Failure mode: fail-open. Any error (Python missing, network blip, malformed input) exits 0 with no output, allowing bash to proceed. Intercept Mode still runs after install, so a failed pre-flight degrades gracefully to existing protection.

Example deny:

safer-dependencies pre-flight audit blocked this install.
Vulnerable pinned version(s) detected:
  - lodash@4.17.20 → GHSA-35jh-r3h4-6jhm (CVSS:7.4): Command Injection in lodash
Re-run with a patched version, or invoke the safer-dependencies skill
for a recommended pin.

Configure .claude/settings.json

with a PostToolUse:Bash

hook to enable post-flight auditing after Bash commands. It runs three independent scans against the command's cwd

, each closing a gap the other hooks can't address:

Scan A — lockfiles. After a successful install verb (npm install

,bundle install

,poetry install

,uv sync

,go mod tidy

, etc.), audits freshly-modified lockfiles (package-lock.json

,Gemfile.lock

,poetry.lock

,uv.lock

,go.sum

,yarn.lock

,pnpm-lock.yaml

,Pipfile.lock

). This closes thetransitive-CVE gap Pre-Install can't see: the user typedpkg@version

, but the resolver may have pulled in dozens of transitives no one named.Scan B — manifests. After any Bash commandnoton a read-only denylist (ls

,cat

,git status

, …), audits freshly-modified manifests. This is theonly fallback for manifest edits made viased -i

,jq

, or a script — those bypass theWrite

/Edit

tool that Intercept Mode hooks on.Scan C — resolved environment. Plainpip install

/pip install -r requirements.txt

writes 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-onlylist --format=json

and OSV-checks the full resolved environment (direct + transitive).

How a scan runs:

  • Claude runs a Bash tool call
  • The PostToolUse

hook firesafterthe command completes and invokessafer-dependencies-posttooluse-bash.sh

  • A pure-bash early filter short-circuits commands that match no scan gate in ~115 ms (same fast-path convention as Pre-Install), so ls

/git

/cat

pay negligible cost - Each scan walks cwd

withfind -maxdepth 5

(covers monorepo layouts; excludesnode_modules

,.git

,.venv

,venv

) for files modified within the last 60 s — override viaSAFE_DEP_POSTINSTALL_MTIME_WINDOW

  • For each freshly-modified file (Scan A/B), the hook forges a synthetic PostToolUse:Write

payload 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 hookSpecificOutput

JSON to the parent agent

What it catches that Pre-Install doesn't: transitive vulnerabilities. A clean-looking bundle install

can pull rack@2.2.23

(CVE-2025-27610) as a transitive of sinatra

— the user never typed rack

, so Pre-Install can't see it, but Post-Install reads the resolved Gemfile.lock

and reports the CVE.

Scope: Scan A does not rewrite resolved versions — the auto-correct contract only applies to manifests Claude wrote directly. For transitive CVEs, the fix is typically "update the direct dep that owns the transitive," which needs human judgment. Scan B does auto-correct, because it audits manifests through the same shim path as Intercept Mode. Scan A skips when the transitive

check tier is set to off

(config set checks.transitive off

).

Failure mode: fail-open, same as other hooks. Any error (missing shim, malformed payload, Python unavailable) exits 0 silently.

Example WARNING:

WARNING: lodash@4.17.10 in lock file has GHSA-29mw-wpgm-hmr9, GHSA-35jh-r3h4-6jhm

The four modes above only fire for root-session tool calls. When the root session dispatches a subagent (via the Agent

tool — many skills and slash commands do this internally), the subagent's Write/Edit/Bash calls bypass all of them. Post-Agent Mode is the reactive safety net for that gap.

  • A PreToolUse:Agent

hook (safer-dependencies-pretooluse-agent.sh

) runs immediately before each Agent dispatch and touches a sentinel file at/tmp/.safer-deps-agent-<PPID>-<session_id>.sentinel

(falling back to a PPID-only name when no session id is available) - The subagent runs and may write manifests or lockfiles

  • A PostToolUse:Agent

hook (safer-dependencies-posttooluse-agent.sh

) runs after the Agent call returns,find

s every manifest and lockfile newer than the sentinel, and audits each via the same shim path - Findings surface as additionalContext

to the root session's next turn; the sentinel is removed

Nested subagents are covered automatically — the root's PostToolUse:Agent

fires only after all of the outer agent's work (including anything it dispatched) is on disk. The one gap is a global install that writes no manifest or lockfile (npm install -g …

): there is nothing to scan. Like the other hooks, it fails open — any error (missing sentinel, missing shim, unreadable payload) exits 0 silently. Full design rationale lives in skills/safer-dependencies.md

.

The skill fires automatically when Claude:

Manifest / install operations

  • Adds or updates a package in package.json

,requirements.txt

,Gemfile

,pom.xml

,build.gradle

,Cargo.toml

,go.mod

, or any other supported manifest - Writes an import

,require

, oruse

for a package not already declared in the manifest - Generates or updates a lock file (checks only new/changed entries)

  • Runs a package-manager install via Bash ( npm install

,bundle install

,poetry install

,uv sync

,go mod tidy

, etc.) — Pre-Install audits the command args, Post-Install audits the resulting lockfile - Writes a Dockerfile

or CI workflow (.github/workflows/*.yml

, etc.) that embeds pinned package-manager install steps

Selection & recommendation questions

  • Library/framework comparisons: "should I use axios or node-fetch?", "moment vs dayjs?", "which is better X or Y?"
  • Recommendation requests: "what's a good HTTP client for Python?", "recommend a logging library for Go", "what package handles CSV in Node?"
  • Version selection: "what version of Django should I use?", "latest stable Flask?"

Intent-to-use expressions (pre-add)

  • "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"

Package health and trust questions

  • "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?"

Scaffolding commands

npx create-react-app

,npm create vite@latest

,django-admin startproject

,rails new

,cargo new

+cargo add

, "bootstrap a new FastAPI project"

Implicit package adds (feature requests that imply a new dependency)

  • "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

Migration and porting

  • "Migrate from requests to httpx", "move from CRA to Vite", "port from moment to date-fns" — audits the incoming package

It does not fire for:

  • Standard library imports ( os

,fs

,java.util.*

, etc.) - Already-declared dependencies that aren't being changed

  • 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
  • Installing OS-level apps, runtimes, or IDE extensions (Python itself, Docker, Homebrew, VS Code extensions)

This is a skill + hook bundle, not a single skill file. A complete install deploys these pieces:

File Role
skills/safer-dependencies.md
The skill (SKILL.md once installed). Describes audit procedures and includes management mode for installation/stats.
skills/safer-dependencies-shim.sh
PostToolUse:Write /Edit hook — audits manifest + lockfile writes and auto-corrects vulnerable versions in place (Intercept Mode).
skills/safer-dependencies-pretooluse-bash.sh
PreToolUse:Bash hook — pre-flight OSV audit of package-manager install commands; denies vulnerable concrete pins before the install runs (Pre-Install Mode).
skills/safer-dependencies-posttooluse-bash.sh
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).
skills/safer-dependencies-pretooluse-agent.sh + skills/safer-dependencies-posttooluse-agent.sh
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).
skills/scripts/
Shared Python library (safedep/ ) and standalone resolver scripts used by all hooks.
skills/scripts/safer_dependencies_manager.py
Management module for interactive installation, usage stats, and setup validation.

The 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 for why a skill on its own can't guarantee coverage.)

Ecosystem Manifest Lock file
npm package.json
package-lock.json , yarn.lock , pnpm-lock.yaml
PyPI requirements.txt , pyproject.toml , Pipfile , setup.py , setup.cfg
Pipfile.lock , poetry.lock , uv.lock
RubyGems Gemfile , *.gemspec
Gemfile.lock
Maven pom.xml , build.gradle , libs.versions.toml
--
Go go.mod
go.sum
Rust Cargo.toml
Cargo.lock
PHP (Composer) composer.json
composer.lock

New to the project? Start with ** GETTING-STARTED.md**. The short version:

git clone https://github.com/robert-auger/safer-dependencies /tmp/safer-dependencies
python3 /tmp/safer-dependencies/skills/scripts/safer_dependencies_manager.py interactive_install

The installer prompts for scope (global vs project) and which hooks to enable, then writes settings.json

for you — both the hook entries and the permissions allowlist that lets the skill's check commands run without an approval prompt on every audit.

Everything 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

permissions allowlist, verifying the setup, updating, pinning to a release tag, and uninstalling.

After install, day-to-day management works via natural language to Claude — install safer-dependencies

(re-run / change hooks), show safer-dependencies stats

, check safer-dependencies setup

— or the /safer-dependencies

menu. Updating is in-session too: /safer-dependencies update

applies the latest release (update --check

for a dry-run, update --rollback

to undo); see INSTALLATION.md for the trust model.

Platform note:macOS, Linux, and Windows are supported. Windows needs Git for Windows (provides bash) and Python 3 onPATH

— no WSL required. Hands-on testing to date has focused onmacOS and Windows; Linux support is exercised by the automated CI matrix.

Two things are configurable after install:

Permissions allowlist— pre-approves the skill's read-only check commands (the exact-formnpm audit

/bundle audit

rules and the skill's own resolver scripts) so audits run without an approval prompt each time;curl

is never pre-approved, andnpm view

/pip-audit

are 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.Security policy— the release-age cooldown window/mode and a per-checkoff

/warn

/block

tier for every check type, edited with/safer-dependencies config

and stored in~/.config/safer-dependencies/config.toml

. Schema and tier semantics:.skills/references/configuration.md

Level Meaning Example
CRITICAL Stop and ask user Typosquat detected, tampered signature
HIGH Warn and proceed Known CVE, package < 30 days old
MEDIUM Warn and proceed Version < 7 days old, missing signature
LOW Warn and proceed Unsigned Ruby gem (expected)

Every check is logged to ~/.claude/safer-dependencies-audit-YYYY-MM.log

(one file per calendar month, where YYYY-MM

is the UTC year-month) as a single JSON line. Override the full path with the SAFE_DEP_AUDIT_LOG

environment variable (when set, the date suffix is not appended). Files are also size-rotated when they exceed SAFE_DEP_LOG_MAX_BYTES

(default 10 MiB; set to 0

to disable). Set SAFE_DEP_MODEL

to override the model value written to source.model

in each entry — useful for A/B comparisons between model versions.

All five modes append to the same file. Each entry carries a ** source block** (schema 2.2) identifying which component wrote it:

source.component | Written by | Trigger | |---|---|---| shim.posttooluse | shim.sh | Manifest or lockfile write (Intercept Mode, Post-Install dispatch) | shim.install_error | shim.sh | Shim preflight install failure | bash.pretooluse | pretooluse-bash.sh | Bash install command (Pre-Install Mode) | bash.posttooluse | posttooluse-bash.sh | Post-Install Bash hook itself, when it fail-opens before reaching the shim | agent.pretooluse | pretooluse-agent.sh | Reserved for Pre-Agent fail-open events (the hook itself is currently silent on success) | agent.posttooluse | posttooluse-agent.sh | Post-Agent hook fail-open events (e.g. shim missing, python_missing) | manual.skill | Claude running Normal Mode | Manual audit invoked inline |

source.model

records the Claude Code model active in the session (e.g. "claude-sonnet-4-6"

). Present in schema 2.1+; entries written by older installs omit the field. The stats command degrades gracefully to "unknown"

when it is absent.

Filter by source.component

with jq

:

jq -r '.source.component' audit.log | sort | uniq -c | sort -rn
jq -c 'select(.source.component == "bash.pretooluse")' audit.log

jq -c 'select(.source.mode == "fail_open") | {component: .source.component, reason: .fail_open.reason, ts}' audit.log

For easier analysis, ask Claude for usage statistics instead of parsing logs manually:

"Show safer-dependencies stats for the last month"

This provides human-readable summaries of activity, security impact, and performance metrics extracted from these audit logs.

Entry shapes (schema 2.2). Three distinct shapes share the same ts

/ schema

/ source

header:

Shape When it's written Distinguishing fields
Audit entry
Manifest / lockfile / bash-install audit file , ecosystem , checked , findings , abandoned , stale , typosquat , unknown , signatures , notes , clean
Install-error entry
Shim preflight install-error (component shim.install_error )
install_error , shim_dir , scripts_dir
Fail-open entry
Any hook entry-point exits early because of helper_missing / shim_missing / python_missing . source.mode is "fail_open"
fail_open: { reason, detail? }

Audit 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

/ stale

/ typosquat

/ signatures

are always empty. Post-Install dispatch (lockfile audit) writes under shim.posttooluse

with findings

populated by WARNING:

strings from the lockfile auditors. The notes

array carries informational NOTE:

signals (e.g. manifest-skipped-because-unpinned).

Schema 2.2 added — additively — four fields to lockfile audit entries: lockfile

, manifest_ref

, relation_summary

(a direct/transitive/unknown classification of each flagged package against the sibling manifest), and a policy

block recording the transitive

tier in force. The bump is backward-compatible: readers of 2.1 entries tolerate the new fields, and the source.model

field remains present from 2.1 onward.

{
  "ts": "2026-04-19T12:34:56Z",
  "schema": "2.2",
  "source": {
    "component": "shim.posttooluse",
    "script": "shim.sh",
    "hook": "PostToolUse:Write",
    "tool": "Write",
    "mode": "intercept",
    "model": "claude-sonnet-4-6"
  },
  "file": "/path/to/project/package.json",
  "ecosystem": "npm",
  "checked": ["express@4.18.2", "lodash@4.17.21"],
  "findings": ["UPDATED: express 4.18.2 → 4.22.1 (HIGH: 1 CVE fixed)"],
  "abandoned": [],
  "stale": [],
  "typosquat": [],
  "unknown": [],
  "signatures": [],
  "notes": [],
  "clean": ["lodash@4.17.21"]
}

Pre-Install Mode example (Bash hook, vulnerable pin denied):

{
  "ts": "2026-04-23T06:56:21Z",
  "schema": "2.2",
  "source": {
    "component": "bash.pretooluse",
    "script": "pretooluse-bash.sh",
    "hook": "PreToolUse:Bash",
    "tool": "Bash",
    "mode": "intercept",
    "model": "claude-sonnet-4-6"
  },
  "file": "bash:npm install lodash@4.17.20 ms@2.1.3",
  "ecosystem": "npm",
  "checked": ["lodash@4.17.20", "ms@2.1.3"],
  "findings": [
    "BLOCKED: lodash@4.17.20 GHSA-35jh-r3h4-6jhm (CVSS:3.1/...): Command Injection in lodash"
  ],
  "abandoned": [],
  "stale": [],
  "typosquat": [],
  "unknown": [],
  "signatures": [],
  "notes": [],
  "clean": ["ms@2.1.3"]
}

Fail-open Mode example (Post-Install Bash hook called with no shim adjacent — broken install):

{
  "ts": "2026-05-03T07:14:11Z",
  "schema": "2.2",
  "source": {
    "component": "bash.posttooluse",
    "script": "safer-dependencies-posttooluse-bash.sh",
    "hook": "PostToolUse",
    "tool": "Bash",
    "mode": "fail_open",
    "model": "claude-sonnet-4-6"
  },
  "fail_open": {
    "reason": "shim_missing",
    "detail": "/home/alice/.claude/skills/safer-dependencies"
  }
}

A 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")

) to surface every silent loss-of-protection event in your log.

When the shim runs in dry-run mode (SAFE_DEP_DRY_RUN=1

), entries also include "mode": "dry_run"

so post-hoc analysis can filter audit-only invocations.

  • Python 3.9+ (the hooks probe for this and fail-open on older interpreters) curl

(for registry API calls and OSV vulnerability checks)- Ecosystem tools (optional, skill falls back to OSV API if missing): npm

for npm packagespip-audit

for Python packagesbundle

for Ruby packagesdependency-check

for Java packages

Design-decision rationale (why PostToolUse

instead of PreToolUse

, why signatures aren't verified, why scripts and shim are duplicated, skill- gotchas, etc.) is documented in FAQ.md.

── more in #ai-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/safer-dependencies-i…] indexed:0 read:20min 2026-08-25 ·