fix(ci): share provider workflows to restore static gates

This commit is contained in:
morluto
2026-10-10 23:15:02 +08:00
parent bddddccdee
commit a97bd5ed29
22 changed files with 290 additions and 304 deletions
+16 -16
View File
@@ -123,23 +123,23 @@ npx rea-agents@latest setup
REA به Node.js 22.x (>=22.19)، 24.x (>=24.11) یا 26+، به‌همراه npm نیاز دارد. ابزارهای اضافی و پشتیبانی سیستم میزبان به نوع هدف بستگی دارند:
| هدف | خروجی REA | پیش‌نیازها و راهنما |
| --------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| باینری‌های بومی | شبه‌کد، اسمبلی، رشته‌ها، نمادها، فراخوانی‌ها و ارجاعات | Hopper، Ghidra یا IDA؛ [تحلیل بومی](https://rea.tools/guides/native/) |
| ساختار ELF آفلاین | بخش‌ها، سگمنت‌ها، نمادها و relocationهای اصلی و گزینه‌های احتمالی کاهش آسیب‌پذیری ایستا | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ [تشخیص باینری](docs/binary-diagnostics.md) |
| بایت‌کد EVM | selectorهای dispatch، offsetهای بایتی، آرگومان‌های استنباط‌شده و قابلیت تغییر وضعیت | دادهٔ خام/hex محلی روی Linux x64؛ [راهنمای EVM آفلاین](docs/evm-bytecode.md) |
| کرش‌های ثبت‌شدهٔ لینوکس | یادداشت‌های خام، رجیسترها/سیگنال‌های تمام threadهای ثبت‌شده و گزینه‌های احتمالی نگاشت | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ GDB/pwndbg اختیاری؛ [کرش‌های ثبت‌شده](docs/recorded-crashes.md) |
| JavaScript / Electron | ماژول‌ها، importها، source mapها، مسیرها، IPC و ارتباط افزونه‌های بومی | Node.js و npm؛ [تحلیل برنامه](https://rea.tools/guides/javascript/) |
| وب‌سایت‌ها | ساختار صفحه، اسکریپت‌ها، مشاهدات شبکه و اسکرین‌شات‌های درخواستی | مرورگر مبتنی بر Chrome؛ [تحلیل مرورگر](https://rea.tools/guides/browser/) |
| داده‌های ضبط‌شدهٔ شبکه | درخواست‌ها، پاسخ‌ها، payloadهای آشکارشده و مکان آن‌ها در منبع | HAR؛ ابزار mitmdump در لینوکس برای ضبط‌های بومی mitmproxy؛ [راهنمای ضبط شبکه](docs/web-network-captures.md) |
| اسمبلی‌های .NET | فراداده، دستورالعمل‌های CIL، وابستگی‌های بومی اعلام‌شده و مقایسهٔ buildها | بررسی ایستا؛ [راهنمای کد مدیریت‌شده](docs/managed-code-analysis.md) |
| فایل‌های APK اندروید | تعاریف Manifest، کلاس‌ها، متدهای دیکامپایل‌شده و ارجاعات | JADX بدون رابط گرافیکی و JDK کامل در Linux/macOS/Windows x64؛ [راهنمای اندروید](docs/android-analysis.md) |
| دستگاه‌های اندروید | دستگاه‌ها، بسته‌ها، فرایندها، لاگ‌ها، دامپ‌ها، اسکرین‌شات‌ها و انتقال APK/فایل | adb تأمین‌شده توسط فراخوان‌کننده (شبیه‌ساز یا دستگاه)؛ [راهنمای ADB](docs/adb-device-analysis.md) |
| هدف | خروجی REA | پیش‌نیازها و راهنما |
| --------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| باینری‌های بومی | شبه‌کد، اسمبلی، رشته‌ها، نمادها، فراخوانی‌ها و ارجاعات | Hopper، Ghidra یا IDA؛ [تحلیل بومی](https://rea.tools/guides/native/) |
| ساختار ELF آفلاین | بخش‌ها، سگمنت‌ها، نمادها و relocationهای اصلی و گزینه‌های احتمالی کاهش آسیب‌پذیری ایستا | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ [تشخیص باینری](docs/binary-diagnostics.md) |
| بایت‌کد EVM | selectorهای dispatch، offsetهای بایتی، آرگومان‌های استنباط‌شده و قابلیت تغییر وضعیت | دادهٔ خام/hex محلی روی Linux x64؛ [راهنمای EVM آفلاین](docs/evm-bytecode.md) |
| کرش‌های ثبت‌شدهٔ لینوکس | یادداشت‌های خام، رجیسترها/سیگنال‌های تمام threadهای ثبت‌شده و گزینه‌های احتمالی نگاشت | pwntools تأمین‌شده توسط فراخواننده روی Linux x64؛ GDB/pwndbg اختیاری؛ [کرش‌های ثبت‌شده](docs/recorded-crashes.md) |
| JavaScript / Electron | ماژول‌ها، importها، source mapها، مسیرها، IPC و ارتباط افزونه‌های بومی | Node.js و npm؛ [تحلیل برنامه](https://rea.tools/guides/javascript/) |
| وب‌سایت‌ها | ساختار صفحه، اسکریپت‌ها، مشاهدات شبکه و اسکرین‌شات‌های درخواستی | مرورگر مبتنی بر Chrome؛ [تحلیل مرورگر](https://rea.tools/guides/browser/) |
| داده‌های ضبط‌شدهٔ شبکه | درخواست‌ها، پاسخ‌ها، payloadهای آشکارشده و مکان آن‌ها در منبع | HAR؛ ابزار mitmdump در لینوکس برای ضبط‌های بومی mitmproxy؛ [راهنمای ضبط شبکه](docs/web-network-captures.md) |
| اسمبلی‌های .NET | فراداده، دستورالعمل‌های CIL، وابستگی‌های بومی اعلام‌شده و مقایسهٔ buildها | بررسی ایستا؛ [راهنمای کد مدیریت‌شده](docs/managed-code-analysis.md) |
| فایل‌های APK اندروید | تعاریف Manifest، کلاس‌ها، متدهای دیکامپایل‌شده و ارجاعات | JADX بدون رابط گرافیکی و JDK کامل در Linux/macOS/Windows x64؛ [راهنمای اندروید](docs/android-analysis.md) |
| دستگاه‌های اندروید | دستگاه‌ها، بسته‌ها، فرایندها، لاگ‌ها، دامپ‌ها، اسکرین‌شات‌ها و انتقال APK/فایل | adb تأمین‌شده توسط فراخوان‌کننده (شبیه‌ساز یا دستگاه)؛ [راهنمای ADB](docs/adb-device-analysis.md) |
| پروژه‌های JEB | واحدهای پروژه از موتور، هش‌های مصنوع و شبه‌کدِ نوع/متد | یک کلاینت JEB که توسط فراخوان‌کننده اجرا شده و MCP ارائه می‌دهد و با REA_JEB_MCP_URL انتخاب می‌شود؛ [راهنمای JEB](docs/jeb-analysis.md) |
| منابع اندروید | مانیفست رمزگشایی‌شده، جداول رشته‌ها، زبان‌ها و اطلاعات نسخه | راه‌انداز Apktool تأمین‌شده توسط فراخوان‌کننده؛ [راهنمای منابع](docs/apktool-resource-analysis.md) |
| Firmware | ناحیه‌ها، نتایج استخراج و تحویل به ابزارهای تحلیل بومی | Binwalk / Unblob در لینوکس؛ [راهنمای Firmware](docs/firmware-analysis.md) |
| بسته‌ها و منابع | فهرست فایل‌ها، digestها، فایل‌های plist، ساختار bundle اپل و منابع استخراج‌شده | [راهنمای آرتیفکت و JavaScript](docs/javascript-artifact-reconstruction.md)، [برنامه‌های اپل](docs/apple-application-analysis.md) |
| رفتار فرایند | خروجی ترمینال، تعاملات، وضعیت خروج، مشاهدات سیستم فایل و مقایسهٔ اجراها | Linux/macOS با PTY بومی؛ [ضبط فرایند](docs/process-capture.md) |
| منابع اندروید | مانیفست رمزگشایی‌شده، جداول رشته‌ها، زبان‌ها و اطلاعات نسخه | راه‌انداز Apktool تأمین‌شده توسط فراخوان‌کننده؛ [راهنمای منابع](docs/apktool-resource-analysis.md) |
| Firmware | ناحیه‌ها، نتایج استخراج و تحویل به ابزارهای تحلیل بومی | Binwalk / Unblob در لینوکس؛ [راهنمای Firmware](docs/firmware-analysis.md) |
| بسته‌ها و منابع | فهرست فایل‌ها، digestها، فایل‌های plist، ساختار bundle اپل و منابع استخراج‌شده | [راهنمای آرتیفکت و JavaScript](docs/javascript-artifact-reconstruction.md)، [برنامه‌های اپل](docs/apple-application-analysis.md) |
| رفتار فرایند | خروجی ترمینال، تعاملات، وضعیت خروج، مشاهدات سیستم فایل و مقایسهٔ اجراها | Linux/macOS با PTY بومی؛ [ضبط فرایند](docs/process-capture.md) |
بررسی ایستای JavaScript و .NET فایل‌های ارائه‌شده را بدون اجرای برنامه می‌خواند. ضبط زمان اجرا، هدف انتخاب‌شده را با مجوزهای کاربری شما اجرا می‌کند یا با آن تعامل دارد؛ آثار این عملیات در راهنمای مربوط به هر نوع ضبط توضیح داده شده‌اند.
+16 -16
View File
@@ -131,23 +131,23 @@ npx rea-agents@latest setup
REA には Node.js 22.x(>=22.19)、24.x(>=24.11)、または 26+ と npm が必要です。追加のツールや対応ホストは、対象によって異なります。
| 対象 | REA が返すもの | 要件とガイド |
| -------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| ネイティブバイナリ | 疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照 | Hopper、Ghidra、または IDA;[ネイティブ分析](https://rea.tools/guides/native/) |
| オフライン ELF レイアウト | セクション、セグメント、元のシンボル/再配置情報、静的解析によるセキュリティ対策の候補 | Linux x64 で利用者が用意する pwntools;[バイナリ診断](docs/binary-diagnostics.md) |
| EVM バイトコード | ディスパッチセレクター、バイトオフセット、推定された引数と状態変更の可否 | Linux x64 上のローカルの生バイト/16 進数の入力ファイル;[オフライン EVM ガイド](docs/evm-bytecode.md) |
| 記録済みの Linux クラッシュ | 生の note レコード、記録された全スレッドのレジスター/シグナル、任意のマッピング候補 | Linux x64 で利用者が用意する pwntools;GDB/pwndbg は任意;[記録済みクラッシュ](docs/recorded-crashes.md) |
| JavaScript / Electron | モジュール、インポート、ソースマップ、ルート、IPC、ネイティブアドオンの関係 | Node.js と npm;[アプリケーション分析](https://rea.tools/guides/javascript/) |
| ウェブサイト | ページ構造、スクリプト、ネットワークの観測結果、依頼されたスクリーンショット | Chrome 系ブラウザー;[ブラウザー分析](https://rea.tools/guides/browser/) |
| 保存済みのネットワークキャプチャ | リクエスト、レスポンス、取得可能なペイロード、ソース内の位置 | HAR;mitmproxy ネイティブ形式のキャプチャには Linux 上の mitmdump;[キャプチャガイド](docs/web-network-captures.md) |
| .NET アセンブリ | メタデータ、CIL 命令、宣言されたネイティブ依存関係、ビルドの比較 | 静的な検査;[マネージドコードのガイド](docs/managed-code-analysis.md) |
| Android APK | マニフェストの宣言、クラス、逆コンパイルされたメソッド、参照 | Linux/macOS/Windows x64 上のヘッドレス JADX と完全な JDK;[Android ガイド](docs/android-analysis.md) |
| Android デバイス | デバイス、パッケージ、プロセス、ログ、ダンプ、スクリーンキャプチャ、APK/ファイル転送 | 呼び出し側が用意した adb(エミュレーターまたは実機);[ADB ガイド](docs/adb-device-analysis.md) |
| 対象 | REA が返すもの | 要件とガイド |
| -------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| ネイティブバイナリ | 疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照 | Hopper、Ghidra、または IDA;[ネイティブ分析](https://rea.tools/guides/native/) |
| オフライン ELF レイアウト | セクション、セグメント、元のシンボル/再配置情報、静的解析によるセキュリティ対策の候補 | Linux x64 で利用者が用意する pwntools;[バイナリ診断](docs/binary-diagnostics.md) |
| EVM バイトコード | ディスパッチセレクター、バイトオフセット、推定された引数と状態変更の可否 | Linux x64 上のローカルの生バイト/16 進数の入力ファイル;[オフライン EVM ガイド](docs/evm-bytecode.md) |
| 記録済みの Linux クラッシュ | 生の note レコード、記録された全スレッドのレジスター/シグナル、任意のマッピング候補 | Linux x64 で利用者が用意する pwntools;GDB/pwndbg は任意;[記録済みクラッシュ](docs/recorded-crashes.md) |
| JavaScript / Electron | モジュール、インポート、ソースマップ、ルート、IPC、ネイティブアドオンの関係 | Node.js と npm;[アプリケーション分析](https://rea.tools/guides/javascript/) |
| ウェブサイト | ページ構造、スクリプト、ネットワークの観測結果、依頼されたスクリーンショット | Chrome 系ブラウザー;[ブラウザー分析](https://rea.tools/guides/browser/) |
| 保存済みのネットワークキャプチャ | リクエスト、レスポンス、取得可能なペイロード、ソース内の位置 | HAR;mitmproxy ネイティブ形式のキャプチャには Linux 上の mitmdump;[キャプチャガイド](docs/web-network-captures.md) |
| .NET アセンブリ | メタデータ、CIL 命令、宣言されたネイティブ依存関係、ビルドの比較 | 静的な検査;[マネージドコードのガイド](docs/managed-code-analysis.md) |
| Android APK | マニフェストの宣言、クラス、逆コンパイルされたメソッド、参照 | Linux/macOS/Windows x64 上のヘッドレス JADX と完全な JDK;[Android ガイド](docs/android-analysis.md) |
| Android デバイス | デバイス、パッケージ、プロセス、ログ、ダンプ、スクリーンキャプチャ、APK/ファイル転送 | 呼び出し側が用意した adb(エミュレーターまたは実機);[ADB ガイド](docs/adb-device-analysis.md) |
| JEBプロジェクト | エンジンによるプロジェクトユニット、アーティファクトのダイジェスト、型/メソッドの擬似コード | 呼び出し側が起動し MCP を提供する JEB クライアント(REA_JEB_MCP_URL で選択);[JEB ガイド](docs/jeb-analysis.md) |
| Android リソース | デコードされたマニフェスト、文字列テーブル、ロケール、バージョン情報 | 呼び出し側が用意した Apktool ランチャー;[リソースガイド](docs/apktool-resource-analysis.md) |
| ファームウェア | 領域、抽出結果、ネイティブ分析への引き継ぎ | Linux 上の Binwalk / Unblob;[ファームウェアガイド](docs/firmware-analysis.md) |
| パッケージとリソース | ファイル一覧、ダイジェスト、plist、Apple バンドルの構造、抽出されたリソース | [アーティファクトと JavaScript のガイド](docs/javascript-artifact-reconstruction.md)、[Apple アプリケーション](docs/apple-application-analysis.md) |
| プロセスの動作 | ターミナル出力、対話、終了とファイルシステムの観測結果、実行の比較 | ネイティブ PTY を備えた Linux/macOS;[プロセスキャプチャ](docs/process-capture.md) |
| Android リソース | デコードされたマニフェスト、文字列テーブル、ロケール、バージョン情報 | 呼び出し側が用意した Apktool ランチャー;[リソースガイド](docs/apktool-resource-analysis.md) |
| ファームウェア | 領域、抽出結果、ネイティブ分析への引き継ぎ | Linux 上の Binwalk / Unblob;[ファームウェアガイド](docs/firmware-analysis.md) |
| パッケージとリソース | ファイル一覧、ダイジェスト、plist、Apple バンドルの構造、抽出されたリソース | [アーティファクトと JavaScript のガイド](docs/javascript-artifact-reconstruction.md)、[Apple アプリケーション](docs/apple-application-analysis.md) |
| プロセスの動作 | ターミナル出力、対話、終了とファイルシステムの観測結果、実行の比較 | ネイティブ PTY を備えた Linux/macOS;[プロセスキャプチャ](docs/process-capture.md) |
静的な JavaScript と .NET の検査は、指定されたファイルを読み取り、アプリケーションを実行しません。実行時キャプチャは、あなたのユーザー権限で対象を実行したり、対象とやり取りしたりします。それぞれの実行時ガイドに、どのような操作が行われるかを記載しています。
+3 -3
View File
@@ -142,9 +142,9 @@ REA потребує Node.js 22.x (>=22.19), 24.x (>=24.11) або 26+, а та
| Збережені мережеві захоплення | Запити, відповіді, доступні корисні дані та місця в джерелі | HAR; mitmdump у Linux для захоплень у власному форматі mitmproxy; [посібник мережевих захоплень](docs/web-network-captures.md) |
| Збірки .NET | Метадані, інструкції CIL, оголошені нативні залежності й порівняння збірок | Статична перевірка; [посібник керованого коду](docs/managed-code-analysis.md) |
| Android APK | Оголошення маніфесту, класи, декомпільовані методи й посилання | JADX без графічного інтерфейсу й повний JDK у Linux/macOS/Windows x64; [посібник Android](docs/android-analysis.md) |
| Пристрої Android | Пристрої, пакети, процеси, журнали, дампи, знімки екрана та передавання APK/файлів | adb, наданий викликаючою стороною (емулятор або пристрій); [підручник ADB](docs/adb-device-analysis.md) |
| Проєкти JEB | Єдиниці проєкту від рушія, дайджести артефактів і псевдокод типів/методів | Запущений викликаною стороною клієнт JEB з MCP, обирається через REA_JEB_MCP_URL; [посібник JEB](docs/jeb-analysis.md) |
| Ресурси Android | Декодований маніфест, таблиці рядків, мови та відомості про версію | Засіб запуску Apktool, наданий викликаючою стороною; [підручник ресурсів](docs/apktool-resource-analysis.md) |
| Пристрої Android | Пристрої, пакети, процеси, журнали, дампи, знімки екрана та передавання APK/файлів | adb, наданий викликаючою стороною (емулятор або пристрій); [підручник ADB](docs/adb-device-analysis.md) |
| Проєкти JEB | Єдиниці проєкту від рушія, дайджести артефактів і псевдокод типів/методів | Запущений викликаною стороною клієнт JEB з MCP, обирається через REA_JEB_MCP_URL; [посібник JEB](docs/jeb-analysis.md) |
| Ресурси Android | Декодований маніфест, таблиці рядків, мови та відомості про версію | Засіб запуску Apktool, наданий викликаючою стороною; [підручник ресурсів](docs/apktool-resource-analysis.md) |
| Прошивки | Області, результати видобування й передавання до нативного аналізу | Binwalk / Unblob у Linux; [посібник прошивок](docs/firmware-analysis.md) |
| Пакети й ресурси | Переліки файлів, хеші, plist, структура пакетів Apple та видобуті ресурси | [Посібник артефактів і JavaScript](docs/javascript-artifact-reconstruction.md), [застосунки Apple](docs/apple-application-analysis.md) |
| Поведінка процесів | Вивід термінала, взаємодії, спостереження за завершенням і файловою системою та порівняння запусків | Linux/macOS із нативним PTY; [захоплення процесів](docs/process-capture.md) |
+3 -12
View File
@@ -32,6 +32,7 @@ import {
} from "../process/ExecFileOutput.js";
import {
AdbConfigurationFailure,
adbConfigurationAvailability,
inspectAdbClient,
resolveAdbBinary,
type AdbBinarySelection,
@@ -105,7 +106,7 @@ const sha256File = async (path: string): Promise<string> => {
/** adb joins shell operands into a remote POSIX command; quote at that boundary. */
const quoteShellOperand = (value: string): string =>
/^[A-Za-z0-9_./:=@+,\-]+$/u.test(value)
/^[A-Za-z0-9_./:=@+,-]+$/u.test(value)
? value
: "'" + value.replaceAll("'", "'\\''") + "'";
@@ -372,17 +373,7 @@ export class AdbProvider {
};
} catch (cause) {
if (cause instanceof AdbConfigurationFailure)
return {
status: "unavailable",
code:
cause.code === "binary_missing"
? "executable_missing"
: cause.code === "not_absolute"
? "not_configured"
: "version_unresolved",
reason: cause.message,
diagnostics: { ...cause.diagnostics },
};
return adbConfigurationAvailability(cause);
throw cause;
}
}
+2 -11
View File
@@ -40,6 +40,7 @@ import {
} from "../process/ExecFileOutput.js";
import {
ApktoolConfigurationFailure,
apktoolConfigurationAvailability,
inspectApktoolClient,
resolveApktoolCommand,
type ApktoolCommandSelection,
@@ -264,17 +265,7 @@ export class ApktoolProvider {
};
} catch (cause) {
if (cause instanceof ApktoolConfigurationFailure)
return {
status: "unavailable",
code:
cause.code === "command_missing"
? "executable_missing"
: cause.code === "not_absolute"
? "not_configured"
: "version_unresolved",
reason: cause.message,
diagnostics: { ...cause.diagnostics },
};
return apktoolConfigurationAvailability(cause);
throw cause;
}
}
@@ -0,0 +1,65 @@
import { z } from "zod";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisInputError,
} from "../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../domain/evidence.js";
import { jsonObjectSchema } from "../domain/jsonValue.js";
import { err, ok, type Result } from "../domain/result.js";
import type {
AnalysisExecution,
ExecutionOptions,
} from "./AnalysisProvider.js";
/** Validated caller-selected request carrying its operation input. */
export interface ProviderOperationRequest {
readonly input: unknown;
}
/** Provider boundary executing one validated caller-selected request. */
export interface ProviderOperationPort<
TRequest extends ProviderOperationRequest,
> {
execute(
request: TRequest,
options?: ExecutionOptions,
): Promise<Result<AnalysisExecution, AnalysisError>>;
}
/**
* Shared CLI/MCP admission, execution, and Evidence composition for
* single-request providers. The caller retains its operation vocabulary and
* confidence; this normalizes cancellation, input validation, and Evidence
* assembly once instead of per provider.
*/
export const executeProviderOperation = async <
TRequest extends ProviderOperationRequest,
>(
operation: string,
input: unknown,
options: ExecutionOptions | undefined,
schema: z.ZodType<TRequest>,
provider: ProviderOperationPort<TRequest>,
confidence: "observed" | "derived",
): Promise<Result<Evidence, AnalysisError>> => {
if (options?.signal?.aborted === true)
return err(new AnalysisCancelledError(operation));
const request = schema.safeParse({ operation, input });
if (!request.success)
return err(new AnalysisInputError(operation, { cause: request.error }));
const executed = await provider.execute(request.data, options);
if (!executed.ok) return executed;
const execution = executed.value;
return ok(
createEvidence(execution.subject ?? undefined, execution.provider, {
operation,
parameters: jsonObjectSchema.parse(request.data.input),
result: execution.result,
rawResult: execution.rawResult,
limitations: execution.limitations,
locations: execution.locations,
confidence,
}),
);
};
+10 -25
View File
@@ -4,14 +4,10 @@ import {
adbRequestSchema,
type AdbOperation,
} from "../../domain/adb/adbDeviceAnalysis.js";
import {
AnalysisCancelledError,
AnalysisInputError,
} from "../../domain/analysisErrorCore.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { jsonObjectSchema } from "../../domain/jsonValue.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { Evidence } from "../../domain/evidence.js";
import type { Result } from "../../domain/result.js";
import { executeProviderOperation } from "../ProviderOperationExecution.js";
/** Shared CLI/MCP admission, execution, and Evidence composition for ADB. */
export class AdbDeviceAnalysisService {
@@ -23,24 +19,13 @@ export class AdbDeviceAnalysisService {
input: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted === true)
return err(new AnalysisCancelledError(operation));
const request = adbRequestSchema.safeParse({ operation, input });
if (!request.success)
return err(new AnalysisInputError(operation, { cause: request.error }));
const executed = await this.provider.execute(request.data, options);
if (!executed.ok) return executed;
const execution = executed.value;
return ok(
createEvidence(execution.subject ?? undefined, execution.provider, {
operation,
parameters: jsonObjectSchema.parse(request.data.input),
result: execution.result,
rawResult: execution.rawResult,
limitations: execution.limitations,
locations: execution.locations,
confidence: "observed",
}),
return executeProviderOperation(
operation,
input,
options,
adbRequestSchema,
this.provider,
"observed",
);
}
}
@@ -4,14 +4,10 @@ import {
apktoolRequestSchema,
type ApktoolOperation,
} from "../../domain/apktool/apktoolResourceAnalysis.js";
import {
AnalysisCancelledError,
AnalysisInputError,
} from "../../domain/analysisErrorCore.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { jsonObjectSchema } from "../../domain/jsonValue.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { Evidence } from "../../domain/evidence.js";
import type { Result } from "../../domain/result.js";
import { executeProviderOperation } from "../ProviderOperationExecution.js";
/** Shared CLI/MCP admission, execution, and Evidence composition for Apktool. */
export class ApktoolResourceAnalysisService {
@@ -23,24 +19,13 @@ export class ApktoolResourceAnalysisService {
input: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted === true)
return err(new AnalysisCancelledError(operation));
const request = apktoolRequestSchema.safeParse({ operation, input });
if (!request.success)
return err(new AnalysisInputError(operation, { cause: request.error }));
const executed = await this.provider.execute(request.data, options);
if (!executed.ok) return executed;
const execution = executed.value;
return ok(
createEvidence(execution.subject ?? undefined, execution.provider, {
operation,
parameters: jsonObjectSchema.parse(request.data.input),
result: execution.result,
rawResult: execution.rawResult,
limitations: execution.limitations,
locations: execution.locations,
confidence: "observed",
}),
return executeProviderOperation(
operation,
input,
options,
apktoolRequestSchema,
this.provider,
"observed",
);
}
}
+10 -25
View File
@@ -4,14 +4,10 @@ import {
jebRequestSchema,
type JebOperation,
} from "../../domain/jeb/jebAnalysis.js";
import {
AnalysisCancelledError,
AnalysisInputError,
} from "../../domain/analysisErrorCore.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import { jsonObjectSchema } from "../../domain/jsonValue.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { Evidence } from "../../domain/evidence.js";
import type { Result } from "../../domain/result.js";
import { executeProviderOperation } from "../ProviderOperationExecution.js";
/** Shared CLI/MCP admission, execution and Evidence composition for JEB. */
export class JebAnalysisService {
@@ -23,24 +19,13 @@ export class JebAnalysisService {
input: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted === true)
return err(new AnalysisCancelledError(operation));
const request = jebRequestSchema.safeParse({ operation, input });
if (!request.success)
return err(new AnalysisInputError(operation, { cause: request.error }));
const executed = await this.provider.execute(request.data, options);
if (!executed.ok) return executed;
const execution = executed.value;
return ok(
createEvidence(execution.subject ?? undefined, execution.provider, {
operation,
parameters: jsonObjectSchema.parse(request.data.input),
result: execution.result,
rawResult: execution.rawResult,
limitations: execution.limitations,
locations: execution.locations,
confidence: operation === "decompile_jeb_item" ? "derived" : "observed",
}),
return executeProviderOperation(
operation,
input,
options,
jebRequestSchema,
this.provider,
operation === "decompile_jeb_item" ? "derived" : "observed",
);
}
}
+1 -4
View File
@@ -178,10 +178,7 @@ export const adbInputSchemas = {
.string()
.min(1)
.max(256)
.regex(
/^[A-Za-z0-9._-]+$/u,
"Setting keys are restricted to identifiers",
)
.regex(/^[A-Za-z0-9._-]+$/u, "Setting keys are restricted to identifiers")
.describe("Setting key to read"),
}),
collect_adb_bugreport: z.strictObject({
+47
View File
@@ -0,0 +1,47 @@
import type { ServerContext } from "@modelcontextprotocol/server";
import type { Logger } from "pino";
import type { ExecutionOptions } from "../application/AnalysisProvider.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { ToolContract } from "../contracts/toolContractTypes.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import type { Evidence } from "../domain/evidence.js";
import type { Result } from "../domain/result.js";
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import { logToolExecution } from "./toolLogging.js";
/** Application service executing one named contract operation. */
export interface ContractToolService<TOperation extends string> {
execute(
operation: TOperation,
input: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>>;
}
/**
* Bind one service-backed handler to its exact named contract through the
* shared log/execute/record/deliver workflow.
*/
export const createContractToolHandler =
<TOperation extends string>(
server: EvidenceMcpServer,
service: ContractToolService<TOperation>,
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
) =>
(contract: ToolContract<TOperation>) => {
const name = contract.name;
return async (input: unknown, context: ServerContext) => {
const result = await logToolExecution(logger, name, () =>
service.execute(name, input, { signal: context.mcpReq.signal }),
);
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
const recorded = recordEvidence?.(result.value);
return server.delivery.toEvidenceToolResult(
result.value,
contract,
recorded,
);
};
};
+7 -19
View File
@@ -1,12 +1,9 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { ServerContext } from "@modelcontextprotocol/server";
import { AdbDeviceAnalysisService } from "../application/adb/AdbDeviceAnalysisService.js";
import { toolContract } from "../contracts/toolContracts.js";
import type { ToolContract } from "../contracts/toolContractTypes.js";
import type { AdbOperation } from "../domain/adb/adbDeviceAnalysis.js";
import type { Logger } from "pino";
import { logToolExecution } from "./toolLogging.js";
import { createContractToolHandler } from "./contractToolHandler.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
/** Bind ADB handlers to their exact named contracts through shared workflows. */
@@ -16,21 +13,12 @@ export const registerAdbTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
): void => {
const handler = (contract: ToolContract<AdbOperation>) => {
const name = contract.name;
return async (input: unknown, context: ServerContext) => {
const result = await logToolExecution(logger, name, () =>
service.execute(name, input, { signal: context.mcpReq.signal }),
);
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
const recorded = recordEvidence?.(result.value);
return server.delivery.toEvidenceToolResult(
result.value,
contract,
recorded,
);
};
};
const handler = createContractToolHandler(
server,
service,
logger,
recordEvidence,
);
for (const contract of [
toolContract("inspect_adb_client"),
toolContract("list_adb_devices"),
+7 -19
View File
@@ -1,12 +1,9 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { ServerContext } from "@modelcontextprotocol/server";
import { AndroidAnalysisService } from "../application/android/AndroidAnalysisService.js";
import { toolContract } from "../contracts/toolContracts.js";
import type { ToolContract } from "../contracts/toolContractTypes.js";
import type { AndroidOperation } from "../domain/android/androidAnalysis.js";
import type { Logger } from "pino";
import { logToolExecution } from "./toolLogging.js";
import { createContractToolHandler } from "./contractToolHandler.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
/** Bind Android handlers to their exact named contracts through shared workflows. */
@@ -16,21 +13,12 @@ export const registerAndroidTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
): void => {
const handler = (contract: ToolContract<AndroidOperation>) => {
const name = contract.name;
return async (input: unknown, context: ServerContext) => {
const result = await logToolExecution(logger, name, () =>
service.execute(name, input, { signal: context.mcpReq.signal }),
);
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
const recorded = recordEvidence?.(result.value);
return server.delivery.toEvidenceToolResult(
result.value,
contract,
recorded,
);
};
};
const handler = createContractToolHandler(
server,
service,
logger,
recordEvidence,
);
const packageContract = toolContract("inspect_android_package");
server.registerTool(
packageContract.name,
+7 -19
View File
@@ -1,12 +1,9 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { ServerContext } from "@modelcontextprotocol/server";
import { ApktoolResourceAnalysisService } from "../application/apktool/ApktoolResourceAnalysisService.js";
import { toolContract } from "../contracts/toolContracts.js";
import type { ToolContract } from "../contracts/toolContractTypes.js";
import type { ApktoolOperation } from "../domain/apktool/apktoolResourceAnalysis.js";
import type { Logger } from "pino";
import { logToolExecution } from "./toolLogging.js";
import { createContractToolHandler } from "./contractToolHandler.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
/** Bind Apktool handlers to their exact named contracts. */
@@ -16,21 +13,12 @@ export const registerApktoolTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
): void => {
const handler = (contract: ToolContract<ApktoolOperation>) => {
const name = contract.name;
return async (input: unknown, context: ServerContext) => {
const result = await logToolExecution(logger, name, () =>
service.execute(name, input, { signal: context.mcpReq.signal }),
);
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
const recorded = recordEvidence?.(result.value);
return server.delivery.toEvidenceToolResult(
result.value,
contract,
recorded,
);
};
};
const handler = createContractToolHandler(
server,
service,
logger,
recordEvidence,
);
for (const contract of [
toolContract("inspect_apktool_client"),
toolContract("decode_android_resources"),
+7 -19
View File
@@ -1,12 +1,9 @@
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import type { ServerContext } from "@modelcontextprotocol/server";
import { JebAnalysisService } from "../application/jeb/JebAnalysisService.js";
import { toolContract } from "../contracts/toolContracts.js";
import type { ToolContract } from "../contracts/toolContractTypes.js";
import type { JebOperation } from "../domain/jeb/jebAnalysis.js";
import type { Logger } from "pino";
import { logToolExecution } from "./toolLogging.js";
import { createContractToolHandler } from "./contractToolHandler.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
/** Bind JEB handlers to their exact named contracts through shared workflows. */
@@ -16,21 +13,12 @@ export const registerJebTools = (
logger: Logger,
recordEvidence?: EvidenceWriter["recordEvidence"],
): void => {
const handler = (contract: ToolContract<JebOperation>) => {
const name = contract.name;
return async (input: unknown, context: ServerContext) => {
const result = await logToolExecution(logger, name, () =>
service.execute(name, input, { signal: context.mcpReq.signal }),
);
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
const recorded = recordEvidence?.(result.value);
return server.delivery.toEvidenceToolResult(
result.value,
contract,
recorded,
);
};
};
const handler = createContractToolHandler(
server,
service,
logger,
recordEvidence,
);
const clientContract = toolContract("inspect_jeb_client");
server.registerTool(
clientContract.name,
+1 -20
View File
@@ -1,25 +1,6 @@
import { expect, it } from "vitest";
import { createCli } from "../../../src/cli.js";
const serve = async (
argv: readonly string[],
environment: Readonly<Record<string, string | undefined>> = {},
): Promise<{ readonly stdout: string; readonly exitCode: number }> => {
const cli = createCli(environment);
let stdout = "";
let exitCode = 0;
await cli.serve([...argv], {
env: {},
exit: (code) => {
exitCode = code;
},
stdout: (text) => {
stdout += text;
},
});
return { stdout, exitCode };
};
import { serveCli as serve } from "./cliServeHarness.js";
it.each([
["inspect-adb-client"],
+1 -20
View File
@@ -1,25 +1,6 @@
import { expect, it } from "vitest";
import { createCli } from "../../../src/cli.js";
const serve = async (
argv: readonly string[],
environment: Readonly<Record<string, string | undefined>> = {},
): Promise<{ readonly stdout: string; readonly exitCode: number }> => {
const cli = createCli(environment);
let stdout = "";
let exitCode = 0;
await cli.serve([...argv], {
env: {},
exit: (code) => {
exitCode = code;
},
stdout: (text) => {
stdout += text;
},
});
return { stdout, exitCode };
};
import { serveCli as serve } from "./cliServeHarness.js";
it.each(["inspect-apktool-client", "decode-android-resources"])(
"%s advertises a schema through the public CLI",
+27
View File
@@ -0,0 +1,27 @@
import { createCli } from "../../../src/cli.js";
/** Captured CLI invocation for boundary tests. */
export interface ServedCli {
readonly stdout: string;
readonly exitCode: number;
}
/** Serve one CLI invocation with captured output and exit code. */
export const serveCli = async (
argv: readonly string[],
environment: Readonly<Record<string, string | undefined>> = {},
): Promise<ServedCli> => {
const cli = createCli(environment);
let stdout = "";
let exitCode = 0;
await cli.serve([...argv], {
env: {},
exit: (code) => {
exitCode = code;
},
stdout: (text) => {
stdout += text;
},
});
return { stdout, exitCode };
};
+7 -18
View File
@@ -1,26 +1,15 @@
import { expect, it } from "vitest";
import { createCli } from "../../../src/cli.js";
import { serveCli } from "./cliServeHarness.js";
const jebEnvironment = {
REA_JEB_MCP_URL: "http://127.0.0.1:1/mcp",
};
const serve = async (
argv: readonly string[],
): Promise<{ readonly stdout: string; readonly exitCode: number }> => {
const cli = createCli({
REA_JEB_MCP_URL: "http://127.0.0.1:1/mcp",
});
let stdout = "";
let exitCode = 0;
await cli.serve([...argv], {
env: {},
exit: (code) => {
exitCode = code;
},
stdout: (text) => {
stdout += text;
},
});
return { stdout, exitCode };
};
): Promise<{ readonly stdout: string; readonly exitCode: number }> =>
serveCli(argv, jebEnvironment);
it.each([
["inspect-jeb-client"],
+2 -18
View File
@@ -1,14 +1,13 @@
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it, onTestFinished } from "vitest";
import type { AdbDeviceAnalysisPort } from "../../../src/application/adb/AdbDeviceAnalysisPort.js";
import { createAnalysisExecution } from "../../../src/application/AnalysisProvider.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { ok } from "../../../src/domain/result.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { assertAdvertisedMcpContracts } from "./mcpContractHarness.js";
const identity = { id: "adb", name: "Android Debug Bridge", version: null };
@@ -320,22 +319,7 @@ it("advertises exact valid ADB schemas and records inline evidence through MCP",
] as const;
for (const name of operationNames) expect(names, name).toContain(name);
const ajv = new Ajv2020({ strict: false, allErrors: true });
for (const name of operationNames) {
const contract = toolContract(name);
const listed = tools.find((tool) => tool.name === name);
expect(listed, name).toBeTruthy();
if (listed?.inputSchema === undefined) continue;
// The server may present a compacted form of the contract schema; what
// must hold is that the advertised schema compiles and accepts the
// contract's own examples, and rejects unknown input.
const validate = ajv.compile(
JSON.parse(JSON.stringify(listed.inputSchema)),
);
for (const example of contract.examples)
expect(validate(example.input), `${name} example`).toBe(true);
expect(validate({ unknown_argument: true }), `${name} rejects`).toBe(false);
}
assertAdvertisedMcpContracts(operationNames, tools);
const pulled = await client.callTool({
name: "pull_adb_package",
+2 -15
View File
@@ -1,14 +1,13 @@
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect, it, onTestFinished } from "vitest";
import type { ApktoolResourceAnalysisPort } from "../../../src/application/apktool/ApktoolResourceAnalysisPort.js";
import { createAnalysisExecution } from "../../../src/application/AnalysisProvider.js";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { ok } from "../../../src/domain/result.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { assertAdvertisedMcpContracts } from "./mcpContractHarness.js";
const identity = { id: "apktool", name: "Apktool", version: "2.7.0-dirty" };
@@ -77,19 +76,7 @@ it("advertises exact valid Apktool schemas and records inline evidence through M
] as const;
for (const name of operationNames) expect(names, name).toContain(name);
const ajv = new Ajv2020({ strict: false, allErrors: true });
for (const name of operationNames) {
const contract = toolContract(name);
const listed = tools.find((tool) => tool.name === name);
expect(listed, name).toBeTruthy();
if (listed?.inputSchema === undefined) continue;
const validate = ajv.compile(
JSON.parse(JSON.stringify(listed.inputSchema)),
);
for (const example of contract.examples)
expect(validate(example.input), `${name} example`).toBe(true);
expect(validate({ unknown_argument: true }), `${name} rejects`).toBe(false);
}
assertAdvertisedMcpContracts(operationNames, tools);
const decoded = await client.callTool({
name: "decode_android_resources",
+39
View File
@@ -0,0 +1,39 @@
import { Ajv2020 } from "ajv/dist/2020.js";
import { expect } from "vitest";
import {
TOOL_CONTRACTS,
toolContract,
} from "../../../src/contracts/toolContracts.js";
type ToolName = (typeof TOOL_CONTRACTS)[number]["name"];
/** Advertised tool listing shape needed for contract validation. */
export interface AdvertisedMcpTool {
readonly name: string;
readonly inputSchema?: unknown;
}
/**
* Assert every named operation is advertised and its input schema compiles,
* accepts the contract's own examples, and rejects unknown input. The server
* may present a compacted form of the contract schema.
*/
export const assertAdvertisedMcpContracts = (
operationNames: readonly ToolName[],
tools: readonly AdvertisedMcpTool[],
): void => {
const ajv = new Ajv2020({ strict: false, allErrors: true });
for (const name of operationNames) {
const contract = toolContract(name);
const listed = tools.find((tool) => tool.name === name);
expect(listed, name).toBeTruthy();
if (listed?.inputSchema === undefined) continue;
const validate = ajv.compile(
JSON.parse(JSON.stringify(listed.inputSchema)),
);
for (const example of contract.examples)
expect(validate(example.input), `${name} example`).toBe(true);
expect(validate({ unknown_argument: true }), `${name} rejects`).toBe(false);
}
};