Finds visitor-accessible attack surface in web applications using Joern, and produces forward slices (each entry point plus its downstream call tree) that a language-specific Claude triage skill analyses for vulnerabilities.
NOTE: This is 'research grade' code for experimenting with the concept in a lab environment. It should not be relied on in production. Much of the code was generated by an LLM.
Currently supported:
| Language | Frontend | Slice script | Triage skill |
|---|---|---|---|
| PHP | php2cpg |
slicers/php.sc |
php-security-analyst |
| Ruby | rubysrc2cpg |
slicers/ruby.sc |
ruby-security-analyst |
| JavaScript / TS | jssrc2cpg |
slicers/javascript.sc |
javascript-security-analyst |
| WordPress | php2cpg |
slicers/wordpress.sc |
wp-security-analyst |
| Java (source) | javasrc2cpg |
slicers/java.sc |
java-security-analyst |
| Java (bytecode) | jimple2cpg |
slicers/java.sc |
java-security-analyst |
- Joern — install so that
joernand the language frontends (php2cpg,rubysrc2cpg,jssrc2cpg,javasrc2cpg,jimple2cpg) are onPATH. Alternatively, setJOERN=to thejoernbinary and the runner will find the frontends alongside it. - Claude Code — the triage skills are Claude Code slash commands; invoke them after slicing.
- Python 3 — optional, used by
analyze_run_cost.py(cost analysis). - jq — optional, used by
run_analyst_cost.sh(cost analysis). - GNU parallel — optional, only needed for
JOBS>1batch mode.
# Single app (language auto-detected):
SOURCE_ROOT=/path/to/app PROJECT=myapp ./run_surface_slices.sh
# Force the language (skips detection):
LANGUAGE=ruby SOURCE_ROOT=/path/to/app PROJECT=myapp ./run_surface_slices.sh
# Batch: one subdir per app under APPS_DIR (default ./apps):
APPS_DIR=/path/to/apps ./run_surface_slices.sh
JOBS=4 APPS_DIR=/path/to/apps ./run_surface_slices.sh # parallel (needs GNU parallel)On success the runner prints, e.g.:
[+] Done: myapp
Analyze with: /ruby-security-analyst surface_slices/myapp/ /path/to/app
Then invoke that skill (slice-only, or pass the source root if you wish the skill to read source).
/ruby-security-analyst surface_slices/myapp/ /path/to/app
| Var | Default | Meaning |
|---|---|---|
LANGUAGE |
(auto-detect) | Force php / ruby / javascript / wordpress / java / java-source / java-bytecode (js/ts/node alias javascript; bare java resolves source-vs-bytecode by input) |
SOURCE_ROOT |
— | Single-app mode: the app source dir (or compiled-artifact dir for Java bytecode) |
PROJECT |
basename | Single-app project name (CPG key + output dir) |
APPS_DIR |
./apps |
Batch mode: dir of app subdirs |
OUTPUT_BASE |
./surface_slices |
Slice output base |
HEAP |
13g |
joern (analysis) heap |
BUILD_HEAP |
12g |
frontend (parse) heap |
DOWNLOAD_DEPS |
0 |
Ruby only: 1 adds --download-dependencies (network, slow) |
INCLUDE_REGEX |
— | Java only: restrict every pass to TypeDecls whose FQN matches (e.g. com\.mycorp\..*) — tames dependency noise on fat jars/WARs |
EXCLUDE_EXTRA |
— | Extra regex ORed onto the language-default exclude pattern (e.g. (.*/)?(lib)/.*) |
JVM_OPTS |
— | Extra JVM flags passed to both the CPG frontend and joern (e.g. -XX:+UseZGC) |
JOBS |
1 |
Parallel app count (>1 needs GNU parallel) |
surface_slices/<project>/manifest.txt:
# Language: ruby
# Skill: ruby-security-analyst
# Project: myapp
# Resolution: rubysrc2cpg (call-graph sparse; downstream may be incomplete ...)
OK [ROUTE_RAILS]: get /users -> ...UsersController.show [surface_slices/myapp/ROUTE_RAILS_get__users.txt] (3 fns)
OK [CONTROLLER]: UsersController#index -> ... [.../CONTROLLER_UsersController_index.txt] (1 fns)
UNRESOLVED [ROUTE_RAILS]: devise_for -> Devise-generated auth routes ...
# lines are header metadata; OK/UNRESOLVED lines are the entry points. Each
OK line's bracketed path is the slice file (header + entry source + downstream).
- Rails namespace resolution is best-effort.
resources/namespace-nested controllers are matched by class-name suffix / stem; ambiguous cases are tagged(fuzzy class match)/(namespace best-effort). - Ruby visibility (
private/protected) is derived from source text, since it is a runtime construct the CPG does not reliably model.
- Write
slicers/<lang>.scwith the entry-type passes (readcpg, callemit/emitUnresolved; do not redefinecpg/manifest/emitted/usedSlugs; give input predicates unique names). - Add a
casearm tolang_configin the runner (frontend, slicer, skill, exclude regex) and detection signals todetect_language. - Add a
<lang>-security-analystskill under.claude/skills/(clone an existing one; swap in the language's auth + vuln catalogues).