Skip to content

Repository files navigation

vending-machine — multi-language surface slicer

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

Prerequisites

  • Joern — install so that joern and the language frontends (php2cpg, rubysrc2cpg, jssrc2cpg, javasrc2cpg, jimple2cpg) are on PATH. Alternatively, set JOERN= to the joern binary 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>1 batch mode.

Usage

# 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

Key environment variables

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)

Manifest format

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

Known language limitations

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

Adding a language

  1. Write slicers/<lang>.sc with the entry-type passes (read cpg, call emit/emitUnresolved; do not redefine cpg/manifest/emitted/ usedSlugs; give input predicates unique names).
  2. Add a case arm to lang_config in the runner (frontend, slicer, skill, exclude regex) and detection signals to detect_language.
  3. Add a <lang>-security-analyst skill under .claude/skills/ (clone an existing one; swap in the language's auth + vuln catalogues).

About

code slice utility used for the '0-day vending machine' research project

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages