Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8465514782 | ||
|
|
08e8501d7b | ||
|
|
95eb175457 | ||
|
|
1cd471487b | ||
|
|
473bcdd204 | ||
|
|
bc411c24be | ||
|
|
48a1312237 | ||
|
|
76b56957e9 | ||
|
|
3c5c887a2c | ||
|
|
5d6b8a0ab6 | ||
|
|
8c4f96ef47 | ||
|
|
a7f967cf24 | ||
|
|
23e68cbdfb | ||
|
|
aa357d6d91 | ||
|
|
22df16186f | ||
|
|
5d0d243a45 | ||
|
|
1280e7e513 | ||
|
|
5c7755636d | ||
|
|
d20c7b09a6 | ||
|
|
139fd52477 | ||
|
|
72dd48a44c | ||
|
|
9200adea61 | ||
|
|
16e64d58ff | ||
|
|
8f9b2ab948 | ||
|
|
42086fdba7 | ||
|
|
8695ca8257 | ||
|
|
b443b5a89b | ||
|
|
79b3f4694d | ||
|
|
0e3e577568 | ||
|
|
5bb01ca9a3 | ||
|
|
86a0428d5f | ||
|
|
99a5ffae3b | ||
|
|
5a5bcb6b54 | ||
|
|
749ae11b15 | ||
|
|
007f0d214d | ||
|
|
e90782283c | ||
|
|
486111725e | ||
|
|
40717c931b | ||
|
|
142cf188a7 | ||
|
|
c70eefad50 | ||
|
|
74c23005c0 | ||
|
|
9c4dd7eb23 | ||
|
|
1ece9630e6 | ||
|
|
4524bbd8df | ||
|
|
d97db6ebaf | ||
|
|
25aa298c61 | ||
|
|
3a2074b633 | ||
|
|
ef05713d86 | ||
|
|
48cd33af18 | ||
|
|
80d3fd40f1 | ||
|
|
55185abec8 | ||
|
|
d0a8e3c60d | ||
|
|
0d44afed7e | ||
|
|
520f8e8042 | ||
|
|
19520d0008 | ||
|
|
23def04096 | ||
|
|
cc8835c7a3 | ||
|
|
3784a8e547 | ||
|
|
c6ab965062 | ||
|
|
667402846c | ||
|
|
f921cb943e | ||
|
|
a281455f0b | ||
|
|
73d85f6299 | ||
|
|
0473a3091c | ||
|
|
3ed9f4b7e4 | ||
|
|
00a9e95de9 | ||
|
|
0205f870db | ||
|
|
6442329717 | ||
|
|
f250c8cca5 | ||
|
|
2dfcc6463b | ||
|
|
d0e93fa34b | ||
|
|
401d72997b | ||
|
|
cde858d546 | ||
|
|
ba7c3dafab | ||
|
|
553262443a | ||
|
|
98a2322998 | ||
|
|
abbb1d7135 | ||
|
|
379f37307a | ||
|
|
ce48f43550 | ||
|
|
ea579ebfae | ||
|
|
c74bc2108c | ||
|
|
65168f77fb | ||
|
|
18225b9bd6 | ||
|
|
54c666a5ae | ||
|
|
275095a9c6 | ||
|
|
b6833b9f61 | ||
|
|
cc99d896be | ||
|
|
630e6becdf | ||
|
|
99be47a9a3 | ||
|
|
572d0c2340 | ||
|
|
19d975751f | ||
|
|
21b836a327 | ||
|
|
fe0325c6d1 | ||
|
|
e6537df806 | ||
|
|
7af6fa01f5 | ||
|
|
aee249b1dd | ||
|
|
81de82a1a4 | ||
|
|
d53584a10a | ||
|
|
a4bf6f83a6 | ||
|
|
50241b368b | ||
|
|
3ba1942631 | ||
|
|
642f6571ea | ||
|
|
446b284239 | ||
|
|
0154141304 | ||
|
|
fa56001f18 | ||
|
|
667f73aa54 | ||
|
|
f28c0fdfb0 | ||
|
|
0c99194b8c | ||
|
|
e36df2c8de | ||
|
|
461541348e | ||
|
|
45a411e8f8 | ||
|
|
60981e0801 | ||
|
|
012959e3cc | ||
|
|
46111dd091 | ||
|
|
39d1b2d395 | ||
|
|
06828b2e65 | ||
|
|
be85386e27 | ||
|
|
933aa480d3 | ||
|
|
f8f4a5c3b8 | ||
|
|
56685c6ec6 | ||
|
|
6bb4f4bf7d | ||
|
|
b58aa63796 | ||
|
|
e44899d510 | ||
|
|
860b3dd6b0 | ||
|
|
e44ff4666a | ||
|
|
bcd1f95e68 | ||
|
|
14e745950e | ||
|
|
6c4d880f2d | ||
|
|
524b84792f | ||
|
|
1635eadfcc | ||
|
|
5f4e39fea2 | ||
|
|
02c8532901 | ||
|
|
2944afa5b8 | ||
|
|
28a33cae8f | ||
|
|
49c9c9d4a1 | ||
|
|
e073f64be0 | ||
|
|
4c547008c5 | ||
|
|
592c3e2793 | ||
|
|
5d81363391 | ||
|
|
37e92e430b | ||
|
|
4cfc61147b | ||
|
|
6d6cbdf8c4 | ||
|
|
c106cf20cb | ||
|
|
b395190e42 | ||
|
|
b6a0f32b14 | ||
|
|
0407bbfbe6 | ||
|
|
6dff37594c | ||
|
|
7f9a03d6ab | ||
|
|
4f8465d8db | ||
|
|
3e85da9195 | ||
|
|
c8d58d2e7c | ||
|
|
8b1e3143a2 | ||
|
|
e32a0494d4 | ||
|
|
f850b7c894 | ||
|
|
9eb829aca3 | ||
|
|
71a024c789 | ||
|
|
f4b2ba1313 | ||
|
|
ed069bb9d4 | ||
|
|
da9a580a0d | ||
|
|
74ed67da7f | ||
|
|
b7f8bbb49f | ||
|
|
6b7aa124ed | ||
|
|
0281d10130 | ||
|
|
0b17cb621d | ||
|
|
db2a3174f6 | ||
|
|
47353e494d | ||
|
|
0f09630645 | ||
|
|
371f3d91ae | ||
|
|
8b478d0f15 | ||
|
|
15ec8e98db | ||
|
|
c2fe0c6818 | ||
|
|
b5442df02d | ||
|
|
81b94cebec | ||
|
|
c4b7a2e003 | ||
|
|
7af1b20686 | ||
|
|
2775135254 | ||
|
|
6d73515bf1 | ||
|
|
0344d578b2 | ||
|
|
2a3e162246 | ||
|
|
3984d12a30 | ||
|
|
ee0ec027db | ||
|
|
b1ff86832c | ||
|
|
e83e3ceadd | ||
|
|
bd2a84ccba | ||
|
|
6b8f4f7528 | ||
|
|
146dcf46f5 | ||
|
|
1d5357aaad | ||
|
|
861e17a3a7 | ||
|
|
5a311d8afe | ||
|
|
7695466081 | ||
|
|
c717c8a3ec | ||
|
|
37cbeff9f3 | ||
|
|
0d596fa8f7 | ||
|
|
42e1a17d6e | ||
|
|
3afec59523 | ||
|
|
bea6b98e84 | ||
|
|
46217c69bc | ||
|
|
3e87889bfb | ||
|
|
746a41937d | ||
|
|
93a384db77 | ||
|
|
7091be291c | ||
|
|
3571002422 |
No files matched your search
@@ -38,8 +38,9 @@ target, or it fails again, tell the user and ask them to restart the MCP
|
||||
client. If REA could not start its connection, retry once, then tell the user
|
||||
and ask them to restart the MCP client. Do not retry a startup failure that
|
||||
returns the same unchanged result. Run `rea doctor` when REA could not start
|
||||
or tools stay missing after that restart. Read the recovery guide before a
|
||||
second repair attempt.
|
||||
or tools stay missing after that restart. For `cleanup_incomplete`, a retryable
|
||||
capacity limit, or `configuration_invalid`, follow the recovery guide's ladder
|
||||
before asking for a restart. Read that guide before a second repair attempt.
|
||||
|
||||
1. Diagnose without changing REA or client configuration. `npx` may download
|
||||
the package and write to npm's cache. For Codex, run
|
||||
|
||||
@@ -81,6 +81,37 @@ installed CLI's command help. Refresh the catalog if stale; do not guess fields
|
||||
from an older skill or return the entire catalog to repair one field. Correct
|
||||
all reported fields before retrying.
|
||||
|
||||
## Recovery ladder
|
||||
|
||||
Follow the returned remediation. Change a request field or a `REA_*` setting
|
||||
only when that text explicitly recommends the change. Restart REA when it says
|
||||
the value is read at startup. A mention that a control does not raise a limit
|
||||
is not a recommendation to change it. Do not invent a setting. Host memory and
|
||||
`NODE_OPTIONS` do not raise a REA admission unless that remediation says so.
|
||||
|
||||
- MCP connection dropped: retry that call once only when it is safe to repeat
|
||||
or known not to have started. If it may already have changed the target, or
|
||||
it fails again, tell the user REA's connection failed and ask them to restart
|
||||
the MCP client. Do not run `rea doctor` for the drop itself.
|
||||
- REA could not start its connection: retry once, then tell the user and ask
|
||||
them to restart the MCP client. Run `rea doctor` when REA could not start or
|
||||
tools stay missing after that restart. Do not retry a startup failure that
|
||||
returns the same unchanged result.
|
||||
- `cleanup_incomplete`, or retained cleanup owners in the error details: retry
|
||||
that exact cleanup owner. Do not start new work that needs those resources
|
||||
until that owner confirms release. Repeating the original call is not that
|
||||
retry.
|
||||
- Capacity, when the result is `resource_constraint` and `retryable` is true:
|
||||
wait for the active admission to finish, then retry within the reported
|
||||
limits. Do not restart REA to free the slot.
|
||||
- `configuration_invalid`: correct the named setting in the environment that
|
||||
starts REA (the shell or the MCP registration), then restart REA. Run
|
||||
`rea doctor` to see the `runtime-configuration:` check. Re-run `rea setup`
|
||||
for that client when the registration must carry the new setting, then
|
||||
reconnect.
|
||||
- `invalid_request`: correct the reported fields from the current schema. Do
|
||||
not repeat the unchanged call.
|
||||
|
||||
## Repair registration when needed
|
||||
|
||||
For Codex:
|
||||
|
||||
@@ -296,6 +296,35 @@ Exported C and embedded resources remain useful independent evidence, but a
|
||||
timeout does not establish complete analysis or absence of code. See the
|
||||
[deadline and client guide](https://github.com/morluto/rea/blob/main/docs/mcp-contracts.md#ghidra-first-query-deadlines-and-recovery).
|
||||
|
||||
## Bridge request ceiling
|
||||
|
||||
Hopper and Cutter share `REA_BRIDGE_MAX_REQUEST_BYTES`. Unset, both sides use
|
||||
16777216 bytes. The payload budget excludes the terminating LF. A decimal
|
||||
integer from 7464 to 536870887 raises or lowers that ceiling. The floor fits
|
||||
the largest bounded symbol-name command. A tight Cutter status still returns
|
||||
its session id and up to 64 completion receipts. Its path is included when it
|
||||
fits, and is unknown when it does not, so an older descriptor path is not
|
||||
paired with the new generation. The maximum is the encoded payload, one byte under Node's string ceiling so
|
||||
the framing LF still fits. Cutter measures, signs, and writes a fitting
|
||||
response in bounded chunks. The bridge searches only the newly read bytes
|
||||
for the framing LF. The client unescapes each socket chunk into one response
|
||||
string before parsing it, so this ceiling does not keep the paged wire frame
|
||||
and another concatenated copy. Invalid values are configuration errors and do not fall back to 16777216. Set the variable in the environment that starts REA, restart REA,
|
||||
and start Hopper or Cutter from that same environment. An out-of-band bridge
|
||||
without the setting stays at 16777216 bytes. The client reports that mismatch
|
||||
during discovery only after the hello proves a live authenticated bridge, and
|
||||
does not send a status request or the command. A stale descriptor is ignored.
|
||||
Discovery fails if any such live ceiling differs, even when another live
|
||||
session matches. A command response discarded for that ceiling stays an
|
||||
unknown execution with `output_truncated`. A hello
|
||||
that never sends its authenticated frame does not hold the accept loop.
|
||||
At most 8 connection threads run; a further accept is closed, and a failure
|
||||
to start a thread does not stop the listener. `completed_connection` is the
|
||||
longest prefix of connections that have terminated. Dense scalar replies are
|
||||
encoded in bounded batches.
|
||||
The 25000 ms Cutter request deadline and the 256-candidate discovery budget
|
||||
still have no runtime override.
|
||||
|
||||
## Static memory and original file bytes
|
||||
|
||||
For Ghidra, `read_bytes` reads initialized analysis memory, including loader
|
||||
|
||||
@@ -17,7 +17,10 @@ describe the use case and observable outcome. -->
|
||||
## Change and scope
|
||||
|
||||
<!-- Explain the chosen approach, why it fits REA's architecture, and what is
|
||||
intentionally not included. -->
|
||||
intentionally not included. If adding complexity for a speculative or vanishingly
|
||||
unlikely race or security edge case, record the concrete failure mode, likelihood,
|
||||
complexity cost, and the user's buy-in obtained before implementation. Do not
|
||||
silently expand scope for theoretical robustness. -->
|
||||
|
||||
Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
|
||||
Unresolved scope, authority, side effects, or required verification block
|
||||
|
||||
@@ -57,6 +57,7 @@ jobs:
|
||||
contents: read
|
||||
actions: read
|
||||
outputs:
|
||||
dependency_security: ${{ steps.plan.outputs.dependency_security }}
|
||||
tier: ${{ steps.plan.outputs.tier }}
|
||||
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
|
||||
code: ${{ steps.plan.outputs.code }}
|
||||
@@ -764,6 +765,7 @@ jobs:
|
||||
tests/boundary/mcp/nativeDemangleMcp.test.ts
|
||||
tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts
|
||||
tests/boundary/cli/nativeSignatureCli.test.ts
|
||||
tests/boundary/cli/nativeMachoArm64_32Cli.test.ts
|
||||
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
|
||||
src/process/capture/FilesystemSnapshot.test.ts
|
||||
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||
@@ -932,7 +934,7 @@ jobs:
|
||||
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22.19.0
|
||||
node-version: 22.23.4
|
||||
- name: Verify native path boundaries on Node 22
|
||||
run: >-
|
||||
npx vitest run --coverage.enabled=false
|
||||
@@ -1090,6 +1092,45 @@ jobs:
|
||||
if: needs.changes.outputs.website == 'true'
|
||||
uses: ./.github/workflows/website-check.yml
|
||||
|
||||
dependency-security:
|
||||
name: Dependency security
|
||||
needs: changes
|
||||
if: needs.changes.outputs.dependency_security == 'true' || needs.changes.outputs.tier == 'reused'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=1024
|
||||
UV_THREADPOOL_SIZE: "1"
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
ref: ${{ inputs.source_sha || github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
- name: Validate registry identities before installing dependencies
|
||||
run: node scripts/ci/dependency-security.mjs --mode=preflight
|
||||
- run: npm ci --ignore-scripts --no-audit --no-fund --maxsockets=2
|
||||
- name: Check source lock advisories, installed signatures and identities
|
||||
run: node scripts/ci/dependency-security.mjs --mode=source
|
||||
- name: Check a fresh published consumer daily
|
||||
if: github.event_name == 'schedule' && github.event.schedule == '23 3 * * *'
|
||||
run: node scripts/ci/dependency-security.mjs --mode=published
|
||||
- name: Retain dependency security evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: dependency-security-${{ github.run_attempt }}
|
||||
path: .cache/dependency-security/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
required:
|
||||
name: CI required
|
||||
# Report failed dependencies, but release superseded PR concurrency slots.
|
||||
@@ -1097,6 +1138,7 @@ jobs:
|
||||
needs:
|
||||
[
|
||||
changes,
|
||||
dependency-security,
|
||||
static,
|
||||
static-deep,
|
||||
build,
|
||||
|
||||
@@ -25,16 +25,16 @@ jobs:
|
||||
node-version-file: .nvmrc
|
||||
- name: Validate the SDK pin against the provider's verified release
|
||||
run: node scripts/verify-ghidra-bridge-sources.mjs --check-sdk-pin >> "$GITHUB_ENV"
|
||||
# Only the jars and application.properties are extracted: the check
|
||||
# compiles against them and never launches Ghidra or imports a target.
|
||||
- name: Acquire checksum-verified Ghidra jars in isolated runner storage
|
||||
# The bridge probe imports only source-owned ARM/Thumb bytes and never
|
||||
# executes target code. All SDK/project state stays in runner storage.
|
||||
- name: Acquire checksum-verified Ghidra SDK in isolated runner storage
|
||||
shell: bash
|
||||
run: |
|
||||
ghidra_root="${RUNNER_TEMP}/rea-ghidra"
|
||||
mkdir -p "${ghidra_root}"
|
||||
curl -fsSL --retry 3 --write-out 'ghidra_download_bytes=%{size_download} ghidra_download_seconds=%{time_total}\n' -o "${ghidra_root}/ghidra.zip" "${GHIDRA_RELEASE_URL}"
|
||||
echo "${GHIDRA_RELEASE_SHA256} ${ghidra_root}/ghidra.zip" | sha256sum -c -
|
||||
unzip -q "${ghidra_root}/ghidra.zip" '*/Ghidra/*.jar' '*/Ghidra/application.properties' -d "${ghidra_root}"
|
||||
unzip -q "${ghidra_root}/ghidra.zip" -d "${ghidra_root}"
|
||||
rm "${ghidra_root}/ghidra.zip"
|
||||
properties="$(find "${ghidra_root}" -path '*/Ghidra/application.properties' -print -quit)"
|
||||
test -n "${properties}"
|
||||
@@ -46,3 +46,19 @@ jobs:
|
||||
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
|
||||
test -n "${!java_home_variable}"
|
||||
JAVA_HOME="${!java_home_variable}" npm run verify:ghidra:bridge-sources -- --verified-sdk
|
||||
- name: Inspect real ARM and Thumb instruction context through the bridge
|
||||
shell: bash
|
||||
run: |
|
||||
java_home_variable="JAVA_HOME_${GHIDRA_JAVA_MAJOR}_X64"
|
||||
export JAVA_HOME="${!java_home_variable}"
|
||||
probe_root="${RUNNER_TEMP}/rea-arm-context"
|
||||
mkdir -p "${probe_root}/project"
|
||||
printf '\036\377\057\341\160\107\000\000' > "${probe_root}/arm-thumb.bin"
|
||||
timeout 180s "${GHIDRA_INSTALL_DIR}/support/analyzeHeadless" "${probe_root}/project" ArmContext \
|
||||
-import "${probe_root}/arm-thumb.bin" -loader BinaryLoader -processor ARM:LE:32:v7 \
|
||||
-noanalysis -readOnly -deleteProject \
|
||||
-scriptPath "${GITHUB_WORKSPACE}/tests/conformance/ghidra;${GITHUB_WORKSPACE}/bridge/ghidra" \
|
||||
-postScript ReaArmContextProbe.java "${GITHUB_WORKSPACE}/bridge/ghidra" \
|
||||
> "${probe_root}/output.log" 2>&1 || { cat "${probe_root}/output.log"; exit 1; }
|
||||
cat "${probe_root}/output.log"
|
||||
grep -F 'REA_ARM_CONTEXT_OK arm=0 thumb=1 unavailable=null (injected provider value)' "${probe_root}/output.log"
|
||||
@@ -77,15 +77,26 @@ jobs:
|
||||
- name: Rehearse the original qualified tarball
|
||||
env:
|
||||
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||
run: node scripts/verify-release-artifact.mjs .cache/verified-package/package.tgz "$RECOVERY_VERSION"
|
||||
RECOVERY_SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
||||
run: node scripts/verify-release-artifact.mjs .cache/verified-package/package.tgz "$RECOVERY_VERSION" --dependency-security=.cache/dependency-security --dependency-source=.cache/release-source --dependency-source-sha="$RECOVERY_SOURCE_SHA"
|
||||
- name: Verify published CLI, MCP and update
|
||||
env:
|
||||
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||
run: node scripts/verify-published-package.mjs "$RECOVERY_VERSION"
|
||||
RECOVERY_SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
||||
run: node scripts/verify-published-package.mjs "$RECOVERY_VERSION" --dependency-security=.cache/dependency-security --dependency-source=.cache/release-source --dependency-source-sha="$RECOVERY_SOURCE_SHA"
|
||||
- name: Validate released registry metadata
|
||||
working-directory: .cache/release-source
|
||||
run: npm run mcp-registry:check
|
||||
|
||||
- name: Retain recovery consumer dependency security evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: recovery-consumer-security-${{ github.run_attempt }}
|
||||
path: .cache/dependency-security/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
publish-mcp:
|
||||
name: Finish MCP Registry publication
|
||||
needs: verify
|
||||
|
||||
@@ -315,6 +315,9 @@ jobs:
|
||||
|
||||
rehearsal:
|
||||
name: Rehearse qualified release on a clean runner
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
needs: [merged-release, release-please, qualification]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.qualification.result == 'success'
|
||||
@@ -339,7 +342,16 @@ jobs:
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/verify-release-artifact.mjs "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||
run: node scripts/verify-release-artifact.mjs "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION" --dependency-security=.cache/dependency-security
|
||||
|
||||
- name: Retain consumer dependency security evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: qualified-consumer-security-${{ github.run_attempt }}
|
||||
path: .cache/dependency-security/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
publish:
|
||||
name: Publish to npm
|
||||
@@ -348,6 +360,9 @@ jobs:
|
||||
always() && !cancelled() &&
|
||||
needs.qualification.result == 'success' && needs.rehearsal.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
@@ -356,51 +371,78 @@ jobs:
|
||||
with:
|
||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
registry-url: https://registry.npmjs.org
|
||||
- name: Download the original qualified package
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: verified-release-package
|
||||
path: .cache/verified-package/
|
||||
- name: Bind publication to qualified intent
|
||||
id: package
|
||||
run: node scripts/ci/prepare-npm-publication.mjs
|
||||
- name: Publish
|
||||
if: steps.package.outputs.published == 'false'
|
||||
env:
|
||||
NPM_RELEASE_TAG: ${{ steps.package.outputs.tag }}
|
||||
run: >-
|
||||
npm publish .cache/verified-package/package.tgz --ignore-scripts
|
||||
--registry=https://registry.npmjs.org --access=public --tag="$NPM_RELEASE_TAG"
|
||||
|
||||
verify-npm:
|
||||
name: Verify published npm package
|
||||
needs: [merged-release, release-please, publish]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.publish.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
package-manager-cache: false
|
||||
- run: npm ci
|
||||
|
||||
- uses: ./.github/actions/prepare-release-package
|
||||
id: package
|
||||
|
||||
- name: Inspect existing npm version
|
||||
id: registry
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/ci/npm-release.mjs inspect "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||
|
||||
- name: Publish
|
||||
if: steps.registry.outputs.published == 'false'
|
||||
shell: bash
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: |
|
||||
tag="$(node scripts/release-npm-tag.mjs "$VERIFIED_PACKAGE_VERSION")"
|
||||
npm publish "${VERIFIED_PACKAGE_TARBALL}" --access public --tag "${tag}"
|
||||
|
||||
- name: Verify published npm bytes and dist-tag
|
||||
env:
|
||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/ci/npm-release.mjs verify "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||
|
||||
- name: Verify published CLI from npm
|
||||
env:
|
||||
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||
run: node scripts/verify-published-package.mjs "$VERIFIED_PACKAGE_VERSION"
|
||||
run: node scripts/verify-published-package.mjs "$VERIFIED_PACKAGE_VERSION" --dependency-security=.cache/dependency-security
|
||||
|
||||
- name: Retain consumer dependency security evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: published-consumer-security-${{ github.run_attempt }}
|
||||
path: .cache/dependency-security/
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
publish-mcp:
|
||||
name: Publish to MCP Registry
|
||||
needs: [merged-release, release-please, publish]
|
||||
env:
|
||||
TURBO_TOKEN: ""
|
||||
TURBO_CACHE: local:rw
|
||||
needs: [merged-release, release-please, verify-npm]
|
||||
if: >-
|
||||
always() && !cancelled() && needs.publish.result == 'success' &&
|
||||
always() && !cancelled() && needs.verify-npm.result == 'success' &&
|
||||
((needs.merged-release.result == 'success' && needs.merged-release.outputs.release_created == 'true') ||
|
||||
(needs.release-please.result == 'success' && inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
# Exercise unavailable-runtime diagnostics, the first supported release,
|
||||
# and new patches on both supported lines without hand-editing versions.
|
||||
# The repository-pinned Node 24 version also runs the complete test suite.
|
||||
node: ["22.x", "24.15.0", "24.x", "26.x"]
|
||||
node: ["22.x", "24.22.1", "24.x", "26.x"]
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
|
||||
@@ -22,6 +22,8 @@ Keep proof of ownership separate from validity, editability, and readiness. Malf
|
||||
|
||||
For lifecycle changes, follow [ownership and completion invariants](docs/tool-design.md#own-the-lifecycle) and select the applicable [lifecycle regressions](docs/testing.md#lifecycle-regressions). Include late handshakes, cancellation during final cleanup, mixed child outcomes, and cleanup retry where those stages exist; active-execution cancellation alone does not cover finalization.
|
||||
|
||||
Before adding complexity for a speculative or vanishingly unlikely race or security edge case, explain the concrete failure mode, likelihood, and complexity cost to the user and get their buy-in. Do not silently expand scope for theoretical robustness.
|
||||
|
||||
Assess brittleness in touched code during implementation, debugging, review, and maintenance. Fix the producing representation or ownership rule before adding downstream repairs. Normalize once at the owning boundary using format-aware parsers. Remove superseded representations, aliases, wrappers, and redundant tests after verifying affected consumers. Introduce abstractions only when they remove observed complexity.
|
||||
|
||||
When fixing a bug, inspect the producing boundary and affected consumers for duplicated interpretation or unclear ownership. Complete any directly related refactor in the same change and remove superseded code. Passing regression tests alone does not establish that the brittleness is resolved. If related cleanup remains, explain what remains and why.
|
||||
|
||||
@@ -26,6 +26,8 @@ to locate provider and shared-workflow bottlenecks, preserve Evidence, and
|
||||
compare equivalent real tasks before claiming an improvement.
|
||||
|
||||
When adding or changing an MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||
When adding or changing an admission ceiling, budget, or deadline, follow
|
||||
[admission limits and recovery](docs/design/limits-and-recovery.md).
|
||||
|
||||
## Reporting issues
|
||||
|
||||
@@ -247,6 +249,11 @@ base revision, or run the suite on Linux or WSL, before attributing them to a
|
||||
change. See [testing](docs/testing.md#developer-commands) for command scopes
|
||||
and lane selection.
|
||||
|
||||
`git submodule update --init --recursive` fails at the pinned Unblob submodule
|
||||
on Windows and can leave later submodules unchecked out. Apply the
|
||||
[Unblob checkout workaround](docs/firmware-analysis.md#windows-submodule-checkout),
|
||||
then rerun the command.
|
||||
|
||||
## Documentation website
|
||||
|
||||
The VitePress site uses the Markdown files in `docs/`. Run `npm run docs:dev`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
**English** · [简体中文](docs/readme/README_zh.md) · [繁體中文](docs/readme/README_zh-TW.md) · [日本語](docs/readme/README_ja.md) · [한국어](docs/readme/README_ko.md) · [Türkçe](docs/readme/README_tr.md) · [Русский](docs/readme/README_ru.md) · [Tiếng Việt](docs/readme/README_vi.md) · [ไทย](docs/readme/README_th.md) · [Deutsch](docs/readme/README_de.md) · [Español](docs/readme/README_es.md) · [Français](docs/readme/README_fr.md) · [Українська](docs/readme/README_uk.md) · [Polski](docs/readme/README_pl.md) · [Português (Brasil)](docs/readme/README_pt-BR.md) · [العربية](docs/readme/README_ar.md) · [فارسی](docs/readme/README_fa.md) · [Bahasa Indonesia](docs/readme/README_id.md) · [Italiano](docs/readme/README_it.md)
|
||||
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md)
|
||||
· [Ελληνικά](docs/readme/README_el.md) · [മലയാളം](docs/readme/README_ml.md) · [Roman Urdu](docs/readme/README_ur-Latn.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -177,7 +177,7 @@ CLI commands use the same workflows.
|
||||
|
||||
## What you can analyze
|
||||
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+, plus npm.
|
||||
REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+, plus npm.
|
||||
Additional tools and host support depend on the target:
|
||||
|
||||
| Target | What REA returns | Requirements and guide |
|
||||
|
||||
+601
-98
@@ -11,6 +11,7 @@ import json
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import socket
|
||||
import stat
|
||||
@@ -33,9 +34,151 @@ except ImportError:
|
||||
from PySide2.QtCore import QCoreApplication, QObject, Signal
|
||||
|
||||
|
||||
MAX_FRAME_BYTES = 16 * 1024 * 1024
|
||||
# Request and response frame budget in bytes, excluding the trailing LF.
|
||||
# REA_BRIDGE_MAX_REQUEST_BYTES selects it. Unset stays 16777216.
|
||||
# 7464 is the authenticated frame of the largest bounded symbol name.
|
||||
# The 64-receipt recovery status is smaller with current_file null. A path
|
||||
# that fits is kept. A path that does not fit is null, not the descriptor path.
|
||||
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
|
||||
# The ceiling is the encoded payload; JSON expansion is measured before it.
|
||||
# A fitting response is signed from bounded JSON pieces and written in 64 KiB
|
||||
# chunks, so this maximum does not retain several copies of the frame.
|
||||
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES = 536870887
|
||||
# The outer authenticated frame around the escaped JSON payload, excluding LF.
|
||||
_AUTHENTICATED_PREFIX = b'{"response":"'
|
||||
_AUTHENTICATED_PROOF_MARK = b'","proof":"'
|
||||
_PROOF_HEX_LENGTH = 64
|
||||
_AUTHENTICATED_FRAME_OVERHEAD = (
|
||||
len(_AUTHENTICATED_PREFIX)
|
||||
+ len(_AUTHENTICATED_PROOF_MARK)
|
||||
+ _PROOF_HEX_LENGTH
|
||||
+ len(b'"}')
|
||||
)
|
||||
_AUTHENTICATED_WRITE_BUFFER_BYTES = 64 * 1024
|
||||
# Raw JSON is produced in pieces no larger than this many characters.
|
||||
# One character expands to at most six JSON bytes (\u00XX) before the outer
|
||||
# string escape, so a piece stays far below the frame ceiling.
|
||||
_JSON_CHUNK_CHARS = 64 * 1024
|
||||
# Fixed admission cap. It does not follow REA_BRIDGE_MAX_REQUEST_BYTES.
|
||||
# A further accept is closed. Starting a worker must not stop the listener.
|
||||
MAX_CONNECTION_WORKERS = 8
|
||||
|
||||
|
||||
def _close_socket(connection: socket.socket) -> None:
|
||||
try:
|
||||
connection.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_constraint():
|
||||
return (
|
||||
"REA_BRIDGE_MAX_REQUEST_BYTES must be a positive decimal integer "
|
||||
"from %d to %d bytes; the default is %d bytes (16 MiB). "
|
||||
"A value below %d bytes cannot carry the largest admitted "
|
||||
"symbol-name command, and a larger value is refused so the encoded "
|
||||
"payload plus its framing LF stays one string under Node's "
|
||||
"single-string ceiling."
|
||||
% (
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _parse_bridge_max_request_bytes(raw):
|
||||
"""Refuse a non-decimal or out-of-range ceiling. Never fall back."""
|
||||
constraint = _bridge_max_request_bytes_constraint()
|
||||
if isinstance(raw, bool):
|
||||
raise RuntimeError(constraint)
|
||||
if isinstance(raw, int):
|
||||
value = raw
|
||||
elif isinstance(raw, str):
|
||||
# ASCII digits only. str.isdigit() accepts numerals such as U+0668
|
||||
# that the TypeScript setting rejects.
|
||||
if re.fullmatch(r"[1-9][0-9]*", raw) is None:
|
||||
raise RuntimeError(constraint)
|
||||
value = int(raw)
|
||||
else:
|
||||
raise RuntimeError(constraint)
|
||||
if (
|
||||
value < MINIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
or value > MAXIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
):
|
||||
raise RuntimeError(constraint)
|
||||
return value
|
||||
|
||||
|
||||
def _resolve_bridge_max_request_bytes():
|
||||
"""Prefer an injected global, then the process environment, then 16 MiB."""
|
||||
injected = globals().get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if injected is not None:
|
||||
return _parse_bridge_max_request_bytes(injected), "setting"
|
||||
raw = os.environ.get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if raw is None:
|
||||
return DEFAULT_BRIDGE_MAX_REQUEST_BYTES, "default"
|
||||
return _parse_bridge_max_request_bytes(raw), "setting"
|
||||
|
||||
|
||||
MAX_FRAME_BYTES, BRIDGE_MAX_REQUEST_BYTES_SOURCE = (
|
||||
_resolve_bridge_max_request_bytes()
|
||||
)
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_recovery():
|
||||
return (
|
||||
"The effective REA_BRIDGE_MAX_REQUEST_BYTES value is %d bytes "
|
||||
"(source %s; default %d, floor %d, maximum %d). "
|
||||
"Set REA_BRIDGE_MAX_REQUEST_BYTES in the environment that starts REA "
|
||||
"to a decimal integer from %d to %d, then restart REA. "
|
||||
"Restart Hopper or Cutter from that same environment so the bridge "
|
||||
"uses this ceiling."
|
||||
% (
|
||||
MAX_FRAME_BYTES,
|
||||
BRIDGE_MAX_REQUEST_BYTES_SOURCE,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _frame_limit_exceeded():
|
||||
return ValueError(
|
||||
"frame-limit-exceeded: Bridge request exceeds the %d-byte payload limit. %s"
|
||||
% (MAX_FRAME_BYTES, _bridge_max_request_bytes_recovery())
|
||||
)
|
||||
|
||||
|
||||
SOCKET_TIMEOUT_SECONDS = 20
|
||||
AUTH_FRAME_TIMEOUT_SECONDS = 1
|
||||
# The client request deadline. The authenticated frame read may use up to
|
||||
# this long; it does not grow past it when the byte ceiling is raised.
|
||||
REQUEST_DEADLINE_SECONDS = 25
|
||||
|
||||
|
||||
def _authenticated_frame_timeout_seconds():
|
||||
"""Time to hash and transfer one maximum-sized authenticated frame.
|
||||
|
||||
The hello frame stays at one second. The following command frame is
|
||||
built only after that challenge, so its read budget follows the
|
||||
configured payload size, at one second per mebibyte, and never exceeds
|
||||
the 25 second client deadline.
|
||||
"""
|
||||
mebibytes = (MAX_FRAME_BYTES + (1024 * 1024) - 1) // (1024 * 1024)
|
||||
return min(
|
||||
REQUEST_DEADLINE_SECONDS,
|
||||
max(AUTH_FRAME_TIMEOUT_SECONDS, mebibytes),
|
||||
)
|
||||
|
||||
|
||||
MAX_DESCRIPTOR_BYTES = 64 * 1024
|
||||
MAX_COMPLETION_RECEIPTS = 64
|
||||
|
||||
@@ -480,6 +623,17 @@ class _BridgeServer(threading.Thread):
|
||||
self.token = secrets.token_urlsafe(32)
|
||||
self.poisoned = False
|
||||
self.connection_ordinal = 0
|
||||
# The client proof is inside the authenticated frame, so that read
|
||||
# cannot start after proof. It runs off the accept loop. One
|
||||
# authenticated command still runs at a time. completed_connection
|
||||
# is the longest prefix of these ordinals whose handlers have
|
||||
# terminated, not "this status ordinal minus one".
|
||||
self._command_lock = threading.Lock()
|
||||
self._connections_lock = threading.Lock()
|
||||
self._connections: set[socket.socket] = set()
|
||||
self._workers: list[threading.Thread] = []
|
||||
self._finished_through = 0
|
||||
self._finished_ordinals: set[int] = set()
|
||||
self.directory = _bridge_directory()
|
||||
self.descriptor = self.directory / f"cutter-{os.getpid()}-{self.session_id}.json"
|
||||
|
||||
@@ -500,6 +654,7 @@ class _BridgeServer(threading.Thread):
|
||||
"current_file": self.dispatcher.current_file,
|
||||
"cutter_version": self.dispatcher.cutter_version,
|
||||
"identity_status": "partial",
|
||||
"max_request_bytes": MAX_FRAME_BYTES,
|
||||
}
|
||||
if os.name == "nt":
|
||||
_write_windows_descriptor(self.directory, self.descriptor, json.dumps(payload).encode("utf-8"))
|
||||
@@ -541,8 +696,46 @@ class _BridgeServer(threading.Thread):
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
with self._connections_lock:
|
||||
self._workers = [
|
||||
worker for worker in self._workers if worker.is_alive()
|
||||
]
|
||||
at_capacity = len(self._workers) >= MAX_CONNECTION_WORKERS
|
||||
if at_capacity:
|
||||
_close_socket(connection)
|
||||
continue
|
||||
self.connection_ordinal += 1
|
||||
connection_ordinal = self.connection_ordinal
|
||||
worker = threading.Thread(
|
||||
target=self._serve_connection,
|
||||
args=(connection, connection_ordinal),
|
||||
name="rea-cutter-bridge-connection",
|
||||
daemon=True,
|
||||
)
|
||||
with self._connections_lock:
|
||||
self._connections.add(connection)
|
||||
self._workers.append(worker)
|
||||
try:
|
||||
worker.start()
|
||||
except RuntimeError:
|
||||
with self._connections_lock:
|
||||
self._connections.discard(connection)
|
||||
if worker in self._workers:
|
||||
self._workers.remove(worker)
|
||||
self._note_connection_finished(connection_ordinal)
|
||||
_close_socket(connection)
|
||||
with self._connections_lock:
|
||||
workers = list(self._workers)
|
||||
for worker in workers:
|
||||
worker.join(timeout=1)
|
||||
self.server.close()
|
||||
try:
|
||||
self.descriptor.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _serve_connection(self, connection: socket.socket, connection_ordinal: int) -> None:
|
||||
try:
|
||||
with connection:
|
||||
connection.settimeout(AUTH_FRAME_TIMEOUT_SECONDS)
|
||||
authenticated = False
|
||||
@@ -550,60 +743,73 @@ class _BridgeServer(threading.Thread):
|
||||
hello = self._receive_frame(connection)
|
||||
if not isinstance(hello, dict) or hello.get("kind") != "rea-auth-v2" or not isinstance(hello.get("nonce"), str) or len(hello["nonce"]) != 64:
|
||||
self._send(connection, {"ok": False, "error": "authentication-failed"})
|
||||
continue
|
||||
return
|
||||
client_nonce = hello["nonce"]
|
||||
server_nonce = f"{connection_ordinal:016x}{secrets.token_hex(24)}"
|
||||
server_proof = hmac.new(self.token.encode(), f"rea-cutter-server-v2:{self.session_id}:{client_nonce}:{server_nonce}".encode(), hashlib.sha256).hexdigest()
|
||||
self._send(connection, {"session_id": self.session_id, "nonce": server_nonce, "proof": server_proof})
|
||||
authenticated_frame = self._receive_frame(connection)
|
||||
authenticated_frame = self._receive_frame(
|
||||
connection, _authenticated_frame_timeout_seconds()
|
||||
)
|
||||
request_bytes = authenticated_frame.get("request") if isinstance(authenticated_frame, dict) else None
|
||||
expected_proof = hmac.new(self.token.encode(), f"rea-cutter-client-v2:{self.session_id}:{client_nonce}:{server_nonce}:{request_bytes}".encode(), hashlib.sha256).hexdigest()
|
||||
if not isinstance(request_bytes, str) or not isinstance(authenticated_frame.get("proof"), str) or not hmac.compare_digest(authenticated_frame["proof"], expected_proof):
|
||||
presented_proof = authenticated_frame.get("proof") if isinstance(authenticated_frame, dict) else None
|
||||
if not isinstance(request_bytes, str) or not isinstance(presented_proof, str):
|
||||
self._send(connection, {"ok": False, "error": "authentication-failed"})
|
||||
continue
|
||||
return
|
||||
# Update the prefix and the request separately. One joined
|
||||
# preimage would retain another copy of a maximum-sized command.
|
||||
expected_proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
|
||||
expected_proof.update(
|
||||
f"rea-cutter-client-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode()
|
||||
)
|
||||
expected_proof.update(request_bytes.encode())
|
||||
if not hmac.compare_digest(presented_proof, expected_proof.hexdigest()):
|
||||
self._send(connection, {"ok": False, "error": "authentication-failed"})
|
||||
return
|
||||
request = json.loads(request_bytes)
|
||||
if not isinstance(request, dict) or request.get("session_id") != self.session_id:
|
||||
self._send(connection, {"ok": False, "error": "authentication-failed"})
|
||||
continue
|
||||
return
|
||||
authenticated = True
|
||||
connection.settimeout(SOCKET_TIMEOUT_SECONDS)
|
||||
if self.poisoned and request.get("kind") not in ("status", "ack"):
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "session-poisoned-after-ui-timeout",
|
||||
"execution_state": "not_started",
|
||||
}, client_nonce, server_nonce)
|
||||
continue
|
||||
if (
|
||||
request.get("kind") == "command"
|
||||
and not self.dispatcher.can_accept_command()
|
||||
):
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "completion-receipt-capacity-exceeded",
|
||||
"execution_state": "not_started",
|
||||
}, client_nonce, server_nonce)
|
||||
continue
|
||||
request["session_id"] = self.session_id
|
||||
event = threading.Event()
|
||||
cancelled = threading.Event()
|
||||
reply: dict[str, Any] = {}
|
||||
request["_event"] = event
|
||||
request["_cancelled"] = cancelled
|
||||
self.dispatcher.dispatch.emit(request, reply)
|
||||
if not event.wait(SOCKET_TIMEOUT_SECONDS):
|
||||
cancelled.set()
|
||||
self.poisoned = True
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "ui-thread-request-timeout",
|
||||
"execution_state": "unknown",
|
||||
"message": "The UI operation may still complete after this timeout; restart Cutter before issuing further commands.",
|
||||
}, client_nonce, server_nonce)
|
||||
else:
|
||||
if request.get("kind") == "status" and reply.get("ok") is True:
|
||||
reply["completed_connection"] = f"{connection_ordinal - 1:016x}"
|
||||
self._send_authenticated(connection, reply, client_nonce, server_nonce)
|
||||
with self._command_lock:
|
||||
connection.settimeout(SOCKET_TIMEOUT_SECONDS)
|
||||
if self.poisoned and request.get("kind") not in ("status", "ack"):
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "session-poisoned-after-ui-timeout",
|
||||
"execution_state": "not_started",
|
||||
}, client_nonce, server_nonce)
|
||||
return
|
||||
if (
|
||||
request.get("kind") == "command"
|
||||
and not self.dispatcher.can_accept_command()
|
||||
):
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "completion-receipt-capacity-exceeded",
|
||||
"execution_state": "not_started",
|
||||
}, client_nonce, server_nonce)
|
||||
return
|
||||
request["session_id"] = self.session_id
|
||||
event = threading.Event()
|
||||
cancelled = threading.Event()
|
||||
reply: dict[str, Any] = {}
|
||||
request["_event"] = event
|
||||
request["_cancelled"] = cancelled
|
||||
self.dispatcher.dispatch.emit(request, reply)
|
||||
if not event.wait(SOCKET_TIMEOUT_SECONDS):
|
||||
cancelled.set()
|
||||
self.poisoned = True
|
||||
self._send_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "ui-thread-request-timeout",
|
||||
"execution_state": "unknown",
|
||||
"message": "The UI operation may still complete after this timeout; restart Cutter before issuing further commands.",
|
||||
}, client_nonce, server_nonce)
|
||||
else:
|
||||
if request.get("kind") == "status" and reply.get("ok") is True:
|
||||
reply["completed_connection"] = self._completion_watermark()
|
||||
self._send_authenticated(connection, reply, client_nonce, server_nonce)
|
||||
except socket.timeout:
|
||||
if authenticated:
|
||||
self._send_authenticated(connection, {
|
||||
@@ -614,19 +820,41 @@ class _BridgeServer(threading.Thread):
|
||||
}, client_nonce, server_nonce)
|
||||
except Exception as error:
|
||||
self._send(connection, {"ok": False, "error": str(error)})
|
||||
self.server.close()
|
||||
try:
|
||||
self.descriptor.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
finally:
|
||||
self._note_connection_finished(connection_ordinal)
|
||||
with self._connections_lock:
|
||||
self._connections.discard(connection)
|
||||
|
||||
def _note_connection_finished(self, ordinal: int) -> None:
|
||||
"""Record one accepted connection as terminated.
|
||||
|
||||
The watermark is the longest prefix 1..W that has terminated. A later
|
||||
status cannot report an earlier connection that is still reading or
|
||||
still waiting to run.
|
||||
"""
|
||||
with self._connections_lock:
|
||||
if ordinal <= self._finished_through or ordinal in self._finished_ordinals:
|
||||
return
|
||||
self._finished_ordinals.add(ordinal)
|
||||
while self._finished_through + 1 in self._finished_ordinals:
|
||||
self._finished_through += 1
|
||||
self._finished_ordinals.discard(self._finished_through)
|
||||
|
||||
def _completion_watermark(self) -> str:
|
||||
with self._connections_lock:
|
||||
return f"{self._finished_through:016x}"
|
||||
|
||||
@staticmethod
|
||||
def _receive_frame(connection: socket.socket) -> dict[str, Any]:
|
||||
def _receive_frame(
|
||||
connection: socket.socket,
|
||||
timeout_seconds: float = AUTH_FRAME_TIMEOUT_SECONDS,
|
||||
) -> dict[str, Any]:
|
||||
data = bytearray()
|
||||
deadline = time.monotonic() + AUTH_FRAME_TIMEOUT_SECONDS
|
||||
while b"\n" not in data:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
newline = -1
|
||||
while newline < 0:
|
||||
if len(data) > MAX_FRAME_BYTES:
|
||||
raise ValueError("frame-limit-exceeded")
|
||||
raise _frame_limit_exceeded()
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise socket.timeout("authentication frame timed out")
|
||||
@@ -636,57 +864,173 @@ class _BridgeServer(threading.Thread):
|
||||
part = connection.recv(min(65536, MAX_FRAME_BYTES + 1 - len(data)))
|
||||
if not part:
|
||||
raise ValueError("incomplete authentication frame")
|
||||
scanned = len(data)
|
||||
data.extend(part)
|
||||
line, remainder = bytes(data).split(b"\n", 1)
|
||||
if len(line) > MAX_FRAME_BYTES or remainder:
|
||||
# The delimiter is one byte, so it cannot straddle a chunk.
|
||||
# Search only the bytes just received.
|
||||
newline = data.find(b"\n", scanned)
|
||||
if newline > MAX_FRAME_BYTES:
|
||||
raise _frame_limit_exceeded()
|
||||
if newline != len(data) - 1:
|
||||
raise ValueError("invalid framed request")
|
||||
value = json.loads(line)
|
||||
value = json.loads(bytes(data[:newline]))
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
def _send_authenticated(self, connection: socket.socket, value: dict[str, Any], client_nonce: str, server_nonce: str) -> None:
|
||||
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
|
||||
if encoded is None:
|
||||
value = self._truncated_response(value)
|
||||
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
|
||||
if encoded is None:
|
||||
# This fixed-size fallback is checked through the same signed framing path.
|
||||
value = {
|
||||
"ok": False,
|
||||
"error": "response-limit-exceeded",
|
||||
"execution_state": value.get("execution_state", "unknown"),
|
||||
"output": None,
|
||||
"output_truncated": True,
|
||||
"message": "The result exceeded the bridge frame limit; command completion state is preserved.",
|
||||
}
|
||||
encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
|
||||
if encoded is None:
|
||||
if self._deliver_authenticated(connection, value, client_nonce, server_nonce):
|
||||
return
|
||||
try:
|
||||
connection.sendall(encoded)
|
||||
except OSError:
|
||||
pass
|
||||
# Keep the recovery payload. Include the observed path when it
|
||||
# fits. An explicit null path means unknown, so the client does
|
||||
# not pair this generation with the descriptor's older path.
|
||||
bounded = self._bounded_status_response(value)
|
||||
if bounded is not None:
|
||||
observed_path = value.get("current_file")
|
||||
if isinstance(observed_path, str) and observed_path:
|
||||
with_path = dict(bounded)
|
||||
with_path["current_file"] = observed_path
|
||||
with_path.pop("message", None)
|
||||
if self._deliver_authenticated(
|
||||
connection, with_path, client_nonce, server_nonce
|
||||
):
|
||||
return
|
||||
if self._deliver_authenticated(
|
||||
connection, bounded, client_nonce, server_nonce
|
||||
):
|
||||
return
|
||||
command = self._bounded_command_response(value)
|
||||
if command is not None:
|
||||
observed_path = value.get("current_file")
|
||||
if isinstance(observed_path, str) and observed_path:
|
||||
with_path = dict(command)
|
||||
with_path["current_file"] = observed_path
|
||||
with_path.pop("message", None)
|
||||
if self._deliver_authenticated(
|
||||
connection, with_path, client_nonce, server_nonce
|
||||
):
|
||||
return
|
||||
if self._deliver_authenticated(
|
||||
connection, command, client_nonce, server_nonce
|
||||
):
|
||||
return
|
||||
value = self._truncated_response(value)
|
||||
if self._deliver_authenticated(connection, value, client_nonce, server_nonce):
|
||||
return
|
||||
# This fixed-size fallback is checked through the same signed framing path.
|
||||
self._deliver_authenticated(connection, {
|
||||
"ok": False,
|
||||
"error": "response-limit-exceeded",
|
||||
"execution_state": value.get("execution_state", "unknown"),
|
||||
"output": None,
|
||||
"output_truncated": True,
|
||||
"message": "The result exceeded the bridge frame limit; command completion state is preserved.",
|
||||
}, client_nonce, server_nonce)
|
||||
|
||||
def _encode_authenticated_response(self, value: dict[str, Any], client_nonce: str, server_nonce: str) -> bytes | None:
|
||||
def _deliver_authenticated(
|
||||
self, connection: socket.socket, value: dict[str, Any], client_nonce: str, server_nonce: str
|
||||
) -> bool:
|
||||
proof = self._authenticated_proof(value, client_nonce, server_nonce)
|
||||
if proof is None:
|
||||
return False
|
||||
try:
|
||||
self._write_authenticated_frame(connection, value, proof)
|
||||
except OSError:
|
||||
# A disconnected caller must not fall through to another payload.
|
||||
pass
|
||||
return True
|
||||
|
||||
def _authenticated_proof(
|
||||
self, value: dict[str, Any], client_nonce: str, server_nonce: str
|
||||
) -> str | None:
|
||||
if not _within_response_limit(value):
|
||||
return None
|
||||
prefix = b'{"response":"'
|
||||
suffix = b'","proof":"' + (b"0" * 64) + b'"}\n'
|
||||
frame_size = len(prefix) + len(suffix)
|
||||
response_parts: list[bytes] = []
|
||||
escaped_parts: list[bytes] = []
|
||||
for fragment in json.JSONEncoder(ensure_ascii=False, separators=(",", ":")).iterencode(value):
|
||||
raw = fragment.encode("utf-8")
|
||||
escaped = json.dumps(fragment, ensure_ascii=False)[1:-1].encode("utf-8")
|
||||
frame_size += len(escaped)
|
||||
# The payload budget excludes the terminating LF. Fragments are
|
||||
# measured and signed here, then discarded. The write pass encodes
|
||||
# them again so a maximum-sized reply is not held in several copies.
|
||||
frame_size = _AUTHENTICATED_FRAME_OVERHEAD
|
||||
proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
|
||||
proof.update(
|
||||
f"rea-cutter-response-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode()
|
||||
)
|
||||
for fragment in _response_fragments(value):
|
||||
frame_size += len(_escape_json_fragment(fragment))
|
||||
if frame_size > MAX_FRAME_BYTES:
|
||||
return None
|
||||
response_parts.append(raw)
|
||||
escaped_parts.append(escaped)
|
||||
proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
|
||||
proof.update(f"rea-cutter-response-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode())
|
||||
for part in response_parts:
|
||||
proof.update(part)
|
||||
return prefix + b"".join(escaped_parts) + b'","proof":"' + proof.hexdigest().encode() + b'"}\n'
|
||||
proof.update(fragment.encode("utf-8"))
|
||||
return proof.hexdigest()
|
||||
|
||||
def _encode_authenticated_response(
|
||||
self, value: dict[str, Any], client_nonce: str, server_nonce: str
|
||||
) -> bytes | None:
|
||||
"""Return one exact frame. The socket path writes the same pieces."""
|
||||
proof = self._authenticated_proof(value, client_nonce, server_nonce)
|
||||
if proof is None:
|
||||
return None
|
||||
return b"".join(self._iter_authenticated_frame(value, proof))
|
||||
|
||||
def _iter_authenticated_frame(self, value: dict[str, Any], proof: str):
|
||||
yield _AUTHENTICATED_PREFIX
|
||||
for fragment in _response_fragments(value):
|
||||
yield _escape_json_fragment(fragment)
|
||||
yield _AUTHENTICATED_PROOF_MARK + proof.encode("ascii") + b'"}\n'
|
||||
|
||||
def _write_authenticated_frame(
|
||||
self, connection: socket.socket, value: dict[str, Any], proof: str
|
||||
) -> None:
|
||||
pending = bytearray()
|
||||
for chunk in self._iter_authenticated_frame(value, proof):
|
||||
pending.extend(chunk)
|
||||
if len(pending) >= _AUTHENTICATED_WRITE_BUFFER_BYTES:
|
||||
connection.sendall(pending)
|
||||
pending.clear()
|
||||
if pending:
|
||||
connection.sendall(pending)
|
||||
|
||||
@staticmethod
|
||||
def _bounded_status_response(value: dict[str, Any]) -> dict[str, Any] | None:
|
||||
if "completed_commands" not in value and "completed_connection" not in value:
|
||||
return None
|
||||
bounded: dict[str, Any] = {
|
||||
"ok": value.get("ok") is True,
|
||||
"session_id": value.get("session_id"),
|
||||
"document_generation": value.get("document_generation"),
|
||||
"identity_status": value.get("identity_status", "partial"),
|
||||
"completed_commands": value.get("completed_commands"),
|
||||
"current_file": None,
|
||||
"message": (
|
||||
"current_file is unknown because the status frame exceeded "
|
||||
"REA_BRIDGE_MAX_REQUEST_BYTES. Do not reuse an older descriptor "
|
||||
"path with this document_generation."
|
||||
),
|
||||
}
|
||||
if "completed_connection" in value:
|
||||
bounded["completed_connection"] = value["completed_connection"]
|
||||
return bounded
|
||||
|
||||
@staticmethod
|
||||
def _bounded_command_response(value: dict[str, Any]) -> dict[str, Any] | None:
|
||||
if "execution_state" not in value and "document_generation" not in value:
|
||||
return None
|
||||
bounded: dict[str, Any] = {
|
||||
"ok": value.get("ok") is True,
|
||||
"output": None,
|
||||
"execution_state": value.get("execution_state", "unknown"),
|
||||
"output_truncated": True,
|
||||
"current_file": None,
|
||||
"message": (
|
||||
"current_file is unknown because the result frame exceeded "
|
||||
"REA_BRIDGE_MAX_REQUEST_BYTES. The command completion state and "
|
||||
"document_generation are preserved. Do not reuse an older "
|
||||
"descriptor path with this document_generation."
|
||||
),
|
||||
}
|
||||
generation = value.get("document_generation")
|
||||
if isinstance(generation, int) and not isinstance(generation, bool):
|
||||
bounded["document_generation"] = generation
|
||||
if value.get("identity_status") == "partial":
|
||||
bounded["identity_status"] = "partial"
|
||||
if value.get("ok") is not True and isinstance(value.get("error"), str):
|
||||
bounded["error"] = value["error"]
|
||||
return bounded
|
||||
|
||||
@staticmethod
|
||||
def _truncated_response(value: dict[str, Any]) -> dict[str, Any]:
|
||||
@@ -744,6 +1088,17 @@ class _BridgeServer(threading.Thread):
|
||||
self.server.close()
|
||||
except OSError:
|
||||
pass
|
||||
with self._connections_lock:
|
||||
connections = list(self._connections)
|
||||
for connection in connections:
|
||||
try:
|
||||
connection.shutdown(socket.SHUT_RDWR)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
connection.close()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class ReaCutterPlugin(cutter.CutterPlugin):
|
||||
@@ -777,6 +1132,128 @@ def create_cutter_plugin() -> ReaCutterPlugin:
|
||||
return ReaCutterPlugin()
|
||||
|
||||
|
||||
def _json_object_key(key: Any) -> str:
|
||||
"""Match CPython's JSON object-key text before quoting."""
|
||||
if isinstance(key, str):
|
||||
return key
|
||||
if key is True:
|
||||
return "true"
|
||||
if key is False:
|
||||
return "false"
|
||||
if key is None:
|
||||
return "null"
|
||||
if isinstance(key, int):
|
||||
return str(key)
|
||||
if isinstance(key, float):
|
||||
return json.dumps(key, separators=(",", ":"))
|
||||
raise TypeError("keys must be str, int, float, bool or None")
|
||||
|
||||
|
||||
def _response_fragments(value: Any):
|
||||
"""Yield compact JSON pieces whose concatenation is the encoded value.
|
||||
|
||||
String contents are sliced before encoding so a large output never becomes
|
||||
one frame-sized fragment. Each piece is at most ``_JSON_CHUNK_CHARS``
|
||||
characters, or the six-byte ``\\u00XX`` expansion of one such slice.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
yield '"'
|
||||
for start in range(0, len(value), _JSON_CHUNK_CHARS):
|
||||
yield json.dumps(
|
||||
value[start:start + _JSON_CHUNK_CHARS], ensure_ascii=False
|
||||
)[1:-1]
|
||||
yield '"'
|
||||
return
|
||||
if isinstance(value, dict):
|
||||
yield "{"
|
||||
first = True
|
||||
for key, item in value.items():
|
||||
if not first:
|
||||
yield ","
|
||||
first = False
|
||||
yield from _response_fragments(_json_object_key(key))
|
||||
yield ":"
|
||||
yield from _response_fragments(item)
|
||||
yield "}"
|
||||
return
|
||||
if isinstance(value, (list, tuple)):
|
||||
yield from _list_fragments(value)
|
||||
return
|
||||
token = _scalar_token(value)
|
||||
if token is None:
|
||||
raise TypeError("response value is not JSON")
|
||||
yield token
|
||||
|
||||
|
||||
def _scalar_token(value: Any) -> str | None:
|
||||
"""Compact JSON for one scalar. None means the value needs structure."""
|
||||
if type(value) is int:
|
||||
return str(value)
|
||||
if value is None:
|
||||
return "null"
|
||||
if value is True:
|
||||
return "true"
|
||||
if value is False:
|
||||
return "false"
|
||||
if isinstance(value, float):
|
||||
return json.dumps(value, separators=(",", ":"))
|
||||
return None
|
||||
|
||||
|
||||
def _list_fragments(value: list[Any] | tuple[Any, ...]):
|
||||
"""Encode a list, batching scalars into bounded pieces.
|
||||
|
||||
One ``json.dumps`` per number makes a dense array miss the client
|
||||
deadline. A batch is at most ``_JSON_CHUNK_CHARS`` of compact JSON.
|
||||
"""
|
||||
yield "["
|
||||
first = True
|
||||
batch: list[str] = []
|
||||
batch_len = 0
|
||||
|
||||
def flush_batch() -> str | None:
|
||||
nonlocal first, batch, batch_len
|
||||
if not batch:
|
||||
return None
|
||||
piece = ",".join(batch)
|
||||
batch = []
|
||||
batch_len = 0
|
||||
if first:
|
||||
first = False
|
||||
return piece
|
||||
return "," + piece
|
||||
|
||||
for item in value:
|
||||
token = _scalar_token(item)
|
||||
if token is None:
|
||||
emitted = flush_batch()
|
||||
if emitted is not None:
|
||||
yield emitted
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
yield ","
|
||||
yield from _response_fragments(item)
|
||||
continue
|
||||
extra = len(token) if not batch else len(token) + 1
|
||||
if batch and batch_len + extra > _JSON_CHUNK_CHARS:
|
||||
emitted = flush_batch()
|
||||
if emitted is not None:
|
||||
yield emitted
|
||||
extra = len(token)
|
||||
batch.append(token)
|
||||
batch_len += extra
|
||||
emitted = flush_batch()
|
||||
if emitted is not None:
|
||||
yield emitted
|
||||
yield "]"
|
||||
|
||||
|
||||
def _escape_json_fragment(fragment: str) -> bytes:
|
||||
"""Escape one raw JSON piece for the outer response string."""
|
||||
return json.dumps(fragment, ensure_ascii=False)[1:-1].encode("utf-8")
|
||||
|
||||
|
||||
def _within_response_limit(value: Any) -> bool:
|
||||
pending = [value]
|
||||
seen: set[int] = set()
|
||||
@@ -800,13 +1277,22 @@ def _within_response_limit(value: Any) -> bool:
|
||||
# Unencodable output also needs the signed omission fallback.
|
||||
return False
|
||||
size += current.count('"') + current.count("\\")
|
||||
size += sum(5 for character in current if ord(character) < 0x20)
|
||||
# A short escape is two JSON bytes and was already counted as one.
|
||||
# Other controls expand to a six-byte \\u00XX sequence.
|
||||
size += sum(
|
||||
1 if ord(character) in (0x08, 0x09, 0x0A, 0x0C, 0x0D) else 5
|
||||
for character in current
|
||||
if ord(character) < 0x20
|
||||
)
|
||||
elif isinstance(current, dict):
|
||||
identity = id(current)
|
||||
if identity in seen:
|
||||
return False
|
||||
seen.add(identity)
|
||||
size += 2 + 4 * len(current)
|
||||
count = len(current)
|
||||
# Braces, one colon per entry, and commas between entries.
|
||||
# Keys and values are counted when they are visited.
|
||||
size += 2 + count + max(0, count - 1)
|
||||
if size > MAX_FRAME_BYTES:
|
||||
return False
|
||||
pending.extend(current.keys())
|
||||
@@ -816,12 +1302,29 @@ def _within_response_limit(value: Any) -> bool:
|
||||
if identity in seen:
|
||||
return False
|
||||
seen.add(identity)
|
||||
size += 2 + 2 * len(current)
|
||||
count = len(current)
|
||||
# Brackets and commas between items. Each item is counted below.
|
||||
size += 2 + max(0, count - 1)
|
||||
if size > MAX_FRAME_BYTES:
|
||||
return False
|
||||
pending.extend(current)
|
||||
elif current is None:
|
||||
size += 4
|
||||
elif current is True:
|
||||
size += 4
|
||||
elif current is False:
|
||||
size += 5
|
||||
elif isinstance(current, int):
|
||||
size += len(str(current))
|
||||
elif isinstance(current, float):
|
||||
size += len(json.dumps(current, separators=(",", ":")))
|
||||
else:
|
||||
size += 16
|
||||
try:
|
||||
size += len(
|
||||
json.dumps(current, ensure_ascii=False, separators=(",", ":"))
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
if size > MAX_FRAME_BYTES:
|
||||
return False
|
||||
return True
|
||||
@@ -1268,9 +1268,11 @@ public final class ReaGhidraBridge extends HeadlessScript {
|
||||
result.add("procedure", function == null ? JsonNull.INSTANCE : GSON.toJsonTree(canonicalAddress(function.getEntryPoint())));
|
||||
result.addProperty("architecture", currentProgram.getLanguageID().getIdAsString());
|
||||
result.addProperty("mode", currentProgram.getLanguage().getLanguageDescription().getVariant());
|
||||
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:")) {
|
||||
if (currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:") ||
|
||||
currentProgram.getLanguageID().getIdAsString().startsWith("ARM:")) {
|
||||
JsonArray decoderContext = new JsonArray();
|
||||
var modeRegister = currentProgram.getRegister("ISA_MODE");
|
||||
String regName = currentProgram.getLanguageID().getIdAsString().startsWith("MIPS:") ? "ISA_MODE" : "TMode";
|
||||
var modeRegister = currentProgram.getRegister(regName);
|
||||
if (modeRegister != null) {
|
||||
var modeValue = currentProgram.getProgramContext().getValue(modeRegister, address, false);
|
||||
JsonObject context = new JsonObject();
|
||||
|
||||
+91
-5
@@ -8,15 +8,100 @@ API access on this thread: moving dispatch to a worker can deadlock Hopper.
|
||||
import json
|
||||
import hmac
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from typing import Any, Optional, Protocol, Sequence
|
||||
|
||||
BAD_ADDRESSES = (-1, 0xFFFFFFFFFFFFFFFF, None)
|
||||
# The native label setter truncates NSString names at this UTF-16 extent.
|
||||
HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
|
||||
# Maximum request JSON payload size in bytes, excluding its trailing LF.
|
||||
# Matches the Cutter bridge's MAX_FRAME_BYTES payload ceiling.
|
||||
MAX_REQUEST_BYTES = 16 * 1024 * 1024
|
||||
# Request JSON payload budget in bytes, excluding the trailing LF.
|
||||
# REA_BRIDGE_MAX_REQUEST_BYTES selects it. Unset stays 16777216.
|
||||
# 7464 is the authenticated Cutter frame of the largest bounded symbol name.
|
||||
# Cutter's 64-receipt recovery status is smaller with a null path. A path
|
||||
# that does not fit is null, not an older descriptor path.
|
||||
# 536870887 is Node's 536870888-character string ceiling minus the framing LF.
|
||||
# The ceiling is the encoded payload; JSON expansion is measured before it.
|
||||
_BRIDGE_MAX_REQUEST_BYTES_SETTING = "REA_BRIDGE_MAX_REQUEST_BYTES"
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES = 16 * 1024 * 1024
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES = 7464
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES = 536870887
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_constraint():
|
||||
return (
|
||||
"REA_BRIDGE_MAX_REQUEST_BYTES must be a positive decimal integer "
|
||||
"from %d to %d bytes; the default is %d bytes (16 MiB). "
|
||||
"A value below %d bytes cannot carry the largest admitted "
|
||||
"symbol-name command, and a larger value is refused so the encoded "
|
||||
"payload plus its framing LF stays one string under Node's "
|
||||
"single-string ceiling."
|
||||
% (
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _parse_bridge_max_request_bytes(raw):
|
||||
"""Refuse a non-decimal or out-of-range ceiling. Never fall back."""
|
||||
constraint = _bridge_max_request_bytes_constraint()
|
||||
if isinstance(raw, bool):
|
||||
raise RuntimeError(constraint)
|
||||
if isinstance(raw, int):
|
||||
value = raw
|
||||
elif isinstance(raw, str):
|
||||
# ASCII digits only. str.isdigit() accepts numerals such as U+0668
|
||||
# that the TypeScript setting rejects.
|
||||
if re.fullmatch(r"[1-9][0-9]*", raw) is None:
|
||||
raise RuntimeError(constraint)
|
||||
value = int(raw)
|
||||
else:
|
||||
raise RuntimeError(constraint)
|
||||
if (
|
||||
value < MINIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
or value > MAXIMUM_BRIDGE_MAX_REQUEST_BYTES
|
||||
):
|
||||
raise RuntimeError(constraint)
|
||||
return value
|
||||
|
||||
|
||||
def _resolve_bridge_max_request_bytes():
|
||||
"""Prefer the bootstrap injection, then the process environment, then 16 MiB."""
|
||||
injected = globals().get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if injected is not None:
|
||||
return _parse_bridge_max_request_bytes(injected), "setting"
|
||||
raw = os.environ.get(_BRIDGE_MAX_REQUEST_BYTES_SETTING)
|
||||
if raw is None:
|
||||
return DEFAULT_BRIDGE_MAX_REQUEST_BYTES, "default"
|
||||
return _parse_bridge_max_request_bytes(raw), "setting"
|
||||
|
||||
|
||||
MAX_REQUEST_BYTES, BRIDGE_MAX_REQUEST_BYTES_SOURCE = (
|
||||
_resolve_bridge_max_request_bytes()
|
||||
)
|
||||
|
||||
|
||||
def _bridge_max_request_bytes_recovery():
|
||||
return (
|
||||
"The effective REA_BRIDGE_MAX_REQUEST_BYTES value is %d bytes "
|
||||
"(source %s; default %d, floor %d, maximum %d). "
|
||||
"Set REA_BRIDGE_MAX_REQUEST_BYTES in the environment that starts REA "
|
||||
"to a decimal integer from %d to %d, then restart REA. "
|
||||
"Restart Hopper or Cutter from that same environment so the bridge "
|
||||
"uses this ceiling."
|
||||
% (
|
||||
MAX_REQUEST_BYTES,
|
||||
BRIDGE_MAX_REQUEST_BYTES_SOURCE,
|
||||
DEFAULT_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MINIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
MAXIMUM_BRIDGE_MAX_REQUEST_BYTES,
|
||||
)
|
||||
)
|
||||
_rea_session_document = None
|
||||
_search_inventory_cache = {}
|
||||
_pseudocode_cache = {}
|
||||
@@ -743,6 +828,7 @@ def _dispatch(method, params):
|
||||
"version": "1.0.0",
|
||||
"run_id": REA_RUN_ID,
|
||||
"process_id": os.getpid(),
|
||||
"max_request_bytes": MAX_REQUEST_BYTES,
|
||||
}
|
||||
if method in ("shutdown", "shutdown_document"):
|
||||
document = _session_document()
|
||||
@@ -1071,8 +1157,8 @@ def _serve_connection(connection):
|
||||
break
|
||||
if not line.endswith(b"\n"):
|
||||
message = (
|
||||
"Bridge request exceeds the %d-byte payload limit"
|
||||
% MAX_REQUEST_BYTES
|
||||
"Bridge request exceeds the %d-byte payload limit. %s"
|
||||
% (MAX_REQUEST_BYTES, _bridge_max_request_bytes_recovery())
|
||||
if len(line) > MAX_REQUEST_BYTES
|
||||
else "Incomplete bridge request frame"
|
||||
)
|
||||
|
||||
@@ -87,6 +87,10 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "MCP contracts", link: "/mcp-contracts" },
|
||||
{ text: "Tool design", link: "/tool-design" },
|
||||
{
|
||||
text: "Admission limits and recovery",
|
||||
link: "/design/limits-and-recovery",
|
||||
},
|
||||
{ text: "Testing", link: "/testing" },
|
||||
{ text: "Client verification", link: "/client-verification" },
|
||||
{
|
||||
|
||||
@@ -90,7 +90,7 @@ existing analysis families directly, for example
|
||||
| `read_adb_logcat` | read | Bounded `logcat -d -t` dump for one buffer, optional pid filter |
|
||||
| `list_adb_packages` | read | `pm list packages -f` with scope; base APK device paths |
|
||||
| `resolve_adb_packages` | read | Case-insensitive package-name substring search with exact-match flag |
|
||||
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, permission count |
|
||||
| `inspect_adb_package` | read | `dumpsys package` projection: versions, install times, installer, flags, and count of listed requested-permission rows (including Unicode names) |
|
||||
| `list_adb_directory` | read | NUL-framed immediate children, including hidden entries: kinds, permissions, owner, group, sizes, symlink targets |
|
||||
| `pull_adb_package` | read | Complete split APK set from `pm path`, each file digested |
|
||||
| `pull_adb_file` | read | One absolute device path pulled and digested |
|
||||
@@ -103,6 +103,11 @@ existing analysis families directly, for example
|
||||
| `start_adb_activity` | **mutates device** | Starts an intent (`am start -a` with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict |
|
||||
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
|
||||
|
||||
When `push_adb_file` receives an existing directory, adb places the local file
|
||||
inside it under the local basename; Evidence reports that effective file path
|
||||
while retaining the caller's directory path in the input parameters. The
|
||||
private read-only staging copy preserves the source file's executable bits.
|
||||
|
||||
For `inspect_adb_window`, `focus_contexts` retains each display header, its exact
|
||||
display ID when recognized, and every focus declaration with its original line.
|
||||
Declarations without a display header are explicitly `unscoped`. A missing
|
||||
@@ -136,8 +141,10 @@ and during shutdown.
|
||||
labelled hex in `unparsed_lines` and make directory coverage partial.
|
||||
- **Inferences carry their basis.** Device `kind` reports
|
||||
`kind_basis` (serial prefix or USB transport); APK `role` reports
|
||||
`role_basis: file_name`; `emulator_observed` is the build's own
|
||||
`ro.kernel.qemu` value, `null` when absent.
|
||||
`role_basis: file_name`; `emulator_observed` is derived from the build's
|
||||
`ro.kernel.qemu` property: `true` only when its value is `"1"`, `false` for
|
||||
any other reported value, and `null` when the property is absent. The raw
|
||||
property value remains available in `properties`.
|
||||
- **Device paths are not host paths.** `base_apk_device_path` values live on
|
||||
the device filesystem. Only pulls and captures produce host-local paths,
|
||||
each paired with byte size and SHA-256 digest. A pulled file's host name is
|
||||
|
||||
@@ -646,7 +646,7 @@ agent-env run ./grafana-example
|
||||
```text
|
||||
grafana-example/
|
||||
README.md provenance, what it serves, run command, rights note
|
||||
envs/grafana-example/Dockerfile pinned image: node:24.18.0-bookworm-slim + rea-agents@<this version>
|
||||
envs/grafana-example/Dockerfile pinned image: node:24.22.1-bookworm-slim + rea-agents@<this version>
|
||||
envs/grafana-example/env.toml environment_name = "grafana-example"
|
||||
envs/grafana-example/manifest.json byte-exact copy of manifest.json
|
||||
envs/grafana-example/evidence.json byte-exact copy of capture.json
|
||||
@@ -755,8 +755,16 @@ recording replayed in its place and never from a recording whose body the probe
|
||||
two collections can reuse a live id, and an equal pair stays in that collection. A live id in a later request's path, query or body is translated before the
|
||||
environment answers, a path parameter by the collection the live path names (a nested collection keeps its parent values, each decoded once, and a literal route segment stays), except a query value that pages a list, such as `limit`
|
||||
or `page`. Each call is checked against the served tool's input schema first, as the MCP server checks it. After mapping, two clock values compare by format only (ISO-8601 strings, and epoch numbers under
|
||||
time-named fields), and two different values each side generated on its own compare equal: both UUIDs, or one length of
|
||||
`at`, `time`, `date`, `timestamp`, `created`, `updated` or `modified` field names, including snake-case and camel-case forms), and two different values each side generated on its own compare equal: both UUIDs, or one length of
|
||||
eight or more with the same character classes and a digit. The report counts each kind of normalised value.
|
||||
ISO clock offsets are accepted only within RFC 3339's `00`–`23` hour and `00`–`59` minute ranges.
|
||||
Timestamp ordering retains submillisecond precision when the source records it.
|
||||
|
||||
Comparison keeps response representations distinct: JSON values compare structurally, text compares as text, and a
|
||||
retained base64 body compares by bytes (including with UTF-8 text carrying those same bytes), never by its base64
|
||||
spelling. ID mappings apply at identifier fields, `ids`/`uuids`/`guids` lists, an entity's own identity within its
|
||||
collection, and a string field learning confirmed as a state-held ID. ID mappings leave other numeric and string values unchanged. A
|
||||
global conflict involving an equal pair is not used to normalize either pair; the report counts it as an ID conflict.
|
||||
|
||||
A step agrees when the statuses and the normalised bodies are equal. The report gives agreement overall, per probe and
|
||||
per tool, `agreement_ignoring_order` for steps that differ only in item order, and each step's differences, at most 20,
|
||||
|
||||
+10
-5
@@ -99,10 +99,13 @@ share the application workflow, projection, retention and publication owners.
|
||||
certificate (`identified_by: subject-key-identifier`), adds a limitation,
|
||||
and treats that signer as unknown in `v1_signers_match_block_signers`. `certificates` lists every certificate the
|
||||
block carries (SHA-256 and subject) and `signer_infos` gives each
|
||||
`SignerInfo`'s certificate and digest/signature algorithm OIDs.
|
||||
`SignerInfo`'s certificate and digest/signature algorithm OIDs. The PKCS#7
|
||||
`SignedData` envelope is read as BER, including constructed signature OCTET
|
||||
STRINGs; each embedded certificate is still interpreted as DER by the X.509
|
||||
owner.
|
||||
`certificate` is `null` when the block has more than one `SignerInfo` (with
|
||||
nothing verified there is no single signer) or names a certificate it does
|
||||
not carry. A block that cannot be read (malformed DER, an unsupported
|
||||
not carry. A block that cannot be read (malformed ASN.1, an unsupported
|
||||
compression method, Zip64 sizes, a local header that disagrees with the
|
||||
central directory on the name, data-descriptor flag, CRC-32 or a size, as
|
||||
apksig refuses it, or over a bound) keeps its names and carries
|
||||
@@ -219,9 +222,11 @@ instances; pending scans retain no APK bytes and can be cancelled with their
|
||||
original reason. Further requests receive a capacity error. The active slot
|
||||
is held through parsing and cleanup. Signers per scheme, v1 signers and
|
||||
lineage nodes are capped at 32 each as resource admission limits, not format
|
||||
maxima (apksig separately accepts at most 10 v1 signers). Each v1 block is inflated to at most 1 MiB, and its DER
|
||||
is read within 32 levels of nesting and 65,536 elements, with at most 32
|
||||
certificates and 32 `SignerInfo`s; BER indefinite lengths are accepted. The report's size is
|
||||
maxima (apksig separately accepts at most 10 v1 signers). Each v1 block is
|
||||
inflated to at most 1 MiB, and its BER is read within 32 levels of nesting and
|
||||
65,536 elements, with at most 32 certificates and 32 `SignerInfo`s; BER
|
||||
indefinite lengths and constructed OCTET STRING fields are accepted. Embedded
|
||||
X.509 certificates retain their DER requirement. The report's size is
|
||||
bounded from above by those counts before anything is materialised, and the exact
|
||||
serialized size of the report together with its limitations (which repeat
|
||||
archive names) is then checked against an 8 MiB output budget (the exact check
|
||||
|
||||
@@ -167,6 +167,6 @@ Multi-locale and table-only projection were also verified through real CLI and
|
||||
stdio MCP workflows on Linux with Apktool 3.0.3/JDK 21 against NewPipe 0.29.1,
|
||||
ApiDemos 6.0.18 and Appium Settings 8.2.1. Independent XML projection checks,
|
||||
exact facts/Evidence parity and bounded task costs are recorded in the
|
||||
[shared-decode and table-only checkpoint](../experiments/analysis-efficiency/apktool-string-tables.md).
|
||||
[shared-decode and table-only checkpoint](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/apktool-string-tables.md).
|
||||
This qualifies selected resource facts, not application execution or independent
|
||||
binary resource-decoder completeness.
|
||||
@@ -321,25 +321,38 @@ The complete Evidence identifies the stable artifact snapshot by path, byte
|
||||
count and SHA-256.
|
||||
|
||||
- PE rows reproduce the export directory: each used export-address-table slot
|
||||
with its `ordinal`, one row per name (aliases share an ordinal), `address`
|
||||
with its biased `ordinal` (`OrdinalBase` plus the slot index), one row per
|
||||
name (aliases share an ordinal), `address`
|
||||
as the preferred-base virtual address, or `forwarder` text when the slot
|
||||
forwards to another DLL. Ordinal-only exports have a null `name`. The
|
||||
forwards to another DLL. Unused zero-address slots are omitted;
|
||||
ordinal-only exports have a null `name`. The name-ordinal table stores an
|
||||
unbiased 16-bit EAT index; reported biased ordinals may exceed 16 bits. The
|
||||
directory's DLL name, ordinal base and timestamp are reported, and an image
|
||||
without an export directory is a complete empty inventory.
|
||||
- ELF rows list defined `.dynsym` symbols with global, weak or GNU-unique
|
||||
binding and default or protected visibility. Each row keeps `value`
|
||||
(`st_value` as stored; bit 0 marks Thumb code on 32-bit ARM), `size`, `type`
|
||||
and `binding`. `version` is null for an unversioned symbol, the
|
||||
`.gnu.version_d` index, name and `hidden` flag for a versioned one, or
|
||||
`"unknown"` when the version table cannot be read. A hidden version is not
|
||||
and `binding`. ELF string tables do not declare a text encoding. Names that
|
||||
contain non-ASCII bytes are decoded as UTF-8 when those bytes form valid
|
||||
UTF-8; `name_utf8_decodable` records whether the non-ASCII name bytes are
|
||||
valid UTF-8 and `name_bytes_base64` retains those exact bytes (`null` for
|
||||
ASCII names). When they are not valid UTF-8, `name` uses a byte-preserving
|
||||
Latin-1 mapping and the raw bytes remain available. Older retained results
|
||||
report null for the newly added encoding fields. `version` is null for an
|
||||
unversioned symbol. A versioned
|
||||
symbol reports its GNU index, a name resolved from `.gnu.version_d` or
|
||||
`.gnu.version_r`, and the `hidden` flag from `.gnu.version`. `"unknown"`
|
||||
means the symbol-version table could not be read. A hidden version is not
|
||||
the default version that a by-name `dlsym` lookup selects.
|
||||
Definition names take precedence when both tables name the same index.
|
||||
|
||||
Unreadable names, out-of-range name ordinals and unreadable forwarders are
|
||||
listed in `issues`, their entries are not reported, and coverage is `partial`.
|
||||
Unresolved symbol versions keep their rows: the version name is null for an
|
||||
index no definition names, and `version` is `"unknown"` when the version table
|
||||
cannot be read; coverage is likewise `partial`. A dynamic symbol table without
|
||||
its null entry is malformed. An ELF without section headers is `unsupported`:
|
||||
Unresolved symbol versions keep their rows: the version name is null when no
|
||||
definition or requirement names the index, and `version` is
|
||||
`"unknown"` when the version table cannot be read; coverage is likewise
|
||||
`partial`. A dynamic symbol table without its null entry is malformed. An ELF
|
||||
without section headers is `unsupported`:
|
||||
enumerating dynamic symbols from the dynamic segment is not
|
||||
implemented. Mach-O exports are reported by `inspect_macho`.
|
||||
|
||||
|
||||
@@ -327,8 +327,18 @@ Cancellation and clock-request failures retain their own outcomes.
|
||||
## Data minimization
|
||||
|
||||
The Link projection parses repeated CDP header field lines independently and
|
||||
retains their declarations in order, including derived untrusted agent hints.
|
||||
Commas inside URI targets or quoted parameters remain part of those values.
|
||||
retains their declarations in order. Each link keeps its target in `href` and
|
||||
its relationship context in `context_url` with a separate `context_scope`;
|
||||
absent `anchor` uses the response URL, while a relative `anchor` resolves
|
||||
against that URL. Anchored links remain in the projection, but a `link_rel`
|
||||
agent hint is emitted only when the resolved context matches the response URL
|
||||
before userinfo credentials are removed. Following [RFC 8288](https://www.rfc-editor.org/rfc/rfc8288.html),
|
||||
the `rel`, `anchor`, and `type` parameters use their first occurrence, and
|
||||
quoted-pair escapes are decoded as specified by [RFC 9110 §5.6.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.4).
|
||||
The context fields are optional when reading older saved captures. Commas
|
||||
inside URI targets or quoted parameters remain part of those values.
|
||||
Well-known-resource hints require an exact supported pathname; path case is
|
||||
preserved because a case variant may identify a different HTTP resource.
|
||||
COOP and COEP projections retain the exact declared token after validating the
|
||||
complete Structured Field Item, including parameters such as `report-to`.
|
||||
These are declarations, not an assessment of the document's effective policy;
|
||||
@@ -338,6 +348,7 @@ REA retains local observation data and removes only URL userinfo credentials, ex
|
||||
|
||||
- URL observations preserve the complete local URL, including query values, fragments, duplicate parameters, and their order. Only username/password userinfo is removed; malformed original URL strings remain available with `origin: null`.
|
||||
- DOM snapshots retain node types, node names, value lengths, and attribute names, but not text or attribute values.
|
||||
- Supported DOM URL attributes are projected separately as resolved destinations. Empty `action` on HTML or XHTML forms and empty `formaction` on HTML or XHTML submit controls resolve to the document URL; other relative URL attributes resolve against the captured document base.
|
||||
- Accessibility structure and roles are retained by default, while names and descriptions require `include_accessibility_text: true`.
|
||||
- Network observations retain method, status, MIME type, size, type, initiator stack location, and complete URLs with userinfo credentials removed. Redirects reported by CDP are attached as ordered `redirects` on the one request ID, with the prior request URL, CDP response URL, method/type, response status/MIME/encoded length, request timestamp, and the next `requestWillBeSent` event timestamp. CDP delivers `redirectResponse` with that next request event, so the latter is an event boundary rather than an exact response timestamp. Redirect response headers and bodies are discarded; hops outside the selected origins are discarded with the request chain. Headers are discarded after an allowlisted projection of length/encoding, structured CSP/Link/policy fields, and untrusted agent hints. Cookies and authorization headers are never retained.
|
||||
- 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 typed paths, types and observation counts, and discarded. Each path segment is either `{ "kind": "property", "name": "raw property name" }` or `{ "kind": "array-element" }`; array-element types include primitives and nested arrays. Literal `*` properties remain distinct from array elements. `Network.getResponseBody` is never sent unless JSON body shape capture is selected. A JSON-typed request body that CDP reports through `hasPostData` but omits from `postData`, or that cannot be parsed as JSON, makes `json_body_shapes` unavailable. That request's `body_shapes.status` is `partial`, not `included`, when only its response shape was captured. Comparison uses body shapes only when both captures selected them, and compares request/response shapes only where both observations provide those facts. Unavailable or truncated selected coverage makes equivalence unknown; observed differences still report changed.
|
||||
@@ -391,6 +402,14 @@ validation and resource bounds. Omitted script metadata makes its affected
|
||||
section explicitly truncated.
|
||||
Disallowed-origin entries are filtered before retention.
|
||||
|
||||
Script metadata and selected source reads use one fixed admitted-script snapshot.
|
||||
New in-scope script IDs still retained when the final CDP read completes are
|
||||
counted in `scripts.total` and `completeness.dropped_events.scripts`, with
|
||||
`scripts` marked truncated; they do not extend the source-read pass. Repeated
|
||||
late events for the same ID count once. The original snapshot is not replaced
|
||||
or reduced by later updates to an existing ID. These omissions do not imply
|
||||
that a source byte or time budget was exhausted.
|
||||
|
||||
Selected script-source collection shares a 32 MiB aggregate UTF-8 retention
|
||||
budget and the `command_timeout_ms` deadline (default 20000 ms) across the
|
||||
capture. The byte budget has no runtime override. An oversized source is
|
||||
@@ -484,6 +503,10 @@ Current fetches emit a string or null; historical observations may omit
|
||||
An observed URL must fit before its response body is read. If it cannot fit,
|
||||
remaining map results are explicitly unknown; failed cancellation retains its
|
||||
cleanup context and resource constraint.
|
||||
Indexed maps whose decoded mappings cross the next section's starting position
|
||||
are invalid under [ECMA-426](https://tc39.es/ecma426/2024/), rather than complete
|
||||
maps with clipped mapping rows. Fetch collection and source tracing share this
|
||||
validation.
|
||||
|
||||
## Non-goals and threat model
|
||||
|
||||
|
||||
+15
-2
@@ -398,11 +398,17 @@ Entries that change during an import retain an unavailable-entry diagnostic
|
||||
alongside readable siblings. If enumeration of an already admitted root fails,
|
||||
the graph keeps its observed children and records the root failure as a
|
||||
top-level limitation. That partial inventory does not establish absence among
|
||||
unexamined children.
|
||||
unexamined children. POSIX directory-entry bytes that are not valid UTF-8 cannot
|
||||
be represented as graph paths; the containing directory (or root) is marked
|
||||
unsupported, and REA does not access the entry through a replacement-character
|
||||
name. Readable siblings remain inventoried.
|
||||
|
||||
JavaScript and TypeScript import parsing requires valid UTF-8. Malformed source
|
||||
bytes retain their original hashes and sizes with a decoding diagnostic; REA
|
||||
does not infer module targets from replacement characters.
|
||||
does not infer module targets from replacement characters. Exact dynamic-import
|
||||
strings, expression-free templates and runtime-transparent TypeScript wrappers
|
||||
retain their targets; computed imports remain unknown. TypeScript import-equals
|
||||
dependencies remain `requires` relationships whether or not they are exported.
|
||||
|
||||
Historical-source import requires safe no-follow file opens on Linux or
|
||||
macOS. Native Windows returns `unsupported_host`; use Linux REA inside WSL
|
||||
@@ -553,6 +559,13 @@ retain their complete typed diagnostic projection even when the selector matches
|
||||
a field such as `code` or `message`, or names a missing field. `--full-output`
|
||||
controls whether the outer `{ok, error, meta}` envelope is included.
|
||||
|
||||
Sibling array slices that select different fields are combined by original row
|
||||
index and emitted in source order, regardless of selector order. Selecting a
|
||||
whole parent row keeps that row intact and takes precedence over narrower fields
|
||||
selected from the same row. For example, `rows[0,2].name,rows[1,3].version`
|
||||
combines both fields for overlapping rows, while `rows[0,2],rows[1,3].version`
|
||||
retains rows 0 and 1 whole.
|
||||
|
||||
For `cutter-command`, `--json-output` requests command-specific JSON from
|
||||
Cutter when available. This is separate from `--format json`, which selects the
|
||||
CLI result representation; the global `--json` flag selects that same CLI
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# Admission limits and recovery
|
||||
|
||||
How to add a REA-owned admission ceiling, budget, or deadline, and what a caller
|
||||
does when one is hit. This is a design rule, not an inventory. Ceilings that
|
||||
should become settings are tracked in
|
||||
[#6176](https://github.com/morluto/rea/issues/6176). Naming honesty for limit
|
||||
errors is [#5910](https://github.com/morluto/rea/issues/5910).
|
||||
|
||||
## Name a default, then a setting when operators need headroom
|
||||
|
||||
Put the number in a named constant with a unit. Do not leave it as a magic
|
||||
number at the call site.
|
||||
|
||||
When a workload may need more room, add a setting whose unset value is that
|
||||
constant. One module under
|
||||
[`src/config/`](https://github.com/morluto/rea/blob/main/src/config) owns the
|
||||
default, any floor, the maximum, the schema, the parser, and the recovery
|
||||
text. Register that schema on `environmentSchema` in
|
||||
[`environment.ts`](https://github.com/morluto/rea/blob/main/src/config/environment.ts).
|
||||
`parseConfig` calls `parseEnvironment`. Doctor uses that same parser through
|
||||
`nativeAnalysisConfigurationError`, so startup and `rea doctor` accept the
|
||||
same values. Add the name to `SELECTED_REGISTRATION_SETTINGS` so `rea setup`
|
||||
copies it into the client registration. Unset keeps today's behavior. An
|
||||
invalid value is a `ConfigurationError` and projects as `configuration_invalid`.
|
||||
Do not fall back to the default.
|
||||
|
||||
[`REA_REGEX_MATCHING_DEADLINE_MS`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.ts)
|
||||
([#6267](https://github.com/morluto/rea/pull/6267)) is the reference. Default
|
||||
5000 ms, floor 1 ms, maximum 2147483647 ms, because Node schedules a longer
|
||||
`setTimeout` delay as 1 ms. Setup copies the setting. A hit says:
|
||||
|
||||
> The effective REA_REGEX_MATCHING_DEADLINE_MS value is 5000 ms (source default; default 5000 ms, floor 1 ms, maximum 2147483647 ms). Set REA_REGEX_MATCHING_DEADLINE_MS in the environment that starts REA to a positive decimal integer from 1 to 2147483647, then restart REA. The worker old-generation heap does not raise this deadline.
|
||||
|
||||
[`REA_MCP_MAX_RESPONSE_BYTES`](https://github.com/morluto/rea/blob/main/src/config/mcpResponseBudget.ts)
|
||||
is the byte-budget shape: a decimal safe integer with a 10 MiB floor
|
||||
(`MINIMUM_RESPONSE_BYTES`), the same shared schema, and no silent fallback. A
|
||||
change needs a REA restart and a matching client receive buffer. It does not
|
||||
raise REA's reserved string limit or Node's string ceiling. The setup
|
||||
allowlist does not copy this setting. A new headroom setting should, following
|
||||
the regex deadline.
|
||||
|
||||
A per-call budget is not an environment setting. JavaScript `max_heap_mb`
|
||||
(CLI `--max-heap-mb`) is an integer from 128 to 16384 MiB, default 1024. It
|
||||
applies on the next analysis, which starts a new worker, and does not restart
|
||||
REA. It does not raise the parent result budget, transfer ceilings, or fixed
|
||||
parser limits.
|
||||
|
||||
Host memory and `NODE_OPTIONS` do not raise a REA admission unless that
|
||||
admission's recovery says so. The JavaScript parent result budget is one that
|
||||
does: its recovery says to set `NODE_OPTIONS` (for example
|
||||
`--max-old-space-size=<MiB>`) on the process that starts REA, then restart
|
||||
that process. `max_heap_mb` does not raise it.
|
||||
|
||||
## Keep a fixed ceiling honest
|
||||
|
||||
If the ceiling must stay fixed, say so in the failure. Name the limit and the
|
||||
effective value, say this release has no runtime override, and name what does
|
||||
not raise it. Do not invent a setting.
|
||||
|
||||
JSON values stop at `MAX_JSON_DEPTH` (1000) in
|
||||
[`jsonValue.ts`](https://github.com/morluto/rea/blob/main/src/domain/jsonValue.ts):
|
||||
"JSON value exceeds maximum nesting depth of 1000. This release has no runtime
|
||||
override."
|
||||
|
||||
## Recovery text
|
||||
|
||||
The string the caller sees includes:
|
||||
|
||||
- the limit or setting name
|
||||
- the effective value and unit
|
||||
- the source (`default` or `setting`)
|
||||
- the default, the maximum, and the floor when the setting has one
|
||||
- the next action, including where to set it ("in the environment that starts REA")
|
||||
- whether a restart is required
|
||||
- what the change does not raise
|
||||
|
||||
Invalid settings never take this path. They are `configuration_invalid`.
|
||||
Doctor reports `runtime-configuration:<setting>` and says to correct it in the
|
||||
shell or the MCP registration, then rerun.
|
||||
|
||||
## What agents do
|
||||
|
||||
Follow `remediation.action`. Change a request field or a `REA_*` setting only
|
||||
when that text recommends the change. Restart only when it says the value is
|
||||
read at startup. A sentence that a control does not raise the limit is not a
|
||||
request to change it.
|
||||
|
||||
- Dropped MCP connection: retry the call once only when it is safe to repeat
|
||||
or known not to have started. If it may already have changed the target, or
|
||||
it fails again, tell the user and ask them to restart the MCP client. Do not
|
||||
start with `rea doctor`
|
||||
([#6353](https://github.com/morluto/rea/pull/6353)).
|
||||
- REA could not start its connection: retry once. If it fails again, tell the
|
||||
user and ask them to restart the MCP client, then run `rea doctor`. Do not
|
||||
retry a startup that returns the same unchanged result: tell the user REA
|
||||
could not start, ask them to restart the MCP client, then run `rea doctor`.
|
||||
- Unexpected process-command failure: do not repeat the command. Tell the user
|
||||
to check the input files, then run `rea doctor`.
|
||||
- `cleanup_incomplete`: the operation is not retryable. Retry that exact
|
||||
cleanup owner, named in `details.resources`, and wait until it confirms
|
||||
release before admitting new work on those resources
|
||||
([#6357](https://github.com/morluto/rea/pull/6357)).
|
||||
- `resource_constraint` for capacity, when `retryable` is true: wait for the
|
||||
active admission to finish, then retry within the reported limits. Do not
|
||||
restart REA to free the slot.
|
||||
- `configuration_invalid`: correct the named setting in the environment that
|
||||
starts REA, then start REA again. Run `rea doctor` for the
|
||||
`runtime-configuration:` check. Re-run `rea setup` for that client when the
|
||||
setting is on the registration allowlist, then reconnect so the new process
|
||||
sees it.
|
||||
- `invalid_request`: correct the reported fields from the current schema or
|
||||
CLI help. Do not repeat the unchanged call.
|
||||
|
||||
A restart is required for an environment setting and for a client still
|
||||
running the previous registration. It is not the first step for a transient
|
||||
MCP drop, a capacity wait, or retained cleanup.
|
||||
|
||||
## Tests
|
||||
|
||||
Lock the unset default, one accepted override, rejected floor, maximum, and
|
||||
syntax, and the recovery sentence through `parseEnvironment` and `parseConfig`.
|
||||
[`regexMatchingDeadline.test.ts`](https://github.com/morluto/rea/blob/main/src/config/regexMatchingDeadline.test.ts)
|
||||
is the pattern. Project the failure and assert `configuration_invalid` for a
|
||||
bad value and the recovery wording for a hit.
|
||||
@@ -97,7 +97,7 @@ same bundle. Export writes only the caller-selected output file.
|
||||
| Unity Mono | A managed assembly below `*_Data/Managed` or `Contents/Resources/Data/Managed` has an exact UnityEngine assembly reference. |
|
||||
| Unity IL2CPP | A recognized IL2CPP metadata magic (with the raw version when complete) coexists with a conventionally named native `GameAssembly.dll`, `GameAssembly.so`, `GameAssembly.dylib` or `libil2cpp.so` image. Build correspondence remains unknown. |
|
||||
| Godot | A `GDPC` header at file start or a validated executable trailer offset. |
|
||||
| GameMaker | A bounded `FORM` container starts with a `GEN8` chunk. |
|
||||
| GameMaker | A bounded `FORM` container starts with a `GEN8` chunk. The scan probes conventional `data.win`, `game.unx`, and `game.ios` data files for that header. |
|
||||
| Unreal | `Content/Paks` path conventions or an IoStore TOC signature; arbitrary `.pak` filenames are insufficient. |
|
||||
| Electron | A `resources/app.asar` or `resources/default_app.asar` path convention; archive validity is unverified. |
|
||||
|
||||
|
||||
@@ -70,7 +70,10 @@ DOM node indices retain their snapshot positions. Invalid rows are omitted with
|
||||
completeness diagnostics, so `total_nodes` can exceed the retained node count
|
||||
and a parent index can refer to an omitted source node.
|
||||
|
||||
Script metadata retains its execution-context frame ID when CDP supplies one. The capture also
|
||||
Script metadata retains its execution-context frame ID when CDP supplies one and
|
||||
the observed frame generation is authorized. Frames describe the current inventory;
|
||||
script observations can retain earlier authorized frames that have since detached.
|
||||
The capture also
|
||||
inventories worker, service-worker, and shared-worker targets with
|
||||
validated opener-target and parent-frame IDs. Worker discovery uses passive
|
||||
target metadata; REA does not attach to or execute code in those targets.
|
||||
|
||||
+6
-1
@@ -24,6 +24,9 @@ requires an absolute filesystem path and preserves that exact selection:
|
||||
|
||||
The inline Evidence result contains the artifact path, SHA-256, and byte length.
|
||||
`chunks` lists every IFF chunk name, file range, and payload SHA-256.
|
||||
Repeated names retain separate chunk occurrences. Decoded tables and the Code
|
||||
header use the last occurrence of each name, matching OTP's loader; the chunk
|
||||
inventory still exposes earlier payloads and their source ranges.
|
||||
|
||||
When `AtU8` or `Atom` is present, `atoms` is the 1-based name table and
|
||||
`module` is atom 1. Both the pre-OTP 28 table and the OTP 28+ compact table
|
||||
@@ -43,4 +46,6 @@ import, and lambda rows together. Malformed `FOR1`/`BEAM` headers, truncated
|
||||
chunks, and `AtU8` names that are not valid UTF-8 fail without partial success.
|
||||
|
||||
This workflow does not execute bytecode, inflate `LitT` literals, or decode
|
||||
`Attr`/`CInf` external-term payloads.
|
||||
`Attr`/`CInf` external-term payloads. Complete coverage describes this static
|
||||
chunk and table report; it does not establish that an Erlang runtime can load
|
||||
or execute the module.
|
||||
@@ -110,7 +110,9 @@ address-space limit and 30 CPU seconds. The worker command deadline is
|
||||
`timeout_ms` (default 30000 ms, integer 1 through 2147483647) on the next call,
|
||||
or `--timeout-ms` on `rea inspect-evm-interface`. Omission keeps 30000 ms.
|
||||
Those byte, address-space, and CPU ceilings have no runtime override, and
|
||||
`timeout_ms` does not raise them. Virtual
|
||||
`timeout_ms` does not raise them. An oversized selected carrier returns a
|
||||
non-retryable `resource_constraint` with resource `file-size` and its fixed
|
||||
input allowance in `reported_limits.maximum_input_bytes`. Virtual
|
||||
address space differs from resident memory. The worker retains inherited tighter
|
||||
soft/hard limits, reports its configured soft limits inline and leaves hard limits
|
||||
unchanged. WASM trap reservations are disabled
|
||||
|
||||
@@ -50,6 +50,27 @@ 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](https://github.com/morluto/rea/blob/main/third_party/README.md).
|
||||
|
||||
### Windows submodule checkout
|
||||
|
||||
On Windows, the Unblob checkout stops with `error: invalid path`. Several
|
||||
upstream test fixture names under `tests/integration/` contain characters,
|
||||
trailing dots, or trailing spaces that NTFS does not allow; REA does not use
|
||||
those fixtures. Keep the clone, exclude that directory, and retry from the
|
||||
repository root in PowerShell:
|
||||
|
||||
```powershell
|
||||
git -C third_party/unblob config core.protectNTFS false
|
||||
git -C third_party/unblob sparse-checkout set --no-cone '/*' '!/tests/integration/'
|
||||
git submodule update third_party/unblob
|
||||
```
|
||||
|
||||
Both settings are required. While `core.protectNTFS` is enabled, Git for
|
||||
Windows also rejects index paths that the sparse pattern excludes; the pattern
|
||||
keeps the invalid names out of the worktree. The setting applies only to the
|
||||
Unblob submodule. In Git Bash, prefix the `sparse-checkout` command with
|
||||
`MSYS_NO_PATHCONV=1`; otherwise `!/tests/integration/` is rewritten as a
|
||||
Windows path.
|
||||
|
||||
## CLI and MCP
|
||||
|
||||
```sh
|
||||
|
||||
+12
-6
@@ -23,13 +23,19 @@ not name its entry function `entry`. MCP uses `open_binary`, `list_procedures`,
|
||||
`procedure_info`, `read_function_instructions`, and `analyze_function` with the
|
||||
same admitted target and provider semantics.
|
||||
|
||||
The classifier validates MZ page counts, initialized module length, header and
|
||||
The DOS MZ parser validates page counts, initialized module length, header and
|
||||
relocation-table bounds, relocation destinations, and the initialized entry
|
||||
point. It distinguishes DOS load-module and relocation bytes from the Windows
|
||||
new-header field. PE remains PE; NE, LE, and LX declarations are explicitly
|
||||
unsupported. A damaged declared Windows header does not silently become DOS.
|
||||
Inventory classification returns `unknown` when its bounded prefix cannot
|
||||
establish the format; this is distinct from the complete target admission check.
|
||||
point using the available structural bytes and actual file length. It reports
|
||||
those DOS facts separately from Windows-header evidence. A recognized PE
|
||||
signature remains PE; recognized NE, LE, and LX headers are explicitly
|
||||
unsupported. A nonzero Windows-header pointer without a recognized signature
|
||||
remains an unresolved candidate, even when the DOS structure validates:
|
||||
automatic target selection fails closed and reports both observations.
|
||||
REA does not infer DOS intent from padding or infer a damaged Windows header
|
||||
from the pointer alone. Inventory classification is a bounded-prefix format
|
||||
hint; it returns `unknown` for that candidate or when its prefix cannot
|
||||
establish the format. That result is distinct from complete DOS validation and
|
||||
does not decide target admission.
|
||||
|
||||
Ghidra imports a private target snapshot with `MzLoader`, language
|
||||
`x86:LE:16:Real Mode`, and compiler specification `default`. These settings,
|
||||
|
||||
+4
-3
@@ -61,7 +61,7 @@ rea inspect-go-binary ./application --detail summary --evidence_output ./go-evid
|
||||
`--evidence_output` is required for CLI summary delivery and never overwrites an
|
||||
existing file. The default CLI/MCP reply remains complete.
|
||||
|
||||
[Measured workflows](../experiments/analysis-efficiency/go-build-views.md) include
|
||||
[Measured workflows](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/go-build-views.md) include
|
||||
catalog repetition and extra calls. Identity/count questions benefit from a
|
||||
single summary. In the tested Codex full-catalog configuration, a compound
|
||||
question about identity and one dependency uses fewer tokens in captured request JSON with
|
||||
@@ -112,8 +112,9 @@ module record and no dependency records has an observed empty dependency list.
|
||||
The built-in TypeScript reader supports ELF, PE and thin Mach-O images, and one
|
||||
selected slice of a universal Mach-O, with Go's inline or historical pointer
|
||||
build-info encoding. Source ranges refer to the original stable file snapshot,
|
||||
including pointer-backed strings. It does not scan arbitrary byte strings as
|
||||
compiler metadata.
|
||||
including pointer-backed strings. Legacy pointer-backed fields are decoded at
|
||||
the width declared by Go's build-info record, independently of the image's ELF
|
||||
class. It does not scan arbitrary byte strings as compiler metadata.
|
||||
|
||||
A universal (FAT) Mach-O needs `architecture` (`x86`, `x86_64`, `arm`
|
||||
or `arm64`; CLI `--architecture`). REA reads its own FAT table, without `lipo`,
|
||||
|
||||
@@ -27,7 +27,7 @@ selected fields.
|
||||
Conflicting base architectures retain both values, mark object/report/target
|
||||
coverage partial and annotate the affected kernels. CUBIN flags establish a base
|
||||
architecture: `base-only` explicitly leaves feature-profile agreement unknown,
|
||||
including `a` variants. A standalone declaration is `single-source`, and
|
||||
including `a` and `f` variants. A standalone declaration is `single-source`, and
|
||||
unexamined payloads remain `unexamined`. An exact AMDGPU target is normalized at
|
||||
its note owner for comparison with a Clang bundle declaration; raw values remain
|
||||
intact. Unresolved feature comparisons stay `unknown`.
|
||||
@@ -43,8 +43,8 @@ target remains unexamined; an empty filtered result does not establish that
|
||||
no enclosed code declares that target. Discovering every inner declaration
|
||||
requires decoding those payloads.
|
||||
|
||||
HIP IDs with one or two separators before the architecture retain the declared
|
||||
target. If a recognized AMDGPU bundle ID does not expose a target, the same
|
||||
HIP bundle IDs retain the declared processor and v2-v3 `+feature` suffixes for
|
||||
exact selection. If a recognized AMDGPU bundle ID does not expose a target, the same
|
||||
bounded note-discovery owner used for standalone AMDGPU files establishes it
|
||||
before filtering. Those directory/note reads share the inspection work budget;
|
||||
successive queries can select the target returned by inventory.
|
||||
@@ -81,13 +81,24 @@ Kernel representations have different meanings:
|
||||
- PTX reports parameter declarations and virtual registers. Shared bytes cover
|
||||
supported static declarations inside that entry; they do not establish module
|
||||
shared memory, dynamic allocation or physical device resources.
|
||||
PTX 1.0–1.3 declares entry parameters in the body; PTX 1.4 and later uses the
|
||||
entry parameter list. Entry-scoped `.pragma` statements before the body do
|
||||
not end the declaration or suppress its body resources. A `.target
|
||||
compute_xx` spelling is retained as written;
|
||||
NVIDIA PTX defines it as a synonym for `.target sm_xx` when comparing its
|
||||
target identity with the fatbin declaration. `.callprototype` formal
|
||||
parameters are excluded from entry virtual-register counts. Parameter names
|
||||
remain available when pointer qualifiers are understood or an unsupported
|
||||
qualifier prevents layout decoding. A pointer's `.align` describes the
|
||||
pointed-to memory and does not change parameter layout alignment.
|
||||
Scalar register declarations count each bare or percent-prefixed name and
|
||||
each parameterized range. Unsupported declaration forms or overflowing totals
|
||||
retain an unknown count and partial decoding coverage. Unsupported parameter
|
||||
layouts and unknown shared-memory declarations likewise retain their known
|
||||
facts, explicit limitations and partial coverage through retained pages.
|
||||
- CUBIN identifies CUDA entry symbols and reads numeric `nv.info` records and
|
||||
native section declarations. Global register declarations also cover compiler
|
||||
- CUBIN identifies CUDA entry symbols and reads numeric `nv.info` records,
|
||||
including V2 kernel-parameter sizes, and native section declarations. Global
|
||||
register declarations also cover compiler
|
||||
versions that leave the older text-section register field empty.
|
||||
- AMDGPU reads v3+ MessagePack notes. Argument counts include hidden ABI arguments
|
||||
when present; SGPR/VGPR counts and fixed group/private segment sizes retain
|
||||
@@ -119,7 +130,9 @@ A location with `container_id: null` uses original file offsets. Other locations
|
||||
use that inventoried object's decoded byte coordinates. Follow its parent,
|
||||
encoded location and digest to trace decompressed metadata back to a native
|
||||
section and the original file. Decoded offsets are never presented as physical
|
||||
file offsets.
|
||||
file offsets. Reads from an ELF or PE GPU section stay within that declared
|
||||
section extent, so neighboring native section bytes cannot complete a
|
||||
truncated embedded bundle header.
|
||||
|
||||
The CLI uses the same service and contracts:
|
||||
|
||||
|
||||
@@ -36,8 +36,9 @@ The MCP tool `project_harmony_application_graph` takes the same
|
||||
|
||||
- exact component paths, artifact IDs, and hashes (manifests, Ark bytecode,
|
||||
resources, native libraries, JavaScript, signing)
|
||||
- a packaging-model hint: Stage is claimed only from a `module.json` entry,
|
||||
FA only from `config.json`, otherwise `unknown`
|
||||
- a packaging-model hint: Stage is claimed only from a `module.json` entry
|
||||
outside a classified resource path, FA only from `config.json` outside a
|
||||
classified resource path, otherwise `unknown`
|
||||
- FA resources under `assets/<module>/resources/` as well as Stage `resources/`
|
||||
- unavailable component paths with unknown IDs and hashes, including encrypted entries; these make projection coverage partial
|
||||
- App Pack child package paths without recursive inventory
|
||||
|
||||
+22
-17
@@ -206,7 +206,11 @@ image, the single record whose name pointer reaches that image's name string and
|
||||
whose count equals the image's method count. It then requires exactly one
|
||||
`codeGenModulesCount`/`codeGenModules` pair that lists exactly those records. No
|
||||
symbols are needed, so stripped builds work. A method's token row id selects its
|
||||
slot in the module's method pointer array.
|
||||
slot in the module's method pointer array. Within one image, each method must
|
||||
claim a distinct MethodDef token row; duplicate rows are refused before any
|
||||
linked method table or seed output is published. Row ids may repeat across
|
||||
different images, and distinct rows may still resolve to the same address when
|
||||
the binary uses identical-code folding.
|
||||
|
||||
Module records establish names through their declared NUL-terminated string pointers. NUL/0xFF padding, spaces, Unicode and suffix-shared storage are supported. A pointer to a longer string does not match just its suffix; a pointer directly into a shared suffix may name it. Exact reversed UTF-8 matching avoids rescanning the file for every name and retains duplicate-name owners until module records resolve them. A contributor investigated a Linux
|
||||
`GameAssembly.so` from Unity 2023.2.13f1 whose static registration was not located. When no static pair is resolved on x86-64, the reader searches executable ranges for bytes consistent with RIP-relative LEA/MOV references to module-array candidates. Exactly one referenced array candidate must remain. `code_gen_modules_field` is then `null`, and Evidence is **inferred**: instruction boundaries, the absence of a static registration elsewhere, and runtime use were not verified. The module records and slots themselves remain file observations. Static-table associations are **derived**. Otherwise the result is `code-registration-not-found`.
|
||||
@@ -248,7 +252,8 @@ The result adds:
|
||||
field and parameter index with `type`, the index into the binary's
|
||||
`Il2CppType` table, and `name`, rendered from that table: C# keywords for
|
||||
primitives (`int`, `string`, `object`), `Namespace.Outer+Inner` for type
|
||||
definitions, `T[]`, `T[,]` and `T*`, generic instances as
|
||||
definitions, `T[]` for vectors, `T[*]` for rank-one multidimensional arrays,
|
||||
`T[,]` for rank-two arrays and `T*`, generic instances as
|
||||
`System.Collections.Generic.List<string>` and generic parameters by their
|
||||
metadata name (`TKey`). A trailing `&` marks a by-reference type. `name` is
|
||||
`null` only for type index `-1`.
|
||||
@@ -268,21 +273,21 @@ Il2CppType byref flag in different bits, so a rendered name could gain or lose
|
||||
|
||||
The builds checked are listed after the module search rules above.
|
||||
|
||||
| Reason | Category | Meaning |
|
||||
| --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `unsupported-container` | unsupported | Not a 64-bit little-endian Mach-O, PE or ELF image (ELF: AArch64 or x86-64 with dynamic section). |
|
||||
| `slice-selection-required` | unsupported | A universal Mach-O was given without `binary_architecture`. |
|
||||
| `slice-not-found` | unsupported | The requested slice is absent, or a thin binary was given an architecture. |
|
||||
| `unsupported-pointer-encoding` | unsupported | Mach-O chained fixups or Android packed (APS2) ELF relocations; pointers not decoded. |
|
||||
| `malformed-container` | input error | The container headers are damaged. |
|
||||
| `code-gen-modules-not-found` | input error | An image has no module record; often a binary from a different build. |
|
||||
| `ambiguous-code-gen-module` | input error | More than one module record matches an image. |
|
||||
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
|
||||
| `invalid-method-token` | input error | A method token is not a method row inside its module's pointer count. |
|
||||
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
|
||||
| `metadata-registration-not-found` | input error | No single MetadataRegistration matches the type-definition count. |
|
||||
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
|
||||
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
|
||||
| Reason | Category | Meaning |
|
||||
| --------------------------------- | ----------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `unsupported-container` | unsupported | Not a 64-bit little-endian Mach-O, PE or ELF image (ELF: AArch64 or x86-64 with dynamic section). |
|
||||
| `slice-selection-required` | unsupported | A universal Mach-O was given without `binary_architecture`. |
|
||||
| `slice-not-found` | unsupported | The requested slice is absent, or a thin binary was given an architecture. |
|
||||
| `unsupported-pointer-encoding` | unsupported | Mach-O chained fixups or Android packed (APS2) ELF relocations; pointers not decoded. |
|
||||
| `malformed-container` | input error | The container headers are damaged. |
|
||||
| `code-gen-modules-not-found` | input error | An image has no module record; often a binary from a different build. |
|
||||
| `ambiguous-code-gen-module` | input error | More than one module record matches an image. |
|
||||
| `code-registration-not-found` | input error | No unique module table or executable-byte array candidate was resolved. |
|
||||
| `invalid-method-token` | input error | A token is outside its module's method-pointer rows, or a MethodDef row is reused within one image. |
|
||||
| `invalid-method-pointer` | input error | A slot is unavailable or its nonzero address lacks unambiguous executable file coverage. |
|
||||
| `metadata-registration-not-found` | input error | No single MetadataRegistration matches the type-definition count. |
|
||||
| `invalid-type` | input error | A type index or `Il2CppType` record is out of range, unmapped or malformed. |
|
||||
| `unsupported-type-kind` | unsupported | A valid type kind has no implemented rendering. |
|
||||
|
||||
Format and selection failures name `binary_path`; capacity refusals remain typed resource errors. Failed linking preserves the metadata source identity, validated counts, selected binary path/architecture and its SHA-256 when observed, plus prior producer observations. It never publishes an incomplete native table as complete. The binary input is bounded to1GiB.
|
||||
|
||||
|
||||
+2
-1
@@ -62,7 +62,8 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
|
||||
| Use CLI/MCP and retain evidence | [CLI](cli.md), [MCP contracts](mcp-contracts.md), [agent prompts](mcp-prompts.md), [obligation ledgers](reconstruction-obligation-ledgers.md) |
|
||||
|
||||
For development, use [testing](testing.md), [client verification](client-verification.md),
|
||||
[tool design](tool-design.md), and [releasing](releasing.md).
|
||||
[tool design](tool-design.md), [admission limits and recovery](design/limits-and-recovery.md),
|
||||
and [releasing](releasing.md).
|
||||
[Reconstruction readiness](reconstruction-readiness.md) describes a development
|
||||
conformance verifier. [The 6.2 migration](migration-6.2.md) records that release's
|
||||
compatibility changes; [the roadmap](roadmap.md) tracks future work.
|
||||
|
||||
+63
-5
@@ -135,6 +135,15 @@ exits 1. Installing missing Hopper non-interactively additionally requires
|
||||
rea setup --yes --all-detected --install-hopper --json
|
||||
```
|
||||
|
||||
Client configuration writes detect changes to the file read by setup, including
|
||||
when a matching recovery backup already exists. A detected conflict reports
|
||||
`reason: "conflict"` and preserves the newer file; review it and rerun setup.
|
||||
Rollback likewise requires the file identity and content installed by that
|
||||
transaction. Requests for the same resolved path are serialized within one REA
|
||||
process. These checks do not provide an operating-system compare-and-swap or
|
||||
coordinate arbitrary external writers, so avoid editing client configuration
|
||||
while setup is applying it.
|
||||
|
||||
### Skill destinations
|
||||
|
||||
The bundled skill is installed in each selected client's personal skill directory.
|
||||
@@ -404,6 +413,53 @@ symbolic link is retained and reported rather than followed. Uninstall checks
|
||||
REA's managed shared and Claude Code personal skill locations. See the
|
||||
[CLI guide](cli.md#output-and-exit-status) for exit statuses.
|
||||
|
||||
### Missing dependencies in an npx installation
|
||||
|
||||
If REA exits with `ERR_MODULE_NOT_FOUND` before `doctor` can produce a report,
|
||||
inspect the missing path in the error. A path under npm's `_npx/<key>/node_modules`
|
||||
identifies a package-runner installation. In [#5259](https://github.com/morluto/rea/issues/5259),
|
||||
the missing file was `@modelcontextprotocol/client/dist/index.mjs`. That file is
|
||||
present in a clean Windows installation of REA 6.7.0; its absence alone does not
|
||||
establish an incompatible dependency release or explain how the file went missing.
|
||||
|
||||
`npm cache verify` checks npm's downloaded content cache; it does not restore
|
||||
missing files in an already extracted npx installation. Repeating the same npx
|
||||
command can reuse that incomplete installation. To reinstall only the affected
|
||||
entry:
|
||||
|
||||
1. Stop the REA processes and agent clients using that entry. In the same shell
|
||||
environment as the failing command, identify the cache and its entries:
|
||||
|
||||
```powershell
|
||||
npm.cmd config get cache
|
||||
npm.cmd cache npx ls
|
||||
npm.cmd cache npx info REA_CACHE_KEY
|
||||
```
|
||||
|
||||
Replace `REA_CACHE_KEY` with the key from the failing `_npx` path. Check that
|
||||
`info` reports that location and the expected `rea-agents` version. Its
|
||||
`valid` label describes the cache entry metadata, not the presence of every
|
||||
installed file. These subcommands require npm with `cache npx` support.
|
||||
|
||||
2. Remove that specific entry and rerun the affected version with registry
|
||||
access. For the version reported in #5259:
|
||||
|
||||
```powershell
|
||||
npm.cmd cache npx rm REA_CACHE_KEY
|
||||
npx.cmd --yes rea-agents@6.7.0 doctor --json
|
||||
```
|
||||
|
||||
Keep the key argument: this recovery does not require clearing all npm caches
|
||||
or deleting REA configuration, skills, or Evidence. On Linux/macOS, use `npm`
|
||||
and `npx` without `.cmd`. A JSON doctor report confirms startup recovered;
|
||||
it can still exit nonzero for missing analysis engines or registrations.
|
||||
Restart the affected agent after repairing its cache entry.
|
||||
|
||||
If a fresh installation still loses the file, retain the error, REA/Node/npm
|
||||
versions and installation details in the issue. Check installation logs and,
|
||||
on Windows, security software's protection history before attributing the cause.
|
||||
Do not rename `index.cjs` to `index.mjs` or disable protection to make startup pass.
|
||||
|
||||
### Update the owning installation
|
||||
|
||||
`rea update` installs the exact resolved
|
||||
@@ -640,10 +696,12 @@ manual dependency repair.
|
||||
|
||||
When the package-manager step fails, setup reports the failing command and its
|
||||
captured output, so a package conflict is distinguishable from an authorization
|
||||
failure. On CachyOS, an unversioned compatibility package such as
|
||||
`zlib-ng-compat` may conflict with Hopper's versioned `zlib` dependency. Resolve
|
||||
the package conflict yourself or install Hopper's official package manually,
|
||||
then rerun `rea setup`.
|
||||
failure. Hopper is optional, so setup still applies the approved client
|
||||
registrations and skill, then reports `needs_human` with the Hopper failure. On
|
||||
Arch Linux and CachyOS, the unversioned compatibility package `zlib-ng-compat`
|
||||
may conflict with Hopper's versioned `zlib` dependency. Resolve the package
|
||||
conflict yourself, install Hopper's official package manually, or use another
|
||||
provider such as Ghidra; rerun `rea setup` after installing Hopper.
|
||||
|
||||
The supported Linux demo forwards additional launches into its existing
|
||||
application, even across private displays. REA therefore reserves one Linux
|
||||
@@ -1311,7 +1369,7 @@ equivalent entry point. Setup run through `npx` registers
|
||||
`cmd.exe /d /c` so clients can start the runner's command shim. Neither
|
||||
registration points into the runner's temporary package cache.
|
||||
|
||||
REA supports Node.js 22.x (>=22.19), 24.x (>=24.11), and 26+. Node.js 23, 25, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
|
||||
REA supports Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), and 26.12.1+. Node.js 23, 25, earlier 26.x releases, and prereleases are unsupported. It uses the npm already paired with that runtime and never upgrades Node.js, npm, or Homebrew.
|
||||
|
||||
When REA runs on Bun, the `rea doctor` `node` check reports the Bun version and
|
||||
the Node.js compatibility version Bun declares, and passes when that
|
||||
|
||||
@@ -105,7 +105,9 @@ The direct observations are:
|
||||
- `Debugger.scriptParsed`, establishing that a bounded script location was
|
||||
parsed in the observed execution context;
|
||||
- `Debugger.scriptFailedToParse`, retained only as a bounded invalid-script
|
||||
count;
|
||||
count in `scripts.parse_failed_total`. A well-formed parse-failure
|
||||
event is distinct from malformed protocol data and does not make loaded
|
||||
script coverage unavailable. Older Evidence may omit this count;
|
||||
- `Runtime.executionContextCreated`,
|
||||
`Runtime.executionContextDestroyed`, and
|
||||
`Runtime.executionContextsCleared`, establishing context lifecycle within
|
||||
@@ -119,6 +121,12 @@ handlers, or script-unload events. Those facts remain explicit unknowns.
|
||||
`scriptParsed` proves script presence, not the importing module, feature
|
||||
execution, initialization order, or causality.
|
||||
|
||||
For an authorized file script, `location.file_path` is the verified filesystem
|
||||
identity and optional script-item `reported_url` preserves the exact Inspector
|
||||
URL. Script identity includes that reported URL, so query and fragment
|
||||
variants of one file remain separate observations. Older Evidence may omit
|
||||
`reported_url` and remains readable.
|
||||
|
||||
## Node and Electron roles
|
||||
|
||||
The caller declares one role: `node`, `electron-main`, `electron-preload`, or
|
||||
|
||||
@@ -42,7 +42,7 @@ reaches its budget returns a typed timeout naming the phase and the effective
|
||||
milliseconds, while caller cancellation remains a cancellation. Allow execution,
|
||||
cleanup, and delivery headroom in the outer MCP client deadline.
|
||||
|
||||
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. The official Linux x64 CE build `5.48.0.202610071539` was also verified through real CLI and production stdio MCP native-code workflows; see the [Linux CE audit](../experiments/analysis-efficiency/jeb-linux-native.md) for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
|
||||
Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP. The official Linux x64 CE build `5.48.0.202610071539` was also verified through real CLI and production stdio MCP native-code workflows; see the [Linux CE audit](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/jeb-linux-native.md) for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.
|
||||
|
||||
## Operations
|
||||
|
||||
|
||||
@@ -200,8 +200,19 @@ rather than interpreted according to section order.
|
||||
|
||||
### Selected member inspection
|
||||
|
||||
When whole-assembly member decoding reaches the representation budget, select
|
||||
known declaring types before their signatures and CIL bodies are decoded:
|
||||
Member inspection admits type names, tokens, list ranges/counts, nesting facts,
|
||||
module identity and their output representation before decoding members. If
|
||||
later decoding or projection reaches the 64 MiB representation budget, the
|
||||
result retains this type inventory with `coverage.state: "partial"` and the
|
||||
original `resource-limit` coordinate. It does not return partially admitted
|
||||
member collections: empty fields, methods, references and edges are unexamined,
|
||||
not proof of absence; exported types, user strings and signature-resolution
|
||||
attestation remain null. Summary and retained pages keep this partial coverage.
|
||||
The capacity limit is unchanged. If metadata or the type inventory itself cannot
|
||||
be admitted, the result remains unavailable.
|
||||
|
||||
Use the returned `types[].full_name` values to select declaring types before
|
||||
their signatures and CIL bodies are decoded:
|
||||
|
||||
```bash
|
||||
rea inspect-managed-members /absolute/path/Assembly.dll --type Example.Parser
|
||||
@@ -233,6 +244,14 @@ opposite observation's selection unknown, and application graphs report their
|
||||
selected scope as partial. Neither makes an absence claim about the whole
|
||||
assembly. Older member Evidence without `selection` remains readable.
|
||||
|
||||
For MCP callers, request `detail: "summary"` and page the `types` collection
|
||||
using the returned parent Evidence ID. Select one or more returned full names
|
||||
in a subsequent `inspect_managed_members` request. Selection is a recovery path,
|
||||
not a guarantee: a single type or the assembly-wide member references can still
|
||||
exhaust the representation budget. A successful command exit means the Evidence
|
||||
was returned; inspect `coverage.state` and `coverage.issues` before treating any
|
||||
collection as complete.
|
||||
|
||||
### User strings
|
||||
|
||||
`ldstr` anchors keep their build-local `0x70` tokens. `user_strings.literals`
|
||||
@@ -367,6 +386,13 @@ constants, and bounded shape context. It reports all candidates at the winning
|
||||
score and remains ambiguous when the evidence does not distinguish them. The
|
||||
digest does not itself remap metadata tokens.
|
||||
|
||||
Matched methods also compare aligned call sites. `call-shape` reports opcode and
|
||||
operand-kind differences; `call-target` reports differences in the observed
|
||||
target declaring-type or method name from `call_edges`. Target tokens remain
|
||||
build-local and are not compared across artifacts. Missing or unaligned call
|
||||
edges and missing target names leave that facet unknown; equal names do not
|
||||
establish overload identity or runtime dispatch.
|
||||
|
||||
Tokens are always remapped through observed structure. A caller cannot carry
|
||||
`0x06001234` into a new MVID and assume it names the same method.
|
||||
|
||||
@@ -417,7 +443,10 @@ point identities with supplied native export or function evidence. Name/module
|
||||
matches establish declaration links; they do not establish that a call executed.
|
||||
When either side lacks a usable module identity, a matching name remains
|
||||
unresolved and retains the candidate evidence; REA reports a module mismatch
|
||||
only when both identities are available and differ.
|
||||
only when both identities are available and their format-specific comparison
|
||||
establishes a difference. ELF module names retain case, PE module names compare
|
||||
case-insensitively, and a case-only match for Mach-O or an unknown format stays
|
||||
unresolved because the available Evidence does not establish case behavior.
|
||||
|
||||
Metadata tokens are not native addresses. Native body mapping for C++/CLI,
|
||||
ReadyToRun, NativeAOT, and IL2CPP requires separately verified format and provider
|
||||
@@ -500,6 +529,19 @@ CLI `#GUID` heap values retain their exact 16-byte GUID text. REA does not impos
|
||||
RFC 4122 UUID version or variant bits on MVID, EncId, or EncBaseId because
|
||||
ECMA-335 metadata does not require those bit patterns. Tokens and RVAs are
|
||||
build-local coordinates, not durable cross-build identities or native addresses.
|
||||
Field and method signatures validate embedded TypeDefOrRefOrSpec operands
|
||||
against the artifact's metadata tables. An invalid operand leaves the member
|
||||
name and raw signature digest available while marking that signature malformed
|
||||
and coverage partial.
|
||||
A fat method header's nonzero LocalVarSigTok must name an existing StandAloneSig
|
||||
row whose blob is a structurally valid LocalVarSig. REA validates its local
|
||||
types and exact blob coverage but does not project their identities. An invalid
|
||||
token or malformed signature leaves decoded CIL anchors intact but marks the
|
||||
body malformed and coverage partial. Unsupported local type encodings retain
|
||||
the readable body with partial coverage. Zero continues to mean no locals
|
||||
signature. Comparison detects whether a locals declaration is present, but raw
|
||||
table tokens are not cross-build identities and local type contents are not
|
||||
compared.
|
||||
|
||||
## Decoded-CIL fingerprint
|
||||
|
||||
@@ -525,6 +567,10 @@ Tuple operands use these exact projections:
|
||||
- signed integer and variable operands: decimal strings; floating-point
|
||||
operands: JavaScript's `String` representation of the decoded IEEE value.
|
||||
|
||||
For `ldtoken` MemberRef operands, `operand_kind` follows the parsed member
|
||||
signature (`field` or `method`); if that signature does not establish the kind,
|
||||
the anchor keeps the generic `token` classification.
|
||||
|
||||
Opcode names retain short/long forms and prefix opcodes. This makes the value a
|
||||
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
|
||||
it does not resolve tokens across MVIDs, does not commit full control flow or
|
||||
@@ -532,7 +578,8 @@ exception semantics, and does not replace the raw CIL digest. Cross-build
|
||||
comparison requires equal raw CIL digests as well for exact CIL/signature
|
||||
matches and reports ambiguity when structural evidence is insufficient.
|
||||
|
||||
For example, a decoded `nop; ret` body serializes exactly as
|
||||
For example, a present body (including any valid declared locals signature)
|
||||
whose IL is `nop; ret` serializes exactly as
|
||||
`[["nop","none",null],["ret","none",null]]` and has digest
|
||||
`5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636`.
|
||||
This vector specifies the tuple order, `null` handling, absence of whitespace,
|
||||
@@ -693,6 +740,10 @@ Managed member comparisons report a `metadata` dimension when an observed
|
||||
MethodDef flags or implementation flags value differs, including accessibility
|
||||
or synchronization changes. Observed method-body `init_locals` and `max_stack`
|
||||
differences also contribute to `metadata`, even when CIL decoding is incomplete.
|
||||
For complete bodies, presence or absence of a locals signature also contributes;
|
||||
the locals table token is build-local and is not compared. Local type contents
|
||||
are not currently compared, so this facet does not establish equivalent locals
|
||||
declarations.
|
||||
An unavailable header value keeps body coverage unknown rather than establishing
|
||||
equality or a difference. These comparisons are separate from the CIL/signature
|
||||
matching tiers: identical instructions do not establish unchanged method metadata.
|
||||
|
||||
@@ -997,6 +997,10 @@ bytes and returns the `integrity_contradictions` records for the extracted
|
||||
files. An active ASAR unpacked entry whose companion file is absent cannot be
|
||||
materialized; extraction fails as `unavailable` with that logical path.
|
||||
|
||||
Extraction materializes regular files exposed by the active archive reader.
|
||||
When that reader exposes a nested archive as a file, extraction copies the
|
||||
archive file; inspect and extract that file separately to unpack its contents.
|
||||
|
||||
Packaging tools commonly sign or strip `.asar.unpacked` native binaries after
|
||||
writing the archive header, so their declared hashes no longer match. Electron
|
||||
does not check unpacked companions against those hashes at runtime. Integrity
|
||||
|
||||
+21
-4
@@ -30,6 +30,9 @@ result records the exact selected path, file size, header length, header SHA-256
|
||||
bytes read and read scope. GGUF alignment padding is outside this prefix.
|
||||
`artifact.sha256` and the Evidence subject are null: a header digest is not a
|
||||
commitment to the weights. Different files can have identical headers and sizes.
|
||||
The reported GGUF tensor-data offset is aligned after the tensor directory,
|
||||
including when the directory contains no tensors; `header_length` still ends at
|
||||
the directory, before that padding.
|
||||
|
||||
`hash: "full"` additionally streams the remaining file bytes through SHA-256.
|
||||
It returns a full-file digest and Evidence subject, while retaining the same
|
||||
@@ -44,7 +47,10 @@ that defeats filesystem metadata checks.
|
||||
GGUF reports version, byte order, alignment, typed metadata, and each tensor's
|
||||
name, original type ID, dimensions, relative data offset, file offset and derived
|
||||
byte length. Alignment accepts every positive UINT32 multiple of eight, including
|
||||
non-powers of two. Metadata keys use ASCII lower-snake-case hierarchy components;
|
||||
non-powers of two. Partial Evidence retains explicit alignment once validated;
|
||||
the default alignment becomes known after metadata completes. Cancellation
|
||||
during tensor parsing or hashing retains that fact without claiming finalized
|
||||
digests or file identity. Metadata keys use ASCII lower-snake-case hierarchy components;
|
||||
standard numeric components such as `general.base_model.0.name` are preserved.
|
||||
Scalar metadata is complete. Arrays have declared element types,
|
||||
counts and at most eight preview elements per array, with an explicit
|
||||
@@ -56,13 +62,22 @@ declared float type; negative zero is `"-0"`. Unknown tensor types retain their
|
||||
lengths; affected totals and storage validation remain unknown.
|
||||
|
||||
Safetensors reports the header extent, exact string `__metadata__` values, dtype,
|
||||
shape and declared data ranges. JSON duplicate keys, invalid UTF-8, out-of-file
|
||||
shape and declared data ranges. A null `__metadata__` is treated as omitted;
|
||||
unknown fields on tensor entries are ignored within the reader's 32-level JSON
|
||||
nesting limit, while the required dtype, shape and data offsets are still
|
||||
validated. JSON duplicate keys, invalid UTF-8, out-of-file
|
||||
ranges, overlaps, gaps, trailing unindexed data and known dtype/shape byte-length
|
||||
contradictions are rejected with a typed `file_format_rejected` constraint (public code `target_unavailable`, category `unsupported_target`).
|
||||
A missing, non-regular or otherwise unusable selected path is also `target_unavailable`;
|
||||
host I/O or resource failures on a valid selection, such as `EMFILE` or `EIO`, are
|
||||
`execution_failure` with the path and reason in `details.diagnostics`. Empty
|
||||
tensors and scalar shapes are supported; packed sub-byte types require whole
|
||||
tensors and scalar shapes are supported. The reader retains shape dimensions as
|
||||
exact unsigned 64-bit values, including values above JavaScript's safe-integer
|
||||
range when a zero extent makes storage empty. Shape products are checked in
|
||||
declaration order, as in the
|
||||
[64-bit upstream reader](https://github.com/huggingface/safetensors/blob/e246a2560645b7525f5775669ed816eb57c5bcc8/safetensors/src/tensor.rs#L604-L609);
|
||||
a later zero does not erase an earlier product overflow. Actual file and data
|
||||
offsets must still fit exact host offset precision. Packed sub-byte types require whole
|
||||
byte storage. Unknown dtypes retain declared ranges and an explicit limitation
|
||||
that shape-to-storage validation is unavailable. Each tensor records
|
||||
`storage_validation: "consistent" | "unknown"`; `coverage.storage_sizes` remains
|
||||
@@ -79,7 +94,9 @@ GGUF supports zero extents and scalar shapes, with at most four dimensions;
|
||||
known quantized types still require complete blocks in the first dimension.
|
||||
|
||||
MCP defaults to a compact summary with counts, selected short headline metadata,
|
||||
chat-template presence and derived totals. The full type histogram and all rows
|
||||
chat-template presence (default or named template content) and derived totals.
|
||||
A names-only `tokenizer.chat_templates` inventory does not establish template
|
||||
content. The full type histogram and all rows
|
||||
remain in complete Evidence. Headline metadata uses short keys/types and scalar
|
||||
values; long or compound values remain available through explicit metadata
|
||||
queries. Use the returned `parent_evidence_id`:
|
||||
|
||||
@@ -6,6 +6,15 @@ Use a native provider to discover functions, inspect instructions and pseudocode
|
||||
follow references, and examine database types. Static resources and runtime
|
||||
observation complement that analysis when they answer a specific question.
|
||||
|
||||
`inspect_macho` reports build platforms from the selected slice. Legacy
|
||||
`LC_VERSION_MIN_IPHONEOS`, `LC_VERSION_MIN_TVOS` and
|
||||
`LC_VERSION_MIN_WATCHOS` commands are interpreted with the Mach-O CPU type, so
|
||||
Intel slices report simulator platforms while device CPU slices keep the
|
||||
device platform. A chained-import row ending in `+0x...` is ambiguous in
|
||||
dyld's text output: it may be an addend or part of a literal symbol name. REA
|
||||
keeps the full expression in `raw_name`, sets `name` and `addend` to null, and
|
||||
marks `name_status: "ambiguous"`; it does not synthesize a symbol identity.
|
||||
|
||||
## Start with a function
|
||||
|
||||
Choose an installed provider from [setup](installation.md#hopper). For Ghidra:
|
||||
|
||||
@@ -95,6 +95,8 @@ for field names, display filters, decode-as syntax and pass semantics. Invalid
|
||||
fields or filters return producer diagnostics. An empty result does not prove
|
||||
absence of traffic outside the selected recording, filter or read range.
|
||||
Dissector results are observations, not independently verified device behavior.
|
||||
TShark may resolve a selected alias to its canonical field name in packet output;
|
||||
the report preserves the exact requested selection and the producer's output key.
|
||||
|
||||
Evidence includes source path/digest/size, exact selections, typed packet fields,
|
||||
configured and resolved executable paths/digests/full version banner, arguments and stderr. Warnings remain
|
||||
|
||||
@@ -86,6 +86,9 @@ not process identity or proof of the destination's authenticity.
|
||||
All examined inventories are retained within the resource budget; no fixed
|
||||
200/100/50-item truncation is applied. HTTP transport credentials are excluded
|
||||
without rewriting other URI bytes; ambiguous credential spans cause omission.
|
||||
The URI inventory includes absolute-form HTTP request targets when TShark cannot
|
||||
construct `http.request.full_uri` because no Host header was reported; relative
|
||||
request targets are not promoted to absolute URIs.
|
||||
Counts preserve distinct observed source URIs before omission or coalescing.
|
||||
`coverage: "partial"` records unresolved DNS attribution or URI item loss;
|
||||
component credential exclusion is also counted explicitly. Diagnostics retain
|
||||
|
||||
@@ -52,5 +52,13 @@ share a 64 KiB budget per PKCS#7 payload. Unsupported certificate name
|
||||
encodings, including TeletexString (T.61) and GeneralString, fail explicitly
|
||||
rather than returning names decoded with an assumed character set.
|
||||
|
||||
The structural CMS reader accepts definite lengths and BER indefinite lengths
|
||||
for constructed values, including the outer `ContentInfo` sequence. Primitive
|
||||
values must have definite lengths. Embedded X.509 certificates and signed
|
||||
attributes must also use definite length encoding, as required by their DER
|
||||
profiles. This is a bounded structural subset: it does not implement every BER
|
||||
string form or validate DER canonical encoding, and it does not verify
|
||||
signatures.
|
||||
|
||||
This workflow does not verify signatures, certificate chains, timestamps, or
|
||||
Authenticode digest coverage of image bytes.
|
||||
@@ -269,6 +269,13 @@ while comparisons and trace assertions use the normalized text. Retaining the
|
||||
original adds at most `limits.output_bytes` of text, because it comes from the
|
||||
same admitted PTY chunks rather than a second unbounded stream.
|
||||
|
||||
When path normalization is enabled, aliases apply to selected path identities
|
||||
at clear text delimiters. A selected executable path does not alias a longer
|
||||
sibling filename such as `tool-helper`; selected parent directories still
|
||||
normalize within descendant paths. The comparison contract records these rules as
|
||||
`path-component-boundaries-v2`, so captures made with the earlier prefix rules
|
||||
cannot be compared as if the text had the same meaning.
|
||||
|
||||
Rendered frame `lines` hold the visible rows after normalization, with trailing
|
||||
U+0020 spaces removed. Blank rows and internal spaces remain. `columns` records
|
||||
terminal cell width; normalization and Unicode can make string lengths differ
|
||||
@@ -335,6 +342,10 @@ validation is recorded as `unverified` with its reason. Cancellation before any
|
||||
deadline keeps the existing terminal kill. Descendants are not signalled during
|
||||
finalization; owned-process cleanup handles them afterwards.
|
||||
|
||||
For an `exited` capture, `exit.code` is the target's normal exit code. It is
|
||||
`null` when the target ended on a signal, such as one sent by a scenario
|
||||
`signal` event, and the observed signal stays in `exit.signal`.
|
||||
|
||||
`exit.reason` keeps the initiating deadline (`timeout` or `idle_timeout`).
|
||||
`exit.code` is `null` for every deadline exit; the code of an exit observed
|
||||
during finalization is `exit.finalization.exit_code`, and the observed signal
|
||||
|
||||
@@ -19,7 +19,8 @@ the decoded PYZ image for PYZ rows. A CArchive directory record starts at
|
||||
`archive_start + toc_offset + directory_record_offset`; its size is
|
||||
`directory_record_size`. PYZ row directory extents remain unknown.
|
||||
|
||||
Namespace packages remain directories. Their `relative_offset` and
|
||||
Namespace packages remain metadata declarations at their projected query path;
|
||||
they do not claim a destination directory. Their `relative_offset` and
|
||||
`compressed_size` preserve any payload declared by older producers, while
|
||||
`declared_size` is zero because no bytecode is materialized for a namespace.
|
||||
Every declared span must end before the PYZ table of contents.
|
||||
@@ -49,6 +50,15 @@ table index; original names remain available in the supplied artifact.
|
||||
Path failures use bounded escaped previews and report the original length;
|
||||
accepted paths and retained source names are not shortened.
|
||||
|
||||
`compare_artifacts` compares these node-less records as declarations using the
|
||||
container, container name, source name and type code. Equal option or dependency
|
||||
declarations can therefore compare unchanged without implying that an external
|
||||
dependency's bytes were found. For materialized PyInstaller members, the same
|
||||
fields participate in the metadata delta independently of content identity;
|
||||
archive offsets and table positions remain location facts, not declaration
|
||||
identity. Missing declaration facts and unavailable non-metadata content remain
|
||||
unknown.
|
||||
|
||||
Request `detail: "summary"` for a compact inspection, then query retained
|
||||
Evidence with `inspect_analysis_view`. An inventory view can select
|
||||
`collection: "occurrences"`, `entry_kinds: ["metadata"]`, exact logical paths or
|
||||
@@ -95,8 +105,13 @@ returns a resource failure.
|
||||
|
||||
Supported final Python bytecode magic values cover Python 3.7 through 3.14.
|
||||
Supported marshal directory values are lists, tuples, strings, integers and
|
||||
bytes. An unsupported magic or directory stays an opaque member with a
|
||||
limitation and `pyz_status: "opaque"`; its module contents remain unknown.
|
||||
bytes. Directory values follow the identified CPython marshal semantics; for
|
||||
example, a long integer with a zero most-significant digit is malformed and is
|
||||
not normalized into a valid table value. An unsupported magic or malformed or
|
||||
unsupported directory stays an opaque member with a limitation and
|
||||
`pyz_status: "opaque"`; no child entries from that table are projected, and
|
||||
their module contents remain unknown. The original PYZ member and its digest
|
||||
remain available independently. Healthy CArchive siblings are still inventoried.
|
||||
Any opaque PYZ makes producer traversal coverage unknown, including retained
|
||||
summaries and exhausted queries. Per-archive reasons remain on the opaque
|
||||
occurrences; the coverage observation summarizes their count without repeating
|
||||
|
||||
+53
-16
@@ -1,10 +1,10 @@
|
||||
# CPython pyc inspection
|
||||
|
||||
`inspect_python_pyc` / `inspect-python-pyc` answers which CPython version wrote
|
||||
one selected local `.pyc` file, how its 16-byte header records source identity,
|
||||
and which nested code objects marshal stored. It reads the file without running
|
||||
bytecode and requires no Python installation, active binary target, Hopper,
|
||||
Ghidra, or IDA.
|
||||
`inspect_python_pyc` / `inspect-python-pyc` reports the supported CPython format
|
||||
and version declared by one selected local `.pyc` file's magic, how its 16-byte
|
||||
header records source identity, and which nested code objects its decoded
|
||||
marshal graph contains. It reads the file without running bytecode and requires
|
||||
no Python installation, active binary target, Hopper, Ghidra, or IDA.
|
||||
|
||||
```bash
|
||||
rea inspect-python-pyc /absolute/path/to/module.pyc --json
|
||||
@@ -28,24 +28,45 @@ The inline Evidence result contains the artifact path, SHA-256, and byte length.
|
||||
- `magic`, the 16-bit little-endian field;
|
||||
- `python_version` and `code_layout`, mapped from the pinned CPython 3.7–3.13
|
||||
table (`py37`, `py38`, or `py311`); 3.11 alpha magics before 3494 used
|
||||
other code layouts and are unsupported;
|
||||
other code layouts and are unsupported. The 3.7.0a1/a2 magics 3390/3391
|
||||
used 12-byte headers and are outside this reader's supported format;
|
||||
- `flags`, `hash_based`, and `check_source` from PEP 552;
|
||||
- `source_hash_hex` for hash-based pycs, or `source_mtime` and `source_size`
|
||||
for timestamp pycs.
|
||||
|
||||
`code_objects` lists each distinct marshal code object once, the top-level
|
||||
module code first. `nested_code` holds the indexes of the distinct code objects
|
||||
reachable from that object's constants, including code nested in tuples and
|
||||
other containers and the owner itself when a constant container reaches it.
|
||||
`code_objects` lists each code object reachable in the decoded marshal graph
|
||||
once, the top-level module code first. This follows CPython container behavior;
|
||||
it is not an inventory of raw serialized code-object records. `nested_code`
|
||||
holds the indexes of the distinct code objects reachable from that object's
|
||||
constants, including code nested in tuples and other containers and the owner
|
||||
itself when a constant container reaches it.
|
||||
A function shared through a marshal reference is named by index once per owner,
|
||||
even when that owner reaches it again through the same container. Relationships
|
||||
are collected after the marshal graph is complete, so a container that a nested
|
||||
code object also references is not summarized while its items are still missing.
|
||||
Each object reports name, optional qualname,
|
||||
filename,
|
||||
argument counts, flags, names, locals (or `localsplusnames` on 3.11+), const
|
||||
filename, the signed 32-bit `co_firstlineno` value, argument counts, flags,
|
||||
names, locals (or `localsplusnames` on 3.11+), const
|
||||
summaries, and bytecode length plus SHA-256.
|
||||
|
||||
Code-object equality used by sets and dictionaries follows the selected CPython format:
|
||||
3.7–3.10 derive `CO_NOFREE` from the free/cell tables for equality, while
|
||||
3.11+ compare raw flags; raw `co_flags` remain in reported facts. The older
|
||||
layouts ignore line tables, while 3.11+ include line and exception tables and
|
||||
skip inline-cache code units according to the cache table for the exact magic.
|
||||
Reported bytecode length and SHA-256 still describe the original code bytes.
|
||||
This does not emulate CPython's deoptimization of specialized or instrumented
|
||||
opcode bytes in handcrafted pyc files.
|
||||
The artifact SHA-256 identifies the exact selected `.pyc` bytes independently
|
||||
of this decoded object graph. Reference slots follow each tag's CPython behavior:
|
||||
singleton values and `TYPE_REF` do not reserve new slots even when their tag
|
||||
has `FLAG_REF` set.
|
||||
|
||||
Reported names are JavaScript display strings; distinct CPython Unicode names
|
||||
can therefore have the same displayed value and cannot always be reconstructed
|
||||
from that field alone. Code equality uses the retained marshal string bytes,
|
||||
while the artifact SHA-256 continues to identify the original file bytes.
|
||||
|
||||
Bytecode is not decompiled. Const strings may be truncated in the summary while
|
||||
the marshal walk still consumed the original bytes; a string summary's `bytes`
|
||||
is that marshal length. Integer consts report `bits`; their decimal `value` is
|
||||
@@ -53,16 +74,32 @@ is that marshal length. Integer consts report `bits`; their decimal `value` is
|
||||
`-inf`, and `nan` spellings. Dictionary and set lengths follow CPython:
|
||||
unhashable keys and members fail the walk, equal set members count once, equal
|
||||
dictionary keys keep the first key object and replace only its value, code
|
||||
objects are valid keys and members, and a null dictionary value ends the dict
|
||||
without that entry.
|
||||
objects are valid keys and members only when all of their consts are hashable,
|
||||
and a null dictionary value ends the dict without that entry.
|
||||
|
||||
## Limits and failures
|
||||
|
||||
The selected file is limited to 16 MiB by default (64 MiB maximum). Marshal
|
||||
object count, nesting depth, and per-string size are bounded; exceeding a
|
||||
budget fails without partial success. Argument counts, positional-only counts,
|
||||
keyword-only counts, local counts, and stack size are signed CPython fields; a
|
||||
negative encoding is malformed. On the 3.11+ layout, `co_code` must cover a
|
||||
keyword-only counts, and local counts must be non-negative. `co_stacksize` and
|
||||
`co_flags` are retained as signed 32-bit source fields when the selected CPython
|
||||
constructor accepted them. CPython 3.7 and 3.8 alpha constructors through
|
||||
3.8.0a4 did not reject negative stack sizes or flags; the check was added by
|
||||
[CPython commit a9f05d69](https://github.com/python/cpython/commit/a9f05d69ccbf3c75cdd604c25094282697789a62).
|
||||
Magic 3410 is ambiguous: 3.8.0a4 and 3.8.0b1 both use it, but they are on
|
||||
opposite sides of that constructor change. Negative values under magic 3410
|
||||
are preserved with an explicit limitation because the header cannot identify
|
||||
which constructor revision produced them. Magic 3411 and later 3.8 magics were
|
||||
introduced after that change, so their constructors reject negative values.
|
||||
The pinned magic entries distinguish these cases; see the CPython
|
||||
[3.7.0 constructor](https://github.com/python/cpython/blob/v3.7.0/Objects/codeobject.c),
|
||||
[3.8.0a4 constructor](https://github.com/python/cpython/blob/v3.8.0a4/Objects/codeobject.c),
|
||||
[3.8.0b1 constructor](https://github.com/python/cpython/blob/v3.8.0b1/Objects/codeobject.c),
|
||||
and [3.8.0b2 magic table](https://github.com/python/cpython/blob/v3.8.0b2/Lib/importlib/_bootstrap_external.py).
|
||||
Complete marshal coverage means all fields in the selected file were decoded;
|
||||
it does not establish that CPython will accept or execute the code object. On
|
||||
the 3.11+ layout, `co_code` must cover a
|
||||
whole number of code units. Every layout rejects argument counts that do not
|
||||
fit the local table CPython checks (`co_varnames`, including `*args` /
|
||||
`**kwargs`, or fast locals on 3.11+). Those files are malformed, so the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · **العربية** · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: هندسة عكسية لأي شيء
|
||||
|
||||
@@ -137,7 +137,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## ما الذي يمكنك تحليله
|
||||
|
||||
يتطلب REA إصدار Node.js 22.x (>=22.19) أو 24.x (>=24.11) أو 26+، إضافة إلى npm. تعتمد الأدوات الإضافية والأنظمة المضيفة المدعومة على الهدف:
|
||||
يتطلب REA إصدار Node.js 22.x (>=22.23.4) أو 24.x (>=24.22.1) أو 26+ (>=26.12.1)، إضافة إلى npm. تعتمد الأدوات الإضافية والأنظمة المضيفة المدعومة على الهدف:
|
||||
|
||||
| الهدف | ما يعيده REA | المتطلبات والدليل |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · **Deutsch** · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Alles reverse-engineeren
|
||||
|
||||
@@ -137,7 +137,7 @@ Dein Agent ruft REA über MCP auf, um das Ziel zu untersuchen und relevanten Cod
|
||||
|
||||
## Was du analysieren kannst
|
||||
|
||||
REA benötigt Node.js 22.x (>=22.19), 24.x (>=24.11) oder 26+ sowie npm. Zusätzliche Werkzeuge und unterstützte Hostsysteme hängen vom Ziel ab:
|
||||
REA benötigt Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) oder 26+ (>=26.12.1) sowie npm. Zusätzliche Werkzeuge und unterstützte Hostsysteme hängen vom Ziel ab:
|
||||
|
||||
| Ziel | Was REA zurückgibt | Voraussetzungen und Anleitung |
|
||||
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md) · **Ελληνικά** · [മലയാളം](README_ml.md)
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md) · **Ελληνικά** · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -141,7 +141,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Τι μπορείς να αναλύσεις
|
||||
|
||||
Το REA απαιτεί Node.js 22.x (>=22.19), 24.x (>=24.11) ή 26+, καθώς και npm. Τα πρόσθετα εργαλεία και τα υποστηριζόμενα συστήματα εξαρτώνται από τον στόχο:
|
||||
Το REA απαιτεί Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ή 26+ (>=26.12.1), καθώς και npm. Τα πρόσθετα εργαλεία και τα υποστηριζόμενα συστήματα εξαρτώνται από τον στόχο:
|
||||
|
||||
| Στόχος | Τι επιστρέφει το REA | Απαιτήσεις και οδηγός |
|
||||
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · **Español** · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Ingeniería inversa de cualquier cosa
|
||||
|
||||
@@ -137,7 +137,7 @@ Tu agente llama a REA mediante MCP para inspeccionar el objetivo y seguir el có
|
||||
|
||||
## Qué puedes analizar
|
||||
|
||||
REA requiere Node.js 22.x (>=22.19), 24.x (>=24.11) o 26+, además de npm. Las herramientas adicionales y los sistemas anfitriones compatibles dependen del objetivo:
|
||||
REA requiere Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), además de npm. Las herramientas adicionales y los sistemas anfitriones compatibles dependen del objetivo:
|
||||
|
||||
| Objetivo | Qué devuelve REA | Requisitos y guía |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center" dir="rtl">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · **فارسی** · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: مهندسی معکوس هر چیزی
|
||||
|
||||
@@ -129,7 +129,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## چه چیزهایی را میتوان تحلیل کرد؟
|
||||
|
||||
REA به Node.js 22.x (>=22.19)، 24.x (>=24.11) یا 26+، بههمراه npm نیاز دارد. ابزارهای اضافی و پشتیبانی سیستم میزبان به نوع هدف بستگی دارند:
|
||||
REA به Node.js 22.x (>=22.23.4)، 24.x (>=24.22.1) یا 26+ (>=26.12.1)، بههمراه npm نیاز دارد. ابزارهای اضافی و پشتیبانی سیستم میزبان به نوع هدف بستگی دارند:
|
||||
|
||||
| هدف | خروجی REA | پیشنیازها و راهنما |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · **Français** · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA : rétro-ingénierie de tout
|
||||
|
||||
@@ -140,7 +140,7 @@ Votre agent appelle REA via MCP pour inspecter la cible et suivre le code pertin
|
||||
|
||||
## Ce que vous pouvez analyser
|
||||
|
||||
REA nécessite Node.js 22.x (>=22.19), 24.x (>=24.11) ou 26+, ainsi que npm. Les outils supplémentaires et les hôtes pris en charge dépendent de la cible :
|
||||
REA nécessite Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ou 26+ (>=26.12.1), ainsi que npm. Les outils supplémentaires et les hôtes pris en charge dépendent de la cible :
|
||||
|
||||
| Cible | Ce que REA renvoie | Prérequis et guide |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · **Bahasa Indonesia** · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Rekayasa Balik Apa Pun
|
||||
|
||||
@@ -141,7 +141,7 @@ Perintah CLI menggunakan alur kerja yang sama.
|
||||
|
||||
## Hal yang Dapat Anda Analisis
|
||||
|
||||
REA memerlukan Node.js 22.x (>=22.19), 24.x (>=24.11), atau 26+, beserta npm.
|
||||
REA memerlukan Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), atau 26+ (>=26.12.1), beserta npm.
|
||||
|
||||
Alat tambahan dan dukungan sistem host bergantung pada target yang dianalisis:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · **Italiano**
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -140,7 +140,7 @@ Il tuo agente chiama REA tramite MCP per ispezionare il bersaglio e tracciare il
|
||||
|
||||
## Cosa puoi analizzare
|
||||
|
||||
REA richiede Node.js 22.x (>=22.19), 24.x (>=24.11) o 26+, oltre a npm. Strumenti aggiuntivi e supporto host dipendono dal bersaglio:
|
||||
REA richiede Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) o 26+ (>=26.12.1), oltre a npm. Strumenti aggiuntivi e supporto host dipendono dal bersaglio:
|
||||
|
||||
| Obiettivo | Cosa restituisce REA | Requisiti e guida |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · **日本語** · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA:あらゆるものをリバースエンジニアリング
|
||||
|
||||
@@ -137,7 +137,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 分析できる対象
|
||||
|
||||
REA には Node.js 22.x(>=22.19)、24.x(>=24.11)、または 26+ と npm が必要です。追加のツールや対応ホストは、対象によって異なります。
|
||||
REA には Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)、または 26+ (>=26.12.1) と npm が必要です。追加のツールや対応ホストは、対象によって異なります。
|
||||
|
||||
| 対象 | REA が返すもの | 要件とガイド |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · **한국어** · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: 무엇이든 리버스 엔지니어링
|
||||
|
||||
@@ -145,7 +145,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 분석할 수 있는 대상
|
||||
|
||||
REA에는 Node.js 22.x(>=22.19), 24.x(>=24.11) 또는 26+와 npm이 필요합니다. 추가 도구와 호스트 지원은 대상에 따라 다릅니다.
|
||||
REA에는 Node.js 22.x(>=22.23.4), 24.x(>=24.22.1) 또는 26+ (>=26.12.1)와 npm이 필요합니다. 추가 도구와 호스트 지원은 대상에 따라 다릅니다.
|
||||
|
||||
| 대상 | REA가 반환하는 내용 | 요구 사항 및 가이드 |
|
||||
| ---------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · **മലയാളം**
|
||||
· [Ελληνικά](README_el.md) · **മലയാളം** · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: എന്തും Reverse Engineer ചെയ്യുക
|
||||
|
||||
@@ -143,7 +143,7 @@ Target പരിശോധിക്കാനും പ്രസക്തമായ
|
||||
|
||||
## എന്തൊക്കെ വിശകലനം ചെയ്യാം
|
||||
|
||||
REA-യ്ക്ക് Node.js 22.x (>=22.19), 24.x (>=24.11), അല്ലെങ്കിൽ 26+ പതിപ്പും npm-ഉം ആവശ്യമാണ്. Target അനുസരിച്ച് അധിക tools-ഉം host support-ഉം വ്യത്യാസപ്പെടും:
|
||||
REA-യ്ക്ക് Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), അല്ലെങ്കിൽ 26+ (>=26.12.1) പതിപ്പും npm-ഉം ആവശ്യമാണ്. Target അനുസരിച്ച് അധിക tools-ഉം host support-ഉം വ്യത്യാസപ്പെടും:
|
||||
|
||||
| Target | REA നൽകുന്ന ഫലങ്ങൾ | ആവശ്യകതകളും ഗൈഡും |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · **Polski** · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -137,7 +137,7 @@ Twój agent wywołuje REA przez MCP, aby analizować cel i śledzić istotny kod
|
||||
|
||||
## Co możesz analizować
|
||||
|
||||
REA wymaga Node.js 22.x (>=22.19), 24.x (>=24.11) lub 26+ oraz npm. Dodatkowe narzędzia i obsługiwane platformy zależą od celu analizy:
|
||||
REA wymaga Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) lub 26+ (>=26.12.1) oraz npm. Dodatkowe narzędzia i obsługiwane platformy zależą od celu analizy:
|
||||
|
||||
| Cel | Co zwraca REA | Wymagania i przewodnik |
|
||||
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · **Português (Brasil)** · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Faça engenharia reversa de qualquer coisa
|
||||
|
||||
@@ -137,7 +137,7 @@ Seu agente chama o REA por MCP para inspecionar o alvo e rastrear o código rele
|
||||
|
||||
## O que você pode analisar
|
||||
|
||||
O REA requer Node.js 22.x (>=22.19), 24.x (>=24.11) ou 26+, além do npm. As ferramentas adicionais e os sistemas anfitriões compatíveis dependem do alvo:
|
||||
O REA requer Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ou 26+ (>=26.12.1), além do npm. As ferramentas adicionais e os sistemas anfitriões compatíveis dependem do alvo:
|
||||
|
||||
| Alvo | O que o REA retorna | Requisitos e guia |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · **Русский** · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Реверс-инжиниринг чего угодно
|
||||
|
||||
@@ -137,7 +137,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Что можно анализировать
|
||||
|
||||
REA требует Node.js 22.x (>=22.19), 24.x (>=24.11) или 26+, а также npm. Дополнительные инструменты и поддерживаемые системы зависят от цели:
|
||||
REA требует Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) или 26+ (>=26.12.1), а также npm. Дополнительные инструменты и поддерживаемые системы зависят от цели:
|
||||
|
||||
| Цель | Что возвращает REA | Требования и руководство |
|
||||
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · **ไทย** · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
@@ -137,7 +137,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## สิ่งที่วิเคราะห์ได้
|
||||
|
||||
REA ต้องใช้ Node.js 22.x (>=22.19), 24.x (>=24.11) หรือ 26+ และ npm เครื่องมือเพิ่มเติมและแพลตฟอร์มที่รองรับขึ้นอยู่กับเป้าหมาย:
|
||||
REA ต้องใช้ Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) หรือ 26+ (>=26.12.1) และ npm เครื่องมือเพิ่มเติมและแพลตฟอร์มที่รองรับขึ้นอยู่กับเป้าหมาย:
|
||||
|
||||
| เป้าหมาย | สิ่งที่ REA ส่งกลับ | ข้อกำหนดและคู่มือ |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · **Türkçe** · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Her şeyi tersine mühendislikle inceleyin
|
||||
|
||||
@@ -139,7 +139,7 @@ Ajanınız, hedefi incelemek ve ilgili kodu izlemek için MCP üzerinden REA'yı
|
||||
|
||||
## Neleri analiz edebilirsiniz
|
||||
|
||||
REA; Node.js 22.x (>=22.19), 24.x (>=24.11) veya 26+ ve npm gerektirir. Ek araçlar ve desteklenen ana bilgisayar sistemleri hedefe bağlıdır:
|
||||
REA; Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) veya 26+ (>=26.12.1) ve npm gerektirir. Ek araçlar ve desteklenen ana bilgisayar sistemleri hedefe bağlıdır:
|
||||
|
||||
| Hedef | REA'nın döndürdükleri | Gereksinimler ve rehber |
|
||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · **Українська** · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Реверс-інжиніринг будь-чого
|
||||
|
||||
@@ -137,7 +137,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## Що можна аналізувати
|
||||
|
||||
REA потребує Node.js 22.x (>=22.19), 24.x (>=24.11) або 26+, а також npm. Додаткові інструменти й підтримувані системи залежать від цілі:
|
||||
REA потребує Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) або 26+ (>=26.12.1), а також npm. Додаткові інструменти й підтримувані системи залежать від цілі:
|
||||
|
||||
| Ціль | Що повертає REA | Вимоги й посібник |
|
||||
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · **Roman Urdu**
|
||||
|
||||
# REA: Reverse Engineer Anything
|
||||
|
||||
### Binaries, applications aur runtime behavior ki reverse engineering ke liye ek MCP.
|
||||
|
||||
**Koi feature pasand aaya? Samjhein ke woh kaise kaam karta hai, binary level tak.**
|
||||
|
||||
[](https://www.npmjs.com/package/rea-agents)
|
||||
[](../mcp-contracts.md#generated-catalog)
|
||||
[](#kya-kuch-analyze-kar-sakte-hain)
|
||||
[](https://skills.sh/morluto/rea/reverse-engineer-anything)
|
||||
[](../../LICENSE)
|
||||
[](https://discord.gg/GkcryMnJDM)
|
||||
|
||||
<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://rea.tools/) · [Guides](https://rea.tools/guides/) · [Showcases](https://rea.tools/showcase/)**
|
||||
|
||||
[Jaldi shuru karein](#jaldi-shuru-karein) · [REA kaise kaam karta hai](#rea-kaise-kaam-karta-hai) · [Kya kuch analyze kar sakte hain](#kya-kuch-analyze-kar-sakte-hain) · [Amli misaalein](#amli-misaalein) · [FAQ](#faq) · [Documentation](#documentation)
|
||||
|
||||
<code>npx rea-agents setup</code>
|
||||
|
||||
</div>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> REA [morluto/rea](https://github.com/morluto/rea),
|
||||
> [rea.tools](https://rea.tools/) ya [rea-agents npm package](https://www.npmjs.com/package/rea-agents) se hasil karein.
|
||||
> Installation aur release ke links inhi official sources se kholein. Sirf milta-julta
|
||||
> repository naam ya copy kiya hua README kisi download ko official sabit nahi karta.
|
||||
|
||||
<div align="center">
|
||||
|
||||
<br />
|
||||
|
||||
<img src="../assets/rea-hopper-analysis.png" alt="REA ek native binary inspect karte hue Hopper ke andar apna analysis bridge shuru kar raha hai" width="1200" />
|
||||
|
||||
<br /><br />
|
||||
|
||||
<table aria-label="REA community">
|
||||
<tr>
|
||||
<td align="center" width="360">
|
||||
<a href="https://discord.gg/GkcryMnJDM">
|
||||
<img src="../assets/discord.svg" height="42" alt="Discord" /><br />
|
||||
<strong>Reverse Engineering Community mein shamil hon</strong>
|
||||
</a><br />
|
||||
<sub>Discord · Sawal jawab · Apna kaam dikhayein</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Kisi app mein aisa feature dekha jo aap apne product mein chahte hain? Apne agent se kahein ke REA ke zariye uski jaanch kare. Woh app ka source code mile baghair bhi use inspect kar sakta hai, feature kaam kaise karta hai samjha sakta hai, saboot dikha sakta hai aur aapke project ke liye uska ek version bana sakta hai.
|
||||
|
||||
REA aapke agent ko native binaries, JavaScript aur Electron apps, .NET assemblies aur websites inspect karne wale tools se jorta hai. Yehi tools terminal se bhi use kar sakte hain. Analysis local machine par hota hai, aur har natije ke saath uske saboot aur limitations bhi milti hain.
|
||||
|
||||
Setup REA ko aapke agent ke saath register karta hai aur uske mutabiq workflow instructions install karta hai. Native analysis ke liye pehle se installed Hopper, Ghidra ya IDA use ho sakta hai; aapki approval par setup Hopper bhi install kar sakta hai. Static JavaScript analysis ke liye native analysis engine ki zaroorat nahi.
|
||||
|
||||
> Setup instructions, tasveeron ke saath guides aur asli case studies ke liye **[REA ki website dekhein](https://rea.tools/)**.
|
||||
|
||||
## Jaldi shuru karein
|
||||
|
||||
Linux par Ghidra, JADX, firmware tools shuru karne aur static JavaScript/Electron
|
||||
analysis ke liye REA ke `PATH` par procps-compatible `ps` hona chahiye, taake
|
||||
process ownership check ho sake. Masla hal karne ke steps `docs/installation.md`
|
||||
ke "Linux owned-process prerequisite" section mein hain.
|
||||
|
||||
### Apna agent setup karein
|
||||
|
||||
Node.js aur npm installed hon to yeh command chalayein:
|
||||
|
||||
```bash
|
||||
npx rea-agents setup
|
||||
```
|
||||
|
||||
Apne agents select karein, proposed changes dekhein aur approve karein. Setup
|
||||
REA ka MCP server aur matching workflow instructions add karta hai, aur existing
|
||||
configuration ke backups banata hai. Iske baad apna agent restart karein.
|
||||
|
||||
Setup Claude Code, Codex, Cursor, Gemini CLI, Grok Build aur
|
||||
[doosre agents](../installation.md#supported-agents) support karta hai. Provider
|
||||
configuration aur manual MCP registration ke liye
|
||||
[installation aur setup](../installation.md) dekhein.
|
||||
|
||||
Context overhead kam rakhne ke liye MCP default mein compact input schemas use
|
||||
karta hai. Agar tamam advertised fields ki guidance aur nested validation details
|
||||
chahiye hon to apni MCP registration mein `REA_MCP_INPUT_SCHEMA_PROFILE=full`
|
||||
set karein. Dono profiles mein tamam tools aur canonical server validation
|
||||
maujood rehte hain. MCP contracts guide ka "Compact input schema profile"
|
||||
section dekhein.
|
||||
|
||||
### Apne agent se kahein
|
||||
|
||||
```text
|
||||
Samjho ke Notes app mein search kaise kaam karti hai, mujhe saboot dikhao aur
|
||||
mere project ke liye aisa hi feature banao.
|
||||
```
|
||||
|
||||
Notes ki jagah apni target app aur jis feature ko samajhna hai uska naam likhein.
|
||||
|
||||
### Terminal se use karein
|
||||
|
||||
Extract ki hui JavaScript/Electron app directory ya ASAR inspect karein:
|
||||
|
||||
```bash
|
||||
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||
```
|
||||
|
||||
Natije mein modules, imports, Electron boundaries aur unke saboot milte hain.
|
||||
Path ko apne target se badlein, jaise Windows par `"D:/apps/example"`.
|
||||
|
||||
Rozmarra use ke liye `rea` command install karne ke liye:
|
||||
|
||||
```bash
|
||||
npm install --global rea-agents
|
||||
rea --help
|
||||
```
|
||||
|
||||
Native analysis se pehle provider configure karein. Native commands, provider
|
||||
selection, snapshots aur scripting ke liye
|
||||
[CLI aur Evidence guide](../cli.md) dekhein.
|
||||
|
||||
### REA update karein
|
||||
|
||||
REA mein tezi se changes aate hain aur naye releases mein aksar bug fixes hoti
|
||||
hain. Apni installation updated rakhein.
|
||||
|
||||
Agar CLI `npm install --global` ya `bun add --global` se install ki hai:
|
||||
|
||||
```bash
|
||||
rea update
|
||||
```
|
||||
|
||||
`rea --update` isi verified workflow ka alias hai aur wohi global output flags
|
||||
accept karta hai. Doosre command controls ke saath use karte waqt `rea update`
|
||||
likhein.
|
||||
|
||||
Updater commands stdout aur stderr mila kar zyada se zyada 4 MiB retain karti
|
||||
hain. Command khatam hone ke baad is had se bade metadata replies reject ho
|
||||
jate hain; verbose installs bounded diagnostics ke saath jari rehti hain.
|
||||
Insaan ke liye progress output terminal ki backpressure ka khayal rakhta hai
|
||||
aur bheji hui writes mukammal hone ka intezar karta hai.
|
||||
|
||||
Bun global updates ke liye Bun 1.2.17 ya naya version chahiye. Purana Bun ho
|
||||
to `rea update` chalane se pehle Bun manually upgrade karein.
|
||||
|
||||
Agent registrations aur skill refresh karne ke liye update ke output mein
|
||||
di hui setup command chalayein.
|
||||
|
||||
Agar aap `npx` use karte hain to agent setup is tarah update karein:
|
||||
|
||||
```bash
|
||||
npx rea-agents@latest setup
|
||||
```
|
||||
|
||||
Setup changes review karein aur agent restart karein. Ek dafa chalane wali CLI
|
||||
commands ke liye `npx rea-agents@latest` ke baad command likhein. Bun ke saath
|
||||
`npx rea-agents@latest` ki jagah `bunx rea-agents@latest` use karein.
|
||||
|
||||
## REA kaise kaam karta hai
|
||||
|
||||
Aapka agent MCP ke zariye REA ko call karta hai, taake target inspect kare aur
|
||||
relevant code trace kare. REA apni findings saboot ke saath wapas deta hai.
|
||||
Agent unki bunyaad par agle sawal poochta hai, behavior samjhata hai ya
|
||||
implementation likh kar test karta hai. CLI commands bhi yehi workflows use
|
||||
karti hain.
|
||||
|
||||

|
||||
|
||||
[Puri size mein figure kholein](../../website/public/assets/figures/rea-investigation-flow.svg).
|
||||
|
||||
<a id="current-status"></a>
|
||||
|
||||
## Kya kuch analyze kar sakte hain
|
||||
|
||||
REA ke liye Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) ya 26.12.1+, aur npm chahiye.
|
||||
Mazeed tools aur host support target par depend karte hain:
|
||||
|
||||
| Target | REA kya wapas deta hai | Zarooriyat aur guide |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Desktop installations | Runtime candidates, supporting files, component markers aur agle probes | Built-in bounded directory/header inspection; [desktop applications](../desktop-applications.md) |
|
||||
| Native binaries | Pseudocode, assembly, strings, symbols, calls aur references | Hopper, Ghidra ya IDA; [native analysis](../native-investigation.md) |
|
||||
| Offline ELF layout | Sections, segments, original symbols/relocations aur static mitigation candidates | Linux x64 par aapka diya hua pwntools; [binary diagnostics](../binary-diagnostics.md) |
|
||||
| Go binaries | Embedded compiler version, modules, replacements aur build settings | Built-in static ELF/PE/thin Mach-O inspection; Go install karna zaroori nahi |
|
||||
| GPU code objects | CUDA/HIP targets, selected kernel parameters aur resources, retained PTX text | Built-in static ELF/PE/fatbin/CUBIN/PTX inspection; [GPU code objects](../gpu-code-objects.md) |
|
||||
| Model artifacts | GGUF / safetensors metadata, tensor declarations aur unse nikale hue totals | Built-in header inspection; optional full-file hash; [model files](../model-artifacts.md) |
|
||||
| IL2CPP metadata | Unity global-metadata.dat assembly images, type aur method names aur index ranges | Versions 24, 27, 29, 31 aur 39 ki built-in static decoding; Unity zaroori nahi |
|
||||
| Godot PCK | Archive inventory aur extraction, original header/directory facts aur selected content identities | Built-in static formats 2, 3 aur 4; Godot zaroori nahi |
|
||||
| Rust binaries | Embedded cargo-auditable crate names, versions, sources aur dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](../rust-binaries.md) |
|
||||
| PE certificates | Authenticode WIN_CERTIFICATE entries aur PKCS#7 signer/X.509 identity fields | Built-in static PE inspection; [PE certificates](../pe-certificates.md) |
|
||||
| ELF notes | PT_NOTE/SHT_NOTE owners, types, GNU build-id aur ABI-tag fields | Built-in static ELF inspection; [ELF notes](../elf-notes.md) |
|
||||
| Erlang BEAM | IFF chunks, atoms, exports, imports, lambdas aur Code header | Built-in static BEAM inspection; [Erlang BEAM](../erlang-beam.md) |
|
||||
| WASM artifact | Validation, sections, exact import/export WAT forms aur decoded WAT | Aapka diya hua WABT 1.0.42; `inspect_wasm_artifact` |
|
||||
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments aur mutability | Linux x64 par local raw/hex carrier; [offline EVM guide](../evm-bytecode.md) |
|
||||
| SQLite databases | Tables, columns, indexes, views, triggers aur selected ordinary-table rows | Local database/WAL snapshot; native SQLite limits ke saath Node.js 24.x >=24.15 ya 26+ |
|
||||
| Recorded Linux crashes | Raw notes, har recorded thread ke registers/signals aur optional mapping candidates | Linux x64 par aapka diya hua pwntools; optional GDB/pwndbg; [recorded crashes](../recorded-crashes.md) |
|
||||
| JavaScript / Electron | Modules, imports, source maps, routes, IPC aur native add-on relationships | Node.js aur npm; Linux: procps-compatible `ps`; [application analysis](../javascript-application-workflows.md) |
|
||||
| Websites | Page structure, scripts, network observations aur requested screenshots | Chrome-family browser; [browser analysis](../browser-observation.md) |
|
||||
| Saved network captures | Requests, responses, exposed payloads aur source locations | HAR ya Fiddler Everywhere SAZ; native mitmproxy ke liye Linux par mitmdump; [capture guide](../web-network-captures.md) |
|
||||
| .NET assemblies | Metadata, CIL instructions, declared native dependencies aur build comparisons | Koi additional tool nahi (sirf static analysis); [managed-code guide](../managed-code-analysis.md) |
|
||||
| Android APKs | Manifest declarations, classes, decompiled methods aur references | Linux/macOS/Windows x64 par headless JADX aur full JDK; [Android guide](../android-analysis.md) |
|
||||
| APK signatures | v1/v2/v3/v3.1 signers, certificate fingerprints, key rotation aur digest self-consistency | Koi additional tool nahi (sirf static analysis); `inspect_apk_signature` |
|
||||
| Android devices | Devices, packages, processes, logs, dumps, screen captures aur APK/file transfers | Aapka diya hua adb (emulator ya hardware); [ADB guide](../adb-device-analysis.md) |
|
||||
| JEB projects | Engine-backed project units, artifact digests aur type/method pseudo-code | Aapka shuru kiya hua JEB client jo MCP serve kar raha ho, REA_JEB_MCP_URL se select karein; [JEB guide](../jeb-analysis.md) |
|
||||
| Android resources | Decoded manifest, string tables, locales aur version facts | Aapka diya hua Apktool launcher; [resource guide](../apktool-resource-analysis.md) |
|
||||
| Flutter apps | Dart snapshot hash, per-ABI library digests aur engine build provenance | Koi additional tool nahi (static APK parsing); [Flutter guide](../flutter-analysis.md) |
|
||||
| Firmware | Regions, extraction results aur native-analysis handoffs | Linux par Binwalk / Unblob; [firmware guide](../firmware-analysis.md) |
|
||||
| Recorded packets | Selected fields, repeated values, filters aur decode-as rules | Linux: TShark 4.x + prlimit; [capture guide](../packet-captures.md) |
|
||||
| Packet summaries | Endpoints, DNS, TLS SNI, protocol distribution aur HTTP requests | Aapke diye hue tshark aur capinfos (Linux par verified); [pcap guide](../pcap-analysis.md) |
|
||||
| Recorded logic signals | Decoder annotations, sample ranges aur diagnostics | Linux: sigrok-cli 0.7.2 + prlimit; [signal recordings](../signal-recordings.md) |
|
||||
| Packages aur resources | File inventories, digests, plists, Apple bundle structure aur extracted resources | [Artifact aur JavaScript guide](../javascript-artifact-reconstruction.md), [Apple applications](../apple-application-analysis.md) |
|
||||
| Process behavior | Terminal output, interactions, exit aur filesystem observations aur runs ka comparison | Native PTY ke saath Linux/macOS; [process capture](../process-capture.md) |
|
||||
| PyInstaller executables | CArchive members, Python version, entry scripts aur extracted bytecode | Koi additional tool nahi (sirf static analysis); [Python packaged applications](../python-packaged-applications.md) |
|
||||
|
||||
Static JavaScript aur .NET inspection di hui files ko application chalaye baghair
|
||||
parhti hai. Runtime capture aapki user permissions se selected target ko chalata
|
||||
hai ya uske saath interact karta hai; har runtime guide apne effects batati hai.
|
||||
|
||||
Offline native-binary inspection ke liye REA aapke diye hue GNU objdump aur
|
||||
Rizin bhi support karta hai. Provider setup, CLI commands aur Evidence ki
|
||||
limitations ke liye [reverse-engineering tools guide](../reverse-engineering-tools.md)
|
||||
dekhein. REA in dono providers ko install ya update nahi karta.
|
||||
|
||||
<a id="choosing-a-deep-analysis-provider"></a>
|
||||
|
||||
Native formats aur host support har provider ke liye alag ho sakte hain.
|
||||
[Hopper aur Ghidra setup](../installation.md#hopper),
|
||||
[IDA guide](../ida-provider.md) (optional native MCP bridge samet),
|
||||
[Kuna decompiler guide](../kuna-provider.md) aur
|
||||
[experimental Windows Ghidra support](../windows-ghidra-p0.md) dekhein.
|
||||
Ghidra [16-bit DOS analysis](../ghidra-dos.md) bhi support karta hai.
|
||||
Badi binaries ke liye `REA_GHIDRA_STARTUP_TIMEOUT_MS` ya
|
||||
`REA_HOPPER_STARTUP_TIMEOUT_MS` se provider ki startup deadline barhayein.
|
||||
Provider select karne ke liye [CLI guide](../cli.md#choose-a-provider) dekhein.
|
||||
Latest npm release ke baad add hone wale features ki
|
||||
[release availability](../installation.md#released-package-and-main) check karein.
|
||||
|
||||
## Amli misaalein
|
||||
|
||||
[](https://rea.tools/showcase/)
|
||||
|
||||
### DX-Ball: sound-pan calculation dobara banayein
|
||||
|
||||
Sound call se uske position-to-pan helper tak jayein, instructions inspect
|
||||
karein aur adhoore pseudocode ko C mein badlein. Dobara banaya gaya code 3,205
|
||||
original-x86 cases pass karta hai aur compiled function ke tamam 63 bytes
|
||||
dobara hasil karta hai.
|
||||
|
||||
[Case study parhein](https://rea.tools/showcase/dx-ball/) ·
|
||||
[Reconstruction repository](https://github.com/N0zoM1z0/dx-ball)
|
||||
|
||||
### Notion: Electron clipboard bridge trace karein
|
||||
|
||||
Renderer ki clipboard API dhoondein, preload aur IPC ke raste main process tak
|
||||
uska flow follow karein aur rich clipboard format inspect karein.
|
||||
|
||||
[Case study parhein](https://rea.tools/showcase/notion/)
|
||||
|
||||
### TH04: DOS bullet-ring calculation recover karein
|
||||
|
||||
Original PC-98 game ki 16-bit instructions inspect karein, fixed aur aimed angle
|
||||
calculations recover karein aur dobara banaye gaye C++ ko purane compiler ke
|
||||
output se compare karein.
|
||||
|
||||
[Case study parhein](https://rea.tools/showcase/th04/) ·
|
||||
[Reconstruction repository](https://github.com/N0zoM1z0/th04)
|
||||
|
||||
Agar aapne REA se koi dilchasp kaam kiya hai to hum dekhna chahenge. Apna case
|
||||
[issue](https://github.com/morluto/rea/issues) ya
|
||||
[pull request](https://github.com/morluto/rea/pulls) mein share karein. Target,
|
||||
apna sawal, REA ne kaise madad ki aur kya mila, yeh sab batayein.
|
||||
|
||||
## FAQ
|
||||
|
||||
<details>
|
||||
<summary><strong>Kaun se agents REA use kar sakte hain?</strong></summary>
|
||||
|
||||
Har woh agent jo local MCP servers support karta ho. Setup
|
||||
[supported agents](../installation.md#supported-agents) configure karta hai;
|
||||
doosre clients [manual MCP registration](../installation.md#mcp-registry)
|
||||
use kar sakte hain.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Kya mujhe Hopper, Ghidra ya IDA chahiye?</strong></summary>
|
||||
|
||||
Deep native analysis in mein se ek use karta hai. Static JavaScript aur .NET
|
||||
inspection native analysis engine ke baghair kaam karti hai. Approval ke baad
|
||||
setup Hopper install kar sakta hai; Ghidra aur IDA ke liye aapki existing
|
||||
installations use hoti hain. [Provider setup](../installation.md#hopper) dekhein.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Kya pehle Hopper shuru karna hoga?</strong></summary>
|
||||
|
||||
Jab kisi operation ko Hopper chahiye hota hai to REA use shuru karta hai.
|
||||
macOS par pehli dafa khulne wala dialog demo mode select karne ya license
|
||||
activate karne ko keh sakta hai. Pehle CLI/MCP operation ya unattended job se
|
||||
pehle usi user session mein yeh prompt complete karein. Zaroorat ho to Hopper
|
||||
manually kholein.
|
||||
[Hopper startup aur troubleshooting](../installation.md#launcher-paths-and-troubleshooting)
|
||||
dekhein.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>skills.sh se skill install karne se kya hota hai?</strong></summary>
|
||||
|
||||
Skill aapke agent ko jaanch karne ki instructions deti hai. REA ka MCP server
|
||||
register karne aur matching instructions install karne ke liye
|
||||
`npx rea-agents setup` use karein, phir agent restart karein.
|
||||
[Sirf skill ki installation](../installation.md#skill-only-installation) dekhein.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>REA kis tarah ka code wapas deta hai?</strong></summary>
|
||||
|
||||
Native analysis pseudocode aur assembly wapas deta hai. JavaScript/Electron
|
||||
analysis modules aur unke relationships recover karta hai. Aapka agent in
|
||||
findings se implementation likhta aur test karta hai;
|
||||
[amli misaalein](#amli-misaalein) iska tareeqa dikhati hain.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Kya REA meri app upload karta hai?</strong></summary>
|
||||
|
||||
REA targets ko local machine par analyze karta hai. Aapke agent ko tool results
|
||||
milte hain, aur uske model provider ki apni data policy hoti hai.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Bug mile to kya karun?</strong></summary>
|
||||
|
||||
Pehle update karein; ho sakta hai recent release mein masla hal ho chuka ho.
|
||||
|
||||
Agar CLI `npm install --global` ya `bun add --global` se install ki hai:
|
||||
|
||||
```bash
|
||||
rea update
|
||||
```
|
||||
|
||||
`npx` se agent setup ke liye:
|
||||
|
||||
```bash
|
||||
npx rea-agents@latest setup
|
||||
```
|
||||
|
||||
Agar agent use kar rahe hain to [setup refresh](#rea-update-karein) complete
|
||||
karke agent restart karein. Wohi task dobara try karein. Masla phir bhi ho to
|
||||
REA version, target type, reproduce karne ke steps aur error output ke saath
|
||||
[issue kholein](https://github.com/morluto/rea/issues).
|
||||
|
||||
</details>
|
||||
|
||||
## Documentation
|
||||
|
||||
Website ki [amli guides](https://rea.tools/guides/) se shuru karein.
|
||||
Exact options, prerequisites aur result contracts ke liye:
|
||||
|
||||
- [Installation aur setup](../installation.md): agent registration, provider configuration, updates aur uninstall.
|
||||
- [Readiness aur troubleshooting](../installation.md#check-readiness-for-your-task): ek agent ya analysis engine ka masla diagnose karein.
|
||||
- [CLI aur Evidence](../cli.md): commands, provider selection, snapshots, import/export aur exit statuses.
|
||||
- [MCP contracts](../mcp-contracts.md) aur [agent prompts](../mcp-prompts.md): tool results, sessions aur guided investigations.
|
||||
- [Tool catalog](../mcp-contracts.md#generated-catalog): tools, providers aur CLI commands ki generated inventory.
|
||||
- [Roadmap](../roadmap.md): planned work aur capability trackers.
|
||||
|
||||
Security vulnerabilities [SECURITY.md](../../SECURITY.md) ke zariye report karein.
|
||||
|
||||
## Stars ki history
|
||||
|
||||
🎉 **300,000 GitHub stars — shukriya!**
|
||||
|
||||
REA use karne, bugs report karne, features suggest karne, builds test karne aur
|
||||
fixes contribute karne wale sab logon ka shukriya.
|
||||
|
||||
<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 ke GitHub stars ki history" src="https://api.star-history.com/chart?repos=morluto/rea&type=date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Zimmedarana istemal
|
||||
|
||||
Responsible-use guidance ke liye [DISCLAIMER.md](../../DISCLAIMER.md) dekhein.
|
||||
|
||||
Ghair qanooni ya ghair ijazati istemal ke bare mein project ki position REA ke
|
||||
tamam versions, distribution channels aur platforms par lagu hoti hai; har copy
|
||||
par uske applicable licenses barqarar rehte hain.
|
||||
|
||||
REA qanooni research, taleem, interoperability, software preservation aur aise
|
||||
software ke analysis ke liye hai jo aapka apna ho ya jise examine karne ki aapko
|
||||
ijazat ho.
|
||||
|
||||
## Hissa dalein
|
||||
|
||||
REA mein aapki madad ka khairmaqdam hai! Bug report karne ya feature suggest
|
||||
karne ke liye [issue kholein](https://github.com/morluto/rea/issues), ya code aur
|
||||
docs behtar karne ke liye [pull request bhejein](https://github.com/morluto/rea/pulls).
|
||||
|
||||
Development setup aur checks ke liye [CONTRIBUTING.md](../../CONTRIBUTING.md),
|
||||
verification lanes ke liye [testing](../testing.md) aur project structure ke
|
||||
liye [architecture map](../architecture.mermaid) dekhein.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](../../LICENSE)
|
||||
|
||||
REA ka apna likha hua code MIT ke tehat licensed hai. Third-party material
|
||||
ke apne licenses aur copyright notices barqarar rehte hain. Dependency
|
||||
inventory, preserved license texts aur external tools ki terms ke liye
|
||||
[Third-party notices](../../THIRD_PARTY_NOTICES.md) dekhein.
|
||||
|
||||
IDA Pro / Hex-Rays decompilers, Hopper aur JEB samet **commercial tools qanooni
|
||||
tareeqe se hasil karna aur unke applicable licenses ke mutabiq use karne ki
|
||||
ijazat rakhna zaroori hai**. REA koi tool license, key ya activation ka haq
|
||||
nahi deta. Trials aur demos vendor ki terms ke tehat rehte hain, jin mein user,
|
||||
feature, automation aur output sharing ki pabandiyan bhi shamil hain. Cracked
|
||||
copies, chori ki hui keys, ghair ijazati key sharing aur license restrictions
|
||||
bypass karne ki ijazat nahi. Yeh guidance MIT ya third-party licenses ko
|
||||
tabdeel nahi karti.
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · **Tiếng Việt** · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA: Kỹ thuật dịch ngược cho mọi thứ
|
||||
|
||||
@@ -137,7 +137,7 @@ Tác nhân gọi REA qua MCP để kiểm tra mục tiêu và lần theo mã li
|
||||
|
||||
## Những gì bạn có thể phân tích
|
||||
|
||||
REA yêu cầu Node.js 22.x (>=22.19), 24.x (>=24.11) hoặc 26+, cùng với npm. Công cụ bổ sung và nền tảng chạy REA được hỗ trợ tùy thuộc vào mục tiêu:
|
||||
REA yêu cầu Node.js 22.x (>=22.23.4), 24.x (>=24.22.1) hoặc 26+ (>=26.12.1), cùng với npm. Công cụ bổ sung và nền tảng chạy REA được hỗ trợ tùy thuộc vào mục tiêu:
|
||||
|
||||
| Mục tiêu | REA trả về gì | Yêu cầu và hướng dẫn |
|
||||
| --------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · [简体中文](README_zh.md) · **繁體中文** · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA:逆向一切
|
||||
|
||||
@@ -139,7 +139,7 @@ AI 代理通過 MCP 呼叫 REA,檢查目標並追蹤相關程式碼。REA 返
|
||||
|
||||
## 可以分析什麼
|
||||
|
||||
REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。額外工具和主機支援取決於分析目標:
|
||||
REA 需要 Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)或 26+ (>=26.12.1),以及 npm。額外工具和主機支援取決於分析目標:
|
||||
|
||||
| 目標 | REA 返回的內容 | 要求與指南 |
|
||||
| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
[English](../../README.md) · **简体中文** · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Türkçe](README_tr.md) · [Русский](README_ru.md) · [Tiếng Việt](README_vi.md) · [ไทย](README_th.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · [Українська](README_uk.md) · [Polski](README_pl.md) · [Português (Brasil)](README_pt-BR.md) · [العربية](README_ar.md) · [فارسی](README_fa.md) · [Bahasa Indonesia](README_id.md) · [Italiano](README_it.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md)
|
||||
· [Ελληνικά](README_el.md) · [മലയാളം](README_ml.md) · [Roman Urdu](README_ur-Latn.md)
|
||||
|
||||
# REA:逆向一切
|
||||
|
||||
@@ -139,7 +139,7 @@ npx rea-agents@latest setup
|
||||
|
||||
## 可以分析什么
|
||||
|
||||
REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+,以及 npm。额外工具和主机支持取决于分析目标:
|
||||
REA 需要 Node.js 22.x(>=22.23.4)、24.x(>=24.22.1)或 26+ (>=26.12.1),以及 npm。额外工具和主机支持取决于分析目标:
|
||||
|
||||
| 目标 | REA 返回的内容 | 要求与指南 |
|
||||
| --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
|
||||
+56
-5
@@ -15,20 +15,69 @@ permissions restores the retained tarball and verifier runtime. It installs
|
||||
that exact archive into an empty prefix, then exercises CLI help/discovery and
|
||||
the real stdio MCP catalog/session contract. Installation finishes before the
|
||||
protocol deadline begins, with dependency lifecycle scripts disabled. Only a
|
||||
successful rehearsal enables the job with npm publishing authority. Both jobs
|
||||
use the same release-input action; neither rebuilds or repacks the archive.
|
||||
successful rehearsal enables the job with npm publishing authority. The publisher
|
||||
downloads only
|
||||
the original qualified archive and manifest, validates their source/run/attempt,
|
||||
version, Node identity and streamed digest using Node built-ins, then invokes
|
||||
`npm publish --ignore-scripts` against the official registry. A shared built-in
|
||||
registry reader checks existing bytes and tags afresh on every publisher attempt,
|
||||
including retries after partial publication. It does not install
|
||||
application dependencies, restore verifier code or execute package canaries.
|
||||
Pinned Node/npm, Actions and the runner remain trusted toolchain inputs. Neither
|
||||
job rebuilds or repacks the archive.
|
||||
This rehearsal checks package consumption on the Linux release runner;
|
||||
qualification retains its broader native-host and real-provider checks.
|
||||
|
||||
Dependency policy: the source lock pins the build, while an npm consumer resolves
|
||||
the published package's semver ranges. Consumer installations are not pinned by
|
||||
the source lock. Qualification checks live advisories for the source lock,
|
||||
signatures for npm-supported installed dependencies, and installed identities.
|
||||
Hourly qualification reuse still requires fresh dependency evidence; cached
|
||||
provider qualification is not a current registry security result. The daily
|
||||
scheduled run also checks a fresh installation of the current npm release.
|
||||
|
||||
Both the qualified-archive rehearsal and published-package verification run the
|
||||
same checks on their actual installed consumer tree before executing REA.
|
||||
Installations disable lifecycle scripts and retain the generated consumer lock.
|
||||
Evidence artifacts contain command output, npm-resolved package identities and
|
||||
relationships, a content fingerprint, and version/integrity differences against
|
||||
the source lock. Recovery supplies the original released source directory and
|
||||
SHA independently of the corrected controller checkout.
|
||||
New semver-compatible versions are recorded; changed integrity
|
||||
for a source-locked identity fails. Unsupported registries, malformed output,
|
||||
unavailable registry evidence and exceeded output budgets fail the check.
|
||||
Optional omissions remain explicit. npm does not expose a verified-signature
|
||||
count here, so it remains unknown; these checks do not establish that every
|
||||
platform package or transitive lifecycle download is safe, or detect all malware.
|
||||
|
||||
To reproduce the read-only checks locally, use
|
||||
`node scripts/ci/dependency-security.mjs --mode=source` after installing the
|
||||
locked dependencies, or `--mode=published` for a fresh current-release consumer.
|
||||
`--evidence=PATH` selects the retained evidence directory. The CI job limits
|
||||
registry concurrency to two connections and runs scans sequentially; each
|
||||
command has a three-minute deadline and a 16 MiB output budget. No prior pass is
|
||||
used in place of a live advisory or signature check.
|
||||
Over-budget or interrupted command captures retain failure metadata rather than
|
||||
an unsafe truncated prefix; that evidence is unavailable and cannot pass.
|
||||
|
||||
Before publishing, npm lookup failures and malformed metadata stop the job.
|
||||
An existing version must match the qualified tarball's SHA-512 and intended
|
||||
`latest`/`next` dist-tag before it can be reused. Publication never overwrites
|
||||
an existing version or silently changes a dist-tag. After publication, only
|
||||
confirmed registry absence is retried within the propagation window. Content
|
||||
or tag mismatches fail immediately. The post-publication verifier installs
|
||||
or tag mismatches fail immediately. A separate post-publication job with read-only
|
||||
repository/actions permissions verifies registry bytes and tags, then installs
|
||||
from npm and repeats the same CLI/MCP checks; it also exercises a real update
|
||||
from an older stable release. For a prerelease on `next`, that updater still
|
||||
targets stable `latest`, as the public update command does.
|
||||
targets stable `latest`, as the public update command does. MCP publication
|
||||
depends on that verification job succeeding. Rehearsal, publication and both
|
||||
registry-verification/publication jobs do not inherit remote-cache credentials.
|
||||
|
||||
Qualification records the parsed `latest`/`next` channel in manifest schema v2.
|
||||
Rehearsal validates its exact SemVer mapping; the minimal publisher consumes the
|
||||
qualified channel without a second version parser. Only read-only recovery
|
||||
accepts existing schema-v1 artifacts, preserving their original bytes and
|
||||
source/run identity. New publication requires schema v2.
|
||||
|
||||
The required reverse-tools lane runs fresh coverage and both mutation
|
||||
preflights. Complete mutation sweeps run separately on a schedule or manual
|
||||
@@ -50,7 +99,9 @@ gh workflow run release-recovery.yml --ref main \
|
||||
```
|
||||
|
||||
Select the existing version and its original automatic Release run. Recovery
|
||||
requires that run's successful exact-source CI gate and npm publish step. It
|
||||
requires that run's successful exact-source CI gate plus a successful npm write
|
||||
or registry-byte verification. It recognizes verification in either the older
|
||||
publisher job or the separate post-publication job. It
|
||||
uses the existing release tag, original qualified tarball and verifier runtime;
|
||||
expired or missing artifacts stop recovery. The archive's source, version,
|
||||
original run/attempt and Node version must match, and its SHA-512 must equal
|
||||
|
||||
@@ -35,6 +35,15 @@ Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_t
|
||||
|
||||
Persistent GDB sessions, including the MCP workflow and one-shot `rea debug-with-gdb` CLI, are available only on Linux. REA verifies a live inferior's output descriptors through `/proc` before detaching it; it does not claim equivalent descriptor verification on macOS. Windows is also unsupported because owned Job Object cleanup can terminate debugger inferiors. On supported Linux hosts, `start_gdb_session`, `gdb_console`, `gdb_session_status`, and `close_gdb_session` manage a persistent GDB/MI session in MCP. The one-shot CLI requires a caller-selected durable TTY: `rea debug-with-gdb --inferior-tty /dev/pts/7 'file ./program'`. The TTY must remain available after GDB exits so a detached inferior can continue writing. MCP starts GDB with `--nx --quiet --interpreter=mi3`, retries initialization with MI2 if MI3 initialization returns an error and cleanup succeeds, waits for its prompt, and disables auto-loading. Pass optional `inferior_tty` to select the terminal that future inferior runs use; REA configures it before the session becomes ready and checks the exact value with GDB/MI. `gdb_session_status` reports the selected path, whether REA still considers the configuration verified, and whether invalid UTF-8 has been observed in MI bytes (`mi_invalid_utf8`) or stderr (`stderr_invalid_utf8`). Commands that can change the inferior TTY or redirect a run revoke that verification permanently for the session, so close retains GDB ownership if a live inferior remains. Closing a session with one live inferior and a verified TTY automatically detaches the target, which can resume execution; MCP clients receive destructive-operation annotations for this behavior. The console remains unrestricted and can run shell commands, change target state, access files, and use network in the REA process user's security context. Raw MI records and console/target/log streams are returned with Evidence; command output is retained up to 16 MiB and explicitly marked when truncated. GDB startup waits 10000 ms and each MI command waits 120000 ms. This release has no runtime override for those deadlines, the 16 MiB command output budget, session capacity, or the pending-command queue. `command_timeout_ms` does not change them.
|
||||
|
||||
`completion_status` describes completion of the selected console command, not
|
||||
termination of its inferior. After a `^running` result, REA queues a read-only
|
||||
`-gdb-show mi-async` query and waits for its tokenized reply before releasing the
|
||||
command's output collector. The ordered `records` include that query reply;
|
||||
`mi` retains the console command's result. An inferior `*stopped` event or an
|
||||
early compatibility prompt does not establish that an outer command file has
|
||||
finished. See GDB's [MI output syntax](https://sourceware.org/gdb/current/onlinedocs/gdb.html/GDB_002fMI-Output-Syntax.html)
|
||||
and [command-file semantics](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Command-Files.html).
|
||||
|
||||
Before a normal close, REA checks for live GDB thread groups. If one remains and a durable `inferior_tty` is verified, REA asks GDB to detach it and confirms the detach before stopping the debugger. Without that sink, select and apply a disposition to each inferior through `gdb_console` (for example, detach or kill as appropriate), then retry close. REA never infers target ownership from a PID or path. When the MCP server shuts down, REA terminates only its directly owned GDB process after confirming no inferior or a sink-backed detach; otherwise it retains the process and its output streams for retry.
|
||||
|
||||
```text
|
||||
@@ -70,17 +79,48 @@ Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_t
|
||||
Cutter commands use the local REA Python bridge and authenticated loopback messages.
|
||||
The client and plugin must support the current v2 handshake; older peers are
|
||||
rejected before dispatch because they cannot preserve cross-caller recovery proof.
|
||||
The client checks the final encoded request against the bridge's 16 MiB frame
|
||||
limit before dispatch; the 16 MiB budget applies to request payload bytes and
|
||||
excludes the terminating LF. The bridge accepts that full payload size and
|
||||
rejects larger or incomplete frames while bounding the read; an oversized
|
||||
command does not retain a cleanup owner. Hopper uses the same request payload
|
||||
budget.
|
||||
Each bridge request waits 25000 ms. Discovery admits at most 256 descriptor
|
||||
The client checks the final encoded request against
|
||||
`REA_BRIDGE_MAX_REQUEST_BYTES` before dispatch. The budget applies to request
|
||||
payload bytes and excludes the terminating LF. Unset, the ceiling is 16777216
|
||||
bytes (16 MiB). A decimal integer from 7464 to 536870887 is accepted. The floor
|
||||
fits the largest bounded symbol-name command inside an authenticated Cutter
|
||||
frame. A Cutter status keeps its session id, connection proof, and up to 64
|
||||
completion receipts when the frame is tight. The observed `current_file` is
|
||||
included when it fits. When it does not, the status reports that path as
|
||||
unknown, and the client does not reuse the descriptor path with the new
|
||||
generation. The maximum is one byte under Node's 536870888-character string ceiling
|
||||
so the framing LF still fits. The ceiling counts the encoded payload. A JSON
|
||||
escape is measured before serialization and is not divided into the maximum
|
||||
again. A structured Cutter reply is admitted when that encoded payload fits,
|
||||
including a dense array of numbers. Cutter hashes the HMAC prefix and the payload as separate updates, so
|
||||
an exact-limit frame does not build one preimage string. The plugin measures and signs the response
|
||||
from bounded JSON pieces, then writes the authenticated frame in 64 KiB chunks. The bridge searches only the newly read bytes for the framing LF. The client unescapes each socket chunk into one response string, checks the proof, and then parses that string. A fitting reply keeps
|
||||
the original output and does not retain the paged wire frame plus another concatenated copy. Zero, negative, non-decimal, and out-of-range
|
||||
values are `configuration_invalid` and do not fall back to the default. The
|
||||
bridge accepts that full payload size and rejects a larger payload while
|
||||
bounding the read. An incomplete frame is still `Incomplete bridge request
|
||||
frame`. An oversized command does not retain a cleanup owner. Hopper uses the
|
||||
same request payload budget. A Hopper request is serialized when it reaches
|
||||
the bridge, so one waiting behind Hopper's serial bridge does not retain
|
||||
another copy of the encoded frame. REA injects a caller-selected value into the
|
||||
Hopper bootstrap and the bridge also reads the process environment; an injected
|
||||
value wins. Cutter reads the environment that starts Cutter and publishes
|
||||
`max_request_bytes` on its session descriptor. A Hopper or Cutter process
|
||||
started out of band without the setting uses 16777216 bytes. The client
|
||||
compares that advertisement with its own ceiling before a discovery status probe and before sending a command. A different ceiling is a mismatch only after the hello proves that descriptor is a live authenticated bridge; a stale descriptor is ignored. That hello does not send a status request. Discovery fails if any such live ceiling differs, even when another live session matches. A hello that
|
||||
never sends its authenticated frame does not hold the bridge accept loop.
|
||||
At most 8 connection threads run. A further accept is closed, and a failure to start a thread closes that socket without stopping the listener. A status `completed_connection` is the longest prefix of accepted connections that have terminated. It does not treat an earlier connection as finished because a later status reached the command lock first. Scalar values in one Cutter reply are encoded in bounded batches.
|
||||
A command reply that does not fit keeps its completion state and document
|
||||
generation, and reports an oversized path as unknown. Cutter response frames use this same ceiling and
|
||||
also exclude the terminating LF. A command response the client discards because it exceeds that ceiling stays an unknown execution and sets `output_truncated`, so Evidence records the omitted output. Hopper response reads are unchanged.
|
||||
Each bridge request waits 25000 ms. The Cutter hello frame still must arrive
|
||||
within 1 second. The authenticated command frame is read with one second per
|
||||
mebibyte of `REA_BRIDGE_MAX_REQUEST_BYTES`, and that read never waits longer
|
||||
than the same 25000 ms. Discovery admits at most 256 descriptor
|
||||
candidates and reads and probes at most eight concurrently. A larger directory
|
||||
reports `bridge_directory_capacity_exceeded`. This release has no runtime
|
||||
override for the 25000 ms request deadline, the 16777216 byte frame, or the
|
||||
256-candidate discovery budget.
|
||||
override for the 25000 ms request deadline or the 256-candidate discovery
|
||||
budget. Those limits do not follow `REA_BRIDGE_MAX_REQUEST_BYTES`.
|
||||
|
||||
Terminal receipts are recovered before another command, including from a new CLI
|
||||
invocation. A signed status response also confirms completion of older bridge
|
||||
|
||||
+10
-2
@@ -91,14 +91,22 @@ section is present, `auditable_metadata` contains:
|
||||
- `compressed_bytes` and `uncompressed_bytes`;
|
||||
- `format_version`, the embedded integer, or `null` when the field is omitted;
|
||||
- `packages`, in the section's original order, with `name`, `version`, `source`,
|
||||
`kind`, `dependencies`, and `root`;
|
||||
`kind`, `dependencies`, `features`, and `root`;
|
||||
- `root_package_index`, the single package marked `root`, or `null` when none is marked;
|
||||
- `dependency_edges`, pairs of package indices in package-then-dependency order;
|
||||
- `cyclic_dependency_indices`, sorted unique indices that participate in a cycle.
|
||||
|
||||
An omitted `kind` stays `null`. The embedded format treats that omission as
|
||||
runtime, and that default is not stored in the section. An omitted dependency
|
||||
list is an empty array. An omitted `root` flag is false.
|
||||
list is an empty array. An omitted `features` field is `null`, because it does
|
||||
not establish whether features were empty or the producer did not record them;
|
||||
an explicit empty array establishes an empty list. An omitted `root` flag is
|
||||
false.
|
||||
Historical [cargo-auditable 0.4 package records](https://github.com/rust-secure-code/cargo-auditable/blob/v0.4.0/auditable-serde/src/lib.rs)
|
||||
may include the package `features` list; REA preserves those strings. That
|
||||
version omitted empty feature lists during serialization, while version 0.5
|
||||
removed the field entirely, as recorded in its
|
||||
[changelog](https://github.com/rust-secure-code/cargo-auditable/blob/v0.5.0/CHANGELOG.md).
|
||||
|
||||
`rustc_commit` is present only when a `/rustc/<40-hex>/` path is found in
|
||||
read-only data. Its `evidence_kind` is `inferred`. The hash is not a claim that
|
||||
|
||||
@@ -31,7 +31,14 @@ The corresponding MCP request is:
|
||||
|
||||
`decoders` uses sigrok's decoder stack and option/channel syntax. Channel names
|
||||
must match the recording; REA does not guess wiring, baud rate or protocol.
|
||||
Omitting `input_format` requests sigrok's autodetection. Omitting `annotations`
|
||||
When sigrok reports that a selected channel name is missing, REA returns
|
||||
`invalid_request` for `decoders` even if sigrok continues with fallback
|
||||
annotations and exits successfully. The diagnostic and any partial stdout remain
|
||||
in `captured_output`; unrelated producer warnings do not change request status.
|
||||
Omitting `input_format` requests sigrok's autodetection. REA uses an immutable
|
||||
private copy whose filename retains the selected recording's final suffix, so
|
||||
filename-based format detection receives that hint without exposing the
|
||||
caller's directory to sigrok. Omitting `annotations`
|
||||
retains every annotation class. Both omitted selections are recorded as null.
|
||||
See the [upstream manual](https://sigrok.org/wiki/Sigrok-cli) for formats,
|
||||
decoder stacking and annotation selectors. No arbitrary command flags are accepted.
|
||||
@@ -92,7 +99,8 @@ REA_SIGROK_COMMAND=/absolute/path/to/sigrok-cli node scripts/verify-signal-recor
|
||||
|
||||
The verifier synthesizes a 9600-baud 8N1 UART VCD carrying `REA`, then checks
|
||||
actual CLI/MCP parity, advertised schemas, stored Evidence, source hashes,
|
||||
annotation filtering/autodetection, invalid decoder/input failures and cleanup.
|
||||
annotation filtering/autodetection, invalid decoder/input failures, missing
|
||||
channel rejection with retained partial output, and cleanup.
|
||||
An optional positional REA entrypoint verifies an installed package.
|
||||
The real-tool lane requires Linux, sigrok-cli 0.7.2 and libsigrokdecode. A separate
|
||||
kernel-limit regression checks the inherited soft/hard limits and verifies an
|
||||
|
||||
@@ -36,7 +36,8 @@ previous increment.
|
||||
and `--name` must be a kebab-case label of at most 64 characters that is not a
|
||||
device name Windows reserves (`con`, `prn`, `aux`, `nul`, `com1`–`com9`,
|
||||
`lpt1`–`lpt9`), since the env folder could not be created there.
|
||||
- The Dockerfile reads `FROM node:24.18.0-bookworm-slim`, sets
|
||||
- The Dockerfile uses the Node version pinned in `.nvmrc` with the
|
||||
`bookworm-slim` variant, sets
|
||||
`MCP_HOST=0.0.0.0` and `MCP_PORT=18765`, installs Debian's `python3` for the
|
||||
framework's readiness probe, installs
|
||||
`rea-agents@<PRODUCT_IDENTITY.packageVersion>` from the registry or, with
|
||||
|
||||
@@ -44,8 +44,11 @@ credentials and resets it between sequences.
|
||||
`guid` of the answer's own entities (at the read view's entity or list location, a write's response location, or
|
||||
else the root) also maps within the collection the call names, since two collections can reuse a live id for
|
||||
different entities. An equal pair is kept in that collection, so another collection's mapping of the same live id
|
||||
does not replace it. A collection's map is a bijection: the first pairing of either side wins. Outside identifier
|
||||
fields, a string maps when the environment's state holds it as an id, and the keys of two objects keyed by ids map
|
||||
does not replace it. Global mappings are one-to-one and first-pair-wins; equal global pairs also reserve both ids.
|
||||
If an equal pair conflicts with another pairing, its contested live or environment id is not used to normalize
|
||||
either pair, in either observation order. Only non-identity, unconflicted mappings count as mapped ids. Outside
|
||||
identifier fields, a string maps only at a field where learning found it held as an id in environment state. The
|
||||
keys of two objects keyed by ids map
|
||||
when their values pair (keys that partly overlap only when they are digits, UUIDs or their entries' identities, and
|
||||
every value pairs);
|
||||
- ids are learned only from an answer the environment gave this call, from its state or from a recording of exactly
|
||||
@@ -62,8 +65,11 @@ credentials and resets it between sequences.
|
||||
reported `refused`. A browser probe keeps its request bodies; one captured without response bodies is refused, since
|
||||
only statuses would compare, and so is one that makes POST, PUT or PATCH calls without recorded request bodies, since
|
||||
those calls would be made empty.
|
||||
- Answers compare after normalisation: empty response text equals no body (a JSON empty string stays one); mapped ids
|
||||
compare equal, in scalar sets as well, an entity's own identity within the call's collection and any other id by the
|
||||
- Answers compare after normalisation: empty response text and JSON `null` equal no body (a JSON empty string stays one);
|
||||
decoded JSON values compare structurally, plain text compares as text, and retained base64 bodies compare as bytes
|
||||
(including against UTF-8 text with the same bytes), without treating a base64 spelling as text. Mapped ids compare
|
||||
equal in identifier fields, in `ids`/`uuids`/`guids` scalar lists, and for an entity's own identity within the call's
|
||||
collection; other numeric fields stay literal. Any other id compares by the
|
||||
probe's mapping, and an entity's own identity both sides hold verbatim differs when its collection's mapping gives it
|
||||
another environment value (a reference keeps raw equality, as the probe's mapping cannot tell which collection it
|
||||
names); an object's members compare under the environment's mapped keys only when it is keyed by learned ids as
|
||||
|
||||
+22
-2
@@ -236,7 +236,13 @@ instrumentation remains equivalent.
|
||||
|
||||
## Choose verification for the change
|
||||
|
||||
| Change or claim | First check | Additional coverage |
|
||||
`test:local` is fast source-level feedback. For the strongest practical
|
||||
regression, prefer a real public CLI/MCP workflow, then production-boundary
|
||||
integration, then a producer golden; use the table to select quick feedback and
|
||||
claim-specific additional evidence. Provider-behavior claims require the
|
||||
matching real-provider lane.
|
||||
|
||||
| Change or claim | Fast feedback | Claim-specific coverage |
|
||||
| ----------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| Source semantics | `npm run test:local -- PATH...` | Relevant boundary cases when the behavior crosses a port |
|
||||
| CLI/MCP workflow | `npm run test:focused -- PATH...` | Actual public calls; an injected provider has limited scope |
|
||||
@@ -266,6 +272,20 @@ cases. Share setup without hiding inputs or expected evidence. For successful
|
||||
`.ok` assertion adds no coverage. Protocol fixtures must reject unmodeled
|
||||
commands and preserve actual producer reply shapes.
|
||||
|
||||
For comparison normalization, include a legitimate equivalent pair and a
|
||||
distinct pair that would collapse if representation tags or scope were lost.
|
||||
For learned identifiers, cover conflicting pairs and reused values in different
|
||||
collections; mappings must remain one-to-one within their declared scope.
|
||||
|
||||
For format compatibility, identify the producer revision that establishes each
|
||||
rule. Include an accepted boundary case beside the rejected case so a stricter
|
||||
parser cannot pass by refusing both. Keep source-derived behavior, synthetic
|
||||
fixtures, and executed producer or runtime oracles distinct in validation
|
||||
reports. A fixture accepted by REA proves its reader behavior; it does not prove
|
||||
that a real loader accepts the artifact. If multiple producer revisions share a
|
||||
format identifier but differ in behavior, record that ambiguity rather than
|
||||
using one installed version as an oracle for all of them.
|
||||
|
||||
Packed bridge validation uses Python's standard-library AST and tar readers
|
||||
with `-I -S`: caller-local modules, `PYTHONPATH`, and site startup hooks must
|
||||
not replace those readers or execute during validation. Controlled `ast.py`
|
||||
@@ -946,7 +966,7 @@ analyzed as native code (75k methods on arm64) and decompiled through both
|
||||
CLI and MCP with explicit unit selection, plus the thin Java wrapper and
|
||||
decoded manifest. The dedicated Dart snapshot processor and `run_script`
|
||||
remained GUI-only surfaces in that headless build.
|
||||
The separate [Linux CE native audit](../experiments/analysis-efficiency/jeb-linux-native.md)
|
||||
The separate [Linux CE native audit](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/jeb-linux-native.md)
|
||||
uses the official CE `5.48.0.202610071539` direct CLI launcher and real
|
||||
CLI/stdio MCP ELF workflows. That audit does not qualify CE Java/Android
|
||||
decompilation or GUI project opening, and it does not replace the licensed
|
||||
|
||||
+31
-3
@@ -30,8 +30,13 @@ inside one tool.
|
||||
|
||||
A primitive reports a reusable fact about an identified object or relationship.
|
||||
A format-specific decoder qualifies; one application's business interpretation
|
||||
belongs outside the general contract. Expose provider APIs when they add a
|
||||
useful capability that existing tools cannot express.
|
||||
belongs outside the general contract. The format owner reports source
|
||||
observations and structural validation; the application or workflow owner
|
||||
applies use-case-specific admission policy without reinterpreting raw fields.
|
||||
For example, DOS MZ structure and Windows-header evidence stay separate, while
|
||||
target resolution decides whether the unresolved header permits admission.
|
||||
Expose provider APIs when they add a useful capability that existing tools
|
||||
cannot express.
|
||||
|
||||
Compose a workflow when repeated use demonstrates a multi-source outcome REA
|
||||
can join without hiding caller choices or uncertainty. Batch homogeneous reads
|
||||
@@ -57,6 +62,9 @@ satisfy. A plan without execution does not establish runtime behavior.
|
||||
- Keep observed, derived, and inferred relationships distinct. Preserve
|
||||
unresolved edges and partial provider facts instead of requiring every
|
||||
provider to supply the same metadata.
|
||||
When a format constructor changes defaults or flags, retain the source
|
||||
values and use separately derived effective values only for operations
|
||||
whose semantics require them.
|
||||
- For caller/callee reads, `relationship_coverage` describes only the
|
||||
provider-observed direct relationship set. `complete` means that set was
|
||||
returned without a known read limit; it does not claim every possible runtime
|
||||
@@ -66,7 +74,27 @@ satisfy. A plan without execution does not establish runtime behavior.
|
||||
enforce the requested target and lifecycle.
|
||||
- Add limits only for real format, protocol, authority, or measured resource
|
||||
constraints. Report pagination, truncation, cancellation, and partial failure.
|
||||
An assumed agent budget is insufficient justification for a limit.
|
||||
An assumed agent budget is insufficient justification for a limit. Name the
|
||||
ceiling and its recovery as described in
|
||||
[admission limits and recovery](design/limits-and-recovery.md).
|
||||
|
||||
### Interpret boundary facts once
|
||||
|
||||
The format or protocol reader owns interpretation. Pass its parsed facts and
|
||||
explicit uncertainty to consumers; do not make adapters reparse display text,
|
||||
reconstruct omitted values, or infer success from missing diagnostics.
|
||||
|
||||
Keep format recognition separate from provider admission. A recognizable header
|
||||
can supply useful observations while later structure remains unresolved or a
|
||||
provider cannot accept the artifact. Report the reason at the affected boundary
|
||||
without erasing earlier observations or labeling unresolved input malformed.
|
||||
|
||||
Derive validation from the field's own format and revision. A runtime API's
|
||||
narrower argument range need not constrain a wider on-disk field. A shared layout,
|
||||
magic, or version label need not identify every constructor or equality rule.
|
||||
When supported producer revisions differ, retain what the input establishes and
|
||||
state what it cannot distinguish. Static inspection does not establish that the
|
||||
artifact can execute successfully.
|
||||
|
||||
## Own the lifecycle
|
||||
|
||||
|
||||
@@ -58,19 +58,19 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
|
||||
45.5,src/application/Uninstall.ts:removeClient restore,Configuration update fails and restoring the original also fails,"Configuration could not be updated or restored. Restore the backup at {backupPath} manually, then rerun uninstall.","High risk; persistent configuration may be damaged, but retained backup is authoritative",Keep new recovery copy,implemented,verified by injected restoration failure test
|
||||
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
|
||||
48,src/application/DoctorDiagnostics.ts:node,Runtime version is outside supported ranges,"Install Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+.",Low risk; exact dependency recovery,Keep current copy,unchanged,verified by doctor unit test
|
||||
49,src/application/DoctorDiagnostics.ts:host,Host OS/distribution is unsupported,"REA supports macOS 12+, Linux distributions with a recognized apt, dnf, or pacman package family, and the experimental Windows x64 Ghidra P0 boundary; automated Hopper setup additionally requires Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy.",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
|
||||
53,src/application/Setup.ts:engine install,Approved analysis-engine installation returns no launcher,Hopper installation failed; install Hopper manually or rerun setup after resolving the reported system error.,Mentions component intentionally being installed and offers two bounded recovery paths,Keep current copy,unchanged,verified by setup unit test
|
||||
54,src/application/Setup.ts:first-run choice,New macOS analysis-engine install requires a first-run choice,"Open Hopper, choose its demo mode or activate a license, then rerun rea doctor --json.",Low risk; exact demo-or-license choice and verification command,Keep current copy,unchanged,verified by setup unit test
|
||||
55,src/application/Setup.ts:optional engine,Setup completes without optional deep-analysis engine,Hopper is optional for non-Hopper providers. Rerun with --yes --install-hopper for deep native analysis.,Exposes provider terminology but this is an explanatory setup status rather than an error; recovery command is exact,Keep current copy,unchanged,verified by setup unit test
|
||||
56,src/application/Setup.ts:doctor fallback,Post-setup diagnostics remain unhealthy,Run rea doctor and apply each reported remediation.,Low risk; delegates to structured per-check recovery,Keep current copy,unchanged,verified by exact setup unit test
|
||||
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/SetupHost.ts:unsupported Linux,Linux distribution is outside supported set,"Automated Hopper setup supports Ubuntu 24.04+, Fedora 41+, Nobara 44+, 64-bit Arch Linux, CachyOS, and Omarchy; configure an existing supported provider instead.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
|
||||
55,src/application/Setup.ts:optional engine,Setup completes without optional deep-analysis engine on a host where automated Hopper installation is supported,Hopper is optional for non-Hopper providers. Rerun with --yes --install-hopper for deep native analysis.,Exposes provider terminology but this is an explanatory setup status rather than an error; recovery command is exact,Keep current copy,unchanged,verified by setup unit test
|
||||
56,src/application/Setup.ts:doctor fallback,Post-setup diagnostics remain unhealthy (including hosts where automated Hopper installation is unsupported),Run rea doctor and apply each reported remediation.,Low risk; delegates to structured per-check recovery,Keep current copy,unchanged,verified by exact setup unit test
|
||||
57,src/application/SetupPlan.ts:unsupported platform,Platform is neither macOS nor Linux,REA supports Hopper on macOS and selected x86_64 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.23.4), 24.x (>=24.22.1), or 26.12.1+ and rerun setup.",Low risk; exact dependency and retry,Keep current copy,unchanged,verified by setup host tests
|
||||
59,src/application/SetupPlan.ts:unsupported macOS,"macOS version is missing, unparseable, or older than 12",Upgrade to macOS 12 or newer.,Low risk; exact support floor,Keep current copy,unchanged,verified by setup unit test
|
||||
60,src/application/SetupPlan.ts:unsupported Linux,Linux distribution is outside supported set,"Automated Hopper setup requires an x86_64 host running Ubuntu 24.04+, Fedora 41+, Nobara 44+, Arch Linux, CachyOS, or Omarchy; configure an existing supported provider instead.",Low risk; exact supported alternatives,Keep current copy,unchanged,verified by setup unit test
|
||||
61,src/application/process/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/JsonInputFile.ts:memory admission,Projected process JSON value exceeds available heap headroom,Insufficient heap headroom to materialize the complete JSON value,Heap admission depends on live runtime capacity rather than a fixed 64 MiB file limit,"Provide a smaller valid JSON value. For Evidence workflows, re-analyze a smaller selection of the original target and use its complete Evidence; splitting JSON text or trimming Evidence fields does not produce valid workflow input.",implemented,Source verified against shared JSON reader and recovery constant; former fixed-size refusal is superseded
|
||||
63,src/application/process/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
|
||||
@@ -86,20 +86,20 @@ harm_rank,location,trigger,current_copy,user_risk,proposed_replacement,implement
|
||||
74,install.sh:unknown option,Installer receives an unsupported flag,unknown option with caller-provided flag,Low risk; identifies offending input,Keep current copy,unchanged,verified by shell branch inspection
|
||||
75,install.sh:platform,Installer runs outside macOS or supported Linux,REA supports macOS and selected 64-bit Linux distributions.,Low risk; states support boundary,Keep current copy,unchanged,verified by shell branch inspection
|
||||
76,install.sh:curl missing,curl is absent,curl is required.,No recovery instruction,"curl is required. Install curl, then rerun this installer.",implemented,verified by exact missing-curl installer test
|
||||
77,install.sh:node missing,node is absent,"Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from nodejs.org and retry.",Low risk; exact versions and recovery,Keep current copy,unchanged,verified by shell branch inspection
|
||||
77,install.sh:node missing,node is absent,"Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+ is required; install it from nodejs.org and retry.",Low risk; exact versions and recovery,Keep current copy,unchanged,verified by shell branch inspection
|
||||
78,install.sh:npm missing,npm is absent,npm is required; install it with Node.js and retry.,Low risk; exact recovery,Keep current copy,unchanged,verified by shell branch inspection
|
||||
79,install.sh:node unreadable,node exists but does not report a parseable version,could not determine the active Node.js version.,No cause-local recovery,"The active Node.js version could not be read. Check that node works and is on PATH, then retry.",implemented,verified by exact unreadable-Node-version installer test
|
||||
80,install.sh:Node 22 floor,Node 22 minor version is below 19,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
80,install.sh:Node 22 floor,Node 22 version is below 22.23.4,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:
|
||||
REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+. Install one via:
|
||||
nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)
|
||||
fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)
|
||||
Direct download for OS/ARCH:
|
||||
https://nodejs.org/dist/latest-v24.x/ (grab the OS-ARCH tarball)
|
||||
Then rerun this installer.",implemented,verified by exact installer test using Node 20 against the `linux/x64` fake host and by shell branch inspection
|
||||
81,install.sh:Node 24 floor,Node 24 minor version is below 11,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
81,install.sh:Node 24 floor,Node 24 version is below 24.22.1,"Node.js VERSION is unsupported; use Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+.",No install guidance; operator must guess between nvm/fnm/system packages/nodejs.org,"Node.js VERSION is unsupported.
|
||||
|
||||
REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:
|
||||
REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+. Install one via:
|
||||
nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)
|
||||
fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)
|
||||
Direct download for OS/ARCH:
|
||||
|
||||
|
@@ -100,7 +100,7 @@ diagnostic helper hashes were not captured at execution; the record marks that
|
||||
gap instead of assigning the later frozen hash to earlier runs.
|
||||
|
||||
Build each pinned checkout, then run under the existing
|
||||
[resource observer](../../experiments/analysis-efficiency/resource-counters.md)
|
||||
[resource observer](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/resource-counters.md)
|
||||
and a private Xvfb instance. The shared verifier supports all four named cases:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -111,7 +111,7 @@ schemas, exec schema and host rendering are unmeasured. Final same-backend
|
||||
controls and optional-discovery measurements are recorded separately in JSON.
|
||||
|
||||
Actual Codex CLI trials use the runnable
|
||||
[agent harness](../../experiments/analysis-efficiency/javascript-facts-agent.mjs),
|
||||
[agent harness](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/javascript-facts-agent.mjs),
|
||||
installed CLI 0.161.0, requested alias `gpt-5.6-terra` / high, unchanged questions
|
||||
per pair, and fresh processes/MCP. Restricted baseline tools are analysis and
|
||||
export; candidate adds the existing `inspect_analysis_view`. Both can reach the
|
||||
@@ -194,7 +194,7 @@ execution as unknown; the candidate still exported once. The one invalid
|
||||
disabled-host attempt (37.679 s / 5.070758 CPU s / 880,414,720 B) is preserved
|
||||
as a harness failure, with unavailable operation counts.
|
||||
|
||||
Use the [specific artifact oracle](../../experiments/analysis-efficiency/javascript-facts-grade.py) on the compact JSON:
|
||||
Use the [specific artifact oracle](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/javascript-facts-grade.py) on the compact JSON:
|
||||
|
||||
```sh
|
||||
python3 experiments/analysis-efficiency/javascript-facts-grade.py \
|
||||
|
||||
@@ -8,7 +8,7 @@ The [compact measurements](javascript-window-options-2026-10-16.json) retain bot
|
||||
|
||||
## Correctness
|
||||
|
||||
Both trial pairs match the independent export-and-filter oracle and exact complete CLI/MCP view Evidence. The pinned [Joplin checker](../../experiments/analysis-efficiency/joplin-window-options-check.py) verifies exact settings, explicit unknowns, source ranges and stable window identity. Manual negative controls reject a wrong boolean, numeric0 substituted for false, a changed identity and observed instead of inferred provenance.
|
||||
Both trial pairs match the independent export-and-filter oracle and exact complete CLI/MCP view Evidence. The pinned [Joplin checker](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/joplin-window-options-check.py) verifies exact settings, explicit unknowns, source ranges and stable window identity. Manual negative controls reject a wrong boolean, numeric0 substituted for false, a changed identity and observed instead of inferred provenance.
|
||||
|
||||
A raw semantic graph digest changes because the semantic graph commits its parent `application_graph_id`. The final record separately checks complete semantic content after excluding exactly that foreign key and its dependent top-level `graph_id`; original raw digests remain recorded and linked to the independent complete CLI observations. These exclusions follow the canonical semantic graph schema and builder, rather than filtering differing nodes or relations.
|
||||
|
||||
@@ -45,7 +45,7 @@ python3 experiments/analysis-efficiency/joplin-window-options-check.py \
|
||||
docs/verification/javascript-window-options-2026-10-16.json
|
||||
```
|
||||
|
||||
Apply the linked resource observer from the earlier Joplin record to each costly command. Complete CLI observations use the same source selection through `analyze-javascript-application --format json`. The preserved [content comparator](../../experiments/analysis-efficiency/compare-javascript-semantic-content.mjs) takes the baseline CLI JSON, candidate CLI JSON and this measurement record, then emits the semantic proof while retaining both original graph identifiers and raw digests. It reads sequentially and reports a concise failure rather than printing full graphs. The baseline backend is unchanged main `2c7583599b9ac1c847bd77b1940930fd8d4594c7`; only the matching verification harness is applied there. Source and runtime pins distinguish measurement versions from later documentation changes.
|
||||
Apply the linked resource observer from the earlier Joplin record to each costly command. Complete CLI observations use the same source selection through `analyze-javascript-application --format json`. The preserved [content comparator](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/compare-javascript-semantic-content.mjs) takes the baseline CLI JSON, candidate CLI JSON and this measurement record, then emits the semantic proof while retaining both original graph identifiers and raw digests. It reads sequentially and reports a concise failure rather than printing full graphs. The baseline backend is unchanged main `2c7583599b9ac1c847bd77b1940930fd8d4594c7`; only the matching verification harness is applied there. Source and runtime pins distinguish measurement versions from later documentation changes.
|
||||
|
||||
## Ordering review follow-up
|
||||
|
||||
@@ -59,7 +59,7 @@ The stored-alias example raised in review already returns unknown on the reviewe
|
||||
|
||||
The repeated-effects cost was reproducible. Candidate discovery needed only lexical identity but collected full mutation reports; each later capture also materialized every effect before checking for a prior escape. The shared index now offers lexical-only lookup and an early-exit predicate. Windows and request analysis use those operations. The same owner still supplies complete effects to the require-source consumer, which needs filtered effect kinds; no second interpretation or persistent cache was added.
|
||||
|
||||
The [preserved scaling control](../../experiments/analysis-efficiency/window-option-scaling.mjs) measures real production source analysis on synthetic capacity inputs; it is not a real-application or agent task. All samples and module hashes are retained. CPU medians across three sequential samples were:
|
||||
The [preserved scaling control](https://github.com/morluto/rea/blob/42e1a17d6e6c0487f17be0bae248553c4caba7f7/experiments/analysis-efficiency/window-option-scaling.mjs) measures real production source analysis on synthetic capacity inputs; it is not a real-application or agent task. All samples and module hashes are retained. CPU medians across three sequential samples were:
|
||||
|
||||
| Shared-options constructors | Before (CPU s) | After (CPU s) |
|
||||
| --------------------------- | -------------: | ------------: |
|
||||
|
||||
@@ -820,7 +820,7 @@ by null; there is no single events pointer shared by all traces.
|
||||
| Original event and loss declaration | Observed endpoint report; `recovery:packet_lost` does not prove actual network loss. Missing packet number/type remains unknown. |
|
||||
| STREAM ranges and byte totals | Derived per trace and direction from reported offsets and lengths using exact integer arithmetic. `unique_bytes` unions intervals; `frame_length_sum` includes overlaps; `repeated_bytes` is their difference. Overlap alone does not prove retransmission or equal contents. |
|
||||
| FIN final sizes | Reported offset plus length where FIN is true; multiple conflicting sizes remain visible. A final size does not establish complete coverage or a received body. |
|
||||
| Incomplete fact records | Ordinals with omitted/redacted interpretable frame metadata; usable fields in other records survive. An empty list does not establish complete logging or capture. |
|
||||
| Incomplete fact records | Ordinals with omitted/redacted interpretable frame metadata, including an unknown STREAM FIN; usable fields such as known ranges in that record survive. An empty list does not establish complete logging or capture. |
|
||||
| HTTP bodies and causality | `body_bytes: "not-reported"`; lengths and headers are metadata, with no reconstructed body, hash, timing causality or live process attribution. |
|
||||
|
||||
QUIC integer fields interpreted here accept exact nonnegative JSON integer
|
||||
|
||||
@@ -14,7 +14,7 @@ additional permission flag or degraded mode is required.
|
||||
## Supported boundary
|
||||
|
||||
- Windows 10+ x64 with fixed local NTFS target and temporary volumes;
|
||||
- Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+;
|
||||
- Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+;
|
||||
- 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 x86, x86-64, ARMNT (Thumb-2) or ARM64 PE application/DLL, or a
|
||||
|
||||
+7
-3
@@ -57,7 +57,7 @@ fail_node_version() {
|
||||
local observed="$1"
|
||||
node_host_labels
|
||||
printf 'REA installation failed: Node.js %s is unsupported.\n\n' "$observed" >&2
|
||||
printf 'REA requires Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+. Install one via:\n' >&2
|
||||
printf 'REA requires Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+. Install one via:\n' >&2
|
||||
printf ' nvm: nvm install 24 && nvm use 24 (https://github.com/nvm-sh/nvm)\n' >&2
|
||||
printf ' fnm: fnm install 24 && fnm use 24 (https://github.com/Schniz/fnm)\n' >&2
|
||||
printf ' Direct download for %s/%s:\n' "$node_os_label" "$node_arch_label" >&2
|
||||
@@ -112,14 +112,18 @@ main() {
|
||||
platform="$(uname -s)"
|
||||
[ "$platform" = "Darwin" ] || [ "$platform" = "Linux" ] || fail "REA supports macOS and selected 64-bit Linux distributions."
|
||||
command -v curl >/dev/null 2>&1 || fail "curl is required. Install curl, then rerun this installer."
|
||||
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.19), 24.x (>=24.11), or 26+ is required; install it from https://nodejs.org and retry."
|
||||
command -v node >/dev/null 2>&1 || fail "Node.js 22.x (>=22.23.4), 24.x (>=24.22.1), or 26.12.1+ is required; install it from https://nodejs.org and retry."
|
||||
command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
|
||||
|
||||
node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
|
||||
IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
|
||||
matches "$node_major" '^[0-9]+$' && matches "$node_minor" '^[0-9]+$' || fail "the active Node.js version could not be read. Check that node works and is on PATH, then retry."
|
||||
node_patch="${_node_patch%%+*}"
|
||||
if ! matches "$_node_patch" '^[0-9]+(\+[0-9A-Za-z.-]+)?$' ||
|
||||
! ((node_major == 22 && node_minor >= 19 || node_major == 24 && node_minor >= 11 || node_major >= 26)); then
|
||||
! ((node_major == 22 && (node_minor > 23 || node_minor == 23 && node_patch >= 4) ||
|
||||
node_major == 24 && (node_minor > 22 || node_minor == 22 && node_patch >= 1) ||
|
||||
node_major == 26 && (node_minor > 12 || node_minor == 12 && node_patch >= 1) ||
|
||||
node_major > 26)); then
|
||||
fail_node_version "$node_version"
|
||||
fi
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ REA npm dependency inventory
|
||||
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
|
||||
License identifiers are upstream lockfile declarations, not a legal assessment.
|
||||
Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
|
||||
Locked package entries: 758; runtime: 180.
|
||||
Locked package entries: 775; runtime: 180.
|
||||
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
|
||||
|
||||
@algolia/abtesting@1.25.0 | development | MIT
|
||||
@@ -3030,11 +3030,11 @@ possible-typed-array-names@1.1.0 | runtime | MIT
|
||||
Package: https://www.npmjs.com/package/possible-typed-array-names/v/1.1.0
|
||||
Archive: https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz
|
||||
Integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
|
||||
postcss@8.5.29 | development | MIT
|
||||
postcss@8.5.31 | development | MIT
|
||||
Lockfile path: node_modules/postcss
|
||||
Package: https://www.npmjs.com/package/postcss/v/8.5.29
|
||||
Archive: https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz
|
||||
Integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==
|
||||
Package: https://www.npmjs.com/package/postcss/v/8.5.31
|
||||
Archive: https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz
|
||||
Integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==
|
||||
preact@10.29.8 | development | MIT
|
||||
Lockfile path: node_modules/preact
|
||||
Package: https://www.npmjs.com/package/preact/v/10.29.8
|
||||
@@ -3550,11 +3550,96 @@ vfile-message@4.0.3 | development | MIT
|
||||
Package: https://www.npmjs.com/package/vfile-message/v/4.0.3
|
||||
Archive: https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz
|
||||
Integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
|
||||
vite@8.3.2 | development | MIT
|
||||
vite@8.3.5 | development | MIT
|
||||
Lockfile path: node_modules/vite
|
||||
Package: https://www.npmjs.com/package/vite/v/8.3.2
|
||||
Archive: https://registry.npmjs.org/vite/-/vite-8.3.2.tgz
|
||||
Integrity: sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==
|
||||
Package: https://www.npmjs.com/package/vite/v/8.3.5
|
||||
Archive: https://registry.npmjs.org/vite/-/vite-8.3.5.tgz
|
||||
Integrity: sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==
|
||||
@oxc-project/types@0.155.0 | development | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@oxc-project/types
|
||||
Package: https://www.npmjs.com/package/@oxc-project/types/v/0.155.0
|
||||
Archive: https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz
|
||||
Integrity: sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==
|
||||
@rolldown/binding-android-arm-eabi@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-android-arm-eabi
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-android-arm-eabi/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz
|
||||
Integrity: sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==
|
||||
@rolldown/binding-android-arm64@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-android-arm64
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-android-arm64/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz
|
||||
Integrity: sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==
|
||||
@rolldown/binding-darwin-arm64@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-darwin-arm64
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-darwin-arm64/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz
|
||||
Integrity: sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==
|
||||
@rolldown/binding-darwin-x64@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-darwin-x64
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-darwin-x64/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz
|
||||
Integrity: sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==
|
||||
@rolldown/binding-freebsd-x64@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-freebsd-x64
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-freebsd-x64/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz
|
||||
Integrity: sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==
|
||||
@rolldown/binding-linux-arm-gnueabihf@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-arm-gnueabihf/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz
|
||||
Integrity: sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==
|
||||
@rolldown/binding-linux-arm64-gnu@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-arm64-gnu/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz
|
||||
Integrity: sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==
|
||||
@rolldown/binding-linux-arm64-musl@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-arm64-musl/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz
|
||||
Integrity: sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==
|
||||
@rolldown/binding-linux-ppc64-gnu@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-ppc64-gnu/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz
|
||||
Integrity: sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==
|
||||
@rolldown/binding-linux-s390x-gnu@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-s390x-gnu/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz
|
||||
Integrity: sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==
|
||||
@rolldown/binding-linux-x64-gnu@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-x64-gnu/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz
|
||||
Integrity: sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==
|
||||
@rolldown/binding-linux-x64-musl@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-linux-x64-musl/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz
|
||||
Integrity: sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==
|
||||
@rolldown/binding-openharmony-arm64@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-openharmony-arm64/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz
|
||||
Integrity: sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==
|
||||
@rolldown/binding-win32-arm64-msvc@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-win32-arm64-msvc/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz
|
||||
Integrity: sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==
|
||||
@rolldown/binding-win32-x64-msvc@1.2.15 | development, optional | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc
|
||||
Package: https://www.npmjs.com/package/@rolldown/binding-win32-x64-msvc/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz
|
||||
Integrity: sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==
|
||||
rolldown@1.2.15 | development | MIT
|
||||
Lockfile path: node_modules/vite/node_modules/rolldown
|
||||
Package: https://www.npmjs.com/package/rolldown/v/1.2.15
|
||||
Archive: https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz
|
||||
Integrity: sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==
|
||||
vitepress@1.6.4 | development | MIT
|
||||
Lockfile path: node_modules/vitepress
|
||||
Package: https://www.npmjs.com/package/vitepress/v/1.6.4
|
||||
|
||||
Generated
+327
-11
@@ -84,7 +84,7 @@
|
||||
"vitest": "5.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.19.0 || ^24.11.0 || >=26.0.0"
|
||||
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"frida": "17.23.2"
|
||||
@@ -8861,9 +8861,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.29",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
|
||||
"integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
|
||||
"version": "8.5.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
|
||||
"integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -8881,7 +8881,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.19",
|
||||
"nanoid": "^3.3.20",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.2"
|
||||
},
|
||||
@@ -10308,17 +10308,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz",
|
||||
"integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==",
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
|
||||
"integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.33.0",
|
||||
"picomatch": "^4.0.7",
|
||||
"postcss": "^8.5.28",
|
||||
"rolldown": "~1.2.11",
|
||||
"postcss": "^8.5.30",
|
||||
"rolldown": "~1.2.14",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -10335,7 +10335,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.7.1",
|
||||
"@vitejs/devtools": "^0.7.1 || ^0.8.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
@@ -10386,6 +10386,322 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@oxc-project/types": {
|
||||
"version": "0.155.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
|
||||
"integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/oxc-project"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-android-arm-eabi": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz",
|
||||
"integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz",
|
||||
"integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz",
|
||||
"integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz",
|
||||
"integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz",
|
||||
"integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz",
|
||||
"integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz",
|
||||
"integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz",
|
||||
"integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz",
|
||||
"integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz",
|
||||
"integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/rolldown": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz",
|
||||
"integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.155.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm-eabi": "1.2.15",
|
||||
"@rolldown/binding-android-arm64": "1.2.15",
|
||||
"@rolldown/binding-darwin-arm64": "1.2.15",
|
||||
"@rolldown/binding-darwin-x64": "1.2.15",
|
||||
"@rolldown/binding-freebsd-x64": "1.2.15",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.2.15",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.2.15",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.2.15",
|
||||
"@rolldown/binding-linux-x64-musl": "1.2.15",
|
||||
"@rolldown/binding-openharmony-arm64": "1.2.15",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.2.15",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.2.15"
|
||||
}
|
||||
},
|
||||
"node_modules/vitepress": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz",
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
"rea-agents": "scripts/rea.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.19.0 || ^24.11.0 || >=26.0.0"
|
||||
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
||||
},
|
||||
"packageManager": "npm@11.16.0",
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { parseArgs } from "node:util";
|
||||
import { checkDependencySecurity } from "../lib/dependency-security.mjs";
|
||||
import { requireRegistryLock } from "../lib/dependency-security-facts.mjs";
|
||||
import { lockedPackages } from "../lib/npm-locked-packages.mjs";
|
||||
import { installCanaryPackage } from "../lib/release-package-install.mjs";
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
mode: { type: "string", default: "source" },
|
||||
evidence: { type: "string", default: ".cache/dependency-security" },
|
||||
},
|
||||
});
|
||||
const referenceLockfile = resolve("package-lock.json");
|
||||
// This pre-install check uses only Node built-ins and executes no dependency code.
|
||||
requireRegistryLock(
|
||||
lockedPackages(JSON.parse(await readFile(referenceLockfile, "utf8"))),
|
||||
);
|
||||
if (values.mode === "preflight") {
|
||||
console.log(
|
||||
"Source lock uses the supported npm registry and SHA-512 identities",
|
||||
);
|
||||
} else if (values.mode === "source") {
|
||||
const report = await checkDependencySecurity({
|
||||
directory: process.cwd(),
|
||||
referenceLockfile,
|
||||
evidenceDirectory: values.evidence,
|
||||
sourceSha: process.env.GITHUB_SHA ?? null,
|
||||
});
|
||||
console.log(JSON.stringify(report));
|
||||
if (!report.checks_passed) process.exitCode = 1;
|
||||
} else if (values.mode === "published") {
|
||||
const response = await fetch("https://registry.npmjs.org/rea-agents/latest", {
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
headers: { accept: "application/json" },
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`Published REA metadata unavailable: ${response.status}`);
|
||||
const product = await response.json();
|
||||
if (
|
||||
product.name !== "rea-agents" ||
|
||||
typeof product.version !== "string" ||
|
||||
!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(
|
||||
product.version,
|
||||
)
|
||||
)
|
||||
throw new Error("Invalid published REA identity");
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-dependency-consumer-"));
|
||||
let report;
|
||||
try {
|
||||
const userconfig = join(workspace, "user.npmrc"),
|
||||
globalconfig = join(workspace, "global.npmrc");
|
||||
await writeFile(userconfig, "", { mode: 0o600 });
|
||||
await writeFile(globalconfig, "", { mode: 0o600 });
|
||||
const environment = {
|
||||
...process.env,
|
||||
npm_config_registry: "https://registry.npmjs.org",
|
||||
npm_config_userconfig: userconfig,
|
||||
npm_config_globalconfig: globalconfig,
|
||||
npm_config_maxsockets: "2",
|
||||
npm_config_prefer_online: "true",
|
||||
};
|
||||
const installation = await installCanaryPackage(
|
||||
`rea-agents@${product.version}`,
|
||||
workspace,
|
||||
environment,
|
||||
);
|
||||
report = await checkDependencySecurity({
|
||||
directory: installation.directory,
|
||||
referenceLockfile,
|
||||
evidenceDirectory: values.evidence,
|
||||
mode: "consumer",
|
||||
expectedVersion: product.version,
|
||||
sourceSha: process.env.GITHUB_SHA ?? null,
|
||||
environment,
|
||||
});
|
||||
} finally {
|
||||
// A cleanup failure propagates; never report a successful completed run first.
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
}
|
||||
console.log(JSON.stringify(report));
|
||||
if (!report.checks_passed) process.exitCode = 1;
|
||||
} else throw new Error("Expected --mode=preflight, source, or published");
|
||||
@@ -0,0 +1,19 @@
|
||||
export type NpmReleaseState =
|
||||
| { status: "absent" }
|
||||
| {
|
||||
status: "published";
|
||||
integrity: string;
|
||||
tag: "latest" | "next";
|
||||
taggedVersion: string | null;
|
||||
stableVersion: string | null;
|
||||
};
|
||||
export function readQualifiedNpmRelease(
|
||||
version: string,
|
||||
tag: "latest" | "next",
|
||||
request?: (url: string, init: RequestInit) => Promise<Response>,
|
||||
): Promise<NpmReleaseState>;
|
||||
export function requireMatchingNpmRelease(
|
||||
tarball: string,
|
||||
version: string,
|
||||
published: NpmReleaseState,
|
||||
): Promise<void>;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { verifyPublishedPackageIntegrity } from "./qualified-package-envelope.mjs";
|
||||
|
||||
/** Parse fresh registry state using the channel already owned by qualification. */
|
||||
export async function readQualifiedNpmRelease(version, tag, request = fetch) {
|
||||
if (!["latest", "next"].includes(tag))
|
||||
throw new Error("Invalid qualified npm channel");
|
||||
const response = await request("https://registry.npmjs.org/rea-agents", {
|
||||
headers: {
|
||||
Accept: "application/vnd.npm.install-v1+json",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
if (response.status === 404) return { status: "absent" };
|
||||
if (!response.ok)
|
||||
throw new Error(`npm registry lookup failed: HTTP ${response.status}`);
|
||||
const metadata = requireRecord(await response.json());
|
||||
if (metadata.name !== "rea-agents")
|
||||
throw new Error("Unexpected npm package name");
|
||||
const versions = requireRecord(metadata.versions);
|
||||
const tags = requireRecord(metadata["dist-tags"]);
|
||||
if (Object.values(tags).some((value) => typeof value !== "string"))
|
||||
throw new Error("Invalid npm dist-tags");
|
||||
if (!Object.hasOwn(versions, version)) return { status: "absent" };
|
||||
const published = requireRecord(versions[version]);
|
||||
const dist = requireRecord(published.dist);
|
||||
if (
|
||||
published.name !== "rea-agents" ||
|
||||
published.version !== version ||
|
||||
typeof dist.integrity !== "string" ||
|
||||
dist.integrity.length === 0
|
||||
)
|
||||
throw new Error("Invalid published npm identity or integrity");
|
||||
return {
|
||||
status: "published",
|
||||
integrity: dist.integrity,
|
||||
tag,
|
||||
taggedVersion: tags[tag] ?? null,
|
||||
stableVersion: tags.latest ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function requireRecord(value) {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value))
|
||||
throw new Error("Invalid npm registry metadata");
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Validate content before accepting a previously published or newly visible version. */
|
||||
export async function requireMatchingNpmRelease(tarball, version, published) {
|
||||
if (published.status !== "published")
|
||||
throw new Error(`rea-agents@${version} is not published`);
|
||||
await verifyPublishedPackageIntegrity(tarball, published.integrity);
|
||||
if (published.taggedVersion !== version)
|
||||
throw new Error(
|
||||
`Expected npm ${published.tag} to select ${version}, observed ${published.taggedVersion ?? "missing"}; refusing to change an existing dist-tag`,
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,9 @@
|
||||
export type NpmReleaseState =
|
||||
| { status: "absent" }
|
||||
| {
|
||||
status: "published";
|
||||
integrity: string;
|
||||
tag: "latest" | "next";
|
||||
taggedVersion: string | null;
|
||||
stableVersion: string | null;
|
||||
};
|
||||
import type { NpmReleaseState } from "./npm-release-state.mjs";
|
||||
|
||||
export function readNpmRelease(
|
||||
version: string,
|
||||
request?: (url: string, init: RequestInit) => Promise<Response>,
|
||||
): Promise<NpmReleaseState>;
|
||||
export function requireMatchingNpmRelease(
|
||||
tarball: string,
|
||||
version: string,
|
||||
published: NpmReleaseState,
|
||||
): Promise<void>;
|
||||
export function stableUpdateVersion(
|
||||
version: string,
|
||||
published: NpmReleaseState,
|
||||
|
||||
+11
-61
@@ -2,46 +2,16 @@ import { appendFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { z } from "zod";
|
||||
import { prerelease, valid } from "semver";
|
||||
import { releaseNpmTag } from "../release-npm-tag.mjs";
|
||||
import { verifyPublishedPackageIntegrity } from "./verified-package.mjs";
|
||||
import {
|
||||
readQualifiedNpmRelease,
|
||||
requireMatchingNpmRelease,
|
||||
} from "./npm-release-state.mjs";
|
||||
|
||||
/** Parse one npm metadata snapshot; errors and malformed records are never absence. */
|
||||
/** Read-only callers derive the channel through the canonical SemVer owner. */
|
||||
export async function readNpmRelease(version, request = fetch) {
|
||||
const tag = releaseNpmTag(version);
|
||||
const response = await request("https://registry.npmjs.org/rea-agents", {
|
||||
headers: {
|
||||
Accept: "application/vnd.npm.install-v1+json",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
});
|
||||
if (response.status === 404) return { status: "absent" };
|
||||
if (!response.ok)
|
||||
throw new Error(`npm registry lookup failed: HTTP ${response.status}`);
|
||||
const metadata = z
|
||||
.object({
|
||||
name: z.literal("rea-agents"),
|
||||
versions: z.record(z.string(), z.unknown()),
|
||||
"dist-tags": z.record(z.string(), z.string()),
|
||||
})
|
||||
.parse(await response.json());
|
||||
if (!Object.hasOwn(metadata.versions, version)) return { status: "absent" };
|
||||
const published = z
|
||||
.object({
|
||||
name: z.literal("rea-agents"),
|
||||
version: z.literal(version),
|
||||
dist: z.object({ integrity: z.string().min(1) }),
|
||||
})
|
||||
.parse(metadata.versions[version]);
|
||||
return {
|
||||
status: "published",
|
||||
integrity: published.dist.integrity,
|
||||
tag,
|
||||
taggedVersion: metadata["dist-tags"][tag] ?? null,
|
||||
stableVersion: metadata["dist-tags"].latest ?? null,
|
||||
};
|
||||
return readQualifiedNpmRelease(version, releaseNpmTag(version), request);
|
||||
}
|
||||
|
||||
/** REA update follows stable latest even when the candidate is published on next. */
|
||||
@@ -60,17 +30,6 @@ export function stableUpdateVersion(version, published) {
|
||||
return stable;
|
||||
}
|
||||
|
||||
/** Validate content before accepting a previously published or newly visible version. */
|
||||
export async function requireMatchingNpmRelease(tarball, version, published) {
|
||||
if (published.status !== "published")
|
||||
throw new Error(`rea-agents@${version} is not published`);
|
||||
await verifyPublishedPackageIntegrity(tarball, published.integrity);
|
||||
if (published.taggedVersion !== version)
|
||||
throw new Error(
|
||||
`Expected npm ${published.tag} to select ${version}, observed ${published.taggedVersion ?? "missing"}; refusing to change an existing dist-tag`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Retry only confirmed absence during the existing registry propagation window. */
|
||||
export async function waitForNpmRelease(
|
||||
version,
|
||||
@@ -91,26 +50,17 @@ export async function waitForNpmRelease(
|
||||
async function main() {
|
||||
const [operation, tarball, version, ...extra] = process.argv.slice(2);
|
||||
if (
|
||||
!["inspect", "verify"].includes(operation) ||
|
||||
operation !== "verify" ||
|
||||
tarball === undefined ||
|
||||
version === undefined ||
|
||||
extra.length
|
||||
)
|
||||
throw new Error(
|
||||
"Usage: node scripts/ci/npm-release.mjs inspect|verify <qualified-tarball> <version>",
|
||||
"Usage: node scripts/ci/npm-release.mjs verify <qualified-tarball> <version>",
|
||||
);
|
||||
const published =
|
||||
operation === "inspect"
|
||||
? await readNpmRelease(version)
|
||||
: await waitForNpmRelease(version);
|
||||
if (published.status === "published")
|
||||
await requireMatchingNpmRelease(tarball, version, published);
|
||||
if (operation === "inspect") {
|
||||
await appendFile(
|
||||
z.string().min(1).parse(process.env.GITHUB_OUTPUT),
|
||||
`published=${published.status === "published"}\n`,
|
||||
);
|
||||
} else if (process.env.GITHUB_STEP_SUMMARY !== undefined) {
|
||||
const published = await waitForNpmRelease(version);
|
||||
await requireMatchingNpmRelease(tarball, version, published);
|
||||
if (process.env.GITHUB_STEP_SUMMARY !== undefined) {
|
||||
await appendFile(
|
||||
process.env.GITHUB_STEP_SUMMARY,
|
||||
`npm: rea-agents@${version} is published, matches the qualified tarball, and is selected by ${published.tag}.\n`,
|
||||
|
||||
@@ -204,6 +204,10 @@ if (tier === "reused") {
|
||||
.split("\0")
|
||||
.filter(Boolean);
|
||||
for (const file of files) {
|
||||
selected.dependency_security ||= matchesAny(
|
||||
file,
|
||||
SCOPES.dependency_security,
|
||||
);
|
||||
if (matchesAny(file, SCOPES.reverse_tools)) selected.reverse_tools = true;
|
||||
if (
|
||||
/^(README(?:_[^/]+)?\.md|AGENTS\.md|CONTRIBUTING\.md|CHANGELOG\.md|SECURITY\.md|native\/windows\/README\.md|\.github\/pull_request_template\.md)$/.test(
|
||||
@@ -245,6 +249,7 @@ if (tier === "reused") {
|
||||
selected.code = true;
|
||||
selected.runtime = true;
|
||||
const owners = Object.entries(SCOPES)
|
||||
.filter(([owner]) => owner !== "dependency_security")
|
||||
.filter(([, patterns]) =>
|
||||
patterns.some((pattern) => matchesGlob(file, pattern)),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { appendFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
validateVerifiedPackageEnvelope,
|
||||
workflowPackageIdentity,
|
||||
} from "./qualified-package-envelope.mjs";
|
||||
import {
|
||||
readQualifiedNpmRelease,
|
||||
requireMatchingNpmRelease,
|
||||
} from "./npm-release-state.mjs";
|
||||
|
||||
/** Validate the original artifact and fresh registry state on every publisher attempt. */
|
||||
export async function prepareNpmPublication(request = fetch) {
|
||||
const { manifest, tarball } = await validateVerifiedPackageEnvelope({
|
||||
directory: ".cache/verified-package",
|
||||
...(await workflowPackageIdentity()),
|
||||
});
|
||||
const published = await readQualifiedNpmRelease(
|
||||
manifest.packageVersion,
|
||||
manifest.releaseTag,
|
||||
request,
|
||||
);
|
||||
if (published.status === "published")
|
||||
await requireMatchingNpmRelease(
|
||||
tarball,
|
||||
manifest.packageVersion,
|
||||
published,
|
||||
);
|
||||
if (!process.env.GITHUB_OUTPUT)
|
||||
throw new Error("GITHUB_OUTPUT is required for publication intent");
|
||||
// Fixed archive path and whitelisted channel; no opaque version enters workflow outputs.
|
||||
await appendFile(
|
||||
process.env.GITHUB_OUTPUT,
|
||||
`tag=${manifest.releaseTag}\npublished=${published.status === "published"}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
// The complete import graph intentionally uses only Node built-ins.
|
||||
// Rehearsal validates SemVer/channel semantics on the same immutable artifact first.
|
||||
if (
|
||||
process.argv[1] !== undefined &&
|
||||
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
)
|
||||
await prepareNpmPublication();
|
||||
@@ -0,0 +1,173 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
export const MANIFEST_NAME = "manifest.json";
|
||||
export const TARBALL_NAME = "package.tgz";
|
||||
const execFileAsync = promisify(execFile);
|
||||
const MANIFEST_KEYS = [
|
||||
"schemaVersion",
|
||||
"sourceSha",
|
||||
"runId",
|
||||
"runAttempt",
|
||||
"platform",
|
||||
"architecture",
|
||||
"packageVersion",
|
||||
"nodeVersion",
|
||||
"releaseTag",
|
||||
"sha256",
|
||||
].sort();
|
||||
|
||||
/** Construct the immutable envelope; semantic release intent belongs to qualification. */
|
||||
export function createVerifiedPackageEnvelope(metadata) {
|
||||
const manifest = { schemaVersion: 2, ...metadata };
|
||||
validateManifestShape(manifest);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/** Validate a retained package against the current checkout and workflow attempt. */
|
||||
export async function validateVerifiedPackageEnvelope({
|
||||
directory,
|
||||
sourceSha,
|
||||
runId,
|
||||
runAttempt,
|
||||
packageVersion,
|
||||
nodeVersion,
|
||||
allowLegacyManifest = false,
|
||||
}) {
|
||||
if (!/^[a-f0-9]{40}$/u.test(sourceSha))
|
||||
throw new Error("Expected source SHA must be a full commit SHA");
|
||||
if (!Number.isSafeInteger(runId) || runId <= 0)
|
||||
throw new Error("Expected workflow run ID must be a positive integer");
|
||||
if (!Number.isSafeInteger(runAttempt) || runAttempt <= 0)
|
||||
throw new Error("Expected workflow run attempt must be a positive integer");
|
||||
const manifestPath = join(directory, MANIFEST_NAME);
|
||||
let manifest;
|
||||
try {
|
||||
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
||||
} catch (cause) {
|
||||
throw new Error(`Could not read verified package manifest: ${cause}`, {
|
||||
cause,
|
||||
});
|
||||
}
|
||||
validateManifestShape(manifest, allowLegacyManifest);
|
||||
assertEqual(manifest.sourceSha, sourceSha, "source SHA");
|
||||
assertEqual(manifest.runId, runId, "workflow run ID");
|
||||
// A successful producer may belong to an earlier attempt when only failed
|
||||
// publisher jobs are rerun. The workflow still requires same-run qualification.
|
||||
if (manifest.runAttempt > runAttempt)
|
||||
throw new Error("Verified package workflow run attempt is from the future");
|
||||
assertEqual(manifest.platform, "linux", "verifier platform");
|
||||
assertEqual(manifest.architecture, "x64", "verifier architecture");
|
||||
assertEqual(manifest.packageVersion, packageVersion, "package version");
|
||||
assertEqual(manifest.nodeVersion, nodeVersion, "Node.js version");
|
||||
|
||||
// The artifact format deliberately fixes the name; no manifest value can
|
||||
// redirect validation to another path.
|
||||
const tarball = resolve(directory, TARBALL_NAME);
|
||||
const digest = await sha256File(tarball);
|
||||
assertEqual(manifest.sha256, digest, "tarball SHA-256");
|
||||
return { tarball, manifest };
|
||||
}
|
||||
|
||||
function validateManifestShape(manifest, allowLegacyManifest = false) {
|
||||
if (
|
||||
typeof manifest !== "object" ||
|
||||
manifest === null ||
|
||||
Array.isArray(manifest)
|
||||
)
|
||||
throw new Error("Verified package manifest must be a JSON object");
|
||||
const legacy = allowLegacyManifest && manifest.schemaVersion === 1;
|
||||
const expectedKeys = legacy
|
||||
? MANIFEST_KEYS.filter((key) => key !== "releaseTag")
|
||||
: MANIFEST_KEYS;
|
||||
const keys = Object.keys(manifest).sort();
|
||||
if (
|
||||
keys.length !== expectedKeys.length ||
|
||||
keys.some((key, index) => key !== expectedKeys[index])
|
||||
)
|
||||
throw new Error("Verified package manifest has missing or unknown fields");
|
||||
if (manifest.schemaVersion !== 2 && !legacy)
|
||||
throw new Error("Unsupported verified package manifest schema version");
|
||||
if (
|
||||
typeof manifest.sourceSha !== "string" ||
|
||||
!/^[a-f0-9]{40}$/u.test(manifest.sourceSha)
|
||||
)
|
||||
throw new Error("Verified package manifest has an invalid source SHA");
|
||||
if (!Number.isSafeInteger(manifest.runId) || manifest.runId <= 0)
|
||||
throw new Error("Verified package manifest has an invalid workflow run ID");
|
||||
if (!Number.isSafeInteger(manifest.runAttempt) || manifest.runAttempt <= 0)
|
||||
throw new Error(
|
||||
"Verified package manifest has an invalid workflow run attempt",
|
||||
);
|
||||
for (const key of [
|
||||
"platform",
|
||||
"architecture",
|
||||
"packageVersion",
|
||||
"nodeVersion",
|
||||
])
|
||||
if (typeof manifest[key] !== "string" || manifest[key].length === 0)
|
||||
throw new Error(`Verified package manifest has an invalid ${key}`);
|
||||
if (!legacy && !["latest", "next"].includes(manifest.releaseTag))
|
||||
throw new Error("Verified package manifest has an invalid release tag");
|
||||
if (
|
||||
typeof manifest.sha256 !== "string" ||
|
||||
!/^[a-f0-9]{64}$/u.test(manifest.sha256)
|
||||
)
|
||||
throw new Error("Verified package manifest has an invalid tarball SHA-256");
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, label) {
|
||||
if (actual !== expected)
|
||||
throw new Error(`Verified package ${label} mismatch`);
|
||||
}
|
||||
|
||||
export async function sha256File(path) {
|
||||
const hash = createHash("sha256");
|
||||
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
/** Require the registry to serve the exact archive selected for publication. */
|
||||
export async function verifyPublishedPackageIntegrity(tarball, integrity) {
|
||||
const hash = createHash("sha512");
|
||||
for await (const chunk of createReadStream(tarball)) hash.update(chunk);
|
||||
if (integrity !== `sha512-${hash.digest("base64")}`)
|
||||
throw new Error(
|
||||
"Published npm integrity differs from the original qualified package",
|
||||
);
|
||||
}
|
||||
|
||||
/** Bind each consumer to the checked-out source and the same workflow run. */
|
||||
export async function workflowPackageIdentity() {
|
||||
const sourceSha = process.env.GITHUB_SHA;
|
||||
if (sourceSha === undefined || !/^[a-f0-9]{40}$/u.test(sourceSha))
|
||||
throw new Error("GITHUB_SHA must be a full commit SHA");
|
||||
const checkoutSha = (
|
||||
await execFileAsync("git", ["rev-parse", "HEAD"])
|
||||
).stdout.trim();
|
||||
assertEqual(checkoutSha, sourceSha, "checked out source SHA");
|
||||
const packageJson = JSON.parse(await readFile("package.json", "utf8"));
|
||||
return {
|
||||
sourceSha,
|
||||
runId: parsePositiveInteger(process.env.GITHUB_RUN_ID, "GITHUB_RUN_ID"),
|
||||
runAttempt: parsePositiveInteger(
|
||||
process.env.GITHUB_RUN_ATTEMPT,
|
||||
"GITHUB_RUN_ATTEMPT",
|
||||
),
|
||||
packageVersion: packageJson.version,
|
||||
nodeVersion: process.version,
|
||||
};
|
||||
}
|
||||
|
||||
export function parsePositiveInteger(value, name) {
|
||||
if (value === undefined || !/^[1-9]\d*$/u.test(value))
|
||||
throw new Error(`${name} must be a positive integer`);
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed))
|
||||
throw new Error(`${name} must be a safe positive integer`);
|
||||
return parsed;
|
||||
}
|
||||
@@ -6,10 +6,8 @@ import { promisify } from "node:util";
|
||||
import { valid } from "semver";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
validateVerifiedPackage,
|
||||
verifyPublishedPackageIntegrity,
|
||||
} from "./verified-package.mjs";
|
||||
import { validateVerifiedPackage } from "./verified-package.mjs";
|
||||
import { verifyPublishedPackageIntegrity } from "./qualified-package-envelope.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const sha = z.string().regex(/^[a-f0-9]{40}$/u);
|
||||
@@ -82,9 +80,19 @@ export function selectRecoveryRelease({
|
||||
throw new Error(`Original release did not pass ${name}`);
|
||||
const publish = requireJob("Publish to npm");
|
||||
const writes = publish.steps.filter((step) => step.name === "Publish");
|
||||
const verified = publish.steps.filter(
|
||||
(step) => step.name === "Verify published npm bytes and dist-tag",
|
||||
const verificationJobs = parsedJobs.filter(
|
||||
(job) => job.name === "Verify published npm package",
|
||||
);
|
||||
if (
|
||||
verificationJobs.length > 1 ||
|
||||
verificationJobs.some((job) => job.status !== "completed")
|
||||
)
|
||||
throw new Error(
|
||||
"Expected at most one completed original npm verification job",
|
||||
);
|
||||
const verified = [publish, ...verificationJobs]
|
||||
.flatMap((job) => job.steps)
|
||||
.filter((step) => step.name === "Verify published npm bytes and dist-tag");
|
||||
if (
|
||||
!(
|
||||
(writes.length === 1 && writes[0].conclusion === "success") ||
|
||||
@@ -118,6 +126,8 @@ export async function verifyRecoveryPackage({
|
||||
runAttempt,
|
||||
packageVersion: version,
|
||||
nodeVersion: process.version,
|
||||
// Existing published releases retain their original v1 artifact; never rewrite it.
|
||||
allowLegacyManifest: true,
|
||||
});
|
||||
await verifyPublishedPackageIntegrity(tarball, integrity);
|
||||
return { version, sourceSha, runId, runAttempt, integrity };
|
||||
|
||||
@@ -28,9 +28,12 @@ if (
|
||||
);
|
||||
if (outputs.tier === "full" && selectedScopes.length !== CI_FLAGS.length)
|
||||
throw new Error("Full qualification requires every canonical CI scope");
|
||||
const required = reused ? ["changes"] : ["changes", "static", "test"];
|
||||
const required = reused
|
||||
? ["changes", "dependency-security"]
|
||||
: ["changes", "static", "test"];
|
||||
if (outputs.tier === "full") required.push("static-deep");
|
||||
const lanes = {
|
||||
dependency_security: ["dependency-security"],
|
||||
code: [
|
||||
"build",
|
||||
"test-shard",
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
// Shared build inputs are classified once in plan.mjs rather than repeated in
|
||||
// every workflow.
|
||||
export const SCOPES = {
|
||||
// Cross-cutting live registry evidence; does not replace provider ownership.
|
||||
dependency_security: [
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
".nvmrc",
|
||||
".npmrc",
|
||||
".github/workflows/ci.yml",
|
||||
".github/workflows/release.yml",
|
||||
".github/workflows/release-recovery.yml",
|
||||
"scripts/ci/dependency-security.mjs",
|
||||
"scripts/lib/dependency-security*.mjs",
|
||||
"scripts/lib/npm-locked-packages.mjs",
|
||||
"scripts/lib/npm-command.mjs",
|
||||
"scripts/lib/transport-authentication.mjs",
|
||||
"scripts/lib/release-package-install.mjs",
|
||||
"scripts/verify-release-artifact.mjs",
|
||||
"scripts/verify-published-package.mjs",
|
||||
"tests/boundary/filesystem/dependencySecurity*.test.ts",
|
||||
],
|
||||
reverse_tools: [
|
||||
".github/workflows/mutation.yml",
|
||||
"src/application/reverse/**",
|
||||
@@ -81,6 +100,7 @@ export const SCOPES = {
|
||||
"tests/boundary/mcp/nativeDemangleMcp.test.ts",
|
||||
"tests/boundary/providers/native/nativeSignatureTargetBinding.test.ts",
|
||||
"tests/boundary/cli/nativeSignatureCli.test.ts",
|
||||
"tests/boundary/cli/nativeMachoArm64_32Cli.test.ts",
|
||||
"tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts",
|
||||
"src/process/capture/FilesystemSnapshot.test.ts",
|
||||
"tests/process-global/filesystemSnapshotFallbackCancellation.test.ts",
|
||||
@@ -108,9 +128,14 @@ export const SCOPES = {
|
||||
".github/workflows/release-recovery.yml",
|
||||
"scripts/ci/release-recovery.mjs",
|
||||
"scripts/ci/verified-package.mjs",
|
||||
"scripts/ci/qualified-package-envelope.mjs",
|
||||
"scripts/ci/prepare-npm-publication.mjs",
|
||||
"scripts/ci/npm-release.*",
|
||||
"scripts/ci/npm-release-state.*",
|
||||
"scripts/verify-release-artifact.mjs",
|
||||
"scripts/lib/release-package-canary.mjs",
|
||||
"scripts/lib/release-package-install.mjs",
|
||||
"scripts/lib/npm-command.mjs",
|
||||
"scripts/verify-published-package.mjs",
|
||||
"tests/boundary/filesystem/releaseRecovery.test.ts",
|
||||
"tests/boundary/filesystem/mcpRegistryPublication.test.ts",
|
||||
|
||||
+43
-178
@@ -1,28 +1,19 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { execFile } from "node:child_process";
|
||||
import { copyFile, readFile, writeFile } from "node:fs/promises";
|
||||
import { copyFile, writeFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { valid } from "semver";
|
||||
import { releaseNpmTag } from "../release-npm-tag.mjs";
|
||||
import {
|
||||
MANIFEST_NAME,
|
||||
TARBALL_NAME,
|
||||
createVerifiedPackageEnvelope,
|
||||
parsePositiveInteger,
|
||||
sha256File,
|
||||
validateVerifiedPackageEnvelope,
|
||||
workflowPackageIdentity,
|
||||
} from "./qualified-package-envelope.mjs";
|
||||
|
||||
const MANIFEST_NAME = "manifest.json";
|
||||
const TARBALL_NAME = "package.tgz";
|
||||
const MANIFEST_KEYS = [
|
||||
"schemaVersion",
|
||||
"sourceSha",
|
||||
"runId",
|
||||
"runAttempt",
|
||||
"platform",
|
||||
"architecture",
|
||||
"packageVersion",
|
||||
"nodeVersion",
|
||||
"sha256",
|
||||
].sort();
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
/** Normalize workflow identity and host facts into the manifest-owned fields. */
|
||||
/** Qualification owns the exact SemVer and its parsed publication channel. */
|
||||
export function createVerifiedPackageMetadata({
|
||||
sourceSha,
|
||||
runId,
|
||||
@@ -42,28 +33,38 @@ export function createVerifiedPackageMetadata({
|
||||
architecture,
|
||||
packageVersion,
|
||||
nodeVersion,
|
||||
releaseTag: releaseNpmTag(packageVersion),
|
||||
};
|
||||
}
|
||||
|
||||
/** Build the single supported manifest representation for a verified tarball. */
|
||||
function validateReleaseIntent(manifest) {
|
||||
if (valid(manifest.packageVersion) !== manifest.packageVersion)
|
||||
throw new Error("Verified package manifest has an invalid package version");
|
||||
if (
|
||||
manifest.schemaVersion === 2 &&
|
||||
releaseNpmTag(manifest.packageVersion) !== manifest.releaseTag
|
||||
)
|
||||
throw new Error("Verified package release tag mismatch");
|
||||
}
|
||||
|
||||
function createVerifiedPackageManifest(metadata) {
|
||||
const manifest = { schemaVersion: 1, ...metadata };
|
||||
validateManifestShape(manifest);
|
||||
const manifest = createVerifiedPackageEnvelope(metadata);
|
||||
validateReleaseIntent(manifest);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/** Write a manifest after the caller has completed qualification and cleanup. */
|
||||
/** Write only after qualification and its cleanup have completed. */
|
||||
export async function writeVerifiedPackageManifest(directory, metadata) {
|
||||
const manifest = createVerifiedPackageManifest(metadata);
|
||||
await writeFile(
|
||||
join(directory, MANIFEST_NAME),
|
||||
`${JSON.stringify(manifest, null, 2)}\n`,
|
||||
{ encoding: "utf8" },
|
||||
"utf8",
|
||||
);
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/** Copy the qualified archive under its fixed name and prepare its manifest. */
|
||||
/** Retain the exact archive without unpacking or rebuilding it. */
|
||||
export async function retainVerifiedPackage({
|
||||
sourceTarball,
|
||||
directory,
|
||||
@@ -71,174 +72,40 @@ export async function retainVerifiedPackage({
|
||||
}) {
|
||||
const tarball = join(directory, TARBALL_NAME);
|
||||
await copyFile(sourceTarball, tarball);
|
||||
const sha256 = await sha256File(tarball);
|
||||
return {
|
||||
directory,
|
||||
metadata: createVerifiedPackageManifest({ ...metadata, sha256 }),
|
||||
metadata: createVerifiedPackageManifest({
|
||||
...metadata,
|
||||
sha256: await sha256File(tarball),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Validate a retained package against the current checkout and workflow attempt. */
|
||||
export async function validateVerifiedPackage({
|
||||
directory,
|
||||
sourceSha,
|
||||
runId,
|
||||
runAttempt,
|
||||
packageVersion,
|
||||
nodeVersion,
|
||||
}) {
|
||||
if (!/^[a-f0-9]{40}$/u.test(sourceSha))
|
||||
throw new Error("Expected source SHA must be a full commit SHA");
|
||||
if (!Number.isSafeInteger(runId) || runId <= 0)
|
||||
throw new Error("Expected workflow run ID must be a positive integer");
|
||||
if (!Number.isSafeInteger(runAttempt) || runAttempt <= 0)
|
||||
throw new Error("Expected workflow run attempt must be a positive integer");
|
||||
const manifestPath = join(directory, MANIFEST_NAME);
|
||||
let manifest;
|
||||
try {
|
||||
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
||||
} catch (cause) {
|
||||
throw new Error(`Could not read verified package manifest: ${cause}`, {
|
||||
cause,
|
||||
});
|
||||
}
|
||||
validateManifestShape(manifest);
|
||||
assertEqual(manifest.sourceSha, sourceSha, "source SHA");
|
||||
assertEqual(manifest.runId, runId, "workflow run ID");
|
||||
// A successful producer may belong to an earlier attempt when only failed
|
||||
// publisher jobs are rerun. The workflow still requires same-run qualification.
|
||||
if (manifest.runAttempt > runAttempt)
|
||||
throw new Error("Verified package workflow run attempt is from the future");
|
||||
assertEqual(manifest.platform, "linux", "verifier platform");
|
||||
assertEqual(manifest.architecture, "x64", "verifier architecture");
|
||||
assertEqual(manifest.packageVersion, packageVersion, "package version");
|
||||
assertEqual(manifest.nodeVersion, nodeVersion, "Node.js version");
|
||||
|
||||
// The artifact format deliberately fixes the name; no manifest value can
|
||||
// redirect validation to another path.
|
||||
const tarball = resolve(directory, TARBALL_NAME);
|
||||
const digest = await sha256File(tarball);
|
||||
assertEqual(manifest.sha256, digest, "tarball SHA-256");
|
||||
/** Read-only consumers additionally validate SemVer and its qualified channel. */
|
||||
export async function validateVerifiedPackage(options) {
|
||||
const { tarball, manifest } = await validateVerifiedPackageEnvelope(options);
|
||||
validateReleaseIntent(manifest);
|
||||
return tarball;
|
||||
}
|
||||
|
||||
function validateManifestShape(manifest) {
|
||||
if (
|
||||
typeof manifest !== "object" ||
|
||||
manifest === null ||
|
||||
Array.isArray(manifest)
|
||||
)
|
||||
throw new Error("Verified package manifest must be a JSON object");
|
||||
const keys = Object.keys(manifest).sort();
|
||||
if (
|
||||
keys.length !== MANIFEST_KEYS.length ||
|
||||
keys.some((key, index) => key !== MANIFEST_KEYS[index])
|
||||
)
|
||||
throw new Error("Verified package manifest has missing or unknown fields");
|
||||
if (manifest.schemaVersion !== 1)
|
||||
throw new Error("Unsupported verified package manifest schema version");
|
||||
if (
|
||||
typeof manifest.sourceSha !== "string" ||
|
||||
!/^[a-f0-9]{40}$/u.test(manifest.sourceSha)
|
||||
)
|
||||
throw new Error("Verified package manifest has an invalid source SHA");
|
||||
if (!Number.isSafeInteger(manifest.runId) || manifest.runId <= 0)
|
||||
throw new Error("Verified package manifest has an invalid workflow run ID");
|
||||
if (!Number.isSafeInteger(manifest.runAttempt) || manifest.runAttempt <= 0)
|
||||
throw new Error(
|
||||
"Verified package manifest has an invalid workflow run attempt",
|
||||
);
|
||||
for (const key of [
|
||||
"platform",
|
||||
"architecture",
|
||||
"packageVersion",
|
||||
"nodeVersion",
|
||||
])
|
||||
if (typeof manifest[key] !== "string" || manifest[key].length === 0)
|
||||
throw new Error(`Verified package manifest has an invalid ${key}`);
|
||||
if (valid(manifest.packageVersion) !== manifest.packageVersion)
|
||||
throw new Error("Verified package manifest has an invalid package version");
|
||||
if (
|
||||
typeof manifest.sha256 !== "string" ||
|
||||
!/^[a-f0-9]{64}$/u.test(manifest.sha256)
|
||||
)
|
||||
throw new Error("Verified package manifest has an invalid tarball SHA-256");
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, label) {
|
||||
if (actual !== expected)
|
||||
throw new Error(`Verified package ${label} mismatch`);
|
||||
}
|
||||
|
||||
async function sha256File(path) {
|
||||
const hash = createHash("sha256");
|
||||
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
/** Require the registry to serve the exact archive selected for publication. */
|
||||
export async function verifyPublishedPackageIntegrity(tarball, integrity) {
|
||||
const hash = createHash("sha512");
|
||||
for await (const chunk of createReadStream(tarball)) hash.update(chunk);
|
||||
if (integrity !== `sha512-${hash.digest("base64")}`)
|
||||
throw new Error(
|
||||
"Published npm integrity differs from the original qualified package",
|
||||
);
|
||||
}
|
||||
|
||||
async function validateFromCli(directory) {
|
||||
const sourceSha = process.env.GITHUB_SHA;
|
||||
const runId = parsePositiveInteger(
|
||||
process.env.GITHUB_RUN_ID,
|
||||
"GITHUB_RUN_ID",
|
||||
);
|
||||
const runAttempt = parsePositiveInteger(
|
||||
process.env.GITHUB_RUN_ATTEMPT,
|
||||
"GITHUB_RUN_ATTEMPT",
|
||||
);
|
||||
if (sourceSha === undefined || !/^[a-f0-9]{40}$/u.test(sourceSha))
|
||||
throw new Error("GITHUB_SHA must be a full commit SHA");
|
||||
const checkoutSha = (
|
||||
await execFileAsync("git", ["rev-parse", "HEAD"])
|
||||
).stdout.trim();
|
||||
assertEqual(checkoutSha, sourceSha, "checked out source SHA");
|
||||
const packageJson = JSON.parse(await readFile("package.json", "utf8"));
|
||||
const tarball = await validateVerifiedPackage({
|
||||
directory,
|
||||
sourceSha,
|
||||
runId,
|
||||
runAttempt,
|
||||
packageVersion: packageJson.version,
|
||||
nodeVersion: process.version,
|
||||
});
|
||||
const expected = await workflowPackageIdentity();
|
||||
const tarball = await validateVerifiedPackage({ directory, ...expected });
|
||||
if (process.env.GITHUB_OUTPUT === undefined)
|
||||
throw new Error(
|
||||
"GITHUB_OUTPUT is required to publish the validated tarball path",
|
||||
);
|
||||
await writeFile(
|
||||
process.env.GITHUB_OUTPUT,
|
||||
`tarball=${tarball}\nversion=${packageJson.version}\n`,
|
||||
{
|
||||
encoding: "utf8",
|
||||
flag: "a",
|
||||
},
|
||||
`tarball=${tarball}\nversion=${expected.packageVersion}\n`,
|
||||
{ encoding: "utf8", flag: "a" },
|
||||
);
|
||||
process.stdout.write(`${tarball}\n`);
|
||||
}
|
||||
|
||||
function parsePositiveInteger(value, name) {
|
||||
if (value === undefined || !/^[1-9]\d*$/u.test(value))
|
||||
throw new Error(`${name} must be a positive integer`);
|
||||
const parsed = Number(value);
|
||||
if (!Number.isSafeInteger(parsed))
|
||||
throw new Error(`${name} must be a safe positive integer`);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1];
|
||||
if (
|
||||
invokedPath !== undefined &&
|
||||
resolve(invokedPath) === fileURLToPath(import.meta.url)
|
||||
process.argv[1] !== undefined &&
|
||||
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||
) {
|
||||
const [command, directory, ...extra] = process.argv.slice(2);
|
||||
if (command !== "validate" || directory === undefined || extra.length > 0) {
|
||||
@@ -246,7 +113,5 @@ if (
|
||||
"Usage: node scripts/ci/verified-package.mjs validate <artifact-directory>\n",
|
||||
);
|
||||
process.exitCode = 2;
|
||||
} else {
|
||||
await validateFromCli(directory);
|
||||
}
|
||||
} else await validateFromCli(directory);
|
||||
}
|
||||
Loaded 100 of 945 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user