Joern
Skill Lu1sDV/skillsmd/joern
Personal skills collection for Claude
npx -y skills add Lu1sDV/skillsmd --skill joernAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when auditing code for vulnerabilities, assessing change impact, tracing dataflow, finding callers/callees of a function, mapping a refactor's blast radius, hunting sinks across a large codebase, or needing repeatable interprocedural queries over a Code Property Graph. Triggers: "joern", "CPG", "code property graph", "blast radius", "dataflow", "taint trace", "who calls X", "callgraph", "dead code", "find sinks", "interprocedural".
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
6.8 KB, as published. Nobody here has run it
Joern Code Property Graph Analysis
Overview
Joern parses source code into a Code Property Graph (CPG) — AST + CFG + dataflow merged into one queryable graph. After a one-time build (~30s–several min depending on codebase size), all subsequent queries are interactive and avoid repeated grep/find/manual call-tracing.
Core principle: Pay the parse cost once, then ask the same codebase arbitrarily many structural / interprocedural questions instantly.
Companion to CodeQL: Joern is faster to set up and supports 14+ languages; CodeQL has stronger taint libraries for Java/JS/Python. Use joern for fast structural sweeps, CodeQL for deep canned taint suites.
When to Use
- Pre-refactor: "what breaks if I change this function?" →
blast_radius - Vuln research: "every call into
Runtime.execreachable from an HTTP handler" →reachableBy - Unknown codebase: "what are the public entry points / sinks of this module?"
- Patch diffing: build CPGs for pre/post commits, query the diff for new sinks
- When
grepis too coarse (catches comments, string literals, false matches)
When NOT to Use
- Single-file question — just
Readit - Codebase too small to justify CPG build (<20 files)
- You need a one-shot answer the LSP can give faster (
lsp_find_references)
Setup
Joern is a CLI. Install once:
# Linux
curl -L https://github.com/joernio/joern/releases/latest/download/joern-cli.zip -o /tmp/joern.zip
unzip /tmp/joern.zip -d ~/joern && export PATH="$HOME/joern/joern-cli:$PATH"
# macOS
brew install joern
Requires Java 21+.
Core Workflow
1. Build a CPG (once per codebase / commit)
# Auto-detect language
joern-parse <path-to-src> --output cpg.bin
# Force a frontend (faster, fewer surprises)
joern-parse <path-to-src> --frontend javasrc2cpg --output cpg.bin
joern-parse <path-to-src> --frontend jssrc2cpg --output cpg.bin
joern-parse <path-to-src> --frontend pysrc2cpg --output cpg.bin
joern-parse <path-to-src> --frontend gosrc2cpg --output cpg.bin
joern-parse <path-to-src> --frontend c2cpg --output cpg.bin # C/C++
Frontends: c2cpg, javasrc2cpg, jssrc2cpg, pysrc2cpg, gosrc2cpg, kotlin2cpg, rubysrc2cpg, swiftsrc2cpg, csharpsrc2cpg, php2cpg, plus binary frontends (ghidra2cpg, jimple2cpg).
2. Query interactively or via script
# REPL
joern
joern> importCpg("cpg.bin")
joern> cpg.method.name("exec").caller.name.l
# Headless one-shot
joern --script query.sc --param cpgFile=cpg.bin
3. Common queries (CPG Query Language / Scala-DSL)
// Blast radius — who transitively calls method `getCursor`?
cpg.method.name("getCursor").caller.repeat(_.caller)(_.emit.times(3)).name.dedup.l
// All sinks: any call to Runtime.exec/ProcessBuilder
cpg.call.methodFullName("(?i).*(Runtime.exec|ProcessBuilder).*").l
// Dataflow: from any HTTP param to exec()
def src = cpg.parameter.name("(?i).*(req|request|params).*")
def snk = cpg.call.methodFullName(".*exec.*")
snk.reachableByFlows(src).p
// Callers of a function with file:line
cpg.method.name("spawn_worker").caller.location.toJsonPretty
// Methods in a directory
cpg.method.filename(".*scripts/adw/decompose/.*").name.l
// Dead code candidates (no callers, not exported)
cpg.method.internal.where(_.caller.size(0)).name.l // ⚠ false positives: callbacks, framework hooks, API handlers
4. Save scripts under version control
Drop reusable queries into .joern/queries/*.sc and run headless:
joern --script .joern/queries/find_ssrf_sinks.sc --param cpgFile=cpg.bin > findings.json
Quick Reference
| Goal | Query |
|---|---|
| Find a method | cpg.method.name("X").l |
| Direct callers | cpg.method.name("X").caller.l |
| Transitive callers (depth 3) | cpg.method.name("X").caller.repeat(_.caller)(_.emit.times(3)).l |
| Callees | cpg.method.name("X").callee.l |
| All calls to a fully-qualified target | cpg.call.methodFullName(".*target.*").l |
| Param → sink dataflow | snk.reachableByFlows(src).p |
| File-scoped query | .filename(".*path/.*") |
| External (3rd-party) vs internal | .external / .internal |
| Location (file:line) | .location or .lineNumber |
CPG Hygiene
- Stale CPG is the #1 footgun. Rebuild after any meaningful source change. Tie the build to a git commit hash:
joern-parse src --output cpg-$(git rev-parse --short HEAD).bin - Large codebases:
--max-num-def 2000to cap dataflow expansion, or use--exclude-regexto skip vendored/node_modulespaths. - For monorepos: build per-module CPGs; cross-module queries take exponentially longer.
Common Mistakes
| Mistake | Fix |
|---|---|
| Querying stale CPG after edits | Rebuild. Verify with cpg.metaData.l (timestamp) before trusting results. |
| Wrong frontend → empty graph | Check cpg.method.size immediately after import; if 0 you picked the wrong language. |
Treating dead_code results as ground truth | Always cross-check: framework callbacks (Flask routes, Spring @RequestMapping, JS event handlers) look dead but aren't. |
Forgetting .l to materialize | cpg.method.name("X") returns a Steps traversal; .l evaluates to a list. |
| Dataflow returns nothing | Default reachableByFlows respects taint semantics — make sure source/sink are Call/Parameter nodes, not method nodes. |
Confusing methodFullName vs name | name = bare identifier; methodFullName = fully qualified incl. signature. Sinks usually need methodFullName. |
| Building CPG in source tree | Always output to a separate .joern/ dir; CPG files are large and shouldn't be committed. |
Project Integration
- Pre-build CPGs per target audit under
.joern/<target>/cpg.bin(excluded from git via.gitignore). - Store reusable queries in
.joern/queries/and reference from finding write-ups so reviewers can re-run. - Pair with
vuln-researchskill: joern produces structuredreachableByFlowsevidence that subagents can include inverdict.md. - Complement
codeqlskill: run joern for fast structural sweeps, escalate to CodeQL when deeper taint suite coverage is needed.
References
- Official docs: https://docs.joern.io
- CPG Query Language: https://docs.joern.io/cpgql/
- Frontend list: https://docs.joern.io/frontends
- Query database (curated examples): https://queries.joern.io