Compare commits
236
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b33236ec08 | ||
|
|
fc58fa1eb0 | ||
|
|
49887f0239 | ||
|
|
4a6073810a | ||
|
|
fc4a9a0592 | ||
|
|
abd7a5ed09 | ||
|
|
58381eefa2 | ||
|
|
ebb065e2dc | ||
|
|
2cedde9f1c | ||
|
|
248972f327 | ||
|
|
5047373006 | ||
|
|
7dc40d3d9e | ||
|
|
93aa9eb5c2 | ||
|
|
d76f7d1508 | ||
|
|
9f496ef2d4 | ||
|
|
5065fcd520 | ||
|
|
0a4caf4a0a | ||
|
|
98b2b027af | ||
|
|
8aaf9a1978 | ||
|
|
d5c43d65f0 | ||
|
|
a45fe86a39 | ||
|
|
71ac83f3dd | ||
|
|
fb3749aff6 | ||
|
|
f26c8b5b8c | ||
|
|
ea4db4c9f7 | ||
|
|
f8d84da127 | ||
|
|
33d4761cc7 | ||
|
|
b5afa5f91e | ||
|
|
ef1a0cc65a | ||
|
|
a6ba772c08 | ||
|
|
a65755728f | ||
|
|
7acf4e741a | ||
|
|
c7530f9a81 | ||
|
|
6d74992efe | ||
|
|
3e24a72465 | ||
|
|
dda8e6a728 | ||
|
|
2366bda37b | ||
|
|
e0d76ae80d | ||
|
|
f14b6a8430 | ||
|
|
118d43e787 | ||
|
|
c92a344d47 | ||
|
|
ac3bae3cb9 | ||
|
|
348c65c7ec | ||
|
|
9e39795895 | ||
|
|
2e81e867b2 | ||
|
|
810fe53e79 | ||
|
|
28713f9b4d | ||
|
|
2ccc86cd13 | ||
|
|
dcde958450 | ||
|
|
e6ba9c3af3 | ||
|
|
37d2ebac4c | ||
|
|
45fcd28b40 | ||
|
|
9226ade511 | ||
|
|
aa8082ea6c | ||
|
|
b3f013fa18 | ||
|
|
2ed89e6529 | ||
|
|
88c940b165 | ||
|
|
d93ebec9df | ||
|
|
fd552a8c4b | ||
|
|
be121d87eb | ||
|
|
4f25ee33dc | ||
|
|
b9292516f4 | ||
|
|
f826aa893f | ||
|
|
55249704c5 | ||
|
|
b956bd589f | ||
|
|
62634e4c01 | ||
|
|
28fd49f0f6 | ||
|
|
4f5110306d | ||
|
|
9a15525298 | ||
|
|
27c272bd9a | ||
|
|
dd3e7aef0d | ||
|
|
b72c079a9a | ||
|
|
1d769cd261 | ||
|
|
071cfd1bb6 | ||
|
|
f0a68dce60 | ||
|
|
2db668f6ca | ||
|
|
7a9f587c3a | ||
|
|
900d7c8606 | ||
|
|
62b37111e7 | ||
|
|
5574287101 | ||
|
|
5b5b76a583 | ||
|
|
4d2faef37b | ||
|
|
96a9f32dc1 | ||
|
|
f6a58f1062 | ||
|
|
d5f28fb29c | ||
|
|
15ab5dbcaf | ||
|
|
4e3953cc45 | ||
|
|
8b243877c2 | ||
|
|
418a5562f6 | ||
|
|
7714e763d9 | ||
|
|
df7151ba30 | ||
|
|
53bfa7e3f7 | ||
|
|
e52fe9719f | ||
|
|
d421d3f286 | ||
|
|
aeebba391e | ||
|
|
c78fe6990b | ||
|
|
eab34a9d8c | ||
|
|
73370e1c06 | ||
|
|
c60b737f7d | ||
|
|
b08228d19f | ||
|
|
767c6c9a95 | ||
|
|
f430c416c7 | ||
|
|
4801a24089 | ||
|
|
5afd8a850b | ||
|
|
9f48a76e11 | ||
|
|
2a4667bb79 | ||
|
|
0804722cdf | ||
|
|
898b0f62d7 | ||
|
|
6729ee7636 | ||
|
|
e5ba6934e4 | ||
|
|
faaee693d5 | ||
|
|
05f43ed388 | ||
|
|
76b70203d9 | ||
|
|
b808e2d6bb | ||
|
|
4037c6545d | ||
|
|
d52af3b321 | ||
|
|
d98dcfa8c2 | ||
|
|
dd114d3980 | ||
|
|
ab985c6a10 | ||
|
|
e32028d1b5 | ||
|
|
01be082e9e | ||
|
|
a08be51e9f | ||
|
|
507e8b3c7b | ||
|
|
e3e7664665 | ||
|
|
c371d6e1e9 | ||
|
|
14f9b3d931 | ||
|
|
37fe105ba1 | ||
|
|
9075ce71ad | ||
|
|
00f707b250 | ||
|
|
ae849a7281 | ||
|
|
5738a0cf8c | ||
|
|
895079dd09 | ||
|
|
eb68761eb0 | ||
|
|
602d0021d4 | ||
|
|
0674facd46 | ||
|
|
8e8921d40c | ||
|
|
63e0822cc7 | ||
|
|
cfd2b0c75d | ||
|
|
1be370543f | ||
|
|
dd90965058 | ||
|
|
77cf757103 | ||
|
|
021ba690b0 | ||
|
|
01779a6f61 | ||
|
|
35c82b93eb | ||
|
|
6c88094da4 | ||
|
|
e0fd8207a1 | ||
|
|
da0970672d | ||
|
|
676959ecb5 | ||
|
|
b7df5b7805 | ||
|
|
4aa2b60c1d | ||
|
|
4e17be5705 | ||
|
|
bb19c9477e | ||
|
|
f608127635 | ||
|
|
957069d14d | ||
|
|
93091abb4d | ||
|
|
c57f1b74e0 | ||
|
|
3427593daf | ||
|
|
bddeb634b5 | ||
|
|
3b92a2bced | ||
|
|
8b9940a7b5 | ||
|
|
b6d9acca63 | ||
|
|
2d4a910fcd | ||
|
|
35e5508fd0 | ||
|
|
69d8afefc1 | ||
|
|
c55ae3b9ec | ||
|
|
166dcd21d0 | ||
|
|
c182f72ad3 | ||
|
|
6cc45e8dc9 | ||
|
|
e20da1f205 | ||
|
|
4a6c17ce83 | ||
|
|
c9f49db907 | ||
|
|
b912b117f0 | ||
|
|
f72931d9dd | ||
|
|
5cbf06c7ad | ||
|
|
11f27deddf | ||
|
|
bef4321afb | ||
|
|
3f1a77f68e | ||
|
|
880cca1bca | ||
|
|
f85056d83e | ||
|
|
527b76ef78 | ||
|
|
ce73c3fed2 | ||
|
|
430c297ca4 | ||
|
|
c3f2b8e774 | ||
|
|
bc2cd8b874 | ||
|
|
63012eeb60 | ||
|
|
5fd0db37d9 | ||
|
|
e7cbfe04fd | ||
|
|
522c2d0ecb | ||
|
|
5ead2cfbc4 | ||
|
|
3a4a63f70a | ||
|
|
58ef8ac3aa | ||
|
|
be3cfbffdf | ||
|
|
11fc3b9815 | ||
|
|
78f4848d70 | ||
|
|
c13c9d41f8 | ||
|
|
ad1f6b508f | ||
|
|
6aa6350c80 | ||
|
|
f7954a8fd6 | ||
|
|
de9593ad48 | ||
|
|
1260cacc67 | ||
|
|
ef535b81fe | ||
|
|
64ebdf4501 | ||
|
|
6dd719eaf6 | ||
|
|
3ed59b2812 | ||
|
|
764ff05231 | ||
|
|
75f50d86f6 | ||
|
|
6caa38a0ca | ||
|
|
0374865836 | ||
|
|
e9fac56232 | ||
|
|
5a0e0ba3b8 | ||
|
|
3af3811ab0 | ||
|
|
2572e1eb2f | ||
|
|
24a0bf9a61 | ||
|
|
630bdafcac | ||
|
|
dd3640548f | ||
|
|
e84ad86a75 | ||
|
|
b3b44d8a2d | ||
|
|
02beb74535 | ||
|
|
21a8bd0ccd | ||
|
|
78014e3d14 | ||
|
|
472b72a530 | ||
|
|
894c30ef02 | ||
|
|
f9eaf056ef | ||
|
|
50787b2faf | ||
|
|
911d580634 | ||
|
|
b62931f833 | ||
|
|
d2aed172ab | ||
|
|
00a6ac3de9 | ||
|
|
4bfd8038be | ||
|
|
e1ca412757 | ||
|
|
66c26f280b | ||
|
|
cffc548cd3 | ||
|
|
ae81f64d2a | ||
|
|
c945931961 | ||
|
|
69be514c67 | ||
|
|
554e9b1612 |
No files matched your search
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [main, "release/*"]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.number || github.ref }}
|
||||
@@ -77,6 +77,8 @@ jobs:
|
||||
- run: npm ci
|
||||
- if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
||||
run: npm run check:ci
|
||||
- if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
||||
run: npm run verify:test-discovery
|
||||
- if: github.event_name == 'pull_request' && needs.changes.outputs.code != 'true'
|
||||
run: npm run format:check
|
||||
|
||||
@@ -237,20 +239,46 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
package-e2e:
|
||||
name: Package/install integration (${{ matrix.os }})
|
||||
name: Native package baseline (${{ matrix.platform }} ${{ matrix.architecture }})
|
||||
needs: [changes, windows-native-build]
|
||||
if: github.event_name == 'push' || needs.changes.outputs.package == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-14]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
architecture: x64
|
||||
- os: ubuntu-24.04-arm
|
||||
platform: linux
|
||||
architecture: arm64
|
||||
- os: macos-15
|
||||
platform: darwin
|
||||
architecture: arm64
|
||||
- os: macos-15-intel
|
||||
platform: darwin
|
||||
architecture: x64
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=1024"
|
||||
UV_THREADPOOL_SIZE: "2"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- name: Verify native runtime identity
|
||||
env:
|
||||
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
||||
EXPECTED_CI_ARCHITECTURE: ${{ matrix.architecture }}
|
||||
run: >-
|
||||
node -e "const assert = require('node:assert/strict');
|
||||
assert.equal(process.platform, process.env.EXPECTED_CI_PLATFORM);
|
||||
assert.equal(process.arch, process.env.EXPECTED_CI_ARCHITECTURE);
|
||||
console.log(JSON.stringify({ platform: process.platform, architecture: process.arch, node: process.version }));"
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -258,12 +286,15 @@ jobs:
|
||||
path: native/windows/build/
|
||||
- run: npm run verify:windows-native:artifact
|
||||
- run: npm run verify:package
|
||||
- name: Verify real Node Inspector discovery and observation
|
||||
if: matrix.platform == 'darwin'
|
||||
run: npm run verify:inspector
|
||||
|
||||
apple-artifacts:
|
||||
name: Real Apple artifact CLI/MCP E2E
|
||||
needs: changes
|
||||
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -274,8 +305,15 @@ jobs:
|
||||
- run: npm ci
|
||||
- run: npm run verify:keyed-archive
|
||||
- run: npm run verify:asset-catalog
|
||||
- run: npm run verify:macos-bundle
|
||||
- name: Native compiled NIB hierarchy regressions
|
||||
run: npm run test:focused -- tests/boundary/filesystem/nibNativeHierarchy.test.ts tests/boundary/filesystem/interfaceBuilderBundle.test.ts
|
||||
- name: Real binary metadata and Interface Builder integration
|
||||
run: npm run verify:apple-dispatch && npm run verify:interface-builder
|
||||
- name: Native plist CLI selection regressions
|
||||
run: npm run test:focused -- tests/boundary/cli/nativePlistCli.test.ts tests/boundary/cli/nativePlistPrototypeKeyCli.test.ts tests/boundary/cli/nativePlistSelectionFailureCli.test.ts
|
||||
- name: Native Swift demangling CLI and MCP regressions
|
||||
run: npm run test:focused -- tests/boundary/cli/nativeDemangleCli.test.ts tests/boundary/mcp/nativeDemangleMcp.test.ts
|
||||
|
||||
reconstruction-readiness:
|
||||
name: Reconstruction readiness
|
||||
@@ -312,16 +350,38 @@ jobs:
|
||||
if: github.event_name == 'push' || needs.changes.outputs.code == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
architecture: x64
|
||||
- os: ubuntu-24.04-arm
|
||||
platform: linux
|
||||
architecture: arm64
|
||||
- os: windows-latest
|
||||
platform: win32
|
||||
architecture: x64
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=1024"
|
||||
UV_THREADPOOL_SIZE: "2"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- name: Verify native runtime identity
|
||||
env:
|
||||
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
||||
EXPECTED_CI_ARCHITECTURE: ${{ matrix.architecture }}
|
||||
run: >-
|
||||
node -e "const assert = require('node:assert/strict');
|
||||
assert.equal(process.platform, process.env.EXPECTED_CI_PLATFORM);
|
||||
assert.equal(process.arch, process.env.EXPECTED_CI_ARCHITECTURE);
|
||||
console.log(JSON.stringify({ platform: process.platform, architecture: process.arch, node: process.version }));"
|
||||
- run: npm ci
|
||||
- name: Verify real Node Inspector discovery and observation
|
||||
run: npm run verify:inspector
|
||||
@@ -368,6 +428,8 @@ jobs:
|
||||
tests/boundary/filesystem/safeOutputTree.test.ts
|
||||
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
|
||||
tests/boundary/filesystem/setupUnavailableProvider.test.ts
|
||||
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
||||
src/cliReferenceSourceImportStatus.test.ts
|
||||
tests/boundary/filesystem/asarInventoryFreshness.test.ts
|
||||
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
|
||||
tests/conformance/providers/ghidra/windowsCapabilities.test.ts
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Documentation build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: documentation-build-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run docs:build
|
||||
@@ -5,10 +5,34 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/real-browser.yml"
|
||||
- "scripts/verify-real-browser.mjs"
|
||||
- "scripts/verify-browser-network.mjs"
|
||||
- "scripts/verify-browser-scripts.mjs"
|
||||
- "scripts/verify-browser-modules.mjs"
|
||||
- "scripts/verify-browser-dom-destinations.mjs"
|
||||
- "scripts/lib/browser-*.mjs"
|
||||
- "scripts/fixtures/browser-network-site.mjs"
|
||||
- "scripts/fixtures/browser-script-site.mjs"
|
||||
- "scripts/fixtures/browser-module-site.mjs"
|
||||
- "scripts/fixtures/browser-verifier-site.mjs"
|
||||
- "scripts/fixtures/page-cdp-proxy.mjs"
|
||||
- "src/browser/**"
|
||||
- "src/domain/browser*.ts"
|
||||
- "src/domain/webScript*.ts"
|
||||
- "src/domain/webModule*.ts"
|
||||
- "src/application/WebModule*.ts"
|
||||
- "src/composition/webModules.ts"
|
||||
- "src/artifacts/readStableArtifact.ts"
|
||||
- "src/artifacts/ArtifactPaths.ts"
|
||||
- "src/application/WebScriptExportService.ts"
|
||||
- "src/contracts/webScriptToolContracts.ts"
|
||||
- "src/server/registerWebScriptTool.ts"
|
||||
- "src/server/registerWebModuleTool.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/cli/webModuleCommands.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/cliCommandNames.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "src/cli/webScriptCommands.ts"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Real JavaScript recovery
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/real-javascript-recovery.yml"
|
||||
- "src/javascript/recovery/**"
|
||||
- "src/domain/javascript/**"
|
||||
- "src/application/javascript/JavaScriptRecovery*.ts"
|
||||
- "src/composition/javascriptRecovery.ts"
|
||||
- "src/contracts/javascript/javascriptRecoveryToolContracts.ts"
|
||||
- "src/cli/javascriptRecoveryCommands.ts"
|
||||
- "src/cli/javascriptApplicationAnalysis.ts"
|
||||
- "src/server/registerJavaScriptRecoveryTool.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/server/sessionAvailabilityPolicy.ts"
|
||||
- "src/application/CapabilityInventory.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/cliCommandNames.ts"
|
||||
- "src/contracts/toolContracts.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "scripts/rea.mjs"
|
||||
- "src/process/**"
|
||||
- "src/artifacts/SafeOutputTree.ts"
|
||||
- "scripts/verify/javascript/recovery.mjs"
|
||||
- "scripts/fixtures/javascript-recovery/**"
|
||||
- "third_party/wakaru"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: javascript-recovery-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
NODE_OPTIONS: "--max-old-space-size=768"
|
||||
RAYON_NUM_THREADS: "1"
|
||||
|
||||
jobs:
|
||||
real-javascript-recovery:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Fetch pinned Wakaru release into the job workspace
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "$RUNNER_TEMP/rea-wakaru"
|
||||
curl --fail --location --retry 3 https://github.com/pionxzh/wakaru/releases/download/v1.13.0/wakaru-linux-x64.tar.gz -o "$RUNNER_TEMP/rea-wakaru/release.tar.gz"
|
||||
tar -xzf "$RUNNER_TEMP/rea-wakaru/release.tar.gz" -C "$RUNNER_TEMP/rea-wakaru" wakaru
|
||||
printf '%s %s\n' e8c7ca052974604197389f6ace7464e4dd24dce64e2730971521bd6606ec9c9e "$RUNNER_TEMP/rea-wakaru/wakaru" | sha256sum --check
|
||||
- name: Install isolated fixture compilers
|
||||
run: npm install --prefix "$RUNNER_TEMP/rea-fixture-compilers" --ignore-scripts --no-audit --no-fund esbuild@0.25.10 webpack@5.101.3
|
||||
- run: npm run build:cached
|
||||
- run: npm run verify:javascript:recovery
|
||||
env:
|
||||
REA_WAKARU_COMMAND: ${{ runner.temp }}/rea-wakaru/wakaru
|
||||
REA_JAVASCRIPT_FIXTURE_TOOLS: ${{ runner.temp }}/rea-fixture-compilers
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Real website runtime attribution
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/real-web-runtime.yml"
|
||||
- "scripts/verify-browser-runtime.mjs"
|
||||
- "scripts/lib/browser-runtime-*.mjs"
|
||||
- "src/process/ProviderProcess.ts"
|
||||
- "src/process/ProcessOwnership*.ts"
|
||||
- "scripts/fixtures/browser-runtime-site.mjs"
|
||||
- "src/browser/execution/**"
|
||||
- "src/browser/CdpConnection.ts"
|
||||
- "src/browser/CdpEndpoint.ts"
|
||||
- "src/browser/CdpTarget*.ts"
|
||||
- "src/browser/CdpCaptureValues.ts"
|
||||
- "src/browser/CdpAuthorized*.ts"
|
||||
- "src/domain/webRuntime.ts"
|
||||
- "src/domain/webExecution.ts"
|
||||
- "src/domain/webEventListeners.ts"
|
||||
- "src/domain/browserObservationError*.ts"
|
||||
- "src/application/WebRuntime*.ts"
|
||||
- "src/composition/webRuntime.ts"
|
||||
- "src/contracts/webRuntimeToolContracts.ts"
|
||||
- "src/contracts/toolEffects.ts"
|
||||
- "src/server/registerWebRuntimeTools.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/cli/webRuntimeCommands.ts"
|
||||
- "src/cli/commandCancellation.ts"
|
||||
- "src/cli.ts"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: real-web-runtime-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
HUSKY: "0"
|
||||
jobs:
|
||||
real-web-runtime:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run verify:browser:runtime
|
||||
env:
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Real website source-map verification
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/real-web-source-map.yml"
|
||||
- "scripts/verify-browser-source-maps.mjs"
|
||||
- "scripts/lib/browser-source-map-e2e.mjs"
|
||||
- "scripts/fixtures/browser-source-map-site.mjs"
|
||||
- "src/javascript/sourceMaps/**"
|
||||
- "src/browser/assets/**"
|
||||
- "src/domain/webSource*.ts"
|
||||
- "src/domain/webScript*.ts"
|
||||
- "src/application/WebSourceLocation*.ts"
|
||||
- "src/application/ValidateSelectedWebScript.ts"
|
||||
- "src/composition/webSourceLocations.ts"
|
||||
- "src/server/registerWebSourceLocationTool.ts"
|
||||
- "src/server/createServer.ts"
|
||||
- "src/contracts/webScriptToolContracts.ts"
|
||||
- "src/process/**"
|
||||
- "src/cli/webSourceLocationCommands.ts"
|
||||
- "src/cli.ts"
|
||||
- "src/artifacts/readStableArtifact.ts"
|
||||
- "package.json"
|
||||
- "package-lock.json"
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: real-web-source-map-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
HUSKY: "0"
|
||||
jobs:
|
||||
real-web-source-map:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Install the pinned compiler into an isolated fixture directory
|
||||
run: npm install --prefix "$RUNNER_TEMP/rea-web-source-map-compiler" --no-audit --no-fund --package-lock=false esbuild@0.25.10
|
||||
- run: npm run verify:browser:source-maps
|
||||
env:
|
||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||
REA_BROWSER_NO_SANDBOX: "true"
|
||||
REA_WEB_SOURCE_MAP_COMPILER: ${{ runner.temp }}/rea-web-source-map-compiler/node_modules/esbuild/lib/main.js
|
||||
@@ -2,7 +2,8 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: ["release/*"]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HUSKY: "0"
|
||||
@@ -21,13 +22,47 @@ jobs:
|
||||
outputs:
|
||||
release_created: ${{ steps.release.outputs.release_created }}
|
||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
sha: ${{ steps.release.outputs.sha }}
|
||||
steps:
|
||||
- name: Validate release selection
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" != "refs/heads/${RELEASE_BRANCH}" || "${RELEASE_BRANCH}" != release/* ]]; then
|
||||
echo "Select a release/ branch; main and tags are not release checkpoints." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! git check-ref-format "refs/heads/${RELEASE_BRANCH}"; then
|
||||
echo "Invalid release branch: ${RELEASE_BRANCH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
selected_sha="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${RELEASE_BRANCH}" --jq '.object.sha')"
|
||||
if [[ "${selected_sha}" != "${GITHUB_SHA}" ]]; then
|
||||
echo "The release branch moved after this run was triggered; review its new checkpoint before publication." >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'Selected release checkpoint: %s\n' "${selected_sha}"
|
||||
|
||||
- uses: googleapis/release-please-action@v4
|
||||
id: release
|
||||
with:
|
||||
# A dedicated credential lets release-PR updates trigger CI. GitHub
|
||||
# requires manual approval for pull_request events from GITHUB_TOKEN.
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
target-branch: ${{ github.ref_name }}
|
||||
|
||||
- name: Bind publication to the triggering checkpoint
|
||||
if: steps.release.outputs.release_created == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_SHA: ${{ steps.release.outputs.sha }}
|
||||
run: |
|
||||
if [[ "${RELEASE_SHA}" != "${GITHUB_SHA}" ]]; then
|
||||
echo "The tagged release SHA differs from the triggering checkpoint; refusing to publish mismatched source provenance." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check out release pull request
|
||||
if: steps.release.outputs.prs_created == 'true'
|
||||
@@ -99,6 +134,9 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -164,7 +202,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.release-please.outputs.tag_name }}
|
||||
ref: ${{ needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify package and registry metadata versions
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Website checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "website/**"
|
||||
- "scripts/verify-website.py"
|
||||
- "scripts/prepare-website.py"
|
||||
- ".github/workflows/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: website-check-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Website checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare the example download
|
||||
run: python3 scripts/prepare-website.py
|
||||
- name: Check website links and publishing configuration
|
||||
run: python3 scripts/verify-website.py
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Publish REA website
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: website-pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
pages: read
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare the example download
|
||||
run: python3 scripts/prepare-website.py
|
||||
- name: Check website links and publishing configuration
|
||||
run: python3 scripts/verify-website.py
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload the website
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: website/public
|
||||
|
||||
deploy:
|
||||
needs: prepare
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Publish to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -37,6 +37,7 @@ coverage/
|
||||
test-results.xml
|
||||
.turbo/
|
||||
.cache/
|
||||
docs/.vitepress/cache/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
@@ -44,3 +45,6 @@ Thumbs.db
|
||||
|
||||
# Local reference sources and analysis artifacts
|
||||
/_reference/
|
||||
|
||||
# Generated website download
|
||||
/website/public/examples/notes-example.zip
|
||||
@@ -10,3 +10,6 @@
|
||||
[submodule "third_party/ghidra-nativeaot"]
|
||||
path = third_party/ghidra-nativeaot
|
||||
url = https://github.com/Washi1337/ghidra-nativeaot.git
|
||||
[submodule "third_party/wakaru"]
|
||||
path = third_party/wakaru
|
||||
url = https://github.com/pionxzh/wakaru.git
|
||||
+15
-49
@@ -6,11 +6,19 @@
|
||||
"max-depth": ["warn", 4],
|
||||
"max-lines": [
|
||||
"warn",
|
||||
{ "max": 500, "skipBlankLines": true, "skipComments": true }
|
||||
{
|
||||
"max": 500,
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
],
|
||||
"max-lines-per-function": [
|
||||
"warn",
|
||||
{ "max": 100, "skipBlankLines": true, "skipComments": true }
|
||||
{
|
||||
"max": 100,
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
],
|
||||
"max-params": ["warn", 4],
|
||||
"no-var": "warn",
|
||||
@@ -20,65 +28,23 @@
|
||||
{
|
||||
"files": [
|
||||
"scripts/electron-active-hook*.cjs",
|
||||
"tests/conformance/readiness/electron/*.js"
|
||||
"tests/conformance/readiness/electron/*.js",
|
||||
"website/public/examples/notes-electron/*.js"
|
||||
],
|
||||
"rules": {
|
||||
"no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"src/{contracts,domain,server}/**/*.ts",
|
||||
"src/application/**/*.ts"
|
||||
],
|
||||
"excludeFiles": ["src/application/runtime.ts"],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"group": ["**/ghidra/**", "**/hopper/**", "**/ida/**"],
|
||||
"message": "Deep-provider imports belong behind adapters or in the application composition root."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["tests/**/*.ts", "src/**/*.test.ts", "src/**/*.fixture.ts"],
|
||||
"rules": {
|
||||
"max-lines": "off",
|
||||
"max-lines-per-function": [
|
||||
"error",
|
||||
{ "max": 150, "skipBlankLines": true, "skipComments": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/domain/**/*.{fixture,test}.ts"],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"group": [
|
||||
"**/application/**",
|
||||
"**/server/**",
|
||||
"**/process/**",
|
||||
"**/browser/**",
|
||||
"**/ghidra/**",
|
||||
"**/hopper/**",
|
||||
"**/ida/**",
|
||||
"**/replay/**",
|
||||
"**/cli.*",
|
||||
"**/main.*"
|
||||
],
|
||||
"message": "Domain tests may import only their owning domain and inward dependencies."
|
||||
}
|
||||
]
|
||||
"max": 150,
|
||||
"skipBlankLines": true,
|
||||
"skipComments": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "4.1.0"
|
||||
".": "5.0.0"
|
||||
}
|
||||
@@ -20,10 +20,10 @@ REA is a local-only tool. Preserve caller-selected inputs, captured output, URLs
|
||||
REA is a layered ESM TypeScript application. Dependencies flow inward from pure domain logic through contracts, providers, application workflows, and CLI/MCP adapters. See [docs/architecture.mermaid](docs/architecture.mermaid) for the component map.
|
||||
|
||||
- `src/domain/` owns pure provider-neutral semantics; `src/contracts/` owns caller-visible schemas and the canonical tool inventory.
|
||||
- `src/hopper/`, `src/ghidra/`, `src/ida/`, `src/browser/`, `src/native/`, `src/artifacts/`, and `src/dotnet/` own provider-specific boundaries. Keep provider protocols out of domain and application code.
|
||||
- `src/hopper/`, `src/ghidra/`, `src/ida/`, `src/browser/`, `src/inspector/`, `src/native/`, `src/artifacts/`, and `src/dotnet/` own provider-specific boundaries. Keep provider protocols out of domain and application code.
|
||||
- `src/application/` composes shared CLI/MCP workflows; `src/server/` translates MCP requests; `src/cli.ts` and `src/main.ts` are the CLI and MCP entry points.
|
||||
- `src/process/` owns shared process lifecycle primitives, not provider wire protocols. `bridge/` contains provider-side adapters.
|
||||
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` contains real-toolchain checks.
|
||||
- `tests/` contains unit, composition, boundary, acceptance, and conformance tests. `scripts/verify-*` and capability directories under `scripts/verify/` contain real-toolchain checks.
|
||||
- `docs/product-catalog.json` is generated. Update its source contracts and regenerate it; do not edit it directly.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
@@ -91,6 +91,12 @@ Name tests `*.test.ts`. Use Vitest and production seams (`tests/fixtures/`) rath
|
||||
|
||||
Keep tool catalogs complete and self-describing; prefer capability- and session-scoped availability over schema truncation. Serialized bytes alone do not measure agent usability or model context cost.
|
||||
|
||||
## Cursor Cloud specific instructions
|
||||
|
||||
Development requires Node.js 24.18.0 and npm 11.16.0 (`.nvmrc` and `packageManager`). The Cloud Agent image places an older `node` on `PATH` ahead of a normal install. Environment setup installs the pinned toolchain under `/usr/local` and prepends `/usr/local/bin` for login shells. Confirm `node -v` is `v24.18.0` before installing dependencies.
|
||||
|
||||
`npm ci` installs locked dependencies. `npm run build:cached` produces the CLI and MCP server. `npm run check:fast` is the pre-push typecheck and lint. Hopper, Ghidra, and IDA are optional bring-your-own providers. JavaScript analysis and the deterministic Vitest suites do not need them. `rea doctor` reports those engines as missing until they are configured.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
Use Conventional Commit subjects because Release Please derives versions and changelogs from them. Examples: `feat: add historical source import`, `fix(process): stop timers after exit`, and `docs: update architecture`. Use `!` or a `BREAKING CHANGE:` footer for breaking changes. Pull request titles must follow the same format because squash merges use the title as the release commit. Pull requests should describe contract or behavior changes, list verification commands, link issues, and include sanitized MCP examples when schemas change. State whether real Hopper/Ghidra verification was performed. Never commit binaries, Hopper or Ghidra project documents, credentials, `dist/`, `node_modules/`, or local planning artifacts (e.g. `.codex/`).
|
||||
|
||||
+259
@@ -1,5 +1,264 @@
|
||||
# Changelog
|
||||
|
||||
## [5.0.0](https://github.com/morluto/rea/compare/rea-agents-4.1.0...rea-agents-5.0.0) (2026-10-07)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **native:** preserve incomplete native UI captures ([#991](https://github.com/morluto/rea/issues/991))
|
||||
* **android:** Android analysis now requires a full JDK 17 or newer with its compiler module for the packaged Java source bridge.
|
||||
|
||||
### Features
|
||||
|
||||
* **apple:** project macOS bundle anatomy in application graphs ([88c940b](https://github.com/morluto/rea/commit/88c940b165d788a491e212ea93e00462c4dde6a2))
|
||||
* **apple:** project macOS bundle anatomy in application graphs ([dd3e7ae](https://github.com/morluto/rea/commit/dd3e7aef0d52c478f6cdf475501c42ebe7f5c97e))
|
||||
* **application:** wire Android and Apple inventory graph tools ([#852](https://github.com/morluto/rea/issues/852)) ([e0fd820](https://github.com/morluto/rea/commit/e0fd8207a1fb738bcc34f204b55c4fa154f5f02d))
|
||||
* **browser:** attribute native execution and listener sources ([#917](https://github.com/morluto/rea/issues/917)) ([b5afa5f](https://github.com/morluto/rea/commit/b5afa5f91eed9ae31d670aeab9bbf5112d790ec2))
|
||||
* **browser:** capture correlated network content and lifecycle evidence ([#797](https://github.com/morluto/rea/issues/797)) ([f9eaf05](https://github.com/morluto/rea/commit/f9eaf056ef58215094513176a70fabae0740081f))
|
||||
* **browser:** trace captured website module imports ([#879](https://github.com/morluto/rea/issues/879)) ([01be082](https://github.com/morluto/rea/commit/01be082e9e11c78b91828c06a9c543c754783c07))
|
||||
* **browser:** trace captured website source-map locations ([#897](https://github.com/morluto/rea/issues/897)) ([f430c41](https://github.com/morluto/rea/commit/f430c416c72d444fbe521e0263f8935da4cdb040))
|
||||
* **javascript:** recover readable modules with Wakaru ([#848](https://github.com/morluto/rea/issues/848)) ([166dcd2](https://github.com/morluto/rea/commit/166dcd21d06f687a9c480a6a6d36beb3b4954dd0))
|
||||
* **native:** decode chained fixups, binds, ObjC categories and properties ([a657557](https://github.com/morluto/rea/commit/a65755728f7a10d984514c75fa863b84ddcdaee9))
|
||||
* **native:** decode chained fixups, binds, ObjC categories and properties ([3e24a72](https://github.com/morluto/rea/commit/3e24a72465fe6c628db1f836e93d74d7f4a92b80))
|
||||
* **native:** open iOS-style and iOS-on-Mac wrapper app bundles ([2ed89e6](https://github.com/morluto/rea/commit/2ed89e6529924248d2e21084f8730bedbb16bc3f))
|
||||
* **native:** open iOS-style and iOS-on-Mac wrapper app bundles ([aeebba3](https://github.com/morluto/rea/commit/aeebba391ee5c288b75cd828e7d18d32b40aa698))
|
||||
* **web:** export captured scripts for JavaScript application analysis ([#801](https://github.com/morluto/rea/issues/801)) ([472b72a](https://github.com/morluto/rea/commit/472b72a53068a8e2ec78fe239d687023482daaa7))
|
||||
* **website:** add a shared back-to-top link ([5047373](https://github.com/morluto/rea/commit/504737300632d8644fbd6c658d2c97e38bb54c3b))
|
||||
* **website:** introduce REA and the DX-Ball reconstruction ([#906](https://github.com/morluto/rea/issues/906)) ([4e3953c](https://github.com/morluto/rea/commit/4e3953cc45ce9c21786a7a6fc495d2350aa85d24))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* address review nits from batch integration follow-ups ([#818](https://github.com/morluto/rea/issues/818)) ([5a0e0ba](https://github.com/morluto/rea/commit/5a0e0ba3b81ea0ec336addb07da98ec7bc0a35d3))
|
||||
* **android:** align runtime suffix inference ([#977](https://github.com/morluto/rea/issues/977)) ([fb3749a](https://github.com/morluto/rea/commit/fb3749aff692a7a8225dff00a4516b48a76866b6))
|
||||
* **android:** bound fragmented JADX MCP frame memory ([#937](https://github.com/morluto/rea/issues/937)) ([9e39795](https://github.com/morluto/rea/commit/9e397958951edb543134e3ad57e7ab253d6df21a))
|
||||
* **android:** preserve JVM settings and reuse metadata sessions ([#908](https://github.com/morluto/rea/issues/908)) ([eab34a9](https://github.com/morluto/rea/commit/eab34a9d8c5eebad6b04b85750be0276d679c3f9))
|
||||
* **apple:** attribute only application-root components in macOS inventories ([5524970](https://github.com/morluto/rea/commit/55249704c5c83cbf8d20ac7869863cc196da00a6))
|
||||
* **apple:** count only real framework version directories ([2e81e86](https://github.com/morluto/rea/commit/2e81e867b24da8314f666f62d154de5803040cdf))
|
||||
* **apple:** pair bridge hypotheses within one application root ([7acf4e7](https://github.com/morluto/rea/commit/7acf4e741a6a314ea7aabb18fc757e196158ff76))
|
||||
* **apple:** pair bridge hypotheses within one application root ([9226ade](https://github.com/morluto/rea/commit/9226ade5114f287eb8e1b00b08dd91da1cc66003))
|
||||
* **apple:** traverse keyed archive hierarchy collections ([#976](https://github.com/morluto/rea/issues/976)) ([71ac83f](https://github.com/morluto/rea/commit/71ac83f3dd376135098f460c86e7897183456ec5))
|
||||
* **artifacts:** accept APFS image disks ejected with their container ([d93ebec](https://github.com/morluto/rea/commit/d93ebec9df951d40e66ced667f07fadd2dd5d25a))
|
||||
* **artifacts:** accept APFS image disks ejected with their container ([b72c079](https://github.com/morluto/rea/commit/b72c079a9a90c374bd6dd30ff8c4387306b8f59b))
|
||||
* **artifacts:** accept the bundle subject when extracting an app bundle ([#891](https://github.com/morluto/rea/issues/891)) ([37d2eba](https://github.com/morluto/rea/commit/37d2ebac4c2a524eec6e32c4c57cbbb632491ad8))
|
||||
* **artifacts:** decode flat keyed-archive NIBs with data and shared references ([b3f013f](https://github.com/morluto/rea/commit/b3f013fa183e8351319a053575aeee5df337c276))
|
||||
* **artifacts:** decode flat keyed-archive NIBs with data and shared references ([2a4667b](https://github.com/morluto/rea/commit/2a4667bb79117b202abb009c0bb9ebfdfde57158))
|
||||
* **artifacts:** harden reader lifecycle boundaries ([#951](https://github.com/morluto/rea/issues/951)) ([2366bda](https://github.com/morluto/rea/commit/2366bda37b1084016d42a0d06920dc57e7c7723f))
|
||||
* **artifacts:** keep the post-snapshot detach check out of provenance ([62634e4](https://github.com/morluto/rea/commit/62634e4c0177bde8a08decc356da98fbe25d2000))
|
||||
* **artifacts:** preserve reader failure diagnostics ([#987](https://github.com/morluto/rea/issues/987)) ([fc4a9a0](https://github.com/morluto/rea/commit/fc4a9a05922ce8474fd0717bc800343b0a95bd41))
|
||||
* **artifacts:** project compiled AppKit actions from the control to its target ([b08228d](https://github.com/morluto/rea/commit/b08228d19f8a0f6dbc940e8d322ef2bb42d2ce1a))
|
||||
* **artifacts:** project compiled AppKit actions from the control to its target ([#905](https://github.com/morluto/rea/issues/905)) ([900d7c8](https://github.com/morluto/rea/commit/900d7c8606cbc1ccbca818827a55bf2eab28bca4))
|
||||
* **artifacts:** project UIKit runtime outlet and event connections ([aa8082e](https://github.com/morluto/rea/commit/aa8082ea6c0898feb4d842135aea6b9539f769f8))
|
||||
* **artifacts:** project UIKit runtime outlet and event connections ([8b24387](https://github.com/morluto/rea/commit/8b243877c22a5550d005e7b8abf93791ce8566b9))
|
||||
* **artifacts:** read AppKit connectors from flat keyed-archive NIBs ([#900](https://github.com/morluto/rea/issues/900)) ([27c272b](https://github.com/morluto/rea/commit/27c272bd9a42e7235c7991881196c60dbc126714))
|
||||
* **artifacts:** read keyed archive dictionaries keyed UID as dictionaries ([cfd2b0c](https://github.com/morluto/rea/commit/cfd2b0c75dfb9d51b936788a46d0338145803ece))
|
||||
* **artifacts:** read keyed archive dictionaries keyed UID as dictionaries ([35c82b9](https://github.com/morluto/rea/commit/35c82b93eb2579687bb446c3a8a2a9f1a7563d28))
|
||||
* **artifacts:** report which path constraint failed ([#847](https://github.com/morluto/rea/issues/847)) ([3f1a77f](https://github.com/morluto/rea/commit/3f1a77f68ec4355d8b792ccb2970bc03485889c9))
|
||||
* **browser:** distinguish computed call keys from identifiers ([#808](https://github.com/morluto/rea/issues/808)) ([02beb74](https://github.com/morluto/rea/commit/02beb745358f02bdaabed4bb523c5e3527a9841e))
|
||||
* **browser:** distinguish WebMCP registration frame owners ([#813](https://github.com/morluto/rea/issues/813)) ([b3b44d8](https://github.com/morluto/rea/commit/b3b44d8a2d8b2aa70a8ed8b9cddf365865fcc53f))
|
||||
* **browser:** fingerprint cookies at the selected page URL ([#763](https://github.com/morluto/rea/issues/763)) ([78014e3](https://github.com/morluto/rea/commit/78014e3d149f762c848581a0aac7e44f75d51225))
|
||||
* **browser:** identify NodeNext TypeScript original source artifacts ([#811](https://github.com/morluto/rea/issues/811)) ([e84ad86](https://github.com/morluto/rea/commit/e84ad86a7578fb46568663aca6fe6a6f3986cc8d))
|
||||
* **browser:** preserve evidence through deep AST analysis ([#924](https://github.com/morluto/rea/issues/924)) ([f826aa8](https://github.com/morluto/rea/commit/f826aa893fa7d55e87d907f0c94c39421a166dd8))
|
||||
* **browser:** preserve form destinations and satisfies mutation uncertainty ([#825](https://github.com/morluto/rea/issues/825)) ([6dd719e](https://github.com/morluto/rea/commit/6dd719eaf605b37cd5dad6d01d33527ca9d59f06)), closes [#820](https://github.com/morluto/rea/issues/820)
|
||||
* **browser:** preserve Link header value delimiters ([#805](https://github.com/morluto/rea/issues/805)) ([dd36405](https://github.com/morluto/rea/commit/dd3640548fe3d1e8b92784a63fac55d757dcc1f0))
|
||||
* **build:** validate installed dependencies before cache hits ([#982](https://github.com/morluto/rea/issues/982)) ([a45fe86](https://github.com/morluto/rea/commit/a45fe86a39b9e7a7c21e3665b6de4763cb72d782))
|
||||
* capture IndexedDB object-store record fingerprints ([#809](https://github.com/morluto/rea/issues/809)) ([21a8bd0](https://github.com/morluto/rea/commit/21a8bd0ccde2326c8bc022714a88a438d8b89dde))
|
||||
* **cli:** align structured output guard with native flags ([#777](https://github.com/morluto/rea/issues/777)) ([e9fac56](https://github.com/morluto/rea/commit/e9fac56232e74c8287863ca5c4cc7d8e39fb4a40))
|
||||
* **cli:** emit empty structured filter projections ([#914](https://github.com/morluto/rea/issues/914)) ([2db668f](https://github.com/morluto/rea/commit/2db668f6ca91d2514a354fd459239b84031812e7))
|
||||
* **cli:** honor the selected native plist path ([#773](https://github.com/morluto/rea/issues/773)) ([0374865](https://github.com/morluto/rea/commit/0374865836aa4960657fb92f588c71e35dd3bed8))
|
||||
* **cli:** preserve output and comparison input validation ([#981](https://github.com/morluto/rea/issues/981)) ([d5c43d6](https://github.com/morluto/rea/commit/d5c43d65f0f6a1de8fb2eb5f2c1940a6ea2af20c))
|
||||
* **cli:** preserve unsupported historical-source imports ([#795](https://github.com/morluto/rea/issues/795)) ([911d580](https://github.com/morluto/rea/commit/911d58063477912042da5e9e573a9e8008956622))
|
||||
* **contracts:** align CLI/MCP boundary contracts ([#946](https://github.com/morluto/rea/issues/946)) ([4a60738](https://github.com/morluto/rea/commit/4a6073810a955d058c10f209157cbd68832a92c5))
|
||||
* **deps:** remediate smol-toml advisories ([93091ab](https://github.com/morluto/rea/commit/93091abb4dbd3cceee217d179db816895df24d70))
|
||||
* **deps:** remediate smol-toml advisories ([c55ae3b](https://github.com/morluto/rea/commit/c55ae3b9eccc17e6e8f4369e13044995d0650704))
|
||||
* **dotnet:** compare members with undecoded signatures by exact identity ([28fd49f](https://github.com/morluto/rea/commit/28fd49f0f6cf54e373411f5999e6b5722dd826f5))
|
||||
* **dotnet:** compare members with undecoded signatures by exact identity ([d52af3b](https://github.com/morluto/rea/commit/d52af3b321a321964598fb302edeb26552c9dffd))
|
||||
* **dotnet:** inspect native boundaries of non-managed and malformed PEs ([d5f28fb](https://github.com/morluto/rea/commit/d5f28fb29c5c3e8acef940fcae042e660e3a4d72))
|
||||
* **dotnet:** inspect native boundaries of non-managed and malformed PEs ([d98dcfa](https://github.com/morluto/rea/commit/d98dcfa8c2e46a7cc918aeb01058c26c4a84060c))
|
||||
* **dotnet:** keep a decoded member unknown beside an undecoded counterpart ([76b7020](https://github.com/morluto/rea/commit/76b70203d941ab85c1772f330407fd832d438c9c))
|
||||
* **dotnet:** keep admitted CLI header facts when metadata is unreadable ([e5ba693](https://github.com/morluto/rea/commit/e5ba6934e4ff819c09077ca70bd2417d37f85bc0))
|
||||
* **dotnet:** name the shared key in exact-signature ambiguities ([7714e76](https://github.com/morluto/rea/commit/7714e763d9ac2e87994068b3dee37ca6c5ffc6f4))
|
||||
* **dotnet:** treat ambiguous undecoded members as possible counterparts ([73370e1](https://github.com/morluto/rea/commit/73370e1c064899753988581c9a821e0e7f579561))
|
||||
* **errors:** report union input issues at the union's path ([bddeb63](https://github.com/morluto/rea/commit/bddeb634b583cf4f7d5fa6cf3a1ee821e05d2923))
|
||||
* **evaluation:** canonicalize Unicode argument keys ([#980](https://github.com/morluto/rea/issues/980)) ([0a4caf4](https://github.com/morluto/rea/commit/0a4caf4a0a1355b6a121fc88886b22e55843ac76))
|
||||
* **firmware:** accept the verified Binwalk and Unblob release lines ([#844](https://github.com/morluto/rea/issues/844)) ([bef4321](https://github.com/morluto/rea/commit/bef4321afb3d0d480e8a04ad7e256663542caa3d))
|
||||
* **firmware:** retain multi-file extraction failures ([#978](https://github.com/morluto/rea/issues/978)) ([8aaf9a1](https://github.com/morluto/rea/commit/8aaf9a19788beb6612332505850ad3407cb00027))
|
||||
* **ghidra:** admit the 12.1 line and the install's JDK range ([#840](https://github.com/morluto/rea/issues/840)) ([5cbf06c](https://github.com/morluto/rea/commit/5cbf06c7ad0ace599721bffd7da566b8a790b622))
|
||||
* **ghidra:** select exact packaged bridge scripts ([#800](https://github.com/morluto/rea/issues/800)) ([894c30e](https://github.com/morluto/rea/commit/894c30ef02d946f0c8de3ae6c9168d9525d95033))
|
||||
* **ida:** share concurrent session cleanup ([#950](https://github.com/morluto/rea/issues/950)) ([f14b6a8](https://github.com/morluto/rea/commit/f14b6a84306116716e78c5ffc84353ac07939593))
|
||||
* **installer:** validate exact semantic versions ([#954](https://github.com/morluto/rea/issues/954)) ([248972f](https://github.com/morluto/rea/commit/248972f327776687037dc07099117584c0674e77))
|
||||
* integrate 24 reviewed browser/native/semantics corrections ([#815](https://github.com/morluto/rea/issues/815)) ([3af3811](https://github.com/morluto/rea/commit/3af3811ab0a212b6aab6185628d47ecad37958fc))
|
||||
* **javascript:** advertise the exact empty seed rule and match empty API keys ([35e5508](https://github.com/morluto/rea/commit/35e5508fd095a062d7477bd6bb7d2d8e28201cd8))
|
||||
* **javascript:** analyze source maps with empty source names ([58ef8ac](https://github.com/morluto/rea/commit/58ef8ac3aa803d7b64dd12a5b2a2e8ec24261060))
|
||||
* **javascript:** analyze sources containing empty string literals ([e7cbfe0](https://github.com/morluto/rea/commit/e7cbfe04fdfd7affe50dba9e2c7a9a0a72291041))
|
||||
* **javascript:** analyze sources containing empty string literals ([11fc3b9](https://github.com/morluto/rea/commit/11fc3b981509e8107b10b2e75363f5bb489879db))
|
||||
* **javascript:** filter keyed-collection receivers only for get/delete ([00f707b](https://github.com/morluto/rea/commit/00f707b250451246d69a1205c39c6843fab6bb5b))
|
||||
* **javascript:** handle deep AST traversal and addition without stack overflow ([#915](https://github.com/morluto/rea/issues/915)) ([62b3711](https://github.com/morluto/rea/commit/62b37111e7f09887eb712c6e8e721e0334cf10b9))
|
||||
* **javascript:** handle deep static member paths ([#939](https://github.com/morluto/rea/issues/939)) ([348c65c](https://github.com/morluto/rea/commit/348c65c7ece1aee280f9431d995996b444e7549d))
|
||||
* **javascript:** keep bare relative URLs for computed-method XHR ([e32028d](https://github.com/morluto/rea/commit/e32028d1b566872ba8cd08fc6800c198d6aef30b))
|
||||
* **javascript:** keep computed-method XHR request URLs ([602d002](https://github.com/morluto/rea/commit/602d0021d45a6cdab9f806554f3ee12ea670ec97))
|
||||
* **javascript:** keep exact empty env keys and method names ([522c2d0](https://github.com/morluto/rea/commit/522c2d0ecb1eb7be262eaef2926cceac9a68badc))
|
||||
* **javascript:** keep exact empty env keys and method names ([f7954a8](https://github.com/morluto/rea/commit/f7954a8fd692f262ef3ce1507b957681150ce9c4)), closes [#783](https://github.com/morluto/rea/issues/783)
|
||||
* **javascript:** keep exact empty require paths and request fields ([ad1f6b5](https://github.com/morluto/rea/commit/ad1f6b508fbdd44d8bf1884ed96ae69082808e7d)), closes [#783](https://github.com/morluto/rea/issues/783)
|
||||
* **javascript:** keep exact empty values in graph identities ([f608127](https://github.com/morluto/rea/commit/f608127635ed8e0d105ad95252e5ea0dff899f5b))
|
||||
* **javascript:** keep exact empty values in graph identities ([63012ee](https://github.com/morluto/rea/commit/63012eeb601fc74ca82f3a027d852a5da1b099d2))
|
||||
* **javascript:** keep keyed-collection reads out of endpoints ([9075ce7](https://github.com/morluto/rea/commit/9075ce71ada4334407aa2d194dc82e3031e30260))
|
||||
* **javascript:** keep keyed-collection reads out of endpoints ([eb68761](https://github.com/morluto/rea/commit/eb68761eb0a35b876e33dd6115d0d07f97e99dd5))
|
||||
* **javascript:** keep literal HTTP methods on storage-named receivers ([9f48a76](https://github.com/morluto/rea/commit/9f48a76e1186cc43c6928eff1c82e93280caee1a))
|
||||
* **javascript:** keep literal-method and URL-literal requests on aliased receivers ([e3e7664](https://github.com/morluto/rea/commit/e3e76646657e950e24241ba715eb97ea385933e9))
|
||||
* **javascript:** keep mutated object initializer values unknown ([#752](https://github.com/morluto/rea/issues/752)) ([6caa38a](https://github.com/morluto/rea/commit/6caa38a0cad369ddff52b08f8d7d44c1b92ecac0))
|
||||
* **javascript:** keep storage open versions out of XHR endpoints ([4f51103](https://github.com/morluto/rea/commit/4f5110306d9edab94bf63bf81cf259324d96af57))
|
||||
* **javascript:** keep storage open versions out of XHR endpoints ([898b0f6](https://github.com/morluto/rea/commit/898b0f62d76ea1cf793140cb255cad16ab85b44d))
|
||||
* **javascript:** keep XHR and client endpoints that receiver spellings hid ([05f43ed](https://github.com/morluto/rea/commit/05f43ed388ae4e9316d320b592b6eba5b119ce89))
|
||||
* **javascript:** leave empty application values unlabeled ([527b76e](https://github.com/morluto/rea/commit/527b76ef78e84d29bb41bd45f1f83fae05ca66ca))
|
||||
* **javascript:** match exact empty literal channels and seeds ([e20da1f](https://github.com/morluto/rea/commit/e20da1f205b105b1b6a5decd3c2d0fe36fd12410))
|
||||
* **javascript:** never read window or document open as XHR ([ae849a7](https://github.com/morluto/rea/commit/ae849a72811857ba9755962ec48a1d76c9d1181a))
|
||||
* **javascript:** preserve computed Electron member uncertainty ([3a4a63f](https://github.com/morluto/rea/commit/3a4a63f70a2e42fb1fa48881e08b08d612289f2e))
|
||||
* **javascript:** preserve computed Electron member uncertainty ([1260cac](https://github.com/morluto/rea/commit/1260cacc67b8e0c93fb9434452053feca956e7e7))
|
||||
* **javascript:** project decoded file URL paths ([#810](https://github.com/morluto/rea/issues/810)) ([2572e1e](https://github.com/morluto/rea/commit/2572e1eb2f853a4cebfc8303d9e293e1d106d608))
|
||||
* **javascript:** read open() endpoints only from XHR method/URL pairs ([63e0822](https://github.com/morluto/rea/commit/63e0822cc794bd6ec1688340b3230ffb6b5c4a8c))
|
||||
* **javascript:** read open() endpoints only from XHR method/URL pairs ([1be3705](https://github.com/morluto/rea/commit/1be370543f7ea39016f86ebdec80a8ceaf7f8a28))
|
||||
* **javascript:** read substitution-free templates as exact strings ([c371d6e](https://github.com/morluto/rea/commit/c371d6e1e9cceb28e6cfc874e14c4e740bb87751))
|
||||
* **javascript:** read substitution-free templates as exact strings ([77cf757](https://github.com/morluto/rea/commit/77cf75710369acc6c304ec352ab25aa7274e04b1))
|
||||
* **javascript:** resolve bare source-map and worker URLs next to the script ([#890](https://github.com/morluto/rea/issues/890)) ([45fcd28](https://github.com/morluto/rea/commit/45fcd28b4018723bdb508a9229b74ab9eb749f75))
|
||||
* **javascript:** retain recovered call argument positions ([#929](https://github.com/morluto/rea/issues/929)) ([e6ba9c3](https://github.com/morluto/rea/commit/e6ba9c3af3053485f154825eea6a156127328be9))
|
||||
* **linux:** improve Hopper reliability and CachyOS support ([#963](https://github.com/morluto/rea/issues/963)) ([98b2b02](https://github.com/morluto/rea/commit/98b2b027afd8c80b68e9daaf97bdaa4d942105cc))
|
||||
* **mcp:** advertise reserved process environment key ([#949](https://github.com/morluto/rea/issues/949)) ([e0d76ae](https://github.com/morluto/rea/commit/e0d76ae80ddc70ae4d5afcbd1427afb81eee3669))
|
||||
* **mcp:** isolate optional observation adapter failures ([#838](https://github.com/morluto/rea/issues/838)) ([be3cfbf](https://github.com/morluto/rea/commit/be3cfbffdfbbb65da75dcec0518e2ebf387e66bc))
|
||||
* **mcp:** preserve exact guided prompt context ([#761](https://github.com/morluto/rea/issues/761)) ([66c26f2](https://github.com/morluto/rea/commit/66c26f280b1d494806ee182b52564166760f0ae2))
|
||||
* **native-ui:** handle incomplete accessibility child collections ([#944](https://github.com/morluto/rea/issues/944)) ([ac3bae3](https://github.com/morluto/rea/commit/ac3bae3cb97c4fb622eb1b6ac1f3b84e6a70ba03))
|
||||
* **native:** inspect plists containing data, dates, and non-finite reals ([bc2cd8b](https://github.com/morluto/rea/commit/bc2cd8b874e115eee446860043758a80bd583ad0))
|
||||
* **native:** inspect plists containing data, dates, and non-finite reals ([5ead2cf](https://github.com/morluto/rea/commit/5ead2cfbc47896b43502363d3568330f5d94762e))
|
||||
* **native:** keep echoed codesign paths out of signature fields ([#865](https://github.com/morluto/rea/issues/865)) ([4aa2b60](https://github.com/morluto/rea/commit/4aa2b60c1d84607a138395ae93260b2e50c2a721))
|
||||
* **native:** keep echoed Mach-O paths out of load commands and symbols ([ab985c6](https://github.com/morluto/rea/commit/ab985c6a10e9da856ed151100cee8459793c3573))
|
||||
* **native:** keep echoed Mach-O paths out of load commands and symbols ([37fe105](https://github.com/morluto/rea/commit/37fe105ba168488d05226f2912bed802e2d48d75))
|
||||
* **native:** keep integral plist reals beyond the JSON range as reals ([#860](https://github.com/morluto/rea/issues/860)) ([3b92a2b](https://github.com/morluto/rea/commit/3b92a2bceddaa1659654da08160813a3affbc6b7))
|
||||
* **native:** keep plist XML element types for large numbers ([f85056d](https://github.com/morluto/rea/commit/f85056d83e42a461c8c947792774580294d0ceb9))
|
||||
* **native:** keep the resolved bundle plist and permission failures ([418a556](https://github.com/morluto/rea/commit/418a5562f6f640503c751924a4787586c2c5c98a))
|
||||
* **native:** pass Swift symbols to swift-demangle as operands ([dd90965](https://github.com/morluto/rea/commit/dd90965058a9e6f7dd7f30eb7e462572e777db7f))
|
||||
* **native:** pass Swift symbols to swift-demangle as operands ([6c88094](https://github.com/morluto/rea/commit/6c88094da45258b346fe594764e561f999622a58))
|
||||
* **native:** preserve incomplete native UI captures ([#991](https://github.com/morluto/rea/issues/991)) ([9f496ef](https://github.com/morluto/rea/commit/9f496ef2d4a3aa68816287bf5d2ba1620d72c97a))
|
||||
* **native:** report exact plist integers beyond the JSON number range ([c9f49db](https://github.com/morluto/rea/commit/c9f49db907db0513ad8b22c9b2bbdcee400aff11))
|
||||
* **native:** report exact plist integers beyond the JSON number range ([430c297](https://github.com/morluto/rea/commit/430c297ca4ddb3491c2b835b889486e6a2ffdc17))
|
||||
* **native:** report plist permission denials as access failures ([5738a0c](https://github.com/morluto/rea/commit/5738a0cf8c111833847fecb2f7d0c2ee35eabbd6))
|
||||
* **native:** report why a selected plist cannot be inspected ([14f9b3d](https://github.com/morluto/rea/commit/14f9b3d93119a60a3cf5f3d9c138ca829a5462e8))
|
||||
* **native:** report why a selected plist cannot be inspected ([0674fac](https://github.com/morluto/rea/commit/0674facd468c3321584ece908031feee7220b9c6))
|
||||
* **plist:** keep character references beyond Unicode literal ([5afd8a8](https://github.com/morluto/rea/commit/5afd8a850b2ebd62e3400a5e2ece1a1cfa98bab6))
|
||||
* **plist:** read XML plists containing a __proto__ key ([faaee69](https://github.com/morluto/rea/commit/faaee693d5bb4bb957387ebd0f4338513c4c914a))
|
||||
* **plist:** read XML plists containing a __proto__ key ([a08be51](https://github.com/morluto/rea/commit/a08be51e9f0d23bd46b66bbdb88dbda44bc54c88))
|
||||
* **plist:** report omitted __proto__ entries in signatures and Interface Builder ([15ab5db](https://github.com/morluto/rea/commit/15ab5dbcaf3fac09756e98926373965247dad622))
|
||||
* **plist:** report omitted __proto__ entries in signatures and Interface Builder ([6729ee7](https://github.com/morluto/rea/commit/6729ee7636889a0a2ebc9fca8e35860476f8ca34))
|
||||
* preserve native UI failure reasons and dedupe version literals ([#854](https://github.com/morluto/rea/issues/854)) ([2d4a910](https://github.com/morluto/rea/commit/2d4a910fcd30e8d5b96fcb957b931e9aa3e4ab0c))
|
||||
* **process:** clarify Windows capture support and recovery ([#798](https://github.com/morluto/rea/issues/798)) ([50787b2](https://github.com/morluto/rea/commit/50787b2faf1a4bea799d041960ca2b91199ef403))
|
||||
* **process:** harden filesystem snapshot identity and cancellation ([#956](https://github.com/morluto/rea/issues/956)) ([93aa9eb](https://github.com/morluto/rea/commit/93aa9eb5c2551984182a4025d836e4223e8ecc8a))
|
||||
* **process:** retain provider output until streams close ([#928](https://github.com/morluto/rea/issues/928)) ([b929251](https://github.com/morluto/rea/commit/b9292516f4eedc2dedba713abf57f665ef1fb596))
|
||||
* **reference:** retain partial imports for unreadable directories ([#766](https://github.com/morluto/rea/issues/766)) ([cffc548](https://github.com/morluto/rea/commit/cffc548cd3127c5db811f20db4816039846b3170))
|
||||
* **release:** drive checkpoint releases from frozen branches ([fc58fa1](https://github.com/morluto/rea/commit/fc58fa1eb01fe5a8200a78e9551799c9c1a3d256))
|
||||
* **release:** publish from explicit source checkpoints ([49887f0](https://github.com/morluto/rea/commit/49887f0239da4483153a5e1610058619855525db))
|
||||
* **semantics:** isolate named function expression bindings ([#776](https://github.com/morluto/rea/issues/776)) ([ae81f64](https://github.com/morluto/rea/commit/ae81f64d2ad10e5cdbf8356819351c02480aee7a))
|
||||
* **semantics:** preserve direct promise ownership boundaries ([#758](https://github.com/morluto/rea/issues/758)) ([e1ca412](https://github.com/morluto/rea/commit/e1ca412757bb01be855a66bf72006e3becd497b6))
|
||||
* **semantics:** preserve empty string property keys ([#764](https://github.com/morluto/rea/issues/764)) ([630bdaf](https://github.com/morluto/rea/commit/630bdafcacc621105f6a43ed68a6e51651aca629))
|
||||
* **semantics:** recognize Node builtin default namespaces ([#769](https://github.com/morluto/rea/issues/769)) ([24a0bf9](https://github.com/morluto/rea/commit/24a0bf9a618604e2fa30817d673ce911c7842819))
|
||||
* **semantics:** retain trailing rest argument flows ([#756](https://github.com/morluto/rea/issues/756)) ([4bfd803](https://github.com/morluto/rea/commit/4bfd8038be0a60c43a452bec15e23799a0b040c0))
|
||||
* **setup:** accept the pinned Hopper RPM launcher ([#933](https://github.com/morluto/rea/issues/933)) ([dda8e6a](https://github.com/morluto/rea/commit/dda8e6a72890f87bdf8bbd3250b68b6648aae553))
|
||||
* **setup:** read an OpenCode V2 server named type as a table entry ([#866](https://github.com/morluto/rea/issues/866)) ([4e17be5](https://github.com/morluto/rea/commit/4e17be57053fb0ce2561d92e05b6e38ef789b2d4))
|
||||
* **setup:** register OpenCode V2 configurations in mcp.servers ([957069d](https://github.com/morluto/rea/commit/957069d14d832ddeac3f90dd141d379b81e2832a))
|
||||
* **setup:** register OpenCode V2 configurations in mcp.servers ([69d8afe](https://github.com/morluto/rea/commit/69d8afefc19e014a4bba7a182983a9e77e100a75))
|
||||
* **target:** decode app Info.plist executables with an XML parser ([c57f1b7](https://github.com/morluto/rea/commit/c57f1b74e0af5cf5fbb459cb247e1448cbf8df1b))
|
||||
* **target:** decode app Info.plist executables with an XML parser ([c182f72](https://github.com/morluto/rea/commit/c182f72ad3d9eea10b084a43527acb7c8ca2d6bf))
|
||||
* **test:** disconnect Hopper fixture IPC on SIGTERM ([b912b11](https://github.com/morluto/rea/commit/b912b117f019d5d3e4b4e3ef683e9b25f3a709bc))
|
||||
* **windows:** accept ordinary drive path separators ([#791](https://github.com/morluto/rea/issues/791)) ([b62931f](https://github.com/morluto/rea/commit/b62931f8335f58bfe344883a4f149e9eef73d835))
|
||||
* **windows:** remove cancelled snapshot outputs ([#957](https://github.com/morluto/rea/issues/957)) ([d76f7d1](https://github.com/morluto/rea/commit/d76f7d150895289aa1fb85a057b81ca7ff102df4))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **artifacts:** stream ASAR members without full buffering ([#962](https://github.com/morluto/rea/issues/962)) ([abd7a5e](https://github.com/morluto/rea/commit/abd7a5ed091061e4b1e83e5933e9009fb5b8d577))
|
||||
* **call-path:** prune dead ends and enumerate without recursion ([#909](https://github.com/morluto/rea/issues/909)) ([e52fe97](https://github.com/morluto/rea/commit/e52fe9719fec03f2aa79b1a1b9e11b38c229903c))
|
||||
* **cli:** defer catalog digests and use canonical cache scope ([9a15525](https://github.com/morluto/rea/commit/9a1552529889e9f5b52a88739eb767b92e57e24e))
|
||||
* **cli:** defer unused catalog work during command startup ([#920](https://github.com/morluto/rea/issues/920)) ([9a15525](https://github.com/morluto/rea/commit/9a1552529889e9f5b52a88739eb767b92e57e24e))
|
||||
* **comparison:** reuse canonical sort keys ([#990](https://github.com/morluto/rea/issues/990)) ([ebb065e](https://github.com/morluto/rea/commit/ebb065e2dc15f5763276f401521896f66bae6339))
|
||||
* **dotnet:** index declaring-type ranges once per metadata table ([#918](https://github.com/morluto/rea/issues/918)) ([7a9f587](https://github.com/morluto/rea/commit/7a9f587c3a9998d828b83388438976527ca7074f))
|
||||
* **dotnet:** index ordered declaring-type ranges once per table ([7a9f587](https://github.com/morluto/rea/commit/7a9f587c3a9998d828b83388438976527ca7074f))
|
||||
* **evidence:** scale imports and dependency graphs ([#941](https://github.com/morluto/rea/issues/941)) ([7dc40d3](https://github.com/morluto/rea/commit/7dc40d3d9e00caf013109942a5d9a1d7fdf9ec62))
|
||||
* **inspector:** bound runtime script location authorization ([#945](https://github.com/morluto/rea/issues/945)) ([58381ee](https://github.com/morluto/rea/commit/58381eefa24f73580e31eaa09dc14b9c04d78d6c))
|
||||
* **javascript:** analyze member chains without recursive copying ([#932](https://github.com/morluto/rea/issues/932)) ([dcde958](https://github.com/morluto/rea/commit/dcde9584508830a5d99ac5e2032f539563c69002))
|
||||
* **javascript:** index Promise return-site ownership ([#971](https://github.com/morluto/rea/issues/971)) ([2cedde9](https://github.com/morluto/rea/commit/2cedde9f1cf72b04bef37f9f004bb68d5ff6502a))
|
||||
* **javascript:** index semantic fingerprint relations ([#942](https://github.com/morluto/rea/issues/942)) ([c92a344](https://github.com/morluto/rea/commit/c92a344d4770125d85a11f3fc7a23d800bd30003))
|
||||
* **javascript:** reduce analysis and source-map work ([#955](https://github.com/morluto/rea/issues/955)) ([c7530f9](https://github.com/morluto/rea/commit/c7530f9a816d84354516d3ea137f4a0d91d3b8b3))
|
||||
* **providers:** assemble fragmented responses once per line ([#911](https://github.com/morluto/rea/issues/911)) ([df7151b](https://github.com/morluto/rea/commit/df7151ba305eab3a33a9261a023705b29862b308))
|
||||
* **reconstruction:** index coverage evaluation ([#983](https://github.com/morluto/rea/issues/983)) ([f26c8b5](https://github.com/morluto/rea/commit/f26c8b5b8cff10b93173359b5453874d659f2440))
|
||||
* **setup:** reuse initial diagnostics during planning ([#940](https://github.com/morluto/rea/issues/940)) ([118d43e](https://github.com/morluto/rea/commit/118d43e787e38857714852cc846b1b604a61950d))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* add incremental capability migration safeguards ([#819](https://github.com/morluto/rea/issues/819)) ([75f50d8](https://github.com/morluto/rea/commit/75f50d86f6d504f4dd97a707871d39ec022b3047))
|
||||
* **android:** group layered source and verification ([#873](https://github.com/morluto/rea/issues/873)) ([8e8921d](https://github.com/morluto/rea/commit/8e8921d40c63b40ab7e1598f672063dd304d93f0))
|
||||
* **apple:** group artifact producers and verification ([#952](https://github.com/morluto/rea/issues/952)) ([ef1a0cc](https://github.com/morluto/rea/commit/ef1a0cc65af7918ef7204a39f42a4299b486c453))
|
||||
* **artifacts:** isolate JavaScript file acquisition ([#916](https://github.com/morluto/rea/issues/916)) ([f0a68dc](https://github.com/morluto/rea/commit/f0a68dce60e81b4d2f420bf774b61957a37326ac))
|
||||
* **binary:** group session selection and snapshot ownership ([#886](https://github.com/morluto/rea/issues/886)) ([dd114d3](https://github.com/morluto/rea/commit/dd114d39801973e958a582c8fcba7ccce0e50299))
|
||||
* **binary:** move production composition outside application workflows ([#965](https://github.com/morluto/rea/issues/965)) ([33d4761](https://github.com/morluto/rea/commit/33d4761cc7a96630b7c2fed890df83d433c45914))
|
||||
* **browser:** align inspector exclusions with canonical vocabulary ([#834](https://github.com/morluto/rea/issues/834)) ([de9593a](https://github.com/morluto/rea/commit/de9593ad48c0b97507f18949b7ad86ad9c743ebc))
|
||||
* **cli:** split core analysis command registrar ([#858](https://github.com/morluto/rea/issues/858)) ([8b9940a](https://github.com/morluto/rea/commit/8b9940a7b5d3fcd5a8eda6d397f0ab7c3aaf2ac9))
|
||||
* colocate investigation record implementation ([#867](https://github.com/morluto/rea/issues/867)) ([bb19c94](https://github.com/morluto/rea/commit/bb19c9477ef3eefd9c5b2e6cabe78cc94e6779ae))
|
||||
* compose investigation record ownership ([#845](https://github.com/morluto/rea/issues/845)) ([ce73c3f](https://github.com/morluto/rea/commit/ce73c3fed2e5fd20ae0113579345a69763eb7655))
|
||||
* declare auxiliary capabilities from source ([#853](https://github.com/morluto/rea/issues/853)) ([4a6c17c](https://github.com/morluto/rea/commit/4a6c17ce83dfd20412f0e168525314a20c61169e))
|
||||
* **domain:** consolidate property-key extraction behind semanticStaticPropertyKey ([#823](https://github.com/morluto/rea/issues/823)) ([764ff05](https://github.com/morluto/rea/commit/764ff05231f1a666dfa655492da42b4b6ba742c3))
|
||||
* **domain:** converge comparison mechanics on shared vocabulary ([#824](https://github.com/morluto/rea/issues/824)) ([c13c9d4](https://github.com/morluto/rea/commit/c13c9d41f8441a711266adee21429fb7433a6aa1))
|
||||
* **domain:** extract artifact path syntax helpers ([#830](https://github.com/morluto/rea/issues/830)) ([ef535b8](https://github.com/morluto/rea/commit/ef535b81fefc358b0bdb655e5aa057fc3949ce5f))
|
||||
* **domain:** extract source-map envelope walk and disambiguate stringValue ([#828](https://github.com/morluto/rea/issues/828)) ([64ebdf4](https://github.com/morluto/rea/commit/64ebdf45016bda679674cbd2287576eadf66a1fc))
|
||||
* **domain:** map validation taxonomies and add inventory Result seam ([#833](https://github.com/morluto/rea/issues/833)) ([78f4848](https://github.com/morluto/rea/commit/78f4848d70ce462426e9f221d6e7b3e34219bf46))
|
||||
* **domain:** split Interface Builder keyed-archive parse from graph builder ([#859](https://github.com/morluto/rea/issues/859)) ([da09706](https://github.com/morluto/rea/commit/da0970672da725dbb095ffcaefad07303ae85aab))
|
||||
* **firmware:** group layered source and verification ([#878](https://github.com/morluto/rea/issues/878)) ([507e8b3](https://github.com/morluto/rea/commit/507e8b3c7be75f8de69be214618394aca3585404))
|
||||
* **ghidra:** extract createClient and load-image attestation ([#856](https://github.com/morluto/rea/issues/856)) ([b7df5b7](https://github.com/morluto/rea/commit/b7df5b78052539e57967db8e6562befd70c6ebdc))
|
||||
* give Inspector its own capability directory ([#869](https://github.com/morluto/rea/issues/869)) ([021ba69](https://github.com/morluto/rea/commit/021ba690b07eab4b990be47f90946db9f70e0fc0))
|
||||
* **javascript:** group application workflows and verification ([#896](https://github.com/morluto/rea/issues/896)) ([0804722](https://github.com/morluto/rea/commit/0804722cdf721a8d767d02f9342ebdf8d186f7e1))
|
||||
* **javascript:** group domain semantics and contracts ([#907](https://github.com/morluto/rea/issues/907)) ([f6a58f1](https://github.com/morluto/rea/commit/f6a58f10629614fb7a6970421218b73d41bce6a5))
|
||||
* **managed:** group capability workflows and verification ([#926](https://github.com/morluto/rea/issues/926)) ([28713f9](https://github.com/morluto/rea/commit/28713f9b4dcb882f0e811653890fde70cb5fdadb))
|
||||
* **native:** group analyst semantics workflows and contracts ([#973](https://github.com/morluto/rea/issues/973)) ([ea4db4c](https://github.com/morluto/rea/commit/ea4db4c9f7cbc10dc701c4d5770109c08952a5b8))
|
||||
* **native:** split Apple dispatch metadata decode facets ([#855](https://github.com/morluto/rea/issues/855)) ([676959e](https://github.com/morluto/rea/commit/676959ecb55b606a567af3d2eb3d3edc96f0aa2f))
|
||||
* **provider:** declare deep capabilities from source ([#892](https://github.com/morluto/rea/issues/892)) ([b808e2d](https://github.com/morluto/rea/commit/b808e2d6bbbae38c1ff2e293e1cf77464d889bdd))
|
||||
* share Android and firmware provider factories ([#826](https://github.com/morluto/rea/issues/826)) ([3ed59b2](https://github.com/morluto/rea/commit/3ed59b281247f2180b3c79e658078507e4cc1be2))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* add concise lawful-use disclaimer ([#888](https://github.com/morluto/rea/issues/888)) ([4037c65](https://github.com/morluto/rea/commit/4037c6545dc8cc47f4602d8f06e5329ed641cdbb))
|
||||
* add DX-Ball reconstruction showcase ([#876](https://github.com/morluto/rea/issues/876)) ([895079d](https://github.com/morluto/rea/commit/895079dd093f02487789d9aa967f0cccbfe379d8))
|
||||
* add prominent website and showcase links to READMEs ([2ccc86c](https://github.com/morluto/rea/commit/2ccc86cd13d71f08b553d3b9962175144b882539))
|
||||
* add README star history chart ([#850](https://github.com/morluto/rea/issues/850)) ([11f27de](https://github.com/morluto/rea/commit/11f27deddf88703368942d3ddfc239a7ebe72e3e))
|
||||
* add website and showcase links to READMEs ([#930](https://github.com/morluto/rea/issues/930)) ([2ccc86c](https://github.com/morluto/rea/commit/2ccc86cd13d71f08b553d3b9962175144b882539))
|
||||
* **apple:** describe directory-based framework plist ambiguity ([6d74992](https://github.com/morluto/rea/commit/6d74992efed3271e8370c505afdde347609f8353))
|
||||
* audit and improve localized readmes translations and wording ([00a6ac3](https://github.com/morluto/rea/commit/00a6ac3de99f432bd6dbb88148a2aab5f8d3d338))
|
||||
* clarify NativeAOT scope and regenerate conformance ledger ([c945931](https://github.com/morluto/rea/commit/c945931961cbd488163c6d92ef91336ed506942e))
|
||||
* fix stale IDA roadmap item, mermaid paths, toolchain wording ([d2aed17](https://github.com/morluto/rea/commit/d2aed172ab79d98ff4e62c14d8f34cc49c1d17b4))
|
||||
* move quick start up and mark skill install optional ([554e9b1](https://github.com/morluto/rea/commit/554e9b1612f7799c2bc7177eb1a9b04a0f27d2e9))
|
||||
* note Cloud Agent Node.js toolchain ([#837](https://github.com/morluto/rea/issues/837)) ([6aa6350](https://github.com/morluto/rea/commit/6aa6350c800796d0953937e894ff176ae248e930))
|
||||
* publish VitePress documentation on GitHub Pages ([c78fe69](https://github.com/morluto/rea/commit/c78fe6990bcface6853ac5c11c90df6bc0df562d))
|
||||
* separate ambiguous and unavailable provider recovery ([c3f2b8e](https://github.com/morluto/rea/commit/c3f2b8e7742915dc27ab7c801a8a1a9127169896))
|
||||
* show task-scoped readiness and provider preference in the quick start ([880cca1](https://github.com/morluto/rea/commit/880cca1bca497bb53c97a821b470df0f2235e043))
|
||||
* show task-scoped readiness and provider preference in the quick start ([5fd0db3](https://github.com/morluto/rea/commit/5fd0db37d94f1c1a0495920c79e121d799da0d30)), closes [#726](https://github.com/morluto/rea/issues/726)
|
||||
* update release boundary to npm 4.1.0 with Windows native bundle ([69be514](https://github.com/morluto/rea/commit/69be514c67c8b2eb8b450355a9782d9237748fda))
|
||||
* **website:** add Notion case study and improve onboarding ([a6ba772](https://github.com/morluto/rea/commit/a6ba772c08e2648e6602787a0ea537d75d72802d))
|
||||
* **website:** add TH04 bullet-ring case study ([f8d84da](https://github.com/morluto/rea/commit/f8d84da127fad871050c098b004a503b36795ad0))
|
||||
* **website:** add worked guides and keep Pages publishing manual ([#925](https://github.com/morluto/rea/issues/925)) ([4f25ee3](https://github.com/morluto/rea/commit/4f25ee33dcd4195f8d877f8835f81c4e08c95235))
|
||||
* **website:** clarify guides and case studies ([5065fcd](https://github.com/morluto/rea/commit/5065fcd5205ec7c0d259131431c2cf54d9f69cf6))
|
||||
|
||||
|
||||
### Tests
|
||||
|
||||
* **artifacts:** report compiled connector records when a storyboard route is missing ([d421d3f](https://github.com/morluto/rea/commit/d421d3f286ce515b5126859a0f9cac60667a168e))
|
||||
* **artifacts:** report observed action routes when a compiled route is missing ([c60b737](https://github.com/morluto/rea/commit/c60b737f7db8e2c9a1982cdb4357fcda1f47ff08))
|
||||
* **artifacts:** write the storyboard fixture's action in Cocoa syntax ([53bfa7e](https://github.com/morluto/rea/commit/53bfa7e3f792cdb9f2e95fc954f3616426414bcc))
|
||||
* eliminate fixture races and prune redundant scaffolding ([#849](https://github.com/morluto/rea/issues/849)) ([f72931d](https://github.com/morluto/rea/commit/f72931d9dd54609bfca422750fc2667f80af691c))
|
||||
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
* consolidate macOS native baseline checks ([#935](https://github.com/morluto/rea/issues/935)) ([810fe53](https://github.com/morluto/rea/commit/810fe53e799b89dc215c52c9245d36fb2a44d9c4))
|
||||
* verify native arm64 and Intel macOS workflows ([#898](https://github.com/morluto/rea/issues/898)) ([4801a24](https://github.com/morluto/rea/commit/4801a24089964860db7f4855be21d2d4313c7eab))
|
||||
|
||||
## [4.1.0](https://github.com/morluto/rea/compare/rea-agents-4.0.1...rea-agents-4.1.0) (2026-10-06)
|
||||
|
||||
|
||||
|
||||
+31
-29
@@ -2,11 +2,15 @@
|
||||
|
||||
REA welcomes focused bug fixes, documentation improvements, tests, and reverse-engineering workflow enhancements. Open an issue before a large contract or architecture change so its scope can be agreed before implementation.
|
||||
|
||||
For capability organization and provider composition, follow the incremental
|
||||
[migration guide](docs/capability-migration.md). Run `npm run verify:test-discovery`
|
||||
after adding or moving tests.
|
||||
|
||||
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||
|
||||
## Development setup
|
||||
|
||||
REA development requires Node.js 24.18.x and npm 11.16.x. Real-Hopper verification additionally requires either macOS 12+ or an officially supported Linux host (Ubuntu 24.04+, Fedora 41+, or 64-bit Arch) and an installed Hopper application. Linux demo verification uses its own private Xvfb display and does not require a desktop session. Run `nvm use` before installing dependencies.
|
||||
REA development requires Node.js 24.18.x and npm 11.16.x (pinned toolchain via `nvm use`; the supported runtime range is Node.js ^22.19 || ^24.11 || >=26, as the README badge states). Real-Hopper verification additionally requires either macOS 12+ or an officially supported Linux host (Ubuntu 24.04+, Fedora 41+, 64-bit Arch, or CachyOS) and an installed Hopper application. Linux demo verification uses its own private Xvfb display and does not require a desktop session. Run `nvm use` before installing dependencies.
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
@@ -22,6 +26,22 @@ worktrees. After a package, lockfile, or managed-skill version change, run
|
||||
|
||||
Keep dependencies flowing inward through the existing domain, contracts, provider, application, server, and adapter layers. Parse unknown values at process and protocol boundaries, model expected failures with `Result`, and preserve the canonical tool inventory defined by `TOOL_CONTRACTS` unless a deliberate contract change updates every verifier, generated catalog artifact, and snapshot. Keep tool discovery complete and report capability- and session-scoped availability through `binary_session`.
|
||||
|
||||
## Documentation website
|
||||
|
||||
The VitePress site uses the Markdown files in `docs/` and deploys to
|
||||
<https://morluto.github.io/rea/>. Run `npm run docs:dev` for live editing,
|
||||
`npm run docs:build` to check the production build and links, and
|
||||
`npm run docs:preview` to preview that build at `/rea/`.
|
||||
|
||||
Site navigation lives in `docs/.vitepress/config.ts`. Keep links to guides
|
||||
relative so they work on GitHub and the website; link to repository files
|
||||
outside `docs/` using their full GitHub URLs. Generated reference documents
|
||||
still use `npm run docs:generate`; `docs:build` only builds the website.
|
||||
|
||||
The documentation workflow builds pull requests and deploys changes on `main`.
|
||||
The repository's **Settings → Pages → Build and deployment → Source** must be
|
||||
set to **GitHub Actions** before the first deployment.
|
||||
|
||||
## Development feedback and PR verification
|
||||
|
||||
For an ordinary edit, run the relevant regression and cached static checks:
|
||||
@@ -100,32 +120,14 @@ Describe the behavior change and verification performed in the pull request. Nev
|
||||
|
||||
## Maintainer release checklist
|
||||
|
||||
Run `npm run check:pr`, the isolated package verifier, package dry run, and two-target real-Hopper verifier described above. Build a local tarball and exercise the executable through the package boundary:
|
||||
Use the [checkpoint release guide](docs/releasing.md). Releases start from an
|
||||
explicit `release/VERSION` branch cut at a recorded commit. Pushing the branch prepares
|
||||
the bot PR. Wait for its exact-head CI and review; merging into that frozen
|
||||
branch triggers publication through the official Release workflow. Main
|
||||
pushes do not update release PRs or publish packages. Both publishers build
|
||||
the exact SHA tagged by Release Please.
|
||||
|
||||
```bash
|
||||
npm pack
|
||||
```
|
||||
|
||||
Use the exact filename printed by `npm pack` to run the packaged executable:
|
||||
|
||||
```bash
|
||||
npm exec --yes --package ./rea-agents-VERSION.tgz -- rea --help
|
||||
```
|
||||
|
||||
Replace `VERSION` with the packed version; do not use a tarball from an earlier
|
||||
build.
|
||||
|
||||
Publish the public package:
|
||||
|
||||
```bash
|
||||
npm publish --access public
|
||||
```
|
||||
|
||||
After npm registry propagation, verify the published CLI and connect the client SDK version pinned in `package.json` to the published server to confirm the canonical tool catalog:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest --help
|
||||
npx -y rea-agents@latest doctor
|
||||
npx -y rea-agents@latest setup --yes --all-detected
|
||||
npx -y rea-agents@latest mcp
|
||||
```
|
||||
Keep new implementation commits on main for the next release. The workflow
|
||||
owns packaged-artifact verification, npm publication, the published CLI/MCP
|
||||
canary, and MCP Registry publication. Sync release metadata back to main after
|
||||
publication; see the guide for partial-publication recovery and verification.
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[Website](https://morluto.github.io/rea/) · [Guides](https://morluto.github.io/rea/guides/) · [Showcases](https://morluto.github.io/rea/showcase/)**
|
||||
|
||||
[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [Tool catalog](#tool-catalog-for-investigation) · [Roadmap](#roadmap) · [How it works](#how-it-works)
|
||||
|
||||
<code>npx rea-agents setup</code>
|
||||
@@ -51,49 +53,6 @@ REA connects your agent to tools for inspecting native binaries, JavaScript and
|
||||
|
||||
Setup registers REA with your agent and installs matching workflow instructions. Native analysis can use an existing Hopper or Ghidra installation; setup can optionally install Hopper with approval. Static JavaScript analysis needs neither engine.
|
||||
|
||||
## Just ask your agent
|
||||
|
||||
After [setup](#quick-start), restart your agent and ask:
|
||||
|
||||
```text
|
||||
Understand how search works in the Notes app, show me the evidence, and build a
|
||||
similar feature for my project.
|
||||
```
|
||||
|
||||
Replace Notes with the app you want to understand, or ask for an overview first.
|
||||
|
||||
## The investigation model
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Decompile</strong><br /><br />
|
||||
Open an app and recover readable code, strings, names, and other clues about how it works.
|
||||
</td>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Understand</strong><br /><br />
|
||||
Follow the code from one part of the app to another until the agent can explain how a feature actually works.
|
||||
</td>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Recreate</strong><br /><br />
|
||||
Turn what the agent learned into a feature for your own product, adapted to your stack, interface, and requirements.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
REA shows how it reached its conclusions. It does not claim to recover original source code or automatically clone an application.
|
||||
|
||||
## Why REA
|
||||
|
||||
| | |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| **Built for agents** | Ask what an app does and let your agent inspect it instead of guessing. |
|
||||
| **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or agent. |
|
||||
| **Guided setup** | Configure your agent, connect an existing analysis tool, or install Hopper with your approval. |
|
||||
| **From insight to code** | Understand a feature, then build your own version in the same coding session. |
|
||||
| **Local by design** | Analysis runs on your supported local host. REA does not upload the app to a hosted analysis service. |
|
||||
| **Keeps context** | Investigate several apps without starting over for every question. |
|
||||
|
||||
## Quick start
|
||||
|
||||
### Run setup (recommended)
|
||||
@@ -108,25 +67,21 @@ Choose which supported agents should use REA, then review the exact paths and ch
|
||||
|
||||
Setup shows its changes before applying them and backs up existing configuration. See [Installation and setup](docs/installation.md) for requirements and setup options.
|
||||
|
||||
### With an agent (recommended)
|
||||
### AI Coding Assistants (optional)
|
||||
|
||||
After setup, restart your agent and [describe the app or feature](#just-ask-your-agent) you want to understand. Hopper can run in demo mode; if it shows a first-run prompt, choose the demo or enter an existing license.
|
||||
|
||||
REA supports Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, and VS Code. Existing REA registrations are selected by default during setup; other detected agents remain unselected until chosen. Other agents can use the [manual MCP configuration](#manual-mcp-configuration).
|
||||
|
||||
### Install the skill separately
|
||||
Add the skill to your AI coding assistant for richer context:
|
||||
|
||||
```bash
|
||||
npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
```
|
||||
|
||||
This installs agent instructions, not MCP registration or analysis engines.
|
||||
Follow the skill's [conditional connection guide](skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed):
|
||||
prepare a scoped `npx rea-agents setup` plan if registration is missing, review
|
||||
and approve its actual changes, then restart/reconnect and verify the tools.
|
||||
Guided setup above installs a version-matched skill by default. The skills.sh
|
||||
route uses repository instructions, which can be ahead of the released server;
|
||||
see [released package and main](docs/installation.md#released-package-and-main).
|
||||
The skill provides REA's investigation workflow. Run setup above to connect REA to your agent and configure analysis tools. Setup already installs a version-matched skill by default; this command installs the repository version.
|
||||
|
||||
### With an agent (recommended)
|
||||
|
||||
After setup, restart your agent and [describe the app or feature](#just-ask-your-agent) you want to understand. Hopper can run in demo mode; if it shows a first-run prompt, choose the demo or enter an existing license.
|
||||
|
||||
REA supports Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI, Command Code, and VS Code. Existing REA registrations are selected by default during setup; other detected agents remain unselected until chosen. Other agents can use the [manual MCP configuration](#manual-mcp-configuration).
|
||||
|
||||
### First result from the terminal
|
||||
|
||||
@@ -142,6 +97,40 @@ MCP setup, Hopper, Ghidra, or executing the application. For a native app, confi
|
||||
its engine first, then use `analyze` with that app's path. Run `doctor` when you
|
||||
need diagnosis; it is not a prerequisite for each analysis.
|
||||
|
||||
### Check readiness for the task at hand
|
||||
|
||||
`rea doctor` without options is an audit of the whole integration. It checks
|
||||
every detected agent registration, the installed skill, and every optional
|
||||
analysis engine, so it can report `healthy: false` while your current task works.
|
||||
Choose a readiness scope for the work you are doing:
|
||||
|
||||
| Task | Readiness check |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| Static JavaScript/Electron analysis | None. Run `analyze-javascript-application` directly. |
|
||||
| Troubleshoot one analysis engine | `rea doctor --provider ghidra --json` (or `hopper`, `ida`) |
|
||||
| Check one agent's MCP registration | `rea doctor --client codex --json` (see [client IDs](docs/installation.md#supported-agents)) |
|
||||
| Check the installed skill | `rea doctor --skill --json` |
|
||||
|
||||
A scoped report has `scope.mode: "explicit"`. Only `scope_checks` determine
|
||||
`healthy` and the exit status. Everything else is listed in
|
||||
`informational_checks`; you don't need to fix it for this task. For example, a
|
||||
missing engine you are not using needs no repair. `environment_healthy` still
|
||||
summarizes the full audit. `--target` adds a target check, but without a scope
|
||||
option the report remains audit-wide.
|
||||
|
||||
When more than one installed engine supports a native target, REA does not pick
|
||||
one: opening the target fails with `code: "capability_unavailable"`,
|
||||
`details.selection_reason: "ambiguous"`, and the providers in
|
||||
`details.candidate_ids`. Choose one once: pass `--provider` on
|
||||
the CLI or `provider_id` on `open_binary`, or set `REA_ANALYSIS_PROVIDER` as a
|
||||
standing preference. An explicit selector overrides the environment variable.
|
||||
The session keeps that choice and never falls back to another engine.
|
||||
Recovery depends on `details.selection_reason`. For `ambiguous`, choose one of
|
||||
the candidates. For `provider_unavailable`, the engine you selected needs repair,
|
||||
so run `rea doctor --provider <id> --json` and follow its remediation. Neither
|
||||
reason means you have to install every engine. See
|
||||
[Choosing a deep-analysis provider](#choosing-a-deep-analysis-provider).
|
||||
|
||||
### Install the rea command
|
||||
|
||||
Install the command-line interface:
|
||||
@@ -168,7 +157,7 @@ external-tool prerequisites depend on the selected workflow; the native
|
||||
provider guides describe their supported platforms.
|
||||
|
||||
- macOS 12 or newer
|
||||
- Ubuntu 24.04+, Fedora 41+, or 64-bit Arch Linux
|
||||
- Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, or CachyOS
|
||||
- Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+
|
||||
- npm; REA does not require or install a particular npm version
|
||||
|
||||
@@ -176,9 +165,9 @@ Deep native binary analysis requires [Hopper](https://www.hopperapp.com/), [Ghid
|
||||
|
||||
Firmware region inspection and explicit extraction use caller-supplied Binwalk and Unblob on Linux. See [Firmware analysis](docs/firmware-analysis.md) for setup, provenance, resource limits and native handoff.
|
||||
|
||||
Static APK analysis uses a separately supplied headless JADX JAR and Java, with no emulator or APK execution. See [Android analysis](docs/android-analysis.md) for setup, CLI/MCP operations, coverage and public test fixtures.
|
||||
Static APK analysis uses a separately supplied headless JADX JAR and a full JDK, with no emulator or APK execution. See [Android analysis](docs/android-analysis.md) for setup, CLI/MCP operations, coverage and public test fixtures. Authenticated IPA and macOS `.app`, ZIP, or DMG inventory Evidence can be projected into bundle anatomy, such as XPC services, app extensions, login items, privileged helpers, and launchd plists, with [Apple application analysis](docs/apple-application-analysis.md).
|
||||
|
||||
Repository main includes experimental Windows x64 Ghidra support for native x86-64 PE applications on local NTFS, with bundled Job Object, private-DACL, and path-admission controls. Check the [release boundary](docs/installation.md#released-package-and-main) before expecting this from an npm package. See [Windows Ghidra P0](docs/windows-ghidra-p0.md) for prerequisites and verified scope.
|
||||
Repository main and npm 4.1.0 include experimental Windows x64 Ghidra support for native x86-64 PE applications on local NTFS, with bundled Job Object, private-DACL, and path-admission controls. Check the [release boundary](docs/installation.md#released-package-and-main) before expecting this from an older npm package. See [Windows Ghidra P0](docs/windows-ghidra-p0.md) for prerequisites and verified scope.
|
||||
|
||||
If something is not working, run:
|
||||
|
||||
@@ -194,7 +183,7 @@ On macOS, setup can install Hopper in `~/Applications` after approval. It verifi
|
||||
|
||||
On supported Linux distributions, setup can install Hopper and its demo-session dependencies through your system package manager. You may see a system authorization prompt. Demo sessions use a private virtual display, leaving your desktop alone. See [Hopper installation](docs/installation.md#hopper) for download verification and platform details.
|
||||
|
||||
The normal Linux launcher is `/opt/hopper/bin/Hopper`. If Hopper was installed elsewhere:
|
||||
REA prefers an executable `/opt/hopper/bin/Hopper` on Linux. If it is unavailable, REA automatically checks `~/.local/share/rea/hopper/bin/Hopper`. If Hopper was installed elsewhere:
|
||||
|
||||
```bash
|
||||
export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper
|
||||
@@ -204,16 +193,16 @@ rea doctor --json
|
||||
If doctor reports a missing analysis engine even though the file exists, inspect shared-library resolution with:
|
||||
|
||||
```bash
|
||||
ldd /opt/hopper/bin/Hopper | grep 'not found'
|
||||
ldd /absolute/path/to/Hopper | grep 'not found'
|
||||
```
|
||||
|
||||
Install the missing packages and rerun `rea setup`. The Linux demo needs Xvfb, Python 3, X11, and XTEST; approved setup installs these dependencies. If you use the curl installer, add `~/.local/bin` to your shell `PATH` when needed.
|
||||
|
||||
REA defaults `HOPPER_LAUNCHER_PATH` to `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` on macOS and `/opt/hopper/bin/Hopper` on Linux. Explicit configuration always takes precedence.
|
||||
REA uses `/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` by default on macOS. On Linux it prefers executable `/opt/hopper/bin/Hopper`, then executable `~/.local/share/rea/hopper/bin/Hopper`, and keeps `/opt/hopper/bin/Hopper` as the diagnostic fallback if neither exists. Explicit `HOPPER_LAUNCHER_PATH` configuration always takes precedence.
|
||||
|
||||
### Ghidra analysis provider
|
||||
|
||||
Already use Ghidra? REA can connect it to your agent on Linux x64 or macOS x64/arm64. It requires **Ghidra 12.1.4** and a **64-bit JDK 21**. On macOS, your Ghidra installation must also include the native decompiler for your architecture.
|
||||
Already use Ghidra? REA can connect it to your agent on Linux x64 or macOS x64/arm64. It accepts **Ghidra 12.1.x** and the **64-bit full JDK** that installation declares (`application.java.min` through `application.java.max`). Current 12.1 releases require JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4 and JDK 21. On macOS, your Ghidra installation must also include the native decompiler for your architecture.
|
||||
|
||||
Set the installation paths, then run setup:
|
||||
|
||||
@@ -297,6 +286,11 @@ Import an older source tree as a reference. REA keeps it separate from observati
|
||||
rea import-reference-source /absolute/path/to/source
|
||||
```
|
||||
|
||||
Historical-source import requires safe no-follow file opens and currently runs
|
||||
on Linux and macOS. Native Windows reports `unsupported_host`; run the import
|
||||
with Linux REA in WSL or on another supported host. Changing permissions or
|
||||
reinstalling REA does not enable this Windows workflow.
|
||||
|
||||
Imports read the path supplied to the command. File names do not cause automatic omissions; files are represented by hashes and metadata. To exclude selected paths, set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON string array of ignore patterns. Exports never replace an existing file unless `--overwrite` is explicit.
|
||||
|
||||
Use a snapshot to save successful analysis results and reuse them on later runs. REA reuses a result only when the target bytes, operation, parameters, analysis tool, and settings match. It does not cache changes or cursor-dependent calls. Snapshot files stay local and use owner-only permissions.
|
||||
@@ -314,6 +308,49 @@ result is returned. Pass `snapshot_path` and, when required, `overwrite: true` t
|
||||
`close_binary` to save atomically before Hopper resources are released. If the
|
||||
save fails, REA deliberately leaves the session open.
|
||||
|
||||
## Just ask your agent
|
||||
|
||||
After [setup](#quick-start), restart your agent and ask:
|
||||
|
||||
```text
|
||||
Understand how search works in the Notes app, show me the evidence, and build a
|
||||
similar feature for my project.
|
||||
```
|
||||
|
||||
Replace Notes with the app you want to understand, or ask for an overview first.
|
||||
|
||||
## The investigation model
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Decompile</strong><br /><br />
|
||||
Open an app and recover readable code, strings, names, and other clues about how it works.
|
||||
</td>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Understand</strong><br /><br />
|
||||
Follow the code from one part of the app to another until the agent can explain how a feature actually works.
|
||||
</td>
|
||||
<td width="33%" valign="top">
|
||||
<strong>Recreate</strong><br /><br />
|
||||
Turn what the agent learned into a feature for your own product, adapted to your stack, interface, and requirements.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
REA shows how it reached its conclusions. It does not claim to recover original source code or automatically clone an application.
|
||||
|
||||
## Why REA
|
||||
|
||||
| | |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| **Built for agents** | Ask what an app does and let your agent inspect it instead of guessing. |
|
||||
| **CLI and MCP** | Run the same reverse-engineering capabilities from your terminal or agent. |
|
||||
| **Guided setup** | Configure your agent, connect an existing analysis tool, or install Hopper with your approval. |
|
||||
| **From insight to code** | Understand a feature, then build your own version in the same coding session. |
|
||||
| **Local by design** | Analysis runs on your supported local host. REA does not upload the app to a hosted analysis service. |
|
||||
| **Keeps context** | Investigate several apps without starting over for every question. |
|
||||
|
||||
## One prompt, a full investigation
|
||||
|
||||
```text
|
||||
@@ -334,6 +371,21 @@ REA gives the agent a clear path from that request to working code:
|
||||
|
||||
REA handles the app analysis in steps 1 through 5. The agent performs step 6 with its normal file-editing and test tools, using what it learned about the app.
|
||||
|
||||
## Showcase
|
||||
|
||||
### DX-Ball: game reconstruction (in progress)
|
||||
|
||||
[DX-Ball](https://github.com/N0zoM1z0/dx-ball) follows the journey from a classic
|
||||
Windows game's executable to maintainable C. Using REA's Ghidra provider, the
|
||||
project traces functions, game state and dependencies, then checks reconstructed
|
||||
behavior with original-x86 differential tests and pinned-compiler replay.
|
||||
|
||||
A [sound-pan investigation](https://github.com/N0zoM1z0/dx-ball/blob/main/docs/GAMEPLAY_OWNER.md)
|
||||
turns incomplete pseudocode into a C implementation that passes 3,205 original-x86
|
||||
cases and reproduces all 63 compiled function bytes. Follow its
|
||||
[REA workflow](https://github.com/N0zoM1z0/dx-ball/blob/main/docs/REA.md)
|
||||
from binary evidence to reconstruction.
|
||||
|
||||
## What agents can do
|
||||
|
||||
- Investigate a feature you like and build a version tailored to your own product.
|
||||
@@ -349,20 +401,20 @@ See [native investigation](docs/native-investigation.md) for keyed archives, ins
|
||||
|
||||
## Tool catalog for investigation
|
||||
|
||||
| Tool family | Count | Examples |
|
||||
| ------------------------- | ----: | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Native inspection | 41 | functions, pseudocode, assembly, strings, symbols, calls, references, annotations, byte reads, and file offsets |
|
||||
| Investigation workflows | 14 | app overviews, function dossiers, native APIs and dispatch, batch decompilation, feature traces, call paths, call graphs, Swift and Objective-C discovery |
|
||||
| Native macOS utilities | 7 | Mach-O metadata, code signatures, plists, architectures, and Swift demangling without launching Hopper |
|
||||
| Artifact graph | 5 | directory and package inventories, compiled Interface Builder files, Apple asset catalogs, and extraction |
|
||||
| Managed PE/CLI | 7 | .NET identity, metadata, CIL instructions, native dependencies, reconstruction imports, and build comparisons |
|
||||
| Firmware | 2 | Linux firmware region inspection and explicit extraction |
|
||||
| Android APK | 5 | package and manifest declarations, class search, member inventories, method decompilation, and incoming static references |
|
||||
| Browser observation | 9 | page structure, network metadata, scripts, source maps, WebMCP discovery, screenshots, and capture comparisons |
|
||||
| Electron analysis | 5 | renderer observation, static app mapping, and static/runtime reconciliation |
|
||||
| JavaScript runtime | 2 | Node/Electron Inspector target discovery, script locations, and execution-context events |
|
||||
| Application workflows | 7 | cross-layer feature traces, build comparisons, historical source mapping, static return-shape comparison, and reconstruction checks |
|
||||
| Workspace and observation | 21 | sessions, evidence bundles, navigation context, process/artifact/function comparisons, and open-question tracking |
|
||||
| Tool family | Count | Examples |
|
||||
| ------------------------- | ----: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Native inspection | 41 | functions, pseudocode, assembly, strings, symbols, calls, references, annotations, byte reads, and file offsets |
|
||||
| Investigation workflows | 14 | app overviews, function dossiers, native APIs and dispatch, batch decompilation, feature traces, call paths, call graphs, Swift and Objective-C discovery |
|
||||
| Native macOS utilities | 7 | Mach-O metadata, code signatures, plists, architectures, and Swift demangling without launching Hopper |
|
||||
| Artifact graph | 5 | directory and package inventories, compiled Interface Builder files, Apple asset catalogs, and extraction |
|
||||
| Managed PE/CLI | 7 | .NET identity, metadata, CIL instructions, native dependencies, reconstruction imports, and build comparisons |
|
||||
| Firmware | 2 | Linux firmware region inspection and explicit extraction |
|
||||
| Android APK | 5 | package and manifest declarations, class search, member inventories, method decompilation, and incoming static references |
|
||||
| Browser observation | 11 | page structure, network metadata, scripts, source maps, WebMCP discovery, screenshots, and capture comparisons |
|
||||
| Electron analysis | 5 | renderer observation, static app mapping, and static/runtime reconciliation |
|
||||
| JavaScript runtime | 2 | Node/Electron Inspector target discovery, script locations, and execution-context events |
|
||||
| Application workflows | 13 | captured website script export; Android/Apple inventory projections; cross-layer feature traces, build comparisons, historical source mapping, static return-shape comparison, and reconstruction checks |
|
||||
| Workspace and observation | 21 | sessions, evidence bundles, navigation context, process/artifact/function comparisons, and open-question tracking |
|
||||
|
||||
The public interface describes what the agent is trying to learn. Providers decide how to answer. macOS utilities handle common semantic inspection without launching Hopper; Hopper handles deeper native analysis; the process harness records direct behavioral captures.
|
||||
|
||||
@@ -370,12 +422,37 @@ The public interface describes what the agent is trying to learn. Providers deci
|
||||
|
||||
REA supports native application, JavaScript, Electron, .NET, and browser investigation on macOS and Linux. Individual tools have platform and runtime prerequisites. `rea capabilities` and `rea providers` describe the binary-session providers and auxiliary operations; they are not an inventory of every browser, Android, or application workflow. Use the connected MCP tool list and `binary_session` tool availability for the full MCP surface, and the relevant guide for each tool's prerequisites. Repository main can be ahead of [the npm release](docs/installation.md#released-package-and-main).
|
||||
|
||||
Static Android APK inspection is verified on Linux with headless JADX; see [Android analysis](docs/android-analysis.md) for its separate prerequisites and coverage.
|
||||
Static Android APK inspection supports Linux and macOS; the current metadata bridge is verified on macOS arm64 with headless JADX; see [Android analysis](docs/android-analysis.md) for its separate prerequisites and coverage.
|
||||
|
||||
- **Native binaries:** Open Mach-O, ELF, PE, and Mac `.app` targets through a selected deep provider. Hopper and Ghidra cover broad inventory and function analysis; the IDA adapter supplies its documented read-only function/string operations. Hopper also accepts `.hop` databases and supports annotations.
|
||||
- **Packages and resources:** Inspect directories, ZIP, APK, IPA, ASAR, plists, compiled Interface Builder files, and Apple asset catalogs. Artifact requests name the input and requested extraction or traversal directly; macOS DMG traversal also requires the host's native mounting support.
|
||||
- **JavaScript and Electron:** Map modules, imports, source maps, routes, IPC channels, storage, and native add-ons without running the app. Compare builds and trace a feature across the recovered graph. Dynamic and ambiguous relationships remain unresolved. See [JavaScript application workflows](docs/javascript-application-workflows.md).
|
||||
- **Websites:** Inspect a selected page in an existing Chrome-family browser. Capture page structure, network metadata, script evidence, and screenshots requested by the call. Passive observation does not navigate or execute page JavaScript. See [browser observation](docs/browser-observation.md).
|
||||
|
||||
Export retained scripts with `export_web_scripts` / `rea export-web-scripts`
|
||||
into a verified local directory, then use the existing JavaScript analysis
|
||||
and tracing tools. Source URLs, frame or transaction references, competing
|
||||
versions, and missing-source states remain inline. See
|
||||
[captured website scripts](docs/website-script-export.md).
|
||||
Inspect a selected node's listener sources or observe an externally triggered
|
||||
execution window with `inspect_web_event_listeners` / `observe_web_execution`.
|
||||
Precise coverage resets counters and affects optimized execution; see
|
||||
[website runtime attribution](docs/web-runtime.md).
|
||||
Trace one exported script's native imports with `trace_web_module_imports` /
|
||||
`rea trace-web-module-imports`, preserving query/fragment identity and optional
|
||||
import-map context. Requires caller-supplied Chromium via
|
||||
`REA_BROWSER_EXECUTABLE`; see [module relationships](docs/website-module-trace.md).
|
||||
|
||||
- **Source locations:** Trace one retained website script point through a selected
|
||||
local source map with `rea trace-web-source-location`. See
|
||||
[captured website source locations](docs/web-source-location.md) for byte
|
||||
identities, embedded original content and coverage limits.
|
||||
Recover readable modules from selected local bundles with
|
||||
`recover_javascript_sources` / `rea recover-javascript-sources`, then pass
|
||||
the returned `analysis_input` to static analysis. This optional Linux x64
|
||||
adapter requires caller-supplied Wakaru 1.13.0; see
|
||||
[JavaScript source recovery](docs/javascript-recovery.md).
|
||||
|
||||
- **Electron and Node runtime observation:** Inspect selected Electron pages or attach to a Node/Electron V8 Inspector target. Inspector observation records script locations and execution-context events; it does not infer imports, IPC activity, or which modules executed. See [runtime observation](docs/javascript-runtime-observation.md).
|
||||
- **.NET assemblies:** Inspect metadata and CIL instructions, compare builds, and check declared native dependencies without loading or running the assembly. Imported decompiler output is labeled as analyst inference. See [managed-code analysis](docs/managed-code-analysis.md).
|
||||
- **Controlled behavior capture:** Run process, browser, or Electron scenarios with the target, actions, and lifecycle declared in each request, then compare the resulting evidence. Missing observations cannot establish that two runs behaved the same way.
|
||||
@@ -457,7 +534,7 @@ The [current status](#current-status) section describes shipped capabilities. Th
|
||||
|
||||
1. **Expand browser and Electron interaction:** add scenario actions beyond the current click and wait operations.
|
||||
2. **Observe native apps at runtime:** explore LLDB, Frida, system logs, and native API tracing.
|
||||
3. **Evaluate more tools and targets:** assess IDA/Hex-Rays, Binary Ninja, Rizin, LIEF, Windows-native tools, mobile apps, and firmware.
|
||||
3. **Evaluate more tools and targets:** assess Binary Ninja, Rizin, LIEF, Windows-native tools, mobile apps, and firmware. IDA support already ships via the bring-your-own upstream adapter; see the [IDA provider guide](docs/ida-provider.md).
|
||||
|
||||
Setup already lets you choose agent integration and Hopper installation. Support for installing additional analysis tools is future work, described in the [installation roadmap](docs/roadmap.md).
|
||||
|
||||
@@ -476,7 +553,7 @@ Setup offers supported agent integrations for selection. Existing REA registrati
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,9 +707,9 @@ Process Capture records behavior and is not a security sandbox.
|
||||
Capture a scenario or compare two saved Process Capture Evidence records:
|
||||
|
||||
```bash
|
||||
rea capture-process ./scenario.json > authority.json
|
||||
rea capture-process ./reconstruction.json > reconstruction.json
|
||||
rea compare-process-captures authority.json reconstruction.json
|
||||
rea capture-process ./authority-scenario.json --json > authority-capture.json
|
||||
rea capture-process ./reconstruction-scenario.json --json > reconstruction-capture.json
|
||||
rea compare-process-captures authority-capture.json reconstruction-capture.json --json
|
||||
```
|
||||
|
||||
The comparison reports each observed dimension separately and identifies the
|
||||
@@ -640,9 +717,16 @@ first terminal, interaction, exit, filesystem, or process divergence.
|
||||
See [Process Capture](docs/process-capture.md) for scenario fields, limits, and
|
||||
evidence boundaries.
|
||||
|
||||
REA installs a prebuilt PTY backend for supported macOS, Linux, and Windows
|
||||
architectures. If the capability check reports that the backend is unavailable,
|
||||
reinstall REA for the current platform and architecture.
|
||||
Process Capture currently runs on Linux and macOS with a working native PTY
|
||||
backend. Native Windows capture remains unavailable until the PTY adapter can
|
||||
verify descendant cleanup; installing or reinstalling its Windows PTY binary
|
||||
does not enable capture. For Linux commands, run Linux REA inside WSL.
|
||||
Comparing existing capture Evidence remains available on Windows.
|
||||
|
||||
On supported capture hosts, a missing or incompatible PTY binary can be fixed
|
||||
by reinstalling REA for the current platform, architecture, and Node.js version
|
||||
with optional dependencies enabled. Use separate scenario and output files;
|
||||
`--json` produces the JSON consumed by the comparison command.
|
||||
|
||||
ASAR inventory verifies Electron integrity metadata for both archive entries
|
||||
and `.asar.unpacked` companion files. Integrity failures identify the logical
|
||||
@@ -727,3 +811,17 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution ch
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
## Star history
|
||||
|
||||
<a href="https://www.star-history.com/?repos=morluto%2Frea&type=date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=morluto/rea&type=date&theme=dark&legend=top-left" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=morluto/rea&type=date" />
|
||||
<img alt="REA GitHub star history" src="https://api.star-history.com/chart?repos=morluto/rea&type=date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
REA provides tools for lawful reverse-engineering research, analysis, and reconstruction. You are responsible for obtaining any required authorization and complying with applicable laws. The project does not endorse illegal or unauthorized use.
|
||||
+47
-58
@@ -2,9 +2,9 @@
|
||||
|
||||
[English](README.md) · [简体中文](README_zh.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · **العربية**
|
||||
|
||||
# REA: هندسة أي شيء عكسيًا
|
||||
# REA: اعكس هندسة أي شيء
|
||||
|
||||
### استقصِ سلوك التطبيقات والملفات التنفيذية الأصلية باستخدام وكيلك.
|
||||
### وسيط MCP واحد للهندسة العكسية عبر الملفات التنفيذية والتطبيقات وسلوك وقت التشغيل.
|
||||
|
||||
**اعثر على ميزة تعجبك. افهم آلية عملها. ابنها بالطريقة التي تريدها.**
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/82054/weekly?language=TypeScript" alt="morluto/rea | Trendshift" width="250" height="55" /></a>
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[الموقع (بالإنجليزية)](https://morluto.github.io/rea/) · [أدلة الاستخدام](https://morluto.github.io/rea/guides/) · [دراسة حالة DX-Ball](https://morluto.github.io/rea/showcase/dx-ball/)**
|
||||
|
||||
[البدء السريع](#البدء-السريع) · [حالة الدعم](#حالة-الدعم) · [من الملف التنفيذي إلى السلوك](#من-الملف-التنفيذي-إلى-السلوك) · [منصة أدوات التحقيق](#منصة-أدوات-التحقيق) · [خطة العمل](#خطة-العمل) · [كيف يعمل؟](#كيف-يعمل)
|
||||
|
||||
@@ -47,7 +49,7 @@
|
||||
|
||||
يوفر REA أدوات لتحليل الملفات التنفيذية الأصلية وتطبيقات JavaScript وElectron وتجميعات .NET والمواقع. يمكنك استخدامها من وكيلك أو من الطرفية. يعمل التحليل محليًا، وتتضمن النتائج الأدلة والقيود.
|
||||
|
||||
يضبط Setup إعدادات الوكيل ويربطه بتثبيت موجود من Hopper أو Ghidra. ويمكنه تثبيت Hopper بعد موافقتك.
|
||||
يضبط Setup إعدادات الوكيل ويربطه بتثبيت موجود من Hopper أو Ghidra، ويثبّت سير عمل REA الموجّه. ويمكنه تثبيت Hopper بعد موافقتك. ولا يحتاج التحليل الثابت لتطبيقات JavaScript إلى أي محرك.
|
||||
|
||||
## اطلب من وكيلك مباشرة
|
||||
|
||||
@@ -69,14 +71,14 @@
|
||||
|
||||
## لماذا REA؟
|
||||
|
||||
| | |
|
||||
| -------------------- | ------------------------------------------------------------------------------------- |
|
||||
| **مصمم للوكلاء** | اسأل عما يفعله تطبيق مترجم ودع الوكيل يجمع الأدلة بدلًا من التخمين. |
|
||||
| **CLI وMCP** | استخدم قدرات الهندسة العكسية نفسها من الطرفية أو وكيل البرمجة. |
|
||||
| **يتولى التعقيد** | يدير REA إعداد الأدوات وفتح التطبيق واستمرار الاستقصاء والتنظيف بعد الانتهاء. |
|
||||
| **سير عمل كامل** | انتقل من النظرة الأولى إلى الشيفرة شبه المصدرية وعلاقات الاستدعاء والأنواع وأدلة التنفيذ. |
|
||||
| **محلي حسب التصميم** | يجري التحليل على جهازك المدعوم ولا يرفع REA الملف التنفيذي إلى خدمة تحليل مستضافة. |
|
||||
| **يحافظ على السياق** | استقصِ عدة ملفات تنفيذية من دون بدء التحليل من جديد عند كل سؤال. |
|
||||
| | |
|
||||
| ------------------------ | ---------------------------------------------------------------------------- |
|
||||
| **مصمم للوكلاء** | اسأل عما يفعله التطبيق ودع الوكيل يفحصه بدلًا من التخمين. |
|
||||
| **CLI وMCP** | استخدم قدرات الهندسة العكسية نفسها من الطرفية أو الوكيل. |
|
||||
| **إعداد موجّه** | اضبط وكيلك، واربط أداة تحليل موجودة، أو ثبّت Hopper بموافقتك. |
|
||||
| **من الفهم إلى الشيفرة** | افهم الميزة ثم ابنِ نسختك الخاصة في جلسة البرمجة نفسها. |
|
||||
| **محلي حسب التصميم** | يجري التحليل على مضيفك المحلي المدعوم، ولا يرفع REA التطبيق إلى خدمة مستضافة. |
|
||||
| **يحافظ على السياق** | استقصِ عدة تطبيقات دون بدء التحليل من جديد عند كل سؤال. |
|
||||
|
||||
## البدء السريع
|
||||
|
||||
@@ -98,28 +100,23 @@ npx rea-agents setup
|
||||
|
||||
يمكن تشغيل Hopper في الوضع التجريبي. إذا ظهرت رسالة عند التشغيل الأول، فاختر التجربة أو أدخل ترخيصًا موجودًا.
|
||||
|
||||
### Skill instructions and a first CLI result
|
||||
### مهارة مساعد البرمجة (اختياري)
|
||||
|
||||
أضف المهارة إلى مساعد البرمجة للحصول على سياق أغنى:
|
||||
|
||||
```bash
|
||||
npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
```
|
||||
|
||||
This installs instructions, not MCP registration or analysis engines. Follow the
|
||||
[conditional connection guide](skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
`npx rea-agents setup` installs a matching skill and registers selected clients
|
||||
only after showing the plan and receiving approval. Restart/reconnect after
|
||||
registration, then verify REA tools in the active session. Repository skills can
|
||||
be ahead of the [released package](docs/installation.md#released-package-and-main).
|
||||
توفر المهارة سير عمل التحقيق في REA. نفّذ الإعداد أعلاه لربط REA بالوكيل وضبط أدوات التحليل. يثبّت الإعداد افتراضيًا مهارة مطابقة للإصدار؛ وهذا الأمر يثبّت نسخة المستودع.
|
||||
|
||||
For your extracted JavaScript/Electron tree or ASAR, no MCP setup or native
|
||||
engine is required:
|
||||
لشجرة JavaScript/Electron المستخرجة أو ملف ASAR، لا يلزم إعداد MCP ولا محرك أصلي:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
Replace the path with your target; Windows paths such as `"D:/apps/example"`
|
||||
are supported by this static workflow. Native analysis requires its own engine.
|
||||
استبدل المسار بهدفك (مثل `"D:/apps/example"` على Windows). يدعم سير العمل الثابت هذه المسارات، بينما يتطلب التحليل الأصلي محركه الخاص.
|
||||
|
||||
### تثبيت أمر rea
|
||||
|
||||
@@ -141,22 +138,22 @@ rea setup
|
||||
### المتطلبات
|
||||
|
||||
- macOS 12 أو أحدث
|
||||
- Ubuntu 24.04+ أو Fedora 41+ أو Arch Linux بنواة 64 بت
|
||||
- Ubuntu 24.04+ أو Fedora 41+ أو Arch Linux بنواة 64 بت أو CachyOS
|
||||
- Node.js 22.x (>=22.19) أو 24.x (>=24.11) أو 26+ وnpm
|
||||
|
||||
يتطلب تحليل الملفات التنفيذية الأصلية Hopper أو Ghidra. Hopper برنامج منفصل، وللوضع التجريبي قيود يحددها المورّد، لكن الترخيص المدفوع ليس إلزاميًا.
|
||||
|
||||
يدعم Ghidra نظامي Linux x64 وmacOS x64/arm64. ثبت Ghidra 12.1.4 وJDK 21 الكامل بنواة 64 بت بصورة منفصلة، ثم اضبط REA لاستخدامهما. يحتاج macOS أيضًا إلى أداة فك الترجمة الأصلية المطابقة لمعمارية الجهاز.
|
||||
يدعم Ghidra نظامي Linux x64 وmacOS x64/arm64. ثبت Ghidra 12.1.x وJDK الكامل بنواة 64 بت الذي يعلنه ذلك التثبيت (`application.java.min` حتى `application.java.max`) بصورة منفصلة، ثم اضبط REA لاستخدامهما. تتطلب إصدارات 12.1 الحالية JDK 21 أو أحدث ولا تحدد حدًا أعلى. جرى التحقق من الجسر مع Ghidra 12.1.4 وJDK 21. يحتاج macOS أيضًا إلى أداة فك الترجمة الأصلية المطابقة لمعمارية الجهاز.
|
||||
|
||||
يستطيع Setup التحقق من التثبيت وحفظ المسارات؛ ولا يثبت أو يحدّث Ghidra أو Java أو Node.js أو npm أو Homebrew.
|
||||
|
||||
Repository main includes experimental Windows x64 Ghidra P0 for native, non-managed, non-DLL x86-64 PE applications on fixed local NTFS. The native controls are implemented; npm 4.0.1 does not contain them. See the [release boundary](docs/installation.md#released-package-and-main) and [Windows Ghidra P0](docs/windows-ghidra-p0.md).
|
||||
يتضمن فرع main والحزمة npm 4.1.0 دعمًا تجريبيًا لـ Windows x64 Ghidra P0 لتطبيقات PE الأصلية x86-64 (غير المُدارة وغير DLL) على NTFS محلي، مع ضوابط Job Object وقوائم DACL الخاصة وقبول المسارات المجمّعة. تحقق من [حد الإصدار](docs/installation.md#released-package-and-main) قبل توقع ذلك من حزمة npm أقدم. راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للمتطلبات والنطاق الموثق.
|
||||
|
||||
### تشخيص المشكلات
|
||||
|
||||
يشخّص `npx -y rea-agents@latest doctor` الجهاز والاعتماديات وأدوات التحليل وإعدادات الوكيل من دون تغييرها. أضف `--json` للحصول على تشخيص منظم.
|
||||
|
||||
مسار Hopper الافتراضي على Linux هو `/opt/hopper/bin/Hopper`. استخدم `HOPPER_LAUNCHER_PATH` للمسارات الأخرى. إذا كان الملف موجودًا لكن محرك التحليل لا يظهر، فشغّل `ldd /opt/hopper/bin/Hopper | grep 'not found'` للتحقق من المكتبات الناقصة. راجع [دليل Hopper](docs/installation.md#hopper).
|
||||
على Linux، يفضّل REA الملف التنفيذي `/opt/hopper/bin/Hopper`، وإذا لم يكن متاحًا يتحقق تلقائيًا من `~/.local/share/rea/hopper/bin/Hopper`. استخدم `HOPPER_LAUNCHER_PATH` للمسارات الأخرى. إذا كان الملف موجودًا لكن محرك التحليل لا يظهر، فشغّل `ldd /absolute/path/to/Hopper | grep 'not found'` على مسار Hopper الفعلي للتحقق من المكتبات الناقصة. راجع [دليل Hopper](docs/installation.md#hopper).
|
||||
|
||||
### التحديث والإزالة
|
||||
|
||||
@@ -166,21 +163,12 @@ Repository main includes experimental Windows x64 Ghidra P0 for native, non-mana
|
||||
|
||||
## حالة الدعم
|
||||
|
||||
The current repository capabilities and platform requirements are described in
|
||||
[the English support guide](README.md#current-status). Main can be ahead of the
|
||||
[npm release](docs/installation.md#released-package-and-main).
|
||||
تُوصف قدرات المستودع الحالية ومتطلبات الأنظمة في [دليل الدعم الإنجليزي](README.md#current-status). قد يسبق فرع main [إصدار npm](docs/installation.md#released-package-and-main).
|
||||
|
||||
- Ghidra provides 25 read-only operations on Linux x64, macOS x64/arm64, and the
|
||||
experimental Windows x64 P0 boundary. Linux/macOS additionally supports atomic
|
||||
session function annotations. Windows P0 is read-only; Ghidra has no GUI controls.
|
||||
- Static Android inspection has separate bring-your-own JADX/Java prerequisites;
|
||||
real-provider verification covers Linux. See [Android analysis](docs/android-analysis.md).
|
||||
- Browser, Electron, and process requests specify their target, actions, and
|
||||
lifecycle directly. They require the host's actual access, without separate
|
||||
REA permission grants. Setup configuration writes and Hopper installation
|
||||
still require approval of their exact plan.
|
||||
- `rea capabilities` describes binary-session operations rather than every
|
||||
application/runtime tool. Consult the full MCP tool list and relevant guide.
|
||||
- يوفر Ghidra 25 عملية للقراءة فقط على Linux x64 وmacOS x64/arm64 وحدود Windows x64 P0 التجريبية. يدعم Linux/macOS أيضًا تعليقات الدوال الذرية على مستوى الجلسة. نظام Windows P0 للقراءة فقط، ولا يوفر Ghidra أي تحكم في الواجهة.
|
||||
- يتطلب الفحص الثابت لنظام Android تجهيز JADX/Java بشكل منفصل؛ ويغطي التحقق بالموفر الحقيقي نظام Linux. راجع [تحليل Android](docs/android-analysis.md).
|
||||
- تحدد طلبات المتصفح وElectron والعمليات هدفها وإجراءاتها ودورة حياتها مباشرة، وتعتمد على وصول المضيف الفعلي دون منح أذونات منفصلة من REA. ما زالت كتابة إعدادات Setup وتثبيت Hopper تتطلب الموافقة على خطتها الدقيقة.
|
||||
- يصف `rea capabilities` عمليات جلسة الملف التنفيذي لا كل أدوات التطبيقات ووقت التشغيل. راجع قائمة أدوات MCP الكاملة والدليل ذا الصلة.
|
||||
|
||||
## استقصاء كامل بطلب واحد
|
||||
|
||||
@@ -225,20 +213,20 @@ The current repository capabilities and platform requirements are described in
|
||||
|
||||
## منصة أدوات التحقيق
|
||||
|
||||
| عائلة الأدوات | العدد | الاستخدام |
|
||||
| --------------------- | ----: | -------------------------------------------------------------------------------------- |
|
||||
| فحص الملفات التنفيذية | 41 | الدوال والشيفرة شبه المصدرية والتعليمات والسلاسل والرموز والمراجع والتعليقات |
|
||||
| التحليل المركب | 14 | النظرة العامة وتحليل الدوال وفك الترجمة الدفعي ومخطط الاستدعاء وفحص Swift وObjC |
|
||||
| أدوات macOS الأصلية | 7 | بيانات Mach-O والتوقيعات وplist والمعماريات واستعادة أسماء Swift |
|
||||
| الملفات والحزم | 5 | فحص الدلائل والحزم وInterface Builder وموارد Apple والاستخراج |
|
||||
| .NET PE/CLI | 7 | هوية التجميع والبيانات الوصفية وCIL والاستدعاءات الأصلية والمقارنة واستيراد إعادة البناء |
|
||||
| البرامج الثابتة | 2 | فحص مناطق البرامج الثابتة واستخراجها الصريح على Linux |
|
||||
| Android APK | 5 | بيانات الحزمة وmanifest والبحث عن الأصناف والأعضاء وفك ترجمة الأساليب والمراجع الثابتة |
|
||||
| مراقبة المتصفح | 9 | الصفحات والبرامج النصية وخرائط المصدر وWebMCP والصور ومقارنة الالتقاط |
|
||||
| تحليل Electron | 5 | مراقبة الصفحات وبنية التطبيق وربط النتائج الثابتة بنتائج التشغيل |
|
||||
| وقت تشغيل JavaScript | 2 | الاتصال بهدف Node/Electron Inspector موجود لمراقبة البرامج وسياقات التنفيذ |
|
||||
| سير عمل التطبيقات | 7 | تتبع الميزات ومقارنة الإصدارات وبنية قيم الإرجاع والتحقق من إعادة الإنشاء |
|
||||
| جلسة الملف التنفيذي | 21 | تبديل الأهداف وحفظ الأدلة ومقارنة العمليات والدوال وتسجيل الأسئلة غير المحسومة |
|
||||
| عائلة الأدوات | العدد | الاستخدام |
|
||||
| -------------------- | ----: | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| الفحص الأصلي | 41 | الدوال والشيفرة شبه المصدرية والتعليمات والسلاسل والرموز والاستدعاءات والمراجع والتعليقات وقراءة البايتات ومواضع الملفات |
|
||||
| سير عمل التحقيق | 14 | نظرات عامة على التطبيقات وملفات الدوال وواجهات API الأصلية والتوزيع وفك الترجمة الدفعي وتتبع الميزات ومسارات الاستدعاء ومخططاته واكتشاف Swift وObjective-C |
|
||||
| أدوات macOS الأصلية | 7 | بيانات Mach-O والتوقيعات وplist والمعماريات واستعادة أسماء Swift دون تشغيل Hopper |
|
||||
| مخطط المخرجات | 5 | جرد الدلائل والحزم وملفات Interface Builder المترجمة وكتالوجات موارد Apple والاستخراج |
|
||||
| PE/CLI المُدار | 7 | هوية تجميعات .NET وبياناتها الوصفية وتعليمات CIL والاعتماديات الأصلية واستيراد إعادة البناء ومقارنة الإصدارات |
|
||||
| البرامج الثابتة | 2 | فحص مناطق البرامج الثابتة على Linux واستخراجها الصريح |
|
||||
| Android APK | 5 | بيانات الحزمة وmanifest والبحث عن الأصناف وقوائم الأعضاء وفك ترجمة الأساليب والمراجع الثابتة |
|
||||
| مراقبة المتصفح | 11 | بنية الصفحات وبيانات الشبكة والبرامج النصية وخرائط المصدر واكتشاف WebMCP والصور ومقارنة الالتقاط |
|
||||
| تحليل Electron | 5 | مراقبة العارضين وخريطة التطبيق الثابتة والمواءمة بين نتائج الثبات والتشغيل |
|
||||
| وقت تشغيل JavaScript | 2 | اكتشاف أهداف Node/Electron Inspector ومواضع البرامج وأحداث سياقات التنفيذ |
|
||||
| سير عمل التطبيقات | 13 | تصدير النصوص البرمجية الملتقطة، تتبع الميزات عبر الطبقات ومقارنة الإصدارات ومواءمة المصدر التاريخي ومقارنة أشكال القيم المعادة والتحقق من إعادة الإنشاء |
|
||||
| مساحة العمل والملاحظة | 21 | الجلسات وحزم الأدلة وسياق التنقل ومقارنة العمليات والمخرجات والدوال وتسجيل الأسئلة غير المحسومة |
|
||||
|
||||
## خطة العمل
|
||||
|
||||
@@ -255,7 +243,7 @@ The current repository capabilities and platform requirements are described in
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,8 +287,9 @@ npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/
|
||||
|
||||
## الأمان والخصوصية
|
||||
|
||||
- يبقى تحليل الملف التنفيذي محليًا بين REA ومزوّد التحليل المختار.
|
||||
- يبقى التحليل محليًا بين REA وموفر التحليل المختار عبر مقابس محلية خاصة، ومع IDA عبر تسجيل MCP المحلي المضبوط.
|
||||
- لا يرفع REA الأهداف إلى خدمة مستضافة.
|
||||
- تعمل أدوات التحليل والأهداف المطلقة بصلاحيات المستخدم نفسه؛ وما زال التقاط واجهة النظام على macOS يعتمد على الوصول إلى تسهيلات الاستخدام وتسجيل الشاشة. ولا ينفّذ التحليل الثابت للـ JavaScript الوحدات المستخرجة.
|
||||
- تستخدم جلسات Ghidra مشروعًا مؤقتًا معزولًا ولا تفتح مشاريع Ghidra التي يملكها المستخدم أو تعدّلها.
|
||||
- راجع الملفات التنفيذية غير الموثوقة واعزلها كما تفعل مع أي مدخل أصلي قد يكون ضارًا.
|
||||
- أبلغ عن الثغرات وفق [سياسة الأمان](SECURITY.md)، وليس عبر قضية عامة.
|
||||
@@ -324,7 +313,7 @@ npx -y rea-agents@latest compare /absolute/path/to/left-evidence.json /absolute/
|
||||
<details>
|
||||
<summary><strong>هل يعمل REA على Linux أو Windows؟</strong></summary>
|
||||
|
||||
يدعم REA نظام macOS 12+ وUbuntu 24.04+ وFedora 41+ وArch Linux بنواة 64 بت. يعمل تحليل Ghidra للقراءة فقط على Linux x64 وmacOS x64/arm64 مع Ghidra 12.1.4 وJDK 21 الكامل. تحليل Ghidra على Windows غير متاح بسبب الضوابط الأصلية غير المنفذة؛ راجع [Windows Ghidra P0](docs/windows-ghidra-p0.md).
|
||||
يدعم REA نظام macOS 12+ وUbuntu 24.04+ وFedora 41+ وArch Linux بنواة 64 بت وCachyOS. يعمل تحليل Ghidra للقراءة فقط على Linux x64 وmacOS x64/arm64 مع Ghidra 12.1.x وJDK الكامل الذي يعلنه التثبيت (JDK 21 أو أحدث دون حد أعلى في إصدارات 12.1 الحالية)، إضافة إلى حد Windows x64 P0 التجريبي لتطبيقات PE الأصلية x86-64 على NTFS محلي؛ راجع [دليل Windows Ghidra P0](docs/windows-ghidra-p0.md) للنطاق الموثق.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
+41
-53
@@ -15,7 +15,9 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/82054/weekly?language=TypeScript" alt="morluto/rea | Trendshift" width="250" height="55" /></a>
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[ウェブサイト(英語)](https://morluto.github.io/rea/) · [ガイド](https://morluto.github.io/rea/guides/) · [DX-Ball の事例](https://morluto.github.io/rea/showcase/dx-ball/)**
|
||||
|
||||
[クイックスタート](#クイックスタート) · [現在の対応状況](#現在の対応状況) · [バイナリから動作へ](#バイナリから動作へ) · [調査ツールカタログ](#調査ツールカタログ) · [ロードマップ](#ロードマップ) · [仕組み](#仕組み)
|
||||
|
||||
@@ -69,14 +71,14 @@ REA は調査をバイナリ上の根拠に結び付けます。元のソース
|
||||
|
||||
## REA を選ぶ理由
|
||||
|
||||
| | |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| **エージェント向け** | コンパイル済みアプリについて質問し、推測ではなく根拠を集めさせることができます。 |
|
||||
| **CLI と MCP** | ターミナルとコーディングエージェントから同じリバースエンジニアリング機能を使えます。 |
|
||||
| **複雑さを処理** | ツール設定、アプリの読み込み、調査の維持、終了後のクリーンアップを REA が担います。 |
|
||||
| **一連の調査に対応** | 最初の概要から疑似コード、呼び出し関係、型、実装の手掛かりまで掘り下げられます。 |
|
||||
| **ローカルで解析** | 解析は対応するローカルホストで実行され、REA がバイナリをホスト型解析サービスへ送ることはありません。 |
|
||||
| **コンテキストを維持** | 質問ごとに解析を最初からやり直さず、複数のバイナリを続けて調査できます。 |
|
||||
| | |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| **エージェント向け** | アプリの動作について質問し、推測ではなくエージェントに調査させることができます。 |
|
||||
| **CLI と MCP** | ターミナルとエージェントから同じリバースエンジニアリング機能を使えます。 |
|
||||
| **ガイド付きセットアップ** | エージェントの設定、既存の解析ツールへの接続、承認後の Hopper インストールに対応します。 |
|
||||
| **洞察からコードへ** | 機能を理解したら、同じコーディングセッションで自分のプロダクト向けに実装できます。 |
|
||||
| **ローカルで解析** | 解析は対応するローカルホストで実行され、REA がバイナリをホスト型解析サービスへ送ることはありません。 |
|
||||
| **コンテキストを維持** | 質問ごとに解析を最初からやり直さず、複数のアプリを続けて調査できます。 |
|
||||
|
||||
## クイックスタート
|
||||
|
||||
@@ -98,28 +100,23 @@ Setup は最初に連携するエージェントを複数選択できるよう
|
||||
|
||||
Hopper はデモモードで使えます。初回起動の画面ではデモを選択するか、既存のライセンスを入力してください。
|
||||
|
||||
### Skill instructions and a first CLI result
|
||||
### AI コーディングアシスタント向けスキル(任意)
|
||||
|
||||
AI コーディングアシスタントにスキルを追加すると、より豊富なコンテキストが得られます:
|
||||
|
||||
```bash
|
||||
npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
```
|
||||
|
||||
This installs instructions, not MCP registration or analysis engines. Follow the
|
||||
[conditional connection guide](skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
`npx rea-agents setup` installs a matching skill and registers selected clients
|
||||
only after showing the plan and receiving approval. Restart/reconnect after
|
||||
registration, then verify REA tools in the active session. Repository skills can
|
||||
be ahead of the [released package](docs/installation.md#released-package-and-main).
|
||||
このスキルは REA の調査ワークフローを提供します。上記の setup を実行して REA をエージェントに接続し、解析ツールを設定してください。Setup は既定でバージョンに合ったスキルをインストールします。このコマンドではリポジトリ版のスキルをインストールします。
|
||||
|
||||
For your extracted JavaScript/Electron tree or ASAR, no MCP setup or native
|
||||
engine is required:
|
||||
展開済みの JavaScript/Electron アプリのツリーや ASAR であれば、MCP の設定もネイティブエンジンも不要です:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
Replace the path with your target; Windows paths such as `"D:/apps/example"`
|
||||
are supported by this static workflow. Native analysis requires its own engine.
|
||||
パスを調査対象に置き換えてください(Windows では `"D:/apps/example"` のようなパス)。この静的ワークフローではそのようなパスも扱えます。ネイティブ解析には専用のエンジン設定が必要です。
|
||||
|
||||
### rea コマンドをインストール
|
||||
|
||||
@@ -141,22 +138,22 @@ rea setup
|
||||
### 要件
|
||||
|
||||
- macOS 12 以降
|
||||
- Ubuntu 24.04+、Fedora 41+、または 64 ビット Arch Linux
|
||||
- Ubuntu 24.04+、Fedora 41+、64 ビット Arch Linux、または CachyOS
|
||||
- Node.js 22.x (>=22.19)、24.x (>=24.11)、または 26+ と npm
|
||||
|
||||
ネイティブバイナリ解析には Hopper または Ghidra が必要です。Hopper は別製品です。デモにはベンダー所定の制限がありますが、有料ライセンスは必須ではありません。
|
||||
|
||||
Ghidra は Linux x64 と macOS x64/arm64 に対応します。Ghidra 12.1.4 と完全な 64 ビット JDK 21 を別途インストールし、REA で使うように設定してください。macOS ではホストのアーキテクチャに合うネイティブデコンパイラーも必要です。
|
||||
Ghidra は Linux x64 と macOS x64/arm64 に対応します。Ghidra 12.1.x と、そのインストールが宣言する完全な 64 ビット JDK(`application.java.min` から `application.java.max` まで)を別途インストールし、REA で使うように設定してください。現行の 12.1 リリースは JDK 21 以降を要求し、上限は設定しません。ブリッジは Ghidra 12.1.4 と JDK 21 で検証しています。macOS ではホストのアーキテクチャに合うネイティブデコンパイラーも必要です。
|
||||
|
||||
Setup はインストールを確認し、パスを保存できます。Ghidra、Java、Node.js、npm、Homebrew のインストールや更新は行いません。
|
||||
|
||||
Repository main includes experimental Windows x64 Ghidra P0 for native, non-managed, non-DLL x86-64 PE applications on fixed local NTFS. The native controls are implemented; npm 4.0.1 does not contain them. See the [release boundary](docs/installation.md#released-package-and-main) and [Windows Ghidra P0](docs/windows-ghidra-p0.md).
|
||||
リポジトリの main と npm 4.1.0 には、ローカル NTFS 上のネイティブ x86-64 PE アプリケーション(非マネージド、非 DLL 向け)を対象とする実験的な Windows x64 Ghidra P0 対応が含まれています。Job Object、プライベート DACL、パス許可の各制御が同梱されています。古い npm パッケージに期待する前に[リリース境界](docs/installation.md#released-package-and-main)を確認してください。前提条件と検証済みの範囲は [Windows Ghidra P0](docs/windows-ghidra-p0.md) を参照してください。
|
||||
|
||||
### トラブルシューティング
|
||||
|
||||
`npx -y rea-agents@latest doctor` はホスト、依存関係、解析ツール、エージェント設定を変更せずに確認します。構造化された診断には `--json` を追加してください。
|
||||
|
||||
Linux の既定ランチャーは `/opt/hopper/bin/Hopper` です。別の場所には `HOPPER_LAUNCHER_PATH` を設定します。ファイルがあるのに解析エンジンが見つからない場合は、`ldd /opt/hopper/bin/Hopper | grep 'not found'` で不足ライブラリを確認してください。詳しくは [Hopper ガイド](docs/installation.md#hopper)を参照してください。
|
||||
Linux では実行可能な `/opt/hopper/bin/Hopper` を優先し、利用できない場合は `~/.local/share/rea/hopper/bin/Hopper` を自動的に確認します。それ以外の場所では `HOPPER_LAUNCHER_PATH` を設定します。ファイルがあるのに解析エンジンが見つからない場合は、実際の Hopper パスに対して `ldd /absolute/path/to/Hopper | grep 'not found'` を実行し、不足ライブラリを確認してください。詳しくは [Hopper ガイド](docs/installation.md#hopper)を参照してください。
|
||||
|
||||
### 更新とアンインストール
|
||||
|
||||
@@ -166,21 +163,12 @@ Linux の既定ランチャーは `/opt/hopper/bin/Hopper` です。別の場所
|
||||
|
||||
## 現在の対応状況
|
||||
|
||||
The current repository capabilities and platform requirements are described in
|
||||
[the English support guide](README.md#current-status). Main can be ahead of the
|
||||
[npm release](docs/installation.md#released-package-and-main).
|
||||
リポジトリの現在の機能とプラットフォーム要件は[英語の対応状況ガイド](README.md#current-status)で説明しています。main は [npm リリース](docs/installation.md#released-package-and-main)より先行する場合があります。
|
||||
|
||||
- Ghidra provides 25 read-only operations on Linux x64, macOS x64/arm64, and the
|
||||
experimental Windows x64 P0 boundary. Linux/macOS additionally supports atomic
|
||||
session function annotations. Windows P0 is read-only; Ghidra has no GUI controls.
|
||||
- Static Android inspection has separate bring-your-own JADX/Java prerequisites;
|
||||
real-provider verification covers Linux. See [Android analysis](docs/android-analysis.md).
|
||||
- Browser, Electron, and process requests specify their target, actions, and
|
||||
lifecycle directly. They require the host's actual access, without separate
|
||||
REA permission grants. Setup configuration writes and Hopper installation
|
||||
still require approval of their exact plan.
|
||||
- `rea capabilities` describes binary-session operations rather than every
|
||||
application/runtime tool. Consult the full MCP tool list and relevant guide.
|
||||
- Ghidra は Linux x64、macOS x64/arm64、実験的な Windows x64 P0 境界で 25 件の読み取り専用操作を提供します。Linux/macOS ではアトミックなセッション内関数注釈にも対応します。Windows P0 は読み取り専用であり、Ghidra に GUI 操作はありません。
|
||||
- 静的な Android 検査には持ち込みの JADX/Java が別途必要です。実プロバイダー検証は Linux を対象とします。詳しくは[Android 解析](docs/android-analysis.md)を参照してください。
|
||||
- ブラウザー、Electron、プロセスの各リクエストは対象・操作・ライフサイクルを直接指定し、ホストの実際のアクセスに依存します。REA 側の個別の許可付与は不要です。Setup による設定書き込みと Hopper のインストールは、引き続き正確な計画の承認が必要です。
|
||||
- `rea capabilities` はバイナリセッション操作の説明であり、すべてのアプリ/ランタイムツールの目録ではありません。接続済み MCP ツール一覧と関連ガイドを参照してください。
|
||||
|
||||
## ひとつのプロンプトで調査を完結
|
||||
|
||||
@@ -214,20 +202,20 @@ REA は手順 1〜5 のバイナリ解析を処理し、手順 6 はエージェ
|
||||
|
||||
## 調査ツールカタログ
|
||||
|
||||
| ツール分類 | 数 | 用途 |
|
||||
| -------------------- | --: | ------------------------------------------------------------------------------- |
|
||||
| バイナリ検査 | 41 | 関数、疑似コード、アセンブリ、文字列、シンボル、参照、注釈 |
|
||||
| 組み合わせた解析 | 14 | 概要、関数解析、一括逆コンパイル、呼び出しグラフ、Swift と ObjC |
|
||||
| macOS ネイティブ | 7 | Mach-O メタデータ、署名、plist、アーキテクチャ、Swift 名の復元 |
|
||||
| ファイルとパッケージ | 5 | ディレクトリとパッケージ、Interface Builder、Apple リソース、抽出 |
|
||||
| .NET PE/CLI | 7 | アセンブリ識別、メタデータ、CIL、ネイティブ呼び出し、比較、再構築の取り込み |
|
||||
| ファームウェア | 2 | Linux の領域検査と明示的な展開 |
|
||||
| Android APK | 5 | パッケージと manifest、クラス検索、メンバー一覧、メソッド逆コンパイル、静的参照 |
|
||||
| ブラウザー観察 | 9 | ページ、スクリプト、ソースマップ、WebMCP、スクリーンショット、比較 |
|
||||
| Electron 解析 | 5 | ページ観察、アプリ構造、静的・実行時結果の対応付け |
|
||||
| JavaScript 実行時 | 2 | 既存の Node/Electron Inspector に接続し、スクリプトと実行コンテキストを観察 |
|
||||
| アプリのワークフロー | 7 | 機能追跡、バージョン比較、戻り値構造の比較、再実装の検証 |
|
||||
| バイナリセッション | 21 | 対象切り替え、根拠の保存、プロセス・関数比較、未解決事項の記録 |
|
||||
| ツール分類 | 数 | 用途 |
|
||||
| -------------------- | --: | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ネイティブ検査 | 41 | 関数、疑似コード、アセンブリ、文字列、シンボル、呼び出し、参照、注釈、バイト読み取り、ファイルオフセット |
|
||||
| 調査ワークフロー | 14 | アプリ概要、関数ドシエ、ネイティブ API とディスパッチ、一括逆コンパイル、機能トレース、コールパス、コールグラフ、Swift と Objective-C の検出 |
|
||||
| macOS ネイティブ | 7 | Hopper を起動せずに扱える Mach-O メタデータ、署名、plist、アーキテクチャ、Swift 名の復元 |
|
||||
| 成果物グラフ | 5 | ディレクトリとパッケージの目録、コンパイル済み Interface Builder ファイル、Apple アセットカタログ、抽出 |
|
||||
| マネージド PE/CLI | 7 | .NET の識別、メタデータ、CIL 命令、ネイティブ依存関係、再構築インポート、ビルド比較 |
|
||||
| ファームウェア | 2 | Linux ファームウェア領域の検査と明示的な展開 |
|
||||
| Android APK | 5 | パッケージと manifest の宣言、クラス検索、メンバー一覧、メソッド逆コンパイル、静的参照 |
|
||||
| ブラウザー観察 | 11 | ページ構成、ネットワークメタデータ、スクリプト、ソースマップ、WebMCP 検出、スクリーンショット、キャプチャ比較 |
|
||||
| Electron 解析 | 5 | レンダラー観察、静的アプリマッピング、静的/実行時結果の対応付け |
|
||||
| JavaScript 実行時 | 2 | Node/Electron Inspector 対象の検出、スクリプト位置、実行コンテキストイベント |
|
||||
| アプリワークフロー | 13 | 取得した Web スクリプトのエクスポート、層をまたぐ機能トレース、ビルド比較、履歴ソース対応、静的戻り値形状の比較、再実装検証 |
|
||||
| ワークスペースと観察 | 21 | セッション、証跡バンドル、ナビゲーション文脈、プロセス・成果物・関数比較、未解決事項の記録 |
|
||||
|
||||
## ロードマップ
|
||||
|
||||
@@ -244,7 +232,7 @@ Setup は Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsurf
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
-53
@@ -15,7 +15,9 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/82054/weekly?language=TypeScript" alt="morluto/rea | Trendshift" width="250" height="55" /></a>
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[웹사이트(영문)](https://morluto.github.io/rea/) · [가이드](https://morluto.github.io/rea/guides/) · [DX-Ball 사례](https://morluto.github.io/rea/showcase/dx-ball/)**
|
||||
|
||||
[빠른 시작](#빠른-시작) · [현재 지원 범위](#현재-지원-범위) · [바이너리에서 동작까지](#바이너리에서-동작까지) · [조사 도구 카탈로그](#조사-도구-카탈로그) · [로드맵](#로드맵) · [작동 방식](#작동-방식)
|
||||
|
||||
@@ -70,14 +72,14 @@ REA는 분석을 바이너리 증거에 근거하게 합니다. 원본 소스
|
||||
|
||||
## REA를 사용하는 이유
|
||||
|
||||
| | |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| **에이전트용** | 컴파일된 앱에 관해 질문하고 추측 대신 증거를 수집하게 합니다. |
|
||||
| **CLI와 MCP** | 터미널과 코딩 에이전트에서 동일한 리버스 엔지니어링 기능을 사용합니다. |
|
||||
| **복잡성 처리** | 도구 설정, 앱 열기, 조사 유지, 작업 후 정리를 REA가 처리합니다. |
|
||||
| **전체 조사 과정** | 첫 개요에서 의사 코드, 호출 관계, 타입, 구현 단서까지 이어서 조사합니다. |
|
||||
| **로컬 분석** | 분석은 지원되는 로컬 호스트에서 실행되며 REA는 바이너리를 호스팅 분석 서비스에 업로드하지 않습니다. |
|
||||
| **컨텍스트 유지** | 질문마다 분석을 처음부터 시작하지 않고 여러 바이너리를 연속으로 조사합니다. |
|
||||
| | |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| **에이전트용** | 앱의 동작에 대해 질문하고 추측 대신 에이전트가 조사하게 합니다. |
|
||||
| **CLI와 MCP** | 터미널과 에이전트에서 동일한 리버스 엔지니어링 기능을 사용합니다. |
|
||||
| **안내식 설정** | 에이전트 설정, 기존 분석 도구 연결, 승인 후 Hopper 설치를 지원합니다. |
|
||||
| **통찰에서 코드로** | 기능을 이해한 뒤 같은 코딩 세션에서 자신의 제품에 맞는 버전을 구현합니다. |
|
||||
| **로컬 분석** | 분석은 지원되는 로컬 호스트에서 실행되며 REA는 바이너리를 호스팅 분석 서비스에 업로드하지 않습니다. |
|
||||
| **컨텍스트 유지** | 질문마다 분석을 처음부터 시작하지 않고 여러 앱을 연속으로 조사합니다. |
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
@@ -99,28 +101,23 @@ Setup은 먼저 연결할 에이전트를 여러 개 선택하도록 안내합
|
||||
|
||||
Hopper는 데모 모드로 사용할 수 있습니다. 첫 실행 안내가 나오면 데모를 선택하거나 기존 라이선스를 입력하세요.
|
||||
|
||||
### Skill instructions and a first CLI result
|
||||
### AI 코딩 어시스턴트용 스킬(선택)
|
||||
|
||||
AI 코딩 어시스턴트에 스킬을 추가하면 더 풍부한 컨텍스트를 얻을 수 있습니다.
|
||||
|
||||
```bash
|
||||
npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
```
|
||||
|
||||
This installs instructions, not MCP registration or analysis engines. Follow the
|
||||
[conditional connection guide](skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
`npx rea-agents setup` installs a matching skill and registers selected clients
|
||||
only after showing the plan and receiving approval. Restart/reconnect after
|
||||
registration, then verify REA tools in the active session. Repository skills can
|
||||
be ahead of the [released package](docs/installation.md#released-package-and-main).
|
||||
이 스킬은 REA의 조사 워크플로를 제공합니다. 위 setup을 실행해 REA를 에이전트에 연결하고 분석 도구를 설정하세요. Setup은 기본적으로 버전에 맞는 스킬을 설치합니다. 이 명령은 저장소 버전의 스킬을 설치합니다.
|
||||
|
||||
For your extracted JavaScript/Electron tree or ASAR, no MCP setup or native
|
||||
engine is required:
|
||||
압축을 해제한 JavaScript/Electron 앱 트리나 ASAR라면 MCP 설정이나 네이티브 엔진 없이 바로 분석할 수 있습니다.
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
Replace the path with your target; Windows paths such as `"D:/apps/example"`
|
||||
are supported by this static workflow. Native analysis requires its own engine.
|
||||
경로를 조사 대상으로 바꾸세요(Windows에서는 `"D:/apps/example"` 같은 경로). 이 정적 워크플로에서는 이런 경로도 지원됩니다. 네이티브 분석에는 전용 엔진 설정이 필요합니다.
|
||||
|
||||
### rea 명령 설치하기
|
||||
|
||||
@@ -142,22 +139,22 @@ rea setup
|
||||
### 요구 사항
|
||||
|
||||
- macOS 12 이상
|
||||
- Ubuntu 24.04+, Fedora 41+ 또는 64비트 Arch Linux
|
||||
- Ubuntu 24.04+, Fedora 41+, 64비트 Arch Linux 또는 CachyOS
|
||||
- Node.js 22.x (>=22.19), 24.x (>=24.11) 또는 26+와 npm
|
||||
|
||||
네이티브 바이너리 분석에는 Hopper 또는 Ghidra가 필요합니다. Hopper는 별도 소프트웨어입니다. 데모에는 공급업체의 제한이 있지만 유료 라이선스가 필수는 아닙니다.
|
||||
|
||||
Ghidra는 Linux x64와 macOS x64/arm64를 지원합니다. Ghidra 12.1.4와 완전한 64비트 JDK 21을 별도로 설치한 뒤 REA가 사용하도록 설정하세요. macOS에서는 호스트 아키텍처에 맞는 네이티브 디컴파일러도 필요합니다.
|
||||
Ghidra는 Linux x64와 macOS x64/arm64를 지원합니다. Ghidra 12.1.x와 그 설치본이 선언한 완전한 64비트 JDK(`application.java.min`부터 `application.java.max`까지)를 별도로 설치한 뒤 REA가 사용하도록 설정하세요. 현재 12.1 릴리스는 JDK 21 이상을 요구하고 상한은 없습니다. 브리지는 Ghidra 12.1.4와 JDK 21에서 검증됩니다. macOS에서는 호스트 아키텍처에 맞는 네이티브 디컴파일러도 필요합니다.
|
||||
|
||||
Setup은 설치를 확인하고 경로를 저장할 수 있습니다. Ghidra, Java, Node.js, npm, Homebrew를 설치하거나 업데이트하지 않습니다.
|
||||
|
||||
Repository main includes experimental Windows x64 Ghidra P0 for native, non-managed, non-DLL x86-64 PE applications on fixed local NTFS. The native controls are implemented; npm 4.0.1 does not contain them. See the [release boundary](docs/installation.md#released-package-and-main) and [Windows Ghidra P0](docs/windows-ghidra-p0.md).
|
||||
저장소 main과 npm 4.1.0에는 로컬 NTFS상의 네이티브 x86-64 PE 애플리케이션(비관리, 비 DLL 대상)을 위한 실험적인 Windows x64 Ghidra P0 지원이 포함되어 있습니다. Job Object, 비공개 DACL, 경로 승인 제어가 함께 제공됩니다. 이전 npm 패키지에 이 기능이 있을 것으로 기대하기 전에 [릴리스 경계](docs/installation.md#released-package-and-main)를 확인하세요. 전제 조건과 검증 범위는 [Windows Ghidra P0](docs/windows-ghidra-p0.md)를 참고하세요.
|
||||
|
||||
### 문제 해결
|
||||
|
||||
`npx -y rea-agents@latest doctor`는 호스트, 의존성, 분석 도구, 에이전트 설정을 변경 없이 확인합니다. 구조화된 진단에는 `--json`을 추가하세요.
|
||||
|
||||
Linux의 기본 Hopper 실행 파일은 `/opt/hopper/bin/Hopper`입니다. 다른 위치에는 `HOPPER_LAUNCHER_PATH`를 설정하세요. 파일이 있는데도 분석 엔진이 없다고 하면 `ldd /opt/hopper/bin/Hopper | grep 'not found'`로 누락된 라이브러리를 확인하세요. 자세한 내용은 [Hopper 안내](docs/installation.md#hopper)를 참고하세요.
|
||||
Linux에서는 실행 가능한 `/opt/hopper/bin/Hopper`를 우선 사용하고, 사용할 수 없으면 `~/.local/share/rea/hopper/bin/Hopper`를 자동으로 확인합니다. 다른 위치에는 `HOPPER_LAUNCHER_PATH`를 설정하세요. 파일이 있는데도 분석 엔진이 없다고 하면 실제 Hopper 경로에 대해 `ldd /absolute/path/to/Hopper | grep 'not found'`를 실행해 누락된 라이브러리를 확인하세요. 자세한 내용은 [Hopper 안내](docs/installation.md#hopper)를 참고하세요.
|
||||
|
||||
### 업데이트와 제거
|
||||
|
||||
@@ -167,21 +164,12 @@ Linux의 기본 Hopper 실행 파일은 `/opt/hopper/bin/Hopper`입니다. 다
|
||||
|
||||
## 현재 지원 범위
|
||||
|
||||
The current repository capabilities and platform requirements are described in
|
||||
[the English support guide](README.md#current-status). Main can be ahead of the
|
||||
[npm release](docs/installation.md#released-package-and-main).
|
||||
저장소의 현재 기능과 플랫폼 요구 사항은 [영문 지원 가이드](README.md#current-status)에 설명되어 있습니다. main은 [npm 릴리스](docs/installation.md#released-package-and-main)보다 앞설 수 있습니다.
|
||||
|
||||
- Ghidra provides 25 read-only operations on Linux x64, macOS x64/arm64, and the
|
||||
experimental Windows x64 P0 boundary. Linux/macOS additionally supports atomic
|
||||
session function annotations. Windows P0 is read-only; Ghidra has no GUI controls.
|
||||
- Static Android inspection has separate bring-your-own JADX/Java prerequisites;
|
||||
real-provider verification covers Linux. See [Android analysis](docs/android-analysis.md).
|
||||
- Browser, Electron, and process requests specify their target, actions, and
|
||||
lifecycle directly. They require the host's actual access, without separate
|
||||
REA permission grants. Setup configuration writes and Hopper installation
|
||||
still require approval of their exact plan.
|
||||
- `rea capabilities` describes binary-session operations rather than every
|
||||
application/runtime tool. Consult the full MCP tool list and relevant guide.
|
||||
- Ghidra는 Linux x64, macOS x64/arm64, 실험적인 Windows x64 P0 경계에서 25개의 읽기 전용 작업을 제공합니다. Linux/macOS에서는 원자적 세션 함수 주석도 추가로 지원합니다. Windows P0는 읽기 전용이며 Ghidra에는 GUI 제어가 없습니다.
|
||||
- 정적 Android 검사는 별도로 준비한 JADX/Java가 필요합니다. 실제 제공자 검증은 Linux를 대상으로 합니다. [Android 분석](docs/android-analysis.md)을 참고하세요.
|
||||
- 브라우저, Electron, 프로세스 요청은 대상·동작·수명 주기를 직접 지정하며 호스트의 실제 접근에 의존합니다. 별도의 REA 권한 승인은 필요 없습니다. Setup의 설정 쓰기와 Hopper 설치는 여전히 정확한 계획에 대한 승인이 필요합니다.
|
||||
- `rea capabilities`는 모든 앱/런타임 도구의 목록이 아니라 바이너리 세션 작업을 설명합니다. 연결된 MCP 도구 목록과 관련 가이드를 참고하세요.
|
||||
|
||||
## 하나의 프롬프트로 전체 조사
|
||||
|
||||
@@ -215,20 +203,20 @@ REA는 1–5단계의 바이너리 분석을 처리합니다. 6단계는 에이
|
||||
|
||||
## 조사 도구 카탈로그
|
||||
|
||||
| 도구 분류 | 수 | 용도 |
|
||||
| ----------------- | --: | --------------------------------------------------------------------- |
|
||||
| 바이너리 검사 | 41 | 함수, 의사 코드, 어셈블리, 문자열, 심볼, 참조, 주석 |
|
||||
| 결합 분석 | 14 | 개요, 함수 분석, 일괄 디컴파일, 호출 그래프, Swift와 ObjC 검사 |
|
||||
| macOS 네이티브 | 7 | Mach-O 메타데이터, 서명, plist, 아키텍처, Swift 이름 복원 |
|
||||
| 파일과 패키지 | 5 | 디렉터리와 패키지 검사, Interface Builder, Apple 리소스, 추출 |
|
||||
| .NET PE/CLI | 7 | 어셈블리 식별, 메타데이터, CIL, 네이티브 호출, 비교, 재구성 가져오기 |
|
||||
| 펌웨어 | 2 | Linux 펌웨어 영역 검사 및 명시적 추출 |
|
||||
| Android APK | 5 | 패키지와 manifest, 클래스 검색, 멤버 목록, 메서드 디컴파일, 정적 참조 |
|
||||
| 브라우저 관찰 | 9 | 페이지, 스크립트, 소스 맵, WebMCP, 스크린샷, 캡처 비교 |
|
||||
| Electron 분석 | 5 | 페이지 관찰, 앱 구조, 정적·런타임 결과 연결 |
|
||||
| JavaScript 런타임 | 2 | 기존 Node/Electron Inspector에 연결해 스크립트와 실행 컨텍스트 관찰 |
|
||||
| 앱 워크플로 | 7 | 기능 추적, 버전 비교, 반환 구조 비교, 재구현 검증 |
|
||||
| 바이너리 세션 | 21 | 대상 전환, 근거 저장, 프로세스·함수 비교, 미해결 항목 기록 |
|
||||
| 도구 분류 | 수 | 용도 |
|
||||
| ----------------- | --: | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 네이티브 검사 | 41 | 함수, 의사 코드, 어셈블리, 문자열, 심볼, 호출, 참조, 주석, 바이트 읽기, 파일 오프셋 |
|
||||
| 조사 워크플로 | 14 | 앱 개요, 함수 기록서, 네이티브 API와 디스패치, 일괄 디컴파일, 기능 추적, 호출 경로, 호출 그래프, Swift와 Objective-C 탐색 |
|
||||
| macOS 네이티브 | 7 | Hopper 실행 없이 처리하는 Mach-O 메타데이터, 서명, plist, 아키텍처, Swift 이름 복원 |
|
||||
| 아티팩트 그래프 | 5 | 디렉터리와 패키지 목록, 컴파일된 Interface Builder 파일, Apple 애셋 카탈로그, 추출 |
|
||||
| 관리 PE/CLI | 7 | .NET 식별, 메타데이터, CIL 명령어, 네이티브 종속성, 재구성 가져오기, 빌드 비교 |
|
||||
| 펌웨어 | 2 | Linux 펌웨어 영역 검사 및 명시적 추출 |
|
||||
| Android APK | 5 | 패키지와 manifest 선언, 클래스 검색, 멤버 목록, 메서드 디컴파일, 정적 참조 |
|
||||
| 브라우저 관찰 | 11 | 페이지 구조, 네트워크 메타데이터, 스크립트, 소스 맵, WebMCP 탐색, 스크린샷, 캡처 비교 |
|
||||
| Electron 분석 | 5 | 렌더러 관찰, 정적 앱 매핑, 정적·런타임 결과 연결 |
|
||||
| JavaScript 런타임 | 2 | Node/Electron Inspector 대상 탐색, 스크립트 위치, 실행 컨텍스트 이벤트 |
|
||||
| 앱 워크플로 | 13 | 캡처한 웹 스크립트 내보내기, 계층 간 기능 추적, 빌드 비교, 히스토리 소스 매핑, 정적 반환 구조 비교, 재구현 검증 |
|
||||
| 작업 공간과 관찰 | 21 | 세션, 근거 번들, 탐색 컨텍스트, 프로세스·아티팩트·함수 비교, 미해결 항목 기록 |
|
||||
|
||||
## 로드맵
|
||||
|
||||
@@ -245,7 +233,7 @@ Setup은 Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-52
@@ -15,7 +15,9 @@
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/82054/weekly?language=TypeScript" alt="morluto/rea | Trendshift" width="250" height="55" /></a>
|
||||
<a href="https://trendshift.io/repositories/82054?utm_source=repository-badge&utm_medium=badge&utm_campaign=badge-repository-82054" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/repositories/82054" alt="morluto%2Frea | Trendshift" width="250" height="55"/></a>
|
||||
|
||||
**[网站(英文)](https://morluto.github.io/rea/) · [使用指南](https://morluto.github.io/rea/guides/) · [DX-Ball 案例](https://morluto.github.io/rea/showcase/dx-ball/)**
|
||||
|
||||
[快速开始](#快速开始) · [当前状态](#当前状态) · [从二进制到行为](#从二进制到行为) · [调查工具目录](#调查工具目录) · [路线图](#路线图) · [工作原理](#工作原理)
|
||||
|
||||
@@ -71,12 +73,12 @@ REA 让调查始终以二进制证据为依据。它不会声称能恢复原始
|
||||
|
||||
| | |
|
||||
| ---------------- | -------------------------------------------------------------------- |
|
||||
| **为智能体设计** | 直接询问编译后应用的行为,让智能体搜集证据,而不是猜测。 |
|
||||
| **CLI 与 MCP** | 在终端或编程智能体中使用同一套逆向工程能力。 |
|
||||
| **处理复杂流程** | REA 负责工具设置、打开应用、维持调查过程,并在完成后清理资源。 |
|
||||
| **完整工作流** | 从初步概览推进到伪代码、调用关系、类型和实现线索。 |
|
||||
| **为智能体设计** | 直接询问应用的行为,让智能体去检查,而不是猜测。 |
|
||||
| **CLI 与 MCP** | 在终端或智能体中使用同一套逆向工程能力。 |
|
||||
| **引导式设置** | 配置智能体、连接已有分析工具,或在你批准后安装 Hopper。 |
|
||||
| **从洞察到代码** | 理解功能后,在同一次编程会话中构建属于你自己的版本。 |
|
||||
| **本地运行** | 分析在受支持的本机系统上运行;REA 不会把二进制上传到托管式分析服务。 |
|
||||
| **保留上下文** | 连续调查多个二进制文件,无需为每个问题重新开始整个分析过程。 |
|
||||
| **保留上下文** | 连续调查多个应用,无需为每个问题重新开始整个分析过程。 |
|
||||
|
||||
## 快速开始
|
||||
|
||||
@@ -98,28 +100,23 @@ Setup 会先展示变更并备份已有配置。要求与更多选项见[安装
|
||||
|
||||
Hopper 支持演示模式。如果首次启动时出现提示,选择演示模式或输入已有许可证。
|
||||
|
||||
### Skill instructions and a first CLI result
|
||||
### AI 编程助手技能(可选)
|
||||
|
||||
为你的 AI 编程助手添加 skill,以获得更丰富的上下文:
|
||||
|
||||
```bash
|
||||
npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
```
|
||||
|
||||
This installs instructions, not MCP registration or analysis engines. Follow the
|
||||
[conditional connection guide](skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
`npx rea-agents setup` installs a matching skill and registers selected clients
|
||||
only after showing the plan and receiving approval. Restart/reconnect after
|
||||
registration, then verify REA tools in the active session. Repository skills can
|
||||
be ahead of the [released package](docs/installation.md#released-package-and-main).
|
||||
该 skill 提供 REA 的调查工作流。先运行上文的 setup,把 REA 连接到你的智能体并配置分析工具。Setup 默认会安装版本匹配的 skill;这条命令安装的是仓库版本。
|
||||
|
||||
For your extracted JavaScript/Electron tree or ASAR, no MCP setup or native
|
||||
engine is required:
|
||||
对于已解包的 JavaScript/Electron 应用目录或 ASAR,无需 MCP 配置,也无需原生引擎:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
Replace the path with your target; Windows paths such as `"D:/apps/example"`
|
||||
are supported by this static workflow. Native analysis requires its own engine.
|
||||
把路径换成你的目标(例如 Windows 上的 `"D:/apps/example"`)。这条静态流程支持此类路径。原生分析仍需要先配置对应的分析引擎。
|
||||
|
||||
### 安装 rea 命令
|
||||
|
||||
@@ -138,25 +135,25 @@ npm install --global rea-agents
|
||||
rea setup
|
||||
```
|
||||
|
||||
### 运行要求
|
||||
### 系统要求
|
||||
|
||||
- macOS 12 或更高版本
|
||||
- Ubuntu 24.04+、Fedora 41+ 或 64 位 Arch Linux
|
||||
- Ubuntu 24.04+、Fedora 41+、64 位 Arch Linux 或 CachyOS
|
||||
- Node.js 22.x (>=22.19)、24.x (>=24.11) 或 26+,以及 npm
|
||||
|
||||
原生二进制分析需要 Hopper 或 Ghidra。Hopper 是独立软件;演示模式有厂商规定的限制,不要求购买许可证。
|
||||
|
||||
Ghidra 支持 Linux x64 和 macOS x64/arm64。单独安装 Ghidra 12.1.4 和完整的 64 位 JDK 21,然后配置 REA 使用它们。macOS 还需要对应架构的原生反编译器。
|
||||
Ghidra 支持 Linux x64 和 macOS x64/arm64。单独安装 Ghidra 12.1.x,以及该安装在 `application.java.min` 到 `application.java.max` 中声明的完整 64 位 JDK,然后配置 REA 使用它们。当前 12.1 发行版要求 JDK 21 或更新版本,并且不设置上限。桥接在 Ghidra 12.1.4 和 JDK 21 上完成验证。macOS 还需要对应架构的原生反编译器。
|
||||
|
||||
Setup 可以验证安装并保存路径,不会安装或升级 Ghidra、Java、Node.js、npm 或 Homebrew。
|
||||
|
||||
Repository main includes experimental Windows x64 Ghidra P0 for native, non-managed, non-DLL x86-64 PE applications on fixed local NTFS. The native controls are implemented; npm 4.0.1 does not contain them. See the [release boundary](docs/installation.md#released-package-and-main) and [Windows Ghidra P0](docs/windows-ghidra-p0.md).
|
||||
仓库 main 分支与 npm 4.1.0 已包含实验性的 Windows x64 Ghidra P0 支持,适用于本地 NTFS 上的原生 x86-64 PE 应用(非托管、非 DLL),并附带 Job Object、私有 DACL 与路径准入控制。请先确认[发布边界](docs/installation.md#released-package-and-main),再假定旧版 npm 包具备该能力。详见 [Windows Ghidra P0](docs/windows-ghidra-p0.md) 的前置条件与已验证范围。
|
||||
|
||||
### 故障排查
|
||||
### 故障排除
|
||||
|
||||
运行 `npx -y rea-agents@latest doctor`,检查主机、依赖、分析工具和智能体配置。该命令不会修改文件。添加 `--json` 可获取结构化诊断。
|
||||
|
||||
Linux 上的默认 Hopper 启动器为 `/opt/hopper/bin/Hopper`。其他路径可通过 `HOPPER_LAUNCHER_PATH` 指定。如果文件存在但仍报告缺少分析引擎,运行 `ldd /opt/hopper/bin/Hopper | grep 'not found'` 检查缺少的系统库。安装详情见 [Hopper 指南](docs/installation.md#hopper)。
|
||||
Linux 上 REA 会优先使用可执行的 `/opt/hopper/bin/Hopper`;如果不可用,则自动检查 `~/.local/share/rea/hopper/bin/Hopper`。其他路径可通过 `HOPPER_LAUNCHER_PATH` 指定。如果文件存在但仍报告缺少分析引擎,请对实际 Hopper 路径运行 `ldd /absolute/path/to/Hopper | grep 'not found'` 检查缺少的系统库。安装详情见 [Hopper 指南](docs/installation.md#hopper)。
|
||||
|
||||
### 更新与卸载
|
||||
|
||||
@@ -166,21 +163,12 @@ Linux 上的默认 Hopper 启动器为 `/opt/hopper/bin/Hopper`。其他路径
|
||||
|
||||
## 当前状态
|
||||
|
||||
The current repository capabilities and platform requirements are described in
|
||||
[the English support guide](README.md#current-status). Main can be ahead of the
|
||||
[npm release](docs/installation.md#released-package-and-main).
|
||||
仓库当前能力与平台要求见[英文支持指南](README.md#current-status)。main 分支可能领先于 [npm 发布版](docs/installation.md#released-package-and-main)。
|
||||
|
||||
- Ghidra provides 25 read-only operations on Linux x64, macOS x64/arm64, and the
|
||||
experimental Windows x64 P0 boundary. Linux/macOS additionally supports atomic
|
||||
session function annotations. Windows P0 is read-only; Ghidra has no GUI controls.
|
||||
- Static Android inspection has separate bring-your-own JADX/Java prerequisites;
|
||||
real-provider verification covers Linux. See [Android analysis](docs/android-analysis.md).
|
||||
- Browser, Electron, and process requests specify their target, actions, and
|
||||
lifecycle directly. They require the host's actual access, without separate
|
||||
REA permission grants. Setup configuration writes and Hopper installation
|
||||
still require approval of their exact plan.
|
||||
- `rea capabilities` describes binary-session operations rather than every
|
||||
application/runtime tool. Consult the full MCP tool list and relevant guide.
|
||||
- Ghidra 在 Linux x64、macOS x64/arm64 以及实验性的 Windows x64 P0 边界上提供 25 个只读操作。Linux/macOS 额外支持原子化的会话级函数注释。Windows P0 为只读;Ghidra 不提供 GUI 控制。
|
||||
- 静态 Android 检查需要自备 JADX/Java,前置条件独立;真机验证覆盖 Linux。详见 [Android 分析](docs/android-analysis.md)。
|
||||
- 浏览器、Electron 与进程请求需直接声明目标、动作与生命周期,并依赖宿主的实际访问权限,无需 REA 另行授权。Setup 的配置写入与 Hopper 安装仍需逐项批准确切计划。
|
||||
- `rea capabilities` 描述的是二进制会话操作,而非全部应用/运行时工具。请以已连接 MCP 工具列表与相关指南为准。
|
||||
|
||||
## 一个提示词,完成一次完整调查
|
||||
|
||||
@@ -214,20 +202,20 @@ REA 负责第 1–5 步中的二进制分析。第 6 步由智能体使用其常
|
||||
|
||||
## 调查工具目录
|
||||
|
||||
| 工具类别 | 数量 | 用途 |
|
||||
| ----------------- | ---: | -------------------------------------------------------------- |
|
||||
| 二进制检查 | 41 | 函数、伪代码、汇编、字符串、符号、段、引用与注释 |
|
||||
| 组合分析 | 14 | 概览、函数分析、批量反编译、调用图、Swift 与 ObjC 检查 |
|
||||
| macOS 原生工具 | 7 | Mach-O 元数据、代码签名、plist、架构与 Swift 符号还原 |
|
||||
| 制品检查 | 5 | 目录与软件包检查、Interface Builder、Apple 资源目录与提取 |
|
||||
| .NET PE/CLI | 7 | 程序集身份、元数据、CIL、原生调用声明、成员比较与重建导入 |
|
||||
| 韧体 | 2 | Linux 韧体区域识别与显式解包 |
|
||||
| Android APK | 5 | 包与 manifest 声明、class 搜索、成员清单、方法反编译与静态引用 |
|
||||
| 浏览器观察 | 9 | 页面、脚本、来源映射、WebMCP 工具、截图与捕获比较 |
|
||||
| Electron 分析 | 5 | 页面观察、应用结构与静态/运行时结果关联 |
|
||||
| JavaScript 运行时 | 2 | 连接现有 Node/Electron Inspector,观察脚本与执行上下文 |
|
||||
| 应用工作流 | 7 | 功能追踪、版本比较、返回结构比较与重建验证 |
|
||||
| 二进制会话 | 21 | 目标切换、证据包、进程与函数比较、待解决问题记录 |
|
||||
| 工具类别 | 数量 | 用途 |
|
||||
| ----------------- | ---: | ------------------------------------------------------------------------------------------------------ |
|
||||
| 原生检查 | 41 | 函数、伪代码、汇编、字符串、符号、调用、引用、注释、字节读取与文件偏移 |
|
||||
| 调查工作流 | 14 | 应用概览、函数档案、原生 API 与分发、批量反编译、功能追踪、调用路径与调用图、Swift 与 Objective-C 发现 |
|
||||
| macOS 原生工具 | 7 | Mach-O 元数据、代码签名、plist、架构与 Swift 符号还原,无需启动 Hopper |
|
||||
| 产物图 | 5 | 目录与软件包清单、编译后的 Interface Builder 文件、Apple 资源目录与提取 |
|
||||
| 托管 PE/CLI | 7 | .NET 程序集身份、元数据、CIL 指令、原生依赖声明、重建导入与构建比较 |
|
||||
| 固件 | 2 | Linux 固件区域检查与显式提取 |
|
||||
| Android APK | 5 | 包与 manifest 声明、类搜索、成员清单、方法反编译与静态引用 |
|
||||
| 浏览器观察 | 11 | 页面结构、网络元数据、脚本、来源映射、WebMCP 发现、截图与捕获比较 |
|
||||
| Electron 分析 | 5 | 渲染进程观察、静态应用映射、静态/运行时结果关联 |
|
||||
| JavaScript 运行时 | 2 | Node/Electron Inspector 目标发现、脚本位置与执行上下文事件 |
|
||||
| 应用工作流 | 13 | 捕获的网站脚本导出、跨层功能追踪、构建比较、历史源码映射、静态返回结构比较与重建验证 |
|
||||
| 工作区与观察 | 21 | 会话、证据包、导航上下文、进程/产物/函数比较与待解决问题跟踪 |
|
||||
|
||||
## 路线图
|
||||
|
||||
@@ -244,7 +232,7 @@ Setup 支持 Claude Code、Claude Desktop、Codex、Cursor、Gemini CLI、Windsu
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
import com.atxx.jhmcp.*;
|
||||
import com.google.gson.*;
|
||||
import io.modelcontextprotocol.kotlin.sdk.server.*;
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.*;
|
||||
import jadx.api.*;
|
||||
import jadx.core.dex.nodes.*;
|
||||
import java.io.PrintStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
import kotlin.Unit;
|
||||
import kotlin.coroutines.EmptyCoroutineContext;
|
||||
import kotlinx.coroutines.BuildersKt;
|
||||
import kotlinx.io.CoreKt;
|
||||
import kotlinx.io.JvmCoreKt;
|
||||
import kotlinx.serialization.json.JsonObject;
|
||||
|
||||
/** REA-owned metadata boundary for the unmodified jadx-headless-mcp 0.7.1 JAR. */
|
||||
class ReaJadxBridge {
|
||||
private static final Gson JSON = new Gson();
|
||||
private final SessionHolder holder;
|
||||
private JadxSession indexedSession;
|
||||
private Map<String, List<ClassSnapshot>> classes = Map.of();
|
||||
|
||||
private record MethodSnapshot(String name, String shortId, Map<String, Object> summary) {}
|
||||
private record ClassSnapshot(JavaClass node, List<MethodSnapshot> methods, Map<String, Object> summary) {}
|
||||
|
||||
private ReaJadxBridge(SessionHolder holder) { this.holder = holder; }
|
||||
|
||||
private static CallToolResult text(String value, boolean failed) {
|
||||
return new CallToolResult(List.of(new TextContent(value, null, null)), failed, null, null);
|
||||
}
|
||||
|
||||
private static CallToolResult json(Object value) { return text(JSON.toJson(value), false); }
|
||||
|
||||
private static String string(JsonObject args, String name) {
|
||||
JsonElement value = JsonParser.parseString(args.toString()).getAsJsonObject().get(name);
|
||||
if (value == null || !value.isJsonPrimitive() || !value.getAsJsonPrimitive().isString())
|
||||
throw new IllegalArgumentException(name + " must be a string");
|
||||
return value.getAsString();
|
||||
}
|
||||
|
||||
private static int integer(JsonObject args, String name, int fallback) {
|
||||
JsonElement value = JsonParser.parseString(args.toString()).getAsJsonObject().get(name);
|
||||
if (value == null) return fallback;
|
||||
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isNumber())
|
||||
throw new IllegalArgumentException(name + " must be a nonnegative integer");
|
||||
try {
|
||||
int result = value.getAsBigDecimal().intValueExact();
|
||||
if (result < 0) throw new ArithmeticException();
|
||||
return result;
|
||||
} catch (ArithmeticException error) {
|
||||
throw new IllegalArgumentException(name + " must be a nonnegative integer", error);
|
||||
}
|
||||
}
|
||||
|
||||
private JadxSession session() {
|
||||
JadxSession current = holder.current();
|
||||
if (current == null) throw new IllegalArgumentException("No APK loaded");
|
||||
return current;
|
||||
}
|
||||
|
||||
private ClassSnapshot selected(JsonObject args) {
|
||||
JadxSession current = session();
|
||||
if (indexedSession != current) {
|
||||
Map<String, List<ClassSnapshot>> index = new LinkedHashMap<>();
|
||||
if (!current.getClasses().isEmpty()) {
|
||||
JadxDecompiler decompiler = current.getClasses().get(0).getClassNode().root().getDecompiler();
|
||||
// JavaClass.getMethods/getFields/getInnerClasses all load code. Raw nodes do not.
|
||||
// Snapshot every identity before code generation can rewrite aliases or members.
|
||||
for (JavaClass cls : decompiler.getClassesWithInners()) {
|
||||
ClassNode node = cls.getClassNode();
|
||||
List<MethodNode> ordered = new ArrayList<>(node.getMethods());
|
||||
ordered.sort(Comparator.comparing(MethodNode::getAlias));
|
||||
List<MethodSnapshot> methods = ordered.stream().map(method -> {
|
||||
String name = method.getAlias();
|
||||
String signature = name + "(" + method.getArgTypes().stream()
|
||||
.map(Object::toString).collect(Collectors.joining(", "))
|
||||
+ "): " + method.getReturnType().toString();
|
||||
return new MethodSnapshot(name, method.getMethodInfo().getShortId(), Map.of(
|
||||
"name", name, "signature", signature,
|
||||
"is_constructor", method.isConstructor(), "def_pos", 0));
|
||||
}).toList();
|
||||
List<Map<String, Object>> fields = node.getFields().stream()
|
||||
.sorted(Comparator.comparing(FieldNode::getAlias))
|
||||
.map(field -> Map.<String, Object>of("name", field.getAlias(),
|
||||
"type", field.getType().toString(), "def_pos", 0)).toList();
|
||||
List<String> inner = node.getInnerClasses().stream().map(ClassNode::getFullName).toList();
|
||||
Map<String, Object> summary = Map.of(
|
||||
"full_name", cls.getFullName(), "name", cls.getName(),
|
||||
"method_count", methods.size(), "field_count", fields.size(),
|
||||
"inner_class_count", inner.size(), "inner_classes", inner,
|
||||
"methods", methods.stream().map(MethodSnapshot::summary).toList(), "fields", fields);
|
||||
index.computeIfAbsent(cls.getFullName(), ignored -> new ArrayList<>())
|
||||
.add(new ClassSnapshot(cls, methods, summary));
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableMap(index);
|
||||
indexedSession = current;
|
||||
}
|
||||
String name = string(args, "class_name");
|
||||
List<ClassSnapshot> candidates = classes.get(name);
|
||||
if (candidates == null) throw new IllegalArgumentException("Exact class not found: " + name);
|
||||
if (candidates.size() != 1) throw new IllegalArgumentException("Ambiguous metadata class identity: " + name
|
||||
+ "; raw identities: " + candidates.stream().map(candidate -> candidate.node().getRawName()).toList());
|
||||
return candidates.get(0);
|
||||
}
|
||||
|
||||
private List<MethodSnapshot> methods(ClassSnapshot cls, JsonObject args) {
|
||||
String name = string(args, "method_name");
|
||||
List<MethodSnapshot> result = cls.methods().stream().filter(method -> method.name().equals(name)).toList();
|
||||
if (result.isEmpty()) throw new IllegalArgumentException("Method not found: " + name);
|
||||
return result;
|
||||
}
|
||||
|
||||
private JavaMethod method(ClassSnapshot cls, MethodSnapshot selected) {
|
||||
JavaMethod result = cls.node().searchMethodByShortId(selected.shortId());
|
||||
if (result == null) throw new IllegalArgumentException("Method identity no longer available: " + selected.shortId());
|
||||
return result;
|
||||
}
|
||||
|
||||
private CallToolResult handle(String name, JsonObject args) {
|
||||
if (name.equals("rea_jvm_status")) return json(Map.of(
|
||||
"engine_reported_version", BuildInfo.VERSION,
|
||||
"max_heap_bytes", Runtime.getRuntime().maxMemory(),
|
||||
"available_processors", Runtime.getRuntime().availableProcessors(),
|
||||
"java_version", System.getProperty("java.version"),
|
||||
"metadata_scope", "parsed_members"));
|
||||
JadxSession current = session();
|
||||
if (name.equals("list_classes")) {
|
||||
List<String> names = current.getClassFqns();
|
||||
int offset = integer(args, "offset", 0);
|
||||
int limit = integer(args, "limit", 200);
|
||||
int start = Math.min(offset, names.size());
|
||||
int end = (int) Math.min((long) start + limit, names.size());
|
||||
return json(Map.of("total", names.size(), "offset", offset, "limit", limit,
|
||||
"items", names.subList(start, end)));
|
||||
}
|
||||
ClassSnapshot cls = selected(args);
|
||||
if (name.equals("get_class_summary")) return json(cls.summary());
|
||||
if (name.equals("get_method_body")) {
|
||||
List<MethodSnapshot> overloads = methods(cls, args);
|
||||
int index = integer(args, "overload_index", 0);
|
||||
if (index >= overloads.size()) throw new IllegalArgumentException("overload_index out of range: " + index);
|
||||
JavaMethod method = method(cls, overloads.get(index));
|
||||
JadxSession.SmartCode code = current.getMethodBodySmart(method, current.getMaxSourceBytes(), true);
|
||||
return json(Map.of("class_name", cls.summary().get("full_name"),
|
||||
"method_name", overloads.get(index).name(), "full_name", method.getFullName(),
|
||||
"overload_index", index, "overload_count", overloads.size(),
|
||||
"mode", code.getKind(), "fell_back", code.getFellBack(),
|
||||
"markers", code.getMarkers(), "body", code.getText()));
|
||||
}
|
||||
List<JavaNode> uses;
|
||||
String target = string(args, "class_name");
|
||||
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
|
||||
else {
|
||||
List<MethodSnapshot> overloads = methods(cls, args);
|
||||
if (overloads.size() != 1) throw new IllegalArgumentException("Method references require a unique overload");
|
||||
target += "." + overloads.get(0).name();
|
||||
uses = method(cls, overloads.get(0)).getUseIn();
|
||||
}
|
||||
// REA consumes all references without materializing source line information.
|
||||
List<Map<String, Object>> items = uses.stream().map(node -> current.describeUsage(node, false)).toList();
|
||||
return json(Map.of("target", target, "total", uses.size(), "count", items.size(),
|
||||
"resolve_line", false, "items", items));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
PrintStream protocolOut = System.out;
|
||||
System.setOut(System.err);
|
||||
SessionHolder holder = new SessionHolder(new SessionConfig(
|
||||
1048576, 0, 90000L, 1, List.of(), List.of(), 64, ResourceMode.FULL));
|
||||
ServerCapabilities capabilities = new ServerCapabilities(
|
||||
new ServerCapabilities.Tools(null), null, null, null, null, null, null, Map.of());
|
||||
Server server = new Server(new Implementation("rea-jadx-bridge", "1", null, null, null),
|
||||
new ServerOptions(capabilities), "", ignored -> Unit.INSTANCE);
|
||||
SessionToolsKt.registerSessionTools(server, holder);
|
||||
ManifestToolsKt.registerManifestTools(server, holder);
|
||||
ClassToolsKt.registerClassTools(server, holder);
|
||||
MethodToolsKt.registerMethodTools(server, holder);
|
||||
XrefToolsKt.registerXrefTools(server, holder);
|
||||
ReaJadxBridge bridge = new ReaJadxBridge(holder);
|
||||
Set<String> consumed = Set.of("load_apk", "get_app_info", "get_android_manifest",
|
||||
"list_classes", "get_class_summary", "get_method_body",
|
||||
"get_xrefs_to_class", "get_xrefs_to_method");
|
||||
for (String name : new ArrayList<>(server.getTools().keySet()))
|
||||
if (!consumed.contains(name)) server.removeTool(name);
|
||||
Tool diagnostics = new Tool("rea_jvm_status",
|
||||
new ToolSchema(null, new JsonObject(Map.of()), List.of(), null),
|
||||
"Observed worker JVM and metadata scope", null, null, null, null, null, null);
|
||||
server.addTool(diagnostics, (connection, request, continuation) -> bridge.handle("rea_jvm_status", request.getArguments()));
|
||||
for (String name : List.of("list_classes", "get_class_summary",
|
||||
"get_method_body", "get_xrefs_to_class", "get_xrefs_to_method")) {
|
||||
Tool tool = server.getTools().get(name).getTool();
|
||||
server.removeTool(name);
|
||||
List<String> fields = switch (name) {
|
||||
case "list_classes" -> List.of("offset", "limit");
|
||||
case "get_method_body" -> List.of("class_name", "method_name", "overload_index");
|
||||
case "get_xrefs_to_method" -> List.of("class_name", "method_name");
|
||||
default -> List.of("class_name");
|
||||
};
|
||||
Map<String, kotlinx.serialization.json.JsonElement> properties = new LinkedHashMap<>();
|
||||
for (String field : fields) properties.put(field, tool.getInputSchema().getProperties().get(field));
|
||||
String description = switch (name) {
|
||||
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
|
||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index, with smali fallback.";
|
||||
default -> "Complete static references to an exact class or unique method; source lines are unresolved.";
|
||||
};
|
||||
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
|
||||
tool.getInputSchema().getRequired(), null), description, null, null, null, null, null, null);
|
||||
server.addTool(narrowed, (connection, request, continuation) -> {
|
||||
try { return bridge.handle(name, request.getArguments()); }
|
||||
catch (IllegalArgumentException error) { return text(error.getMessage(), true); }
|
||||
});
|
||||
}
|
||||
CountDownLatch done = new CountDownLatch(1);
|
||||
server.onClose(() -> { done.countDown(); return Unit.INSTANCE; });
|
||||
StdioServerTransport transport = new StdioServerTransport(
|
||||
CoreKt.buffered(JvmCoreKt.asSource(System.in)),
|
||||
CoreKt.buffered(JvmCoreKt.asSink(protocolOut)));
|
||||
transport.onClose(() -> { done.countDown(); return Unit.INSTANCE; });
|
||||
ServerSession connection = null;
|
||||
try {
|
||||
connection = BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(scope, continuation) -> server.createSession(transport, continuation));
|
||||
done.await();
|
||||
} finally {
|
||||
try {
|
||||
if (connection != null) {
|
||||
ServerSession connected = connection;
|
||||
// Unsubscribe the connection before the SDK joins its notification service.
|
||||
BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(scope, continuation) -> connected.close(continuation));
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(scope, continuation) -> server.close(continuation));
|
||||
} finally {
|
||||
BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE,
|
||||
(scope, continuation) -> holder.unload(continuation));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
struct ChildBatch<Element> {
|
||||
let values: [Element]
|
||||
let complete: Bool
|
||||
}
|
||||
|
||||
struct ChildCount {
|
||||
let value: Int?
|
||||
let error: Int32?
|
||||
}
|
||||
|
||||
func serializeHelperOutput(_ value: Any) throws -> Data {
|
||||
try JSONSerialization.data(withJSONObject: value, options: [.sortedKeys, .withoutEscapingSlashes])
|
||||
}
|
||||
|
||||
func captureChildCount(status: Int32, success: Int32, value: Int) -> ChildCount {
|
||||
guard status == success else { return ChildCount(value: nil, error: status) }
|
||||
return ChildCount(value: value, error: nil)
|
||||
}
|
||||
|
||||
func captureChildValues<Element>(requestedCount: Int, copy: () -> [Element]?) -> ChildBatch<Element> {
|
||||
guard requestedCount > 0 else { return ChildBatch(values: [], complete: true) }
|
||||
let returned = copy() ?? []
|
||||
let values = Array(returned.prefix(requestedCount))
|
||||
return ChildBatch(values: values, complete: values.count == requestedCount)
|
||||
}
|
||||
@@ -22,15 +22,17 @@ func attribute(_ element: AXUIElement, _ key: String) -> CFTypeRef? {
|
||||
var value: CFTypeRef?
|
||||
return AXUIElementCopyAttributeValue(element, key as CFString, &value) == .success ? value : nil
|
||||
}
|
||||
func childCount(_ element: AXUIElement) -> Int {
|
||||
func childCount(_ element: AXUIElement) -> ChildCount {
|
||||
var count = 0
|
||||
return AXUIElementGetAttributeValueCount(element, kAXChildrenAttribute as CFString, &count) == .success ? count : 0
|
||||
let status = AXUIElementGetAttributeValueCount(element, kAXChildrenAttribute as CFString, &count)
|
||||
return captureChildCount(status: status.rawValue, success: AXError.success.rawValue, value: count)
|
||||
}
|
||||
func children(_ element: AXUIElement, limit: Int) -> [AXUIElement] {
|
||||
var value: CFArray?
|
||||
let count = min(limit, childCount(element))
|
||||
if count == 0 { return [] }
|
||||
return AXUIElementCopyAttributeValues(element, kAXChildrenAttribute as CFString, 0, count, &value) == .success ? value as? [AXUIElement] ?? [] : []
|
||||
func children(_ element: AXUIElement, count: Int) -> ChildBatch<AXUIElement> {
|
||||
captureChildValues(requestedCount: count) {
|
||||
var value: CFArray?
|
||||
guard AXUIElementCopyAttributeValues(element, kAXChildrenAttribute as CFString, 0, count, &value) == .success else { return nil }
|
||||
return value as? [AXUIElement]
|
||||
}
|
||||
}
|
||||
func text(_ element: AXUIElement, _ key: String) -> Any { (attribute(element, key) as? String) ?? NSNull() as Any }
|
||||
func windowBounds(_ element: AXUIElement) -> CGRect? {
|
||||
@@ -87,7 +89,11 @@ func observe(_ request: Request) async throws -> [String: Any] {
|
||||
var element = root
|
||||
for index in action.path ?? [] {
|
||||
var selectedChild: CFArray?
|
||||
guard index >= 0 && index < childCount(element),
|
||||
let count = childCount(element)
|
||||
guard let available = count.value else {
|
||||
try fail("element-count-unavailable", "Cannot validate selected accessibility path because child count failed with AXError \(count.error ?? -1)")
|
||||
}
|
||||
guard index >= 0 && index < available,
|
||||
AXUIElementCopyAttributeValues(element, kAXChildrenAttribute as CFString, index, 1, &selectedChild) == .success,
|
||||
let item = (selectedChild as? [AXUIElement])?.first else { try fail("element-missing", "Selected accessibility path no longer exists") }
|
||||
element = item
|
||||
@@ -103,21 +109,29 @@ func observe(_ request: Request) async throws -> [String: Any] {
|
||||
}
|
||||
guard outcome == .success else { try fail("action-failed", "Accessibility action failed with AXError \(outcome.rawValue)") }
|
||||
}
|
||||
var nodes: [[String: Any]] = []; var truncated = false
|
||||
var nodes: [[String: Any]] = []; var truncated = false; var gaps: [String] = []
|
||||
if request.accessibility, let root = selected {
|
||||
var pending: [(AXUIElement, [Int])] = [(root, [])]
|
||||
while let (element, path) = pending.popLast() {
|
||||
if nodes.count >= request.max_nodes { truncated = true; break }
|
||||
let totalChildren = childCount(element)
|
||||
let childCountResult = childCount(element)
|
||||
var actions: CFArray?
|
||||
AXUIElementCopyActionNames(element, &actions)
|
||||
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": totalChildren])
|
||||
nodes.append(["path": path, "role": text(element, kAXRoleAttribute), "title": text(element, kAXTitleAttribute), "value": text(element, kAXValueAttribute), "actions": actions as? [String] ?? [], "children_count": childCountResult.value.map { $0 as Any } ?? NSNull()])
|
||||
guard let totalChildren = childCountResult.value else {
|
||||
truncated = true
|
||||
gaps.append("AX child count unavailable at path \(path): AXError \(childCountResult.error ?? -1)")
|
||||
continue
|
||||
}
|
||||
if path.count >= 32 { if totalChildren > 0 { truncated = true }; continue }
|
||||
let available = max(0, request.max_nodes - nodes.count - pending.count)
|
||||
let count = min(available, totalChildren)
|
||||
let items = children(element, limit: count)
|
||||
let batch = children(element, count: count)
|
||||
if !batch.complete { truncated = true }
|
||||
if totalChildren > count { truncated = true }
|
||||
for index in (0..<count).reversed() { pending.append((items[index], path + [index])) }
|
||||
for (index, item) in batch.values.enumerated().reversed() {
|
||||
pending.append((item, path + [index]))
|
||||
}
|
||||
}
|
||||
}
|
||||
var screenshot: Any = NSNull()
|
||||
@@ -139,18 +153,18 @@ func observe(_ request: Request) async throws -> [String: Any] {
|
||||
}
|
||||
screenshot = ["mime_type": "image/png", "base64": png.base64EncodedString(), "sha256": SHA256.hash(data: png).map({ String(format: "%02x", $0) }).joined(), "width": image.width, "height": image.height]
|
||||
}
|
||||
return ["window": ["pid": request.pid, "window_id": request.window_id, "executable": request.executable, "launch_time": launch, "title": window[kCGWindowName as String] as? String ?? ""], "nodes": nodes, "truncated": truncated, "screenshot": screenshot, "gaps": request.accessibility ? [] : ["Accessibility capture was disabled"]]
|
||||
return ["window": ["pid": request.pid, "window_id": request.window_id, "executable": request.executable, "launch_time": launch, "title": window[kCGWindowName as String] as? String ?? ""], "nodes": nodes, "truncated": truncated, "screenshot": screenshot, "gaps": request.accessibility ? gaps : ["Accessibility capture was disabled"]]
|
||||
}
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let request = try JSONDecoder().decode(Request.self, from: Data(CommandLine.arguments[1].utf8))
|
||||
let result = try await observe(request)
|
||||
let output = try JSONSerialization.data(withJSONObject: ["ok": true, "result": result], options: [.sortedKeys])
|
||||
let output = try serializeHelperOutput(["ok": true, "result": result])
|
||||
FileHandle.standardOutput.write(output)
|
||||
} catch {
|
||||
let failure = error as? BoundaryFailure
|
||||
let output: [String: Any] = ["ok": false, "code": failure?.code ?? "capture-failed", "message": failure?.message ?? error.localizedDescription]
|
||||
if let data = try? JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) { FileHandle.standardOutput.write(data) }
|
||||
if let data = try? serializeHelperOutput(output) { FileHandle.standardOutput.write(data) }
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
export default defineConfig({
|
||||
title: "REA",
|
||||
description:
|
||||
"Reverse engineer binaries, applications, and runtime behavior with one CLI and MCP server.",
|
||||
lang: "en-US",
|
||||
base: "/rea/",
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: "Get started", link: "/installation" },
|
||||
{ text: "Reference", link: "/mcp-contracts" },
|
||||
{ text: "Roadmap", link: "/roadmap" },
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
text: "Getting started",
|
||||
items: [
|
||||
{ text: "Installation and setup", link: "/installation" },
|
||||
{ text: "Agent prompts", link: "/mcp-prompts" },
|
||||
{ text: "Roadmap", link: "/roadmap" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Investigation guides",
|
||||
items: [
|
||||
{ text: "Native binaries", link: "/native-investigation" },
|
||||
{
|
||||
text: "JavaScript and Electron",
|
||||
link: "/javascript-artifact-reconstruction",
|
||||
},
|
||||
{
|
||||
text: "JavaScript workflows",
|
||||
link: "/javascript-application-workflows",
|
||||
},
|
||||
{ text: "Browser observation", link: "/browser-observation" },
|
||||
{ text: "Electron observation", link: "/electron-observation" },
|
||||
{ text: "Process capture", link: "/process-capture" },
|
||||
{ text: ".NET assemblies", link: "/managed-code-analysis" },
|
||||
{ text: "Android applications", link: "/android-analysis" },
|
||||
{ text: "Apple applications", link: "/apple-application-analysis" },
|
||||
{ text: "Firmware", link: "/firmware-analysis" },
|
||||
{
|
||||
text: "Reconstruction readiness",
|
||||
link: "/reconstruction-readiness",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Provider guides",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "IDA", link: "/ida-provider" },
|
||||
{ text: "Ghidra on Windows", link: "/windows-ghidra-p0" },
|
||||
{ text: "Ghidra DOS analysis", link: "/ghidra-dos" },
|
||||
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
|
||||
{ text: "Provider evaluation", link: "/provider-evaluation" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Reference and development",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "MCP contracts", link: "/mcp-contracts" },
|
||||
{ text: "Tool design", link: "/tool-design" },
|
||||
{ text: "Testing", link: "/testing" },
|
||||
{ text: "Releasing", link: "/releasing" },
|
||||
{ text: "Architecture decisions", link: "/adr/README" },
|
||||
],
|
||||
},
|
||||
],
|
||||
search: { provider: "local" },
|
||||
socialLinks: [
|
||||
{ icon: "github", link: "https://github.com/morluto/rea" },
|
||||
{ icon: "discord", link: "https://discord.gg/GkcryMnJDM" },
|
||||
],
|
||||
editLink: {
|
||||
pattern: "https://github.com/morluto/rea/edit/main/docs/:path",
|
||||
},
|
||||
footer: {
|
||||
message: "Released under the MIT License.",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -29,12 +29,12 @@ same bytes could therefore disagree because of language, compiler, loader,
|
||||
analyzer, or provider-version differences while appearing target-compatible.
|
||||
|
||||
The decision addressed the earlier representations of
|
||||
[`CompositeProvider`](../../src/application/CompositeProvider.ts),
|
||||
[runtime composition](../../src/application/runtime.ts),
|
||||
[`BinarySession`](../../src/application/BinarySession.ts),
|
||||
[`BinaryTarget`](../../src/domain/binaryTarget.ts),
|
||||
[analysis snapshot](../../src/domain/analysisSnapshot.ts), and
|
||||
[session lifecycle inputs](../../src/contracts/sessionLifecycleInputs.ts).
|
||||
[`CompositeProvider`](https://github.com/morluto/rea/blob/main/src/application/binary/CompositeProvider.ts),
|
||||
[runtime composition](https://github.com/morluto/rea/blob/main/src/composition/binary.ts),
|
||||
[`BinarySession`](https://github.com/morluto/rea/blob/main/src/application/binary/BinarySession.ts),
|
||||
[`BinaryTarget`](https://github.com/morluto/rea/blob/main/src/domain/binaryTarget.ts),
|
||||
[analysis snapshot](https://github.com/morluto/rea/blob/main/src/domain/analysisSnapshot.ts), and
|
||||
[session lifecycle inputs](https://github.com/morluto/rea/blob/main/src/contracts/sessionLifecycleInputs.ts).
|
||||
|
||||
The next deep provider must not weaken the existing product invariants:
|
||||
|
||||
|
||||
+61
-14
@@ -2,16 +2,18 @@
|
||||
|
||||
REA wraps an existing headless JADX engine through `src/android/`. Its source is
|
||||
kept as an unmodified, commit-pinned Git submodule; see
|
||||
[upstream provenance](../third_party/README.md). CLI and MCP use the same
|
||||
[upstream provenance](https://github.com/morluto/rea/blob/main/third_party/README.md). CLI and MCP use the same
|
||||
application workflow and return artifact-bound Evidence inline.
|
||||
|
||||
This family is present on repository main and absent from npm 4.0.1. Check the
|
||||
This family is present on repository main and in npm 4.1.0. Check the
|
||||
[released package boundary](installation.md#released-package-and-main) and the
|
||||
connected server's tool list before selecting it.
|
||||
|
||||
## Supply tools
|
||||
|
||||
Use **Java 17 or newer** and **jadx-headless-mcp 0.7.1**. REA does not install Java,
|
||||
Use a **full JDK 17 or newer** (including its compiler module) and
|
||||
**jadx-headless-mcp 0.7.1**. REA runs its packaged Java metadata bridge in
|
||||
source-file mode against the unmodified engine JAR. REA does not install Java,
|
||||
an Android SDK, JADX, a device service or an emulator. Set the path in the shell
|
||||
or MCP server environment:
|
||||
|
||||
@@ -21,8 +23,9 @@ export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
|
||||
export JAVA_HOME=/absolute/path/existing-jdk
|
||||
```
|
||||
|
||||
Linux is verified with the public Appium ApiDemos fixture. The POSIX adapter can
|
||||
run on macOS, but that host has not undergone real Android verification.
|
||||
The current metadata bridge is verified on macOS arm64 with OpenJDK 21 and the
|
||||
public Appium ApiDemos fixture. The POSIX adapter also supports Linux; the new
|
||||
bridge has not yet undergone real verification on Linux/JDK 25.
|
||||
Windows is unsupported for this provider's owned stdio process boundary.
|
||||
|
||||
## Answer an analyst question
|
||||
@@ -72,7 +75,10 @@ selected overload. Native/abstract methods with no decompiled body report
|
||||
Signature verification is explicitly `not_performed`.
|
||||
- Class search consumes all pages and checks their counts. An empty `query`
|
||||
inventories all class names; class names are decompiler representations.
|
||||
- Class inventories return display signatures, fields and inner class names.
|
||||
- Class inventories read parsed metadata without generating source and return
|
||||
full reported display types, fields and inner class names. Synthetic members
|
||||
that code generation hides can appear in the inventory; it stays consistent
|
||||
after method decompilation within the retained session.
|
||||
Exact DEX descriptors are unknown. Overload indices are tied to this artifact
|
||||
and engine, not stable identities across different builds.
|
||||
- Method inspection reports Java or smali, fallback markers and complete/partial
|
||||
@@ -88,28 +94,69 @@ selected overload. Native/abstract methods with no decompiled body report
|
||||
These operations do not execute the APK or provide split APK/AAB handling,
|
||||
signature validation, full resource-table semantics, native-library analysis or
|
||||
Android runtime capture. Existing artifact inventory/extraction tools can supply
|
||||
archive evidence; `project_android_application_graph` remains a separate,
|
||||
execution-free inventory projection.
|
||||
archive evidence. Project that Evidence with the execution-free inventory tool:
|
||||
|
||||
```sh
|
||||
rea project-android-application-graph '{"inventory_evidence":[<inventory_artifact Evidence>]}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "project_android_application_graph",
|
||||
"arguments": {
|
||||
"inventory_evidence": ["<inventory_artifact Evidence>"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The projection reports exact component paths and hashes, runtime-family hints,
|
||||
and path-based bridge hypotheses. It does not decode DEX or claim observed
|
||||
runtime calls.
|
||||
|
||||
## Resource and lifecycle limits
|
||||
|
||||
Each request creates private, immutable APK and JAR copies. Their workspace and
|
||||
owned process group are cleaned on success, failure, timeout and cancellation.
|
||||
REA retains one serialized session with private, immutable APK, engine JAR and
|
||||
metadata-bridge copies. Matching requests reuse the loaded engine. APK/engine
|
||||
digests and JVM configuration are checked before reuse; changed inputs retire
|
||||
the previous session. Sessions are cleaned after 60 seconds of inactivity, on
|
||||
target/configuration changes, failure, timeout, cancellation, MCP disconnect or
|
||||
server shutdown. One-shot CLI commands always join cleanup before returning.
|
||||
REA never writes to the original APK or launches target code.
|
||||
|
||||
If process cleanup cannot be confirmed, REA retains the workspace and reports its
|
||||
location. That provider instance blocks subsequent engine launches until the
|
||||
caller resolves the reported resources and starts a fresh instance.
|
||||
|
||||
The provider serializes requests within each REA instance, uses one worker and
|
||||
sets JVM maximum heap to **512 MiB**. Heap is not a total RSS cap: the JVM also
|
||||
The provider uses one JADX analysis worker independently of JVM processor and
|
||||
garbage-collector settings. REA supplies no default heap or visible-CPU override;
|
||||
Java uses its normal ergonomics and inherits caller JVM settings. Optional
|
||||
provider settings add explicit JVM arguments:
|
||||
|
||||
```sh
|
||||
export REA_JADX_HEAP_MIB=8192
|
||||
# Optional, independent of the single JADX worker:
|
||||
export REA_JADX_ACTIVE_PROCESSOR_COUNT=4
|
||||
# Or select settings through Java's standard environment options:
|
||||
export JAVA_TOOL_OPTIONS=-Xmx8g
|
||||
```
|
||||
|
||||
Both REA settings must be positive safe integers; processor counts must also fit
|
||||
the JVM's signed 32-bit range. REA preserves
|
||||
`JAVA_TOOL_OPTIONS`, `JDK_JAVA_OPTIONS` and `_JAVA_OPTIONS` rather than replacing
|
||||
them. Java's standard precedence applies: explicit command-line heap settings
|
||||
override `JAVA_TOOL_OPTIONS`, while `_JAVA_OPTIONS` can override command-line
|
||||
settings. Evidence reports the worker's observed `Runtime.maxMemory()` rounded
|
||||
down to MiB and retains exact bytes, Java version and visible processors in its
|
||||
raw runtime response. The observed allocatable heap can differ from configured
|
||||
`-Xmx`, especially with Serial GC. Heap is not a total RSS cap: the JVM also
|
||||
uses native memory. Independent REA instances have independent queues. Whole
|
||||
operations have a **120-second** execution deadline after reaching the queue's
|
||||
front; upstream decompilation is limited to **90 seconds**.
|
||||
|
||||
Decoded manifest and method text have a **1 MiB** upstream byte budget and report
|
||||
truncation. A protocol frame above **8 MiB**, or cumulative stdout/stderr above
|
||||
**32 MiB**, fails with a diagnostic instead of returning an incomplete inventory
|
||||
truncation. Class-name pages use indexed slices of up to 4,096 names without changing the
|
||||
complete, case-sensitive search contract. A protocol frame above **8 MiB**, or combined stdout/stderr above
|
||||
**32 MiB per operation**, fails with a diagnostic instead of returning an incomplete inventory
|
||||
as complete. These are memory-safety boundaries, not undocumented result caps.
|
||||
|
||||
## Real verification
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
# Apple Application Inventory Projection
|
||||
|
||||
REA can project one or more authenticated `inventory_artifact` Evidence records
|
||||
for the same IPA into an Apple application inventory. The projection is
|
||||
execution-free: it reports exact paths, content hashes, detected formats,
|
||||
runtime-family hints, and path-based bridge hypotheses. It does not parse plist
|
||||
or CMS semantics or claim observed runtime calls.
|
||||
for the same IPA, macOS `.app` directory, ZIP, or DMG into an Apple application
|
||||
inventory. The projection is execution-free: it reports exact paths, content
|
||||
hashes, detected formats, bundle anatomy, runtime-family hints, and path-based
|
||||
bridge hypotheses. It does not parse plist or CMS semantics or claim observed
|
||||
runtime calls.
|
||||
|
||||
```sh
|
||||
rea project-apple-application-graph '{"inventory_evidence":[<inventory_artifact Evidence>]}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "project_apple_application_graph",
|
||||
"arguments": {
|
||||
"inventory_evidence": ["<inventory_artifact Evidence>"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The result includes every component in each inventory category and every
|
||||
JavaScript-to-native bridge candidate pair. It has no caller-selected component
|
||||
@@ -16,3 +30,83 @@ Coverage is `complete-within-inventory` when the supplied pages reconstruct the
|
||||
complete authenticated inventory, and `partial` otherwise. A partial result
|
||||
states that absence is unknown. Component and bridge-candidate arrays are still
|
||||
the complete projection of the inventory that was supplied.
|
||||
|
||||
## Application roots
|
||||
|
||||
| Inventory root | Application root |
|
||||
| -------------------------------------- | --------------------------------------------------- |
|
||||
| IPA | Each `Payload/<Name>.app` |
|
||||
| `.app` directory | `.`, the inventoried bundle itself |
|
||||
| Directory, ZIP, or DMG containing apps | Each outermost `<Name>.app` followed by `Contents/` |
|
||||
|
||||
ZIP archives made with `ditto -c -k --keepParent` report `<Name>.app`. A DMG
|
||||
reports `<image>.dmg/<Volume>/<Name>.app`; DMG child inventory needs the native
|
||||
macOS mounting adapter. When a DMG has only its root identity, the projection is
|
||||
`partial` and says that bundle absence is unknown. Other inventory formats, such
|
||||
as APK or ASAR, are rejected as input errors.
|
||||
|
||||
An inventoried directory whose name ends in `.app` is its own root even when its
|
||||
inventory is partial or lacks `Contents/`. For directory, ZIP, and DMG
|
||||
inventories, components outside every application root are not attributed to
|
||||
the application. This covers installers, disk-image extras, and AppleDouble
|
||||
sidecars, which therefore stay out of runtime families and bridge candidates. A
|
||||
limitation reports how many inventoried entries were left out. IPA projection
|
||||
still lists every archive component, including `SwiftSupport/`.
|
||||
|
||||
`platforms` lists `macos` for application roots with `Contents/`. It lists `ios`
|
||||
for shallow application roots that have an `Info.plist`. An application root
|
||||
with neither, such as an empty inventory, has no platform.
|
||||
|
||||
## macOS bundle anatomy
|
||||
|
||||
`bundles` lists each application root and every nested bundle under it. Each
|
||||
bundle reports its `parent_path`, `layout`, `info_plist_path`,
|
||||
`executable_candidates`, and `signing_paths`.
|
||||
|
||||
| Layout | Meaning | Content directory |
|
||||
| --------------------- | --------------- | --------------------------------------------- |
|
||||
| `macos-deep` | Has `Contents/` | `Contents/`, executables in `Contents/MacOS/` |
|
||||
| `versioned-framework` | Has `Versions/` | Each real `Versions/<version>/` |
|
||||
| `shallow` | Neither | The bundle directory itself |
|
||||
|
||||
Roles are path conventions (`role_basis: "path-convention"`):
|
||||
|
||||
| Role | Path |
|
||||
| -------------------- | --------------------------------------------------------------- |
|
||||
| `app-extension` | `*.appex` |
|
||||
| `xpc-service` | `*.xpc`, including XPC services inside frameworks |
|
||||
| `framework` | `*.framework` |
|
||||
| `system-extension` | `*.systemextension` |
|
||||
| `driver-extension` | `*.dext` |
|
||||
| `plug-in` | `PlugIns/*.bundle`, `*.plugin`, `*.qlgenerator`, `*.mdimporter` |
|
||||
| `resource-bundle` | `Resources/*.bundle`, such as SwiftPM resource bundles |
|
||||
| `bundle` | Any other `*.bundle` |
|
||||
| `login-item` | `Contents/Library/LoginItems/*.app` |
|
||||
| `helper-application` | `Contents/Helpers/*.app` |
|
||||
| `nested-application` | Any other nested `*.app` |
|
||||
|
||||
The components also list:
|
||||
|
||||
- `privileged_helpers`: files directly in `Contents/Library/LaunchServices/`,
|
||||
where SMJobBless helpers live;
|
||||
- `launchd_plists`: `.plist` files directly in `Contents/Library/LaunchAgents/`
|
||||
(`domain: "agent"`) or `Contents/Library/LaunchDaemons/` (`domain: "daemon"`),
|
||||
as used by SMAppService;
|
||||
- `helpers`: files directly in `Contents/Helpers/`.
|
||||
|
||||
Symlinks are reported by path in `symlinks`. Their targets are not inventoried,
|
||||
so a versioned framework is described through its real `Versions/<version>/`
|
||||
directories rather than `Versions/Current`. A framework's `info_plist_path` is
|
||||
`null`, with a limitation explaining why, when it has more than one real
|
||||
`Versions/<version>/` directory (even if only one of them holds a plist,
|
||||
because `Versions/Current` decides which one applies) or when its single
|
||||
version directory has no `Resources/Info.plist`.
|
||||
|
||||
AppleDouble sidecar files (`._*` and `__MACOSX/`) are inventory facts, but they
|
||||
describe neighbouring files. They are excluded from roots, bundle roles, and the
|
||||
anatomy components.
|
||||
|
||||
The projection does not read plists. Use `inspect_plist` on a bundle's
|
||||
`info_plist_path` or a launchd plist to read `CFBundleExecutable`, identifiers,
|
||||
`SMPrivilegedExecutables`, `BundleProgram`, URL schemes, and usage descriptions.
|
||||
Use `inspect_signature` on an executable candidate for code-signing claims.
|
||||
@@ -1,22 +1,28 @@
|
||||
graph TD
|
||||
subgraph "Entry Points"
|
||||
CLI["src/cli.ts<br/>One-shot CLI adapter<br/>setup, diagnostics, analysis, decompilation"]
|
||||
MAIN["src/main.ts<br/>MCP stdio server adapter<br/>config, session, process lifetime"]
|
||||
CLI["src/cli.ts + src/cli/<br/>One-shot CLI adapter<br/>setup, diagnostics, analysis, decompilation"]
|
||||
MAIN["src/main.ts + src/main/<br/>MCP stdio server adapter<br/>config, session, process lifetime"]
|
||||
DISPATCH["scripts/rea.mjs<br/>Executable dispatcher<br/>routes mcp / one-shot"]
|
||||
end
|
||||
|
||||
subgraph "Production Composition"
|
||||
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading"]
|
||||
end
|
||||
|
||||
subgraph "Adapters"
|
||||
SERVER["src/server/<br/>MCP request translation<br/>createServer, tool registration, toolResult"]
|
||||
HOPPER_BRIDGE["bridge/hopper_bridge.py<br/>Runs inside Hopper<br/>adapts operations to Hopper Python API"]
|
||||
GHIDRA_BRIDGE["bridge/ghidra/ReaGhidraBridge.java<br/>Packaged HeadlessScript<br/>inventory, decompiler, references, CFG"]
|
||||
INSTALL["Host installation adapters<br/>macOS: verified vendor DMG to ~/Applications<br/>Linux: verified vendor package + apt/dnf/pacman"]
|
||||
INSTALL["src/cli/ + src/application/Setup*<br/>Host installation adapters<br/>macOS: verified vendor DMG to ~/Applications<br/>Linux: verified vendor package + apt/dnf/pacman"]
|
||||
end
|
||||
|
||||
subgraph "Application Layer"
|
||||
APP["src/application/<br/>Shared CLI/MCP workflows<br/>enhanced analysis and investigations"]
|
||||
SESSION["SessionProviderRouter + BinarySession<br/>one immutable deep binding per target<br/>disjoint auxiliary routing"]
|
||||
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
|
||||
JS_RECON["JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
|
||||
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
|
||||
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots<br/>compatibility facade and post-commit notifications"]
|
||||
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
|
||||
end
|
||||
|
||||
subgraph "Provider Layers"
|
||||
@@ -25,7 +31,9 @@ graph TD
|
||||
IDA["src/ida/<br/>Upstream MCP contract adaptation<br/>live GUI binding or owned headless database"]
|
||||
IDA_MCP["External ida-pro-mcp<br/>GUI proxy or database supervisor<br/>upstream installation and engine lifecycle"]
|
||||
AUX["src/artifacts/ + src/native/<br/>disjoint artifact and native capabilities"]
|
||||
BROWSER["src/browser/<br/>Loopback CDP transport<br/>exact-origin passive observation"]
|
||||
BROWSER["src/browser/<br/>CDP / Playwright capture<br/>captured scripts and native module resolution<br/>execution/: coverage, listener and source attribution"]
|
||||
JS_CODEC["src/javascript/sourceMaps/<br/>pinned upstream map codec<br/>owned heap-limited Node process, complete point evidence"]
|
||||
INSPECTOR["src/inspector/<br/>passive V8 Inspector discovery and capture<br/>script and target locations"]
|
||||
DOTNET["src/dotnet/<br/>Managed static metadata provider<br/>PE/CLI metadata inventory, members, IL"]
|
||||
FIRMWARE["src/firmware/<br/>Binwalk / Unblob adapters<br/>owned processes, report normalization, private extraction"]
|
||||
ANDROID["src/android/<br/>Static APK adapter<br/>owned SDK stdio, pinned headless JADX, normalization"]
|
||||
@@ -48,7 +56,7 @@ graph TD
|
||||
end
|
||||
|
||||
subgraph "Configuration"
|
||||
CONFIG["src/config.ts<br/>Zod-validated env parsing<br/>provider settings and origins"]
|
||||
CONFIG["src/config.ts + src/config/<br/>Zod-validated env parsing<br/>provider settings and origins"]
|
||||
end
|
||||
|
||||
subgraph "Tests"
|
||||
@@ -65,6 +73,10 @@ graph TD
|
||||
MAIN --> SERVER
|
||||
MAIN --> APP
|
||||
CLI --> APP
|
||||
MAIN --> COMPOSITION
|
||||
CLI --> COMPOSITION
|
||||
SERVER --> COMPOSITION
|
||||
COMPOSITION --> SESSION
|
||||
APP --> FIRMWARE
|
||||
FIRMWARE --> PROCESS
|
||||
FIRMWARE --> CONTRACTS
|
||||
@@ -75,9 +87,15 @@ graph TD
|
||||
CLI --> ERRORS
|
||||
APP --> SESSION
|
||||
SESSION --> REGISTRY
|
||||
REGISTRY --> HOPPER
|
||||
REGISTRY --> GHIDRA
|
||||
REGISTRY --> IDA
|
||||
SESSION --> SNAPSHOT
|
||||
SNAPSHOT --> RECORDS
|
||||
APP -->|record callbacks| RECORDS
|
||||
SERVER -->|narrow record ports| RECORDS
|
||||
RECORDS --> DOMAIN
|
||||
COMPOSITION --> HOPPER
|
||||
COMPOSITION --> GHIDRA
|
||||
COMPOSITION --> IDA
|
||||
COMPOSITION --> AUX
|
||||
IDA --> IDA_MCP
|
||||
IDA --> CONTRACTS
|
||||
IDA --> DOMAIN
|
||||
@@ -87,6 +105,12 @@ graph TD
|
||||
SERVER --> CONTRACTS
|
||||
SERVER --> APP
|
||||
APP --> BROWSER
|
||||
APP -->|source-map codec port| JS_CODEC
|
||||
JS_CODEC --> PROCESS
|
||||
JS_CODEC --> DOMAIN
|
||||
APP -->|runtime observation port| INSPECTOR
|
||||
INSPECTOR -->|shared CDP and file-location helpers| BROWSER
|
||||
INSPECTOR --> DOMAIN
|
||||
APP --> DOTNET
|
||||
DOTNET --> DOMAIN
|
||||
HOPPER --> HOPPER_BRIDGE
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Website observation with CDP
|
||||
|
||||
Captured Debugger sources can be exported for REA's existing static JavaScript
|
||||
analysis with `export_web_scripts`. See [captured website scripts](website-script-export.md)
|
||||
for the capture options, source mappings, and browser/local resolution limits.
|
||||
Use [captured module relationships](website-module-trace.md) to resolve one
|
||||
exported script's native imports under an explicit URL/import-map context.
|
||||
|
||||
REA can attach to a user-owned Chrome-family browser through the Chrome DevTools Protocol (CDP) and produce bounded Evidence about an existing page. This is a passive reverse-engineering capability, not a general browser automation or remote-control surface.
|
||||
|
||||
## Shipped surfaces
|
||||
@@ -8,7 +14,7 @@ REA can attach to a user-owned Chrome-family browser through the Chrome DevTools
|
||||
- `inspect_web_page` / `rea inspect-web-page` captures DOM structure, accessibility nodes, scripts, resources, safe response/DOM metadata, attach-window network and console metadata, WebSocket frame sizes, workers, quota, and optionally selected storage key names, redacted storage-content fingerprints, or script sources.
|
||||
- `analyze_web_bundle` / `rea analyze-web-bundle` parses selected script artifacts without execution and derives chunk edges, route and endpoint candidates, vendor fingerprints, static WebMCP declarations, and optionally source-map/original-source evidence.
|
||||
- `observe_web_session` / `rea observe-web-session` arms an observation window of the requested duration for an external user action and records ordered reload, SPA navigation, redirect, failure, lifecycle, and target-termination metadata.
|
||||
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete structural input-schema summary inline. REA never exposes `WebMCP.invokeTool`; malformed protocol payloads fail explicitly instead of returning incomplete tool metadata.
|
||||
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete structural input-schema summary inline. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. REA never exposes `WebMCP.invokeTool`; malformed protocol payloads fail explicitly instead of returning incomplete tool metadata.
|
||||
- `compare_web_captures` / `rea compare-web-captures` compares passive captures or exact-step-aligned browser scenarios. Scenario results commit reproducible literal normalization and expose alignment failures plus artifact-level action, screenshot, DOM, accessibility, URL, history, storage, and event differences. Missing or truncated evidence is never treated as equivalence.
|
||||
- `capture_web_screenshot` / `rea capture-web-screenshot` returns a content-addressed visible-viewport PNG.
|
||||
- `compare_web_screenshots` / `rea compare-web-screenshots` performs bounded local PNG pixel comparison without OCR or external services.
|
||||
@@ -18,6 +24,17 @@ REA can attach to a user-owned Chrome-family browser through the Chrome DevTools
|
||||
Target discovery returns the complete in-scope target array in one result.
|
||||
|
||||
Electron `file://` pages use a separate provider and target boundary; see [electron-observation.md](electron-observation.md).
|
||||
|
||||
## Provider authorities
|
||||
|
||||
CDP page capture, V8 Inspector observation, and Playwright scenarios stay
|
||||
separate providers because their authorities differ: CDP and V8 attach to an
|
||||
already-running target and never launch, drive, evaluate, or mutate it, while
|
||||
Playwright providers own and drive the runtime (launch browsers, run actions,
|
||||
capture step snapshots). Merging them would mix attach-only and owned-process
|
||||
lifecycles in one contract. What they share instead is one exclusion
|
||||
vocabulary (`BrowserExclusionReason`): every denial reason in every stack maps
|
||||
onto it, even where a wire schema keeps a narrower historical bucket.
|
||||
Existing static application Evidence and passive web/Electron captures can be
|
||||
combined later through
|
||||
[JavaScript static/runtime reconciliation](javascript-runtime-reconciliation.md).
|
||||
@@ -120,7 +137,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
|
||||
- Request/response bodies are not requested or parsed by default. When selected, only allowed-origin JSON media types are read, converted immediately to complete value-free property paths/types, and discarded. `Network.getResponseBody` is never sent unless JSON body shape capture is selected.
|
||||
- Console observations with a stack source retain call type, argument types, timestamp, and source location. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded.
|
||||
- WebSocket observations retain direction, opcode, and payload byte length. When shape capture is selected, text frames are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw frames are never retained.
|
||||
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
|
||||
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
|
||||
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Source content is omitted unless explicitly requested and is returned inline as a self-verifying artifact with its SHA-256 digest.
|
||||
|
||||
Frames, resources, scripts, events, and workers outside the selected origin scope are excluded. An explicit `allowed_origins` list can include additional origins; excluded target details are counted without being exposed.
|
||||
@@ -142,10 +159,12 @@ Disallowed-origin entries are filtered before retention.
|
||||
|
||||
CDP discovery, WebSocket connection, and commands continue until the peer responds or the caller cancels the operation; REA has no internal byte, message-count, or pending-command quota for these exchanges. Without caller cancellation, a stalled peer can leave an operation waiting. On cancellation, REA closes only its own WebSocket, waiting up to one second before terminating it. Malformed unsolicited protocol events poison the connection so later commands fail closed.
|
||||
|
||||
Network and console coverage starts only after REA attaches and enables the relevant CDP domains. `prior_activity_available` is always `false`; absence from these arrays is not evidence that an event never occurred. Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Every requested map, mapping, original source, AST node, and finding from parsed content is returned inline; failed maps remain explicitly unavailable and make bundle completeness partial.
|
||||
Network and console coverage starts only after REA attaches and enables the relevant CDP domains. `prior_activity_available` is always `false`; absence from these arrays is not evidence that an event never occurred. Source maps are not fetched by inspection. Bundle analysis fetches them only when `fetch_source_maps` is selected, with exact-origin redirect checks and no credentials, cookies, or referrer. Every requested map, mapping, original source, AST node, and finding from parsed content is returned inline; failed maps remain explicitly unavailable and make bundle completeness partial. Original source artifacts with `.ts`, `.tsx`, `.mts`, or `.cts` suffixes carry `text/typescript`; other source names retain `text/javascript`.
|
||||
|
||||
## Non-goals and threat model
|
||||
|
||||
The browser provider deliberately does not expose generic CDP commands, `Runtime.evaluate`, WebMCP invocation, navigation, input, downloads, page closure, or browser closure. Screenshot capture is the sole pixel surface and returns an image only when requested. REA disables the domains it enabled, detaches the target session, and closes only its own WebSocket.
|
||||
|
||||
This feature is not a browser sandbox or network containment mechanism. The attached page and browser continue running with their existing privileges and may make external requests independently of REA. CDP gives deep access to the selected browser profile, so use a dedicated profile, include only origins you intend to inspect, and treat other same-user processes as outside this boundary.
|
||||
|
||||
Response metadata normalizes a `Referrer-Policy` fallback list to its last recognized nonempty policy, including mixed-case tokens. Unknown policy names are skipped; malformed token syntax and lists without a recognized policy remain `null`. This reports the response's declared policy, not the effective policy of a document or an inferred browser default.
|
||||
@@ -194,6 +194,83 @@ external browser without closing it. The request itself defines the operation;
|
||||
there is no additional REA grant step. The browser and host still enforce their
|
||||
own access and process rules.
|
||||
|
||||
## Network evidence
|
||||
|
||||
`capture.events: ["network"]` retains request, response, completion, failure,
|
||||
and unfinished-request metadata. Select content independently:
|
||||
|
||||
```json
|
||||
{
|
||||
"browser": {
|
||||
"mode": "launch",
|
||||
"executable_path": "/absolute/path/to/chromium"
|
||||
},
|
||||
"start_url": { "url": "http://127.0.0.1:3000" },
|
||||
"actions": [
|
||||
{
|
||||
"step_id": "run",
|
||||
"action": "click",
|
||||
"locator": { "kind": "css", "selector": "#start" },
|
||||
"timeout_ms": 10000
|
||||
},
|
||||
{
|
||||
"step_id": "ready",
|
||||
"action": "wait_for",
|
||||
"locator": { "kind": "css", "selector": "#done" },
|
||||
"state": "visible",
|
||||
"timeout_ms": 10000
|
||||
}
|
||||
],
|
||||
"capture": {
|
||||
"network": {
|
||||
"request_body": true,
|
||||
"response_body": true,
|
||||
"header_values": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All three options default to `false`. Any selected network content also enables
|
||||
network metadata observation; listing `network` in `events` is then optional.
|
||||
Each observed request object receives a capture-scoped `transaction_id`.
|
||||
Requests sharing a URL remain distinct. Redirects link to the preceding
|
||||
transaction only when it was observed; a null predecessor does not establish
|
||||
that no earlier hop existed. Older captures without IDs remain readable and
|
||||
their transaction association is unknown.
|
||||
|
||||
`scenario.network_content` records the resolved content selection. Comparing
|
||||
captures with different selection, or legacy captures without that coverage
|
||||
information, reports network evidence as unknown with a recovery instruction.
|
||||
Capturing more data alone does not establish a website change.
|
||||
|
||||
`network-content` events reference the exact request or response event through
|
||||
`source_event_sequence`. They include independently typed headers and body
|
||||
states. Captured bodies retain base64 bytes, byte count, media type, and SHA-256
|
||||
of the retained bytes. Request bytes are those exposed by Playwright; a
|
||||
`not_exposed` request body does not prove absence or complete multipart file
|
||||
coverage. Response bytes are decoded by the browser, not the compressed wire
|
||||
representation. No later fetch is substituted for the original response.
|
||||
Headers preserve producer order and duplicates. Authorization,
|
||||
Proxy-Authorization, Cookie, and Set-Cookie values are null with
|
||||
`redacted: true`; ordinary values remain intact. Declared UTF-8 secret values
|
||||
and their standard URI/form or JSON-escaped representations are replaced without
|
||||
decoding unrelated binary bytes. Other encodings are not interpreted. Digests describe the
|
||||
retained representation after redaction.
|
||||
|
||||
Only completed responses are read. At the end of the final step, observation
|
||||
stops; requests still in flight emit `request-unfinished`, and their observed
|
||||
responses have explicitly unavailable bodies. This cutoff does not wait for
|
||||
long-lived streaming connections. Already-started content reads settle before
|
||||
browser cleanup, with a provider-owned five-second timeout per read and
|
||||
cancellation support. Failed or unexposed reads retain per-content states and make selected
|
||||
event coverage incomplete. Body events can arrive during later steps or final
|
||||
drain; event bounds describe receipt during the step, while the source event
|
||||
reference establishes the transaction phase. Comparison attributes content to
|
||||
its source event's step and excludes the sequence reference from normalization.
|
||||
Playwright scenario capture does not supply initiator stacks; passive CDP
|
||||
observation retains its own initiator evidence.
|
||||
|
||||
## Scenario comparison
|
||||
|
||||
`compare_web_captures` and `compare-web-captures` also accept two complete
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
# Incremental capability migration
|
||||
|
||||
This working guide tracks the migration in #740. Update it as concrete domains
|
||||
migrate. Existing public contracts and domain-specific ports remain authoritative;
|
||||
there is no new universal provider interface or fixed module manifest.
|
||||
|
||||
## Ownership to preserve
|
||||
|
||||
| Responsibility | Current owner | Migration direction |
|
||||
| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------- |
|
||||
| Analyst semantics and Evidence representation | domain | Keep pure and provider-neutral |
|
||||
| Named public input/output contracts | contracts | Keep exact CLI/MCP meaning and complete discovery |
|
||||
| Producer parsing and external tool protocols | provider adapters | Keep behind the relevant typed port |
|
||||
| Shared analyst workflows | application | Share between CLI and MCP |
|
||||
| Concrete provider construction | composition, MCP startup | Keep fresh typed factories at the production boundary |
|
||||
| Investigation Evidence and Unknowns | InvestigationRecords/EvidenceLedger | Consume narrow read/write/atomic record ports |
|
||||
| Target/profile snapshots and invalidation | BinarySessionRecords/BinarySession | Keep binary-owned |
|
||||
| Subprocess ownership and host primitives | process/windows | Reuse; preserve cancellation and cleanup |
|
||||
| Public translation | CLI/MCP adapters | Delegate to shared workflows and named contracts |
|
||||
|
||||
Retain exact Evidence IDs, provenance, detached reads, atomic Evidence/Unknown
|
||||
mutations and optimistic revisions. Preserve current close and failed-cleanup
|
||||
record clearing while extracting ownership; #721 owns any retention-policy change.
|
||||
Each CLI invocation and MCP connection keeps its own state. Provider registration
|
||||
must not launch engines or install prerequisites.
|
||||
|
||||
## Migrate before generalizing
|
||||
|
||||
1. Record the actual callers, state, environment, protocol and verification owner
|
||||
of the capability being migrated.
|
||||
2. Move concrete construction behind its existing typed port. Preserve injected
|
||||
ports, selected environment, queues, failure state and cleanup behavior.
|
||||
3. Test both caller paths and the applicable package/real-provider boundary.
|
||||
4. Use independently migrated capabilities to identify repeated metadata and
|
||||
composition needs. Derive a small source declaration only when those needs
|
||||
are established; keep unlike target/lifecycle semantics separate.
|
||||
5. Move a coherent capability's files, fixtures and verification lanes in a
|
||||
mechanical PR. Update all path-sensitive consumers in that same PR.
|
||||
|
||||
Pure helpers do not need an interface each. A capture-format adapter or typed
|
||||
factory may be sufficient. Avoid generic JSON execution, external MCP catalog
|
||||
passthrough, eager provider acquisition, new permission fields or a multi-package
|
||||
rewrite. Optional loading should preserve successful peers and report the failed
|
||||
capability without hiding tools from the catalog.
|
||||
|
||||
## Checks before and after a move
|
||||
|
||||
`npm run verify:test-discovery` compares actual Vitest file discovery with every
|
||||
repository-owned `*.test.ts` under src and tests, including newly added files. The
|
||||
check requires Git, includes untracked non-ignored tests, and excludes removed
|
||||
paths during a move. It
|
||||
rejects missing files and overlapping project ownership. It only lists files;
|
||||
it does not replace behavior tests or real-provider verification.
|
||||
|
||||
Check imports, project isolation and coverage, development-test selection,
|
||||
lint exceptions, catalog/generator imports, npm and CI verification entrypoints,
|
||||
fixture ownership, and installed-package bridge/native path resolution.
|
||||
Domain and contracts must not import application workflows, caller adapters or
|
||||
provider implementations. Production composition is a deliberate outer boundary.
|
||||
Other existing adapter imports will migrate incrementally; do not widen a lint
|
||||
exception to conceal new protocol code inside application workflows.
|
||||
|
||||
`npm run verify:module-boundaries` parses repository-owned source imports,
|
||||
reexports, type imports and literal dynamic imports, then checks their resolved
|
||||
source layer. Nested names such as `domain/android` are domain code; `src/android`
|
||||
is the provider adapter. The check runs with lint, including cached static gates.
|
||||
It covers tracked and untracked non-ignored TypeScript source and skips cached
|
||||
paths removed during a move. Pass exact source paths for a focused check.
|
||||
|
||||
Pure source and its tests keep inward dependencies. Application/server provider
|
||||
construction guards cover the migrated deep, Android, firmware, JavaScript recovery and observation
|
||||
implementations. Application workflows cannot import composition, including the
|
||||
former binary runtime entrypoint; shared browser capture/export helpers remain admitted while their
|
||||
ownership is reviewed. Test lane restrictions still use Oxlint. Producer-dependent
|
||||
browser and managed comparison tests live in boundary lanes with their original
|
||||
fixtures and assertions. This is a development guard, not runtime authorization.
|
||||
|
||||
Select verification by the claim changed: schema/composition, package, real
|
||||
engine or host-native workflow. Record host/target prerequisites and source-owned
|
||||
fixtures. Run heavy lanes serially with bounded workers/heaps and reuse existing
|
||||
engines. Recording ports do not prove real provider or platform support.
|
||||
|
||||
## Extension examples to validate during migration
|
||||
|
||||
An implementation of an existing port should require a provider adapter, typed
|
||||
production factory selection, exact producer parsing/provenance and matching
|
||||
verification. Existing workflow meaning and public contracts should stay stable.
|
||||
|
||||
A new analyst capability deliberately adds semantics, named contracts, a typed
|
||||
port/workflow when needed, both caller adapters, availability, documentation and
|
||||
verification. Its integration is broader than swapping a provider. The migration
|
||||
should eliminate incidental duplicate wiring, not erase those design decisions.
|
||||
|
||||
Update these examples with real migrated entrypoints before closing #740.
|
||||
|
||||
## First production factory migration
|
||||
|
||||
`src/composition/android.ts` and `src/composition/firmware.ts` construct providers
|
||||
behind the existing AndroidAnalysisPort and FirmwareAnalysisPort. CLI registrars
|
||||
pass their selected environment; MCP uses the process environment unless a caller
|
||||
injects a port. Each factory call returns a fresh instance and acquires no process,
|
||||
workspace or toolchain. Queues, cleanup-failure state and per-request cleanup stay
|
||||
inside the provider. The existing launcher seams remain available to boundary
|
||||
fixtures, which now exercise the production factories.
|
||||
|
||||
Application workflows and MCP registration must not import these concrete
|
||||
providers. Composition may import their implementations. Binary composition also
|
||||
uses this outer boundary after its direct-analysis callers were migrated.
|
||||
This concrete pilot does not introduce a shared provider interface or module
|
||||
manifest.
|
||||
|
||||
## Independent optional observation adapters
|
||||
|
||||
Browser observation, browser scenarios, Electron observation, Electron scenarios
|
||||
and V8 Inspector observation now have separate factories in `src/composition/`.
|
||||
CLI commands reuse them; MCP startup dynamically imports each factory independently.
|
||||
`OptionalObservationProviders` describes these five existing typed ports only. It
|
||||
is provisional startup wiring, not an extensible plugin manifest or a universal
|
||||
provider interface.
|
||||
|
||||
A failed import or constructor retains the successful peers. The complete tool
|
||||
catalog remains advertised. Session availability reports the failed adapter and
|
||||
its actual reason, and affected handlers return the existing capability-unavailable
|
||||
error. Explicit loading failures take precedence over configured availability.
|
||||
Passive Electron and active Electron failures are independent; static JavaScript
|
||||
analysis and runtime reconciliation remain usable without either adapter. CLI
|
||||
Electron commands also load only their selected runtime factory at execution.
|
||||
|
||||
Source identities are available without importing the optional implementations.
|
||||
Registration acquires no engines, probes no endpoints, changes no permissions and
|
||||
installs nothing. Core transport failures and shutdown still use their existing
|
||||
lifecycle. Fault-injection coverage uses the production loading/startup path and
|
||||
actual MCP SDK calls; recording ports establish composition behavior, while the
|
||||
real Inspector lane separately establishes runtime behavior.
|
||||
|
||||
## Investigation record ownership pilot
|
||||
|
||||
`src/application/investigation/InvestigationRecords.ts` owns one existing
|
||||
EvidenceLedger per runtime. The production session composition constructs that
|
||||
owner explicitly; the direct BinarySession constructor retains a fresh default
|
||||
for compatibility. There is no second ledger, global store, persistence or new
|
||||
retention policy.
|
||||
|
||||
The ledger, Unknown Evidence helper and the ledger's two original behavior tests
|
||||
now live beside the owner in `src/application/investigation/`. Bundle validation
|
||||
and record-owner consumers follow the new paths; retained-reference access still
|
||||
uses the narrow record ports. The move preserves implementation bodies and test
|
||||
assertions. Binary snapshot/cache files remain with their existing owner.
|
||||
|
||||
EvidenceReader, EvidenceWriter and EvidenceUnknownWriter describe existing
|
||||
read/write/atomic callback needs. UnknownRegistryPort preserves optimistic
|
||||
revisions and consistency verification. Non-binary MCP registrars use these
|
||||
record types without depending on the binary execution/lifecycle surface.
|
||||
BinarySessionPort retains its compatible record methods through composition.
|
||||
|
||||
BinarySessionRecords remains the snapshot owner and compatibility facade. It
|
||||
passes the actual active target explicitly for recordUnknown/updateUnknown;
|
||||
recordEvidenceWithUnknown keeps its target-free mutation subject. Snapshot import
|
||||
merges records without notifying midway, then emits after the cache commits.
|
||||
Bundle import retains the full change delta so Unknown-only changes notify even
|
||||
when recordsAdded is zero. Clearing resets records, cache and invalidation before
|
||||
notification; observer failures remain best effort after a committed mutation.
|
||||
|
||||
Close and failed-cleanup clearing, ordinary target-switch retention, detached
|
||||
reads, Evidence identities and deterministic bundle transfer keep their existing
|
||||
behavior. Snapshots still require an active target and concrete analysis profile;
|
||||
extracting investigation ownership does not make them a target-free record store.
|
||||
#721 remains responsible for any future lifetime-policy change.
|
||||
|
||||
## Inspector adapter ownership
|
||||
|
||||
`src/inspector/` owns V8 Inspector discovery, passive capture, script/target
|
||||
location interpretation and its lightweight provider identity. The composition
|
||||
factory and optional loader use this owner. Browser CDP connection, endpoint/value
|
||||
and Electron file-location helpers remain deliberate shared adapter utilities at
|
||||
their existing paths; no provider wire protocol moves into domain or process.
|
||||
|
||||
Loopback fixtures and producer boundaries now live under
|
||||
`tests/fixtures/inspector/` and `tests/boundary/inspector/`. The socket-backed
|
||||
unresolved-target reconciliation test moves from composition into the forked
|
||||
boundary lane with its original assertions. Adapter tests retain fork isolation.
|
||||
`npm run verify:inspector` keeps its public development entrypoint and delegates
|
||||
to `scripts/verify/inspector/runtime-observation.mjs`, using the real Node fixture
|
||||
under `tests/conformance/inspector/`. Catalog imports, source guards and test
|
||||
discovery follow the new paths. Observation authority, lifecycle, producer
|
||||
interpretation and caller-visible contracts stay unchanged.
|
||||
|
||||
## Auxiliary source declaration pilot
|
||||
|
||||
Artifact, macOS native and managed static metadata now belongs to each adapter's
|
||||
`*ProviderMetadata.ts` module. The implementation and
|
||||
`src/composition/auxiliaryAnalysisProviders.ts` reuse the same identity and exact
|
||||
capability descriptors. The declarations reference existing AnalysisProvider
|
||||
loaders; they cover these three disjoint auxiliary implementations only. Deep
|
||||
candidate selection and the five typed observation ports remain separate.
|
||||
|
||||
The binary runtime reads these source declarations while auxiliary construction
|
||||
stays lazy. The separate managed-only entrypoint retains direct construction for
|
||||
its execution-free workflow. The MCP catalog generator reads declarations without
|
||||
constructing implementations, using its canonical macOS platform; actual runtime
|
||||
host restrictions remain explicit.
|
||||
Discovery and binary operation-kind routing now consume canonical source contracts
|
||||
rather than their generated JSON artifact. No declaration imports its generated
|
||||
output. Generated catalog facts and public schemas stay unchanged.
|
||||
|
||||
All observation registrars resolve contracts by name. An isolated compiled-process
|
||||
test reverses presentation arrays and performs actual MCP SDK calls to verify
|
||||
advertised schemas and handler meaning. Reordering metadata must not select a
|
||||
different operation, schema, provider or deep-provider priority.
|
||||
|
||||
## Android layer and verification ownership
|
||||
|
||||
Android workflows and their existing typed port live in `src/application/android/`,
|
||||
pure APK/inventory semantics in `src/domain/android/`, and the named inspection
|
||||
contracts in `src/contracts/android/`. The headless JADX producer remains in
|
||||
`src/android/`; `src/composition/android.ts` supplies its fresh factory to both
|
||||
caller adapters. Public names, result meaning and JVM/queue/cleanup limits are
|
||||
unchanged. Inventory-only application projection remains execution-free.
|
||||
|
||||
The real lane and signal-cleanup helper belong to `scripts/verify/android/`; its
|
||||
explicit downloader and fixed manifest belong to `scripts/fixtures/android/`.
|
||||
`verify:android` and `fixtures:android` keep their command names and repository-root
|
||||
`_reference/apk-integration/` fixture default. Synthetic producer fixtures stay in
|
||||
`tests/fixtures/android/`, with matching boundary and composition ownership. Real
|
||||
JADX CLI/MCP parity requires the fixed APK and existing audited JAR; synthetic
|
||||
protocol/cancellation success does not establish that engine or an unverified host.
|
||||
|
||||
## Deep-provider source declarations
|
||||
|
||||
Hopper, Ghidra and IDA declare their implemented analyst operations in their
|
||||
own `*ProviderCapabilities.ts` modules. Hopper's inventory is typed against the
|
||||
existing direct-operation contracts; Ghidra reuses its inventory/function adapter
|
||||
operations; IDA retains its read-only operation family. Frozen operation arrays
|
||||
feed the existing capability builders without importing generated catalog data.
|
||||
|
||||
The catalog generator reads these source declarations and joins operation names
|
||||
to canonical `TOOL_CONTRACTS`. A missing contract identifies the declaring provider
|
||||
and operation. Public schemas and descriptions remain owned by contracts; the
|
||||
declarations describe implementation coverage. Provider adapters must not consume
|
||||
the generated MCP catalog that is derived from them. The resolved module guard
|
||||
checks this edge while admitting canonical source contracts, generated package
|
||||
metadata and the existing application snapshot-cache consumer.
|
||||
|
||||
Exact provider effects and lifecycle differences remain in their adapters:
|
||||
Hopper's GUI and mutation effects, Ghidra's experimental Windows authority and
|
||||
mutation restrictions, and IDA's attached/headless effects and live observations.
|
||||
This source increment introduces no common provider lifecycle or new interface.
|
||||
|
||||
## JavaScript application ownership
|
||||
|
||||
`src/application/javascript/` owns JavaScript/Electron artifact analysis and
|
||||
graph projection, feature/version/export/source comparisons, runtime Evidence
|
||||
and reconciliation, observation ports, and source recovery workflows. Pure
|
||||
analysis and public schemas keep their existing domain/contract ownership;
|
||||
shared artifact inventory, investigation records and execution types remain
|
||||
shared. CLI, MCP and production factories import the owning workflow directly.
|
||||
|
||||
`src/domain/javascript/` owns static parsing and semantic IR, application and
|
||||
semantic graphs, Electron facts, runtime reconciliation, recovery result
|
||||
semantics, and source-to-bundle comparisons. Shared Evidence, digests, ordering,
|
||||
comparison semantics and historical-source graphs stay at their existing owners.
|
||||
Native handoffs link exact-subject Evidence without selecting or executing a
|
||||
provider.
|
||||
|
||||
`src/contracts/javascript/` owns the JavaScript/Electron observation and recovery
|
||||
contracts, JavaScript-specific workflow requests, and their examples. The shared
|
||||
`applicationToolContracts.ts` aggregate stays at the contract root because it also
|
||||
owns reconstruction and Android/Apple projection operations. Canonical tool
|
||||
aggregation, named handler binding and SDK schema presentation remain unchanged.
|
||||
Catalog source paths, compiled verifier/SDK fixtures and recovery CI filters
|
||||
follow the domain/contract owners.
|
||||
|
||||
`scripts/verify/javascript/` groups application-workflow, runtime-reconciliation,
|
||||
large-digest and real-recovery entrypoints. Their existing npm commands retain
|
||||
their invocation semantics. Recovery fixture compilation lives beside its
|
||||
unchanged sources under `scripts/fixtures/javascript-recovery/`. Its default
|
||||
REA entrypoint still resolves to `scripts/rea.mjs`. Managed conformance imports
|
||||
the relocated compiled workflow; owning CI source filters follow the new paths.
|
||||
|
||||
`src/artifacts/javascript/` owns directory/ASAR reader construction and relevant
|
||||
file acquisition, including root/nested byte verification, UTF-8 diagnostics and
|
||||
nested reader cleanup. Its narrow factory returns the existing `ArtifactReader`;
|
||||
the application retains input admission, inventory, graph construction and its
|
||||
original root-reader `finally` close. Pure file facts live in
|
||||
`src/domain/javascript/javascriptArtifactFiles.ts`, and the shared immutable
|
||||
inventory snapshot lives in `src/domain/artifactInventorySnapshot.ts`. Inventory
|
||||
options and integrity policy remain application-owned. Shared stream hashing
|
||||
lives in `src/artifacts/ArtifactHash.ts`, preserving complete byte counts and the
|
||||
bounded classification prefix. Acquisition/hash guards reject imports from
|
||||
application, composition or caller adapters; JavaScript workflows use the factory
|
||||
instead of importing concrete ASAR/directory reader classes.
|
||||
|
||||
ZIP, native-DMG and Mach-O inventory reader selection keep their existing
|
||||
lifecycles. Runtime ports retain their distinct Inspector, passive Electron and
|
||||
active Electron effects; shipped Electron hooks remain at their existing
|
||||
provider boundary.
|
||||
|
||||
## Managed capability ownership
|
||||
|
||||
`src/domain/managed/` owns managed artifact and member semantics, reconstruction
|
||||
import, native-boundary verification and application graph projection.
|
||||
`src/application/managed/` owns their shared Evidence workflows, and
|
||||
`src/contracts/managed/` owns their named contracts and examples. Producer
|
||||
metadata/IL parsing stays in `src/dotnet/`; target resolution, Evidence, ordering
|
||||
and investigation records retain their shared owners. Path-based member
|
||||
comparison preserves its actual target/byte-digest admission and existing parser
|
||||
helper.
|
||||
|
||||
`scripts/verify/managed/` groups the portable conformance entrypoint and its
|
||||
support, manifest, oracle and completion-report helpers. Its PE byte fixture
|
||||
builder and declaration live in `scripts/fixtures/managed/`, shared with the
|
||||
installed-package lane and existing managed conformance tests. `verify:managed`
|
||||
keeps its command name and optional caller-cwd manifest resolution. Compiled
|
||||
catalog and generator imports follow the owners; document-check cache inputs
|
||||
include the relocated runtime verifier and fixture files.
|
||||
|
||||
Portable byte fixtures, optional operator-local manifests, actual ILSpy oracles
|
||||
and real Ghidra NativeAOT checks retain their distinct proof levels and
|
||||
prerequisites. The existing NativeAOT fixture/engine lane keeps its owner. This
|
||||
move preserves implementations, Evidence identities and generated commitments.
|
||||
|
||||
## Binary application ownership
|
||||
|
||||
`src/application/binary/` owns the active session, deep-provider registry and
|
||||
evaluation, operation routing, disjoint provider composition, lazy auxiliary
|
||||
clients, cancellation and client cleanup. Its records facade and snapshot
|
||||
cache/files remain bound to the exact target and analysis profile. Investigation
|
||||
records still have their separate owner in `src/application/investigation/`.
|
||||
|
||||
`AnalysisProvider.ts` stays shared because its identity/execution types also serve
|
||||
nonbinary ports. `BinaryTargetResolver.ts` stays shared because Android, firmware,
|
||||
managed and artifact workflows use its target parsing. Production wiring lives
|
||||
in `src/composition/binary.ts`; application workflows gain no outward composition
|
||||
dependency.
|
||||
|
||||
The three colocated application tests move with their owner. Composition cases
|
||||
remain in `tests/composition/analysis-sessions/`; filesystem and SDK cases retain
|
||||
their boundary lanes. The shared `tests/fixtures/binarySession.ts` factory supports
|
||||
several capability families. Existing recursive application globs discover the
|
||||
moved tests; bridge assets and real-verifier entrypoints remain at their owners.
|
||||
|
||||
## Firmware layer and verification ownership
|
||||
|
||||
Firmware workflows and their existing typed port live in
|
||||
`src/application/firmware/`, pure request/result semantics in
|
||||
`src/domain/firmware/`, and named contracts in `src/contracts/firmware/`.
|
||||
`src/firmware/` keeps producer commands, report parsing, source copies, publication
|
||||
and cleanup; `src/composition/firmware.ts` supplies a fresh typed factory.
|
||||
|
||||
The real lane belongs to `scripts/verify/firmware/analysis.mjs`; the fixture runner
|
||||
and its unchanged Python producer belong together in `scripts/fixtures/firmware/`.
|
||||
The npm command names and selected `REA_FIRMWARE_FIXTURE_ROOT` are unchanged. The
|
||||
runner retains its working-directory-relative default; the verifier retains its
|
||||
repository-root default. Both point to `_reference/firmware-integration/generated`
|
||||
when invoked through the documented npm commands. Optional ext4/Ghidra lanes keep
|
||||
their own prerequisites. Source-fixture generation and synthetic producer tests
|
||||
do not establish real Binwalk/Unblob analysis or another host's execution support.
|
||||
|
||||
## Apple artifact capability ownership
|
||||
|
||||
`src/domain/apple/` owns Apple application anatomy/projection, asset catalog
|
||||
facts, Interface Builder graphs, keyed archive semantics and plist values.
|
||||
`src/application/apple/AppleApplicationService.ts` retains the shared workflow
|
||||
that projects authenticated IPA/macOS inventory Evidence.
|
||||
|
||||
`src/artifacts/apple/` owns the actual producer boundary: directory acquisition,
|
||||
assetutil invocation, plist/NIB decoding, encoded view-parent interpretation and
|
||||
keyed archive inspection. These helpers are used by ArtifactProvider, retain
|
||||
its existing typed analysis boundary, injected assetutil seam and original reader
|
||||
cleanup, and do not require a new port for each helper. Producer code no longer
|
||||
lives in the application layer. Guards reject application/composition/caller
|
||||
imports from this owner while admitting pure facts and shared artifact readers.
|
||||
Adapter-source tests retain their assertions in the existing forked adapter lane.
|
||||
|
||||
`scripts/verify/apple/` groups archive, asset catalog, Interface Builder,
|
||||
Objective-C/Swift dispatch and macOS bundle verification. The macOS bundle fixture
|
||||
builder lives in `scripts/fixtures/apple/`; NIB byte fixtures stay beside their
|
||||
artifact decoder. Existing format-specific conformance sources and goldens keep
|
||||
their locations, and relocated verifiers resolve them against their own file
|
||||
URLs. Public npm entrypoints and CLI/MCP contracts stay stable; generated-document
|
||||
cache inputs include the relocated runtime scripts and fixture builder.
|
||||
|
||||
Portable decoder and injected producer tests establish their format/boundary
|
||||
claims. Actual Swift/Xcode, assetutil, native NIB, bundle signing and DMG lifecycle
|
||||
claims require the real Apple lane. Native binary API/value-flow semantics and
|
||||
shared cross-domain contract aggregates keep their separate owners.
|
||||
|
||||
## Binary production composition closure
|
||||
|
||||
`src/composition/binary.ts` is the shared production owner for deep candidate
|
||||
construction, lazy auxiliary composition and the managed-only session factory.
|
||||
It preserves the existing constructor bodies, selection policy and fresh session
|
||||
per invocation. Factory registration acquires no engine or target.
|
||||
|
||||
`DirectAnalysis` and `DirectAnalysisStatus` receive these two existing factories
|
||||
through `DirectAnalysisDependencies`. They retain configuration parsing, selected
|
||||
status environment, cancellation, snapshot replay/binding, Evidence projection
|
||||
and finally-close. The dependency uses the existing BinarySession type, including
|
||||
its replay policy; it adds no universal backend or lifecycle contract.
|
||||
|
||||
`src/composition/directAnalysis.ts` binds the production factories for CLI and
|
||||
one-shot managed MCP callers. Binding creates no session and stores no retained
|
||||
analysis state. MCP startup constructs its connection session through the same
|
||||
binary factory. Source and compiled fixtures use these entrypoints. The former
|
||||
application runtime path is removed, and the source guard no longer admits its
|
||||
outward provider/composition imports.
|
||||
|
||||
## Native analyst capability ownership
|
||||
|
||||
`src/domain/native/` owns portable native inspection, instruction/data types,
|
||||
load-image facts, API boundaries, annotations, metadata recovery, UI observation
|
||||
semantics and value/investigation graphs. `src/application/native/` owns shared
|
||||
API projection, call routes, dispatch inspection and UI/value tracing. Its tests
|
||||
keep the existing service lane. `src/contracts/native/` owns the named native
|
||||
adapter contracts; shared official/enhanced aggregates remain at their owners.
|
||||
|
||||
Producer interpretation, external commands and host UI lifecycle remain in
|
||||
`src/native/`, Hopper, Ghidra and IDA. The shared function dossier still lives in
|
||||
`domain/hopperValues.ts`: its historical name does not make its meaning exclusive
|
||||
to Hopper. This move retains existing typed ports, exact result schemas,
|
||||
observed/derived/unknown distinctions, profiles and Evidence links.
|
||||
|
||||
Source and compiled verifier imports follow the new owners. Host UI and real
|
||||
native-value verifier locations are retained until their own proof lanes migrate;
|
||||
interactive macOS UI success and real dependency tracing require their respective
|
||||
host/engine workflows. Portable source fixtures and injected call-route tests
|
||||
establish their narrower boundaries, without expanding provider/platform support.
|
||||
@@ -1,7 +1,10 @@
|
||||
# Firmware analysis
|
||||
|
||||
REA integrates **Binwalk 3.1.0** for region inspection and **Unblob 26.6.4** for
|
||||
explicit extraction. This first firmware milestone is verified on Linux x64.
|
||||
REA accepts **Binwalk 3.1.x** for region inspection and **Unblob 26.6.x** for
|
||||
explicit extraction. The report parsers are verified with Binwalk 3.1.0 and
|
||||
Unblob 26.6.4. Another build on those lines is accepted, the observed version
|
||||
is reported, and a limitation records that the parser was not verified against
|
||||
that build. This first firmware milestone is verified on Linux x64.
|
||||
No device, root privilege, mount, emulation or execution of extracted programs
|
||||
is needed. Arbitrary firmware bytes do not pass native executable admission;
|
||||
select a returned ELF separately for supported Ghidra analysis.
|
||||
@@ -16,7 +19,8 @@ export REA_UNBLOB_COMMAND=/absolute/path/to/unblob
|
||||
```
|
||||
|
||||
REA does not install these tools, their dependencies or system packages. It
|
||||
checks the actual tool version when executing a request. Inspection and
|
||||
checks the tool's `--version` banner when executing a request. Binwalk must
|
||||
print `binwalk <version>` and Unblob must print the version alone. Inspection and
|
||||
extraction have independent prerequisites; one does not require the other.
|
||||
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
|
||||
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed.
|
||||
@@ -34,7 +38,7 @@ git submodule update --init third_party/binwalk third_party/unblob
|
||||
```
|
||||
|
||||
Normal REA builds and npm packages do not include or build these sources or
|
||||
engine binaries. See [upstream provenance](../third_party/README.md).
|
||||
engine binaries. See [upstream provenance](https://github.com/morluto/rea/blob/main/third_party/README.md).
|
||||
|
||||
## CLI and MCP
|
||||
|
||||
@@ -161,7 +165,7 @@ Additional lanes are explicit, so the base lane stays small:
|
||||
REA_FIRMWARE_VERIFY_EXT4=1 npm run fixtures:firmware
|
||||
REA_FIRMWARE_VERIFY_EXT4=1 npm run verify:firmware
|
||||
|
||||
# Existing Ghidra 12.1.4 and Java required.
|
||||
# Existing Ghidra 12.1.x and its declared JDK required.
|
||||
REA_FIRMWARE_VERIFY_GHIDRA=1 npm run verify:firmware
|
||||
```
|
||||
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
|
||||
REA can analyze DOS MZ executables and explicitly selected COM images through the bring-your-own Ghidra adapter.
|
||||
The Linux x64 verification lane exercises actual 16-bit disassembly and
|
||||
decompilation through the CLI and stdio MCP. It requires the same Ghidra
|
||||
12.1.4 and 64-bit JDK 21 installation as other Ghidra sessions; no DOS emulator
|
||||
or cross-compiler is required.
|
||||
decompilation through the CLI and stdio MCP. Provider admission accepts Ghidra
|
||||
12.1.x and that installation's declared JDK range. The verification lane uses
|
||||
Ghidra 12.1.4 and JDK 21; no DOS emulator or cross-compiler is required.
|
||||
|
||||
## Open and inspect
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ source notices are retained. No upstream source or algorithms are rewritten.
|
||||
|
||||
The initial tested layout is .NET runtime **8.0.22**, NativeAOT RTR **9.1**,
|
||||
x86-64 ELF and native Windows PE **targets analyzed on Linux x64**, using
|
||||
Ghidra **12.1.4** and JDK **21**. Other layouts/architectures/hosts are unsupported.
|
||||
Ghidra **12.1.4** and JDK **21**. The build accepts any Ghidra 12.1.x release and
|
||||
javac 21 or newer, and it compiles with `--release 21`. Real verification of
|
||||
this layout remains the Ghidra 12.1.4 and JDK 21 pair. Other NativeAOT layouts,
|
||||
architectures, and hosts are unsupported.
|
||||
Windows Ghidra P0 has no database mutation authority. A PE/CLI or ReadyToRun
|
||||
assembly should use `inspect_managed_artifact`; it is not NativeAOT.
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: REA
|
||||
text: Reverse Engineer Anything
|
||||
tagline: Understand binaries, applications, and runtime behavior with one CLI and MCP server.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get started
|
||||
link: /installation
|
||||
- theme: alt
|
||||
text: View on GitHub
|
||||
link: https://github.com/morluto/rea
|
||||
|
||||
features:
|
||||
- title: Investigate with your agent
|
||||
details: Connect REA to your coding agent, describe the app or feature you want to understand, and follow the evidence.
|
||||
link: /mcp-prompts
|
||||
- title: Inspect across formats
|
||||
details: Explore native binaries, JavaScript and Electron apps, .NET assemblies, Android packages, firmware, and websites.
|
||||
link: /installation
|
||||
- title: Keep the evidence
|
||||
details: Analysis runs locally and returns observations, limitations, and unknowns through the CLI and MCP.
|
||||
link: /mcp-contracts
|
||||
---
|
||||
|
||||
## Start with one command
|
||||
|
||||
```bash
|
||||
npx rea-agents setup
|
||||
```
|
||||
|
||||
Choose your agent, review the planned changes, and approve setup. Native binary
|
||||
analysis can use an existing Hopper, Ghidra, or IDA installation; static
|
||||
JavaScript analysis needs no native analysis engine.
|
||||
|
||||
For a first result from an extracted JavaScript application or ASAR:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
See [installation and setup](./installation.md) for requirements and configuration.
|
||||
|
||||

|
||||
+22
-13
@@ -68,13 +68,12 @@ selects the npm release, and persistent MCP registrations are pinned to the
|
||||
version that performed setup. Installing newer instructions does not update a
|
||||
running server or its registration.
|
||||
|
||||
The release checked on **2026-10-06** was **4.0.1** (116 MCP tools). That artifact
|
||||
has static JavaScript inspection and full inline Evidence workflows, but does
|
||||
not include main's Windows native control bundle, Android/JADX or firmware
|
||||
tools, Ghidra function annotation workflow, or retained application-Evidence
|
||||
The release checked on **2026-10-06** was **4.1.0** (125 MCP tools). That artifact
|
||||
includes main's Windows native control bundle, Android/JADX and firmware
|
||||
tools, Ghidra function annotation workflow, and retained application-Evidence
|
||||
references.
|
||||
Main's catalog describes those additions; a source build or a subsequent
|
||||
release containing them is required. Package startup alone does not verify a
|
||||
Main's catalog describes the current code; a source build or a subsequent
|
||||
release containing later changes is required for anything newer. Package startup alone does not verify a
|
||||
provider's real platform workflow.
|
||||
|
||||
To check the published version, run `npm view rea-agents dist-tags.latest`.
|
||||
@@ -91,7 +90,7 @@ npx skills add morluto/rea --skill reverse-engineer-anything
|
||||
|
||||
This installs agent instructions and bundled references, not REA MCP
|
||||
registration or analysis engines. Follow the skill's
|
||||
[conditional connection guide](../skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
[conditional connection guide](https://github.com/morluto/rea/blob/main/skills/reverse-engineer-anything/SKILL.md#connect-only-when-needed).
|
||||
Working tools can be used immediately. If tools are missing, inspect the current
|
||||
client's registration with `doctor --client codex --json` (substitute its client
|
||||
ID), then plan repairs with `setup --client codex --dry-run --json`. Show and
|
||||
@@ -109,7 +108,9 @@ npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --
|
||||
```
|
||||
|
||||
It returns the complete Evidence record directly and requires no native engine.
|
||||
Provider failures in doctor do not prevent unrelated target-free tools.
|
||||
Provider failures in doctor do not prevent unrelated target-free tools. To check
|
||||
readiness for one task instead of auditing every integration, see
|
||||
[Check readiness for the task at hand](https://github.com/morluto/rea/blob/main/README.md#check-readiness-for-the-task-at-hand).
|
||||
|
||||
## Supported agents
|
||||
|
||||
@@ -130,6 +131,11 @@ Setup can configure these clients for REA's local MCP server:
|
||||
| Command Code | `commandcode` |
|
||||
| VS Code | `vscode` |
|
||||
|
||||
For OpenCode, setup writes the V1 `mcp.rea` entry, which OpenCode V1 and V2
|
||||
both load. If the configuration already uses OpenCode V2's native
|
||||
`mcp.servers` table, setup registers REA there instead and replaces any earlier
|
||||
`mcp.rea` entry from REA.
|
||||
|
||||
## Review setup changes
|
||||
|
||||
`rea setup` first offers the supported agents in a multi-select. Existing REA
|
||||
@@ -265,7 +271,10 @@ copying license secrets into logs, or killing unrelated Hopper processes.
|
||||
|
||||
REA connects to an existing Ghidra installation on Linux x64, macOS x64/arm64,
|
||||
or experimental Windows x64 P0.
|
||||
It requires Ghidra 12.1.4 and a 64-bit full JDK 21. On macOS, the installation
|
||||
It accepts Ghidra 12.1.x and the 64-bit full JDK declared by that installation's
|
||||
`application.java.min` and `application.java.max`. Current 12.1 releases require
|
||||
JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4
|
||||
and JDK 21. On macOS, the installation
|
||||
must include the native decompiler for the host architecture; REA does not
|
||||
build it or change Gatekeeper quarantine settings.
|
||||
|
||||
@@ -311,9 +320,9 @@ installation remains unavailable on Windows. Setup never installs Ghidra,
|
||||
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
|
||||
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
|
||||
|
||||
Doctor validates the platform, architecture, application version,
|
||||
`support/analyzeHeadless` or `support/analyzeHeadless.bat`, Java
|
||||
version/bitness, and the presence of `javac`/`javac.exe`.
|
||||
Doctor validates the platform, architecture, Ghidra 12.1.x application version,
|
||||
`support/analyzeHeadless` or `support/analyzeHeadless.bat`, the installation's
|
||||
Java major range, 64-bit JDK bitness, and the presence of `javac`/`javac.exe`.
|
||||
When Java is found through `PATH`, setup records its observed JDK home so GUI
|
||||
MCP clients do not depend on an incidental shell path. Setup shows every exact
|
||||
environment entry in its plan, writes only after approval, and never downloads,
|
||||
@@ -399,7 +408,7 @@ For a client that requires manual configuration, use:
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@4.1.0", "mcp"]
|
||||
"args": ["-y", "rea-agents@5.0.0", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,16 @@ registrations/removals and dispatch, Node timers and cancellation handles, async
|
||||
defaults, request construction and response consumers, parse/coercion/
|
||||
validation boundaries, and built-in resource acquisition/release.
|
||||
|
||||
Mutation tracking covers explicit member assignments, updates, deletion, and
|
||||
loop assignment targets, including the supported local aliases and shared
|
||||
nested references. Calls such as `Object.assign`, `Reflect.set`, and
|
||||
`Reflect.deleteProperty`, and writes reaching a caller's object through another
|
||||
function's parameter, are not tracked by this mutation pass. These channels
|
||||
can leave an initializer-derived literal in the graph even after the runtime
|
||||
value changes. Treat such a result as an uncovered mutation channel, not as
|
||||
proof of the current runtime value; a follow-up needs to model that channel
|
||||
and verify its caller/alias behavior.
|
||||
|
||||
Function fingerprints commit normalized syntax, control-flow shape, relation
|
||||
shape, literal sets, arity, and detected effects without using local names or
|
||||
source offsets. Equal duplicate fingerprints remain ambiguous. Dynamic
|
||||
@@ -164,7 +174,7 @@ in `application`, or `left`/`right` for comparisons. `RETURNED_ID` is a template
|
||||
not a literal valid ID. Native Evidence arrays still use complete records.
|
||||
References are scoped to one connection; `close_binary` clears them. Export a
|
||||
bundle before closing and import it on another connection, or supply the full
|
||||
inline Evidence there. npm 4.0.1 accepts full inline Evidence only; installing
|
||||
inline Evidence there. Versions before 4.1.0 accept full inline Evidence only; installing
|
||||
newer skill instructions does not change that schema. See
|
||||
[MCP Evidence inputs](mcp-contracts.md#retained-application-evidence-inputs).
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# JavaScript source recovery
|
||||
|
||||
`recover_javascript_sources` / `recover-javascript-sources` derives readable
|
||||
modules from one explicitly selected UTF-8 JavaScript file or bundle. Use it on
|
||||
files returned by `export_web_scripts` or other local scripts. The operation
|
||||
never executes the selected or recovered application code.
|
||||
|
||||
## Caller-supplied engine
|
||||
|
||||
The initial adapter is verified on Linux x64 with Wakaru **1.13.0**. Set
|
||||
`REA_WAKARU_COMMAND` to the absolute path of an executable supplied by the caller.
|
||||
The adapter uses util-linux `prlimit` (default `/usr/bin/prlimit`; override with
|
||||
an absolute `REA_JAVASCRIPT_PRLIMIT_COMMAND`). REA does not install either tool.
|
||||
Engine configuration is checked only when recovery is requested; other REA
|
||||
capabilities remain available without it.
|
||||
|
||||
Unmodified upstream source is retained in `third_party/wakaru`, pinned to
|
||||
`6070266d24b32951fa2fe1d2dad5b4313bd5c81b` (v1.13.0, Apache-2.0).
|
||||
The result reports the executed binary's exact path, SHA-256 and observed version.
|
||||
The audited source revision is distinct from `executed_source_revision: null`:
|
||||
a version string cannot prove which source produced a caller-supplied binary.
|
||||
|
||||
## Recover and analyze
|
||||
|
||||
```sh
|
||||
rea recover-javascript-sources /tmp/web-scripts/app.js /tmp/recovered-app --json
|
||||
rea analyze-javascript-application /tmp/recovered-app/modules --json
|
||||
```
|
||||
|
||||
MCP input:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "/tmp/web-scripts/app.js",
|
||||
"output_directory": "/tmp/recovered-app"
|
||||
}
|
||||
```
|
||||
|
||||
The output directory must be absent, absolute, and have an existing parent.
|
||||
The source is snapshotted in a private workspace, and the engine writes only to
|
||||
its own staging directory. Reported module paths, input identities, UTF-8 byte
|
||||
ranges, regular files and emitted map representations are validated before
|
||||
publication. REA writes verified bytes to an exclusively owned output tree and
|
||||
rolls it back on failure. It also checks that the original source and engine
|
||||
bytes remain unchanged before committing. The existing output is never overwritten.
|
||||
|
||||
Published contents:
|
||||
|
||||
- `inputs/bundle.js`: exact original snapshot; its digest binds the Evidence.
|
||||
- `modules/`: derived modules and any upstream-emitted source maps.
|
||||
- `reports/stdout.json` and `reports/provenance.json`: exact producer reports.
|
||||
- `manifest.json`: identities, modules, transform choices, warnings and limitations.
|
||||
|
||||
The inline result includes complete file paths/digests/sizes, extraction ranges,
|
||||
warnings and `analysis_input`, directly usable as the arguments to
|
||||
`analyze_javascript_application`. Raw Evidence retains selected command arguments,
|
||||
reported input paths, exit status, stderr and reports; temporary paths are recorded
|
||||
as historical execution coordinates after their workspace is removed.
|
||||
|
||||
## Transform choices and interpretation
|
||||
|
||||
`extraction_mode` defaults to `structural` (Wakaru `--unpack=strict`). `heuristic`
|
||||
uses `--unpack=auto`; `inspection` uses `--unpack=inspect` and can return finer,
|
||||
non-executable regions. `rewrite_level` accepts `minimal`, `standard` (default)
|
||||
and `aggressive`. Maps and provenance are requested in every operation.
|
||||
|
||||
Structural extraction can fall back to a single file. In the real fixtures,
|
||||
webpack 5 yields two modules; the esbuild scope-hoisted fixture remains one
|
||||
unknown-format script. Returned module counts and detected formats report those
|
||||
actual outcomes. A valid report with failed transformations returns `partial`
|
||||
with original warnings and files. An unusable report, inconsistent provenance,
|
||||
escaping path, symlink or missing module is an error, with owned cleanup.
|
||||
|
||||
Evidence confidence is `derived`. Extraction ranges are half-open **UTF-8 byte
|
||||
offsets in the original snapshot**, rather than rewritten line positions or a
|
||||
coverage assertion. Source maps are preserved without claiming independently
|
||||
verified mapping accuracy. Original variable names and arbitrary runtime
|
||||
equivalence remain unknown. No network fetching occurs.
|
||||
|
||||
## Resource policy and verification
|
||||
|
||||
Each operation uses one worker, a 1 GiB address-space ceiling and a 120-second
|
||||
deadline; it accepts up to 64 MiB of input and 128 MiB/10000 entries of staged
|
||||
output, with 8 MiB per diagnostic/report stream. Exceeding a limit is an explicit
|
||||
failure with rollback. These limits apply per operation and do not claim aggregate
|
||||
host containment across concurrent callers.
|
||||
|
||||
See [testing.md](testing.md#javascript-source-recovery) for the focused real
|
||||
Wakaru CLI/MCP lane and its source-owned compiler fixtures. Other host platforms
|
||||
and Bun/Vue recovery are not claimed by this adapter.
|
||||
@@ -25,7 +25,7 @@ part of the current tool set.
|
||||
|
||||
This guide describes the implementation and verification of
|
||||
[ADR-0003](adr/0003-managed-code-evidence-and-provider-boundary.md). The canonical
|
||||
tool inventory is [`product-catalog.json`](product-catalog.json).
|
||||
tool inventory is [`product-catalog.json`](https://github.com/morluto/rea/blob/main/docs/product-catalog.json).
|
||||
|
||||
## Shipped scope
|
||||
|
||||
@@ -90,6 +90,51 @@ coverage, and route while retaining the outer bundle commitment.
|
||||
| Obfuscated assembly | Same positive byte observations as its underlying deployment form | Meaning inferred only from names |
|
||||
| Malformed/unsupported | Exact admitted regions and failure locations | Completeness, successfully skipped rows, or semantics beyond the admitted parser boundary |
|
||||
|
||||
### NativeAOT metadata recovery
|
||||
|
||||
NativeAOT removes the ordinary CIL method bodies, so a CIL decompiler cannot
|
||||
reconstruct those bodies as C# source. The executable remains a native target
|
||||
for Hopper or Ghidra, where decompilation produces pseudocode and analyst
|
||||
inference. This is a different route from ordinary managed member inspection.
|
||||
|
||||
NativeAOT is not limited to Windows `.exe` files. The native image can be PE,
|
||||
ELF, or Mach-O, and NativeAOT can also publish shared libraries (PE DLL, ELF
|
||||
`.so`, or Mach-O `.dylib`) with explicitly exported entry points. Identify the
|
||||
image from its bytes and loader metadata, not its filename extension. When the
|
||||
image is inside an application bundle, inventory or extract the bundle first
|
||||
and carry the component identity into native analysis. Matching PDB, ELF debug,
|
||||
or dSYM sidecars can add symbol evidence when the selected provider supports
|
||||
them; verify their identity and keep them separate from observations made from
|
||||
the executable itself. [Microsoft's NativeAOT overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
|
||||
lists supported OS/architecture targets and deployment limitations, while its
|
||||
[native library guide](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/libraries)
|
||||
describes exported shared-library entry points.
|
||||
|
||||
The [Ghidra NativeAOT analyzer](https://github.com/Washi1337/ghidra-nativeaot)
|
||||
is a useful optional companion for recovering ReadyToRun metadata. It
|
||||
rehydrates the `DEHYDRATED_DATA` section and annotates method tables, type
|
||||
relationships, vtable slots, frozen objects, and strings. It does not recover
|
||||
original C# method bodies. It is a separate Ghidra extension and interactive
|
||||
metadata browser; REA does not currently install or invoke it through its
|
||||
headless bridge. Its README describes ReadyToRun header discovery, including
|
||||
symbol-based and heuristic paths, so a missed header or unsupported binary
|
||||
should remain an explicit limitation rather than a failed claim about the
|
||||
binary's contents.
|
||||
|
||||
The upstream analyzer currently gates its own analysis to x86-64. NativeAOT's
|
||||
broader platform matrix does not establish REA coverage: PE/COFF, ELF, Mach-O,
|
||||
shared libraries, target architectures, and runtime metadata versions each need
|
||||
their own provider verification. REA's experimental Windows Ghidra boundary is
|
||||
limited to admitted native x86-64 PE applications, so it does not currently
|
||||
establish coverage for PE DLLs. Mobile NativeAOT targets should remain
|
||||
experimental until REA has a matching provider and package-level verification.
|
||||
|
||||
For REA's native analysis, preserve the same artifact path and digest used by
|
||||
`inspect_managed_artifact`, and report recovered type data as provider
|
||||
observations. Linking those types to native function addresses requires verified
|
||||
provider evidence; names or vtable similarity alone do not prove that a native
|
||||
function implements a specific managed method.
|
||||
|
||||
## Evidence record shape
|
||||
|
||||
Every planned operation returns a provider result and Evidence with four
|
||||
@@ -239,7 +284,13 @@ Cross-version matching first prefers exact CIL/signature identity. When that
|
||||
does not match, it pairs an exact declared type, method name, and raw signature
|
||||
before trying structural body shape. The exact-signature key uses names only
|
||||
as part of that full tuple; names alone never select a pair. Duplicate tuples
|
||||
remain ambiguous rather than being paired by token order.
|
||||
remain ambiguous rather than being paired by token order. Fields follow the
|
||||
same exact tiers. The raw signature is exact whether or not REA decoded it, so
|
||||
undecoded signatures pair by that tuple too, but they never enter structural
|
||||
rounds. An unmatched member is therefore reported as `unknown`, not added or
|
||||
removed, when its own signature was not decoded or when the other side has an
|
||||
unpaired member, one-sided or ambiguous, with the same declared type and name
|
||||
whose signature was not decoded.
|
||||
|
||||
For a matched method, an unavailable or partial body makes body-shape facets
|
||||
unknown while preserving observed signature differences. Structural identity
|
||||
|
||||
@@ -6,6 +6,12 @@ coverage and unknowns. The CLI and MCP use the same application workflows.
|
||||
|
||||
## Static inspection
|
||||
|
||||
`inspect_macho` retains otool segment `file_offset` values relative to the
|
||||
selected Mach-O slice. Its evidence file-offset ranges address the original
|
||||
input file and include the observed lipo slice offset for universal binaries.
|
||||
If that offset is unavailable, segment evidence locations are omitted with an
|
||||
explicit limitation; architecture inventory locations remain available.
|
||||
|
||||
- `inspect_asset_catalog` / `rea inspect-asset-catalog <app>` reads compiled
|
||||
`Assets.car` metadata through macOS `assetutil --info`. Catalog digests, raw
|
||||
rendition fields, pagination and exact UI resource-name matches are returned.
|
||||
@@ -24,8 +30,8 @@ coverage and unknowns. The CLI and MCP use the same application workflows.
|
||||
connections and controller/class names. `verify:interface-builder` compiles
|
||||
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
|
||||
iOS platform; unsupported archive forms remain explicit.
|
||||
- `inspect_native_dispatch_metadata` / `rea inspect-native-dispatch-metadata
|
||||
<app-or-binary>` prefers a validated macOS Mach-O byte reader. It decodes
|
||||
- `inspect_native_dispatch_metadata` /
|
||||
`rea inspect-native-dispatch-metadata <app-or-binary>` prefers a validated macOS Mach-O byte reader. It decodes
|
||||
64-bit little-endian Objective-C class/metaclass records, superclass pointers,
|
||||
absolute/relative method entries, ivar offsets/sizes/alignment and protocol
|
||||
declarations. It also decodes simple Swift conformances, static synchronous
|
||||
@@ -33,9 +39,20 @@ coverage and unknowns. The CLI and MCP use the same application workflows.
|
||||
vtable descriptors. Records include exact virtual addresses/file offsets and
|
||||
artifact evidence. Vtable indexes are metadata word offsets; witness indexes
|
||||
start after the conformance header. Names unavailable in metadata remain null.
|
||||
Chained fixups, external bindings, authenticated pointers, categories,
|
||||
properties, generic/resilient/async/coroutine tables and inherited overrides
|
||||
have explicit unsupported or partial coverage. Other providers retain the
|
||||
Pointers are decoded through the image's fixups:
|
||||
- `LC_DYLD_CHAINED_FIXUPS` rebases and binds, including the authenticated
|
||||
arm64e formats;
|
||||
- legacy `LC_DYLD_INFO` bind opcodes.
|
||||
|
||||
So an external superclass resolves from its `_OBJC_CLASS_$_` bind (for
|
||||
example `NSObject`). The `pointer_fixups` coverage facet names the mechanism.
|
||||
Class properties, with parsed attributes, and `__objc_catlist` categories are
|
||||
also decoded. Categories record the extended class, local or external, plus
|
||||
their methods, protocols and properties; category methods appear as
|
||||
implementations with a `category`. Swift field-offset globals that are only
|
||||
initialized at runtime stay unresolved. Generic, resilient, async and
|
||||
coroutine tables, and inherited overrides, have explicit unsupported or
|
||||
partial coverage. Other providers retain the
|
||||
existing symbol-based inventory with its narrower coverage.
|
||||
|
||||
## Native instruction, call and type primitives
|
||||
@@ -144,7 +161,7 @@ actions may change application data or trigger network activity.
|
||||
|
||||
## Provider and verification boundaries
|
||||
|
||||
Install Ghidra 12.1.4 and a full 64-bit JDK 21 separately, then configure REA to
|
||||
Install a Ghidra 12.1.x release and the 64-bit full JDK it declares, then configure REA to
|
||||
use them. Ghidra analysis supports Linux x64 and macOS x64/arm64; macOS requires
|
||||
the matching native decompiler. Experimental Windows x64 P0 admits native
|
||||
x86-64 PE applications on local NTFS using bundled Job Object ownership,
|
||||
@@ -159,8 +176,10 @@ GUI authority, and REA never falls back automatically to Hopper.
|
||||
instruction/call facts, value dependencies and process/project cleanup.
|
||||
- `npm run verify:ghidra:aarch64-jump-table`: optimized ELF and byte/halfword
|
||||
relative tables, plus ARM64 Mach-O on an ARM64 macOS host.
|
||||
- `npm run verify:apple-dispatch`: source-built Objective-C protocols/classes and
|
||||
Swift conformances/vtables, repeated after stripping local symbols.
|
||||
- `npm run verify:apple-dispatch`: source-built Objective-C protocols, classes,
|
||||
properties and categories, and Swift conformances and vtables. Each is linked
|
||||
with legacy `LC_DYLD_INFO` and with chained fixups (and as arm64e on Apple
|
||||
silicon), then repeated after stripping local symbols.
|
||||
- `npm run verify:native-ui`: one source-owned fixture window, successful
|
||||
selected-window capture and actions, changed-target rejection, and cleanup.
|
||||
Missing OS permissions fail this lane.
|
||||
|
||||
@@ -12,6 +12,24 @@ sandbox. Filesystem observation paths select what REA snapshots; they do not
|
||||
restrict what the process can read or write. The inherited environment is not
|
||||
recorded, so its influence may remain unknown.
|
||||
|
||||
## Host support
|
||||
|
||||
Capturing a new scenario currently requires Linux or macOS and a working
|
||||
native PTY backend. Native Windows capture is unavailable because the PTY
|
||||
adapter does not yet verify descendant cleanup. The native Job Object controls
|
||||
used by other REA providers do not establish PTY capture support. Reinstalling
|
||||
the Windows PTY binary does not enable this workflow.
|
||||
|
||||
For Linux commands, use Linux REA inside WSL. Adapt the scenario to that host;
|
||||
this does not establish capture of a native Windows process tree. Comparing
|
||||
existing capture Evidence through the CLI or MCP remains available on Windows
|
||||
and does not launch a PTY or the captured target.
|
||||
|
||||
On supported capture hosts, a missing or incompatible native PTY binary has a
|
||||
different recovery: reinstall REA for the active platform, architecture, and
|
||||
Node.js version with optional dependencies enabled. Capability diagnostics
|
||||
distinguish this from the Windows capture-adapter limitation.
|
||||
|
||||
## Capture a command
|
||||
|
||||
Write a JSON scenario and pass its path to the CLI:
|
||||
@@ -22,6 +40,8 @@ rea capture-process ./scenario.json --json > capture.json
|
||||
|
||||
`--json` is required when saving input for JSON consumers; the default terminal
|
||||
format is TOON. The file contains the complete capture Evidence record.
|
||||
Choose an output file distinct from the scenario input: shell redirection opens
|
||||
and truncates the output before REA reads the scenario.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
+58
-12
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "rea-agents",
|
||||
"version": "4.1.0",
|
||||
"version": "5.0.0",
|
||||
"sdk": {
|
||||
"server": "2.3.1",
|
||||
"client": "2.3.1",
|
||||
"core": "2.3.1"
|
||||
},
|
||||
"skill_version": "26"
|
||||
"skill_version": "27"
|
||||
},
|
||||
"tools": {
|
||||
"total": 125,
|
||||
"total": 133,
|
||||
"families": [
|
||||
{
|
||||
"id": "direct",
|
||||
@@ -142,7 +142,7 @@
|
||||
{
|
||||
"id": "browser",
|
||||
"surface": "browser-provider",
|
||||
"count": 9,
|
||||
"count": 11,
|
||||
"tools": [
|
||||
"analyze_web_bundle",
|
||||
"capture_browser_scenario",
|
||||
@@ -150,8 +150,10 @@
|
||||
"compare_web_captures",
|
||||
"compare_web_screenshots",
|
||||
"discover_webmcp_tools",
|
||||
"inspect_web_event_listeners",
|
||||
"inspect_web_page",
|
||||
"list_browser_targets",
|
||||
"observe_web_execution",
|
||||
"observe_web_session"
|
||||
]
|
||||
},
|
||||
@@ -179,15 +181,21 @@
|
||||
{
|
||||
"id": "application",
|
||||
"surface": "application-workflow",
|
||||
"count": 7,
|
||||
"count": 13,
|
||||
"tools": [
|
||||
"build_reconstruction_obligation_ledger",
|
||||
"compare_application_versions",
|
||||
"compare_javascript_export_shapes",
|
||||
"compare_source_to_bundle",
|
||||
"evaluate_reconstruction_coverage",
|
||||
"export_web_scripts",
|
||||
"project_android_application_graph",
|
||||
"project_apple_application_graph",
|
||||
"recover_javascript_sources",
|
||||
"trace_application_feature",
|
||||
"trace_javascript_semantics"
|
||||
"trace_javascript_semantics",
|
||||
"trace_web_module_imports",
|
||||
"trace_web_source_location"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -349,6 +357,18 @@
|
||||
"observe_native_ui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rea-android-application",
|
||||
"name": "REA Android application workflows",
|
||||
"version": "1",
|
||||
"capabilities": ["project_android_application_graph"]
|
||||
},
|
||||
{
|
||||
"id": "rea-apple-application",
|
||||
"name": "REA Apple application workflows",
|
||||
"version": "1",
|
||||
"capabilities": ["project_apple_application_graph"]
|
||||
},
|
||||
{
|
||||
"id": "rea-artifact-graph",
|
||||
"name": "REA safe artifact graph provider",
|
||||
@@ -371,8 +391,10 @@
|
||||
"compare_web_captures",
|
||||
"compare_web_screenshots",
|
||||
"discover_webmcp_tools",
|
||||
"inspect_web_event_listeners",
|
||||
"inspect_web_page",
|
||||
"list_browser_targets",
|
||||
"observe_web_execution",
|
||||
"observe_web_session"
|
||||
]
|
||||
},
|
||||
@@ -450,11 +472,27 @@
|
||||
"observe_javascript_runtime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rea-web-script-export",
|
||||
"name": "REA captured script export",
|
||||
"version": "1",
|
||||
"capabilities": [
|
||||
"export_web_scripts",
|
||||
"trace_web_module_imports",
|
||||
"trace_web_source_location"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "unblob",
|
||||
"name": "Unblob",
|
||||
"version": "26.6.4",
|
||||
"capabilities": ["extract_firmware"]
|
||||
},
|
||||
{
|
||||
"id": "wakaru",
|
||||
"name": "Wakaru",
|
||||
"version": "1.13.0",
|
||||
"capabilities": ["recover_javascript_sources"]
|
||||
}
|
||||
],
|
||||
"setup_clients": [
|
||||
@@ -532,7 +570,7 @@
|
||||
}
|
||||
],
|
||||
"cli": {
|
||||
"primary_count": 80,
|
||||
"primary_count": 88,
|
||||
"commands": [
|
||||
"address-to-file-offset",
|
||||
"analyze",
|
||||
@@ -562,6 +600,7 @@
|
||||
"evaluate-reconstruction-coverage",
|
||||
"evidence-export",
|
||||
"evidence-import",
|
||||
"export-web-scripts",
|
||||
"extract-artifact",
|
||||
"extract-firmware",
|
||||
"function",
|
||||
@@ -587,6 +626,7 @@
|
||||
"inspect-native-load-image",
|
||||
"inspect-plist",
|
||||
"inspect-signature",
|
||||
"inspect-web-event-listeners",
|
||||
"inspect-web-page",
|
||||
"instructions",
|
||||
"list-architectures",
|
||||
@@ -595,11 +635,15 @@
|
||||
"list-javascript-runtime-targets",
|
||||
"observe-javascript-runtime",
|
||||
"observe-native-ui",
|
||||
"observe-web-execution",
|
||||
"observe-web-session",
|
||||
"project-android-application-graph",
|
||||
"project-apple-application-graph",
|
||||
"project-managed-application-graph",
|
||||
"providers",
|
||||
"read-bytes",
|
||||
"reconcile-javascript-runtime",
|
||||
"recover-javascript-sources",
|
||||
"resolve-native-call-targets",
|
||||
"search",
|
||||
"search-android-classes",
|
||||
@@ -610,6 +654,8 @@
|
||||
"trace-javascript-semantics",
|
||||
"trace-native-ui-action",
|
||||
"trace-native-values",
|
||||
"trace-web-module-imports",
|
||||
"trace-web-source-location",
|
||||
"uninstall",
|
||||
"update",
|
||||
"verify-managed-native-boundaries",
|
||||
@@ -624,15 +670,15 @@
|
||||
},
|
||||
"runtime_catalog": {
|
||||
"counts": {
|
||||
"cli_commands": 80,
|
||||
"mcp_tools": 125,
|
||||
"cli_commands": 88,
|
||||
"mcp_tools": 133,
|
||||
"mcp_prompts": 6
|
||||
},
|
||||
"digests": {
|
||||
"tools_sha256": "eec3abb8fa5b25c6e5d6e361b7ebd157e3599f69b68defda85680dbfe061b3d3",
|
||||
"tools_sha256": "af7fe81ec689c2a77f04579b6f4beb4abe86aa71d49c4844d8e0f4d7ed7a3a1e",
|
||||
"prompts_sha256": "ef2de8814390b340623c32001610aa2709d90bed51537b6e562ab3abf144ab4d",
|
||||
"combined_sha256": "ed9c36900e9223561d38ba30a44921f4ff101d5eca989920f9edc9dc91df470b",
|
||||
"providers_sha256": "044634314b36b966bd918f99f116f4684108556ac1dabc912e76ccd351d97e3e"
|
||||
"combined_sha256": "83b4c3445ed0ec003c0aeefa3188f123d9ae319484f8b1e2b611d2fe278c9c72",
|
||||
"providers_sha256": "aea15f517c822d80918ccaba57a4fa06557f484cf27b8acbaed4e1a660fac896"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
This guide describes repository main. Check the [released package boundary](installation.md#released-package-and-main) when using npm.
|
||||
|
||||
Ghidra read-only analysis is available on Linux x64 and macOS x64/arm64.
|
||||
Install Ghidra 12.1.4 and a full 64-bit JDK 21 separately, then configure REA to
|
||||
use them. macOS installations need the matching native decompiler.
|
||||
Install a Ghidra 12.1.x release and the 64-bit full JDK that release declares,
|
||||
then configure REA to use them. Current 12.1 releases require JDK 21 or newer
|
||||
and set no maximum. The bridge is verified with Ghidra 12.1.4 and JDK 21.
|
||||
macOS installations need the matching native decompiler.
|
||||
|
||||
REA imports one target into a temporary project and exposes 25 read-only
|
||||
operations after analysis completes. They cover inventories, search,
|
||||
@@ -53,9 +55,11 @@ follow.
|
||||
|
||||
## Shipped foundation boundary
|
||||
|
||||
`GHIDRA_INSTALL_DIR` must identify an extracted official 12.1.4 release;
|
||||
optional `JAVA_HOME` must identify a 64-bit full JDK 21, otherwise doctor probes
|
||||
`java`/`javac` or `java.exe`/`javac.exe` from `PATH`. Supported Linux and macOS
|
||||
`GHIDRA_INSTALL_DIR` must identify an extracted Ghidra 12.1.x release. Optional
|
||||
`JAVA_HOME` must identify a 64-bit full JDK inside that installation's
|
||||
`application.java.min` and `application.java.max` (JDK 21 or newer, with no
|
||||
maximum, when the release leaves those at the 12.1 defaults). Otherwise doctor
|
||||
probes `java`/`javac` or `java.exe`/`javac.exe` from `PATH`. Supported Linux and macOS
|
||||
hosts accept compatible ELF, PE, and Mach-O executable targets. Host admission
|
||||
and target compatibility are separate checks. Windows x64 P0 admits only native,
|
||||
non-managed, non-DLL x86-64 PE applications on fixed local NTFS, with the packaged
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
# Releasing from a checkpoint
|
||||
|
||||
REA releases use a frozen source checkpoint. Main can continue accepting
|
||||
changes while a release is tested and published. The Release workflow runs
|
||||
on `release/*` branches; main pushes do not refresh release PRs.
|
||||
|
||||
## 1. Cut the release branch
|
||||
|
||||
Choose the next version from the unreleased Conventional Commits, including
|
||||
breaking changes. Record the full source SHA and create `release/VERSION` at
|
||||
that commit. The version is a maintainer choice; Release Please's proposed
|
||||
version must agree before publication.
|
||||
|
||||
For example, after selecting a reviewed commit for 5.0.0:
|
||||
|
||||
```bash
|
||||
git fetch origin main
|
||||
git branch release/5.0.0 SOURCE_SHA
|
||||
git push origin release/5.0.0
|
||||
```
|
||||
|
||||
Replace `SOURCE_SHA` with the selected full commit SHA. The checkpoint must
|
||||
include this release workflow and CI support for release branches. For an
|
||||
older checkpoint, backport only the release infrastructure first and record
|
||||
that additional commit. Do not merge later implementation changes into the
|
||||
release branch or rebase the candidate onto a moving main.
|
||||
|
||||
The branch push runs Release Please against that branch and opens a PR for
|
||||
the version, changelog, and registry metadata. The generation step normalizes
|
||||
the product catalog. To retry preparation after a failed run, select the same
|
||||
frozen branch explicitly:
|
||||
|
||||
```bash
|
||||
gh workflow run release.yml --ref release/5.0.0
|
||||
```
|
||||
|
||||
## 2. Review and merge the bot PR
|
||||
|
||||
Record the final bot PR head after normalization. Approve GitHub-blocked bot
|
||||
workflow runs for that head when needed. Review the candidate's version,
|
||||
notes, generated metadata, and package contents. Wait for the candidate's CI
|
||||
and relevant real-provider checks. Routine local iterations need focused
|
||||
checks; CI owns full deterministic coverage and platform lanes. If an artifact
|
||||
or real-provider check is unavailable, report that limit before deciding to
|
||||
publish.
|
||||
|
||||
Merge the reviewed PR into `release/5.0.0` with its head SHA matched. **This
|
||||
merge authorizes and triggers publication.** Further main commits do not change
|
||||
this candidate. Do not push or merge anything else into the release branch
|
||||
while publication is running. A necessary release fix must be reviewed and
|
||||
tested before the bot PR merges and establishes a new recorded checkpoint.
|
||||
|
||||
## 3. Verify publication
|
||||
|
||||
The merge push runs Release Please again, creates the tag and GitHub release,
|
||||
and publishes npm followed by MCP Registry metadata. Both publishers check
|
||||
out Release Please's exact release SHA. The triggering workflow also runs
|
||||
from that release commit so npm's provenance records the actual source.
|
||||
|
||||
Before invoking Release Please, the workflow checks that the release branch
|
||||
still equals the triggering SHA and rejects main or tag refs. Before either
|
||||
registry publish, it rejects a mismatch between the tagged release SHA and
|
||||
the triggering SHA. These checks detect moved branches; maintainers must also
|
||||
keep the release branch frozen throughout publication.
|
||||
See [npm's provenance implementation](https://github.com/npm/cli/blob/v11.16.0/workspaces/libnpmpublish/lib/provenance.js)
|
||||
for the use of GitHub's workflow ref and commit SHA.
|
||||
|
||||
The workflow builds the bundled Windows controls and verifies the packaged
|
||||
artifact before npm publication. It then verifies the published CLI, the
|
||||
capability-scoped MCP catalog, and the isolated package update path before
|
||||
publishing MCP Registry metadata.
|
||||
|
||||
Record these outcomes separately:
|
||||
|
||||
- GitHub release and tag, including the resolved commit SHA.
|
||||
- npm's exact version and integrity, with the published-package canary passed.
|
||||
- MCP Registry's exact server version and matching npm package version.
|
||||
|
||||
A GitHub tag alone does not establish npm or MCP Registry publication.
|
||||
|
||||
## Sync metadata back to main
|
||||
|
||||
After publication, open a PR from the release branch back to main. Preserve
|
||||
main's later implementation changes and resolve generated-file conflicts by
|
||||
regenerating from the combined contracts with the released package version.
|
||||
Review and test this synchronization PR, then use a merge commit so the release
|
||||
tag remains in main's ancestry. Keep the released tag unchanged.
|
||||
|
||||
Close any superseded rolling release PR. Future releases repeat the checkpoint
|
||||
procedure from main; never resume automatic release-PR refreshes on main pushes.
|
||||
|
||||
## Partial publication and retries
|
||||
|
||||
Inspect the failed job and public registry state before retrying. When npm is
|
||||
already published, the npm job verifies that exact version instead of
|
||||
publishing it again. Re-run failed jobs in the original publication run so its
|
||||
release SHA and outputs stay fixed. If only MCP publication failed, retry that
|
||||
job after checking that the npm canary succeeded.
|
||||
|
||||
Do not start a fresh workflow run to repair an already-created release:
|
||||
Release Please will not create the same release again. Do not move the tag,
|
||||
delete the release, or unpublish npm as a retry. A defective public package
|
||||
requires a reviewed correction and a new version.
|
||||
+4
-2
@@ -45,8 +45,10 @@ verified paths for an existing Ghidra installation. It configures Claude Code,
|
||||
Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity,
|
||||
GitHub Copilot CLI, Command Code, and VS Code using each client's configuration format.
|
||||
|
||||
Ghidra analysis supports Linux x64 and macOS x64/arm64 with Ghidra 12.1.4 and a
|
||||
64-bit full JDK 21. macOS also requires the matching native decompiler. The
|
||||
Ghidra analysis supports Linux x64 and macOS x64/arm64 with Ghidra 12.1.x and the
|
||||
64-bit full JDK that installation declares. Current 12.1 releases require JDK 21
|
||||
or newer and set no maximum; the bridge is verified with Ghidra 12.1.4 and JDK 21.
|
||||
macOS also requires the matching native decompiler. The
|
||||
adapter exposes thirteen inventory operations and twelve function-analysis
|
||||
operations, for 25 read-only operations total. Linux and macOS additionally support
|
||||
atomic function-name and entry-comment edits with refreshed analysis; metadata
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
# Test suite audit
|
||||
|
||||
## Scope and priority
|
||||
|
||||
The expanded audit on 2026-10-04 reviewed every one of the 355 Vitest files
|
||||
present at its start: 82 domain/contracts, 56 application/composition, 147
|
||||
adapter/configuration/process/filesystem/CLI files, and 70 MCP/acceptance/
|
||||
conformance/evaluation files. Three review passes checked the proposed removals
|
||||
against existing stronger coverage and checked the final changes for lost
|
||||
assertions. The remaining suite has 351 files.
|
||||
|
||||
Prefer full real-provider E2E, then integration across schemas and transports,
|
||||
then real-data goldens. Remove lower-level duplication once the replacement
|
||||
asserts the same behavior. Keep distinct failure regressions that a successful
|
||||
E2E cannot exercise. Do not lower coverage thresholds to accommodate pruning.
|
||||
|
||||
## Changes made
|
||||
|
||||
| Previous test or assertion | Decision | Replacement or retained claim |
|
||||
| ----------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Asset application tests using fabricated `.car` bytes and injected utility JSON | Remove two plumbing cases | `verify:asset-catalog`: real actool → assetutil → CLI and stdio MCP, including digest, full metadata, pagination and invalid input |
|
||||
| Keyed archive positive binary/XML objects assembled with the same plist library as the reader | Remove two synthetic success cases | Captured Foundation XML plus reviewed complete object/reference graph golden; real Foundation binary/XML → CLI and stdio MCP |
|
||||
| Generic contract schema/annotation/example loops and output-map inventory comparisons | Consolidate into one SDK wire test | Assert exported schemas, guidance, effect annotations and valid examples on the actual advertised MCP catalog |
|
||||
| Ghidra function operation enum membership | Remove | Canonical catalog, provider handshake and actual operation roundtrips establish admission; malformed provider results remain covered |
|
||||
| Asset page assertion pinning `10_001` | Remove | Invalid-page rejection through real MCP; malformed utility data still covered |
|
||||
| Suggested prompt tool positions | Remove incidental ordering assertions | Keep authority, evidence, untrusted input and preparation constraints |
|
||||
| Package “acceptance” assertions pinning CI matrix/shard values and fake launcher command arrays | Remove configuration-shaped cases | Actual package/install verifier remains; retain a compact release verification-before-publish and registry-canary static guard |
|
||||
| Package failure classifier under acceptance | Move to package evaluation | Preserve all malformed/unrelated-failure rejection cases; do not call them E2E |
|
||||
| Native value graph checks conditional on success | Strengthen | Require success before pagination/ambiguity assertions; unexpected errors can no longer pass |
|
||||
| Native UI verifier treating permission denial as a positive pass | Separate outcomes | Default lane requires real capture/action success; opt-in permissions lane explicitly reports no positive E2E proof |
|
||||
|
||||
Ten previous test cases were removed or consolidated; three replacement
|
||||
Vitest cases cover the captured goldens and release guard. The two real artifact
|
||||
E2E scripts add product-level evidence outside the deterministic Vitest count.
|
||||
|
||||
## Exhaustive follow-up
|
||||
|
||||
The follow-up removes or consolidates another 31 test declarations, including
|
||||
three redundant test files. Parameterized declarations and executed cases have
|
||||
different counts; the figures above count source declarations. It also repairs
|
||||
assertions that could pass without exercising their stated behavior.
|
||||
|
||||
| Area | Cleanup | Stronger retained evidence |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| Configuration | Merge duplicate target/loader tests and preserve custom launcher, explicit provider and actionable error diagnostics | Independent malformed inputs; each capability flag is rejected separately |
|
||||
| Browser scenarios | Remove fake launch success and expression-selected artifact success | Real Chromium scenarios, actual artifacts, CLI capture and owned/external cleanup in `verify:browser` |
|
||||
| CLI policy | Remove duplicate confirmation helper cases | Compiled CLI proves project files stay unchanged until `--yes`; distinct interactive choices remain |
|
||||
| Provider capabilities | Remove numerical catalog freezes; consolidate Hopper byte-read discovery | Exact canonical capability identities, admission, mutation effects and detached descriptors |
|
||||
| Evidence ledger | Merge reordered-key checks into serialized bundle import; remove one-record quota claims | Atomic import, conflicts, residual revisions, actual large collections and byte regressions |
|
||||
| MCP batching and concurrency | Replace maximum concurrency and empty indistinguishable responses | Every distinct ordered batch result; concurrently gated requests complete in reverse order with matching payloads |
|
||||
| MCP inventories and graphs | Remove traversal/inventory call order and redundant catalog/prose checks | Exact complete cyclic graph, full inline inventories, canonical SDK catalog and all-handler roundtrips |
|
||||
| Authority and lifecycle | Attempt the refused Ghidra mutation; require successful setup, call and switch; close twice | Typed refusal with zero mutation dispatch, restored target calls and exact owned-resource cleanup |
|
||||
| Process and provider output | Remove incidental environment read and command option order/defaults | PID/ownership validation, exact disabled-policy failure and complete decoded response above 10 MiB |
|
||||
| Conformance and CI guards | Merge identical replay runs; remove duplicate missing-row fault, trivial hash/self equality and incidental worker/step counts | Distinct faulty evidence, digest order, actual test discovery, file existence and release/runner authority |
|
||||
| Pure schemas | Remove fabricated comparison-dimension injection and impossible long boundary success | Real artifact comparison and SDK coverage evaluation with returned boundary identity |
|
||||
|
||||
The removed long boundary selector test only parsed a 1,000-character request;
|
||||
workspace boundary identities admit at most 200 characters, so that positive
|
||||
could never represent a successful evaluation. The actual MCP evaluation stays
|
||||
covered. One unused ledger serialization helper was also retired after removing
|
||||
its only test consumer; no CLI or MCP contract changed.
|
||||
|
||||
No further immediately safe pruning was identified in the final review. Retained
|
||||
replacement candidates below are coverage gaps, rather than permission to delete
|
||||
unique tests. Test-only domain scaffolding also stays pending a separate decision
|
||||
about the corresponding source behavior; deleting those tests alone would leave
|
||||
unverified code and fail unused-export checks.
|
||||
|
||||
## Stronger replacements from the closing pass
|
||||
|
||||
Three more synthetic success cases were removed. Native value composition now
|
||||
runs through the production CLI and a separate stdio MCP subprocess with real
|
||||
Ghidra, using a source-built native fixture. It checks argument/parameter/return
|
||||
relationships, the fixture's global operand, authenticated Evidence, upstream
|
||||
and workflow analysis profiles, identical CLI/MCP graphs, discovery availability
|
||||
and successful MCP session closure. Ambiguity, cancellation, target changes,
|
||||
missing flow and bounded-work regressions remain in the focused suite.
|
||||
|
||||
That replacement found missing CLI workflow routing for `trace_native_values`
|
||||
and missing workflow profile classification. The routing and discovery now use
|
||||
the composed operation's actual provider prerequisites; workflow Evidence commits
|
||||
to the upstream Ghidra profile.
|
||||
|
||||
The lone hand-built client-document equality test is replaced by a real TOML
|
||||
filesystem transaction: reversed environment keys leave the original bytes
|
||||
unchanged and create no backup. A real ZIP entry with an application root longer
|
||||
than 5,000 characters replaces injected schema-only positive data. Inventory
|
||||
completeness tests partition actual scans into authenticated partial observations
|
||||
and require exact assembled results and every source citation, including more
|
||||
than 100 citations. These are consumer merge tests, not provider pagination
|
||||
claims. Replay aggregation now covers all four statuses and retains actual runner
|
||||
diagnostics; it does not claim to execute a real sandbox. Retained pixel-metric
|
||||
fixtures now write correct PNG chunk checksums.
|
||||
|
||||
The all-zero screenshot buffer is replaced by a real Chromium PNG of 9,467,940
|
||||
bytes, transported intact through CLI and stdio MCP and decoded by the production
|
||||
pixel comparator. The verifier configures the SDK client's receive buffer because
|
||||
its 10 MiB default is smaller than the inline JSON response. This does not claim
|
||||
large comparison-request transport coverage. A real SDK initialize request,
|
||||
captured during a successful production stdio handshake, replaces the JSON-RPC
|
||||
decoder's field-count-only oracle with exact field/value expectations.
|
||||
|
||||
The closing pass also removed assertions tied to internal call order, defaults,
|
||||
private cache sizes, duplicated polling and fabricated schema extensions. Unique
|
||||
malformed-input, security-capacity and cleanup regressions remain.
|
||||
|
||||
## Real process regression found
|
||||
|
||||
The full suite exposed a renderer ordering race. Publishing a terminal output
|
||||
journal entry could synchronously trigger a reactive resize before the preceding
|
||||
output was queued for rendering. Queue that output first. The existing real
|
||||
source-owned interactive PTY test now also requires the first resized frame to
|
||||
contain the output that triggered the resize, preserving the original event
|
||||
order rather than sorting or changing timestamps to conceal it.
|
||||
|
||||
## Coverage kept
|
||||
|
||||
Malformed archives, broken and cyclic references, unsupported metadata pointers,
|
||||
relative Objective-C method layouts, ambiguous call candidates, digest mismatch,
|
||||
cancellation, host permission denial, bounded output and owned-resource cleanup
|
||||
remain covered. These cases provide evidence beyond a successful real-tool run.
|
||||
The hand-assembled relative method fixture stays until compiler-produced
|
||||
relative-list fixtures establish equivalent assertions.
|
||||
|
||||
## Remaining priorities
|
||||
|
||||
1. Extend the real Ghidra CLI/stdio MCP journey from value tracing to dispatch
|
||||
and UI handlers before pruning their hand-authored composition success cases.
|
||||
Native value tracing now establishes full transport parity.
|
||||
2. Real binary dispatch and nib → full product transports, with normalized
|
||||
semantic goldens. The compiler-backed readers already run in the new Apple
|
||||
CI lane; synthetic pointer/layout rejection tests remain useful.
|
||||
3. An approved interactive macOS runner for native UI capture/actions, including
|
||||
stdio MCP scenarios. Hosted runners without permissions cannot supply this
|
||||
proof. No arbitrary application's windows should be opened for this lane.
|
||||
4. Real macOS/Linux Ghidra CI runners with bring-your-own installations. This
|
||||
change adds Apple artifact CI without installing Java or Ghidra.
|
||||
5. Classify fake-runner replay/application acceptance accurately and replace
|
||||
repeated happy paths only after real sandbox journeys assert the same claims.
|
||||
The replay workflow's allow-unavailable mode must not imply executed replay.
|
||||
6. Replace repeated-array schema ceiling tests with real producer/transport
|
||||
regressions before deleting them. Existing integration tests do not yet
|
||||
establish their large-input guarantees.
|
||||
|
||||
Some static guards protect irreversible release authority rather than product
|
||||
behavior. Keep these few guards explicit and separate from E2E evidence.
|
||||
|
||||
## Verification after the expanded audit
|
||||
|
||||
- `npm run check:pr`: 351 files passed; 1,794 tests passed and two skipped.
|
||||
Typecheck, lint, formatting, unused-code and generated-document gates passed.
|
||||
- `npm run check:fast`: passed; 70 lint warnings and zero errors.
|
||||
- `npm run verify:browser`: actual Chromium observation and scenario CLI routes,
|
||||
retained artifacts, owned/external lifecycle checks and large CLI/stdio MCP PNG
|
||||
capture passed.
|
||||
- `npm run verify:ghidra`: macOS ARM64 with Ghidra 12.1.4/JDK 21 passed, including
|
||||
real native value tracing through CLI and stdio MCP: 171 nodes, 298 edges and
|
||||
six decompilations.
|
||||
- `npm run verify:asset-catalog`: real actool/assetutil → CLI and stdio MCP,
|
||||
complete records, pagination and malformed input checks passed.
|
||||
- `npm run verify:keyed-archive`: real Foundation binary/XML → CLI and stdio MCP,
|
||||
full graph golden, shared/cyclic identity and malformed scope checks passed.
|
||||
|
||||
- `npm run verify:fixtures`: seven source-built host fixtures, source/artifact
|
||||
digests and exhaustive inventory checks passed.
|
||||
|
||||
No Hopper windows were opened for this audit. The closing pass runs real Ghidra
|
||||
on macOS ARM64; earlier Hopper evidence remains separate. No new hosted CI success
|
||||
is claimed.
|
||||
+198
-15
@@ -32,6 +32,12 @@ registries but do not cross production filesystem, process, socket, or browser b
|
||||
Boundary tests cross one production boundary. Only acceptance tests assemble
|
||||
the complete runtime or invoke the compiled product surface.
|
||||
|
||||
Binary session, registry, lazy-client and snapshot module cases live under
|
||||
`src/application/binary/`. Their composition cases stay in
|
||||
`tests/composition/analysis-sessions/`; snapshot persistence and actual SDK calls
|
||||
stay in the filesystem and MCP boundary lanes. The shared injected session fixture
|
||||
serves several capabilities and remains under `tests/fixtures/`.
|
||||
|
||||
Focused immutable builders and recording ports shared by one test family live
|
||||
beside their production owner as `src/**/*.fixture.ts`. They are typechecked
|
||||
with the suite and excluded from package builds; broader runtime and provider
|
||||
@@ -55,6 +61,16 @@ needed, exercise the actual target through browser, Electron, or process
|
||||
capture. Real model trials are manual; Vitest covers deterministic evaluator
|
||||
logic.
|
||||
|
||||
`verify:managed` runs the portable PE byte-fixture conformance entrypoint under
|
||||
`scripts/verify/managed/`, with its byte builder under
|
||||
`scripts/fixtures/managed/`. It checks static classification, members,
|
||||
reconstruction, native-boundary relationships and application graphs without
|
||||
executing fixture PE files. Operator-local manifests and actual ILSpy oracles
|
||||
remain optional, separately reported checks; the real Ghidra NativeAOT lane has
|
||||
its own toolchain prerequisites. See [the managed guide](managed-code-analysis.md)
|
||||
for those configurations. Generated completion-ledger checks use the same owning
|
||||
entrypoint and include its verifier/fixture files in their cache inputs.
|
||||
|
||||
## End-to-end, integration and golden evidence
|
||||
|
||||
Full E2E tests invoke the production command dispatcher and real providers,
|
||||
@@ -65,6 +81,27 @@ an XML graph golden. `verify:asset-catalog` compiles source-owned colors with
|
||||
`actool`, invokes real `assetutil`, then checks CLI/MCP results, exact catalog
|
||||
digest, every raw metadata field, pagination and malformed input rejection.
|
||||
Neither artifact workflow requires Hopper or launches it. Both run in macOS CI.
|
||||
`verify:macos-bundle` needs only macOS with Command Line Tools. It compiles a
|
||||
source-owned app with `clang`: a versioned framework, XPC services, an app
|
||||
extension, a login item, a privileged helper, launchd plists, and a helper tool,
|
||||
signed ad hoc. It packs the app as a directory, a `ditto` ZIP, and an APFS DMG,
|
||||
then checks that `inspect-artifact` plus `project-apple-application-graph`
|
||||
report the same bundle anatomy for all three through the CLI, with stdio MCP
|
||||
parity. It also checks that the DMG is detached afterwards. It runs in macOS CI.
|
||||
|
||||
Apple artifact verifiers live in `scripts/verify/apple/`, with the macOS bundle
|
||||
builder under `scripts/fixtures/apple/` and NIB byte fixtures beside the decoder
|
||||
in `src/artifacts/apple/`. The npm entrypoints are unchanged. Format-specific
|
||||
Swift/XIB/asset sources and goldens retain their locations; real Apple workflows
|
||||
resolve them from the verifier file URL and run in the macOS CI lane.
|
||||
|
||||
Portable native semantics and their tests live in `src/domain/native/`; shared
|
||||
analyst workflows and service-lane tests live in `src/application/native/`.
|
||||
Named native contracts live in `src/contracts/native/`. Provider protocol and
|
||||
host UI tests retain their adapter/boundary lanes, and real verifier command
|
||||
names remain unchanged.
|
||||
The existing Apple CI job also runs the host Swift-demangling CLI/MCP regression
|
||||
suites, including option-like symbols, carriage returns and multiline rejection.
|
||||
|
||||
MCP SDK transport tests with recording providers remain integration tests.
|
||||
They are useful for schema drift and failure projection but do not prove that
|
||||
@@ -85,8 +122,34 @@ regressions until a real fixture establishes equivalent coverage.
|
||||
decoding. Its SDK client explicitly permits the larger inline JSON response;
|
||||
this lane does not establish large image-comparison request transport coverage.
|
||||
|
||||
The [test suite audit](test-suite-audit.md) records the pruning decisions,
|
||||
replacement evidence and remaining priorities.
|
||||
`verify:browser:network` is a focused real-browser lane for transaction identity,
|
||||
selected request/response bytes, binary and compressed responses, duplicate
|
||||
headers, credential and declared-secret redaction, redirects, streaming cutoff,
|
||||
CLI/MCP parity, and owned-profile cleanup. Set `REA_BROWSER_EXECUTABLE` to an
|
||||
installed Chrome-family browser. An optional script argument selects an already
|
||||
installed package's `scripts/rea.mjs` entry point for packaged-artifact checks.
|
||||
The complete `verify:browser` lane includes these same checks.
|
||||
|
||||
After building, `verify:browser:dom` checks empty and HTML-whitespace form
|
||||
destinations against a native Chrome DOM-property oracle through CLI and stdio
|
||||
MCP. It requires `REA_BROWSER_EXECUTABLE` and accepts an optional installed REA
|
||||
entrypoint. The full browser lane includes the same public-adapter assertions
|
||||
before other fixtures navigate the selected page.
|
||||
|
||||
`verify:browser:scripts` checks active script capture → exact-byte export →
|
||||
existing static JavaScript analysis through CLI and stdio MCP, including
|
||||
manifest readback, competing query variants, and resolved relative imports.
|
||||
It uses an installed browser and accepts an optional installed REA entrypoint.
|
||||
The complete `verify:browser` lane also exercises passive script export through
|
||||
both public adapters. See [website script export](website-script-export.md).
|
||||
|
||||
`verify:browser:modules` compares CLI and stdio MCP traces against an independent
|
||||
real Chromium module-loading fixture: import-map scopes, package prefixes,
|
||||
null/backtracking rejection, query/fragment identities, repeated module instances,
|
||||
lazy and computed unknowns, exact source/map identities and no implicit refetch.
|
||||
Set `REA_BROWSER_EXECUTABLE`; the optional script argument selects an installed
|
||||
package entrypoint. The complete `verify:browser` lane and existing conditional
|
||||
Chrome CI include this verifier. See [module relationships](website-module-trace.md).
|
||||
|
||||
## Real-toolchain verification lanes
|
||||
|
||||
@@ -97,6 +160,11 @@ commands. Check prerequisites before starting expensive work and name the
|
||||
missing command, target, and lane in any failure message. A lane must not imply
|
||||
that a host or target is covered when it was skipped.
|
||||
|
||||
Provider admission accepts Ghidra 12.1.x and the JDK range declared by that
|
||||
installation (`application.java.min` through `application.java.max`). Current
|
||||
12.1 releases require JDK 21 or newer and set no maximum. The lanes below still
|
||||
prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
|
||||
|
||||
| Ghidra lane | Supported runner/target | Additional local tools |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| `npm run verify:ghidra` | Linux x64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
|
||||
@@ -142,12 +210,21 @@ requires unresolved table bounds or case mappings to remain visible as
|
||||
residual unknowns.
|
||||
|
||||
`npm run verify:inspector` requires the supported Node.js runtime and installed
|
||||
REA dependencies. CI runs it on Linux and Windows. It starts owned loopback
|
||||
REA dependencies. CI runs it on Linux and macOS x64/arm64 and Windows x64. It starts owned loopback
|
||||
Node Inspector fixtures and verifies discovery and passive observation through
|
||||
the CLI and stdio MCP, including special filenames, unresolved discovery
|
||||
locations, and independently resolved loaded scripts. Double-quote filenames
|
||||
are tested on POSIX only because Windows does not support them.
|
||||
|
||||
Inspector adapters live in `src/inspector/`, with loopback fixtures under
|
||||
`tests/fixtures/inspector/` and forked producer boundaries under
|
||||
`tests/boundary/inspector/`. The real lane is owned by
|
||||
`scripts/verify/inspector/runtime-observation.mjs` and
|
||||
`tests/conformance/inspector/`. Browser and Inspector deliberately share the
|
||||
existing browser CDP transport/value and file-location helpers. Node fixture
|
||||
success proves this Inspector workflow on the tested host; it does not prove
|
||||
Electron GUI behavior or another engine.
|
||||
|
||||
## Android APK analysis
|
||||
|
||||
`npm run verify:android` requires an existing Java 17+ and an explicit
|
||||
@@ -158,6 +235,16 @@ kept under ignored `_reference/`. No Gradle build, Android SDK, emulator or
|
||||
application execution is required. The lane compares real CLI/MCP package,
|
||||
class search, class inventory, method decompilation and incoming references.
|
||||
See [Android analysis](android-analysis.md) for boundaries and resource budgets.
|
||||
Authenticated IPA and macOS application inventory projection is documented in
|
||||
[Apple application analysis](apple-application-analysis.md).
|
||||
|
||||
The lane and owned-process cancellation helper live in `scripts/verify/android/`.
|
||||
The explicit downloader and fixed manifest live in `scripts/fixtures/android/`;
|
||||
their default remains repository-root `_reference/apk-integration/`. Producer
|
||||
fixtures live in `tests/fixtures/android/`, boundary cases in
|
||||
`tests/boundary/android/`, and inventory projection composition cases in
|
||||
`tests/composition/android/`. Real engine success and synthetic protocol success
|
||||
are separate proof levels.
|
||||
|
||||
Synthetic producer regressions run independently:
|
||||
|
||||
@@ -256,6 +343,35 @@ Keep the provider-specific acceptance path independent from optional
|
||||
cross-compilers. Cross-format failures belong to the cross-format lane and must
|
||||
not make native host acceptance unavailable.
|
||||
|
||||
## Native platform baseline in CI
|
||||
|
||||
CI exercises the pinned Node.js runtime on native hosted runners:
|
||||
|
||||
| Host | Runner | Baseline checks |
|
||||
| --------------------- | ------------------ | ---------------------------------------------------------------------------------------- |
|
||||
| Linux x64 | `ubuntu-latest` | Installed package and real Node Inspector CLI/MCP |
|
||||
| Linux arm64 (aarch64) | `ubuntu-24.04-arm` | Installed package and real Node Inspector CLI/MCP |
|
||||
| macOS 15 arm64 | `macos-15` | Installed package and real Node Inspector CLI/MCP |
|
||||
| macOS 15 x64 | `macos-15-intel` | Installed package and real Node Inspector CLI/MCP |
|
||||
| Windows x64 | `windows-latest` | Curated capabilities, native controls, installed package and real Node Inspector CLI/MCP |
|
||||
|
||||
Package and Inspector matrices assert the actual Node platform/architecture
|
||||
before verification and record those values with the Node version. Each matrix
|
||||
runs at most two jobs concurrently with explicit timeouts and Node heap/thread
|
||||
limits. Package checks cover installation, CLI/MCP discovery, target-free
|
||||
analysis, configuration backups/recovery, Evidence and owned lifecycle; Inspector
|
||||
checks execute source-owned loopback targets and special filename cases.
|
||||
|
||||
macOS uses one OS version with one native baseline job per architecture. Each job
|
||||
runs package and Inspector checks after a single dependency installation. The
|
||||
separate Inspector matrix covers Linux and Windows; Apple artifact checks retain
|
||||
their own macOS 15 arm64 job for Xcode-dependent workflows.
|
||||
|
||||
These native baseline checks complement the Linux source-test shards and the
|
||||
separate Apple-artifact and real-provider lanes. Actual Hopper, Ghidra, IDA,
|
||||
browser and managed-tool claims require their corresponding verification lanes.
|
||||
Runner labels follow the [GitHub hosted-runner reference](https://docs.github.com/en/actions/reference/runners/github-hosted-runners).
|
||||
|
||||
## Developer commands
|
||||
|
||||
Use source feedback while editing, explicit boundary checks for the changed
|
||||
@@ -361,26 +477,93 @@ that explicit lane, not the routine iteration requirement.
|
||||
|
||||
## Apple native metadata and UI
|
||||
|
||||
`npm run verify:apple-dispatch` compiles Objective-C class/protocol and Swift
|
||||
conformance/vtable fixtures, inspects their bytes and repeats after stripping
|
||||
local symbols. It requires macOS and the host Xcode toolchain; targets are not
|
||||
executed. `npm run verify:native-ui` launches exactly one source-owned fixture
|
||||
window and requires successful selected-window capture and selected actions.
|
||||
An OS permission denial fails the positive lane. `npm run verify:native-ui:permissions`
|
||||
allows a host-permission-boundary-only result and explicitly reports
|
||||
`positive_e2e: false`; it must not be reported as capture/action proof.
|
||||
Both commands reject a changed executable digest and clean up the fixture
|
||||
process and helper. These lanes require an interactive macOS desktop. See [native investigation](native-investigation.md)
|
||||
for the exact ABI, authority, graph and observation boundaries.
|
||||
`npm run verify:apple-dispatch` compiles the Objective-C fixture (classes,
|
||||
protocols, a property and an `NSString` category) and the Swift
|
||||
conformance/vtable fixture.
|
||||
|
||||
- Each fixture is linked with legacy `LC_DYLD_INFO` binds and with chained
|
||||
fixups; on Apple silicon the ObjC fixture is also built as arm64e, which uses
|
||||
authenticated pointers.
|
||||
- The lane inspects the bytes and repeats after stripping local symbols.
|
||||
- It requires the bound `NSObject` superclass, the external category, and the
|
||||
matching `pointer_fixups` coverage. It requires macOS and the host Xcode toolchain; targets are not
|
||||
executed. `npm run verify:native-ui` launches exactly one source-owned fixture
|
||||
window and requires successful selected-window capture and selected actions.
|
||||
An OS permission denial fails the positive lane. `npm run verify:native-ui:permissions`
|
||||
allows a host-permission-boundary-only result and explicitly reports
|
||||
`positive_e2e: false`; it must not be reported as capture/action proof.
|
||||
Both commands reject a changed executable digest and clean up the fixture
|
||||
process and helper. These lanes require an interactive macOS desktop. See [native investigation](native-investigation.md)
|
||||
for the exact ABI, authority, graph and observation boundaries.
|
||||
|
||||
### Firmware adapters
|
||||
|
||||
`npm run fixtures:firmware` uses existing Python 3 and a host C compiler to make
|
||||
an ignored gzip/USTAR firmware fixture and independent offset/hash oracle.
|
||||
`npm run verify:firmware` requires caller-supplied Binwalk 3.1.0, Unblob 26.6.4
|
||||
and util-linux prlimit on Linux. It verifies CLI/MCP parity, selected ranges,
|
||||
and util-linux prlimit on Linux. The provider also accepts other 3.1.x and
|
||||
26.6.x builds and reports them as unverified; this lane proves the audited
|
||||
releases. It verifies CLI/MCP parity, selected ranges,
|
||||
unknown chunks, depth limits and extracted child digests. The optional
|
||||
`REA_FIRMWARE_VERIFY_EXT4=1` lane requires existing mke2fs/debugfs; the separate
|
||||
`REA_FIRMWARE_VERIFY_GHIDRA=1` lane checks a selected host ELF through real Ghidra.
|
||||
Neither optional toolchain is a base-lane prerequisite. See
|
||||
[firmware analysis](firmware-analysis.md) for limits and unverified formats.
|
||||
|
||||
The real entrypoint is `scripts/verify/firmware/analysis.mjs`; the fixture runner
|
||||
and unchanged Python producer belong together in `scripts/fixtures/firmware/`.
|
||||
Producer fixtures and boundaries remain in `tests/fixtures/firmware/` and
|
||||
`tests/boundary/firmware/`. Source-fixture generation proves the runner and
|
||||
independent oracle; actual Binwalk/Unblob CLI/MCP proof requires selected tools.
|
||||
|
||||
### JavaScript source recovery
|
||||
|
||||
Build once with `npm run build:cached`, then run `npm run verify:javascript:recovery`.
|
||||
This focused lane requires Linux x64, util-linux `prlimit`,
|
||||
`REA_WAKARU_COMMAND` pointing to the official Wakaru 1.13.0 Linux x64 binary,
|
||||
and `REA_JAVASCRIPT_FIXTURE_TOOLS` pointing to an isolated npm prefix containing
|
||||
esbuild 0.25.10 and webpack 5.101.3. No global installation is required.
|
||||
The lane compiles source-owned fixtures, exercises CLI and stdio MCP, verifies
|
||||
published bytes and provenance, feeds recovered modules into existing analysis,
|
||||
and compares a finite set of known fixture results. It does not establish
|
||||
arbitrary recovered-application equivalence. CI installs these prerequisites only
|
||||
in `.github/workflows/real-javascript-recovery.yml`; the existing `real-browser`
|
||||
lane uses real Chrome for browser capture and website workflows.
|
||||
|
||||
### Captured website source-map lane
|
||||
|
||||
`npm run verify:browser:source-maps` checks actual Chromium capture/export and
|
||||
source-map point tracing through CLI and stdio MCP. It requires absolute
|
||||
`REA_BROWSER_EXECUTABLE` and `REA_WEB_SOURCE_MAP_COMPILER` pointing to esbuild
|
||||
0.25.10's `lib/main.js` in a caller-owned isolated installation. Preflight reports
|
||||
missing prerequisites for this lane. The compiler is used only to generate the
|
||||
source-owned fixture. No Hopper, Ghidra or application dependency installation
|
||||
is required.
|
||||
|
||||
```bash
|
||||
REA_BROWSER_EXECUTABLE=/absolute/path/to/chromium \
|
||||
REA_WEB_SOURCE_MAP_COMPILER=/absolute/path/to/fixture-tools/node_modules/esbuild/lib/main.js \
|
||||
npm run verify:browser:source-maps
|
||||
```
|
||||
|
||||
`scripts/verify-browser-source-maps.mjs /absolute/path/to/installed/rea.mjs`
|
||||
checks an installed package after building the verifier dependencies. The separate
|
||||
conditional `real-web-source-map` CI job supplies Chrome and an isolated pinned
|
||||
fixture compiler; static/unit checks do not acquire a browser. See
|
||||
[the source location guide](web-source-location.md) for the verified decoder profile.
|
||||
|
||||
### Website runtime attribution lane
|
||||
|
||||
`npm run verify:browser:runtime` uses caller-supplied
|
||||
`REA_BROWSER_EXECUTABLE` and an owned synthetic site/profile. It exercises public
|
||||
CLI and stdio MCP for precise execution and native listener source locations,
|
||||
including actual armed progress, Unicode/CRLF digests, repeated source URLs with
|
||||
distinct script IDs, zero branches and function-only unknowns on repeated
|
||||
coverage, request initiators and an externally owned page that remains open.
|
||||
|
||||
An optional entrypoint argument to `scripts/verify-browser-runtime.mjs` runs the
|
||||
same checks through an isolated installed package. The conditional
|
||||
`real-web-runtime` CI job runs only for relevant changes and needs no fixture
|
||||
compiler. Ordinary unit/static gates acquire no browser. See
|
||||
[website runtime attribution](web-runtime.md) for effects, resource bounds and
|
||||
coverage limits.
|
||||
@@ -56,7 +56,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
|
||||
46,src/application/Uninstall.ts:removeManagedPath symlink,Managed cache/state/skill path is a symbolic link,Refused to follow symlink {path}.,Exposes local path and does not explain manual safety check,"REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.",implemented,focused assertion passed
|
||||
47,src/application/Uninstall.ts:removeManagedPath failure,Managed item removal fails,{path} could not be removed.,Exposes local path and gives no recovery,"This item could not be removed. Check file permissions, then rerun uninstall.",implemented,verified by injected managed-path removal failure test
|
||||
48,src/application/DoctorDiagnostics.ts:node,Runtime version is outside supported ranges,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",Low risk; exact dependency recovery,Keep current copy,unchanged,verified by doctor unit test
|
||||
49,src/application/Doctor.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests
|
||||
49,src/application/Doctor.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS.",Low risk; clearly states supported choices,Keep current copy,unchanged,verified by doctor unit tests
|
||||
50,src/application/Doctor.ts:analysis engine,Optional deep-analysis executable cannot be found,"Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.",Low risk in setup-specific diagnostic; exact recovery paths,Keep current copy,unchanged,verified by doctor unit tests
|
||||
51,src/application/Doctor.ts:target,Optional target path fails shared validation,Supply a readable local app or program path.,Low risk; tells caller valid target class,Keep current copy,unchanged,verified by doctor unit test
|
||||
52,src/application/Setup.ts:approval,Setup plan lacks approval,"Review the setup plan, then rerun interactively or with --yes.",Low risk; exact authorization recovery,Keep current copy,unchanged,verified by setup unit tests
|
||||
@@ -67,7 +67,7 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
|
||||
57,src/application/Setup.ts:unsupported platform,Platform is neither macOS nor Linux,REA supports Hopper on macOS and selected 64-bit Linux distributions.,Low risk; states support boundary,Keep current copy,unchanged,verified by setup unit test
|
||||
58,src/application/SetupHost.ts:unsupported Node,Runtime version cannot run setup,"Install Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ and rerun setup.",Low risk; exact dependency and retry,Keep current copy,unchanged,verified by setup host tests
|
||||
59,src/application/Setup.ts:unsupported macOS,macOS version is missing or older than 12,Upgrade to macOS 12 or newer.,Low risk; exact support floor,Keep current copy,unchanged,verified by setup host tests
|
||||
60,src/application/Setup.ts:unsupported Linux,Linux distribution is outside supported set,"REA supports Hopper on Ubuntu 24.04+, Fedora 41+, and 64-bit Arch Linux.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
|
||||
60,src/application/Setup.ts:unsupported Linux,Linux distribution is outside supported set,"REA supports Hopper on Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, and CachyOS.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
|
||||
61,src/application/ProcessCli.ts:readJson read,Process scenario or evidence file cannot be read,Raw filesystem exception through CLI framework,Can expose local paths/codes and no recovery,"Process input file could not be read. Check that the path exists and is readable.",implemented,verified by focused CLI test
|
||||
62,src/application/ProcessCli.ts:readJson size,Process scenario or evidence file exceeds 64 MiB,Process JSON input exceeds byte limit,Does not state limit or recovery,"Process input file is too large. Reduce it below 64 MiB, then try again.",implemented,verified by focused sparse-file CLI test
|
||||
63,src/application/ProcessCli.ts:readJson parse,Process scenario or evidence file is malformed JSON,Raw JSON parser exception through CLI framework,Can expose parser implementation and lacks recovery,"Process input file is not valid JSON. Repair the file, then try again.",implemented,verified by focused CLI test
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"manifest_id": "ecm_fbd47cbd3815d7e252ebcb60c4d086dcc1fc6b63f020e83513e28bf4d94c732c",
|
||||
"manifest_id": "ecm_880356f1ceceb9e29b43e3e89f7a326aa83132e1e1fae0573245848ba714f404",
|
||||
"verifier": {
|
||||
"id": "rea-managed-conformance",
|
||||
"version": "1"
|
||||
@@ -13,7 +13,7 @@
|
||||
"skill_digests": [
|
||||
{
|
||||
"skill_id": "reverse-engineer-anything",
|
||||
"sha256": "b317babebb82157b46b4dff698a170045490bcb21aaf9af582e20908ba2bb998"
|
||||
"sha256": "97dc167b6e0d41ac69953501c96a918319fa6e4b3be0affe2fc2119294e7101d"
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Website runtime source attribution
|
||||
|
||||
REA integrates native Chrome DevTools Protocol inspection and precise V8 coverage through two composable tools. Supply an existing loopback CDP endpoint and a page target from `list_browser_targets`. With omitted origins, REA selects the page's current HTTP(S) origin. The browser and page remain externally owned.
|
||||
|
||||
## Observe execution
|
||||
|
||||
```bash
|
||||
rea observe-web-execution http://127.0.0.1:9222 TARGET_ID \
|
||||
--observation-ms 10000 --json
|
||||
```
|
||||
|
||||
MCP: `observe_web_execution` with `cdp_endpoint`, `target_id`, optional `allowed_origins` and optional `observation_ms` (default 10000). The operation itself selects instrumentation; no extra approval flag is needed.
|
||||
|
||||
Starting precise coverage resets counters and prevents optimized execution until REA stops it. This changes timing/JIT behavior. REA requests accurate call counts and detailed ranges, takes one resetting sample, stops coverage, disables enabled domains and detaches its own connection. It does not navigate, click, evaluate selected application code or close the page.
|
||||
|
||||
Wait for the actual armed progress event before performing an action. MCP emits negotiated progress notifications; CLI emits `rea_progress` JSON on stderr while the Evidence result stays on stdout. Use ordinary commands, scripts or user actions to trigger behavior. CLI SIGINT/SIGTERM and MCP cancellation await instrumentation cleanup; unconfirmed cleanup is an error with the target, failed commands and previous failure.
|
||||
|
||||
Results retain:
|
||||
|
||||
- The selected target, frame and document loader identity; local armed/end timestamps and separate backend monotonic coverage timestamps.
|
||||
- Every admitted producer function/block range, exclusive end offset and execution count in UTF-16 code units, with verified bounds when exact text is available. Nested/overlapping counts are preserved and must not be summed.
|
||||
- Main-document script inventory, exact session script IDs, resource start coordinates, sourceURL/source-map declarations, producer hash and independently computed UTF-8 text digest.
|
||||
- Armed-window requests from the selected main frame and selected origins, complete reported initiator objects (including stack hierarchy/descriptions), derived callsites, exact script-ID associations and unresolved async parent IDs. Source association does not establish UI causality.
|
||||
|
||||
The coverage counter interval starts at backend acceptance before the armed receipt and ends at the resetting sample after the local timer. Counts may include execution during these command intervals; request events are restricted to the locally armed window. The result retains both backend timestamps and the distinct local clock.
|
||||
|
||||
Source metadata remains collected through receipt of the resetting sample, including scripts parsed during the final command. REA then freezes this inventory for source joins. Fatal protocol parsing failures terminate the observer even when no command is outstanding; listener inspection checks producer failures after its final document assertion.
|
||||
|
||||
A document replacement ends the window; final coverage then remains unavailable. Missing coverage is unknown: an already loaded script may be absent from a sample. A detailed request also does not guarantee block granularity for already compiled functions. If `is_block_coverage` is false, the function-only ranges are observed and branch execution remains unknown. Native Chrome verification reproduced block coverage in the first window and function-only coverage in a subsequent window, including without REA.
|
||||
|
||||
Same-document navigation such as `history.pushState()` remains valid when the frame and known loader identity stay stable and the current origin is allowed. Unknown loader identity retains a conservative URL check; newly reported or replaced loaders are rejected. Malformed producer events end an armed window immediately and trigger cleanup, independently of the selected duration.
|
||||
|
||||
Target detachment ends the window even when the browser's root connection stays open. When the closed session cannot acknowledge cleanup, the operation returns a failure with the selected target, failed commands and original producer reason; it does not claim successful cleanup.
|
||||
|
||||
Child frames, workers, service workers, WebAssembly, chronological execution order and framework-specific causal interpretation are outside this operation. Script ownership comes from frame/context metadata, never URL equality. Repeated URLs and synthetic names therefore retain separate identities.
|
||||
|
||||
Observed script metadata remains available when ownership is unknown; source text and source association remain excluded or unknown. Request/initiator resource URLs omit transport userinfo. Stack and script URLs can also be inert `sourceURL` names: REA preserves declared names and unknown representations, and only removes script URL userinfo when stable producer metadata explicitly reports `hasSourceURL: false`. This policy also applies when metadata arrives after a request. Unknown producer extension fields and source-map declarations are preserved.
|
||||
|
||||
## Inspect a node's listeners
|
||||
|
||||
```bash
|
||||
rea inspect-web-event-listeners http://127.0.0.1:9222 TARGET_ID '#submit' --json
|
||||
```
|
||||
|
||||
MCP: `inspect_web_event_listeners` with the same scope and an explicit CSS `selector`. Native DOM selection inspects the first matching node in the main document. A missing match is an explicit empty result.
|
||||
|
||||
The result contains directly registered listener types/flags, callback coordinates, source identity/text/digest and execution unknowns. REA uses native DOM/DOMDebugger operations and releases its own remote object group. It never invokes a listener or dispatches an event. Delegated ancestor listeners, descendants, closed shadow roots and framework registries are not enumerated.
|
||||
|
||||
## Bounds and verification
|
||||
|
||||
Each protocol message has a 64 MiB budget; script/context metadata and request metadata each have an 8 MiB retained budget. Complete retained source text has a 32 MiB budget. Oversized or malformed evidence returns no partial successful result. Each command and the complete inspection/source-join phase have a 20-second deadline; cleanup has a separate 5-second command deadline plus transport closure. The selected observation duration must fit the host timer's positive 32-bit millisecond range. REA does not control the externally owned browser's CPU or memory.
|
||||
|
||||
`npm run verify:browser:runtime` uses caller-supplied `REA_BROWSER_EXECUTABLE` and the native `ps` command on Linux/macOS against an owned synthetic fixture, through real public CLI and stdio MCP. It checks exact Unicode/CRLF text/digests, same-URL identities, same-document navigation, inert declared names, executed and zero-count branches, function-only unknowns on repeated observation, listener selection, initiator association, actual arming and preservation of the external page. A separate fixture page closes during an armed MCP window to verify prompt termination and inline cleanup diagnostics. The optional positional entrypoint to `scripts/verify-browser-runtime.mjs` permits the same checks against an isolated installed tarball.
|
||||
|
||||
Real workflow coverage is Linux x64 / Chrome 153 / Node 24. Other browser engines and host workflows remain unverified. The conditional `real-web-runtime` CI lane acquires Chrome only for relevant changes; ordinary unit/static checks need no browser.
|
||||
|
||||
Primary protocol references: [Profiler and Debugger](https://raw.githubusercontent.com/ChromeDevTools/devtools-protocol/master/pdl/js_protocol.pdl), [DOMDebugger](https://raw.githubusercontent.com/ChromeDevTools/devtools-protocol/master/pdl/domains/DOMDebugger.pdl).
|
||||
|
||||
Source ownership requires both the selected main frame and producer evidence for its default execution world. Extension/automation isolated worlds, worker worlds and unknown worlds cannot establish website source joins. Contradictory world metadata stays unowned. Referenced source records retain the reported execution context and script auxiliary context metadata; source text is read only for proven main-document default worlds.
|
||||
|
||||
Known script/context identities are monitored through asynchronous source completion after the sample cutoff. A changed identity excludes its retained text, frame attribution, coverage and request/source joins. Chromium's specific native CSS syntax rejection returns an input error for `selector`, including the selected target; unrelated node, command and transport failures retain their original category.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Captured website source locations
|
||||
|
||||
`trace_web_source_location` maps one selected position in a script retained by
|
||||
`export_web_scripts` through one explicitly paired local source map. The shared
|
||||
CLI/MCP workflow verifies actual manifest, script and map byte identities and
|
||||
returns the original position and embedded source inline. It acquires no browser,
|
||||
fetches no URL and executes no application code.
|
||||
|
||||
```bash
|
||||
rea trace-web-source-location \
|
||||
/analysis/scripts/manifest.json 0 \
|
||||
/analysis/app.js.map 'https://example.test/maps/app.js.map?v=7#context' \
|
||||
1 25 --json
|
||||
```
|
||||
|
||||
The six positional arguments select the manifest, zero-based script index,
|
||||
absolute map path, absolute map URL context, one-based generated line and
|
||||
zero-based UTF-16 column. The MCP arguments are:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_path": "/analysis/scripts/manifest.json",
|
||||
"script_index": 0,
|
||||
"source_map": {
|
||||
"path": "/analysis/app.js.map",
|
||||
"url": "https://example.test/maps/app.js.map?v=7#context"
|
||||
},
|
||||
"generated_position": { "line": 1, "column": 25 }
|
||||
}
|
||||
```
|
||||
|
||||
The URL supplies the context for resolving `sources` and `sourceRoot`; it is not
|
||||
an instruction to fetch. Absolute virtual or file contexts can be selected as
|
||||
well. The map/script association is caller-selected: a declaration or local map
|
||||
does not prove deployment authenticity or execution. Use
|
||||
[import tracing](website-module-trace.md) to inspect outgoing native module edges.
|
||||
|
||||
## Meaning of the evidence
|
||||
|
||||
Lookup follows the greatest generated position less than or equal to the
|
||||
selected point, across lines, and returns **every** segment at that position.
|
||||
It includes generated-only mappings. This follows
|
||||
[ECMA-426 GetOriginalPositions](https://tc39.es/ecma426/#sec-getoriginalpositions),
|
||||
with one-based lines in REA's public contract. A point result does not establish
|
||||
coverage of a source range, execution or UI causality.
|
||||
|
||||
Mapped entries preserve the raw source name/root, section path, section-local
|
||||
source index and flattened index. Duplicate URLs remain distinct, including
|
||||
entries with different embedded contents. Missing source names retain a null
|
||||
resolved URL. Original coordinates outside embedded text retain the reported
|
||||
coordinates and a null offset.
|
||||
|
||||
Embedded content hashes identify the UTF-8 encoding of the decoded
|
||||
`sourcesContent` string, including its line endings. They do not identify an
|
||||
independently captured original file. Raw map text is retained in Evidence's
|
||||
`raw_result`, including unknown extensions; these extensions are not interpreted.
|
||||
Capture context and source Evidence links accompany independently verified
|
||||
selected file identities.
|
||||
|
||||
## Adapter and resource boundaries
|
||||
|
||||
The adapter uses the locked upstream
|
||||
[`@jridgewell/trace-mapping` 0.3.31](https://github.com/jridgewell/trace-mapping)
|
||||
without modifying its code. It validates map/VLQ structure before allocation,
|
||||
normalizes nested indexed offsets before upstream flattening, checks section
|
||||
boundaries and indexes, and retains raw declaration identity. It supports regular
|
||||
maps and inline indexed sections. External section URLs return an actionable
|
||||
unsupported-profile error. This is a defined decoder profile, not a claim to
|
||||
implement every current source-map extension.
|
||||
|
||||
One owned Node subprocess decodes the map. Its private request directory is
|
||||
removed before success is returned. Only this child's inherited `NODE_OPTIONS`
|
||||
is removed; explicit V8 flags set a 192 MiB old-generation and 8 MiB semi-space
|
||||
budget, one V8 worker and a 20-second deadline. Reported actual V8 heap limits
|
||||
are checked. These are heap limits, not an aggregate process RSS limit. Cleanup
|
||||
may extend the deadline and an unconfirmed cleanup overrides success with
|
||||
`cleanup_incomplete` and the owning operation/resources.
|
||||
|
||||
Complete evidence is bounded by a 4 MiB map, 32 MiB codec reply, 262144 decoded
|
||||
rows/segments and 64 nested section levels. Exceeding a budget returns an explicit
|
||||
failure, without partial results. The existing 32 MiB manifest and 16 MiB selected
|
||||
script boundaries also apply. No global toolchain or browser configuration changes
|
||||
are made.
|
||||
|
||||
## Verification
|
||||
|
||||
Focused tests cover duplicate mappings, indexed offsets/overlap/local indexes,
|
||||
32-bit VLQ overflow, nullable content, UTF-16/CRLF positions, immutable artifact
|
||||
identity, cancellation, late output closure, cleanup failures, SDK schemas and
|
||||
public CLI behavior. The actual codec process is exercised on the test host.
|
||||
|
||||
The optional `verify:browser:source-maps` lane compiles a source-owned TypeScript
|
||||
fixture with isolated esbuild 0.25.10, serves it to real Chromium, captures and
|
||||
exports the actual response, then checks the original compiler point and exact
|
||||
retained content through CLI and stdio MCP. It can target an installed package.
|
||||
Only this lane requires Chromium and the fixture compiler. Linux x64 is the real
|
||||
workflow covered by the dedicated CI job; other host workflows remain unverified.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Captured website module relationships
|
||||
|
||||
`trace_web_module_imports` / `rea trace-web-module-imports` traces one script's
|
||||
outgoing native ES imports and re-exports from an
|
||||
[exported capture](website-script-export.md). It verifies the selected source's
|
||||
SHA-256 and byte count, preserves browser URL identities, and returns every
|
||||
matching capture candidate inline. It does not refetch website assets.
|
||||
|
||||
## Select the source and context
|
||||
|
||||
Provide a caller-supplied Chromium executable supporting `import.meta.resolve`.
|
||||
REA does not install or upgrade a browser. Real verification covers Linux x64;
|
||||
other hosts have not received this feature's real workflow verification.
|
||||
|
||||
```bash
|
||||
REA_BROWSER_EXECUTABLE=/absolute/path/to/chrome \
|
||||
rea trace-web-module-imports /analysis/export/manifest.json 0 \
|
||||
--importer-url 'https://example.test/app/main.js?build=2#entry' \
|
||||
--import-map-path /analysis/import-map.json \
|
||||
--import-map-base-url https://example.test/maps/ \
|
||||
--json
|
||||
```
|
||||
|
||||
The MCP input expresses the same choices:
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_path": "/analysis/export/manifest.json",
|
||||
"script_index": 0,
|
||||
"importer_url": "https://example.test/app/main.js?build=2#entry",
|
||||
"import_map": {
|
||||
"path": "/analysis/import-map.json",
|
||||
"base_url": "https://example.test/maps/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`script_index` is zero-based in `manifest.scripts`. If `importer_url` is omitted,
|
||||
the trace selects the reported source URL. An inline script's document base
|
||||
cannot be established from that URL alone; choose the context explicitly when
|
||||
known. `import_map` is optional and reads local JSON only. Its exact bytes and
|
||||
base URL are retained; selecting it does not prove the map was installed in the
|
||||
captured page. HTTP(S) importer contexts without URL userinfo are supported initially.
|
||||
|
||||
## Interpret the evidence
|
||||
|
||||
- Static imports, re-exports and literal dynamic imports receive the native
|
||||
engine's resolved URL or exact rejection message. Empty strings are retained.
|
||||
- Computed dynamic imports retain their expression and source positions with
|
||||
`computed-specifier` unknown resolution. Erased type-only imports/exports and
|
||||
inferred bundler IDs are excluded from native import claims.
|
||||
- Locations use UTF-16 offsets and columns, with one-based lines. Parse recovery
|
||||
diagnostics and unparseable input are explicit.
|
||||
- Query variants and fragments remain distinct. An exact reported URL is an
|
||||
`exact-reported-url` candidate. A response URL stripped of its fragment is a
|
||||
weaker `response-url-without-fragment` content candidate. Multiple captured
|
||||
versions are all returned; the trace selects none as canonical.
|
||||
- Candidate metadata remains reported capture evidence; only the selected
|
||||
source's bytes are reverified. Missing/lazy targets can resolve successfully
|
||||
with no captured candidate. Every relationship reports execution as unknown.
|
||||
- Source-map declarations remain declarations; this operation does not read or
|
||||
invent map bytes, original-source mappings, worker ownership or UI causality.
|
||||
|
||||
The selected source is loaded from `files/` under the manifest's current
|
||||
directory. Relocation retains the manifest's originally reported paths and the
|
||||
actual current source path separately. Unsafe paths, symlink source directories,
|
||||
nonregular files, wrong digests/sizes and malformed UTF-8 fail explicitly.
|
||||
|
||||
## Engine and lifecycle
|
||||
|
||||
An owned temporary browser context runs REA's trusted resolver stub. The selected
|
||||
application is parsed as data and is never executed by this trace. Page requests
|
||||
are fulfilled with REA's synthetic document/stub or blocked; no website asset is
|
||||
downloaded. This does not establish an OS sandbox or browser-wide network
|
||||
containment. The engine version and native report are retained in Evidence.
|
||||
|
||||
Sources without literal imports do not acquire a browser. The manifest, source
|
||||
and import map have 32 MiB, 16 MiB and 4 MiB input limits. Native resolution has
|
||||
one 20-second deadline; cleanup may extend that deadline. One renderer is
|
||||
requested, and there is no aggregate host memory-limit claim. Cancellation
|
||||
closes only the owned browser; cleanup failure cannot return a successful trace.
|
||||
`REA_BROWSER_NO_SANDBOX=true` passes Chromium's corresponding launch option when
|
||||
the host requires it; it does not enable an REA security boundary.
|
||||
|
||||
See [testing](testing.md) for the native loading oracle and CLI/MCP real lane.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Captured website scripts and static JavaScript analysis
|
||||
|
||||
`export_web_scripts` bridges retained browser sources into the existing local
|
||||
JavaScript analyzer. Both CLI and MCP use the same workflow. It reads a saved
|
||||
`inspect_web_page` or `capture_browser_scenario` capture and writes scripts plus a
|
||||
verified manifest into one caller-selected, absent directory.
|
||||
|
||||
## Capture and export
|
||||
|
||||
For passive inspection, select `include_script_sources: true` (CLI:
|
||||
`--include-script-sources`). For scenarios, select
|
||||
`capture.network.response_body: true`. Save either the complete Evidence record
|
||||
or its normalized result as UTF-8 JSON. The compact MCP wrapper's `evidence`
|
||||
field contains the complete Evidence record.
|
||||
|
||||
```sh
|
||||
rea export-web-scripts /analysis/capture.json /analysis/exported-scripts --json
|
||||
rea analyze-javascript-application /analysis/exported-scripts/files --json
|
||||
```
|
||||
|
||||
MCP uses `export_web_scripts` with `capture_path` and `output_directory` absolute
|
||||
filesystem paths. Its inline result includes `analysis_input`, which can be
|
||||
passed directly to `analyze_javascript_application`. When no script bytes are
|
||||
exportable, `analysis_input` is null and the manifest retains the unavailable
|
||||
sources and their reasons. Capture again with the corresponding content option.
|
||||
|
||||
The destination must not exist. Use a new directory for each capture; an
|
||||
existing directory or symlink is rejected without overwriting its contents.
|
||||
REA creates private output, verifies every written file by digest and durable
|
||||
readback, and removes its own partial output on failure or cancellation.
|
||||
A failed rollback reports residual paths.
|
||||
|
||||
## Sources and mappings
|
||||
|
||||
Every source is returned inline with its URL and its script/frame identity or
|
||||
request transaction and event sequence references. The result also contains
|
||||
the input capture's byte digest, original coverage, source Evidence link when
|
||||
provided, and the persisted manifest's digest and byte count. A saved Evidence
|
||||
envelope must have a valid semantic identifier and matching operation.
|
||||
|
||||
Exported `relative_path` values are relative to `analysis_input.input_path`.
|
||||
The persisted `manifest.json` contains the same inline manifest fields, excluding
|
||||
its own descriptor. It does not duplicate script bytes.
|
||||
|
||||
- Passive sources are the Debugger-exposed text encoded as UTF-8, with the
|
||||
verified source digest and source-map declaration retained. Unavailable or
|
||||
explicitly non-JavaScript sources remain unavailable.
|
||||
- Scenario sources use observed requests classified as `script`, matched to
|
||||
completed response bytes by transaction and event identity. URLs never select
|
||||
the response. Binary and empty retained bytes are written exactly.
|
||||
- Ordinary query-free `.js`, `.mjs`, and `.cjs` URL paths are arranged under a
|
||||
directory for each origin, preserving their relative module layout when it
|
||||
is unambiguous.
|
||||
- Competing versions, query/fragment variants, case and file/directory prefix
|
||||
collisions, inline sources, and unrepresentable paths use unique isolated
|
||||
names with a stated reason. Missing competing sources also prevent selection
|
||||
of an apparently canonical version.
|
||||
|
||||
## Interpretation and limits
|
||||
|
||||
Export does not fetch URLs or execute JavaScript. Retained Debugger sources and
|
||||
response bytes do not establish execution or a complete website. Response bytes
|
||||
are browser-decoded bytes; they do not represent transfer encoding. Declared
|
||||
redaction is preserved and can change JavaScript syntax; passive captures do
|
||||
not report a byte-redaction flag, so that value remains unknown.
|
||||
|
||||
The existing analyzer inventories and parses the actual exported files. It can
|
||||
resolve ordinary captured relative modules, but does not implement browser
|
||||
import maps, root-relative URL loading, remote imports, or runtime behavior.
|
||||
Its local resolver may strip import queries/fragments; isolated variants are
|
||||
kept away from canonical paths to prevent those from selecting a different
|
||||
observed version. Missing dependencies remain unknown. Source maps are not
|
||||
fetched or reconstructed during export.
|
||||
|
||||
The export Evidence describes a derivation from the saved capture. Subsequent
|
||||
application-analysis Evidence describes the local exported artifact. Their
|
||||
identities and authorities remain separate.
|
||||
|
||||
## Verification
|
||||
|
||||
`npm run verify:browser:scripts` exercises active capture, exact source export,
|
||||
manifest readback, query variants, and relative-module analysis through a real
|
||||
browser, CLI, and stdio MCP. Set `REA_BROWSER_EXECUTABLE` to an installed
|
||||
Chrome-family browser. The verifier accepts an optional installed REA entrypoint
|
||||
argument for package verification. `npm run verify:browser` additionally covers
|
||||
passive `inspect_web_page` export through both public adapters.
|
||||
@@ -1,8 +1,8 @@
|
||||
# Windows Ghidra P0
|
||||
|
||||
This guide describes repository main. npm 4.0.1 does not include the Windows
|
||||
This guide describes repository main. npm 4.1.0 includes the Windows
|
||||
native bundle; check the [release boundary](installation.md#released-package-and-main)
|
||||
before applying these instructions to a published package.
|
||||
before applying these instructions to an older published package.
|
||||
|
||||
Status: experimental Windows x64 support for the read-only P0 boundary. Windows
|
||||
package builds bundle native process, filesystem, and DACL controls. REA
|
||||
@@ -13,8 +13,8 @@ additional permission flag or degraded mode is required.
|
||||
|
||||
- Windows 10+ x64 with fixed local NTFS target and temporary volumes;
|
||||
- Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+;
|
||||
- an operator-installed official Ghidra 12.1.4 distribution;
|
||||
- a 64-bit full JDK 21;
|
||||
- an operator-installed Ghidra 12.1.x distribution (verified with 12.1.4);
|
||||
- a 64-bit full JDK inside that installation's declared Java range (JDK 21 or newer, with no maximum, for current 12.1 releases);
|
||||
- an explicit native, non-managed, non-DLL x86-64 PE application; and
|
||||
- the 25 read-only Ghidra inventory, memory, and function-analysis operations.
|
||||
|
||||
@@ -32,6 +32,10 @@ REA does not install or upgrade Ghidra, Java, Python, npm, Node.js, Hopper, or a
|
||||
compiler. The adapter uses the packaged Java `HeadlessScript`; Python and
|
||||
PyGhidra are not prerequisites. Users do not build the native addon.
|
||||
|
||||
REA selects its packaged bridge scripts by explicit path. Same-named files or
|
||||
directories in the caller's working directory do not select a different
|
||||
script or require users to clean that directory before analysis.
|
||||
|
||||
## Configuration
|
||||
|
||||
```powershell
|
||||
@@ -144,7 +148,7 @@ private runtime files; crash-time transactional deletion is not claimed.
|
||||
|
||||
## Verification
|
||||
|
||||
See [native controls](../native/windows/README.md) for the separate artifact
|
||||
See [native controls](https://github.com/morluto/rea/blob/main/native/windows/README.md) for the separate artifact
|
||||
build lane and native boundary checks. Development tarballs without the built
|
||||
artifact report native controls unavailable; publishing requires the verified
|
||||
artifact and manifest.
|
||||
|
||||
+12
-2
@@ -3,6 +3,10 @@ set -euo pipefail
|
||||
|
||||
PACKAGE="rea-agents"
|
||||
REPOSITORY="morluto/rea"
|
||||
numeric_identifier='(0|[1-9][0-9]*)'
|
||||
prerelease_identifier="(${numeric_identifier}|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)"
|
||||
build_identifier='[0-9A-Za-z-]+'
|
||||
version_pattern="${numeric_identifier}\\.${numeric_identifier}\\.${numeric_identifier}(-${prerelease_identifier}(\\.${prerelease_identifier})*)?(\\+${build_identifier}(\\.${build_identifier})*)?"
|
||||
version="${REA_VERSION:-}"
|
||||
dry_run=false
|
||||
start_setup=true
|
||||
@@ -67,14 +71,20 @@ if ! [[ "$_node_patch" =~ ^[0-9]+(\+[0-9A-Za-z.-]+)?$ ]] ||
|
||||
fi
|
||||
|
||||
if [[ -n "$version" ]]; then
|
||||
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "version must be an exact semantic version."
|
||||
[[ "$version" =~ ^${version_pattern}$ ]] || fail "version must be an exact semantic version."
|
||||
else
|
||||
release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "the latest REA release could not be resolved. Check network access or pass --version VERSION, then retry."
|
||||
tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})' 2>/dev/null)" || fail "the release response was invalid. Retry later or pass --version VERSION."
|
||||
[[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
|
||||
[[ "$tag" =~ ^rea-agents-(${version_pattern})$ ]] || fail "the latest release tag was invalid. Retry later or pass --version VERSION."
|
||||
version="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# npm's exact-version resolver cleans build metadata from the requested version,
|
||||
# so it may install the base release and make the post-install identity check fail.
|
||||
if [[ "$version" =~ \+ ]]; then
|
||||
fail "npm cannot install an exact version with build metadata. Pass a version without build metadata, then retry."
|
||||
fi
|
||||
|
||||
prefix_args=()
|
||||
if [[ "$platform" == "Linux" ]]; then
|
||||
prefix_args=(--prefix "$HOME/.local")
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
"$schema": "./node_modules/knip/schema.json",
|
||||
"entry": ["src/main.ts", "src/cli.ts", "src/contracts/errorSchemas.ts"],
|
||||
"project": ["src/**/*.ts", "tests/**/*.ts", "scripts/**/*.mjs"],
|
||||
"ignoreDependencies": ["electron"],
|
||||
"vitepress": {
|
||||
"entry": ["docs/.vitepress/config.ts"]
|
||||
},
|
||||
"ignoreBinaries": ["mkfifo"],
|
||||
"ignoreDependencies": ["electron", "esbuild", "webpack"],
|
||||
"ignoreExportsUsedInFile": true,
|
||||
"rules": {
|
||||
"files": "error",
|
||||
|
||||
@@ -66,6 +66,11 @@ Windows failures retain their constraint, requested coordinate, Win32 code,
|
||||
and system message. Unsupported filesystems and reparse paths are distinct
|
||||
from OS access denial and missing packaged controls.
|
||||
|
||||
Ordinary drive-absolute paths accept backslashes, forward slashes, or mixed
|
||||
separators. File identity retains the original requested spelling. Separator
|
||||
translation does not resolve dot components, repeated separators, or extend
|
||||
the supported device/UNC namespaces; existing admission checks still apply.
|
||||
|
||||
The bearer-token descriptor remains under its immutable private lease until
|
||||
`runtime_close` on Windows; POSIX removes the descriptor after the bridge reads
|
||||
it. Job ownership guarantees process termination on owner death, not deletion
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
|
||||
namespace rea {
|
||||
|
||||
// Translate ordinary DOS drive separators without resolving components or
|
||||
// changing extended, device, UNC, relative, or drive-relative namespaces.
|
||||
inline std::wstring ordinaryDriveSeparators(std::wstring path) {
|
||||
if (path.size() >= 3 && path[1] == L':' &&
|
||||
((path[0] >= L'A' && path[0] <= L'Z') || (path[0] >= L'a' && path[0] <= L'z')) &&
|
||||
(path[2] == L'\\' || path[2] == L'/'))
|
||||
std::replace(path.begin(), path.end(), L'/', L'\\');
|
||||
return path;
|
||||
}
|
||||
|
||||
struct Failure : std::runtime_error {
|
||||
DWORD win32;
|
||||
std::wstring path;
|
||||
|
||||
@@ -14,6 +14,7 @@ static std::wstring hex(const unsigned char* bytes, size_t count) {
|
||||
}
|
||||
|
||||
static std::wstring localPath(std::wstring path) {
|
||||
path = ordinaryDriveSeparators(std::move(path));
|
||||
if (path.rfind(L"\\\\?\\", 0) == 0) path.erase(0, 4);
|
||||
require(path.size() >= 3 && path[1] == L':' && path[2] == L'\\' &&
|
||||
((path[0] >= L'A' && path[0] <= L'Z') || (path[0] >= L'a' && path[0] <= L'z')),
|
||||
@@ -283,7 +284,7 @@ static Handle createPrivateFile(Runtime& root, const std::wstring& relative) {
|
||||
verifyPrivate(parent->get(), parentPath, parentPath == root.path);
|
||||
Security security;
|
||||
auto file = createRelative(parent->get(), path.substr(path.find_last_of(L'\\') + 1),
|
||||
GENERIC_WRITE | GENERIC_READ | READ_CONTROL, false, security, path);
|
||||
DELETE | GENERIC_WRITE | GENERIC_READ | READ_CONTROL, false, security, path);
|
||||
verifyPrivate(file.get(), path, false);
|
||||
return file;
|
||||
}
|
||||
@@ -411,6 +412,18 @@ static void copySnapshot(napi_env, void* pointer) {
|
||||
static void completeSnapshot(napi_env env, napi_status status, void* pointer) {
|
||||
std::unique_ptr<SnapshotWork> work(static_cast<SnapshotWork*>(pointer));
|
||||
work->root->snapshotPending = false;
|
||||
const auto reject = [&](const Failure& failure) {
|
||||
try {
|
||||
dispose(work->destination.get(), work->path);
|
||||
work->destination.reset();
|
||||
napi_reject_deferred(env, work->deferred, failureValue(env, failure));
|
||||
} catch (const Failure& cleanupFailure) {
|
||||
const Failure combined(
|
||||
failure.constraint + "; incomplete snapshot cleanup failed: " + cleanupFailure.constraint,
|
||||
work->path, cleanupFailure.win32);
|
||||
napi_reject_deferred(env, work->deferred, failureValue(env, combined));
|
||||
}
|
||||
};
|
||||
try {
|
||||
if (work->failure) throw *work->failure;
|
||||
require(status == napi_ok && !work->root->snapshotCancelled.load(), "Native snapshot worker was cancelled", work->path, ERROR_OPERATION_ABORTED);
|
||||
@@ -421,10 +434,9 @@ static void completeSnapshot(napi_env env, napi_status status, void* pointer) {
|
||||
work->root->immutableFiles.push_back(std::move(work->destination));
|
||||
check(napi_resolve_deferred(env, work->deferred, result));
|
||||
} catch (const Failure& failure) {
|
||||
napi_reject_deferred(env, work->deferred, failureValue(env, failure));
|
||||
reject(failure);
|
||||
} catch (const std::exception& failure) {
|
||||
napi_reject_deferred(env, work->deferred,
|
||||
failureValue(env, Failure(failure.what(), work->path, ERROR_GEN_FAILURE)));
|
||||
reject(Failure(failure.what(), work->path, ERROR_GEN_FAILURE));
|
||||
}
|
||||
napi_delete_reference(env, work->rootReference);
|
||||
napi_delete_async_work(env, work->work);
|
||||
@@ -505,7 +517,13 @@ napi_value filesystemCall(napi_env env, const std::wstring& operation, const std
|
||||
auto file = runtimeDirectory(root, path.substr(0, path.find_last_of(L'\\')));
|
||||
file->path = path;
|
||||
file->requestedPath = path;
|
||||
file->handles.push_back(openComponent(path, GENERIC_READ, false));
|
||||
// Retained output handles have write and DELETE access, so this read open
|
||||
// must share both to pass Windows' symmetric share check. Those retained
|
||||
// handles still omit FILE_SHARE_WRITE and FILE_SHARE_DELETE, blocking
|
||||
// competing writes and delete opens.
|
||||
file->handles.push_back(openComponent(path, GENERIC_READ, false,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE |
|
||||
FILE_SHARE_DELETE));
|
||||
verifyPrivate(file->get(), path, false);
|
||||
auto result = identity(env, file->get(), path);
|
||||
set(env, result, "handle", wrap(env, std::move(file)));
|
||||
|
||||
@@ -44,7 +44,8 @@ static void pipe(Handle& read, Handle& write) {
|
||||
|
||||
std::unique_ptr<Process> spawnProcess(const std::wstring& command, const std::wstring& commandLine,
|
||||
const std::wstring& cwd, const std::vector<std::wstring>& environment) {
|
||||
require(command.size() >= 3 && command[1] == L':' && command[2] == L'\\',
|
||||
const auto executable = ordinaryDriveSeparators(command);
|
||||
require(executable.size() >= 3 && executable[1] == L':' && executable[2] == L'\\',
|
||||
"Owned Windows process requires an absolute executable path", command, ERROR_INVALID_PARAMETER);
|
||||
auto result = std::make_unique<Process>();
|
||||
result->job = createJob();
|
||||
@@ -102,7 +103,7 @@ std::unique_ptr<Process> spawnProcess(const std::wstring& command, const std::ws
|
||||
if (uniqueEnvironment.empty()) variables.push_back(L'\0');
|
||||
std::vector<wchar_t> line(commandLine.begin(), commandLine.end()); line.push_back(L'\0');
|
||||
PROCESS_INFORMATION process{};
|
||||
require(CreateProcessW(command.c_str(), line.data(), nullptr, nullptr, TRUE,
|
||||
require(CreateProcessW(executable.c_str(), line.data(), nullptr, nullptr, TRUE,
|
||||
CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED | CREATE_NO_WINDOW,
|
||||
variables.data(), cwd.empty() ? nullptr : cwd.c_str(), &startup.StartupInfo, &process),
|
||||
"Create process atomically inside owned Job Object failed", command);
|
||||
|
||||
Generated
+2588
-31
File diff suppressed because it is too large.
Load diff
+40
-19
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rea-agents",
|
||||
"mcpName": "io.github.morluto/rea",
|
||||
"version": "4.1.0",
|
||||
"version": "5.0.0",
|
||||
"description": "Reverse engineer anything from your terminal or agent with one CLI and MCP server.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -42,10 +42,12 @@
|
||||
"dist",
|
||||
"install.sh",
|
||||
"bridge/hopper_bridge.py",
|
||||
"bridge/native/NativeUIChildren.swift",
|
||||
"bridge/native/ReaNativeUI.swift",
|
||||
"bridge/ghidra/ReaGhidraBridge.java",
|
||||
"bridge/ghidra/ReaGhidraExtensions.java",
|
||||
"bridge/ghidra/ReaGhidraPrepareCom.java",
|
||||
"bridge/android/ReaJadxBridge.java",
|
||||
"scripts/rea.mjs",
|
||||
"scripts/electron-active-hook.cjs",
|
||||
"scripts/electron-active-hook-boundaries.cjs",
|
||||
@@ -81,11 +83,11 @@
|
||||
"check:pr": "turbo run typecheck lint format:check knip metadata:check test:run && npm run docs:check && npm run jscpd && npm run scan:todos",
|
||||
"precheck:ci": "npm run deps:check",
|
||||
"check:ci": "turbo run typecheck lint format:check knip metadata:check jscpd scan:todos",
|
||||
"build:cached": "turbo run build",
|
||||
"build:cached": "node scripts/check-dependency-install.mjs && turbo run build",
|
||||
"format": "oxfmt .",
|
||||
"format:check": "oxfmt --check .",
|
||||
"lint": "oxlint",
|
||||
"lint:fix": "oxlint --fix",
|
||||
"lint": "oxlint && npm run verify:module-boundaries",
|
||||
"lint:fix": "oxlint --fix && npm run verify:module-boundaries",
|
||||
"knip": "knip",
|
||||
"jscpd": "jscpd src/ tests/",
|
||||
"scan:todos": "node scripts/scan-todos.mjs",
|
||||
@@ -119,29 +121,32 @@
|
||||
"typecheck": "tsc --noEmit",
|
||||
"verify:test-temp-hygiene": "npm run build:cached && node scripts/verify-test-temp-hygiene.mjs",
|
||||
"verify:hopper": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper.mjs",
|
||||
"verify:apple-dispatch": "npm run build:cached && node scripts/verify-apple-dispatch.mjs",
|
||||
"verify:apple-dispatch": "npm run build:cached && node scripts/verify/apple/dispatch.mjs",
|
||||
"verify:native-ui": "npm run build:cached && node scripts/verify-native-ui.mjs",
|
||||
"verify:keyed-archive": "npm run build:cached && node scripts/verify-keyed-archive.mjs",
|
||||
"verify:keyed-archive": "npm run build:cached && node scripts/verify/apple/keyed-archive.mjs",
|
||||
"verify:macos-bundle": "npm run build:cached && node scripts/verify/apple/macos-bundle.mjs",
|
||||
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
|
||||
"verify:ida": "npm run build:cached && node scripts/verify-real-ida.mjs",
|
||||
"verify:ghidra:switch": "npm run build:cached && node scripts/verify-real-ghidra-switch.mjs",
|
||||
"verify:ghidra:dos": "npm run build:cached && node scripts/verify-real-ghidra-dos.mjs",
|
||||
"verify:ghidra:com": "npm run build:cached && node scripts/verify-real-ghidra-com.mjs",
|
||||
"verify:ghidra:aarch64-jump-table": "npm run build:cached && node scripts/verify-real-ghidra.mjs --aarch64-jump-table",
|
||||
"verify:interface-builder": "npm run build:cached && node scripts/verify-interface-builder.mjs",
|
||||
"verify:interface-builder": "npm run build:cached && node scripts/verify/apple/interface-builder.mjs",
|
||||
"verify:ghidra:cross-format": "npm run build:cached && node scripts/verify-real-ghidra.mjs --cross-format",
|
||||
"verify:ghidra:windows": "npm run build:cached && npm run build:ghidra-windows-fixture && node scripts/verify-real-ghidra-windows.mjs",
|
||||
"verify:browser": "npm run build:cached && node scripts/verify-real-browser.mjs",
|
||||
"verify:browser:dom": "node scripts/verify-browser-dom-destinations.mjs",
|
||||
"verify:browser:network": "npm run build:cached && node scripts/verify-browser-network.mjs",
|
||||
"verify:electron": "npm run build:cached && node scripts/verify-real-electron.mjs",
|
||||
"verify:inspector": "npm run build:cached && node scripts/verify-real-javascript-runtime-observation.mjs",
|
||||
"verify:javascript-runtime": "npm run build:cached && node scripts/verify-local-javascript-runtime.mjs",
|
||||
"verify:javascript:digests": "npm run build:cached && node scripts/verify-large-javascript-digests.mjs",
|
||||
"verify:application-workflows": "npm run build:cached && node scripts/verify-local-application-workflows.mjs",
|
||||
"verify:managed": "npm run build:cached && node scripts/verify-managed-conformance.mjs",
|
||||
"verify:firmware": "npm run build:cached && node scripts/verify-real-firmware.mjs",
|
||||
"fixtures:firmware": "node scripts/generate-firmware-fixtures.mjs",
|
||||
"verify:android": "npm run build:cached && node scripts/verify-real-android.mjs",
|
||||
"fixtures:android": "node scripts/fetch-android-fixtures.mjs",
|
||||
"verify:inspector": "npm run build:cached && node scripts/verify/inspector/runtime-observation.mjs",
|
||||
"verify:javascript-runtime": "npm run build:cached && node scripts/verify/javascript/runtime-observation.mjs",
|
||||
"verify:javascript:digests": "npm run build:cached && node scripts/verify/javascript/digests.mjs",
|
||||
"verify:application-workflows": "npm run build:cached && node scripts/verify/javascript/application-workflows.mjs",
|
||||
"verify:managed": "npm run build:cached && node scripts/verify/managed/conformance.mjs",
|
||||
"verify:firmware": "npm run build:cached && node scripts/verify/firmware/analysis.mjs",
|
||||
"fixtures:firmware": "node scripts/fixtures/firmware/generate.mjs",
|
||||
"verify:android": "npm run build:cached && node scripts/verify/android/analysis.mjs",
|
||||
"fixtures:android": "node scripts/fixtures/android/fetch.mjs",
|
||||
"verify:agent": "npm run build:cached && node scripts/verify-agent-experience.mjs",
|
||||
"verify:hopper:linux": "npm run verify:fixtures && npm run build:cached && node scripts/verify-real-hopper-linux.mjs",
|
||||
"verify:setup:linux": "npm run build:cached && node scripts/verify-linux-setup-e2e.mjs",
|
||||
@@ -156,6 +161,9 @@
|
||||
"config:print": "node scripts/print-mcp-config.mjs",
|
||||
"evidence:generate": "npm run build:cached && node scripts/generate-completion-ledger.mjs",
|
||||
"evidence:check": "npm run build:cached && node scripts/generate-completion-ledger.mjs --check",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs",
|
||||
"docs:generate": "node scripts/run-exclusive.mjs docs npm run docs:generate:unlocked",
|
||||
"docs:generate:unlocked": "npm run metadata:generate && npm run build:cached && node scripts/generate-mcp-tool-catalog.mjs && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs && node scripts/generate-error-schema.mjs",
|
||||
"docs:check": "node scripts/run-exclusive.mjs docs turbo run docs:check:built",
|
||||
@@ -163,7 +171,7 @@
|
||||
"docs:error-schema": "npm run build:cached && node scripts/generate-error-schema.mjs",
|
||||
"prepack": "node scripts/prepack.mjs",
|
||||
"prepare": "node scripts/prepare.mjs",
|
||||
"verify:asset-catalog": "npm run build:cached && node scripts/verify-asset-catalog.mjs",
|
||||
"verify:asset-catalog": "npm run build:cached && node scripts/verify/apple/asset-catalog.mjs",
|
||||
"verify:native-ui:permissions": "npm run build:cached && node scripts/verify-native-ui.mjs --allow-permission-denial",
|
||||
"test:focused": "node scripts/run-development-tests.mjs focused",
|
||||
"build:windows-native": "node scripts/build-windows-native.mjs",
|
||||
@@ -173,7 +181,14 @@
|
||||
"verify:ghidra:windows:package": "node scripts/verify-packaged-ghidra-windows.mjs",
|
||||
"build:ghidra:nativeaot": "node scripts/build-ghidra-nativeaot.mjs",
|
||||
"build:fixtures:nativeaot": "node scripts/generate-nativeaot-fixtures.mjs",
|
||||
"verify:ghidra:nativeaot": "npm run build:cached && node scripts/verify-real-ghidra-nativeaot.mjs"
|
||||
"verify:ghidra:nativeaot": "npm run build:cached && node scripts/verify-real-ghidra-nativeaot.mjs",
|
||||
"verify:browser:scripts": "npm run build:cached && node scripts/verify-browser-scripts.mjs",
|
||||
"verify:browser:modules": "npm run build:cached && node scripts/verify-browser-modules.mjs",
|
||||
"verify:test-discovery": "node scripts/verify-test-discovery.mjs",
|
||||
"verify:javascript:recovery": "node scripts/verify/javascript/recovery.mjs",
|
||||
"verify:module-boundaries": "node scripts/verify-module-boundaries.mjs",
|
||||
"verify:browser:source-maps": "npm run build:cached && node scripts/verify-browser-source-maps.mjs",
|
||||
"verify:browser:runtime": "npm run build:cached && node scripts/verify-browser-runtime.mjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}": [
|
||||
@@ -205,11 +220,16 @@
|
||||
"playwright-core": "1.58.2",
|
||||
"plist": "5.0.0",
|
||||
"semver": "7.8.5",
|
||||
"smol-toml": "1.7.0",
|
||||
"smol-toml": "1.9.0",
|
||||
"write-file-atomic": "7.0.0",
|
||||
"ws": "8.21.0",
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
"overrides": {
|
||||
"vitepress": {
|
||||
"vite": "6.4.4"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fast-check/vitest": "0.5.0",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
@@ -227,6 +247,7 @@
|
||||
"oxlint": "1.87.0",
|
||||
"turbo": "2.11.7",
|
||||
"typescript": "7.0.2",
|
||||
"vitepress": "1.6.4",
|
||||
"vitest": "5.0.3",
|
||||
"yaml": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -20,14 +20,17 @@ const source = resolve(
|
||||
const install = process.env.GHIDRA_INSTALL_DIR;
|
||||
if (!install)
|
||||
throw new Error(
|
||||
"NativeAOT build lane requires existing GHIDRA_INSTALL_DIR (Ghidra 12.1.4).",
|
||||
"NativeAOT build lane requires existing GHIDRA_INSTALL_DIR (Ghidra 12.1.x).",
|
||||
);
|
||||
const properties = await readFile(
|
||||
join(install, "Ghidra/application.properties"),
|
||||
"utf8",
|
||||
);
|
||||
if (!/^application.version=12\.1\.4\s*$/m.test(properties))
|
||||
throw new Error("NativeAOT build lane requires Ghidra 12.1.4.");
|
||||
const ghidraVersion = propertyValue(properties, "application.version");
|
||||
if (!ghidraReleaseLineAccepted(ghidraVersion))
|
||||
throw new Error(
|
||||
`NativeAOT build lane accepts Ghidra 12.1.x (verified with 12.1.4). Observed application.version=${ghidraVersion ?? "missing"}.`,
|
||||
);
|
||||
const output = resolve(
|
||||
process.env.REA_NATIVEAOT_BUILD_ROOT ??
|
||||
join(root, "_reference/nativeaot-integration/extension"),
|
||||
@@ -47,9 +50,11 @@ const run = (command, args) => {
|
||||
return result.stdout.trim();
|
||||
};
|
||||
const javac = java ? join(java, "javac") : "javac";
|
||||
if (!/^javac 21(?:\.|$)/u.test(run(javac, ["-version"])))
|
||||
const javacBanner = capture(javac, ["-version"]);
|
||||
const javacMajor = Number(/^javac (\d+)/mu.exec(javacBanner)?.[1]);
|
||||
if (!Number.isInteger(javacMajor) || javacMajor < 21)
|
||||
throw new Error(
|
||||
"NativeAOT build lane requires an existing JDK 21 javac; REA does not install or upgrade Java.",
|
||||
`NativeAOT build lane requires javac 21 or newer and compiles with --release 21. Observed: ${javacBanner || "no version"}. REA does not install or upgrade Java.`,
|
||||
);
|
||||
const revision = run("git", ["-C", source, "rev-parse", "HEAD"]);
|
||||
if (
|
||||
@@ -93,6 +98,8 @@ try {
|
||||
run(javac, [
|
||||
"-J-Xmx512m",
|
||||
"-J-XX:ActiveProcessorCount=1",
|
||||
"--release",
|
||||
"21",
|
||||
`@${join(output, "javac.args")}`,
|
||||
]);
|
||||
await writeFile(
|
||||
@@ -119,7 +126,7 @@ try {
|
||||
jar,
|
||||
sha256,
|
||||
upstream_revision: revision,
|
||||
ghidra_version: "12.1.4",
|
||||
ghidra_version: ghidraVersion,
|
||||
integration_api: 1,
|
||||
},
|
||||
null,
|
||||
@@ -131,10 +138,41 @@ try {
|
||||
jar,
|
||||
sha256,
|
||||
upstream_revision: revision,
|
||||
ghidra_version: "12.1.4",
|
||||
ghidra_version: ghidraVersion,
|
||||
integration_api: 1,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await rm(classes, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
/** Read one application.properties value. Empty and missing values stay unknown. */
|
||||
function propertyValue(content, name) {
|
||||
for (const line of content.split(/\r?\n/u)) {
|
||||
const separator = line.indexOf("=");
|
||||
if (separator < 0 || line.slice(0, separator).trim() !== name) continue;
|
||||
const value = line.slice(separator + 1).trim();
|
||||
return value.length === 0 ? null : value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Same 12.1 line as GhidraInstallationPolicy, including a distro suffix. */
|
||||
function ghidraReleaseLineAccepted(raw) {
|
||||
const match = /^(\d+)\.(\d+)(?:\.(\d+))?([-+].*)?$/u.exec(raw ?? "");
|
||||
return match !== null && Number(match[1]) === 12 && Number(match[2]) === 1;
|
||||
}
|
||||
|
||||
function capture(command, args) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
timeout: 120_000,
|
||||
maxBuffer: 4 * 1024 * 1024,
|
||||
});
|
||||
if (result.error || result.status !== 0)
|
||||
throw new Error(
|
||||
`NativeAOT build prerequisite/action ${command} failed: ${result.error?.message ?? ""}\n${result.stdout ?? ""}${result.stderr ?? ""}`,
|
||||
);
|
||||
return `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
||||
}
|
||||
File renamed without changes.
@@ -3,15 +3,17 @@ import { createReadStream } from "node:fs";
|
||||
import { link, mkdir, open, rm } from "node:fs/promises";
|
||||
import { resolve, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { JADX_RELEASE } from "../src/android/JadxRelease.ts";
|
||||
import fixture from "./fixtures/android-apidemos.json" with { type: "json" };
|
||||
import { JADX_RELEASE } from "../../../src/android/JadxRelease.ts";
|
||||
import fixture from "./apidemos.json" with { type: "json" };
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length > 1)
|
||||
throw new Error("Usage: node scripts/fetch-android-fixtures.mjs [DIRECTORY]");
|
||||
throw new Error("Usage: npm run fixtures:android -- [DIRECTORY]");
|
||||
const directory = resolve(
|
||||
args[0] ??
|
||||
fileURLToPath(new URL("../_reference/apk-integration/", import.meta.url)),
|
||||
fileURLToPath(
|
||||
new URL("../../../_reference/apk-integration/", import.meta.url),
|
||||
),
|
||||
);
|
||||
await mkdir(directory, { recursive: true });
|
||||
const hashFile = async (path) => {
|
||||
@@ -0,0 +1,269 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
|
||||
/** Commands the macOS bundle fixture needs from Command Line Tools. */
|
||||
export const MACOS_BUNDLE_FIXTURE_COMMANDS = [
|
||||
["/usr/bin/xcrun", ["--find", "clang"]],
|
||||
["/usr/bin/xcrun", ["--find", "codesign"]],
|
||||
["/usr/bin/ditto", ["-h"]],
|
||||
["/usr/bin/hdiutil", ["help"]],
|
||||
];
|
||||
|
||||
/** Fail with the missing command name before building any fixture. */
|
||||
export async function preflightMacosBundleFixture() {
|
||||
if (process.platform !== "darwin")
|
||||
throw new Error(
|
||||
"macOS bundle verification requires a macOS host with Command Line Tools",
|
||||
);
|
||||
for (const [command, arguments_] of MACOS_BUNDLE_FIXTURE_COMMANDS)
|
||||
try {
|
||||
await exec(command, arguments_);
|
||||
} catch (cause) {
|
||||
// `ditto -h` and `hdiutil help` exit non-zero while printing usage.
|
||||
if (cause?.code === "ENOENT")
|
||||
throw new Error(
|
||||
`macOS bundle verification requires ${command} ${arguments_.join(" ")}`,
|
||||
{ cause },
|
||||
);
|
||||
if (command === "/usr/bin/xcrun")
|
||||
throw new Error(
|
||||
`macOS bundle verification requires Command Line Tools (${arguments_.join(" ")} failed)`,
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const plist = (entries) =>
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
${Object.entries(entries)
|
||||
.map(([key, value]) => ` <key>${key}</key>\n <string>${value}</string>`)
|
||||
.join("\n")}
|
||||
</dict>
|
||||
</plist>
|
||||
`;
|
||||
|
||||
const bundlePlist = (identifier, executable, packageType = "APPL") =>
|
||||
plist({
|
||||
CFBundleIdentifier: identifier,
|
||||
CFBundleExecutable: executable,
|
||||
CFBundlePackageType: packageType,
|
||||
});
|
||||
|
||||
const write = async (path, text) => {
|
||||
await mkdir(dirname(path), { recursive: true });
|
||||
await writeFile(path, text);
|
||||
};
|
||||
|
||||
/** Compile one C source beneath `root/sources` into `output`. */
|
||||
const clang = async (root, { name, source, output, flags = [] }) => {
|
||||
const sourcePath = join(root, "sources", `${name}.c`);
|
||||
await write(sourcePath, source);
|
||||
await mkdir(dirname(output), { recursive: true });
|
||||
await exec("/usr/bin/xcrun", [
|
||||
"clang",
|
||||
"-O0",
|
||||
sourcePath,
|
||||
"-o",
|
||||
output,
|
||||
...flags,
|
||||
]);
|
||||
};
|
||||
|
||||
const PROGRAM = `int main(void) { return 0; }\n`;
|
||||
|
||||
/** A versioned framework whose own rpath supplies a second `libchain.dylib`. */
|
||||
const buildCoreFramework = async (root, frameworks) => {
|
||||
const core = join(frameworks, "Core.framework");
|
||||
const version = join(core, "Versions", "A");
|
||||
const chainFlags = ["-dynamiclib", "-install_name", "@rpath/libchain.dylib"];
|
||||
await clang(root, {
|
||||
name: "chain-app",
|
||||
source: `const char *chain_variant(void) { return "app"; }\n`,
|
||||
output: join(frameworks, "libchain.dylib"),
|
||||
flags: chainFlags,
|
||||
});
|
||||
await clang(root, {
|
||||
name: "chain-framework",
|
||||
source: `const char *chain_variant(void) { return "framework"; }\n`,
|
||||
output: join(version, "Libraries", "libchain.dylib"),
|
||||
flags: chainFlags,
|
||||
});
|
||||
await clang(root, {
|
||||
name: "sibling",
|
||||
source: `int sibling_value(void) { return 7; }\n`,
|
||||
output: join(version, "libsibling.dylib"),
|
||||
flags: ["-dynamiclib", "-install_name", "@loader_path/libsibling.dylib"],
|
||||
});
|
||||
await clang(root, {
|
||||
name: "core",
|
||||
source: `const char *chain_variant(void);
|
||||
int sibling_value(void);
|
||||
const char *core_chain(void) { return sibling_value() == 7 ? chain_variant() : "broken"; }
|
||||
`,
|
||||
output: join(version, "Core"),
|
||||
flags: [
|
||||
"-dynamiclib",
|
||||
"-install_name",
|
||||
"@rpath/Core.framework/Versions/A/Core",
|
||||
"-Wl,-rpath,@loader_path/Libraries",
|
||||
join(version, "libsibling.dylib"),
|
||||
join(version, "Libraries", "libchain.dylib"),
|
||||
],
|
||||
});
|
||||
await write(
|
||||
join(version, "Resources", "Info.plist"),
|
||||
bundlePlist("com.example.rea.core", "Core", "FMWK"),
|
||||
);
|
||||
const service = join(version, "XPCServices", "FwSvc.xpc", "Contents");
|
||||
await clang(root, {
|
||||
name: "framework-service",
|
||||
source: PROGRAM,
|
||||
output: join(service, "MacOS", "FwSvc"),
|
||||
});
|
||||
await write(
|
||||
join(service, "Info.plist"),
|
||||
bundlePlist("com.example.rea.core.service", "FwSvc", "XPC!"),
|
||||
);
|
||||
await symlink("A", join(core, "Versions", "Current"));
|
||||
await symlink("Versions/Current/Core", join(core, "Core"));
|
||||
await symlink("Versions/Current/Resources", join(core, "Resources"));
|
||||
await symlink("Versions/Current/XPCServices", join(core, "XPCServices"));
|
||||
return join(version, "Core");
|
||||
};
|
||||
|
||||
/** The main executable, with a weak dependency deleted after linking. */
|
||||
const buildMainExecutable = async (root, contents, coreBinary) => {
|
||||
const gone = join(contents, "Frameworks", "libgone.dylib");
|
||||
await clang(root, {
|
||||
name: "gone",
|
||||
source: `int gone_value(void) { return 1; }\n`,
|
||||
output: gone,
|
||||
flags: ["-dynamiclib", "-install_name", "@rpath/libgone.dylib"],
|
||||
});
|
||||
const executable = join(contents, "MacOS", "MacFixture");
|
||||
await clang(root, {
|
||||
name: "main",
|
||||
source: `#include <stdio.h>
|
||||
const char *core_chain(void);
|
||||
extern int gone_value(void) __attribute__((weak_import));
|
||||
int main(void) {
|
||||
printf("chain=%s weak=%s\\n", core_chain(), gone_value ? "present" : "absent");
|
||||
return 0;
|
||||
}
|
||||
`,
|
||||
output: executable,
|
||||
flags: [
|
||||
"-Wl,-rpath,@executable_path/../Frameworks",
|
||||
coreBinary,
|
||||
`-Wl,-weak_library,${gone}`,
|
||||
],
|
||||
});
|
||||
await rm(gone);
|
||||
await write(
|
||||
join(contents, "Info.plist"),
|
||||
bundlePlist("com.example.rea.fixture", "MacFixture"),
|
||||
);
|
||||
return executable;
|
||||
};
|
||||
|
||||
/** One `Contents/`-layout bundle with a trivial executable. */
|
||||
const buildNestedBundle = async (root, { bundle, executable, plist }) => {
|
||||
await clang(root, {
|
||||
name: executable,
|
||||
source: PROGRAM,
|
||||
output: join(bundle, "Contents", "MacOS", executable),
|
||||
});
|
||||
await write(join(bundle, "Contents", "Info.plist"), plist);
|
||||
};
|
||||
|
||||
const buildNestedBundles = async (root, contents, coreBinary) => {
|
||||
const service = join(contents, "XPCServices", "Svc.xpc", "Contents");
|
||||
await clang(root, {
|
||||
name: "service",
|
||||
source: `const char *core_chain(void);
|
||||
int main(void) { return core_chain()[0] == 'b'; }
|
||||
`,
|
||||
output: join(service, "MacOS", "Svc"),
|
||||
flags: ["-Wl,-rpath,@executable_path/../../../../Frameworks", coreBinary],
|
||||
});
|
||||
await write(
|
||||
join(service, "Info.plist"),
|
||||
bundlePlist("com.example.rea.fixture.service", "Svc", "XPC!"),
|
||||
);
|
||||
await buildNestedBundle(root, {
|
||||
bundle: join(contents, "PlugIns", "Ext.appex"),
|
||||
executable: "Ext",
|
||||
plist: bundlePlist("com.example.rea.fixture.extension", "Ext", "XPC!"),
|
||||
});
|
||||
await buildNestedBundle(root, {
|
||||
bundle: join(contents, "Library", "LoginItems", "Login.app"),
|
||||
executable: "Login",
|
||||
plist: bundlePlist("com.example.rea.fixture.login", "Login"),
|
||||
});
|
||||
};
|
||||
|
||||
/** A privileged helper, its launchd plists, and a non-bundle helper tool. */
|
||||
const buildLaunchItems = async (root, contents) => {
|
||||
await clang(root, {
|
||||
name: "privileged-helper",
|
||||
source: PROGRAM,
|
||||
output: join(
|
||||
contents,
|
||||
"Library",
|
||||
"LaunchServices",
|
||||
"com.example.rea.helper",
|
||||
),
|
||||
});
|
||||
await write(
|
||||
join(contents, "Library", "LaunchDaemons", "com.example.rea.helper.plist"),
|
||||
plist({
|
||||
Label: "com.example.rea.helper",
|
||||
BundleProgram: "Contents/Library/LaunchServices/com.example.rea.helper",
|
||||
}),
|
||||
);
|
||||
await write(
|
||||
join(contents, "Library", "LaunchAgents", "com.example.rea.agent.plist"),
|
||||
plist({
|
||||
Label: "com.example.rea.agent",
|
||||
BundleProgram: "Contents/Helpers/rea-tool",
|
||||
}),
|
||||
);
|
||||
await clang(root, {
|
||||
name: "tool",
|
||||
source: PROGRAM,
|
||||
output: join(contents, "Helpers", "rea-tool"),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Build `MacFixture.app` with every bundle role and dylib-loading shape REA
|
||||
* projects. The main executable prints which `libchain.dylib` copy dyld
|
||||
* selected, so callers can compare static resolution with a real load.
|
||||
*/
|
||||
export async function buildMacosBundleFixture(root) {
|
||||
const app = join(root, "MacFixture.app");
|
||||
const contents = join(app, "Contents");
|
||||
const coreBinary = await buildCoreFramework(
|
||||
root,
|
||||
join(contents, "Frameworks"),
|
||||
);
|
||||
const executable = await buildMainExecutable(root, contents, coreBinary);
|
||||
await buildNestedBundles(root, contents, coreBinary);
|
||||
await buildLaunchItems(root, contents);
|
||||
await exec("/usr/bin/xcrun", [
|
||||
"codesign",
|
||||
"--force",
|
||||
"--deep",
|
||||
"--sign",
|
||||
"-",
|
||||
app,
|
||||
]);
|
||||
return { app, executable };
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { createServer } from "node:http";
|
||||
|
||||
/** Source-owned module graph with actual native loading and an uncalled lazy edge. */
|
||||
export async function startBrowserModuleSite() {
|
||||
const requests = [];
|
||||
const importMap = {
|
||||
imports: { "pkg/": "/pkg/", alias: "./global.js", blocked: null },
|
||||
scopes: { "/scoped/": { alias: "./scoped.js" } },
|
||||
};
|
||||
const main = `
|
||||
import {marker as alias} from 'alias';
|
||||
import {marker as pkg} from 'pkg/nested.js';
|
||||
const a = await import('/query.js?v=1#one');
|
||||
const b = await import('/query.js?v=1#two');
|
||||
const c = await import('/query.js?v=2');
|
||||
const again = await import('/query.js?v=1#one');
|
||||
const specifiers = ['alias', 'pkg/nested.js', '/query.js?v=1#one', '/query.js?v=1#two', '/query.js?v=2', '/query.js?v=1#one', './lazy.js', 'blocked', 'pkg/../escape.js', 'pkg/%2e%2e/escape.js', ''];
|
||||
globalThis.__moduleProof = {alias, pkg, urls: [a.url,b.url,c.url], instances: [a.instance,b.instance,c.instance], same_module: again === a, different_fragments: a !== b, resolutions: specifiers.map(specifier => {try{return {state: 'resolved', url: import.meta.resolve(specifier)}}catch(error){return {state: 'rejected', message: String(error)}}})};
|
||||
document.body.dataset.ready = 'true';
|
||||
function uncalled(){return import('./lazy.js')}
|
||||
function blocked(){return import('blocked')}
|
||||
function escaped(){return import('pkg/../escape.js')}
|
||||
function encodedEscape(){return import('pkg/%2e%2e/escape.js')}
|
||||
function empty(){return import('')}
|
||||
function computed(name){return import(name)}
|
||||
`;
|
||||
const server = createServer((request, response) => {
|
||||
requests.push(request.url);
|
||||
if (request.url === "/")
|
||||
return response
|
||||
.writeHead(200, { "Content-Type": "text/html" })
|
||||
.end(
|
||||
`<body><base href="/maps/"><script type="importmap">${JSON.stringify(importMap)}</script><script type="module" src="/scoped/main.js?build=2#entry"></script></body>`,
|
||||
);
|
||||
response.setHeader("Content-Type", "text/javascript");
|
||||
if (request.url?.startsWith("/scoped/main.js?")) return response.end(main);
|
||||
if (request.url === "/maps/scoped.js")
|
||||
return response.end("export const marker = 'scoped';");
|
||||
if (request.url === "/pkg/nested.js")
|
||||
return response.end("export const marker = 'pkg';");
|
||||
if (request.url?.startsWith("/query.js?"))
|
||||
return response.end(
|
||||
"globalThis.__moduleCount = (globalThis.__moduleCount ?? 0) + 1; export const instance = globalThis.__moduleCount; export const url = import.meta.url;",
|
||||
);
|
||||
response.writeHead(404).end("Not selected");
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === "string")
|
||||
throw new Error("Module fixture has no TCP address");
|
||||
return {
|
||||
origin: `http://127.0.0.1:${address.port}`,
|
||||
requests,
|
||||
importMap,
|
||||
main,
|
||||
close: () =>
|
||||
new Promise((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { once } from "node:events";
|
||||
import { createServer } from "node:http";
|
||||
import { gzipSync } from "node:zlib";
|
||||
|
||||
export const networkFixtureSecret = "私密-network-fixture";
|
||||
export const networkFixtureBinary = Buffer.from([0xff, 0, 0xfe, 1]);
|
||||
export const networkFixtureDecoded = Buffer.from("decoded-response-marker");
|
||||
|
||||
const html = `<!doctype html><meta charset="utf-8"><body><button id="start">Run</button><main id="done" hidden>Done</main>
|
||||
<script>document.querySelector('#start').onclick = async () => {
|
||||
const send = marker => fetch('/same', {method:'POST', headers:{
|
||||
'Content-Type':'application/json', 'Authorization':'Bearer transport-only', 'X-Marker': marker
|
||||
}, body:JSON.stringify({marker, password:'ordinary-selected-value', declared:'${networkFixtureSecret}'})}).then(r=>r.text());
|
||||
await Promise.all([send('slow'),send('fast'),fetch('/redirect').then(r=>r.text()),
|
||||
fetch('/binary').then(r=>r.arrayBuffer()),fetch('/compressed').then(r=>r.text()),
|
||||
fetch('/empty').then(r=>r.text()),
|
||||
fetch('/form', {method:'POST', body:new URLSearchParams({declared:'${networkFixtureSecret}'})}).then(r=>r.text())]);
|
||||
await fetch('/stream');
|
||||
document.querySelector('#done').hidden=false;
|
||||
};</script>`;
|
||||
|
||||
/** Start an isolated fixture with concurrent, redirected, binary, and streaming responses. */
|
||||
export async function startBrowserNetworkSite() {
|
||||
const counts = new Map();
|
||||
const timers = new Set();
|
||||
const server = createServer(async (request, response) => {
|
||||
const path = new URL(request.url, "http://fixture.test").pathname;
|
||||
counts.set(path, (counts.get(path) ?? 0) + 1);
|
||||
if (path === "/same") {
|
||||
const chunks = [];
|
||||
for await (const chunk of request) chunks.push(chunk);
|
||||
const body = JSON.parse(Buffer.concat(chunks).toString());
|
||||
const timer = setTimeout(
|
||||
() => {
|
||||
timers.delete(timer);
|
||||
response.writeHead(200, [
|
||||
"Content-Type",
|
||||
"application/json",
|
||||
"X-Repeat",
|
||||
"first",
|
||||
"X-Repeat",
|
||||
"second",
|
||||
"Set-Cookie",
|
||||
"fixture_session=transport-cookie; HttpOnly",
|
||||
"X-Declared",
|
||||
encodeURIComponent(networkFixtureSecret),
|
||||
]);
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
marker: body.marker,
|
||||
password: body.password,
|
||||
declared: body.declared,
|
||||
}),
|
||||
);
|
||||
},
|
||||
body.marker === "slow" ? 120 : 5,
|
||||
);
|
||||
timers.add(timer);
|
||||
} else if (path === "/redirect") {
|
||||
response.writeHead(302, { Location: "/landing" });
|
||||
response.end();
|
||||
} else if (path === "/landing") {
|
||||
response.end("redirect-target");
|
||||
} else if (path === "/form") {
|
||||
const chunks = [];
|
||||
for await (const chunk of request) chunks.push(chunk);
|
||||
response.end(
|
||||
new URLSearchParams(Buffer.concat(chunks).toString()).get("declared"),
|
||||
);
|
||||
} else if (path === "/binary") {
|
||||
response.writeHead(200, { "Content-Type": "application/octet-stream" });
|
||||
response.end(networkFixtureBinary);
|
||||
} else if (path === "/compressed") {
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "text/plain",
|
||||
"Content-Encoding": "gzip",
|
||||
});
|
||||
response.end(gzipSync(networkFixtureDecoded));
|
||||
} else if (path === "/empty") {
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
} else if (path === "/stream") {
|
||||
response.writeHead(200, { "Content-Type": "text/event-stream" });
|
||||
response.write("data: still-open\n\n");
|
||||
} else if (path === "/favicon.ico") {
|
||||
response.writeHead(204);
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
response.end(html);
|
||||
}
|
||||
});
|
||||
server.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
const address = server.address();
|
||||
if (typeof address !== "object" || address === null)
|
||||
throw new Error("Network fixture has no TCP address");
|
||||
return {
|
||||
origin: `http://127.0.0.1:${address.port}`,
|
||||
counts,
|
||||
async close() {
|
||||
for (const timer of timers) clearTimeout(timer);
|
||||
server.closeAllConnections();
|
||||
await new Promise((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { createServer } from "node:http";
|
||||
|
||||
/** Owned synthetic site keeps source bytes, repeated URLs and expected execution independent of REA. */
|
||||
export async function startBrowserRuntimeSite() {
|
||||
let origin;
|
||||
let evidenceRequests = 0;
|
||||
const sources = () => ({
|
||||
selected: `// 🔥 UTF-16 prefix\r\nfunction chosen() {\r\n history.pushState({}, '', '/same-document?caller=value#fragment');\r\n fetch('/evidence?marker=chosen');\r\n if (document.body.dataset.branch === 'unused') { fetch('/never'); }\r\n}\r\ndocument.querySelector('#run').addEventListener('click', chosen, { passive: true });\r\n//# sourceURL=${origin.replace("http://", "http://declared:label@")}/same.js\r\n`,
|
||||
other: `function untouched() { return 'other source'; }\r\ndocument.querySelector('#child').addEventListener('click', untouched);\r\n//# sourceURL=${origin.replace("http://", "http://declared:label@")}/same.js\r\n`,
|
||||
});
|
||||
const server = createServer((request, response) => {
|
||||
response.setHeader("content-type", "text/html; charset=utf-8");
|
||||
if (request.url?.startsWith("/evidence")) {
|
||||
evidenceRequests += 1;
|
||||
response.end("ok");
|
||||
return;
|
||||
}
|
||||
if (request.url === "/a.js" || request.url === "/b.js") {
|
||||
response.setHeader(
|
||||
"content-type",
|
||||
"application/javascript; charset=utf-8",
|
||||
);
|
||||
response.end(
|
||||
request.url === "/a.js" ? sources().selected : sources().other,
|
||||
);
|
||||
return;
|
||||
}
|
||||
response.end(
|
||||
'<!doctype html><meta charset="utf-8"><button id="run"><span id="child">Run</span></button><script src="/a.js"></script><script src="/b.js"></script>',
|
||||
);
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === "string")
|
||||
throw new Error("Runtime fixture did not bind TCP");
|
||||
origin = `http://127.0.0.1:${address.port}`;
|
||||
return {
|
||||
origin,
|
||||
sources: sources(),
|
||||
evidenceRequests: () => evidenceRequests,
|
||||
close: () =>
|
||||
new Promise((resolve, reject) =>
|
||||
server.close((error) =>
|
||||
error === undefined ? resolve() : reject(error),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createServer } from "node:http";
|
||||
|
||||
/** Independently authored modules and competing query variants for export verification. */
|
||||
export async function startBrowserScriptSite() {
|
||||
const assets = new Map([
|
||||
[
|
||||
"/app/main.js",
|
||||
"import { sourceMarker } from './lib/dep.js'; export const result = sourceMarker; document.body.dataset.ready = 'true';\n",
|
||||
],
|
||||
[
|
||||
"/app/lib/dep.js",
|
||||
"export const sourceMarker = 'independent-source-marker';\n",
|
||||
],
|
||||
["/variant.js?v=1", "globalThis.variantOne = 'query-variant-one';\n"],
|
||||
["/variant.js?v=2", "globalThis.variantTwo = 'query-variant-two';\n"],
|
||||
]);
|
||||
const counts = new Map();
|
||||
const server = createServer((request, response) => {
|
||||
counts.set(request.url, (counts.get(request.url) ?? 0) + 1);
|
||||
const source = assets.get(request.url);
|
||||
if (source !== undefined) {
|
||||
response.setHeader("content-type", "text/javascript; charset=utf-8");
|
||||
response.end(source);
|
||||
return;
|
||||
}
|
||||
response.setHeader("content-type", "text/html; charset=utf-8");
|
||||
response.end(
|
||||
'<!doctype html><html><body><script src="/variant.js?v=1"></script><script src="/variant.js?v=2"></script><script type="module" src="/app/main.js"></script></body></html>',
|
||||
);
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const address = server.address();
|
||||
if (address === null || typeof address === "string")
|
||||
throw new Error("Script fixture did not bind a port");
|
||||
return {
|
||||
origin: `http://127.0.0.1:${address.port}`,
|
||||
assets,
|
||||
counts,
|
||||
close: () =>
|
||||
new Promise((resolve, reject) =>
|
||||
server.close((cause) =>
|
||||
cause === undefined ? resolve() : reject(cause),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createServer } from "node:http";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { access } from "node:fs/promises";
|
||||
import { isAbsolute } from "node:path";
|
||||
|
||||
/** Generate a real compiler map and serve only source-owned fixture code. */
|
||||
export async function startBrowserSourceMapSite() {
|
||||
const command = process.env.REA_WEB_SOURCE_MAP_COMPILER;
|
||||
if (!command || !isAbsolute(command))
|
||||
throw new Error(
|
||||
"verify:browser:source-maps requires absolute REA_WEB_SOURCE_MAP_COMPILER pointing to isolated esbuild 0.25.10 lib/main.js",
|
||||
);
|
||||
await access(command);
|
||||
const compiler = await import(pathToFileURL(command).href);
|
||||
if (compiler.version !== "0.25.10")
|
||||
throw new Error(`Expected esbuild 0.25.10; observed ${compiler.version}`);
|
||||
const original =
|
||||
'// 🎋 UTF-16 and CRLF fixture\r\nfunction boot(): void {\r\n const marker: string = "source-map proof";\r\n document.body.dataset.ready = marker;\r\n}\r\nboot();\r\n';
|
||||
const compiled = await compiler
|
||||
.transform(original, {
|
||||
loader: "ts",
|
||||
sourcefile: "../src/fixture.ts?v=7#original",
|
||||
sourcemap: "external",
|
||||
minify: true,
|
||||
format: "iife",
|
||||
target: "es2022",
|
||||
})
|
||||
.finally(() => compiler.stop());
|
||||
const token = '"source-map proof"';
|
||||
const generatedOffset = compiled.code.indexOf(token);
|
||||
const originalOffset = original.indexOf(token);
|
||||
if (generatedOffset < 0 || originalOffset < 0)
|
||||
throw new Error("Compiler fixture marker missing");
|
||||
const position = textPosition(compiled.code, generatedOffset);
|
||||
const originalPosition = textPosition(original, originalOffset);
|
||||
let mapRequests = 0;
|
||||
const server = createServer((request, response) => {
|
||||
if (request.url === "/")
|
||||
return response
|
||||
.writeHead(200, { "Content-Type": "text/html" })
|
||||
.end('<body><script src="/app.js?build=7"></script></body>');
|
||||
if (request.url === "/app.js?build=7")
|
||||
return response
|
||||
.writeHead(200, { "Content-Type": "text/javascript" })
|
||||
.end(compiled.code);
|
||||
if (request.url?.startsWith("/maps/app.js.map")) {
|
||||
mapRequests += 1;
|
||||
return response
|
||||
.writeHead(200, { "Content-Type": "application/json" })
|
||||
.end(compiled.map);
|
||||
}
|
||||
response.writeHead(404).end("Unselected");
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string")
|
||||
throw new Error("Source map fixture has no TCP address");
|
||||
return {
|
||||
origin: `http://127.0.0.1:${address.port}`,
|
||||
original,
|
||||
generated: compiled.code,
|
||||
map: compiled.map,
|
||||
position,
|
||||
originalPosition,
|
||||
originalOffset,
|
||||
mapRequests: () => mapRequests,
|
||||
compiler: { id: "esbuild", version: compiler.version },
|
||||
close: () =>
|
||||
new Promise((resolve, reject) =>
|
||||
server.close((error) => (error ? reject(error) : resolve())),
|
||||
),
|
||||
};
|
||||
}
|
||||
const textPosition = (text, offset) => {
|
||||
const preceding = text.slice(0, offset).split(/\r\n?|\n|\u2028|\u2029/u);
|
||||
return { line: preceding.length, column: preceding.at(-1).length };
|
||||
};
|
||||
@@ -52,8 +52,12 @@ export async function startBrowserVerifierSite() {
|
||||
return;
|
||||
}
|
||||
response.setHeader("content-type", "text/html");
|
||||
response.setHeader("set-cookie", [
|
||||
"rea-root-cookie=root-fixture; Path=/; SameSite=Lax",
|
||||
"rea-app-cookie=app-fixture; Path=/app; SameSite=Lax",
|
||||
]);
|
||||
response.end(
|
||||
'<!doctype html><html><head><title>REA browser verifier</title></head><body><main><h1>Browser evidence</h1><button aria-label="ax-private-label-value">Verify</button></main><script type="module" src="/app.js?token=script-query-secret"></script></body></html>',
|
||||
'<!doctype html><html><head><title>REA browser verifier</title><base href="assets/"></head><body><main><h1>Browser evidence</h1><a href="guide">Guide</a><button aria-label="ax-private-label-value">Verify</button><form action=""></form><form action=" "></form><button formaction=""></button><input formaction=""></main><script type="module" src="/app.js?token=script-query-secret"></script></body></html>',
|
||||
);
|
||||
});
|
||||
const webSockets = new WebSocketServer({ noServer: true });
|
||||
|
||||
@@ -34,7 +34,7 @@ const ext4 =
|
||||
: [];
|
||||
for (const command of ext4) await exec(command, ["-V"]);
|
||||
const generated = await exec(process.env.REA_FIRMWARE_PYTHON ?? "python3", [
|
||||
fileURLToPath(new URL("./lib/generate-firmware-fixture.py", import.meta.url)),
|
||||
fileURLToPath(new URL("./generate.py", import.meta.url)),
|
||||
root,
|
||||
process.env.REA_FIRMWARE_CC ?? "cc",
|
||||
...ext4,
|
||||
File renamed without changes.
@@ -0,0 +1,100 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/** Compile source-owned fixtures using explicitly supplied, pinned toolchains. */
|
||||
export async function buildRecoveryFixtures(root, prefix) {
|
||||
if (!prefix)
|
||||
throw new Error(
|
||||
"verify:javascript:recovery requires REA_JAVASCRIPT_FIXTURE_TOOLS pointing to the isolated esbuild/webpack installation",
|
||||
);
|
||||
const require = createRequire(join(prefix, "package.json"));
|
||||
const esbuild = require("esbuild");
|
||||
const webpack = require("webpack");
|
||||
const webpackVersion = require("webpack/package.json").version;
|
||||
if (esbuild.version !== "0.25.10" || webpackVersion !== "5.101.3")
|
||||
throw new Error(
|
||||
"Fixture compilers must be esbuild 0.25.10 and webpack 5.101.3",
|
||||
);
|
||||
const source = fileURLToPath(new URL("./entry.mjs", import.meta.url));
|
||||
const webpackOutput = join(root, "webpack");
|
||||
await mkdir(webpackOutput);
|
||||
await new Promise((resolve, reject) => {
|
||||
const compiler = webpack({
|
||||
mode: "production",
|
||||
entry: source,
|
||||
parallelism: 1,
|
||||
optimization: { concatenateModules: false },
|
||||
output: { path: webpackOutput, filename: "bundle.js" },
|
||||
});
|
||||
compiler.run((error, stats) => {
|
||||
compiler.close((closeError) => {
|
||||
if (error || closeError) return reject(error ?? closeError);
|
||||
if (!stats || stats.hasErrors())
|
||||
return reject(new Error(stats?.toString() ?? "No webpack stats"));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
const esbuildPath = join(root, "esbuild.js");
|
||||
await esbuild.build({
|
||||
entryPoints: [source],
|
||||
outfile: esbuildPath,
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
minify: true,
|
||||
logLevel: "silent",
|
||||
});
|
||||
// One self-contained source fixture avoids unresolved imports in the oracle.
|
||||
const helperSource = await readFile(
|
||||
fileURLToPath(new URL("./helpers.mjs", import.meta.url)),
|
||||
"utf8",
|
||||
);
|
||||
const plain = await esbuild.transform(
|
||||
helperSource.replaceAll("export ", "") +
|
||||
"\nglobalThis.recoveryFixture = {greet, total};",
|
||||
{ minify: true },
|
||||
);
|
||||
const minifiedPath = join(root, "minified.js");
|
||||
await writeFile(minifiedPath, plain.code);
|
||||
if (plain.code.length === 0)
|
||||
throw new Error("Expected a real minifier output");
|
||||
return {
|
||||
files: [
|
||||
{
|
||||
name: "minified",
|
||||
path: minifiedPath,
|
||||
expectedFormat: "unknown",
|
||||
expectedModules: 1,
|
||||
},
|
||||
{
|
||||
name: "webpack",
|
||||
path: join(webpackOutput, "bundle.js"),
|
||||
expectedFormat: "webpack5",
|
||||
expectedModules: 2,
|
||||
},
|
||||
{
|
||||
name: "esbuild-scope-hoisted",
|
||||
path: esbuildPath,
|
||||
expectedFormat: "unknown",
|
||||
expectedModules: 1,
|
||||
},
|
||||
{
|
||||
name: "heuristic-aggressive",
|
||||
path: minifiedPath,
|
||||
expectedFormat: "unknown",
|
||||
expectedModules: 1,
|
||||
options: { extraction_mode: "heuristic", rewrite_level: "aggressive" },
|
||||
},
|
||||
{
|
||||
name: "inspection-minimal",
|
||||
path: join(webpackOutput, "bundle.js"),
|
||||
expectedFormat: "webpack5",
|
||||
expectedModules: 2,
|
||||
options: { extraction_mode: "inspection", rewrite_level: "minimal" },
|
||||
},
|
||||
],
|
||||
toolchains: { esbuild: esbuild.version, webpack: webpackVersion },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { greet, total } from "./helpers.mjs";
|
||||
|
||||
globalThis.recoveryFixture = { greet, total };
|
||||
@@ -0,0 +1,6 @@
|
||||
export function greet(name) {
|
||||
return `Hello ${name ?? "world"}`;
|
||||
}
|
||||
export function total(items) {
|
||||
return items.reduce((sum, value) => sum + value, 0);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -22,7 +22,7 @@ delete verifierEnvironment.REA_ILSPY_CMD_PATH;
|
||||
|
||||
const { stdout } = await exec(
|
||||
process.execPath,
|
||||
[join(root, "scripts/verify-managed-conformance.mjs")],
|
||||
[join(root, "scripts/verify/managed/conformance.mjs")],
|
||||
{
|
||||
cwd: root,
|
||||
env: verifierEnvironment,
|
||||
|
||||
@@ -6,10 +6,8 @@ import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import { McpServer } from "@modelcontextprotocol/server";
|
||||
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../dist/contracts/managedWorkflowToolContracts.js";
|
||||
import { ArtifactProvider } from "../dist/artifacts/ArtifactProvider.js";
|
||||
import { ManagedStaticProvider } from "../dist/dotnet/ManagedStaticProvider.js";
|
||||
import { NativeMacOSProvider } from "../dist/native/NativeMacOSProvider.js";
|
||||
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../dist/contracts/managed/managedWorkflowToolContracts.js";
|
||||
import { auxiliaryAnalysisProviderDeclarations } from "../dist/composition/auxiliaryAnalysisProviders.js";
|
||||
import { toolRegistrationOptions } from "../dist/server/toolRegistrationOptions.js";
|
||||
import { ensureGeneratedFile } from "./lib/generated-file.mjs";
|
||||
|
||||
@@ -88,14 +86,9 @@ async function sdkToolCatalog() {
|
||||
await Promise.allSettled([client.close(), server.close()]);
|
||||
}
|
||||
}
|
||||
const auxiliaryProviders = [
|
||||
new ArtifactProvider(CATALOG_PLATFORM),
|
||||
new NativeMacOSProvider(undefined, CATALOG_PLATFORM),
|
||||
new ManagedStaticProvider(),
|
||||
].map((provider) => ({
|
||||
identity: provider.identity(),
|
||||
capabilities: provider.capabilities(),
|
||||
}));
|
||||
const auxiliaryProviders = auxiliaryAnalysisProviderDeclarations(
|
||||
CATALOG_PLATFORM,
|
||||
).map(({ identity, capabilities }) => ({ identity, capabilities }));
|
||||
|
||||
const canonicalizeJson = (value) => {
|
||||
if (Array.isArray(value)) return value.map(canonicalizeJson);
|
||||
|
||||
@@ -20,11 +20,14 @@ import time
|
||||
# Keep this digest aligned with LinuxHopper.ts and rerun real-Hopper verification
|
||||
# before accepting another vendor build.
|
||||
SUPPORTED_HOPPER_SHA256 = {
|
||||
"0294ced141cc373468ee22d8343e7dac41980cb05a937994ca81c9f09afe7ded"
|
||||
"0294ced141cc373468ee22d8343e7dac41980cb05a937994ca81c9f09afe7ded",
|
||||
"1339f9e58377442b0c6fcb0dfc3cec20d593cc557408521fad9a00dbc6b8da13",
|
||||
}
|
||||
EXPECTED_SCREEN = (1280, 1024)
|
||||
EXPECTED_DIALOG = (189, 370, 901, 284)
|
||||
DEMO_CLICK = (305, 632)
|
||||
DIALOG_TITLES = {"Registration", "Registration — Hopper Disassembler"}
|
||||
HOPPER_WINDOW_TITLE = "Hopper Disassembler"
|
||||
DEMO_CLICK_LEFT = 116
|
||||
DEMO_CLICK_BOTTOM = 22
|
||||
PRIVATE_DISPLAY_UNAVAILABLE = 70
|
||||
X11_AUTHORIZATION_FAILED = 71
|
||||
UNSUPPORTED_HOPPER_BUILD = 72
|
||||
@@ -196,6 +199,47 @@ def windows(x11: ctypes.CDLL, display: int, root: int) -> list[tuple[int, int, i
|
||||
return found
|
||||
|
||||
|
||||
def window_title(x11: ctypes.CDLL, display: int, window: int) -> str | None:
|
||||
title = ctypes.c_char_p()
|
||||
if not x11.XFetchName(display, window, ctypes.byref(title)) or not title.value:
|
||||
return None
|
||||
try:
|
||||
return title.value.decode("utf-8", "replace")
|
||||
finally:
|
||||
x11.XFree(ctypes.cast(title, ctypes.c_void_p))
|
||||
|
||||
|
||||
def window_transient_for(
|
||||
x11: ctypes.CDLL, display: int, window: int
|
||||
) -> int | None:
|
||||
parent = ctypes.c_ulong()
|
||||
if not x11.XGetTransientForHint(display, window, ctypes.byref(parent)):
|
||||
return None
|
||||
return int(parent.value)
|
||||
|
||||
|
||||
def find_demo_dialog(
|
||||
x11: ctypes.CDLL,
|
||||
display: int,
|
||||
observed: list[tuple[int, int, int, int, int]],
|
||||
) -> tuple[int, int, int, int, int] | None:
|
||||
for candidate in observed:
|
||||
window = candidate[0]
|
||||
if window_title(x11, display, window) not in DIALOG_TITLES:
|
||||
continue
|
||||
parent = window_transient_for(x11, display, window)
|
||||
if parent is None:
|
||||
continue
|
||||
if window_title(x11, display, parent) == HOPPER_WINDOW_TITLE:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def demo_click(dialog: tuple[int, int, int, int, int]) -> tuple[int, int]:
|
||||
_window, x, y, _width, height = dialog
|
||||
return x + DEMO_CLICK_LEFT, y + height - DEMO_CLICK_BOTTOM
|
||||
|
||||
|
||||
def configure_x11(x11: ctypes.CDLL, xtst: ctypes.CDLL) -> None:
|
||||
window_pointer = ctypes.POINTER(ctypes.c_ulong)
|
||||
x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||
@@ -212,6 +256,18 @@ def configure_x11(x11: ctypes.CDLL, xtst: ctypes.CDLL) -> None:
|
||||
ctypes.POINTER(XWindowAttributes),
|
||||
]
|
||||
x11.XGetWindowAttributes.restype = ctypes.c_int
|
||||
x11.XFetchName.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
ctypes.POINTER(ctypes.c_char_p),
|
||||
]
|
||||
x11.XFetchName.restype = ctypes.c_int
|
||||
x11.XGetTransientForHint.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
]
|
||||
x11.XGetTransientForHint.restype = ctypes.c_int
|
||||
x11.XQueryTree.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
@@ -644,6 +700,7 @@ def run_launch(args: argparse.Namespace) -> int:
|
||||
|
||||
deadline = time.monotonic() + 10
|
||||
observed: list[tuple[int, int, int, int, int]] = []
|
||||
dialog: tuple[int, int, int, int, int] | None = None
|
||||
while time.monotonic() < deadline:
|
||||
if socket.exists():
|
||||
x11.XCloseDisplay(display)
|
||||
@@ -659,7 +716,8 @@ def run_launch(args: argparse.Namespace) -> int:
|
||||
"owned Hopper process exited",
|
||||
)
|
||||
observed = windows(x11, display, root)
|
||||
if any(window[1:] == EXPECTED_DIALOG for window in observed):
|
||||
dialog = find_demo_dialog(x11, display, observed)
|
||||
if dialog is not None:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
@@ -675,8 +733,10 @@ def run_launch(args: argparse.Namespace) -> int:
|
||||
"expected Hopper demo dialog not found",
|
||||
)
|
||||
|
||||
assert dialog is not None
|
||||
click_x, click_y = demo_click(dialog)
|
||||
input_results = (
|
||||
xtst.XTestFakeMotionEvent(display, -1, DEMO_CLICK[0], DEMO_CLICK[1], 0),
|
||||
xtst.XTestFakeMotionEvent(display, -1, click_x, click_y, 0),
|
||||
xtst.XTestFakeButtonEvent(display, 1, True, 0),
|
||||
xtst.XTestFakeButtonEvent(display, 1, False, 0),
|
||||
)
|
||||
@@ -695,9 +755,8 @@ def run_launch(args: argparse.Namespace) -> int:
|
||||
finally:
|
||||
if display is not None:
|
||||
x11.XCloseDisplay(display)
|
||||
if child is not None and child.poll() is None:
|
||||
child.terminate()
|
||||
child.wait()
|
||||
if child is not None:
|
||||
stop_process(child)
|
||||
stop_process(xvfb)
|
||||
capture.close()
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ const environment = () => ({
|
||||
|
||||
/** Execute the compiled CLI with production providers and no test adapters. */
|
||||
export async function artifactCli(command, target, arguments_ = []) {
|
||||
return (await artifactCliEvidence(command, target, arguments_))
|
||||
.normalized_result;
|
||||
}
|
||||
|
||||
/** Execute the compiled CLI and return its complete Evidence record. */
|
||||
export async function artifactCliEvidence(command, target, arguments_ = []) {
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
@@ -29,7 +35,7 @@ export async function artifactCli(command, target, arguments_ = []) {
|
||||
const evidence = JSON.parse(stdout);
|
||||
assert.equal(evidence.error, undefined, stdout);
|
||||
assert.ok(evidence.normalized_result, "CLI omitted normalized result");
|
||||
return evidence.normalized_result;
|
||||
return evidence;
|
||||
}
|
||||
|
||||
/** Run a real stdio MCP subprocess, owning and closing its transport. */
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { assertFormDestinations } from "./browser-verifier-assertions.mjs";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
|
||||
/** Verify the real fixture's form destinations through CLI and stdio MCP. */
|
||||
export async function verifyBrowserDomDestinations(
|
||||
attachment,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
documentUrl,
|
||||
) {
|
||||
const env = { ...process.env, REA_LOG_LEVEL: "silent" };
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-web-page",
|
||||
attachment.cdp_endpoint,
|
||||
attachment.target_id,
|
||||
"--observation-ms",
|
||||
"100",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 60000, maxBuffer: 32 * 1024 * 1024 },
|
||||
);
|
||||
assertFormDestinations(
|
||||
parseEvidence(JSON.parse(stdout)).normalized_result,
|
||||
documentUrl,
|
||||
);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "browser-dom-destinations", version: "1" });
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const result = await client.callTool({
|
||||
name: "inspect_web_page",
|
||||
arguments: { ...attachment, observation_ms: 100 },
|
||||
});
|
||||
assert.notEqual(result.isError, true, mcpTextValue(result));
|
||||
assertFormDestinations(
|
||||
parseEvidence(JSON.parse(mcpTextValue(result)).evidence)
|
||||
.normalized_result,
|
||||
documentUrl,
|
||||
);
|
||||
return { cli: true, stdio_mcp: true, current_document_destinations: 4 };
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { chromium } from "playwright-core";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { webModuleTraceResultSchema } from "../../dist/domain/webModuleTrace.js";
|
||||
import { NativeModuleResolver } from "../../dist/browser/modules/NativeModuleResolver.js";
|
||||
import { startBrowserModuleSite } from "../fixtures/browser-module-site.mjs";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
|
||||
/** Check native URL resolution against an independently loaded module graph through CLI/MCP. */
|
||||
export async function verifyBrowserModules(
|
||||
executable,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
) {
|
||||
const site = await startBrowserModuleSite();
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-browser-modules-"));
|
||||
const env = {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_BROWSER_EXECUTABLE: executable,
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
|
||||
};
|
||||
const cli = async (...args) => {
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[entrypoint, ...args, "--json"],
|
||||
{ env, timeout: 45000, maxBuffer: 16 * 1024 * 1024 },
|
||||
);
|
||||
return parseEvidence(JSON.parse(stdout));
|
||||
};
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "module-real-e2e", version: "1" });
|
||||
try {
|
||||
const oracle = await loadingOracle(executable, site);
|
||||
const scenario = {
|
||||
browser: { mode: "launch", executable_path: executable },
|
||||
start_url: { url: site.origin },
|
||||
actions: [
|
||||
{
|
||||
step_id: "ready",
|
||||
action: "wait_for",
|
||||
locator: { kind: "css", selector: '[data-ready="true"]' },
|
||||
state: "visible",
|
||||
timeout_ms: 10000,
|
||||
},
|
||||
],
|
||||
capture: { network: { response_body: true } },
|
||||
};
|
||||
const capture = await cli(
|
||||
"capture-browser-scenario",
|
||||
JSON.stringify(scenario),
|
||||
);
|
||||
const capturePath = join(root, "capture.json");
|
||||
await writeFile(capturePath, JSON.stringify(capture));
|
||||
const exported = await cli(
|
||||
"export-web-scripts",
|
||||
capturePath,
|
||||
join(root, "export"),
|
||||
);
|
||||
const manifest = exported.normalized_result;
|
||||
const scriptIndex = manifest.scripts.findIndex(
|
||||
(script) => script.url === `${site.origin}/scoped/main.js?build=2`,
|
||||
);
|
||||
assert.ok(
|
||||
scriptIndex >= 0,
|
||||
"Real capture did not retain main module response",
|
||||
);
|
||||
const mapPath = join(root, "import-map.json");
|
||||
await writeFile(mapPath, JSON.stringify(site.importMap));
|
||||
const input = {
|
||||
manifest_path: manifest.manifest.path,
|
||||
script_index: scriptIndex,
|
||||
importer_url: `${site.origin}/scoped/main.js?build=2#entry`,
|
||||
import_map: { path: mapPath, base_url: `${site.origin}/maps/` },
|
||||
};
|
||||
const before = [...site.requests];
|
||||
const cliEvidence = await cli(
|
||||
"trace-web-module-imports",
|
||||
input.manifest_path,
|
||||
String(scriptIndex),
|
||||
"--importer-url",
|
||||
input.importer_url,
|
||||
"--import-map-path",
|
||||
mapPath,
|
||||
"--import-map-base-url",
|
||||
input.import_map.base_url,
|
||||
);
|
||||
const cliResult = assertTrace(cliEvidence, oracle, site, scriptIndex);
|
||||
await client.connect(transport);
|
||||
const response = await client.callTool({
|
||||
name: "trace_web_module_imports",
|
||||
arguments: input,
|
||||
});
|
||||
assert.notEqual(response.isError, true, mcpTextValue(response));
|
||||
const mcpEvidence = parseEvidence(
|
||||
JSON.parse(mcpTextValue(response)).evidence,
|
||||
);
|
||||
const mcpResult = assertTrace(mcpEvidence, oracle, site, scriptIndex);
|
||||
assert.deepEqual(mcpResult, cliResult, "CLI and MCP module traces differ");
|
||||
assert.equal(
|
||||
cliEvidence.subject.digest.sha256,
|
||||
createHash("sha256").update(site.main).digest("hex"),
|
||||
);
|
||||
const mapBytes = await readFile(mapPath);
|
||||
assert.equal(
|
||||
cliResult.import_map.sha256,
|
||||
createHash("sha256").update(mapBytes).digest("hex"),
|
||||
);
|
||||
assert.deepEqual(
|
||||
site.requests,
|
||||
before,
|
||||
"Tracing refetched or executed a captured website dependency",
|
||||
);
|
||||
for (const surface of ["cli", "stdio_mcp"]) {
|
||||
const withoutMap =
|
||||
surface === "cli"
|
||||
? await cli(
|
||||
"trace-web-module-imports",
|
||||
input.manifest_path,
|
||||
String(scriptIndex),
|
||||
)
|
||||
: parseEvidence(
|
||||
JSON.parse(
|
||||
mcpTextValue(
|
||||
await client.callTool({
|
||||
name: "trace_web_module_imports",
|
||||
arguments: {
|
||||
manifest_path: input.manifest_path,
|
||||
script_index: scriptIndex,
|
||||
},
|
||||
}),
|
||||
),
|
||||
).evidence,
|
||||
);
|
||||
const result = webModuleTraceResultSchema.parse(
|
||||
withoutMap.normalized_result,
|
||||
);
|
||||
assert.equal(result.import_map, null);
|
||||
assert.equal(result.imports[0].resolution.state, "rejected");
|
||||
assert.equal(
|
||||
result.imports[2].resolution.url,
|
||||
`${site.origin}/query.js?v=1#one`,
|
||||
);
|
||||
}
|
||||
assert.deepEqual(
|
||||
site.requests,
|
||||
before,
|
||||
"Map-free tracing refetched an asset",
|
||||
);
|
||||
const warnings = await new NativeModuleResolver(env).resolve({
|
||||
importerUrl: input.importer_url,
|
||||
specifiers: ["alias"],
|
||||
importMap: {
|
||||
baseUrl: input.import_map.base_url,
|
||||
value: { imports: { alias: "./global.js", ignored: 42 } },
|
||||
},
|
||||
});
|
||||
assert.equal(warnings.ok, true, warnings.ok ? "" : warnings.error.message);
|
||||
assert.ok(
|
||||
warnings.value.diagnostics.length > 0,
|
||||
"Native import-map warnings were discarded",
|
||||
);
|
||||
assert.equal(
|
||||
warnings.value.resolutions[0].url,
|
||||
`${site.origin}/maps/global.js`,
|
||||
);
|
||||
const controller = new AbortController();
|
||||
let owned;
|
||||
const interrupted = await new NativeModuleResolver(env, async (options) => {
|
||||
owned = await chromium.launch(options);
|
||||
controller.abort();
|
||||
return owned;
|
||||
}).resolve(
|
||||
{
|
||||
importerUrl: input.importer_url,
|
||||
importMap: null,
|
||||
specifiers: ["./dep.js"],
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
assert.equal(
|
||||
interrupted.ok,
|
||||
false,
|
||||
"Cancellation returned successful native resolution",
|
||||
);
|
||||
assert.equal(interrupted.error._tag, "AnalysisCancelledError");
|
||||
assert.equal(
|
||||
owned.isConnected(),
|
||||
false,
|
||||
"Cancelled native resolver left its owned browser connected",
|
||||
);
|
||||
const syntheticImporter = "http://app.invalid/main.js?build=1#entry";
|
||||
const syntheticMapBase = "https://maps.invalid/maps/map.json?x=1&y=2#base";
|
||||
for (const useMap of [false, true])
|
||||
for (const surface of ["cli", "stdio_mcp"]) {
|
||||
let evidence;
|
||||
if (surface === "cli")
|
||||
evidence = await cli(
|
||||
"trace-web-module-imports",
|
||||
input.manifest_path,
|
||||
String(scriptIndex),
|
||||
"--importer-url",
|
||||
syntheticImporter,
|
||||
...(useMap
|
||||
? [
|
||||
"--import-map-path",
|
||||
mapPath,
|
||||
"--import-map-base-url",
|
||||
syntheticMapBase,
|
||||
]
|
||||
: []),
|
||||
);
|
||||
else {
|
||||
const response = await client.callTool({
|
||||
name: "trace_web_module_imports",
|
||||
arguments: {
|
||||
manifest_path: input.manifest_path,
|
||||
script_index: scriptIndex,
|
||||
importer_url: syntheticImporter,
|
||||
...(useMap
|
||||
? { import_map: { path: mapPath, base_url: syntheticMapBase } }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
assert.notEqual(response.isError, true, mcpTextValue(response));
|
||||
evidence = parseEvidence(JSON.parse(mcpTextValue(response)).evidence);
|
||||
}
|
||||
const result = webModuleTraceResultSchema.parse(
|
||||
evidence.normalized_result,
|
||||
);
|
||||
if (useMap)
|
||||
assert.deepEqual(result.imports[0].resolution, {
|
||||
state: "resolved",
|
||||
url: "https://maps.invalid/maps/global.js",
|
||||
});
|
||||
else assert.equal(result.imports[0].resolution.state, "rejected");
|
||||
assert.equal(
|
||||
result.imports[2].resolution.url,
|
||||
"http://app.invalid/query.js?v=1#one",
|
||||
);
|
||||
assert.equal(
|
||||
evidence.raw_result.native_report.importer_url,
|
||||
syntheticImporter,
|
||||
);
|
||||
assert.equal(
|
||||
result.source.script.url,
|
||||
`${site.origin}/scoped/main.js?build=2`,
|
||||
);
|
||||
}
|
||||
const escapedBase = await new NativeModuleResolver(env).resolve({
|
||||
importerUrl: syntheticImporter,
|
||||
importMap: {
|
||||
baseUrl: "https://maps.invalid/maps/a&b/map.json?x=1&y=2#base",
|
||||
value: { imports: { alias: "./alias.js" } },
|
||||
},
|
||||
specifiers: ["alias"],
|
||||
});
|
||||
assert.equal(
|
||||
escapedBase.ok,
|
||||
true,
|
||||
escapedBase.ok ? "" : escapedBase.error.message,
|
||||
);
|
||||
assert.deepEqual(escapedBase.value.resolutions[0], {
|
||||
state: "resolved",
|
||||
url: "https://maps.invalid/maps/a&b/alias.js",
|
||||
});
|
||||
assert.deepEqual(
|
||||
site.requests,
|
||||
before,
|
||||
"Synthetic-context tracing refetched or executed a website asset",
|
||||
);
|
||||
return {
|
||||
independent_http_importer_and_https_map_base: true,
|
||||
cancellation_cleanup: true,
|
||||
native_warnings_retained: true,
|
||||
cli: true,
|
||||
stdio_mcp: true,
|
||||
public_trace_cases: 8,
|
||||
literal_imports: 11,
|
||||
computed_unknowns: 1,
|
||||
native_loading_oracle: true,
|
||||
independent_module_instances: 3,
|
||||
import_map_scopes: true,
|
||||
null_and_backtracking_errors: true,
|
||||
query_fragment_identity: true,
|
||||
source_identity_verified: true,
|
||||
map_identity_verified: true,
|
||||
implicit_refetches: 0,
|
||||
};
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
await site.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const assertTrace = (evidence, oracle, site, scriptIndex) => {
|
||||
const result = webModuleTraceResultSchema.parse(evidence.normalized_result);
|
||||
assert.equal(result.source.script_index, scriptIndex);
|
||||
assert.equal(result.parser.state, "parsed");
|
||||
assert.equal(result.imports.length, 12);
|
||||
assert.deepEqual(
|
||||
result.imports.slice(0, 11).map((item) => item.resolution),
|
||||
oracle.resolutions,
|
||||
);
|
||||
assert.deepEqual(result.imports[11].resolution, {
|
||||
state: "unknown",
|
||||
reason: "computed-specifier",
|
||||
});
|
||||
assert.ok(result.imports.every((item) => item.execution === "unknown"));
|
||||
assert.equal(
|
||||
result.imports[6].captured_candidates.length,
|
||||
0,
|
||||
"An unloaded lazy URL became a captured source",
|
||||
);
|
||||
for (const index of [2, 3]) {
|
||||
assert.ok(result.imports[index].captured_candidates.length > 0);
|
||||
assert.ok(
|
||||
result.imports[index].captured_candidates.every(
|
||||
(candidate) => candidate.match === "response-url-without-fragment",
|
||||
),
|
||||
);
|
||||
}
|
||||
assert.equal(result.imports[4].resolution.url, `${site.origin}/query.js?v=2`);
|
||||
assert.equal(
|
||||
result.importer.url,
|
||||
`${site.origin}/scoped/main.js?build=2#entry`,
|
||||
);
|
||||
assert.equal(result.engine.id, "chromium-native-module-resolver");
|
||||
return result;
|
||||
};
|
||||
|
||||
const loadingOracle = async (executable, site) => {
|
||||
const browser = await chromium.launch({
|
||||
executablePath: executable,
|
||||
headless: true,
|
||||
timeout: 15000,
|
||||
args: [
|
||||
"--disable-dev-shm-usage",
|
||||
...(process.env.REA_BROWSER_NO_SANDBOX === "true"
|
||||
? ["--no-sandbox"]
|
||||
: []),
|
||||
],
|
||||
});
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
await page.goto(site.origin);
|
||||
await page.waitForFunction(
|
||||
() => globalThis.__moduleProof !== undefined,
|
||||
undefined,
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
const proof = await page.evaluate(() => globalThis.__moduleProof);
|
||||
assert.equal(proof.alias, "scoped");
|
||||
assert.equal(proof.pkg, "pkg");
|
||||
assert.equal(proof.same_module, true);
|
||||
assert.equal(proof.different_fragments, true);
|
||||
assert.equal(new Set(proof.instances).size, 3);
|
||||
assert.deepEqual(proof.urls, [
|
||||
`${site.origin}/query.js?v=1#one`,
|
||||
`${site.origin}/query.js?v=1#two`,
|
||||
`${site.origin}/query.js?v=2`,
|
||||
]);
|
||||
assert.ok(
|
||||
!site.requests.some(
|
||||
(url) =>
|
||||
url.includes("lazy") ||
|
||||
url.includes("escape") ||
|
||||
url.includes("blocked"),
|
||||
),
|
||||
);
|
||||
return proof;
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
networkFixtureBinary,
|
||||
networkFixtureDecoded,
|
||||
networkFixtureSecret,
|
||||
} from "../fixtures/browser-network-site.mjs";
|
||||
|
||||
const retained = (event) => {
|
||||
assert.equal(event?.kind, "network-content");
|
||||
assert.equal(event.body.state, "captured", JSON.stringify(event.body));
|
||||
return Buffer.from(event.body.content, "base64");
|
||||
};
|
||||
|
||||
/** Verify association against server-authored markers rather than provider-derived expectations. */
|
||||
export function assertBrowserNetworkEvidence(
|
||||
capture,
|
||||
origin,
|
||||
expectedCleanup = "terminated-owned-process",
|
||||
) {
|
||||
assert.ok(capture.steps.every((step) => step.status === "completed"));
|
||||
assert.equal(capture.browser.cleanup, expectedCleanup);
|
||||
assert.deepEqual(capture.scenario.network_content, {
|
||||
request_body: true,
|
||||
response_body: true,
|
||||
header_values: true,
|
||||
});
|
||||
assert.equal(capture.completeness.equality_eligible, false);
|
||||
assert.ok(!JSON.stringify(capture).includes(networkFixtureSecret));
|
||||
const events = capture.events.items;
|
||||
const content = events.filter((event) => event.kind === "network-content");
|
||||
const requestEvents = events.filter((event) => event.kind === "request");
|
||||
const sameUrlRequests = requestEvents.filter(
|
||||
(event) => event.url.url === `${origin}/same`,
|
||||
);
|
||||
assert.equal(sameUrlRequests.length, 2);
|
||||
assert.equal(
|
||||
new Set(sameUrlRequests.map((event) => event.transaction_id)).size,
|
||||
2,
|
||||
);
|
||||
const markers = [];
|
||||
for (const request of sameUrlRequests) {
|
||||
const requestContent = content.find(
|
||||
(event) =>
|
||||
event.phase === "request" &&
|
||||
event.transaction_id === request.transaction_id,
|
||||
);
|
||||
const responseContent = content.find(
|
||||
(event) =>
|
||||
event.phase === "response" &&
|
||||
event.transaction_id === request.transaction_id,
|
||||
);
|
||||
const input = JSON.parse(retained(requestContent).toString());
|
||||
const output = JSON.parse(retained(responseContent).toString());
|
||||
assert.equal(
|
||||
output.marker,
|
||||
input.marker,
|
||||
"Response was associated with a different same-URL request",
|
||||
);
|
||||
assert.equal(output.password, "ordinary-selected-value");
|
||||
assert.equal(input.declared, "[REDACTED:network_secret]");
|
||||
assert.equal(output.declared, input.declared);
|
||||
assert.equal(requestContent.body.redacted, true);
|
||||
assert.equal(responseContent.headers.state, "captured");
|
||||
assert.ok(
|
||||
responseContent.headers.items.some(
|
||||
(item) =>
|
||||
item.name.toLowerCase() === "x-declared" &&
|
||||
item.value === "[REDACTED:network_secret]" &&
|
||||
item.redacted,
|
||||
),
|
||||
);
|
||||
assert.deepEqual(
|
||||
responseContent.headers.items
|
||||
.filter((item) => item.name.toLowerCase() === "x-repeat")
|
||||
.map((item) => item.value),
|
||||
["first", "second"],
|
||||
);
|
||||
assert.ok(
|
||||
responseContent.headers.items.some(
|
||||
(item) =>
|
||||
item.name.toLowerCase() === "set-cookie" &&
|
||||
item.value === null &&
|
||||
item.redacted,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
requestContent.headers.items.some(
|
||||
(item) =>
|
||||
item.name.toLowerCase() === "authorization" &&
|
||||
item.value === null &&
|
||||
item.redacted,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
events.some(
|
||||
(event) =>
|
||||
event.kind === "request-finished" &&
|
||||
event.transaction_id === request.transaction_id,
|
||||
),
|
||||
);
|
||||
markers.push(input.marker);
|
||||
}
|
||||
assert.deepEqual(markers.sort(), ["fast", "slow"]);
|
||||
const responseFor = (path) => {
|
||||
const response = events.find(
|
||||
(event) =>
|
||||
event.kind === "response" && event.url.url === `${origin}${path}`,
|
||||
);
|
||||
assert.ok(response, `Missing response ${path}`);
|
||||
return content.find(
|
||||
(event) =>
|
||||
event.phase === "response" &&
|
||||
event.source_event_sequence === response.sequence,
|
||||
);
|
||||
};
|
||||
assert.deepEqual(retained(responseFor("/binary")), networkFixtureBinary);
|
||||
assert.deepEqual(retained(responseFor("/compressed")), networkFixtureDecoded);
|
||||
assert.equal(
|
||||
responseFor("/compressed").body.representation,
|
||||
"browser-decoded-response-bytes",
|
||||
);
|
||||
const formRequest = requestEvents.find(
|
||||
(event) => event.url.url === `${origin}/form`,
|
||||
);
|
||||
assert.ok(formRequest);
|
||||
const formContent = content.find(
|
||||
(event) =>
|
||||
event.phase === "request" &&
|
||||
event.transaction_id === formRequest.transaction_id,
|
||||
);
|
||||
assert.equal(
|
||||
retained(formContent).toString(),
|
||||
"declared=[REDACTED:network_secret]",
|
||||
);
|
||||
assert.equal(
|
||||
retained(responseFor("/form")).toString(),
|
||||
"[REDACTED:network_secret]",
|
||||
);
|
||||
assert.equal(responseFor("/stream").body.state, "unavailable");
|
||||
assert.equal(responseFor("/stream").body.reason, "response-unfinished");
|
||||
const first = requestEvents.find(
|
||||
(event) => event.url.url === `${origin}/redirect`,
|
||||
);
|
||||
const next = requestEvents.find(
|
||||
(event) => event.url.url === `${origin}/landing`,
|
||||
);
|
||||
assert.ok(first && next);
|
||||
assert.equal(next.redirected_from_transaction_id, first.transaction_id);
|
||||
return {
|
||||
same_url_markers: markers,
|
||||
binary_bytes: networkFixtureBinary.length,
|
||||
compressed_representation: "browser-decoded-response-bytes",
|
||||
unfinished_stream: "response-unfinished",
|
||||
empty_response_state: responseFor("/empty").body.state,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { browserScenarioSchema } from "../../dist/domain/browserScenario.js";
|
||||
import { browserScenarioCaptureSchema } from "../../dist/domain/browserScenarioCapture.js";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import {
|
||||
startBrowserNetworkSite,
|
||||
networkFixtureSecret,
|
||||
} from "../fixtures/browser-network-site.mjs";
|
||||
import { assertBrowserNetworkEvidence } from "./browser-network-assertions.mjs";
|
||||
import { scenarioProfiles } from "./browser-scenario-verifier.mjs";
|
||||
|
||||
/** Exercise selected network content through real CLI and stdio MCP, one browser at a time. */
|
||||
export async function verifyBrowserNetworkEvidence(
|
||||
executable,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
attachment,
|
||||
) {
|
||||
const site = await startBrowserNetworkSite();
|
||||
const profilesBefore = await scenarioProfiles();
|
||||
try {
|
||||
const scenario = browserScenarioSchema.parse({
|
||||
browser: { mode: "launch", executable_path: executable },
|
||||
start_url: { url: site.origin },
|
||||
actions: [
|
||||
{
|
||||
step_id: "run",
|
||||
action: "click",
|
||||
locator: { kind: "css", selector: "#start" },
|
||||
timeout_ms: 10_000,
|
||||
},
|
||||
{
|
||||
step_id: "ready",
|
||||
action: "wait_for",
|
||||
locator: { kind: "css", selector: "#done" },
|
||||
state: "visible",
|
||||
timeout_ms: 10_000,
|
||||
},
|
||||
],
|
||||
secrets: [
|
||||
{
|
||||
secret_id: "network_secret",
|
||||
environment_variable: "REA_NETWORK_FIXTURE_SECRET",
|
||||
},
|
||||
],
|
||||
capture: {
|
||||
at_end: ["url"],
|
||||
network: {
|
||||
request_body: true,
|
||||
response_body: true,
|
||||
header_values: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const env = {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
REA_NETWORK_FIXTURE_SECRET: networkFixtureSecret,
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
|
||||
};
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"capture-browser-scenario",
|
||||
JSON.stringify(scenario),
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 60_000, maxBuffer: 16 * 1024 * 1024 },
|
||||
);
|
||||
const cli = browserScenarioCaptureSchema.parse(
|
||||
parseEvidence(JSON.parse(stdout)).normalized_result,
|
||||
);
|
||||
const cliProof = assertBrowserNetworkEvidence(cli, site.origin);
|
||||
// The oracle must reject a deliberately incorrect same-URL association.
|
||||
const swapped = structuredClone(cli);
|
||||
const responses = swapped.events.items.filter(
|
||||
(event) =>
|
||||
event.kind === "network-content" &&
|
||||
event.phase === "response" &&
|
||||
event.body.state === "captured" &&
|
||||
Buffer.from(event.body.content, "base64")
|
||||
.toString()
|
||||
.includes('"marker":'),
|
||||
);
|
||||
assert.equal(responses.length, 2);
|
||||
[responses[0].transaction_id, responses[1].transaction_id] = [
|
||||
responses[1].transaction_id,
|
||||
responses[0].transaction_id,
|
||||
];
|
||||
assert.throws(
|
||||
() => assertBrowserNetworkEvidence(swapped, site.origin),
|
||||
/associated with a different same-URL request/,
|
||||
);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "network-real-e2e", version: "1" });
|
||||
let mcpProof;
|
||||
try {
|
||||
await client.connect(transport);
|
||||
const result = await client.callTool(
|
||||
{ name: "capture_browser_scenario", arguments: scenario },
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
assert.notEqual(result.isError, true, JSON.stringify(result));
|
||||
const evidence = parseEvidence({
|
||||
...result.structuredContent?.evidence,
|
||||
normalized_result: result.structuredContent?.result,
|
||||
});
|
||||
mcpProof = assertBrowserNetworkEvidence(
|
||||
browserScenarioCaptureSchema.parse(evidence.normalized_result),
|
||||
site.origin,
|
||||
);
|
||||
if (attachment !== undefined) {
|
||||
const connectedScenario = browserScenarioSchema.parse({
|
||||
...scenario,
|
||||
browser: { mode: "connect", ...attachment },
|
||||
});
|
||||
const connected = await client.callTool(
|
||||
{ name: "capture_browser_scenario", arguments: connectedScenario },
|
||||
{ timeout: 60_000 },
|
||||
);
|
||||
assert.notEqual(connected.isError, true, JSON.stringify(connected));
|
||||
const connectedEvidence = parseEvidence({
|
||||
...connected.structuredContent?.evidence,
|
||||
normalized_result: connected.structuredContent?.result,
|
||||
});
|
||||
assert.deepEqual(
|
||||
assertBrowserNetworkEvidence(
|
||||
browserScenarioCaptureSchema.parse(
|
||||
connectedEvidence.normalized_result,
|
||||
),
|
||||
site.origin,
|
||||
"disconnected-external",
|
||||
),
|
||||
cliProof,
|
||||
);
|
||||
assert.ok(
|
||||
(await fetch(`${attachment.cdp_endpoint}/json/version`)).ok,
|
||||
"Network capture closed the external browser",
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
await client.close();
|
||||
} finally {
|
||||
await transport.close();
|
||||
}
|
||||
}
|
||||
assert.deepEqual(mcpProof, cliProof);
|
||||
for (const path of [
|
||||
"/redirect",
|
||||
"/landing",
|
||||
"/binary",
|
||||
"/compressed",
|
||||
"/stream",
|
||||
"/empty",
|
||||
"/form",
|
||||
])
|
||||
assert.equal(
|
||||
site.counts.get(path),
|
||||
attachment === undefined ? 2 : 3,
|
||||
`Unexpected implicit refetch of ${path}`,
|
||||
);
|
||||
assert.equal(site.counts.get("/same"), attachment === undefined ? 4 : 6);
|
||||
const profilesAfter = await scenarioProfiles();
|
||||
assert.ok(
|
||||
[...profilesAfter].every((profile) => profilesBefore.has(profile)),
|
||||
"A scenario profile survived cleanup",
|
||||
);
|
||||
return {
|
||||
...cliProof,
|
||||
cli: true,
|
||||
mcp: true,
|
||||
association_fault_rejected: true,
|
||||
implicit_refetches: 0,
|
||||
profile_cleanup: true,
|
||||
connected_network_verified: attachment !== undefined,
|
||||
};
|
||||
} finally {
|
||||
await site.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawn, execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { once } from "node:events";
|
||||
import { access } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { CdpBrowserProvider } from "../../dist/browser/CdpBrowserProvider.js";
|
||||
import { CdpConnection } from "../../dist/browser/CdpConnection.js";
|
||||
import { waitForBrowserDevtoolsPort } from "../../dist/browser/BrowserProcessStartup.js";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { webExecutionSchema } from "../../dist/domain/webExecution.js";
|
||||
import { webEventListenersSchema } from "../../dist/domain/webEventListeners.js";
|
||||
import { startBrowserRuntimeSite } from "../fixtures/browser-runtime-site.mjs";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
import {
|
||||
startRuntimeFixtureBrowser,
|
||||
closeRuntimeFixtureResources,
|
||||
} from "./browser-runtime-fixture-lifecycle.mjs";
|
||||
import { verifyRuntimeTargetTermination } from "./browser-runtime-termination-e2e.mjs";
|
||||
import { verifyRuntimeInvalidSelectors } from "./browser-runtime-selector-e2e.mjs";
|
||||
import {
|
||||
createRuntimeIsolatedWorld,
|
||||
triggerRuntimeFixtureAction,
|
||||
isolatedFixtureUrl,
|
||||
} from "./browser-runtime-world-e2e.mjs";
|
||||
|
||||
/** Exercise native browser runtime attribution through public CLI and stdio MCP, including arming. */
|
||||
export async function verifyBrowserRuntime(
|
||||
executable,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
) {
|
||||
await access(executable);
|
||||
const site = await startBrowserRuntimeSite();
|
||||
let fixture;
|
||||
let primaryError;
|
||||
let stderr = "";
|
||||
let action;
|
||||
let client;
|
||||
try {
|
||||
fixture = await startRuntimeFixtureBrowser(executable, site.origin);
|
||||
const { browser, profile } = fixture;
|
||||
browser.stderr.on("data", (chunk) => {
|
||||
if (stderr.length < 65536) stderr += chunk;
|
||||
});
|
||||
const port = await waitForBrowserDevtoolsPort({
|
||||
child: browser,
|
||||
executable,
|
||||
activePortPath: join(profile, "DevToolsActivePort"),
|
||||
stderr: () => stderr,
|
||||
timeoutMs: 20_000,
|
||||
});
|
||||
const endpoint = `http://127.0.0.1:${port}`;
|
||||
const targets = await new CdpBrowserProvider().listTargets({
|
||||
cdp_endpoint: endpoint,
|
||||
allowed_origins: [site.origin],
|
||||
});
|
||||
if (!targets.ok) throw targets.error;
|
||||
const target = targets.value.targets[0];
|
||||
assert.ok(target, "owned fixture page target missing");
|
||||
const pages = await (await fetch(`${endpoint}/json/list`)).json();
|
||||
const page = pages.find((candidate) => candidate.id === target.target_id);
|
||||
action = await CdpConnection.connect(
|
||||
page.webSocketDebuggerUrl,
|
||||
"inspect_web_page",
|
||||
);
|
||||
await ready(action);
|
||||
let isolatedContext = await createRuntimeIsolatedWorld(action);
|
||||
const env = {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
|
||||
};
|
||||
const input = { cdp_endpoint: endpoint, target_id: target.target_id };
|
||||
const cliListener = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-web-event-listeners",
|
||||
endpoint,
|
||||
target.target_id,
|
||||
"#run",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 40_000, maxBuffer: 64 * 1024 * 1024 },
|
||||
);
|
||||
const cliListenerProof = assertListeners(
|
||||
parseEvidence(JSON.parse(cliListener.stdout)),
|
||||
site,
|
||||
);
|
||||
const cliExecution = await executionCli(
|
||||
entrypoint,
|
||||
input,
|
||||
env,
|
||||
action,
|
||||
isolatedContext,
|
||||
);
|
||||
const cliProof = assertExecution(
|
||||
parseEvidence(JSON.parse(cliExecution)),
|
||||
site,
|
||||
);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
client = new Client({ name: "web-runtime-real-e2e", version: "1" });
|
||||
await client.connect(transport);
|
||||
const invalidSelectors = await verifyRuntimeInvalidSelectors(
|
||||
client,
|
||||
entrypoint,
|
||||
input,
|
||||
env,
|
||||
);
|
||||
const listenerResponse = await client.callTool({
|
||||
name: "inspect_web_event_listeners",
|
||||
arguments: { ...input, selector: "#run" },
|
||||
});
|
||||
assert.notEqual(
|
||||
listenerResponse.isError,
|
||||
true,
|
||||
mcpTextValue(listenerResponse),
|
||||
);
|
||||
const mcpListenerProof = assertListeners(
|
||||
parseEvidence(JSON.parse(mcpTextValue(listenerResponse)).evidence),
|
||||
site,
|
||||
);
|
||||
const mcpProof = await executionMcp(
|
||||
client,
|
||||
input,
|
||||
action,
|
||||
site,
|
||||
isolatedContext,
|
||||
false,
|
||||
);
|
||||
assert.equal(cliProof.selected_sha256, mcpProof.selected_sha256);
|
||||
assert.equal(cliListenerProof.sha256, mcpListenerProof.sha256);
|
||||
await reloadFixture(action);
|
||||
isolatedContext = await createRuntimeIsolatedWorld(action);
|
||||
const freshMcpProof = await executionMcp(
|
||||
client,
|
||||
input,
|
||||
action,
|
||||
site,
|
||||
isolatedContext,
|
||||
);
|
||||
assert.equal(site.evidenceRequests(), 3);
|
||||
const termination = await verifyRuntimeTargetTermination(
|
||||
client,
|
||||
input,
|
||||
site.origin,
|
||||
);
|
||||
const stillOpen = await action.send("Runtime.evaluate", {
|
||||
expression: "Boolean(document.querySelector('#run'))",
|
||||
returnByValue: true,
|
||||
});
|
||||
assert.equal(
|
||||
stillOpen.result.value,
|
||||
true,
|
||||
"REA must preserve the externally owned page",
|
||||
);
|
||||
const location = await action.send("Runtime.evaluate", {
|
||||
expression: "location.href",
|
||||
returnByValue: true,
|
||||
});
|
||||
assert.equal(
|
||||
location.result.value,
|
||||
`${site.origin}/same-document?caller=value#fragment`,
|
||||
);
|
||||
return {
|
||||
product: target,
|
||||
cli: cliProof,
|
||||
mcp: mcpProof,
|
||||
listener: cliListenerProof,
|
||||
public_cases: 8,
|
||||
invalid_selectors: invalidSelectors,
|
||||
termination,
|
||||
fresh_mcp: freshMcpProof,
|
||||
page_remained_open: true,
|
||||
};
|
||||
} catch (cause) {
|
||||
primaryError = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeRuntimeFixtureResources(
|
||||
[
|
||||
() => client?.close(),
|
||||
() => action?.close(),
|
||||
() => fixture?.close(),
|
||||
() => site.close(),
|
||||
],
|
||||
primaryError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function executionMcp(
|
||||
client,
|
||||
input,
|
||||
action,
|
||||
site,
|
||||
isolatedContext,
|
||||
requireBlock = true,
|
||||
) {
|
||||
let actionPromise;
|
||||
const response = await client.callTool(
|
||||
{
|
||||
name: "observe_web_execution",
|
||||
arguments: { ...input, observation_ms: 300 },
|
||||
},
|
||||
{
|
||||
timeout: 40_000,
|
||||
onprogress: (notification) => {
|
||||
if (
|
||||
actionPromise === undefined &&
|
||||
notification.message?.includes(
|
||||
"browser_execution: Browser execution observation armed",
|
||||
)
|
||||
) {
|
||||
actionPromise = triggerRuntimeFixtureAction(action, isolatedContext);
|
||||
void actionPromise.catch(() => undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.ok(actionPromise, "MCP did not emit an actual armed notification");
|
||||
await actionPromise;
|
||||
assert.notEqual(response.isError, true, mcpTextValue(response));
|
||||
return assertExecution(
|
||||
parseEvidence(JSON.parse(mcpTextValue(response)).evidence),
|
||||
site,
|
||||
requireBlock,
|
||||
);
|
||||
}
|
||||
|
||||
async function ready(connection) {
|
||||
for (let attempt = 0; attempt < 100; attempt += 1) {
|
||||
const result = await connection.send("Runtime.evaluate", {
|
||||
expression:
|
||||
"typeof chosen === 'function' && typeof untouched === 'function'",
|
||||
returnByValue: true,
|
||||
});
|
||||
if (result.result.value === true) return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
throw new Error("Runtime fixture sources did not load");
|
||||
}
|
||||
|
||||
async function executionCli(entrypoint, input, env, action, isolatedContext) {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"observe-web-execution",
|
||||
input.cdp_endpoint,
|
||||
input.target_id,
|
||||
"--observation-ms",
|
||||
"300",
|
||||
"--json",
|
||||
],
|
||||
{ env, stdio: ["ignore", "pipe", "pipe"] },
|
||||
);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let actionPromise;
|
||||
const timeout = setTimeout(() => child.kill("SIGTERM"), 40_000);
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
if (actionPromise === undefined && stderr.includes('"completed":1')) {
|
||||
actionPromise = triggerRuntimeFixtureAction(action, isolatedContext);
|
||||
void actionPromise.catch(() => undefined);
|
||||
}
|
||||
});
|
||||
try {
|
||||
const [code] = await once(child, "close");
|
||||
assert.equal(code, 0, stderr);
|
||||
assert.ok(actionPromise, "CLI did not report an actual armed point");
|
||||
await actionPromise;
|
||||
return stdout;
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
if (child.exitCode === null && child.signalCode === null)
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
}
|
||||
|
||||
function sourceByText(result, text) {
|
||||
const source = result.sources.find(
|
||||
(item) => item.source.state === "captured" && item.source.text === text,
|
||||
);
|
||||
assert.ok(source, "Exact independently known source text must be retained");
|
||||
assert.equal(
|
||||
source.source.sha256,
|
||||
createHash("sha256").update(text).digest("hex"),
|
||||
);
|
||||
assert.equal(source.source.utf16_units, text.length);
|
||||
assert.equal(source.source.utf8_bytes, Buffer.byteLength(text));
|
||||
return source;
|
||||
}
|
||||
function assertListeners(evidence, site) {
|
||||
const result = webEventListenersSchema.parse(evidence.normalized_result);
|
||||
assert.equal(
|
||||
result.listeners.length,
|
||||
1,
|
||||
"The selected node's child listener must not be attributed to the selected node",
|
||||
);
|
||||
const listener = result.listeners.find((item) => item.type === "click");
|
||||
assert.ok(listener);
|
||||
assert.equal(listener.passive, true);
|
||||
const source = sourceByText(result, site.sources.selected);
|
||||
assert.equal(listener.location.script_id, source.script_id);
|
||||
assert.equal(listener.location.source_association, "script_id");
|
||||
assert.equal(listener.execution, "unknown");
|
||||
return {
|
||||
sha256: source.source.sha256,
|
||||
line: listener.location.line_number,
|
||||
column: listener.location.column_number,
|
||||
};
|
||||
}
|
||||
function assertExecution(evidence, site, requireBlock = true) {
|
||||
const result = webExecutionSchema.parse(evidence.normalized_result);
|
||||
assert.ok(
|
||||
result.coverage.scripts.every(
|
||||
(item) => item.reported_url !== isolatedFixtureUrl,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
result.sources.every(
|
||||
(item) =>
|
||||
item.url !== isolatedFixtureUrl || item.source.state !== "captured",
|
||||
),
|
||||
);
|
||||
const selected = sourceByText(result, site.sources.selected);
|
||||
const other = sourceByText(result, site.sources.other);
|
||||
assert.equal(selected.url, other.url);
|
||||
assert.equal(selected.has_source_url, true);
|
||||
assert.equal(
|
||||
selected.url,
|
||||
`${site.origin.replace("http://", "http://declared:label@")}/same.js`,
|
||||
);
|
||||
assert.notEqual(selected.script_id, other.script_id);
|
||||
assert.ok(
|
||||
selected.source.utf8_bytes > selected.source.utf16_units,
|
||||
"Unicode oracle must distinguish UTF-8 bytes from UTF-16 units",
|
||||
);
|
||||
const coverage = result.coverage.scripts.find(
|
||||
(script) => script.script_id === selected.script_id,
|
||||
);
|
||||
const fn = coverage?.functions.find((item) => item.name === "chosen");
|
||||
assert.ok(
|
||||
fn,
|
||||
"Externally invoked callback must have precise function evidence",
|
||||
);
|
||||
assert.ok(fn.ranges.some((range) => range.count === 1));
|
||||
if (requireBlock)
|
||||
assert.equal(
|
||||
fn.is_block_coverage,
|
||||
true,
|
||||
"Fresh fixture function must supply block granularity",
|
||||
);
|
||||
if (fn.is_block_coverage)
|
||||
assert.ok(
|
||||
fn.ranges.some((range) => range.count === 0),
|
||||
`Unexecuted branch must remain zero within block coverage: ${JSON.stringify(fn)}`,
|
||||
);
|
||||
assert.equal(
|
||||
fn.ranges[0].start_offset,
|
||||
site.sources.selected.indexOf("function chosen"),
|
||||
"Coverage offsets must index the retained UTF-16 source, including the astral prefix",
|
||||
);
|
||||
for (const range of fn.ranges) {
|
||||
assert.equal(range.source_bounds, "verified");
|
||||
assert.ok(range.end_offset <= selected.source.utf16_units);
|
||||
}
|
||||
const request = result.requests.find((item) =>
|
||||
item.url.endsWith("/evidence?marker=chosen"),
|
||||
);
|
||||
assert.ok(request, "The selected request initiator must be retained");
|
||||
assert.ok(
|
||||
request.callsites.some(
|
||||
(site) =>
|
||||
site.script_id === selected.script_id &&
|
||||
site.source_association === "script_id",
|
||||
),
|
||||
);
|
||||
assert.equal(request.causal_attribution, "unknown");
|
||||
assert.ok(
|
||||
request.callsites.some(
|
||||
(site) =>
|
||||
site.script_id === selected.script_id && site.url === selected.url,
|
||||
),
|
||||
);
|
||||
assert.equal(result.script_inventory.coverage_absence, "unknown");
|
||||
assert.equal(result.instrumentation.cleanup, "confirmed");
|
||||
return {
|
||||
selected_sha256: selected.source.sha256,
|
||||
other_sha256: other.source.sha256,
|
||||
zero_branch: fn.is_block_coverage
|
||||
? "observed-zero"
|
||||
: "unknown-function-only",
|
||||
granularity: fn.is_block_coverage ? "block" : "function",
|
||||
repeated_url_identity: true,
|
||||
same_document_navigation: true,
|
||||
declared_source_url_preserved: true,
|
||||
request_script_id: selected.script_id,
|
||||
isolated_execution_proved_and_excluded: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function reloadFixture(connection) {
|
||||
await connection.send("Page.enable");
|
||||
let dispose;
|
||||
const committed = new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(
|
||||
() => reject(new Error("Fixture reload did not commit")),
|
||||
5000,
|
||||
);
|
||||
const remove = connection.onEvent((event) => {
|
||||
if (
|
||||
event.method === "Page.frameNavigated" &&
|
||||
!event.params.frame?.parentId
|
||||
)
|
||||
resolve();
|
||||
});
|
||||
dispose = () => {
|
||||
clearTimeout(timer);
|
||||
remove();
|
||||
};
|
||||
});
|
||||
void committed.catch(() => undefined);
|
||||
try {
|
||||
await connection.send("Page.reload", { ignoreCache: true });
|
||||
await committed;
|
||||
await ready(connection);
|
||||
} finally {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { once } from "node:events";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { spawnOwnedProviderProcess } from "../../dist/process/ProviderProcess.js";
|
||||
import { createSystemProcessOwnershipHost } from "../../dist/process/ProcessOwnershipObservation.js";
|
||||
import { liveProcesses } from "../../dist/process/ProcessOwnershipProcessTree.js";
|
||||
import { CdpConnection } from "../../dist/browser/CdpConnection.js";
|
||||
|
||||
/** Launch only the synthetic fixture browser in its own verified process group. */
|
||||
export async function startRuntimeFixtureBrowser(executable, origin) {
|
||||
const profile = await mkdtemp(join(tmpdir(), "rea-real-web-runtime-"));
|
||||
let owned;
|
||||
try {
|
||||
owned = await spawnOwnedProviderProcess({
|
||||
command: executable,
|
||||
arguments: [
|
||||
"--headless=new",
|
||||
...(process.env.REA_BROWSER_NO_SANDBOX === "true"
|
||||
? ["--no-sandbox"]
|
||||
: []),
|
||||
"--remote-debugging-address=127.0.0.1",
|
||||
"--remote-debugging-port=0",
|
||||
`--user-data-dir=${profile}`,
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
"--disable-background-networking",
|
||||
"--disable-component-update",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-sync",
|
||||
origin,
|
||||
],
|
||||
runId: randomUUID(),
|
||||
// Installed Chrome launch scripts exec another binary; do not require an argv prefix.
|
||||
expectedCommand: null,
|
||||
});
|
||||
owned.process.stdout?.resume();
|
||||
return {
|
||||
browser: owned.process,
|
||||
profile,
|
||||
close: async () => {
|
||||
await stopOwnedBrowser(owned, profile);
|
||||
await rm(profile, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 5,
|
||||
retryDelay: 100,
|
||||
});
|
||||
},
|
||||
};
|
||||
} catch (cause) {
|
||||
if (owned !== undefined) await stopOwnedBrowser(owned, profile);
|
||||
await rm(profile, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
maxRetries: 5,
|
||||
retryDelay: 100,
|
||||
});
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopOwnedBrowser(owned, profile) {
|
||||
const child = owned.process;
|
||||
const exited =
|
||||
child.exitCode === null && child.signalCode === null
|
||||
? once(child, "exit")
|
||||
: Promise.resolve();
|
||||
void exited.catch(() => undefined);
|
||||
let connection;
|
||||
let timer;
|
||||
const escalation = setTimeout(() => {
|
||||
if (child.exitCode === null && child.signalCode === null)
|
||||
child.kill("SIGKILL");
|
||||
}, 2_000);
|
||||
try {
|
||||
if (child.exitCode === null && child.signalCode === null) {
|
||||
try {
|
||||
const [port, browserPath] = (
|
||||
await readFile(join(profile, "DevToolsActivePort"), "utf8")
|
||||
)
|
||||
.trim()
|
||||
.split("\n");
|
||||
assert.match(port, /^\d+$/);
|
||||
assert.match(browserPath, /^\/devtools\/browser\/[^\s]+$/);
|
||||
const signal = AbortSignal.timeout(2_000);
|
||||
connection = await CdpConnection.connect(
|
||||
`ws://127.0.0.1:${port}${browserPath}`,
|
||||
"inspect_web_page",
|
||||
signal,
|
||||
);
|
||||
// This browser belongs to the verifier; public REA operations never close it.
|
||||
await connection.send("Browser.close", {}, undefined, signal);
|
||||
} catch {
|
||||
if (child.exitCode === null && child.signalCode === null)
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
}
|
||||
if (owned.cleanup !== undefined) {
|
||||
const result = await owned.cleanup();
|
||||
assert.equal(result.cleaned, true, JSON.stringify(result));
|
||||
}
|
||||
await Promise.race([
|
||||
exited,
|
||||
new Promise((_, reject) => {
|
||||
timer = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error("Owned fixture browser did not exit after shutdown"),
|
||||
),
|
||||
5_000,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(escalation);
|
||||
clearTimeout(timer);
|
||||
await connection?.close();
|
||||
}
|
||||
if (process.platform === "win32") return;
|
||||
const host = createSystemProcessOwnershipHost();
|
||||
const deadline = Date.now() + 5_000;
|
||||
while (true) {
|
||||
const members = liveProcesses(await host.listProcesses()).filter(
|
||||
(member) => member.processGroupId === owned.ownership.processGroupId,
|
||||
);
|
||||
if (members.length === 0) return;
|
||||
if (Date.now() >= deadline)
|
||||
throw new Error(
|
||||
`Owned fixture group remains: ${JSON.stringify(members.map(({ pid }) => pid))}`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempt every owned cleanup and retain the original verification failure if cleanup also fails. */
|
||||
export async function closeRuntimeFixtureResources(tasks, primaryError) {
|
||||
const failures = [];
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
await task();
|
||||
} catch (cause) {
|
||||
failures.push(cause);
|
||||
}
|
||||
}
|
||||
if (failures.length > 0)
|
||||
throw new AggregateError(
|
||||
primaryError === undefined ? failures : [primaryError, ...failures],
|
||||
"Runtime fixture cleanup failed",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
|
||||
/** Verify native malformed-selector classification through the public CLI and MCP. */
|
||||
export async function verifyRuntimeInvalidSelectors(
|
||||
client,
|
||||
entrypoint,
|
||||
input,
|
||||
env,
|
||||
) {
|
||||
const cli = await new Promise((resolve) => {
|
||||
execFile(
|
||||
process.execPath,
|
||||
[
|
||||
entrypoint,
|
||||
"inspect-web-event-listeners",
|
||||
input.cdp_endpoint,
|
||||
input.target_id,
|
||||
"[",
|
||||
"--json",
|
||||
],
|
||||
{ env, timeout: 40_000, maxBuffer: 64 * 1024 * 1024 },
|
||||
(error, stdout, stderr) => resolve({ error, stdout, stderr }),
|
||||
);
|
||||
});
|
||||
assert.ok(cli.error, "Invalid native CSS must fail in the CLI");
|
||||
assert.equal(typeof cli.error.code, "number", cli.stderr);
|
||||
assertInputFailure(JSON.parse(cli.stdout), input.target_id);
|
||||
const response = await client.callTool({
|
||||
name: "inspect_web_event_listeners",
|
||||
arguments: { ...input, selector: "[" },
|
||||
});
|
||||
assert.equal(response.isError, true, mcpTextValue(response));
|
||||
assertInputFailure(JSON.parse(mcpTextValue(response)).error, input.target_id);
|
||||
return { cli: "invalid_input", mcp: "invalid_input", selector: "[" };
|
||||
}
|
||||
|
||||
function assertInputFailure(failure, targetId) {
|
||||
assert.equal(failure.code, "invalid_request");
|
||||
assert.equal(failure.category, "invalid_input");
|
||||
assert.equal(failure.details.operation, "inspect_web_event_listeners");
|
||||
assert.ok(
|
||||
failure.details.issues.some(
|
||||
(issue) =>
|
||||
issue.path[0] === "selector" && issue.message.includes(targetId),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { CdpConnection } from "../../dist/browser/CdpConnection.js";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
import { closeRuntimeFixtureResources } from "./browser-runtime-fixture-lifecycle.mjs";
|
||||
|
||||
/** Close a separately owned fixture page after actual MCP arming and inspect native cleanup diagnostics. */
|
||||
export async function verifyRuntimeTargetTermination(client, input, origin) {
|
||||
const version = await (
|
||||
await fetch(`${input.cdp_endpoint}/json/version`)
|
||||
).json();
|
||||
const root = await CdpConnection.connect(
|
||||
version.webSocketDebuggerUrl,
|
||||
"observe_web_execution",
|
||||
);
|
||||
let targetId;
|
||||
let closed = false;
|
||||
let primaryError;
|
||||
try {
|
||||
({ targetId } = await root.send("Target.createTarget", { url: origin }));
|
||||
assert.equal(typeof targetId, "string");
|
||||
await waitForTarget(input.cdp_endpoint, targetId, origin);
|
||||
let closePromise;
|
||||
const response = await client.callTool(
|
||||
{
|
||||
name: "observe_web_execution",
|
||||
arguments: {
|
||||
...input,
|
||||
target_id: targetId,
|
||||
observation_ms: 2_147_483_647,
|
||||
},
|
||||
},
|
||||
{
|
||||
timeout: 20_000,
|
||||
onprogress: (notification) => {
|
||||
if (
|
||||
closePromise === undefined &&
|
||||
notification.message?.includes(
|
||||
"browser_execution: Browser execution observation armed",
|
||||
)
|
||||
) {
|
||||
closePromise = root
|
||||
.send("Target.closeTarget", { targetId })
|
||||
.then((result) => {
|
||||
assert.equal(result.success, true);
|
||||
closed = true;
|
||||
});
|
||||
void closePromise.catch(() => undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
assert.ok(
|
||||
closePromise,
|
||||
"The separate page must be closed after actual arming",
|
||||
);
|
||||
await closePromise;
|
||||
assert.equal(
|
||||
response.isError,
|
||||
true,
|
||||
"Detached instrumentation cannot claim confirmed cleanup",
|
||||
);
|
||||
const failure = mcpTextValue(response);
|
||||
assert.match(failure, /cleanup.*confirmed/u);
|
||||
assert.ok(
|
||||
failure.includes(targetId),
|
||||
"Failure must retain the selected target identity",
|
||||
);
|
||||
assert.match(failure, /Profiler\.stopPreciseCoverage/u);
|
||||
return {
|
||||
target_id: targetId,
|
||||
page_closed_after_arming: true,
|
||||
failure_inline: true,
|
||||
cleanup: "unconfirmed_reported",
|
||||
};
|
||||
} catch (cause) {
|
||||
primaryError = cause;
|
||||
throw cause;
|
||||
} finally {
|
||||
await closeRuntimeFixtureResources(
|
||||
[
|
||||
() =>
|
||||
targetId === undefined || closed
|
||||
? undefined
|
||||
: root.send("Target.closeTarget", { targetId }),
|
||||
() => root.close(),
|
||||
],
|
||||
primaryError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForTarget(endpoint, targetId, origin) {
|
||||
for (let attempt = 0; attempt < 100; attempt += 1) {
|
||||
const pages = await (await fetch(`${endpoint}/json/list`)).json();
|
||||
if (
|
||||
pages.some((page) => page.id === targetId && page.url.startsWith(origin))
|
||||
)
|
||||
return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
throw new Error(
|
||||
"Separate fixture target did not settle on its selected origin",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
export const isolatedFixtureUrl = "rea-isolated-fixture.js";
|
||||
|
||||
/** Create an explicitly owned native isolated-world fixture outside REA's public operations. */
|
||||
export async function createRuntimeIsolatedWorld(connection) {
|
||||
const tree = await connection.send("Page.getFrameTree");
|
||||
const world = await connection.send("Page.createIsolatedWorld", {
|
||||
frameId: tree.frameTree.frame.id,
|
||||
worldName: "rea-verifier-isolated",
|
||||
});
|
||||
const contextId = world.executionContextId;
|
||||
assert.ok(Number.isInteger(contextId));
|
||||
const initialized = await connection.send("Runtime.evaluate", {
|
||||
contextId,
|
||||
expression: `function reaIsolatedFixture() { return 'isolated-proof'; }
|
||||
//# sourceURL=${isolatedFixtureUrl}`,
|
||||
});
|
||||
assert.equal(initialized.exceptionDetails, undefined);
|
||||
return contextId;
|
||||
}
|
||||
|
||||
/** Invoke a known isolated callback and the selected site's callback after actual REA arming. */
|
||||
export async function triggerRuntimeFixtureAction(connection, contextId) {
|
||||
const proof = await connection.send("Runtime.evaluate", {
|
||||
contextId,
|
||||
expression: "reaIsolatedFixture()",
|
||||
returnByValue: true,
|
||||
});
|
||||
assert.equal(proof.result.value, "isolated-proof");
|
||||
return connection.send("Runtime.evaluate", {
|
||||
expression: "document.querySelector('#run').click()",
|
||||
});
|
||||
}
|
||||
@@ -204,11 +204,20 @@ export function assertScenarioCapture(capture) {
|
||||
throw new Error(
|
||||
"Scenario event URL did not report declared-secret redaction",
|
||||
);
|
||||
const unfinished = capture.events.items.filter(
|
||||
({ kind }) => kind === "request-unfinished",
|
||||
);
|
||||
// This fixture polls continuously. A request crossing the capture cutoff
|
||||
// makes later activity unknown even when no response body was selected.
|
||||
const expectedMissing = unfinished.length === 0 ? [] : ["events"];
|
||||
if (
|
||||
capture.events.items.length === 0 ||
|
||||
capture.completeness.equality_eligible !== true
|
||||
capture.completeness.equality_eligible !== (unfinished.length === 0) ||
|
||||
JSON.stringify(capture.completeness.missing_sections) !==
|
||||
JSON.stringify(expectedMissing) ||
|
||||
capture.completeness.truncated_sections.length !== 0
|
||||
)
|
||||
throw new Error(
|
||||
"Scenario launch did not produce complete runtime evidence",
|
||||
`Scenario launch completeness disagrees with its capture cutoff: ${JSON.stringify({ completeness: capture.completeness, unfinished })}`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { webScriptExportResultSchema } from "../../dist/domain/webScriptExport.js";
|
||||
import { javascriptApplicationAnalysisResultSchema } from "../../dist/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
|
||||
/** Verify exact exported bytes and the independently authored source manifest. */
|
||||
export const assertWebScriptExport = async (evidence, capture, assets) => {
|
||||
const result = webScriptExportResultSchema.parse(evidence.normalized_result);
|
||||
assert.equal(result.source_evidence_id, capture.evidence_id);
|
||||
assert.deepEqual(evidence.evidence_links, [capture.evidence_id]);
|
||||
const manifest = await readFile(result.manifest.path);
|
||||
assert.equal(
|
||||
createHash("sha256").update(manifest).digest("hex"),
|
||||
result.manifest.sha256,
|
||||
);
|
||||
const { manifest: descriptor, ...inline } = result;
|
||||
assert.equal(descriptor.bytes, manifest.length);
|
||||
assert.deepEqual(JSON.parse(manifest.toString()), inline);
|
||||
assert.ok(result.analysis_input);
|
||||
assert.ok(result.scripts.length > 0);
|
||||
for (const script of result.scripts) {
|
||||
assert.equal(script.content.state, "exported");
|
||||
const bytes = await readFile(
|
||||
join(result.analysis_input.input_path, script.content.relative_path),
|
||||
);
|
||||
assert.equal(
|
||||
createHash("sha256").update(bytes).digest("hex"),
|
||||
script.content.sha256,
|
||||
);
|
||||
if (assets) {
|
||||
const url = new URL(script.url);
|
||||
assert.equal(
|
||||
bytes.toString(),
|
||||
assets.get(`${url.pathname}${url.search}`),
|
||||
`Wrong bytes for ${script.url}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (assets) {
|
||||
assert.equal(result.scripts.length, assets.size);
|
||||
const variants = result.scripts.filter(({ url }) =>
|
||||
url.includes("/variant.js?"),
|
||||
);
|
||||
assert.equal(variants.length, 2);
|
||||
assert.ok(variants.every(({ content }) => content.layout === "isolated"));
|
||||
assert.equal(
|
||||
new Set(variants.map(({ content }) => content.relative_path)).size,
|
||||
2,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/** Verify existing application analysis and an observed relative module layout. */
|
||||
export const assertExportedScriptAnalysis = (evidence, modules, imports) => {
|
||||
const result = javascriptApplicationAnalysisResultSchema.parse(
|
||||
evidence.normalized_result,
|
||||
);
|
||||
assert.equal(result.statistics.parsed_javascript_files, modules);
|
||||
if (imports)
|
||||
assert.ok(
|
||||
result.graph.edges.some(
|
||||
({ relation, properties }) =>
|
||||
relation === "imports" &&
|
||||
properties.specifier === "./lib/dep.js" &&
|
||||
properties.resolution_status === "resolved",
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,192 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { startBrowserScriptSite } from "../fixtures/browser-script-site.mjs";
|
||||
import {
|
||||
assertWebScriptExport,
|
||||
assertExportedScriptAnalysis,
|
||||
} from "./browser-script-export-assertions.mjs";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
|
||||
/** Verify capture → export → existing analysis through real CLI and stdio MCP. */
|
||||
export async function verifyBrowserScriptExport(
|
||||
executable,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
attachment,
|
||||
) {
|
||||
const site = await startBrowserScriptSite();
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-browser-script-export-"));
|
||||
const env = {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
|
||||
};
|
||||
const cli = exportCli(entrypoint, env);
|
||||
const scenario = scriptCaptureScenario(executable, site.origin);
|
||||
const save = (name, capture) => saveCapture(root, name, capture);
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "web-script-real-e2e", version: "1" });
|
||||
try {
|
||||
const cliCapture = await cli(
|
||||
"capture-browser-scenario",
|
||||
JSON.stringify(scenario),
|
||||
);
|
||||
const cliInput = await save("cli", cliCapture);
|
||||
const counts = new Map(site.counts);
|
||||
const cliExport = await cli(
|
||||
"export-web-scripts",
|
||||
cliInput.capture_path,
|
||||
cliInput.output_directory,
|
||||
);
|
||||
const cliResult = await assertWebScriptExport(
|
||||
cliExport,
|
||||
cliCapture,
|
||||
site.assets,
|
||||
);
|
||||
await cli(
|
||||
"analyze-javascript-application",
|
||||
cliResult.analysis_input.input_path,
|
||||
).then((value) => assertExportedScriptAnalysis(value, 4, true));
|
||||
assert.deepEqual(
|
||||
site.counts,
|
||||
counts,
|
||||
"Export/analysis refetched a captured asset",
|
||||
);
|
||||
await client.connect(transport);
|
||||
const call = async (name, args) => {
|
||||
const result = await client.callTool({ name, arguments: args });
|
||||
assert.notEqual(result.isError, true, mcpTextValue(result));
|
||||
return parseEvidence(JSON.parse(mcpTextValue(result)).evidence);
|
||||
};
|
||||
const mcpCapture = await call("capture_browser_scenario", scenario);
|
||||
const mcpInput = await save("mcp", mcpCapture);
|
||||
const mcpCounts = new Map(site.counts);
|
||||
const mcpResult = await assertWebScriptExport(
|
||||
await call("export_web_scripts", mcpInput),
|
||||
mcpCapture,
|
||||
site.assets,
|
||||
);
|
||||
assertExportedScriptAnalysis(
|
||||
await call("analyze_javascript_application", mcpResult.analysis_input),
|
||||
4,
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(
|
||||
site.counts,
|
||||
mcpCounts,
|
||||
"MCP export/analysis refetched a captured asset",
|
||||
);
|
||||
const passive = attachment
|
||||
? await verifyPassiveExport(attachment, { cli, call, save })
|
||||
: false;
|
||||
return {
|
||||
cli: true,
|
||||
stdio_mcp: true,
|
||||
active_scripts: 4,
|
||||
passive,
|
||||
exact_bytes: true,
|
||||
manifest_readback: true,
|
||||
relative_import_resolved: true,
|
||||
query_variants_isolated: true,
|
||||
implicit_refetches: 0,
|
||||
};
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
await site.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const verifyPassiveExport = async (attachment, { cli, call, save }) => {
|
||||
const input = {
|
||||
...attachment,
|
||||
observation_ms: 100,
|
||||
include_script_sources: true,
|
||||
};
|
||||
const page = await call("inspect_web_page", input);
|
||||
const pageInput = await save("passive", page);
|
||||
const exported = await assertWebScriptExport(
|
||||
await call("export_web_scripts", pageInput),
|
||||
page,
|
||||
);
|
||||
assertExportedScriptAnalysis(
|
||||
await call("analyze_javascript_application", exported.analysis_input),
|
||||
exported.scripts.length,
|
||||
false,
|
||||
);
|
||||
const pageCli = await cli(
|
||||
"inspect-web-page",
|
||||
attachment.cdp_endpoint,
|
||||
attachment.target_id,
|
||||
"--include-script-sources",
|
||||
"--observation-ms",
|
||||
"100",
|
||||
);
|
||||
const pageCliInput = await save("passive-cli", pageCli);
|
||||
const pageCliResult = await assertWebScriptExport(
|
||||
await cli(
|
||||
"export-web-scripts",
|
||||
pageCliInput.capture_path,
|
||||
pageCliInput.output_directory,
|
||||
),
|
||||
pageCli,
|
||||
);
|
||||
assertExportedScriptAnalysis(
|
||||
await cli(
|
||||
"analyze-javascript-application",
|
||||
pageCliResult.analysis_input.input_path,
|
||||
),
|
||||
pageCliResult.scripts.length,
|
||||
false,
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
const exportCli =
|
||||
(entrypoint, env) =>
|
||||
async (...args) => {
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[entrypoint, ...args, "--json"],
|
||||
{ env, timeout: 60000, maxBuffer: 32 * 1024 * 1024 },
|
||||
);
|
||||
return parseEvidence(JSON.parse(stdout));
|
||||
};
|
||||
|
||||
const saveCapture = async (root, name, capture) => {
|
||||
const capturePath = join(root, `${name}.json`);
|
||||
await writeFile(capturePath, JSON.stringify(capture));
|
||||
return {
|
||||
capture_path: capturePath,
|
||||
output_directory: join(root, `${name}-export`),
|
||||
};
|
||||
};
|
||||
|
||||
const scriptCaptureScenario = (executable, origin) => ({
|
||||
browser: { mode: "launch", executable_path: executable },
|
||||
start_url: { url: origin },
|
||||
actions: [
|
||||
{
|
||||
step_id: "ready",
|
||||
action: "wait_for",
|
||||
locator: { kind: "css", selector: '[data-ready="true"]' },
|
||||
state: "visible",
|
||||
timeout_ms: 10000,
|
||||
},
|
||||
],
|
||||
capture: { network: { response_body: true } },
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../../dist/domain/evidence.js";
|
||||
import { webSourceLocationResultSchema } from "../../dist/domain/webSourceLocation.js";
|
||||
import { startBrowserSourceMapSite } from "../fixtures/browser-source-map-site.mjs";
|
||||
import { mcpTextValue } from "./mcp-verifier-results.mjs";
|
||||
|
||||
/** Verify real Chrome capture → export → original compiler point through packaged CLI/MCP. */
|
||||
export async function verifyBrowserSourceMap(
|
||||
executable,
|
||||
entrypoint = fileURLToPath(new URL("../rea.mjs", import.meta.url)),
|
||||
) {
|
||||
const site = await startBrowserSourceMapSite();
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-browser-source-map-"));
|
||||
const env = {
|
||||
...process.env,
|
||||
REA_LOG_LEVEL: "silent",
|
||||
HOPPER_LAUNCHER_PATH: "/rea-unconfigured-provider/hopper",
|
||||
};
|
||||
const cli = async (...args) => {
|
||||
const { stdout } = await promisify(execFile)(
|
||||
process.execPath,
|
||||
[entrypoint, ...args, "--json"],
|
||||
{ env, timeout: 45000, maxBuffer: 16 * 1024 * 1024 },
|
||||
);
|
||||
return parseEvidence(JSON.parse(stdout));
|
||||
};
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [entrypoint, "mcp"],
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
const client = new Client({ name: "source-map-real-e2e", version: "1" });
|
||||
try {
|
||||
const capture = await cli(
|
||||
"capture-browser-scenario",
|
||||
JSON.stringify({
|
||||
browser: { mode: "launch", executable_path: executable },
|
||||
start_url: { url: site.origin },
|
||||
actions: [
|
||||
{
|
||||
step_id: "ready",
|
||||
action: "wait_for",
|
||||
locator: {
|
||||
kind: "css",
|
||||
selector: '[data-ready="source-map proof"]',
|
||||
},
|
||||
state: "visible",
|
||||
timeout_ms: 10000,
|
||||
},
|
||||
],
|
||||
capture: { network: { response_body: true } },
|
||||
}),
|
||||
);
|
||||
const capturePath = join(root, "capture.json");
|
||||
await writeFile(capturePath, JSON.stringify(capture));
|
||||
const exported = await cli(
|
||||
"export-web-scripts",
|
||||
capturePath,
|
||||
join(root, "export"),
|
||||
);
|
||||
const manifest = exported.normalized_result;
|
||||
const manifestBytes = await readFile(manifest.manifest.path);
|
||||
const identity = {
|
||||
sha256: sha(manifestBytes),
|
||||
bytes: manifestBytes.length,
|
||||
};
|
||||
const scriptIndex = manifest.scripts.findIndex(
|
||||
(script) => script.url === `${site.origin}/app.js?build=7`,
|
||||
);
|
||||
assert.ok(
|
||||
scriptIndex >= 0,
|
||||
"Actual browser capture omitted compiled source",
|
||||
);
|
||||
const mapPath = join(root, "app.js.map");
|
||||
await writeFile(mapPath, site.map);
|
||||
const before = site.mapRequests();
|
||||
await client.connect(transport);
|
||||
let expected;
|
||||
for (const context of [
|
||||
site.origin,
|
||||
site.origin.replace("://", "://selected-user:selected-value@"),
|
||||
]) {
|
||||
const input = {
|
||||
manifest_path: manifest.manifest.path,
|
||||
script_index: scriptIndex,
|
||||
source_map: {
|
||||
path: mapPath,
|
||||
url: `${context}/maps/app.js.map?v=7#context`,
|
||||
},
|
||||
generated_position: site.position,
|
||||
};
|
||||
expected = await assertPublicPair(cli, client, {
|
||||
site,
|
||||
input,
|
||||
capture,
|
||||
identity,
|
||||
});
|
||||
}
|
||||
assert.equal(
|
||||
site.mapRequests(),
|
||||
before,
|
||||
"Point tracing fetched the selected map URL",
|
||||
);
|
||||
return {
|
||||
compiler: site.compiler,
|
||||
generated_sha256: sha(site.generated),
|
||||
source_map_sha256: sha(site.map),
|
||||
original_sha256: sha(site.original),
|
||||
public_cli: true,
|
||||
public_stdio_mcp: true,
|
||||
actual_chrome_capture: true,
|
||||
source_map_refetch: false,
|
||||
original_point: site.originalPosition,
|
||||
codec: expected.engine,
|
||||
runtime: expected.runtime,
|
||||
selected_inert_userinfo_context_preserved: true,
|
||||
public_point_cases: 4,
|
||||
};
|
||||
} finally {
|
||||
await client.close();
|
||||
await transport.close();
|
||||
await site.close();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
function assertPoint(evidence, { site, input, capture, identity }) {
|
||||
const result = webSourceLocationResultSchema.parse(
|
||||
evidence.normalized_result,
|
||||
);
|
||||
assert.equal(result.manifest.sha256, identity.sha256);
|
||||
assert.equal(result.manifest.bytes, identity.bytes);
|
||||
assert.equal(result.source.bytes, Buffer.byteLength(site.generated));
|
||||
assert.equal(result.source.sha256, sha(site.generated));
|
||||
assert.equal(result.source_map.sha256, sha(site.map));
|
||||
assert.equal(result.source_map.url, input.source_map.url);
|
||||
assert.equal(result.manifest.source_evidence_id, capture.evidence_id);
|
||||
assert.equal(result.execution, "unknown");
|
||||
assert.equal(result.source_authenticity, "unknown");
|
||||
assert.equal(result.matches.length, 1);
|
||||
const match = result.matches[0];
|
||||
assert.equal(match.state, "mapped");
|
||||
assert.deepEqual(match.original_position, {
|
||||
...site.originalPosition,
|
||||
offset: site.originalOffset,
|
||||
content_position: "verified-in-embedded-text",
|
||||
});
|
||||
assert.equal(
|
||||
match.resolved_url,
|
||||
new URL("../src/fixture.ts?v=7#original", input.source_map.url).href,
|
||||
);
|
||||
assert.equal(match.content.state, "embedded");
|
||||
assert.equal(match.content.text, site.original);
|
||||
assert.equal(match.content.utf8_sha256, sha(site.original));
|
||||
assert.ok(result.runtime.v8_heap_limit_bytes <= 256 * 1024 * 1024);
|
||||
assert.deepEqual(evidence.raw_result, { source_map_text: site.map });
|
||||
return result;
|
||||
}
|
||||
const sha = (text) => createHash("sha256").update(text).digest("hex");
|
||||
|
||||
async function assertPublicPair(cli, client, oracle) {
|
||||
const { input } = oracle;
|
||||
const evidence = await cli(
|
||||
"trace-web-source-location",
|
||||
input.manifest_path,
|
||||
String(input.script_index),
|
||||
input.source_map.path,
|
||||
input.source_map.url,
|
||||
String(input.generated_position.line),
|
||||
String(input.generated_position.column),
|
||||
);
|
||||
const expected = assertPoint(evidence, oracle);
|
||||
const response = await client.callTool({
|
||||
name: "trace_web_source_location",
|
||||
arguments: input,
|
||||
});
|
||||
assert.notEqual(response.isError, true, mcpTextValue(response));
|
||||
const mcpEvidence = parseEvidence(
|
||||
JSON.parse(mcpTextValue(response)).evidence,
|
||||
);
|
||||
assert.deepEqual(assertPoint(mcpEvidence, oracle), expected);
|
||||
assert.equal(evidence.parameters.source_map.url, input.source_map.url);
|
||||
return expected;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export function assertObservation(result, origin) {
|
||||
assertObservationInventory(result, origin);
|
||||
assertObservationEvents(result);
|
||||
assertObservationPrivacy(result);
|
||||
assertObservationMetadata(result);
|
||||
assertObservationMetadata(result, origin);
|
||||
}
|
||||
|
||||
function assertObservationInventory(result, origin) {
|
||||
@@ -86,9 +86,27 @@ function assertObservationPrivacy(result) {
|
||||
);
|
||||
}
|
||||
|
||||
function assertObservationMetadata(result) {
|
||||
function assertObservationMetadata(result, origin) {
|
||||
assertFormDestinations(result);
|
||||
if (
|
||||
!result.metadata.dom_urls.some(
|
||||
({ attribute, url }) =>
|
||||
attribute === "href" && url === `${origin}/assets/guide`,
|
||||
)
|
||||
)
|
||||
throw new Error(
|
||||
"Real Chrome relative DOM URL did not use the document base",
|
||||
);
|
||||
if (!result.storage.local_storage_keys.includes("rea-storage-key"))
|
||||
throw new Error("Real Chrome local-storage key inventory was missing");
|
||||
if (
|
||||
result.storage.content_fingerprints.filter(
|
||||
({ scope }) => scope === "cookie",
|
||||
).length !== 2
|
||||
)
|
||||
throw new Error(
|
||||
"Real Chrome root and current-page cookie fingerprints were missing",
|
||||
);
|
||||
if (!result.storage.indexed_db_names.includes("rea-browser-db"))
|
||||
throw new Error("Real Chrome IndexedDB name inventory was missing");
|
||||
if (!result.storage.cache_names.includes("rea-browser-cache"))
|
||||
@@ -104,6 +122,23 @@ function assertObservationMetadata(result) {
|
||||
throw new Error("Real Chrome safe response metadata was missing");
|
||||
}
|
||||
|
||||
/** Assert the fixture's empty form destinations retain the current document URL. */
|
||||
export function assertFormDestinations(
|
||||
result,
|
||||
documentUrl = result.target.url,
|
||||
) {
|
||||
const destinations = result.metadata.dom_urls.filter(
|
||||
({ attribute }) => attribute === "action" || attribute === "formaction",
|
||||
);
|
||||
if (
|
||||
destinations.length !== 4 ||
|
||||
destinations.some(({ url }) => url !== documentUrl)
|
||||
)
|
||||
throw new Error(
|
||||
`Real Chrome form destinations did not use the current document URL: ${JSON.stringify({ expected: documentUrl, target: result.target.url, destinations })}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Assert real-browser static bundle and source-map findings. */
|
||||
export function assertBundleAnalysis(result) {
|
||||
if (
|
||||
|
||||
@@ -46,6 +46,7 @@ export const toolFamilyCatalog = (sources) => {
|
||||
surface: "browser-provider",
|
||||
contracts: [
|
||||
...sources.browserContracts.BROWSER_TOOL_CONTRACTS,
|
||||
...sources.webRuntimeContracts.WEB_RUNTIME_TOOL_CONTRACTS,
|
||||
...sources.browserScenarioContracts.BROWSER_SCENARIO_TOOL_CONTRACTS,
|
||||
],
|
||||
},
|
||||
@@ -64,7 +65,12 @@ export const toolFamilyCatalog = (sources) => {
|
||||
{
|
||||
id: "application",
|
||||
surface: "application-workflow",
|
||||
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
|
||||
contracts: [
|
||||
...sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
|
||||
...sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
|
||||
...sources.javascriptRecoveryContracts
|
||||
.JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "session",
|
||||
@@ -91,6 +97,21 @@ export const toolFamilyCatalog = (sources) => {
|
||||
|
||||
/** Build provider identities with their sorted capability names. */
|
||||
export const providerCatalog = (sources) => {
|
||||
const contractsByName = new Map(
|
||||
sources.toolContracts.TOOL_CONTRACTS.map((contract) => [
|
||||
contract.name,
|
||||
contract,
|
||||
]),
|
||||
);
|
||||
const declaredContracts = (providerId, operations) =>
|
||||
operations.map((operation) => {
|
||||
const contract = contractsByName.get(operation);
|
||||
if (contract === undefined)
|
||||
throw new TypeError(
|
||||
`Provider ${providerId} declares an unknown analyst operation: ${operation}`,
|
||||
);
|
||||
return contract;
|
||||
});
|
||||
const {
|
||||
applicationContracts,
|
||||
reconciliationContracts,
|
||||
@@ -100,15 +121,21 @@ export const providerCatalog = (sources) => {
|
||||
return [
|
||||
{
|
||||
identity: sources.hopperProvider.HOPPER_PROVIDER_IDENTITY,
|
||||
contracts: sources.hopperProvider.HOPPER_PROVIDER_TOOL_CONTRACTS,
|
||||
contracts: declaredContracts(
|
||||
"hopper",
|
||||
sources.hopperProvider.HOPPER_OPERATIONS,
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.ghidraProvider.GHIDRA_PROVIDER_IDENTITY,
|
||||
contracts: sources.ghidraProvider.GHIDRA_PROVIDER_TOOL_CONTRACTS,
|
||||
contracts: declaredContracts(
|
||||
"ghidra",
|
||||
sources.ghidraProvider.GHIDRA_OPERATIONS,
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.idaProvider.IDA_PROVIDER_IDENTITY,
|
||||
contracts: sources.idaProvider.IDA_PROVIDER_TOOL_CONTRACTS,
|
||||
contracts: declaredContracts("ida", sources.idaProvider.IDA_OPERATIONS),
|
||||
},
|
||||
{
|
||||
identity: sources.nativeProvider.NATIVE_MACOS_PROVIDER_IDENTITY,
|
||||
@@ -145,7 +172,10 @@ export const providerCatalog = (sources) => {
|
||||
},
|
||||
{
|
||||
identity: sources.browserProvider.CDP_BROWSER_PROVIDER_IDENTITY,
|
||||
contracts: sources.browserContracts.BROWSER_TOOL_CONTRACTS,
|
||||
contracts: [
|
||||
...sources.browserContracts.BROWSER_TOOL_CONTRACTS,
|
||||
...sources.webRuntimeContracts.WEB_RUNTIME_TOOL_CONTRACTS,
|
||||
],
|
||||
},
|
||||
{
|
||||
identity:
|
||||
@@ -182,7 +212,32 @@ export const providerCatalog = (sources) => {
|
||||
{
|
||||
identity:
|
||||
sources.artifactProviders.JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER,
|
||||
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS,
|
||||
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
|
||||
({ name }) =>
|
||||
name !== "project_android_application_graph" &&
|
||||
name !== "project_apple_application_graph",
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.ANDROID_APPLICATION_PROVIDER,
|
||||
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
|
||||
({ name }) => name === "project_android_application_graph",
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.APPLE_APPLICATION_PROVIDER,
|
||||
contracts: sources.applicationContracts.APPLICATION_TOOL_CONTRACTS.filter(
|
||||
({ name }) => name === "project_apple_application_graph",
|
||||
),
|
||||
},
|
||||
{
|
||||
identity: sources.artifactProviders.WEB_SCRIPT_EXPORT_PROVIDER,
|
||||
contracts: sources.webScriptContracts.WEB_SCRIPT_TOOL_CONTRACTS,
|
||||
},
|
||||
{
|
||||
identity: sources.javascriptRecoveryProvider.WAKARU_PROVIDER_IDENTITY,
|
||||
contracts:
|
||||
sources.javascriptRecoveryContracts.JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
|
||||
},
|
||||
]
|
||||
.map(({ identity, contracts }) => ({
|
||||
|
||||
@@ -40,31 +40,37 @@ const SOURCE_PATHS = {
|
||||
officialContracts: "dist/contracts/officialToolContracts.js",
|
||||
enhancedContracts: "dist/contracts/enhancedToolContracts.js",
|
||||
sessionContracts: "dist/contracts/sessionToolContracts.js",
|
||||
nativeContracts: "dist/contracts/nativeToolContracts.js",
|
||||
nativeContracts: "dist/contracts/native/nativeToolContracts.js",
|
||||
artifactContracts: "dist/contracts/artifactToolContracts.js",
|
||||
managedContracts: "dist/contracts/managedToolContracts.js",
|
||||
firmwareContracts: "dist/contracts/firmwareToolContracts.js",
|
||||
managedContracts: "dist/contracts/managed/managedToolContracts.js",
|
||||
firmwareContracts: "dist/contracts/firmware/firmwareToolContracts.js",
|
||||
firmwareProvider: "dist/firmware/FirmwareRelease.js",
|
||||
androidContracts: "dist/contracts/androidToolContracts.js",
|
||||
androidContracts: "dist/contracts/android/androidToolContracts.js",
|
||||
androidProvider: "dist/android/JadxRelease.js",
|
||||
managedWorkflowContracts: "dist/contracts/managedWorkflowToolContracts.js",
|
||||
managedWorkflowContracts:
|
||||
"dist/contracts/managed/managedWorkflowToolContracts.js",
|
||||
browserContracts: "dist/contracts/browserToolContracts.js",
|
||||
webRuntimeContracts: "dist/contracts/webRuntimeToolContracts.js",
|
||||
browserScenarioContracts: "dist/contracts/browserScenarioToolContracts.js",
|
||||
electronContracts: "dist/contracts/electronToolContracts.js",
|
||||
electronContracts: "dist/contracts/javascript/electronToolContracts.js",
|
||||
javascriptRuntimeObservationContracts:
|
||||
"dist/contracts/javascriptRuntimeObservationToolContracts.js",
|
||||
"dist/contracts/javascript/javascriptRuntimeObservationToolContracts.js",
|
||||
applicationContracts: "dist/contracts/applicationToolContracts.js",
|
||||
webScriptContracts: "dist/contracts/webScriptToolContracts.js",
|
||||
javascriptRecoveryContracts:
|
||||
"dist/contracts/javascript/javascriptRecoveryToolContracts.js",
|
||||
javascriptRecoveryProvider: "dist/javascript/recovery/WakaruRelease.js",
|
||||
supportedClients: "dist/application/SupportedClients.js",
|
||||
hopperProvider: "dist/hopper/HopperProvider.js",
|
||||
ghidraProvider: "dist/ghidra/GhidraProvider.js",
|
||||
hopperProvider: "dist/hopper/HopperProviderCapabilities.js",
|
||||
ghidraProvider: "dist/ghidra/GhidraProviderCapabilities.js",
|
||||
idaProvider: "dist/ida/IdaProviderCapabilities.js",
|
||||
nativeProvider: "dist/native/NativeMacOSProvider.js",
|
||||
nativeProvider: "dist/native/NativeMacOSProviderMetadata.js",
|
||||
artifactProviders: "dist/application/InvestigationProviders.js",
|
||||
browserProvider: "dist/browser/CdpBrowserProvider.js",
|
||||
browserScenarioProvider: "dist/browser/PlaywrightBrowserScenarioProvider.js",
|
||||
electronProvider: "dist/browser/CdpElectronProvider.js",
|
||||
electronActiveProvider: "dist/browser/PlaywrightElectronActiveProvider.js",
|
||||
v8InspectorProvider: "dist/browser/V8InspectorProvider.js",
|
||||
v8InspectorProvider: "dist/inspector/V8InspectorProvider.js",
|
||||
evidence: "dist/domain/evidence.js",
|
||||
evidenceBundle: "dist/domain/evidenceBundle.js",
|
||||
evidenceCompletion: "dist/domain/evidenceCompletionLedger.js",
|
||||
@@ -77,24 +83,31 @@ const SOURCE_PATHS = {
|
||||
browserScenarioCapture: "dist/domain/browserScenarioCapture.js",
|
||||
browserScenarioDiff: "dist/domain/browserScenarioDiff.js",
|
||||
browserSession: "dist/domain/browserSession.js",
|
||||
electronObservation: "dist/domain/electronObservation.js",
|
||||
electronActiveObservation: "dist/domain/electronActiveObservation.js",
|
||||
javascriptRuntimeObservation: "dist/domain/javascriptRuntimeObservation.js",
|
||||
electronObservation: "dist/domain/javascript/electronObservation.js",
|
||||
electronActiveObservation:
|
||||
"dist/domain/javascript/electronActiveObservation.js",
|
||||
javascriptRuntimeObservation:
|
||||
"dist/domain/javascript/javascriptRuntimeObservation.js",
|
||||
webBundleAnalysis: "dist/domain/webBundleAnalysis.js",
|
||||
webCaptureDiff: "dist/domain/webCaptureDiff.js",
|
||||
managedArtifact: "dist/domain/managedArtifact.js",
|
||||
managedComparison: "dist/domain/managedMemberComparison.js",
|
||||
managedNativeVerification: "dist/domain/managedNativeVerification.js",
|
||||
managedArtifact: "dist/domain/managed/managedArtifact.js",
|
||||
managedComparison: "dist/domain/managed/managedMemberComparison.js",
|
||||
managedNativeVerification: "dist/domain/managed/managedNativeVerification.js",
|
||||
webMcpDiscovery: "dist/domain/webMcpDiscovery.js",
|
||||
webScreenshot: "dist/domain/webScreenshot.js",
|
||||
javascriptApplicationGraph: "dist/domain/javascriptApplicationGraph.js",
|
||||
javascriptApplicationAnalysis: "dist/domain/javascriptApplicationAnalysis.js",
|
||||
javascriptSemanticGraph: "dist/domain/javascriptSemanticGraph.js",
|
||||
javascriptSemanticQuery: "dist/domain/javascriptSemanticQuerySchemas.js",
|
||||
javascriptSemanticTrace: "dist/domain/javascriptSemanticTraceSchemas.js",
|
||||
javascriptFeatureTrace: "dist/domain/javascriptFeatureTraceSchemas.js",
|
||||
javascriptApplicationGraph:
|
||||
"dist/domain/javascript/javascriptApplicationGraph.js",
|
||||
javascriptApplicationAnalysis:
|
||||
"dist/domain/javascript/javascriptApplicationAnalysis.js",
|
||||
javascriptSemanticGraph: "dist/domain/javascript/javascriptSemanticGraph.js",
|
||||
javascriptSemanticQuery:
|
||||
"dist/domain/javascript/javascriptSemanticQuerySchemas.js",
|
||||
javascriptSemanticTrace:
|
||||
"dist/domain/javascript/javascriptSemanticTraceSchemas.js",
|
||||
javascriptFeatureTrace:
|
||||
"dist/domain/javascript/javascriptFeatureTraceSchemas.js",
|
||||
javascriptVersionComparison:
|
||||
"dist/domain/javascriptApplicationVersionComparisonSchemas.js",
|
||||
"dist/domain/javascript/javascriptApplicationVersionComparisonSchemas.js",
|
||||
reconstructionVerification:
|
||||
"dist/domain/reconstructionVerificationSchemas.js",
|
||||
residualUnknown: "dist/domain/residualUnknown.js",
|
||||
|
||||
Loaded 100 of 1144 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user