diff --git a/.github/workflows/rea-jadx-bridge.yml b/.github/workflows/rea-jadx-bridge.yml new file mode 100644 index 0000000..dbe94d2 --- /dev/null +++ b/.github/workflows/rea-jadx-bridge.yml @@ -0,0 +1,40 @@ +# Temporary PR run to retrieve the small bridge artifact for sandbox verification. +name: Package REA JADX bridge + +on: + push: + branches: [main] + paths: + - ".github/workflows/rea-jadx-bridge.yml" + pull_request: + branches: [main] + paths: + - ".github/workflows/rea-jadx-bridge.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + bridge: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Fetch and verify REA-pinned JADX bridge + shell: bash + run: | + set -euo pipefail + mkdir -p dist/rea-jadx-headless-mcp + JAR=dist/rea-jadx-headless-mcp/jadx-headless-mcp-0.7.1-all.jar + curl -fsSL --retry 3 "https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar" -o "$JAR" + echo "6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75 $JAR" | sha256sum -c - + curl -fsSL --retry 3 "https://raw.githubusercontent.com/1013503897/jadx-headless-mcp/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE" -o dist/rea-jadx-headless-mcp/LICENSE-APACHE-2.0.txt + echo "SHA-256 verified: $(sha256sum "$JAR" | cut -d ' ' -f1)" + - name: Upload bridge artifact + uses: actions/upload-artifact@v4 + with: + name: rea-jadx-headless-mcp-0.7.1 + path: dist/rea-jadx-headless-mcp/ + if-no-files-found: error + retention-days: 7 + compression-level: 0 diff --git a/.github/workflows/rea-offline-bundle.yml b/.github/workflows/rea-offline-bundle.yml index 13718ef..3d90a4b 100644 --- a/.github/workflows/rea-offline-bundle.yml +++ b/.github/workflows/rea-offline-bundle.yml @@ -1,4 +1,5 @@ name: Build REA offline bundle +# Temporary artifact retrieval verification PR. on: push: @@ -14,10 +15,6 @@ on: permissions: contents: read -concurrency: - group: rea-offline-bundle - cancel-in-progress: true - jobs: build-linux-x64: name: Linux x86-64 offline kit @@ -48,7 +45,9 @@ jobs: NODE_VERSION="24.11.1" curl -fsSL --retry 3 "https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" -o "$RUNNER_TEMP/node-shasums.txt" curl -fsSL --retry 3 "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o "$RUNNER_TEMP/node.tar.xz" - (cd "$RUNNER_TEMP" && grep " node-v${NODE_VERSION}-linux-x64.tar.xz$" node-shasums.txt | sha256sum -c -) + NODE_SHA256="$(awk -v file="node-v${NODE_VERSION}-linux-x64.tar.xz" '$2 == file {print $1}' "$RUNNER_TEMP/node-shasums.txt")" + test -n "$NODE_SHA256" || { echo "No Node.js checksum found"; exit 1; } + echo "$NODE_SHA256 $RUNNER_TEMP/node.tar.xz" | sha256sum -c - mkdir -p "$ROOT/runtime/node" tar -xJf "$RUNNER_TEMP/node.tar.xz" --strip-components=1 -C "$ROOT/runtime/node" export PATH="$ROOT/runtime/node/bin:$PATH" @@ -82,6 +81,13 @@ jobs: mkdir -p "$ROOT/tools/jadx" unzip -q "$RUNNER_TEMP/jadx.zip" -d "$ROOT/tools/jadx" + # Bundle REA's required headless JADX MCP bridge, pinned and hash-verified by REA itself. + mkdir -p "$ROOT/tools/jadx-headless-mcp" + JADX_MCP_JAR="$ROOT/tools/jadx-headless-mcp/jadx-headless-mcp-0.7.1-all.jar" + curl -fsSL --retry 3 "https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar" -o "$JADX_MCP_JAR" + echo "6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75 $JADX_MCP_JAR" | sha256sum -c - + curl -fsSL --retry 3 "https://raw.githubusercontent.com/1013503897/jadx-headless-mcp/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE" -o "$ROOT/licenses/JADX-HEADLESS-MCP-APACHE-2.0.txt" + # Bundle Apktool, preserving its license when published at the upstream path. curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ https://api.github.com/repos/iBotPeaches/Apktool/releases/latest -o "$RUNNER_TEMP/apktool-release.json" @@ -96,8 +102,10 @@ jobs: #!/usr/bin/env bash set -e ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" - export PATH="$ROOT/runtime/node/bin:$ROOT/tools/jadx/bin:$ROOT/tools/ghidra/support:$PATH" + export PATH="$ROOT/runtime/node/bin:$ROOT/tools/jadx/bin:$ROOT/tools/ghidra:$ROOT/tools/ghidra/support:$PATH" export JAVA_HOME="$ROOT/runtime/jdk" + export GHIDRA_INSTALL_DIR="${GHIDRA_INSTALL_DIR:-$ROOT/tools/ghidra}" + export REA_JADX_MCP_JAR="${REA_JADX_MCP_JAR:-$ROOT/tools/jadx-headless-mcp/jadx-headless-mcp-0.7.1-all.jar}" exec "$ROOT/runtime/node/bin/node" "$ROOT/app/node_modules/rea-agents/scripts/rea.mjs" "$@" EOF @@ -164,7 +172,7 @@ jobs: ## Scope and limits - Platform: Linux x86-64 only. This is not an Android/Termux or Windows build. - - Bundled tools: REA, Node.js, JDK, Ghidra, JADX, and Apktool. + - Bundled tools: REA, Node.js, JDK, Ghidra, JADX, JADX headless MCP bridge, and Apktool. - Commercial tools (IDA Pro, Hopper, JEB) are not included; use valid licenses. - Some optional REA integrations may require additional platform packages or caller-supplied tools. - Analyze only software you own or are authorized to examine. @@ -186,6 +194,7 @@ jobs: echo "Node.js: $NODE_VERSION" echo "Ghidra release: $(jq -r '.tag_name' "$RUNNER_TEMP/ghidra-release.json")" echo "JADX release: $(jq -r '.tag_name' "$RUNNER_TEMP/jadx-release.json")" + echo "JADX headless MCP: 0.7.1 (SHA-256 verified)" echo "Apktool release: $(jq -r '.tag_name' "$RUNNER_TEMP/apktool-release.json")" echo "Target: Linux x86-64" } > "$ROOT/MANIFEST.txt" @@ -196,6 +205,9 @@ jobs: "$ROOT/bin/apktool" --version "$ROOT/runtime/jdk/bin/java" -version "$ROOT/tools/ghidra/support/analyzeHeadless" -h > "$RUNNER_TEMP/ghidra-help.txt" 2>&1 || true + "$ROOT/bin/rea" doctor --json > "$RUNNER_TEMP/rea-doctor.json" || true + jq -e '.providerInspections[] | select(.id == "ghidra") | .available == true' "$RUNNER_TEMP/rea-doctor.json" >/dev/null + jq -e '.providerInspections[] | select(.id == "jadx") | .available == true' "$RUNNER_TEMP/rea-doctor.json" >/dev/null # Avoid packaging transient caches from the online build. rm -rf "$ROOT/app/.npm" "$ROOT/app/node_modules/.cache" @@ -203,13 +215,138 @@ jobs: (cd "$RUNNER_TEMP" && zip -q -r -1 "$GITHUB_WORKSPACE/dist/rea-offline-linux-x64.zip" "rea-offline-linux-x64") sha256sum "$GITHUB_WORKSPACE/dist/rea-offline-linux-x64.zip" > "$GITHUB_WORKSPACE/dist/SHA256SUMS.txt" echo "Bundle size: $(du -h "$GITHUB_WORKSPACE/dist/rea-offline-linux-x64.zip" | cut -f1)" + # GitHub connector downloads have a 512 MiB per-artifact limit; upload independent chunks. + split -b 80M -d -a 2 "$GITHUB_WORKSPACE/dist/rea-offline-linux-x64.zip" "$GITHUB_WORKSPACE/dist/rea-offline-linux-x64.part-" + PART_COUNT="$(find "$GITHUB_WORKSPACE/dist" -maxdepth 1 -type f -name 'rea-offline-linux-x64.part-*' | wc -l)" + test "$PART_COUNT" -eq 14 || { echo "Expected 14 bundle chunks, found $PART_COUNT"; exit 1; } + ZIP_SHA="$(awk '{print $1}' "$GITHUB_WORKSPACE/dist/SHA256SUMS.txt")" + PARTS_SHA="$(cat "$GITHUB_WORKSPACE"/dist/rea-offline-linux-x64.part-* | sha256sum | awk '{print $1}')" + test "$ZIP_SHA" = "$PARTS_SHA" || { echo "Split chunks do not reconstruct the original ZIP"; exit 1; } + ls -lh "$GITHUB_WORKSPACE"/dist/rea-offline-linux-x64.part-* + + - name: Upload bundle part 00 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-00 + path: dist/rea-offline-linux-x64.part-00 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 01 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-01 + path: dist/rea-offline-linux-x64.part-01 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 02 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-02 + path: dist/rea-offline-linux-x64.part-02 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 03 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-03 + path: dist/rea-offline-linux-x64.part-03 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 04 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-04 + path: dist/rea-offline-linux-x64.part-04 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 05 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-05 + path: dist/rea-offline-linux-x64.part-05 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 06 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-06 + path: dist/rea-offline-linux-x64.part-06 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 07 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-07 + path: dist/rea-offline-linux-x64.part-07 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 08 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-08 + path: dist/rea-offline-linux-x64.part-08 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 09 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-09 + path: dist/rea-offline-linux-x64.part-09 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 10 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-10 + path: dist/rea-offline-linux-x64.part-10 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 11 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-11 + path: dist/rea-offline-linux-x64.part-11 + if-no-files-found: error + retention-days: 7 + compression-level: 0 + + - name: Upload bundle part 12 + uses: actions/upload-artifact@v4 + with: + name: rea-offline-part-12 + path: dist/rea-offline-linux-x64.part-12 + if-no-files-found: error + retention-days: 7 + compression-level: 0 - - name: Upload offline bundle + - name: Upload bundle part 13 uses: actions/upload-artifact@v4 with: - name: rea-offline-linux-x64 + name: rea-offline-part-13 path: | - dist/rea-offline-linux-x64.zip + dist/rea-offline-linux-x64.part-13 dist/SHA256SUMS.txt if-no-files-found: error retention-days: 7 diff --git a/.github/workflows/rea-reassemble-bundle.yml b/.github/workflows/rea-reassemble-bundle.yml new file mode 100644 index 0000000..51bd7cc --- /dev/null +++ b/.github/workflows/rea-reassemble-bundle.yml @@ -0,0 +1,68 @@ +# Temporary verification copy for the current offline bundle run. +name: Reassemble REA offline bundle + +on: + pull_request: + branches: [main] + paths: + - ".github/workflows/rea-reassemble-bundle.yml" + workflow_dispatch: + inputs: + run_id: + description: "Successful Build REA offline bundle run ID" + required: true + default: "39485305024" + +permissions: + actions: read + contents: read + +jobs: + reassemble: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + SOURCE_RUN_ID: ${{ inputs.run_id || '39485305024' }} + steps: + - name: Download bundle chunks from the successful build + uses: actions/download-artifact@v4 + with: + github-token: ${{ github.token }} + repository: ${{ github.repository }} + run-id: ${{ env.SOURCE_RUN_ID }} + pattern: rea-offline-part-* + path: downloads + merge-multiple: true + + - name: Reassemble and verify the single ZIP + shell: bash + run: | + set -euo pipefail + mkdir -p dist + for i in $(seq -w 0 13); do + test -s "downloads/rea-offline-linux-x64.part-$i" || { + echo "Missing bundle chunk part-$i"; exit 1; + } + done + test -s downloads/SHA256SUMS.txt + cat downloads/rea-offline-linux-x64.part-* > dist/rea-offline-linux-x64.zip + EXPECTED="$(awk '{print $1}' downloads/SHA256SUMS.txt)" + ACTUAL="$(sha256sum dist/rea-offline-linux-x64.zip | awk '{print $1}')" + printf 'Expected SHA-256: %s\nActual SHA-256: %s\n' "$EXPECTED" "$ACTUAL" + test "$EXPECTED" = "$ACTUAL" || { + echo "Reassembled ZIP checksum mismatch"; exit 1; + } + cp downloads/SHA256SUMS.txt dist/SHA256SUMS.txt + unzip -tq dist/rea-offline-linux-x64.zip >/dev/null + echo "Single offline bundle ZIP verified." + + - name: Upload one-piece bundle + uses: actions/upload-artifact@v4 + with: + name: rea-offline-linux-x64-full + path: | + dist/rea-offline-linux-x64.zip + dist/SHA256SUMS.txt + if-no-files-found: error + retention-days: 7 + compression-level: 0