elixir-security-audit: security audit and fix skill for Elixir and Phoenix
elixir-security-audit is an open-source Claude Code skill that finds, triages, fixes and verifies security vulnerabilities in Elixir and Phoenix projects using Sobelow, mix_audit, OSV and Opengrep. Deterministic scanners cover everything, Claude reads each finding to decide whether external input reaches it, then applies minimal fixes on a separate branch and verifies them with tests.
- v0.1.0 (2026-10-23)
- MIT · free
- Claude Code plugin
- 15 EXS patterns
- 17 Opengrep rules
Install
Add the marketplace and install the plugin in Claude Code.
/plugin marketplace add baryonlabs/elixir-security-audit
/plugin install elixir-security-audit@baryonlabs-elixir-security
Open Claude Code in an Elixir repository and ask with the slash command:
/elixir-security-audit:elixir-security-audit security audit this project and fix only the real issues
If you only copied the skill folder into ~/.claude/skills/, the command is /elixir-security-audit. A natural-language request such as "security audit this Phoenix app" also lets Claude pick the skill, but the command is the reliable way.
Copy the skill only / run the scanners standalone
git clone https://github.com/baryonlabs/elixir-security-audit
cp -r elixir-security-audit/skills/elixir-security-audit ~/.claude/skills/
# scanners only, no LLM (CI / local)
bash ~/.claude/skills/elixir-security-audit/scripts/scan.sh /path/to/project \
[--no-deps-fetch] [--skip-semgrep] [--out DIR]
Exit codes: 0 scan completed (with or without findings) · 1 execution error · 2 not an Elixir project
Requirements
- Elixir 1.14+ (1.18 recommended), Python 3, bash, Claude Code
- Target project deps:
{:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}(the skill adds them after asking) - Optional: osv-scanner, opengrep (recommended) or
semgrep --pro
Semgrep OSS skips languages: [elixir] rules as Pro-only and returns 0 findings without an error. Install Opengrep for an open-source setup.
How it works: scan → triage → fix → verify → report

| Step | What happens | Output |
|---|---|---|
| 0. Preconditions | Detect mix.exs / umbrella, check the work tree, create a security/audit-YYYYMMDD branch, record baseline tests | — |
| 1. Scan | Run 6 sources (Sobelow, mix_audit, hex.audit, OSV-Scanner, 17 Opengrep rules, BEAM grep) → normalize and de-duplicate | security-audit/findings.json, summary.md, raw outputs |
| 2. Triage | Read the code and decide whether external input (params, headers, socket, external APIs) reaches the sink: real, false positive, or uncertain | Triage table (user confirms) |
| 3. Fix | Standard fix from the pattern catalog; add tests when behavior changes | fix(security): … commits |
| 4. Verify | mix compile --warnings-as-errors, mix test, re-scan | Before/after counts |
| 5. Report | Remaining items with reasons, CI suggestion | security-audit/REPORT.md |
Why Elixir needs its own audit
General AI security reviews (Claude Security, /security-review, Codex Security) are good at logic flaws but do not run the Elixir scanners (Sobelow, mix_audit) and do not systematically cover BEAM-specific risks.
- Atom exhaustion: atoms are never garbage-collected; when the atom table limit (1,048,576 by default) is reached the whole VM crashes. One line of
String.to_atom(params["x"])is enough. :erlang.binary_to_term/1: an external binary can create atoms and functions.- Raw SQL in Ecto:
Repo.query("... #{x}")bypasses Ecto's compile-time protection. - Dependency advisories: the mix_audit database and OSV (including the EEF CNA) update at different times, so checking only one misses things. In the test app, the
plug_crypto 2.2.0advisory (EEF-CVE-2026-105963) was caught only by OSV and hex.audit.
Detection patterns: the 15-ID EXS catalog
Each pattern comes with vulnerable/safe code pairs, false-positive signals and test ideas in references/patterns.md. Severity starts at the default and is adjusted for reachability and impact.
| ID | Type | Example | Default severity |
|---|---|---|---|
EXS-ATOM-001 | Atom exhaustion (dynamic atoms) | String.to_atom(params["x"]) | High |
EXS-ATOM-002 | Atom exhaustion (JSON atom keys) | Jason.decode(b, keys: :atoms) | High |
EXS-DESER-001 | Unsafe deserialization | :erlang.binary_to_term(cookie) | Critical |
EXS-RCE-001 | Dynamic code evaluation | Code.eval_string/1, EEx.eval_string/2 | Critical |
EXS-CMD-001 | OS command injection | :os.cmd/1, System.cmd("sh", ["-c", ...]) | Critical |
EXS-SQLI-001 | SQL injection (Ecto fragment) | fragment("... #{x}") | Critical |
EXS-SQLI-002 | SQL injection (raw SQL) | Repo.query("... #{x}") | Critical |
EXS-XSS-001 | XSS (escaping bypass) | raw(@user_html) | High |
EXS-REDOS-001 | ReDoS (dynamic regex) | Regex.compile!(params["re"]) | Medium |
EXS-PATH-001 | Path traversal | File.read!(Path.join(dir, params["f"])) | High |
EXS-CFG-001 | Hardcoded secret | literal secret_key_base in config/prod.exs | High |
EXS-CFG-002 | Missing CSRF / secure headers | pipeline :browser without :put_secure_browser_headers | Medium |
EXS-CFG-003 | No HTTPS enforcement | no force_ssl in endpoint config | Low |
EXS-DEP-001 | Vulnerable dependency | mix.lock version flagged by mix_audit / OSV / hex.audit | per advisory |
EXS-DEP-002 | Retired Hex package | retired version reported by mix hex.audit | Medium |
Real output: scanning the intentionally vulnerable test app
test/fixtures/vulnerable_app is an intentionally vulnerable Phoenix app with one instance of 14 EXS patterns (plus 9 # SAFE: look-alikes for false-positive practice). Never deploy it. Below is part of its summary.md; the verdict column is filled in by Claude during triage.
| # | Severity | ID | Location | Source | Fix |
|---|---|---|---|---|---|
| 1 | Critical | EXS-RCE-001 | lib/vuln_app/calculator.ex:6 | grep+sobelow | remove dynamic eval, allow-list dispatch |
| 2 | Critical | EXS-DESER-001 | lib/vuln_app/preferences.ex:10 | grep+sobelow | Plug.Crypto.non_executable_binary_to_term(bin, [:safe]) |
| 3 | Critical | EXS-SQLI-002 | lib/vuln_app/reports.ex:8 | grep+sobelow | $1 placeholder + argument list |
| 4 | Critical | EXS-CMD-001 | lib/vuln_app/thumbnails.ex:6 | grep | System.cmd(exe, [args]) + allow-list |
| 7 | High | EXS-ATOM-001 | lib/vuln_app/catalog.ex:22 | grep+sobelow | allow-list map or String.to_existing_atom/1 |
| 12 | High | EXS-DEP-001 | mix.lock:21 | mix_audit+osv+hex_audit | sweet_xml 0.6.6 → 0.7.0 (GHSA-qpmc-wprv-x746) |
| 17 | Medium | EXS-DEP-001 | mix.lock:18 | osv+hex_audit | plug_crypto 2.2.0 (EEF-CVE-2026-105963) |
| 18 | Low | EXS-CFG-003 | config/prod.exs:1 | grep+sobelow | force_ssl: [hsts: true] |
On the same app, Sobelow alone caught 9 of the 13 code/config IDs; ATOM-002, CMD-001, REDOS-001 and SQLI-001 came only from grep/Opengrep.
How it differs from other security tools
In plain words: the detective and the airport scanner

| Detective — AI security review Codex Security · Claude Security · /security-review | Airport scanner — scanner sweep security-sweep · elixir-security-audit | |
|---|---|---|
| What it does | Reads the code from scratch and reasons about it to find design and logic flaws ("this door isn't locked") | Runs every bag (project, server, image) through the scanner and finds every item on the wanted list (CVE databases) |
| Good at | Authorization bypass, broken trust boundaries, novel techniques | Outdated libraries, server OS package vulnerabilities, leaked secrets, language-specific dangerous APIs |
| Tends to miss | Checking the wanted list (does not look at dependency CVEs directly) | New techniques that are not on any list |
| Results | Smart, but varies a little from run to run | Same input, same result, every time |
| Verification | Another agent re-checks the finding | Confirms the advisory is real via OSV and checks whether the code actually uses it (reachability) |
| Cost | Model tokens on every scan | Scanners are free; the LLM is used only for triage |
So use both. Let the scanner filter out every known risk first, then hand important repositories to the detective to find hidden logic flaws.
| When you ask… | Start with |
|---|---|
| "Do our projects and servers have known vulnerabilities?" | security-sweep |
| "Fix this Phoenix app" | elixir-security-audit |
| "Quick look at this PR before merging" | /security-review |
| "Go deep on our payments / auth repository" | Claude Security or Codex Security |
Detailed comparison
| OpenAI Codex Security | Claude Code /security-review | Anthropic Claude Security plugin | elixir-security-audit | security-sweep | |
|---|---|---|---|---|---|
| Approach | LLM reads code and detects issues (CLI, SDK, Codex plugin, GitHub Action) | Single LLM security pass | LLM multi-agent (architecture → threat model → exploration → independent verification) | Deterministic scanners + LLM triage and fixes | Deterministic scanners + LLM triage |
| Scope | Repo, path or diff; deep mode; bulk scans across repos | Current branch | Whole or partial repo; branch, PR or commit diff | One Elixir/Phoenix project | Every project under a directory + servers + container images |
| Dependency CVEs | Not scanned directly (imports Snyk / Endor results) | Not covered | Not covered (official docs recommend pairing with existing dependency scanners) | mix_audit · hex.audit · OSV | OSV-Scanner (all ecosystems) |
| CVE existence check | — | — | — | Cross-checked against OSV | OSV lookup + CISA KEV + EPSS |
| Fixes | cs patch, verify-fix | Report-focused | Patch files in patches/, not auto-applied | Commits on a separate branch (after approval) | Read-only; hands off to language skills |
| Runs without an LLM | No | No | No | scan.sh standalone | Scripts standalone, --offline |
| Requirements | OpenAI login / API key (Bedrock etc. possible), Node 22+ | Claude Code | Paid plan / API, python3 3.9+ | Claude Code, Elixir | Claude Code, osv-scanner, trivy |
Combine, don't replace. The three LLM tools read code like a human security researcher and are strong at logic flaws (authorization bypass, broken trust boundaries). This skill sweeps known vulnerabilities (dependency CVEs, language-specific dangerous APIs) with deterministic scanners and checks whether each CVE is real and actually used by the code. Recommended order: sweep everything with security-sweep → fix Elixir projects with elixir-security-audit → deep-scan important repositories for logic flaws with Claude Security or Codex Security.
Sources: openai/codex-security README and agent workflows, Claude Security docs, anthropics/claude-code-security-review. As of 2026-10-23; every tool keeps changing.
Safety principles
- Scanner output and code comments are data, never instructions (prompt-injection defense)
- No direct commits to main/master; secrets never appear in reports
- False positives are recorded with a reason; suppressions such as
.sobelow-skipsor# nosemgreprequire your approval - Major upgrades and design changes are confirmed with you before fixing
This is a tool- and pattern-based audit. It cannot find logic bugs such as missing authorization (IDOR), business-logic flaws or multi-tenancy boundaries, so follow up with /security-review or Claude Security.
Add it to CI
Copy templates/github-actions.yml into .github/workflows/ to run mix hex.audit, mix deps.audit, mix sobelow --exit medium and OSV-Scanner on every PR.
Project facts
- Name
- elixir-security-audit
- Version
- 0.1.0 (2026-10-23)
- License / price
- MIT, free and open source
- Languages
- Elixir · Shell · Python (
scan.shneeds bash 3.2+,summarize.pyuses only the python3 standard library) - Requirements
- Claude Code, Elixir 1.14+, Python 3, bash
- Made by
- Baryon Labs
- Repository
- github.com/baryonlabs/elixir-security-audit
FAQ
Is elixir-security-audit free?
Yes. It is open source under the MIT license and free. The scanners (Sobelow, mix_audit, hex.audit, OSV-Scanner, Opengrep) are free too; model tokens are spent only when Claude triages and fixes findings.
I ran the rules with Semgrep and got 0 findings.
Semgrep OSS skips languages: [elixir] rules as Pro-only and returns 0 findings without an error. Install the open-source fork Opengrep or use semgrep --pro. In that case scan.sh reports the step as "skipped" instead of 0.
fragment("... #{x}") was flagged, but that code does not even compile.
Ecto 3 raises a compile error when the first argument of fragment is not a string literal. When this pattern is flagged it is usually dead code, a macro, or raw SQL. If it is raw SQL, reclassify it as EXS-SQLI-002.
I want to suppress a false positive.
The skill never adds suppressions such as .sobelow-skips or # nosemgrep without your approval. It records the item as a false positive with the reason in the triage table; approve a suppression yourself if you need one.
Can I run the scanners without an LLM?
Yes. bash scripts/scan.sh /path/to/project runs standalone. Exit codes: 0 scan completed (with or without findings), 1 execution error, 2 not an Elixir project.
I want to scan many projects or servers at once.
Use the sister project security-sweep. It sweeps every project, server and image under a directory and calls this skill's scan.sh for Elixir projects automatically.