Contributing
This guide covers building, testing, and extending Disrobe, including the evidence required for recovery claims.
Building and testing
Use the Rust toolchain pinned in rust-toolchain.toml; rustup selects it automatically in the checkout:
cargo build --release
cargo test -p <crate> --features <the crate's test features>
Building the Python bindings requires Python on PATH; set PYO3_PYTHON to select a specific interpreter.
With lefthook installed, run cargo xtask setup-hooks once to install the Git hooks. The pre-push
hook checks changed crates with cargo xtask prepush; use cargo xtask prepush --full to check the
whole workspace.
The JVM decompiler can be memory-intensive on adversarial input. Prefer per-crate test runs over a bare workspace-wide
cargo test --workspacewhen iterating locally.
The pre-push hook batches changed crates under cargo-nextest 0.9.115 or newer. Install it with
cargo install cargo-nextest --locked. The pre-push profile runs independent tests concurrently,
allows at most two external-toolchain tests at once, disables retries, terminates a test after ten
minutes for measured heavy crates and two minutes otherwise, and fails when a child retains the
test's output handles for more than two seconds. Because nextest does not run doctests, the hook
follows it with one scoped cargo test --doc invocation over the same crates.
Push CI also runs the fixed grader allowlist in xtask/data/push_graders.toml on Ubuntu, with a
30-minute job limit. Its 11 targets each cite a reference test and a mutation control; the manifest
names six ecosystems without an eligible grader and gives a reason for each. Run
cargo run --locked -p xtask --no-default-features -- push-graders check to validate the manifest
and citations. Run
cargo run --locked -p xtask --no-default-features -- push-graders run to list and execute every
selected test locally. It fails if any test is skipped or the listed, run, and passed counts differ.
Name the features a crate hides its tests behind
Some crates keep test-bearing modules behind a non-default feature. A per-crate run must name the feature that exposes its selected test target:
cargo test -p disrobe-cli --no-default-features --features auto --test it -- auto_dalvik_feature_gate::
cargo test -p disrobe-pass-mobile --features native-image --test flutter_engine_fallback_identity
cargo test -p disrobe-nir-lift --features as3,beam,dotnet,jvm,lua,python,ruby
cargo test -p disrobe-pass-py-disasm --features alt-runtimes-native
The CLI form covers a refusal that exists only when auto is enabled and jvm is disabled.
The Flutter engine identity test requires native-image for ELF parsing.
cargo run -p xtask -- health enforces this. It reads every crate's default feature set, finds
every test-bearing file the default set removes, and fails when a crate hides tests that no entry in
HIDDEN_TEST_SURFACE in xtask/src/feature_gated_tests.rs declares. It also reads every per-crate
test command written in the README, in docs/src, and in the workflows, and fails when one of them
names a test target the command's own feature set compiles away, or names no feature for a crate
that hides tests. The failure names the crate and prints the command to use instead.
The quality bar
Every commit on main must pass the workspace clippy gate with zero warnings:
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt -p <crate> -- --check
The workspace lints are strict by design: unwrap_used is denied, expect_used is treated as a defect in production paths, and todo! and unimplemented! are denied. New code is fully type-annotated and self-documenting; the codebase carries durable context in dedicated docs rather than inline comments. Unsafe code is restricted to audited boundary code and does not belong in ordinary parsing or recovery paths.
README graphs
The dark-theme benchmark and architecture SVGs in the README are generated, not drawn by hand. The data lives in xtask/data/*.json (every plotted value cites its source gate or harness inline), and xtask renders deterministic SVGs into docs/assets/:
cargo run -p xtask -- graphs # regenerate docs/assets/*.svg
cargo run -p xtask -- graphs --check # fail if committed SVGs are stale (CI runs this)
After changing a number in xtask/data/, rerun graphs and commit the regenerated SVGs; the graphs CI job rejects any drift. Numbers must identify their committed test gate or local measurement harness, comparison rule, and denominator. Distinguish independent comparisons from self-reported coverage; no competitor recovery percentage is plotted.
Docs and the wiki
These pages under docs/src are the single source of truth. The GitHub wiki is generated from them by scripts/wiki_sync.py and the wiki-sync workflow, which runs on every push to main that touches docs/. Do not edit the wiki directly; it is overwritten on the next sync. Edit the page here, then preview the generated wiki locally:
python scripts/wiki_sync.py --out ./.wiki-build # build the wiki tree
python scripts/wiki_sync.py --check --out ./.wiki-build # fail on drift
Adding a pass
A new ecosystem pass is a new disrobe-pass-<name> crate that:
- Implements the shared
Passtrait with an ID, detector, output kind, and artifact transformation. - Uses the IR representation appropriate to its output; raw-to-raw extraction is a valid transition.
- Ships a
pass_run_envelope_roundtriptest and at least one real-fixture integration test incrates/disrobe-cli/tests/. - Wires its standardized emits, returning explicit
applicable: falsestubs for emits it cannot produce.
Every capability claim must be backed by a fixture in corpus/ and a passing test; nothing aspirational ships as done. Fixtures are baked locally from known-good inputs by corpus/generate.{sh,ps1}; copyrighted third-party obfuscated bytecode is never committed to the public corpus.
Recovery evidence
Test recovery against independently produced corpus artifacts and the upstream format specification. Hand-built fixtures should cover boundaries and failure cases as well. Record the input versions, recovered members, and remaining losses; distinguish detection from recovery in the command output and documentation.
Contributor license
Keep pull requests focused. Write each commit subject as a lowercase Conventional Commit with a
module scope, such as fix(pass-lua): bound constant pool reads, and use a GitHub noreply address
if you want to keep your email private. Preserve existing legal attribution; do not add unrelated
authorship or co-author credits.
Contributions are accepted only under the Disrobe Contributor Assignment and License Agreement, which implements Section 8 of the LICENSE. Before a pull request can be merged, post the complete acceptance statement from that agreement as a comment on the pull request, from the account that authored the commits it covers. Opening a pull request grants no rights in Disrobe beyond the LICENSE.
Reporting bugs
Generate an environment report to attach to an issue:
disrobe bug-report --out report.md
disrobe bug-report --out - # write to stdout
For security issues, do not open a public issue; use the private advisory channel. See Security.