droidsaw 1.0.0

DROIDSAW — unified Android reverse engineering CLI. Hermes, DEX, APK signing. JSON output, MCP server. Bytecode is not a security layer.
Documentation
//! MCP prompts — canned analysis workflows.
//!
//! Each prompt is a template that expands into a user message
//! directing the MCP client through a specific analysis workflow.
//! Tool names match the current server surface.

use rmcp::model::{
    GetPromptResult, Prompt, PromptArgument, PromptMessage, PromptMessageRole,
};
use rmcp::ErrorData as McpError;

/// Build the static list of prompts exposed to MCP clients.
pub fn build_prompts() -> Vec<Prompt> {
    vec![
        Prompt::new(
            "get-started",
            Some("Orientation prompt for a fresh session. Explains the tool surface, naming convention, and DB query pattern."),
            None,
        ),
        Prompt::new(
            "analyze-apk",
            Some("First-pass RE analysis of an APK: load, orient by layer, run the security audit, write findings/xrefs/credentials to SQLite, highlight high-value targets."),
            Some(vec![
                PromptArgument::new("apk_path")
                    .with_description("Absolute path to the APK file to analyze")
                    .with_required(true),
            ]),
        ),
        Prompt::new(
            "trace-string-flow",
            Some("Trace how a sensitive string (API URL, token key, error message) flows through the code: find the string, cross-reference to functions, decompile callers."),
            Some(vec![
                PromptArgument::new("pattern")
                    .with_description("Regex to match the target string (e.g. 'api\\\\.example\\\\.com')")
                    .with_required(true),
            ]),
        ),
        Prompt::new(
            "credential-hunt",
            Some("Focused credential search: run the full audit (writes findings + credentials to SQLite), use query to surface verified hits, trace top hits to source with investigate."),
            None,
        ),
        Prompt::new(
            "react-native-audit",
            Some("React Native workflow: enumerate NativeModules bridge calls, map JS→Java boundary, decompile bridge functions, surface what sensitive data crosses the boundary."),
            None,
        ),
        Prompt::new(
            "compare-versions",
            Some("Diff two Hermes bundles or APKs to find what changed: new strings, added/removed functions, for regression or supply-chain analysis."),
            Some(vec![
                PromptArgument::new("baseline_path")
                    .with_description("Absolute path to the older version")
                    .with_required(true),
                PromptArgument::new("new_path")
                    .with_description("Absolute path to the newer version")
                    .with_required(true),
            ]),
        ),
        Prompt::new(
            "crypto-review",
            Some("Cryptographic signing review: schemes active (v1–v4), cert fingerprint, key algorithm, flag weak keys / SHA-1 / v1-only signing."),
            None,
        ),
        Prompt::new(
            "expert-review",
            Some("Open-ended expert review of audit findings. Identifies the most exploitable findings to investigate, then drills in using droidsaw's built-in decompilation and xrefs."),
            None,
        ),
        Prompt::new(
            "taint-analysis",
            Some("Cross-layer taint flow deep-dive (DEX interprocedural + HBC/Hermes + bridge): surface SSA source→sink paths, prioritise by severity, decompile top sinks, assess exploitability."),
            None,
        ),
    ]
}

/// Render a prompt's conversation given its name and argument map.
pub fn render_prompt(
    name: &str,
    args: &serde_json::Map<String, serde_json::Value>,
) -> Result<GetPromptResult, McpError> {
    let get_str = |key: &str| -> Option<String> {
        args.get(key).and_then(|v| v.as_str()).map(|s| s.to_string())
    };

    match name {
        "get-started" => {
            let user = "\
You are connected to the droidsaw MCP server — an Android APK static analysis tool. \
Here is everything you need to know to get started.\n\n\
## Workflow\n\
1. Call `load` with an APK/HBC/DEX path — this populates the session context.\n\
2. Call `info` to see which layers are present (HBC = React Native/Hermes, DEX = Java/Kotlin).\n\
3. Call `audit` to run the full security sweep. It writes findings, xrefs, and credentials to a SQLite DB and returns `db_path`.\n\
4. Use `query` (with `db_path` from step 3) to interrogate the DB with SQL — FTS5/BM25 is enabled on all tables.\n\
5. Use `investigate` to collapse finding → xref → decompile into one call.\n\n\
## Tool naming convention\n\
- `apk_*` — APK-level tools (manifest, signing, ELF, audit, entries, resources, YARA, SBOM)\n\
- `dex_*` — DEX/Java layer (classes, methods, decompile)\n\
- `hbc_info`, `hbc_functions`, `module_list`, `disasm` — Hermes bytecode primitives\n\
- `strings`, `xrefs`, `decompile`, `frida`, `apk_export` — cross-layer tools\n\
- `query`, `investigate` — audit DB tools (use after `audit`)\n\n\
## Audit DB tables (query all via `query` tool)\n\
- `findings` / `findings_fts` — all security findings (severity, layer, id_tag, gauge_class, detail, cwe)\n\
  - `gauge_class` values: Semantic (actionable), Cryptographic (signing facts), Representational (encoding noise)\n\
  - Filter noise: `WHERE gauge_class='Semantic'` to focus on actionable findings\n\
- `credentials` / `credentials_fts` — trufflehog hits (detector, raw, verified)\n\
- `xrefs` / `xrefs_fts` — string→function edges (layer, string_value, function_name)\n\
- `taint_flows` / `taint_flows_fts` — DEX SSA source→sink paths. Use `taint` tool for a structured view, or raw SQL via `query`.\n\
- credentials note: `verified=1` rows are confirmed live secrets; `verified=0` are unverified pattern matches. Always check `verified` before treating a hit as real.\n\
- audit response includes `taint_flow_count` — if non-zero, call `taint` immediately.\n\n\
## Suggested first move\n\
Point `load` at any APK, HBC, or DEX file. Open-source apps give interpretable results — \
Telegram (DEX/Java) and Mattermost (HBC/React Native) are good starting points. \
For React Native apps, start with the `react-native-audit` prompt after loading.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Orientation for a fresh droidsaw MCP session"))
        }

        "analyze-apk" => {
            let apk_path = get_str("apk_path").ok_or_else(|| {
                McpError::invalid_params("analyze-apk requires 'apk_path'", None)
            })?;
            let user = format!("\
Analyze `{apk_path}`.

DROIDSAW decompiles DEX → Java and Hermes → JS with high fidelity. Xrefs are bidirectional. Taint flows are SSA-verified interprocedural paths, not pattern matches. For best coverage, install semgrep and trufflehog.

Phase 1 — Orient:
Load the APK, run audit, then orient with the audit_summary and actionable_findings views. Check manifest for exported components.

Phase 2 — Investigate:
Investigate all Critical findings first, then the most distinct High findings. Use investigate, decompile, and xrefs to drill in. Check finding_urls for endpoint surface. Filter on gauge_class='Semantic' to skip noise.

Phase 3 — Expert review:
Review from these perspectives — for each, 1-2 leads with reasoning, prioritized by impact:
- Cryptographer — primitives, trust boundaries, key/nonce/session
- Reverse engineer — novel, surprising, undocumented
- Bug bounty — exploitable, reproducible, server-side impact
- Enterprise security — ProdSec, TDR, governance, CISO concerns
- Consumer advocate — data handling users should know about
- Vulnerable populations — identity/location/association leakage risk

For each lead, note static-confirmable vs needs-dynamic. Many findings are fully confirmable statically. For dynamic leads, prep with decompilation and xrefs first — understand the code path before instrumenting it. frida generates scoped hooks when needed.

Report concisely: prioritized leads with reasoning, and what to test next.");
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Full APK analysis: orient → investigate → expert review"))
        }

        "trace-string-flow" => {
            let pattern = get_str("pattern").ok_or_else(|| {
                McpError::invalid_params("trace-string-flow requires 'pattern'", None)
            })?;
            let user = format!(
                "Trace how the string matching `{pattern}` flows through the loaded file:\n\n\
                1. Call `strings` with `search={pattern:?}` to confirm it's present and see variants.\n\
                2. Call `xrefs` with the same pattern to find which functions reference it.\n\
                3. For the top 1–3 referencing functions, call `decompile` with their func_id \
                (or `decompile` for DEX classes) to see how the string is used.\n\
                4. Summarize: where does this string originate, who consumes it, and what behavior is conditioned on it?\n\n\
                If step 1 returns nothing, broaden the regex or try a substring."
            );
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("String→function→decompile tracing"))
        }

        "credential-hunt" => {
            let user = "\
I want to hunt for hardcoded credentials and secrets in the loaded APK. Please:\n\n\
Note: trufflehog `verified=1` hits are confirmed live secrets. `verified=0` hits are unverified pattern \
matches — do not treat them as real credentials without manual confirmation.\n\n\
1. Call `audit` with default params (trufflehog runs automatically if in PATH). Note the `db_path` and `trufflehog.verified_count`.\n\
2. Call `query` with: `SELECT detector,raw,extra FROM credentials WHERE verified=1` to see confirmed live secrets first.\n\
3. Call `query` with: `SELECT detector,raw FROM credentials WHERE verified=0 ORDER BY detector LIMIT 20` to review unverified pattern matches (require manual confirmation).\n\
4. Call `query` with: `SELECT rowid,detail FROM findings_fts WHERE findings_fts MATCH 'secret OR token OR key OR password OR api' ORDER BY rank LIMIT 15` to find hardcoded-credential findings.\n\
5. For any promising finding from steps 2–4, call `investigate` with its `rowid` as your first drill-down step — do not manually chain dex_classes + decompile when investigate exists.\n\
6. Summarize: verified credentials first, then unverified pattern matches worth manual review, then confirmed false positives.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Credential and secret hunt workflow"))
        }

        "react-native-audit" => {
            let user = "\
Audit the React Native bridge surface of the currently loaded app:\n\n\
1. Call `xrefs` (bridge calls) to enumerate NativeModules references and their calling JS functions.\n\
2. Call `npm_packages` to list bundled npm packages (supply chain surface).\n\
3. For each bridge function that looks sensitive (auth, payment, file I/O, crypto), \
call `decompile` with its func_id to see what arguments cross the JS→Java boundary.\n\
4. Call `decompile --search` with a regex matching the corresponding Java-side module name to locate the native implementation.\n\
5. Summarize: which native modules are reachable from JS, and which handle sensitive data? \
Flag any modules that accept unsanitized JS input as high priority for dynamic testing.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("React Native bridge audit"))
        }

        "compare-versions" => {
            let baseline = get_str("baseline_path").ok_or_else(|| {
                McpError::invalid_params("compare-versions requires 'baseline_path'", None)
            })?;
            let new_path = get_str("new_path").ok_or_else(|| {
                McpError::invalid_params("compare-versions requires 'new_path'", None)
            })?;
            let user = format!(
                "Compare two versions and report what changed:\n\n\
                1. Call `load` with `{baseline:?}` (baseline).\n\
                2. Call `diff` with path={new_path:?} to get the version delta.\n\
                3. From the added strings, pick 3–5 that look semantically meaningful \
                (API URLs, feature flags, error messages — ignore minified identifiers and hashes).\n\
                4. For each meaningful added string, call `xrefs` to see if it appeared in any \
                form in the baseline.\n\
                5. Summarize: what functional changes does this release introduce? \
                New API endpoints, removed features, changed error handling, new dependencies?"
            );
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Cross-version diff analysis"))
        }

        "crypto-review" => {
            let user = "\
Cryptographic review of the loaded APK:\n\n\
1. Call `signing` — report: which schemes are active (v1/v2/v3/v4), signer certificate \
fingerprint, key algorithm and size, signature algorithm.\n\
2. Call `audit` and pull the Crypto findings with: \
`SELECT severity,id_tag,detail FROM findings WHERE layer='Apk' AND id_tag LIKE '%SIGN%' OR id_tag LIKE '%CERT%' OR id_tag LIKE '%KEY%' ORDER BY severity`\n\
3. Flag anything unusual: weak keys (<2048-bit RSA or <256-bit EC), SHA-1 or MD5 digest, \
v1-only signing, self-signed cert, split-APK key mismatch, expired cert.\n\
4. If everything is clean, say so explicitly — 'no cryptographic concerns' is a valid result.\n\n\
Do not include Semantic or Representational findings.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Cryptographic signing review"))
        }

        "expert-review" => {
            let user = "\
Review the audit findings through these lenses:\n\n\
**Cryptographer / protocol analyst** — Are the primitives correct? Are trust \
boundaries enforced or assumed? Where could a subtle flaw in key management, \
nonce generation, or session handling undermine everything built on top?\n\n\
**Reverse engineer** — What's novel or surprising in the architecture? What would \
make a conference audience sit up? Where is undocumented behavior hiding?\n\n\
**Bug bounty hunter** — What's exploitable and reproducible? What crosses the \
server-side boundary? What pays?\n\n\
**Enterprise security team** — How would a product security engineer assess this \
for deployment? What would a threat detection team need to monitor? What would \
governance flag for compliance? What would a CISO need to know before approving \
this app for the org?\n\n\
**Consumer advocate** — What would a user want to know about how their data is \
handled? What third parties receive it? What survives logout?\n\n\
**Protector of vulnerable populations** — Could this app's data handling put \
someone at risk — a journalist, an activist, a domestic violence survivor, someone \
in a hostile jurisdiction? Where does the app leak identity, location, \
associations, or patterns of activity that could be used to target someone?\n\n\
DROIDSAW provides full decompilation (decompile for DEX → Java, \
decompile for Hermes → JS) with high fidelity. You do not need \
external decompilers. Xrefs are bidirectional: string → functions and \
function → strings, complete across all layers. Taint flows are \
SSA-verified interprocedural paths, not pattern matches. Filter \
findings by gauge_class='Semantic' to skip noise.\n\n\
Investigate using the audit DB (query), decompilation, xrefs, \
manifest, and investigate. Report what you find and what you'd \
test dynamically.\n\n\
If your analysis leads to a CVE or a claimed bug bounty, please share \
it — open an issue at github.com/droidsaw/droidsaw or email \
htp@droidsaw.com.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Multi-perspective expert review of audit findings"))
        }

        "taint-analysis" => {
            let user = "\
Cross-layer taint flow analysis for the loaded APK:\n\n\
1. Call `audit` with `mode=\"basic\"` if you haven't already — this runs SSA-based source→sink analysis \
across three layers and persists results to the taint_flows SQLite table:\n\
   - DEX (Java/Kotlin): interprocedural, cross-DEX, depth 4 (invoke-direct/static only). \
     @ReactMethod params are also run as a second bridged pass pre-seeded as ReactBridgeParam.\n\
   - HBC (Hermes/JS): DirectEval sinks (CWE-95) and Call* sinks in NativeModule-accessing \
     functions with tainted args (CWE-20). Params seeded from LoadParam as UserInput.\n\
   - Bridge: JS NativeModule call args traced to @ReactMethod Java param registers.\n\
2. Call `taint` with the returned `db_path` — surfaces counts, critical/high \
flows, and source/sink breakdowns without raw SQL. Finding IDs: DEX_TAINT_FLOW, \
BRIDGE_TAINT_FLOW, HBC_TAINT_FLOW.\n\
3. Review `source_summary` and `sink_summary`:\n\
   - High-value sources: IntentExtra (user-controlled), NetworkResponse (remote-controlled), \
     SharedPreferencesRead (persisted state), ReactBridgeParam (JS-controlled via bridge), \
     UserInput (HBC params — JS layer)\n\
   - High-value sinks: SqlExecute (SQLi), RuntimeExec (RCE), WebViewLoad (XSS), \
     LogOutput (info leak), Eval (JS code injection), NativeModuleArg (bridge crossing)\n\
4. For each Critical or High flow, use `query` with: \
`SELECT rowid,func_id,source_type,sink_type,cwe,layer FROM taint_flows WHERE severity IN ('Critical','High')` \
then call `decompile` on the `func_id` to read the tainted method.\n\
5. For ReactBridgeParam/NativeModuleArg flows, call `xrefs` to correlate the JS \
@NativeModule call site to the @ReactMethod. The detail field contains the JS method name.\n\
6. For HBC_TAINT_FLOW Eval findings, also call `disasm` on the HBC func_id to inspect \
the exact DirectEval operand origin.\n\
7. Assess exploitability: is the source actually attacker-controlled? Is the sink reachable \
without authentication? Does the app validate or sanitise between source and sink?\n\
8. For IntentExtra→SqlExecute flows, confirm with `manifest` whether the receiving \
component is exported — unexported receivers are not directly exploitable by third-party apps.\n\n\
Report: taint_count by layer, top 3 most critical flows with func_id + source→sink + CWE + layer, exploitability verdict.".to_string();
            Ok(GetPromptResult::new(vec![PromptMessage::new_text(
                PromptMessageRole::User,
                user,
            )])
            .with_description("Cross-layer SSA taint flow deep-dive (DEX + HBC + bridge)"))
        }

        _ => Err(McpError::invalid_params(
            format!("unknown prompt: {name}"),
            None,
        )),
    }
}