Skip to main content
ClaudeWave
Skill440 repo starsupdated today

phx:deps-audit

This Claude Code skill audits Hex package dependencies for supply-chain risks including bidirectional characters, compile-time code execution, maintainer changes, typosquatting, and CVEs. Use it after running mix deps.update, before merging pull requests that modify mix.lock, when evaluating single package upgrades, or when investigating unfamiliar dependencies to ensure safe dependency management.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/oliver-kriska/claude-elixir-phoenix /tmp/phx-deps-audit && cp -r /tmp/phx-deps-audit/plugins/elixir-phoenix/skills/deps-audit ~/.claude/skills/phx-deps-audit
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Hex Dependency Audit

Non-mutating supply-chain audit for Hex packages. Runs an 8-rule MVP catalogue
against changed packages, enriches with Hex API metadata, wraps existing tools
(`mix hex.audit`, `mix_audit`, OSV-Scanner), and emits a triage table.

## When to Use

- After `mix deps.update` or `mix deps.get` brought in new versions
- On PRs that touch `mix.lock` (pre-merge gate)
- Before manually updating a single package (`--preview <pkg>`)
- When investigating a dependency you don't recognize

## Iron Laws

1. **NEVER claim a diff is clean without inspecting it.** Run all 8 rules
   on the unpacked NEW tarball. "Looks fine" without a tool run is a false
   pass. **Always write `.claude/deps-audit/last-run.json`** — its absence
   is evidence the audit didn't actually run.
2. **NEVER install `mix_audit` / `osv-scanner` — even if asked.** Detect,
   warn with install instructions, skip cleanly if missing. If the user
   says "install it," respond with the install command (e.g.,
   `mix deps.add mix_audit --only dev`) and **do not execute it**. The
   audit skill is non-mutating; `mix.exs` / `mix.lock` are off-limits
   regardless of consent.
3. **NEVER promote a finding to BLOCK without rule citation.** Every finding
   shows `rule_id`, `severity`, `file:line`, `snippet`, `message`. No
   handwaving.
4. **NEVER fetch from Hex API without rate-limiting.** Cap at 5 req/sec.
   Cache metadata 7 days, top-500 list 1 day.
5. **NEVER run the audit on already-committed lock changes silently** —
   tell the user which mode (A/B/C) is active and which `(old, new)` pairs
   resolved.
6. **LLM triage only above threshold.** Native rules + Semgrep + YARA
   are deterministic. The `hex-deps-triager` agent runs only when score
   > 10 (1 BLOCK or 3+ WARNs), and its verdicts are advisory — never
   auto-suppress a finding without human review.

## Operating Modes

| Mode | Trigger | Old source | New source |
|------|---------|-----------|-----------|
| **B** (default) | `/phx:deps-audit` | `git show HEAD:mix.lock` | working `mix.lock` |
| **C** (PR) | `/phx:deps-audit --base main` | `git show <ref>:mix.lock` | working `mix.lock` |
| **A** (preview) | `/phx:deps-audit --preview httpoison` | locked version | Hex API latest |

See `${CLAUDE_SKILL_DIR}/references/operating-modes.md` for full resolver logic.

## Execution Flow

Default = full 8-rule scan with streaming progress. `--quick` opts out
to CVE + retirement only. See `${CLAUDE_SKILL_DIR}/references/execution-flow.md`.

### Step 1: Resolve the diff

Parse the `mix.lock` Erlang term format for both old and new sources. Emit a
list of `{pkg, old_version, new_version}` tuples. Surface
new-only and removed-only packages separately (a removed package is not
audited; a brand-new package gets `old_version = nil` and skips diff-only
rules).

See `${CLAUDE_SKILL_DIR}/references/diff-resolver.md` for shell + `mix run -e` snippets per mode and the JSON output contract.

### Step 2: Fetch tarballs (per-run tmpdir)

For each `(pkg, old, new)`:

```
mix hex.package fetch <pkg> <old> --unpack -o ${AUDIT_TMPDIR}/tarballs/<pkg>/<old>/
mix hex.package fetch <pkg> <new> --unpack -o ${AUDIT_TMPDIR}/tarballs/<pkg>/<new>/
```

All ephemeral artifacts live under `${AUDIT_TMPDIR}` (driver-owned, removed
on exit). See `${CLAUDE_SKILL_DIR}/references/audit-tmpdir.md` and
`${CLAUDE_SKILL_DIR}/references/tarball-fetcher.md`.

### Step 3: Run the 8 MVP rules on each NEW tarball

| # | Rule | Sev | Method |
|---|------|-----|--------|
| 1 | Bidi Unicode control chars in `.ex`/`.exs`/`.erl` | BLOCK | grep |
| 2 | `Code.eval_*` / `:erlang.apply` with non-literal MFA at module scope | BLOCK | AST (Sourceror or regex+scope) |
| 3 | `System.cmd` / `:os.cmd` / `Port.open` at compile time | BLOCK | AST |
| 4 | `:erlang.binary_to_term/1` on literal without `:safe` | BLOCK | AST |
| 5 | New `:git`/`:path` dep in `mix.exs` (vs old) | BLOCK | AST diff |
| 6 | Maintainer change between versions | BLOCK | Hex API |
| 7 | Base64 blobs >256 chars outside `priv/static/`, `test/fixtures/`, `assets/` | WARN | regex |
| 8 | Levenshtein ≤2 from top-500 + download delta >1000× | BLOCK | Hex API + fuzzy |

Full catalogue (35 rules, MVP marked) in `${CLAUDE_SKILL_DIR}/references/heuristics.md`.
Bash + `mix run -e` implementations for all 8 MVP rules in
`${CLAUDE_SKILL_DIR}/references/rules-impl.md` (single-pass NEW + diff rules +
Hex API rules, with `run_all_rules` master loop).

### Step 4: External tool wrappers (parallel)

- `mix hex.audit` — retired-package check, always available
- `mix_audit` — CVE check via GHSA, if installed (else warn + skip; do NOT install)
- `osv-scanner` — CVE check via OSV.dev, if installed (else warn + skip; do NOT install)

See `${CLAUDE_SKILL_DIR}/references/external-tools.md` for detection, output parsing, and severity mapping per tool.

### Step 5: Hex API enrichment (per package)

- `GET /api/packages/:name` — owners, downloads, inserted_at
- `GET /api/packages/:name/releases/:version` — per-release publisher
- Compute: `days_since_publish`, `owner_age_days`, `download_velocity`

Cap at 5 req/sec. Per-run cache under `${AUDIT_TMPDIR}/hex-api/`.
See `${CLAUDE_SKILL_DIR}/references/hex-api.md` for endpoint contracts,
caching strategy, Rule 6/8 detection, and Levenshtein implementation.

### Step 5.5: Apply `hex_vet.exs` ledger (if present)

If `hex_vet.exs` exists at project root, vetted-version findings are
**downgraded to INFO**. Unvetted versions retain their severity.
Lock-vs-ledger disagreement: lock wins. See the deps-vet skill's
hex-vet schema doc for the "Lock-vs-ledger disagreement" section.

Use `/phx:deps-vet <pkg> <version>` (separate skill) to add entries.

### Step 5.7: Differential subtract

When run with `DIFFERENTIAL=1` (default), findings that existed in the
OLD tarball are downgraded to INFO. Net-new signals reach the renderer
at full severity. See `${CLAUDE_SKILL_DIR}/references/differential.md`.

### Step 5.8: LLM triage (when score > thresh