[workspace]

[package]
name = "droidsaw"
version = "2.0.0"
exclude = ["/tests", "/orchestration", "/fuzz"]
edition = "2024"
rust-version = "1.93"
license = "BSD-3-Clause"
description = "DROIDSAW — unified Android reverse engineering CLI. Hermes, DEX, APK signing. JSON output, MCP server. Bytecode is not a security layer."
repository = "https://github.com/droidsaw/droidsaw"
homepage = "https://droidsaw.com"
keywords = ["android", "reverse-engineering", "decompiler", "security", "bytecode"]
categories = ["command-line-utilities", "development-tools"]

[[bin]]
name = "droidsaw"
path = "src/main.rs"

[[bin]]
name = "droidsaw-mcp"
path = "src/bin/mcp.rs"
required-features = ["mcp"]

[features]
default = []
mcp = [
    "dep:rmcp",
    "dep:tokio",
    "dep:tracing",
    "dep:tracing-subscriber",
    "dep:tokio-util",
    "dep:libc",
]

[dependencies]
droidsaw-hermes = "2"
droidsaw-dex = "2"
droidsaw-apk = "2"
droidsaw-common = "2"
droidsaw-cli-contract = "2"
anyhow = "1"
thiserror = "2"
clap = { version = "4", features = ["derive", "env"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
regex = "1"
# Already a transitive dep via regex-automata etc.; adding direct so the
# CHA dispatch tables (ClassAnalysis supertypes) can use FxHashMap,
# whose lookup is ~3× faster than BTreeMap<String, _> on this workload.
# FxHash is only used for integer-keyed and VarId-keyed maps where keys
# are internally-minted (bijective hash → uncraftable collisions). For
# String-keyed maps with attacker-controlled keys (DEX class descriptors,
# method names) we use ahash which is randomized + DoS-resistant.
rustc-hash = "2"
ahash = "0.8"
# Data-parallel extraction. Per-class decompile is a pure function of
# (&dex, &data, &cd, &ttm, &enum_inlines) writing to disjoint output
# files — embarrassingly parallel. rayon gives us work-stealing over
# the thread pool without the lifetime/ownership dance of manual
# std::thread::scope.
rayon = "1"
rusqlite = { version = "0.39", features = ["bundled"] }
# threat-model: timestamps on `AcquisitionMetadata` (RFC-3339 ↔ DateTime<Utc>)
# and `schema_meta.applied_at`. `serde` for the type round-trip; `clock` so we
# can stamp migration timestamps with `Utc::now()`.
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"] }
# threat-model: SHA-256 over canonical NDJSON evidence envelopes.
# Workspace-pinned 0.11 (matches droidsaw-apk).
sha2 = "0.11.0"
self_cell = "1"
walkdir = "2"
zip = "2"

rmcp = { version = "1.3", features = ["server", "transport-io"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
tracing = { version = "0.1", optional = true }
tracing-subscriber = { version = "0.3", optional = true }
# CancellationToken used by DroidsawServer for per-tool cancellation
# (mcp/mod.rs + mcp/subprocess.rs). Optional so non-mcp builds don't
# pull it in.
tokio-util = { version = "0.7", optional = true }
# SIGTERM/SIGKILL discipline for subprocess timeout wrapper.
# Transitive dep via tokio on Unix; pinned directly so the mcp-concurrency
# layer can use libc::kill without `unsafe { *(tokio internals) }`.
libc = { version = "0.2", optional = true }
# Temp file used unconditionally:
# (a) mcp subprocess stderr capture (subprocess.rs);
# (b) trufflehog runner strings-dump output (trufflehog/run.rs +
#     mcp/mod.rs). Both code paths produce + delete a per-invocation
#     /tmp file via NamedTempFile to avoid /tmp leaks.
tempfile = "3"

[dev-dependencies]
# tempfile is in [dependencies] as an mcp-feature-optional dep for subprocess.rs;
# listed here too so non-mcp test builds resolve it from dev-deps.
tempfile = "3"
tokio = { version = "1", features = ["full"] }
# Pull in droidsaw-apk under the `test-support` feature so integration
# tests in this crate can construct a zero-init `Apk` via
# `Apk::empty_for_testing()` without driving the parser. Production
# code never enables this feature.
droidsaw-apk = { version = "2", features = ["test-support"] }
# CycloneDX 1.5 spec validation gate for the corpus-sbom harness.
# Re-parses the sbom command's JSON output via `Bom::parse_from_json_v1_5`
# and asserts `validate_version(SpecVersion::V1_5)` is `Passed`. Not a
# runtime dependency — production sbom emit hand-builds the JSON shape
# via serde_json and never touches this crate.
cyclonedx-bom = "0.8.1"
# SARIF 2.1.0 schema-conformance gate for the `sarif` module's
# `output_conforms_to_sarif_2_1_0_schema` test: validates `to_sarif` output
# against the vendored OASIS SARIF 2.1.0 JSON schema. Structural validation
# only — semantic SARIF rules (e.g. level/kind) aren't expressible in JSON
# Schema. Not a runtime dependency. default-features off: the vendored schema
# is self-contained, so the HTTP/file $ref resolvers are unnecessary.
jsonschema = { version = "0.46", default-features = false }

[profile.release]
# Keep line tables so `perf record` + `samply` produce symbolicated call
# stacks instead of address soup. No runtime cost (line tables ship in
# their own ELF section), small binary-size cost (~2-5× uncompressed).
# Temporary; revert if we ever ship stripped binaries.
debug = "line-tables-only"

# Local path overrides for the sibling-repo inter-crate deps. Cargo strips
# this section from the published manifest, so it has no effect on what
# crates.io sees — it's purely a dev-loop redirect so `cargo build` here
# uses the in-tree sibling crates instead of the registry.
[patch.crates-io]
droidsaw-common       = { path = "../droidsaw-common" }
droidsaw-cli-contract = { path = "../droidsaw-cli-contract" }
droidsaw-apk          = { path = "../droidsaw-apk" }
droidsaw-dex          = { path = "../droidsaw-dex" }
droidsaw-hermes       = { path = "../droidsaw-hermes" }
