🌐 中文版
v0.4.0 (2026-10-28): security-left-shift release. JGD can now be inserted into GitHub Actions, GitLab CI, Jenkins, pre-commit, and Nexus repository events, with SARIF, baseline diffing, and deterministic fail gates.
v0.3.0 (2026-10-28): integration, performance, and correctness release. JGD is now callable by other code-audit agents through JSON/SARIF, MCP, and A2A; the bytecode index and dynamic probes are faster; and the deserialization verifier no longer has LLM gatekeeping or build-time false positives.
Root action.yml is a composite action:
- installs JGD on the runner;
- runs
jgd auditwith JSON/SARIF/Markdown; - writes
gate.jsonand optionalrun.json; - supports
baseline,fail-on, dynamic probes, and optional ECJ; - exposes
exit-code,sarif-file,gate-file, andrun-dir; - publishes a job summary and fails the job on policy findings.
Use it directly:
- uses: lupingQAQ/JGD@v0.4.0
with:
target: build/libs
fail-on: high
offline: "true"
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: jgd-report/report.sarifjgd serve webhook receives Nexus component/asset events, authenticates with
Bearer token and/or HMAC-SHA256, and hands .jar artifacts to the normal JGD
service:
jgd serve webhook \
--host 0.0.0.0 --port 8090 \
--token "$JGD_WEBHOOK_TOKEN" \
--secret "$JGD_WEBHOOK_SECRET" \
--nexus-base-url https://nexus.example.com/repository/maven-releases \
--profile standard --fail-on highThe receiver only downloads URLs under the configured Nexus base URL, only
accepts .jar files, enforces a 512 MB limit, and rejects local paths outside
--local-root. Details: ci/README.md.
First run establishes a baseline:
jgd audit --target build/libs --profile standard --out .jgd/baselineLater PR runs fail only on new findings:
jgd audit --target build/libs --profile standard \
--baseline .jgd/baseline/run.json --fail-on high --out jgd-reportEvery run writes gate.json with blockingIds, new, considered, and
failed so CI can render the decision without parsing prose.
- GitLab CI:
ci/gitlab-ci.yml - Jenkins:
ci/Jenkinsfile - pre-commit:
ci/pre-commitand.pre-commit-hooks.yaml - generic webhook / service:
jgd serve webhook, MCP, A2A
- CLI contract with machine-readable JSON, SARIF 2.1.0, Markdown, and stable
exit codes (
0/10/20/30/40). - MCP stdio tools:
jgd_audit,jgd_poc,jgd_get_run,jgd_list_findings,jgd_cancel. - A2A JSON-RPC/SSE: agent card,
SendMessage,SendStreamingMessage,GetTask,ListTasks,CancelTask, optional Bearer auth. - The JGD mining loop stays inside JGD. External agents supply target, authorization, profile, and budget; they consume findings/artifacts rather than reimplementing deserialization analysis.
- Deterministic verifier/terminator owns the verdict; LLM output is advisory only and cannot mark a chain confirmed.
- SQLite bytecode-as-a-database index: 828-JAR warm query 0.038s vs 2.56s legacy (~67x); cold build is incremental and skips unchanged JARs.
- Dynamic probes batch by target classpath, so a batch of bridges shares one
JVM (
DynamicProbe.probe_many). - Probe classpaths use the whole target rather than only the bridge JAR;
Spring Boot
BOOT-INF/lib/*.jarandBOOT-INF/classesare safely extracted under the run directory. - Incremental index counters now report full target coverage on warm runs.
- CC3
ChainedTransformernow uses the realTransformer[]constructor and the standardgetMethod -> invoke -> execsequence. - New
hmz:HashMap carrier defers the bridge trigger toHashMap.readObject(); build/serialization no longer count as deserialization closure. - Removed LLM acceptance gatekeeping from conductor/PoC verdicts.
- Nested fat JARs (Spring Boot
BOOT-INF/lib) are indexed recursively. - Version gates that cannot be verified return
UNVERIFIED, notAPPLICABLE. - Compression-ratio limits apply to
.classentries only, avoiding false rejects on legitimate resources.
An autonomous agent that takes any JAR directory and produces:
- Known public chains — four-state determination (PRESENT / VERSION / ASSEMBLE / FIRE)
- Novel unpublished chains — discovered via static + dynamic analysis with adversarial LLM auditing
- Weaponized PoCs — serialized payloads with RCE closure demo (benign marker file)
All decisions are internalized: JARs in → chains out, zero intermediate user input.
┌─────────────────────────────────────────────────────────────────┐
│ Input: any JAR directory Output: chains + PoCs │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Known │──▶│ Bridge │──▶│ Chain │──▶│ PoC │ │
│ │ Chains │ │ Discovery│ │ Pairing │ │ Weapon │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ signature bytecode dynamic benign │
│ + version + CHA graph + contract payload │
│ gates + dispatch synthesis + fire │
│ edges + 5 carriers test │
└─────────────────────────────────────────────────────────────────┘
These are historical research candidates, not a claim about any current target. Run
jgd auditfor target-specific evidence. A chain is onlyCHAIN_CONFIRMEDwhen the deterministic verifier observesfired_at_deser=true.
| Chain | Bridge Class | Carrier | JDK | Status |
|---|---|---|---|---|
| objlongpair-hashmap | org.apache.activemq.artemis.api.core.ObjLongPair |
HashMap rehash | 17+ | ✅ RCE_CLOSED |
HashMap.readObject() → rehash → hash(key)
→ ObjLongPair.hashCode() → Objects.hash(first, second)
→ Arrays.hashCode() → first.hashCode() [Object field — isInstance always true]
→ EqualsBean.hashCode() → beanHashCode()
→ ObjectBean.toString() → ToStringBean.toString()
→ Templates.getOutputProperties() → TemplatesImpl.newTransformer()
→ TemplatesImpl.defineClass() → payload static block → RCE
Novelty: Entry-side confirmed T1 by adversarial audit — absent from ysoserial, GadgetInspector, and all public CVE writeups. Orthogonal to all known ROME entry paradigms (direct ROME key / BAVE / HotSwappableTargetSource / XString).
| Chain | Bridge Class | Carrier | JDK |
|---|---|---|---|
| tuple1-8-hashmap | io.vavr.Tuple1…Tuple8 |
HashMap rehash | 11 |
| either$left-hashmap | io.vavr.control.Either$Left |
HashMap rehash | 11 |
| either$right-hashmap | io.vavr.control.Either$Right |
HashMap rehash | 11 |
| option$some-hashmap | io.vavr.control.Option$Some |
HashMap rehash | 11 |
| validation$valid-hashmap | io.vavr.control.Validation$Valid |
HashMap rehash | 11 |
| validation$invalid-hashmap | io.vavr.control.Validation$Invalid |
HashMap rehash | 11 |
| hasharraymappedtrie$leafsingleton | io.vavr.HashArrayMappedTrie$LeafSingleton |
HashMap rehash | 11 |
HashMap.readObject → HashMap.hash → Tuple3.hashCode
→ Tuple.hash → Objects.hashCode → _1.hashCode() [Object field, attacker-controlled]
→ EqualsBean.hashCode → beanHashCode → ObjectBean.toString
→ ToStringBean.toString → Templates.getOutputProperties
→ TemplatesImpl.newTransformer → defineClass → RCE
| Chain | Bridge Class | Carrier | JDK |
|---|---|---|---|
| composablepointcut-hashmap | ComposablePointcut |
HashMap rehash | 11 |
| methodmatchers$unionmethodmatcher | MethodMatchers$UnionMethodMatcher |
HashMap rehash | 11 |
| methodmatchers$intersectionmethodmatcher | MethodMatchers$IntersectionMethodMatcher |
HashMap rehash | 11 |
| singletontargetsource-bave | SingletonTargetSource |
BAVE toString | 11 |
| hotswappabletargetsource-bave | HotSwappableTargetSource |
BAVE toString | 11 |
| defaultintroductionadvisor-bave | DefaultIntroductionAdvisor |
BAVE toString | 11 |
| Chain | Bridge Class | Carrier | JDK |
|---|---|---|---|
| functions$formapwithdefault | com.google.common.base.Functions$ForMapWithDefault |
HashMap rehash | 11 |
| predicates$isequaltopredicate | com.google.common.base.Predicates$IsEqualToPredicate |
HashMap rehash | 11 |
| present | com.google.common.base.Present |
HashMap rehash | 11 |
| Chain | Bridge Class | Library | Carrier | JDK |
|---|---|---|---|---|
| mutableobj-bave | cn.hutool.core.lang.mutable.MutableObj |
hutool-core | BAVE | ≤11 |
| antlr4-pair-bave | org.antlr.v4.runtime.misc.Pair |
antlr4-runtime | BAVE | ≤11 |
| clojure-proxy-hashmap | clojure.inspector.proxy$…AbstractTableModel$ff19274a |
clojure | HashMap | 11 |
| jacksoninject$value-bave | JacksonInject$Value |
jackson-annotations | BAVE | 11 |
| objectidgenerator$idkey-bave | ObjectIdGenerator$IdKey |
jackson-databind | BAVE | 11 |
| tolerantmap-hashmap | org.snakeyaml.engine.v2.common.TolerantMap |
snakeyaml | HashMap | 11 |
| scala-objectref-bave | scala.runtime.ObjectRef |
scala-library | BAVE | 11 |
| Chain | Bridge Class | Library | Notes |
|---|---|---|---|
| ewah-hashmap | EWAHCompressedBitmap |
JavaEWAH (Lucene/ES) | Shallow dispatch |
| federation-hashmap* | FederationConfiguration etc. (4) |
Artemis | Config class family |
📊 Full dispatch stacks (click to expand)
ObjLongPair (T1, JDK 17):
HashMap.put → HashMap.hash
→ ObjLongPair.hashCode(ObjLongPair.java:55)
→ Objects.hash → Arrays.hashCode
→ EqualsBean.hashCode → EqualsBean.beanHashCode
→ ObjectBean.toString → ToStringBean.toString → Method.invoke
→ TemplatesImpl.defineClass → payload static block
MutableObj (T2, JDK 11):
BAVE.readObject → val.toString()
→ MutableObj.toString → value.toString()
→ ObjectBean.toString → ToStringBean.toString
→ Templates.getOutputProperties → newTransformer → defineClass → RCE
# 1) Build the known-chain DB (current builder: 115 chains / 168 version gates)
python -m jgd db build
# 2) Static audit: one-pass bytecode index + JSON/SARIF report
python -m jgd audit --target /path/to/jars --profile standard \
--format json,sarif,md --out runs/demo
# 3) Environment preflight
python -m jgd doctorThe scanner does not execute target code. Dynamic probes and PoC closure
run only through an explicit sandbox boundary; the current preferred boundary
is the dedicated Linux VM (docs/VM-SANDBOX.md). PoC and LLM advisory default
on in policy; LLM output is advisory and cannot own a terminal verdict.
# Inside the isolated Linux VM (host sandbox = the VM, not Windows)
python -m jgd audit --target /opt/app/lib --profile standard \
--dynamic --sandbox host --unsafe-host --format json,sarif,md --out runs/app
# Optional container/WSL2 routes (requires Docker or a rebooted WSL2)
python -m jgd audit --target tests/fixtures --profile standard \
--dynamic --sandbox docker --format json --out runs/labDynamic probes batch classes that share a target classpath into one JVM, so
the per-bridge JVM startup cost is paid once per classpath batch. Spring Boot
BOOT-INF/lib JARs and BOOT-INF/classes are extracted under the run for
dynamic loading; the probe classpath is the whole target, not only the bridge
JAR.
# Must run inside the isolated VM; --unsafe-host is mandatory
python -m jgd poc --target /opt/app/lib \
--bridge cn.hutool.core.bean.DynaBean \
--trigger hash --ecj /path/to/ecj.jar \
--out runs/poc --unsafe-hostSee docs/POC.md for the verified Hutool DynaBean + CC3
deserialization closure and the Honest scope caveat.
# MCP stdio (tools: jgd_audit, jgd_poc, jgd_get_run, jgd_list_findings, jgd_cancel)
python -m jgd serve mcp
# A2A v1.0-style JSON-RPC + SSE + agent card
python -m jgd serve a2a --host 127.0.0.1 --port 8080
# card: http://127.0.0.1:8080/.well-known/agent-card.jsonA2A sets Bearer auth when --token is provided. The full JGD mining loop
remains inside JGD; the adapters only start/inspect tasks.
| Entry point | Contract |
|---|---|
| CLI | jgd audit, jgd poc, jgd index, jgd db, jgd doctor, jgd serve |
| JSON | run.json with schema version, coverage, findings, errors, target fingerprint |
| SARIF | SARIF 2.1.0 for code-audit platforms |
| MCP | stdio JSON-RPC tools for audit, PoC, status, findings, cancel |
| A2A | agent card + JSON-RPC/SSE task lifecycle with optional Bearer auth |
External agents should pass a target directory, policy/profile, and budget. They should not reimplement bridge discovery, chain pairing, dynamic verification, or PoC termination; those remain deterministic JGD internals.
# Install optional RAG dependency (Python 3.10+, JDK 11 & 17 for dynamic stages)
pip install chromadb # optional, for RAG persistence
# Point at any JAR directory
python3 audit_target.py --target /path/to/jars --name "your-product"python3 tui.py # English
python3 tui.py --lang zh # 中文| Key | Action |
|---|---|
↑ ↓ / j k |
Navigate chains |
Enter |
Toggle detail view |
t |
Switch language (EN/CN) |
q |
Quit |
jgd/
├── audit_target.py # Product CLI entry point
├── tui.py # Interactive TUI
├── jgd/ # Core agent modules (25)
│ ├── verify_agent.py # Bridge discovery + ds adversarial audit (incremental checkpoint)
│ ├── chain_complete.py # Chain pairing + exhaustion proof (per-item evidence save)
│ ├── poc_gen.py # Weaponized PoC generation (heq/jackson/map-dispatch tails)
│ ├── known_chains.py # Public chain determination (SQLite-backed)
│ ├── build_chain_db.py # Chain DB builder (current: 115 chains / 168 gates)
│ ├── novel_chains.py # Novel chain auto-tiering (GLM propose + DS verify)
│ ├── matrix_agent.py # Multi-JDK probe orchestration
│ ├── bcdisasm.py # Pure-Python bytecode disassembler
│ ├── bridge_fix.py # Operand-stack symbolic execution
│ ├── chroma_store.py # RAG with corpus-scoped collections
│ ├── llm.py # Dual-model (GLM × DeepSeek)
│ ├── scope.py # Corpus fingerprint isolation
│ ├── profiler.py # Node-level performance profiling
│ ├── conductor.py # Acceptance-gated terminal verdict
│ └── ...
├── examples/
│ ├── chains.json # All discovered chains (machine-readable)
│ └── chains.md # Human-readable chain catalog
├── tests/
│ └── test_smoke.py # Third-party reproducible test suite
├── ARCHITECTURE.md # 25-module graph + design decisions
├── CHANGELOG.md # Design decision history (R6-R52)
├── SECURITY.md # Authorized-use policy + disclosure
├── CONTRIBUTING.md # Five development principles
└── LICENSE # MIT
- SQLite database with per-chain: family, source, CVE, trigger method, sink type, JDK range, conditions, jar version gates. The current builder emits 115 chains / 168 gates;
jgd db buildwrites it andjgd auditreportsdb_status/db_chainsexplicitly. - Class signature matching (jar-scoped) + JDK internal class detection
- Version gates with per-jar multi-version verdicts (APPLICABLE / BLOCKED / UNVERIFIED per jar)
- Dynamic assembly + fire verification using target's own JARs
- Static: Operand-stack symbolic execution detects receiver-bridges + argument-bridges + Map-dispatch bridges (
via=mapget) - Graph: Real-time corpus-fingerprinted call graph + CHA dispatch edges + JDK builtin sink seeds
- Dynamic: Batch-parallel JVM probes (5 carriers × field-contract synthesis × multi-JDK)
- Observability: Marker reachability + exception stack frames + marker caller-stack + MAPDISPATCH signals
- Incremental checkpoint: verify/chain/poc all support resume-on-same-fingerprint
- Adaptive scaling: dynamic probes and ds audits scale with INTERESTING candidate count
- Finder ≠ Verifier: GLM selects candidates, DeepSeek audits independently
- Deterministic verifier/terminator: terminal verdicts are owned by evidence, coverage, budget, and policy; LLM output is advisory only. The old LLM acceptance gate is removed.
- "Claims ≠ Evidence" standard: Every fix requires measurement evidence
- Chains auto-derived from mining artifacts (
derive_chains) - Field-type-aware bridge assembly (
make_bridgewith Object/interface-Proxy/Map-injection) - Tail candidates: ROME (toString/hashCode), Jackson (POJONode), CC3, heq (equals carrier)
- Complete loop until RCE closure or candidate exhaustion
- Benign payload: writes
/tmpmarker file only
- RCE_DEMO_FIRED chains with non-known-family bridges are automatically tiered
- GLM proposes tier (T1/T2/T3) + novelty rationale → DeepSeek cross-verifies (can reject/downgrade)
- Anti-forgery guard: tier results only trusted when the stage ran successfully in current run
bench/realworld.py runs the hardened CLI against an authorized corpus and
records coverage, known-chain hits, dynamic probes, and artifacts.
Lab corpus (Tomcat/Commons deps, 30 JARs, 2026-10-26):
| Mode | Duration | Classes | Bridges | Known chains | Dynamic probes |
|---|---|---|---|---|---|
| static standard | 11.9s | 3389 | 492 | 25 | 0 |
| + dynamic host (trusted lab) | 18.2s | 3389 | 492 | 25 | 10 (6 DESER_OK, 4 NOT_SERIALIZABLE) |
Commercial-product testing requires an authorized corpus; do not point this at third-party products without permission. Dynamic probes confirm class load/serialization round-trip, not a full gadget chain; full chain pairing is the next hardening step.
Recent high-star open-source products (static only, 2026-10-27; no target code executed):
| Product | JARs | Classes | Bridges | Known chains | Time |
|---|---|---|---|---|---|
| Nacos 3.2.4 | 15 | 78,393 | 15,169 | 55 | 786s |
| SkyWalking 11.0.0 | 283 | 65,346 | 16,794 | 38 | 400s |
| Jenkins 2.587 | 77 | 29,339 | 4,535 | 47 | 145s |
| RocketMQ 5.5.1 | 160 | 31,126 | 3,575 | 43 | 108s |
| Hutool 5.8.48 | 1 | 1,663 | 172 | 25 | 7.9s |
Details, sources, and caveats: docs/REAL-WORLD-VALIDATION.md
and bench/results/. Known-chain presence is not
exploitability; bridge candidates need dynamic/contract verification.
Dynamic probes inside the Linux VM (2026-10-28):
| Product | Time | Probes | Result |
|---|---|---|---|
| Hutool 5.8.48 | 23.8s | 10 | 4 DESER_OK, incl. cn/hutool/core/bean/DynaBean |
| RocketMQ 5.5.1 | 166.6s | 10 | 8 NOT_SERIALIZABLE, 2 dependency errors |
| Jenkins 2.587 | 166.7s | 10 | 6 NOT_SERIALIZABLE, 4 NPE |
| Nacos 3.2.4 | 835.9s | 10 | 10 NOT_SERIALIZABLE (shaded ASM) |
| SkyWalking 11.0.0 | 549.3s cold / 217.6s warm | 10 | 10 DESER_OK (bundled Nacos API classes), no PoC closure |
DESER_OK is a carrier round-trip, not chain closure. VM setup: docs/VM-SANDBOX.md. Auto-PoC on the Hutool + CC3 lab classpath closed in 43.1s with coverage.poc=2; see docs/POC.md.
| Metric | Value |
|---|---|
| Static index warm query (828 synthetic JARs) | 0.04s vs 2.29s old = 57× (this host, 2026-10-26) |
| Static index cold build (828 synthetic JARs) | 17.98s one-time (5-class fixture; real corpora will differ) |
| Full pipeline (31 jars) | ~90s end-to-end (research baseline, not the hardened scanner) |
| Pair throughput | 1500+ pairs / 9 min (batch + 4 workers, research baseline) |
| PoC generation (3 chains) | 20.3s (2.7× speedup with parallel, research baseline) |
| Memory per JVM | ≤ 256 MB (-Xmx256m, 28% headroom, research baseline) |
| OOM count across full corpus | 0 (research baseline) |
The 828-JAR number is a warm-query measurement on a synthetic corpus of five-class JARs; it proves the archive-I/O/query path, not end-to-end chain mining. Cold build is reported separately and must be measured on the real commercial corpus in Plan 5.
| Claim | Status |
|---|---|
| Public chains: guaranteed detection | Yes — mechanical (finite signature set) |
| Novel chains: domain-exhaustive | Yes — within declared scope (ledger-verifiable) |
| Novel chains: universally exhaustive | No — undecidable (Rice's theorem) |
| PoC: always complete output | Yes — tail candidate loop until RCE closure |
| Carrier families: all classic triggers | Yes — toString/hashCode/equals/compare/compareTo |
- ARCHITECTURE.md — 25-module graph, design decisions
- CHANGELOG.md — 47-round evolution history (R6-R52)
- examples/chains.md — Human-readable chain catalog
- SECURITY.md — Authorized-use policy, responsible disclosure
- docs/THREAT-MODEL.md — Plan 1 static threat model
- docs/superpowers/specs/2026-10-26-jgd-autonomous-agent-hardening-design.md — hardening spec
- docs/superpowers/plans/2026-10-26-jgd-static-bytecode-index.md — Plan 1
For lawful security research, education, and authorized penetration testing only.
All PoC payloads write a benign marker file (/tmp/jgd_poc_fired) — no destructive action.
MIT — Copyright (c) 2026 JGD Contributors