Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528da7cc31 | ||
|
|
e9a71efa31 | ||
|
|
fe7bcc3fbe | ||
|
|
736aa946bc | ||
|
|
d929543198 | ||
|
|
e598b4656a | ||
|
|
ec394a2bdf | ||
|
|
7054bbb9ab | ||
|
|
b2257653ca | ||
|
|
b3b5c8b133 | ||
|
|
befbc024c9 | ||
|
|
fc41b24dc4 | ||
|
|
2c90082a69 | ||
|
|
82763824dc | ||
|
|
41e4c06397 | ||
|
|
a5024a9710 | ||
|
|
fc791bb606 | ||
|
|
8ed3d1df72 | ||
|
|
2b5ee72d26 | ||
|
|
2a2a33ad92 | ||
|
|
c7697595d8 | ||
|
|
c9f9b83c6f | ||
|
|
55cef2566a | ||
|
|
f255899ee8 | ||
|
|
65726afaaa | ||
|
|
30ba5e99f9 | ||
|
|
b6529bf1af | ||
|
|
1b8a9c831b | ||
|
|
cb18d4c9c4 | ||
|
|
47b4628bac | ||
|
|
670ad6bd96 | ||
|
|
c61c108b52 | ||
|
|
d7651b6a0d | ||
|
|
e18fccc548 | ||
|
|
e45eb33f03 | ||
|
|
337d2e33c5 | ||
|
|
6688ed6db7 | ||
|
|
c1fd496e6a | ||
|
|
6fcfc5b5e0 | ||
|
|
35e17a7172 | ||
|
|
f8f3593a8c | ||
|
|
ed4a1c7d95 | ||
|
|
7a83af3cba | ||
|
|
f5b1dce881 | ||
|
|
73c72d5745 | ||
|
|
ce89706145 | ||
|
|
3e88e21705 | ||
|
|
d94ef22d70 | ||
|
|
c61a24b8c5 | ||
|
|
fc712b3750 | ||
|
|
ab45bf641e | ||
|
|
57f30029b1 | ||
|
|
22ec8bdb2f | ||
|
|
a715fb9e6d | ||
|
|
85788452dd | ||
|
|
1b036ecbff | ||
|
|
933f216698 | ||
|
|
cf0a38ad97 | ||
|
|
a1ca8c76af | ||
|
|
034342ba28 | ||
|
|
788964d52c | ||
|
|
acca91d22b | ||
|
|
0323ce0a28 | ||
|
|
089ef732e8 | ||
|
|
f09f454f45 | ||
|
|
70cd4cbfb8 | ||
|
|
b7dcf782aa | ||
|
|
4135c911d3 | ||
|
|
0781716788 | ||
|
|
847b7328cf | ||
|
|
baad9eebe4 | ||
|
|
4692bb3330 | ||
|
|
af6a998066 | ||
|
|
7a66470385 | ||
|
|
90c6922905 | ||
|
|
3ded799bc7 | ||
|
|
d69acc3455 | ||
|
|
7d1e4d0e2b | ||
|
|
949228b606 | ||
|
|
5e8f7c536e | ||
|
|
18591f78ae | ||
|
|
44b197f924 | ||
|
|
610f873e91 | ||
|
|
59f528f0e5 | ||
|
|
c553d1138e | ||
|
|
930dd5357f | ||
|
|
084e540414 | ||
|
|
db689f3c3c | ||
|
|
f15ddbea7a | ||
|
|
68081ac414 | ||
|
|
bddeae5108 | ||
|
|
f78d137b42 | ||
|
|
1a3a96d36b | ||
|
|
c9764084b8 | ||
|
|
0f966e2b44 | ||
|
|
1851f1fcfd | ||
|
|
2b2a5d0f26 | ||
|
|
2a9252e4e6 | ||
|
|
7666a482b6 | ||
|
|
fa318995f5 | ||
|
|
9a44fea347 | ||
|
|
2819defeea | ||
|
|
937b4e69c8 | ||
|
|
1b668a527d | ||
|
|
1e377e6965 | ||
|
|
fb6e2b07e0 | ||
|
|
c4608a2962 | ||
|
|
225f26a5f8 | ||
|
|
a004061abc | ||
|
|
01a1b3fda6 | ||
|
|
3d2846f237 | ||
|
|
709e99d781 | ||
|
|
bb49252523 | ||
|
|
4946688c33 | ||
|
|
333e0ab0df | ||
|
|
c0cfccb45f | ||
|
|
fbdd040324 | ||
|
|
dadf909dce | ||
|
|
097a4bce6f | ||
|
|
c831c23633 | ||
|
|
5a46fbacd5 | ||
|
|
fffb074193 | ||
|
|
09e7f07108 | ||
|
|
1bbf4bd16c | ||
|
|
07a501e82c | ||
|
|
ebecf06f6e | ||
|
|
7e0c7b1244 | ||
|
|
d9cd239c5e | ||
|
|
11f64f653e | ||
|
|
9b7ec809e6 | ||
|
|
170bfc7b3f | ||
|
|
591c87c4b6 | ||
|
|
bfd76c5d16 | ||
|
|
9ac91e0c1e | ||
|
|
30a9139b87 | ||
|
|
a9588be4eb | ||
|
|
fc789d19ff | ||
|
|
4ff2dadf66 | ||
|
|
1a06c82772 | ||
|
|
7d885d632c | ||
|
|
57a23f0a2d | ||
|
|
d2a53beb3d | ||
|
|
a8887a9a85 | ||
|
|
45d5e80144 | ||
|
|
50dd9fcad1 | ||
|
|
6add17a317 | ||
|
|
8c0e431895 | ||
|
|
ceb77f6697 | ||
|
|
fc7e41ff3b | ||
|
|
cb4ff50202 | ||
|
|
b47055edbe | ||
|
|
0405c9d361 | ||
|
|
db8128dc0d | ||
|
|
2abfe53dbd | ||
|
|
efbc486b9b | ||
|
|
11b747f29d | ||
|
|
75104040df | ||
|
|
357996cc89 | ||
|
|
e79e014f23 | ||
|
|
b8510ee00b | ||
|
|
36615d9165 | ||
|
|
c5fb86d194 | ||
|
|
0df0e4f53e | ||
|
|
204b9f3ac1 | ||
|
|
a732c859dd | ||
|
|
10aa74dfed | ||
|
|
93e75bc8fe | ||
|
|
1579b6865e | ||
|
|
569ef4159d | ||
|
|
0f623732bd | ||
|
|
044fb9127a | ||
|
|
1438422dfe | ||
|
|
eb44921c4c | ||
|
|
ca1b2f8608 | ||
|
|
0f55f247cd | ||
|
|
6060de329d | ||
|
|
c0aa2e32b3 | ||
|
|
eab789aed7 | ||
|
|
790657dc0d | ||
|
|
0b4fd11c8c | ||
|
|
c99c6a89d6 | ||
|
|
3b79af28e8 | ||
|
|
cbc0e97c29 | ||
|
|
92a0edf187 | ||
|
|
1b46398795 | ||
|
|
f75b1a416b | ||
|
|
0686b07546 | ||
|
|
c2b79aa462 | ||
|
|
efe0923914 | ||
|
|
a32b4b6ac5 | ||
|
|
60c08802a7 | ||
|
|
7435e79d65 | ||
|
|
44d98de236 | ||
|
|
ba33c706d2 | ||
|
|
aade9bbcb7 | ||
|
|
0930e94e07 | ||
|
|
3f6b5755b3 | ||
|
|
45b6a7fe8f | ||
|
|
2daeff4b24 | ||
|
|
e67c3642c8 | ||
|
|
030c477328 | ||
|
|
3a34ad37bd | ||
|
|
1250aaedd8 | ||
|
|
2b60e097f2 | ||
|
|
5e79d468e5 | ||
|
|
44c9d2924d | ||
|
|
503109995f | ||
|
|
28f5761973 | ||
|
|
dae1e55381 | ||
|
|
322506d0c0 | ||
|
|
7582f0dff2 | ||
|
|
864314ac85 | ||
|
|
0351328e54 | ||
|
|
81f7638203 | ||
|
|
7e5cffa2ae | ||
|
|
1d826f304f | ||
|
|
7fb0a0cc57 | ||
|
|
e5deb832cb | ||
|
|
cf806ea02a | ||
|
|
ee5cfbe05c | ||
|
|
459c0951f2 | ||
|
|
a154b9752c | ||
|
|
ffeb2d4367 | ||
|
|
5f0065c542 | ||
|
|
7c962cc70b | ||
|
|
57290a01ac | ||
|
|
535c8ef303 | ||
|
|
e5828596c5 | ||
|
|
04345767a8 | ||
|
|
ca7450beff | ||
|
|
cd88163e42 | ||
|
|
ccd748635c | ||
|
|
12b3cdb804 | ||
|
|
eef4a0bfb4 | ||
|
|
f2f83934d2 | ||
|
|
f14c2d4df3 | ||
|
|
56128a8424 | ||
|
|
5b3400d5d9 | ||
|
|
57bb7ec5ca | ||
|
|
187f113f79 | ||
|
|
dd1c879580 | ||
|
|
c482ddd6ad | ||
|
|
69b8509602 | ||
|
|
cf6ea09711 | ||
|
|
90d4098a01 | ||
|
|
bdacd4140e | ||
|
|
307af044bc | ||
|
|
9385060f9c | ||
|
|
1c296f11a1 | ||
|
|
fb752d2293 | ||
|
|
8dde6370d0 | ||
|
|
102c5a3ae5 | ||
|
|
11d38cbc00 | ||
|
|
7777abf473 | ||
|
|
27311f88d4 | ||
|
|
eb00112a3f | ||
|
|
76fa5304a8 | ||
|
|
6396851958 | ||
|
|
b8eea783da | ||
|
|
9d00d7d7fa | ||
|
|
74afb04f32 | ||
|
|
7df85f16a0 | ||
|
|
4c401458da | ||
|
|
ada133c256 | ||
|
|
b57a054d5e | ||
|
|
29d3dcb7ea | ||
|
|
dc6872c66f | ||
|
|
16e1cc65ed | ||
|
|
3f11bd9655 | ||
|
|
bcc884556e | ||
|
|
778114a1ab | ||
|
|
b98be5408b | ||
|
|
410537667e | ||
|
|
36c91428fc | ||
|
|
66536d0d2e | ||
|
|
9387282274 | ||
|
|
1341e8c831 | ||
|
|
07540cbcde | ||
|
|
713163e644 | ||
|
|
877d40408b | ||
|
|
e195b8ba91 | ||
|
|
4190630432 | ||
|
|
e5791229f3 | ||
|
|
7484401601 | ||
|
|
7375ab6e34 | ||
|
|
e62e92526c | ||
|
|
b782064d54 | ||
|
|
ef153774f7 | ||
|
|
ae433f0983 | ||
|
|
ad9e22d05f | ||
|
|
7cfcd166e3 | ||
|
|
583166785f | ||
|
|
03ffb2c13d | ||
|
|
576b58cefe | ||
|
|
17e3cf9802 | ||
|
|
a9af4718ad | ||
|
|
bdf865252c | ||
|
|
7e1c560dd6 | ||
|
|
66dcd94d09 | ||
|
|
ee9bf58abc | ||
|
|
96e7c22a9c | ||
|
|
3f5b703379 | ||
|
|
131b2d85f3 | ||
|
|
23c70d674c | ||
|
|
6cc6d22585 | ||
|
|
02aea22f88 | ||
|
|
345c47ccf8 | ||
|
|
f79fd59e96 | ||
|
|
500e32399a | ||
|
|
ec67aebed0 | ||
|
|
f4df6ab280 | ||
|
|
77be0f6666 | ||
|
|
767fe0f0cc | ||
|
|
5b5de1b527 | ||
|
|
7ad75bdc80 | ||
|
|
5830088520 | ||
|
|
f3534749e5 | ||
|
|
d901561640 | ||
|
|
b3c5672ce6 | ||
|
|
70771523c5 | ||
|
|
fd8a2e98ff | ||
|
|
278d7fb284 | ||
|
|
e8d22ec9eb | ||
|
|
e84c58dabf | ||
|
|
bffdd91053 | ||
|
|
e572c38dcf | ||
|
|
eca18e08ac | ||
|
|
443c7aff77 | ||
|
|
55778f20ba | ||
|
|
29a2d13994 | ||
|
|
88afcc738b | ||
|
|
6e344302cb | ||
|
|
896696b2e7 | ||
|
|
9fe54c4073 | ||
|
|
36db19c59c | ||
|
|
5fa7a52d1f | ||
|
|
b940ad0d65 | ||
|
|
31b99306aa | ||
|
|
c7105d9723 | ||
|
|
b8513401e1 | ||
|
|
b11fe63bf4 | ||
|
|
bcc141549a | ||
|
|
fab33a66c6 | ||
|
|
86afc040e5 | ||
|
|
127c621328 | ||
|
|
4d4631ac94 | ||
|
|
1b14870ace | ||
|
|
a688c79784 | ||
|
|
537467ab9e | ||
|
|
a769915f6e | ||
|
|
b58adafa4f | ||
|
|
41a2e84647 | ||
|
|
04672f2b91 | ||
|
|
0f4c484be5 | ||
|
|
6a7f70fd54 | ||
|
|
3ea5fdd7cf | ||
|
|
ff56f78594 | ||
|
|
679658b287 | ||
|
|
5b7fe94710 | ||
|
|
80de3145e4 | ||
|
|
f6905719c3 | ||
|
|
a6c3d3a365 | ||
|
|
ec271121e4 | ||
|
|
55dd21a5e2 | ||
|
|
d82ad1faf0 | ||
|
|
d4bd65c53c | ||
|
|
2dc89f81be | ||
|
|
ec56c1da04 | ||
|
|
30128a03cf | ||
|
|
599a8ee11d | ||
|
|
e049ef70f7 | ||
|
|
bbf0638e88 | ||
|
|
242e2b0001 | ||
|
|
20e4f83097 | ||
|
|
97aff28201 | ||
|
|
641a8395ee | ||
|
|
1ae9000587 | ||
|
|
02987d8e8c | ||
|
|
b1b0211c28 | ||
|
|
80592b82a6 | ||
|
|
b9b3a9098b | ||
|
|
1f122c02b7 | ||
|
|
bcb60c179e | ||
|
|
c6166d4b56 | ||
|
|
3a4d49406a | ||
|
|
7072f87baf | ||
|
|
1ebbc3b371 | ||
|
|
c86b09a109 | ||
|
|
7e9fb6f1bb | ||
|
|
cd740a6764 | ||
|
|
22552525d5 | ||
|
|
0161a593e5 | ||
|
|
70c238e376 | ||
|
|
f0e86997aa | ||
|
|
3ae02f6a45 | ||
|
|
4acda94b76 | ||
|
|
4d98123279 | ||
|
|
6510d0d57f | ||
|
|
aa6f74af04 | ||
|
|
1342eddd03 | ||
|
|
14fef73882 | ||
|
|
06739aeaf2 | ||
|
|
bee73fcb67 | ||
|
|
3620074b31 | ||
|
|
ddcdb3c378 | ||
|
|
4aa51bfe19 | ||
|
|
2ad2a044d4 | ||
|
|
49e73fc03b | ||
|
|
0bf01dfa59 | ||
|
|
9630c8ff46 | ||
|
|
dd36b4a8a0 | ||
|
|
87a8c8c923 | ||
|
|
3cb9d61ec1 | ||
|
|
2320ac23ee | ||
|
|
4c83846947 | ||
|
|
cddf31d55e | ||
|
|
e1897c3c17 | ||
|
|
ed24604ee1 | ||
|
|
20b802f165 | ||
|
|
23dc8fbf83 | ||
|
|
0c6c571a01 | ||
|
|
a9195c3a98 | ||
|
|
4758ba30b6 | ||
|
|
64041e3072 | ||
|
|
39f96d7188 | ||
|
|
02095cd74a | ||
|
|
26525706fc | ||
|
|
45a3509db3 | ||
|
|
e4186af31b | ||
|
|
42667fc1a0 | ||
|
|
729ca2ab6a | ||
|
|
4481f86188 | ||
|
|
72d5bbc96d | ||
|
|
97268f40ea | ||
|
|
388b907c92 | ||
|
|
0e6eca4085 | ||
|
|
c7ceaadb70 | ||
|
|
090b454350 | ||
|
|
0bc422d7be | ||
|
|
2895a18ca9 | ||
|
|
7a6defab18 | ||
|
|
8fef2aa64e | ||
|
|
ebaaa79ca0 | ||
|
|
1d5f95dc4a | ||
|
|
f652709cac | ||
|
|
c8ef1f916c | ||
|
|
f1045001f0 | ||
|
|
69bd81f7f2 | ||
|
|
1b6c899b03 | ||
|
|
93e29d242a | ||
|
|
c6631814e0 | ||
|
|
62c8b3da1b | ||
|
|
0c9ee56c2f | ||
|
|
501c5d45fe | ||
|
|
df3d96da80 | ||
|
|
7e3a198638 | ||
|
|
72b9ab4af1 | ||
|
|
d4f0c5e664 | ||
|
|
3f80b75708 | ||
|
|
855ed9689e | ||
|
|
59298923a5 | ||
|
|
af782dd5f5 | ||
|
|
d86d29c411 | ||
|
|
77e71d55ec | ||
|
|
168bfe5060 | ||
|
|
554ea7611e | ||
|
|
911894d758 | ||
|
|
75e7965dc3 | ||
|
|
522b9927c8 | ||
|
|
2b98fc5f51 | ||
|
|
8b1549a264 | ||
|
|
782cc3b060 | ||
|
|
f825c869d6 | ||
|
|
9240a4e9ac | ||
|
|
ba06d9ec52 | ||
|
|
af7638fa9d | ||
|
|
48b90fbaa4 | ||
|
|
c580ba940a | ||
|
|
2a78763e20 | ||
|
|
70611713e1 | ||
|
|
43e33cd49e | ||
|
|
efc491ab45 | ||
|
|
b19f9e4d2d | ||
|
|
af142f656c | ||
|
|
ffd8c9c03d | ||
|
|
7dcd23c005 | ||
|
|
ed93abed02 | ||
|
|
d005bd5dcd | ||
|
|
688defca0f | ||
|
|
3c39a3f536 | ||
|
|
e6c6fdd21a | ||
|
|
40e94aeb9d | ||
|
|
3e7c630223 | ||
|
|
204a6f3f20 | ||
|
|
e13da084e7 | ||
|
|
94072364c7 | ||
|
|
e103a89889 | ||
|
|
edd9939879 | ||
|
|
1e38a038c6 | ||
|
|
6e25cc3c76 | ||
|
|
7369b5ee63 | ||
|
|
c85abacdb2 | ||
|
|
cbe769b504 | ||
|
|
865c35e25a | ||
|
|
1016c96e6b | ||
|
|
f0b8ae1fc8 | ||
|
|
c9982ad8bf | ||
|
|
9b08c2b1cb | ||
|
|
21aa877e5a | ||
|
|
b17487f482 | ||
|
|
30e9b83a68 | ||
|
|
5278835b53 | ||
|
|
e3a012d6c9 | ||
|
|
fd65e278be | ||
|
|
5996d8160f | ||
|
|
c938f97fb2 | ||
|
|
c45d912c91 | ||
|
|
ce748be2f3 | ||
|
|
173da755cd | ||
|
|
1eca600515 | ||
|
|
ae9a7f34e1 | ||
|
|
96d1f0f3fb | ||
|
|
beca34f522 | ||
|
|
48a683ac8c | ||
|
|
a3daf8a07b | ||
|
|
9680da76c8 | ||
|
|
46f51ee6cc | ||
|
|
c22d7cd756 | ||
|
|
4c79127fcb | ||
|
|
c8e85b962a | ||
|
|
fc0dbe7f9d | ||
|
|
23f72141d5 | ||
|
|
5d12a0b4b6 | ||
|
|
9958754f6c | ||
|
|
a4b8e9ccee | ||
|
|
c6503edfc4 | ||
|
|
5dd2ce3125 |
No files matched your search
@@ -68,9 +68,9 @@ closing-issue reporters. Grouping preserves all those credits in one line.
|
|||||||
When Highlights, detailed entries or migrations reference the same work,
|
When Highlights, detailed entries or migrations reference the same work,
|
||||||
put its thanks in one of those entries, not all of them. Include issue links.
|
put its thanks in one of those entries, not all of them. Include issue links.
|
||||||
Direct-commit bullets carry the commit link and known human credit.
|
Direct-commit bullets carry the commit link and known human credit.
|
||||||
Exclude GitHub bots and known agent accounts. REA's observed Anthropic and
|
Exclude GitHub bots and known agent accounts. REA's observed Anthropic, Cursor
|
||||||
Cursor co-author trailers resolve to `@claude` and `@cursoragent` with GitHub
|
and Codex co-author trailers resolve to `@claude`, `@cursoragent` and `@codex`
|
||||||
type `User`; the credit policy excludes them while retaining their metadata.
|
with GitHub type `User`; the credit policy excludes them while retaining their metadata.
|
||||||
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
|
At the REA maintainer's request, omit `@morluto` self-thanks while preserving
|
||||||
authorship in the inventory. Other human contributors remain eligible.
|
authorship in the inventory. Other human contributors remain eligible.
|
||||||
Use explicit GitHub links for cross-repository references to avoid ambiguous
|
Use explicit GitHub links for cross-repository references to avoid ambiguous
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
name: rea-ci-tests-audit
|
||||||
|
description: Audit REA CI and test suites for correctness, fixture drift, false passes, and measured performance; use for suite or workflow investigations and efficiency changes, rather than ordinary feature testing or a single test failure.
|
||||||
|
---
|
||||||
|
|
||||||
|
# REA CI and tests audit
|
||||||
|
|
||||||
|
Establish which checks still catch real regressions and which costs can be
|
||||||
|
removed without losing that protection. The outcome may be a verified fix,
|
||||||
|
an evidence-backed recommendation, or an explicit decision to stop.
|
||||||
|
|
||||||
|
Follow the checkout's `AGENTS.md`, [testing guide](../../../docs/testing.md),
|
||||||
|
and [contribution rules](../../../CONTRIBUTING.md). Use their current commands
|
||||||
|
and pinned toolchain, rather than commands remembered from an earlier audit.
|
||||||
|
Preserve the requested scope: investigation alone does not authorize edits;
|
||||||
|
this skill does not grant permission to publish issues, push, or merge. Existing
|
||||||
|
user authorization still applies. Keep audit notes and raw results outside the
|
||||||
|
tracked tree, normally in a task-specific `/tmp` directory.
|
||||||
|
|
||||||
|
## Establish the executed baseline
|
||||||
|
|
||||||
|
Fetch when the user asks for latest main. Pin the baseline SHA and inspected
|
||||||
|
workflow revision; use an isolated worktree when changing or comparing code.
|
||||||
|
Do not modify another task's checkout. Record dirty changes and the exact
|
||||||
|
command, toolchain, host, selected tests, instrumentation, and prerequisites.
|
||||||
|
An old run is evidence for its executed revision, even if main advances.
|
||||||
|
|
||||||
|
Trace workflow triggers, changed-path selection, job dependencies, reusable
|
||||||
|
actions, package scripts, test discovery, artifact producers, and report gates
|
||||||
|
through to the commands that actually execute. Include scripts, generated data,
|
||||||
|
runtime registration, and subprocess boundaries missing from import graphs.
|
||||||
|
Check positive and negative selection paths: required files must be discovered
|
||||||
|
once per intended test configuration; deliberate platform/configuration repeats
|
||||||
|
are separate coverage. A missing explicit test or missing required report must
|
||||||
|
not turn green. Optional lanes may skip; preserve their stated prerequisites.
|
||||||
|
|
||||||
|
Use existing timing and failure artifacts to select suspicious families. Run
|
||||||
|
relevant checks to establish a baseline; reserve a full local suite for broad
|
||||||
|
CI diagnosis or uncertainty that focused checks cannot resolve. Do not rebuild
|
||||||
|
or rerun an unchanged complete suite after every documentation edit.
|
||||||
|
|
||||||
|
## Prove the failure or protection
|
||||||
|
|
||||||
|
For each candidate, state the expected observable behavior and its owner,
|
||||||
|
then construct the smallest reproduction against production behavior. Follow
|
||||||
|
the producer and affected consumers before deciding where to fix it.
|
||||||
|
|
||||||
|
- For drift, compare fixtures with current admission requirements, protocol
|
||||||
|
replies, named contracts, tool versions, and actual public entrypoints. A
|
||||||
|
plausible-looking mock can fail before the behavior its test claims to cover.
|
||||||
|
Search the whole affected fixture family, including other version strings.
|
||||||
|
Preserve intentionally malformed fixtures and test their intended rejection.
|
||||||
|
- For a suspicious green test, perturb the relevant behavior in an isolated
|
||||||
|
copy or inject a controlled failure. Verify that the existing assertion or
|
||||||
|
gate detects it. A test may merely repeat its mock, import abandoned code,
|
||||||
|
or hide a subprocess/checker failure. Restore mutations after the experiment.
|
||||||
|
- For shared setup or pruning, map distinct assertions to their retained
|
||||||
|
scenarios. Preserve malformed inputs, cancellation, cleanup failure,
|
||||||
|
permissions, capacity, identity, and meaningful concurrency interactions.
|
||||||
|
A faster run that skips a case or weakens its oracle is not an improvement.
|
||||||
|
- For provider coverage, separate fake-producer, boundary, and actual-engine
|
||||||
|
evidence. Inspect named skips and prerequisites; fixtures do not establish
|
||||||
|
real Ghidra, LLDB, browser, or other provider behavior.
|
||||||
|
|
||||||
|
Classify findings as reproduced defects, supported costs, hypotheses, or
|
||||||
|
unavailable evidence. Record the baseline command, observed output/exit status,
|
||||||
|
expected result, affected scope, and competing explanation. A failure on main
|
||||||
|
may be pre-existing; that does not by itself make it irrelevant to a CI audit.
|
||||||
|
Attribute PR failures using the executed revision and a clean baseline, not
|
||||||
|
just their resemblance to a previous failure. Retry only to test a specific
|
||||||
|
transient explanation; retain the original failure and stop when repetition
|
||||||
|
adds no evidence. A passing retry does not establish a root cause or a fix.
|
||||||
|
|
||||||
|
## Assess efficiency without losing the gate
|
||||||
|
|
||||||
|
Read [measurement and CI checks](references/measurement-and-ci.md) when
|
||||||
|
investigating slowness, caching, runner waits, shared artifacts, or log noise.
|
||||||
|
Compare equivalent completed work before claiming a speedup. Distinguish
|
||||||
|
setup/build time, test execution, coverage instrumentation, dependency waits,
|
||||||
|
and runner scheduling; state which costs remain unmeasured.
|
||||||
|
|
||||||
|
Prefer removing proven duplicate preparation, reusing immutable setup, and
|
||||||
|
reducing repeated subprocess work before shrinking correctness coverage.
|
||||||
|
Preserve independent sessions where lifecycle or concurrency is the oracle.
|
||||||
|
Keep live tests fresh; a cached previous pass cannot validate current host,
|
||||||
|
installed provider contents, permissions, or external state.
|
||||||
|
|
||||||
|
## Fix, verify, and hand off within scope
|
||||||
|
|
||||||
|
If implementation is authorized, make the smallest structural change at the
|
||||||
|
owning boundary and add a regression that fails on the baseline for the right
|
||||||
|
reason. Retest affected consumers and meaningful negative paths; verify cache
|
||||||
|
invalidation or discovery when those mechanisms change. Read the installed
|
||||||
|
Turbo documentation before changing its configuration or commands.
|
||||||
|
|
||||||
|
Run the repository's applicable checks and inspect complete current-head CI
|
||||||
|
results before an authorized merge. Preserve required coverage and discovery
|
||||||
|
gates. Do not suppress a failure, add blanket retries, raise timeouts, or delete
|
||||||
|
distinct assertions solely to make CI green. Changes to these controls need
|
||||||
|
their own demonstrated reason and retained failure detection.
|
||||||
|
|
||||||
|
Report each confirmed issue with exact revision, reproduction, consequence,
|
||||||
|
proposed correction, and validation limits. Report performance with workload
|
||||||
|
and measurements, not just a percentage. Use English issue/PR records when
|
||||||
|
requested; follow the user's assignment, commit identity, review, and merge
|
||||||
|
preferences without assuming them for other users. Open ready-for-review PRs
|
||||||
|
only when that lifecycle is authorized. Resolve necessary review findings and
|
||||||
|
check final main against the tested tree after merging.
|
||||||
|
|
||||||
|
Stop when the authorized work is verified, remaining benefits are unproven,
|
||||||
|
or further changes would trade correctness for speculative speed. List genuine
|
||||||
|
uncertainties without turning them into additional optimization rounds.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "REA CI and tests audit"
|
||||||
|
short_description: "Audit CI/test correctness and measured performance"
|
||||||
|
default_prompt: "Use $rea-ci-tests-audit to investigate CI and test correctness, drift, and performance before proposing changes."
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Measurement and CI checks
|
||||||
|
|
||||||
|
## Equivalent completed work
|
||||||
|
|
||||||
|
Record the source/workflow SHA, command, test identities and counts, skips,
|
||||||
|
assertions or output contract, exit status, coverage scope and thresholds,
|
||||||
|
toolchain/provider versions, host resources, and prepared artifacts. Inspect
|
||||||
|
both the child result and its report: a profiling wrapper may exit successfully
|
||||||
|
after the measured command failed. Focused tests can pass while global coverage
|
||||||
|
thresholds fail; describe both rather than calling the command green.
|
||||||
|
|
||||||
|
Measure wall time, user/system CPU, and peak process-family memory for relevant
|
||||||
|
completed tasks. Parent-only RSS misses workers and their children. On Linux,
|
||||||
|
summed RSS can double-count shared pages; report RSS/PSS semantics, sampling
|
||||||
|
interval, missing samples, and observed cleanup limits. Use equivalent host
|
||||||
|
tools elsewhere. State unknown costs, including agent tokens; fewer log bytes
|
||||||
|
alone prove neither fewer tokens nor faster tests.
|
||||||
|
|
||||||
|
Separate cold execution, warm build/file caches, and warm runtime caches.
|
||||||
|
Use repeated or alternating runs when variance affects the decision, with
|
||||||
|
enough observations to distinguish the proposed benefit from noise. Keep
|
||||||
|
workload, instrumentation, and host conditions comparable; record interference.
|
||||||
|
A run with fewer tests, early rejection, lost coverage, or a different runner
|
||||||
|
does not prove suite acceleration. CI-wide claims need end-to-end measurements,
|
||||||
|
not an extrapolation from one improved family.
|
||||||
|
|
||||||
|
## Workflow, caches, and shared artifacts
|
||||||
|
|
||||||
|
Trace each reused result from producer to consumers and identify its lifetime.
|
||||||
|
Check key/input coverage against the actual file inventory and result-affecting
|
||||||
|
configuration, environment, generated data, tool versions, and host state.
|
||||||
|
Exercise an affected input change to prove invalidation; exercise an irrelevant
|
||||||
|
change to support reuse where useful. Cache deterministic preparation and
|
||||||
|
static analysis only when their inputs fully describe the result.
|
||||||
|
|
||||||
|
Verify downloaded artifacts match the expected revision, platform, runtime,
|
||||||
|
and schema. Check restore misses, failed/partial producers, stale reports,
|
||||||
|
removed output files, and consumers after producer failures. Shared writers
|
||||||
|
need output ownership or locks; tests need isolated state, not an unnecessary
|
||||||
|
global lock. Worker counts do not bound children spawned within each test.
|
||||||
|
Preserve intentionally concurrent scenarios while bounding unrelated batches.
|
||||||
|
|
||||||
|
Check matrix expansion and all job pages before saying CI passed. Inspect
|
||||||
|
required gates after failed, cancelled, and skipped dependencies. `always()`
|
||||||
|
alone does not preserve failures; inspect the aggregation logic. Test/report
|
||||||
|
uploads should retain available diagnostics after failure, while absent or
|
||||||
|
malformed required results must fail closed. Do not demand an optional report
|
||||||
|
from a lane intentionally excluded by the selector.
|
||||||
|
|
||||||
|
## Runner waits and console noise
|
||||||
|
|
||||||
|
Use per-job and per-step timestamps where available. Workflow creation to job
|
||||||
|
start includes dependencies and other delays; it is not necessarily runner
|
||||||
|
queue time. Identify when the job became eligible before attributing waits to
|
||||||
|
runner capacity. If eligibility/queue timestamps are unavailable, report the
|
||||||
|
combined delay and unknown split. Compare equivalent lanes and runner types
|
||||||
|
across enough runs; a single busy runner is not a workflow regression.
|
||||||
|
|
||||||
|
For console reductions, retain checker exit codes, actionable errors, useful
|
||||||
|
summary counts, and complete machine-readable artifacts. Verify authored code
|
||||||
|
remains in the actual checker inventory when excluding generated outputs.
|
||||||
|
Introduce an authored error as a control; it must still fail. An ignore pattern
|
||||||
|
can hide real source as well as noise. Show that cached reports are restored,
|
||||||
|
stale successful reports cannot mask current failures, and report parse errors
|
||||||
|
remain failures. Measure readability or byte reduction separately from runtime.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
name: reverse-engineer-anything
|
name: reverse-engineer-anything
|
||||||
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
|
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
|
||||||
metadata:
|
metadata:
|
||||||
version: "41"
|
version: "42"
|
||||||
---
|
---
|
||||||
|
|
||||||
# REA
|
# REA
|
||||||
@@ -31,12 +31,55 @@ Diagnose the observed blocker, continue useful supported work, and resume the
|
|||||||
original task after repair. Client approval, shell PATH, registration and engine
|
original task after repair. Client approval, shell PATH, registration and engine
|
||||||
availability have different remedies; avoid repeating setup for all of them.
|
availability have different remedies; avoid repeating setup for all of them.
|
||||||
|
|
||||||
|
1. Diagnose without changing REA or client configuration. `npx` may download
|
||||||
|
the package and write to npm's cache. For Codex, run
|
||||||
|
`npx -y rea-agents@latest doctor --client codex --json`. Substitute the current
|
||||||
|
supported client: `claude_code`, `claude_desktop`, `codex`, `cursor`,
|
||||||
|
`gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`, `copilot_cli`,
|
||||||
|
`commandcode`, `vscode`, `grok_build`, `omp`, or `grok_bot`. `grok_bot` has no local
|
||||||
|
file registration: doctor reports the Grok Bot chat step, and setup does not
|
||||||
|
write `mcp.json`. If the client is unknown, use `doctor --json` and
|
||||||
|
inspect its registration results before choosing a setup scope.
|
||||||
|
2. Distinguish the reason. Missing, malformed, or stale registration needs a
|
||||||
|
scoped configuration repair. An aligned registration with no tools in the
|
||||||
|
active session needs a restart/reconnection; doctor cannot prove that the
|
||||||
|
current agent has connected. A missing provider affects only tasks requiring
|
||||||
|
that provider: static JavaScript inspection needs neither Hopper nor Ghidra,
|
||||||
|
and Android inspection has separate bring-your-own JADX/Java prerequisites.
|
||||||
|
3. For a configuration repair, prepare the read-only plan:
|
||||||
|
`npx -y rea-agents@latest setup --client codex --dry-run --json`.
|
||||||
|
Use the current client's ID, show its exact proposed paths, backups, and
|
||||||
|
changes, and obtain approval before setup writes configuration or installs
|
||||||
|
Hopper. Setup normally installs the matching bundled skill too; include that
|
||||||
|
replacement in the reviewed plan. After approval, apply the same scope with
|
||||||
|
`npx -y rea-agents@latest setup --client codex --yes`. Add `--install-hopper`
|
||||||
|
only if that separate installation was needed and explicitly approved.
|
||||||
|
4. Restart/reconnect the affected agent. Verify that REA tools actually appear
|
||||||
|
in the session, then resume the original investigation. If they remain
|
||||||
|
absent, inspect the client's MCP launch error rather than repeating setup.
|
||||||
|
|
||||||
|
REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
|
||||||
|
JADX, Kuna, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
|
||||||
|
software to repair
|
||||||
|
MCP registration. For an unsupported client, use manual stdio registration
|
||||||
|
with a version-pinned `rea-agents` package or continue through the CLI.
|
||||||
|
|
||||||
|
A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
|
||||||
|
```
|
||||||
|
|
||||||
|
No MCP registration or native engine is required. Read the returned Evidence,
|
||||||
|
graph, limitations, and unknowns with the same care as an MCP result; the CLI
|
||||||
|
returns the Evidence record directly. This fallback does not establish that
|
||||||
|
MCP is configured. Native CLI tasks still require their selected engine.
|
||||||
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
|
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
|
||||||
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
|
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
|
||||||
Configuration repair starts with a read-only setup plan; show paths, backups and
|
Configuration repair starts with a read-only setup plan; show paths, backups and
|
||||||
skill replacement before applying authorized changes. Preserve existing approval
|
skill replacement before applying authorized changes. Preserve existing approval
|
||||||
and target-execution boundaries. REA setup never installs or upgrades unrelated
|
and target-execution boundaries. REA setup never installs or upgrades unrelated
|
||||||
software, including Node.js, npm, Homebrew, Java, Ghidra, IDA, JADX, Binwalk or Unblob.
|
software, including Node.js, npm, Homebrew, Java, Ghidra, IDA, JADX, Kuna, Binwalk or Unblob.
|
||||||
|
|
||||||
## Route the target first
|
## Route the target first
|
||||||
|
|
||||||
@@ -61,6 +104,10 @@ explicit path or endpoint and do not need it.
|
|||||||
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
|
consult the NativeAOT workflow in `references/native-and-artifacts.md` for
|
||||||
optional metadata recovery and its supported host/layout boundary.
|
optional metadata recovery and its supported host/layout boundary.
|
||||||
- User-owned browser page already open: `list_browser_targets`.
|
- User-owned browser page already open: `list_browser_targets`.
|
||||||
|
- Browser action the agent must perform (click, type, navigate):
|
||||||
|
`capture_browser_scenario` with declared actions. See
|
||||||
|
[runtime observation](references/runtime-observation.md) for connect/launch
|
||||||
|
and the focused follow-up browser tools.
|
||||||
- Fiddler Everywhere traffic or a native `.saz` capture: read the
|
- Fiddler Everywhere traffic or a native `.saz` capture: read the
|
||||||
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
|
[Fiddler workflow](references/runtime-observation.md#fiddler-everywhere-traffic-debugging).
|
||||||
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
|
Inspect saved SAZ with `inspect_web_network_capture` and `format: "saz"`;
|
||||||
@@ -75,7 +122,7 @@ explicit path or endpoint and do not need it.
|
|||||||
pages; select one trace, stream ID and direction for contribution/range pages.
|
pages; select one trace, stream ID and direction for contribution/range pages.
|
||||||
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
|
Facts retain all-recorded-events-before-selection scope. Other qlog layouts
|
||||||
and PCAP correlation are unsupported.
|
and PCAP correlation are unsupported.
|
||||||
- User-owned Electron runtime already open: `list_electron_targets`.
|
- User-owned Electron runtime already open: `list_electron_targets`. Before an electron-main Inspector attempt, check packaged fuses with `inspect_electron_fuses`; disabled `EnableNodeCliInspectArguments` means the app ignores `--inspect`.
|
||||||
- Packet-field discovery: `inspect_packet_fields` searches installed protocols
|
- Packet-field discovery: `inspect_packet_fields` searches installed protocols
|
||||||
and field names/types before choosing fields for `inspect_packet_capture`.
|
and field names/types before choosing fields for `inspect_packet_capture`.
|
||||||
Query/page the relevant records; registrations do not establish observed traffic.
|
Query/page the relevant records; registrations do not establish observed traffic.
|
||||||
@@ -122,6 +169,14 @@ explicit path or endpoint and do not need it.
|
|||||||
absent metadata does not prove a binary is not Go, and unparsed text records
|
absent metadata does not prove a binary is not Go, and unparsed text records
|
||||||
remain explicit. See the
|
remain explicit. See the
|
||||||
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/go-binaries.md).
|
[Go binary guide](https://github.com/morluto/rea/blob/main/docs/go-binaries.md).
|
||||||
|
- Embedded cargo-auditable Rust dependency metadata: `inspect_rust_binary`
|
||||||
|
with the explicit local ELF, PE, or thin Mach-O file. This built-in
|
||||||
|
target-free inspection requires no Rust installation or disassembler. Preserve
|
||||||
|
package order, dependency indices, and the section file range. A missing
|
||||||
|
`.dep-v0` section is null metadata, not proof the binary has no dependencies.
|
||||||
|
A rustc commit hash is an inference from a read-only `/rustc/<40-hex>/` path.
|
||||||
|
See the
|
||||||
|
[Rust binary guide](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
|
||||||
|
|
||||||
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
|
- Explicit local SQLite database snapshot: `inspect_sqlite_database`. Inspect
|
||||||
with Node.js 24.x >=24.15 or 26+ providing native SQLite limits. Read schema
|
with Node.js 24.x >=24.15 or 26+ providing native SQLite limits. Read schema
|
||||||
@@ -160,6 +215,20 @@ after opening; IDA does not supply `binary_overview`. Consult session availabili
|
|||||||
when composing broader workflows. Modern direct callers and unsupported dossier
|
when composing broader workflows. Modern direct callers and unsupported dossier
|
||||||
facets remain unknown; live IDA results are not replayed from snapshots.
|
facets remain unknown; live IDA results are not replayed from snapshots.
|
||||||
|
|
||||||
|
With `REA_KUNA_COMMAND` configured, `provider_id: "kuna"` answers procedure
|
||||||
|
lists, lookups, pseudocode, assembly and `analyze_function` from Kuna's cached
|
||||||
|
whole-program export. Callers/callees use a separate body-free graph shared by
|
||||||
|
the session; `xrefs` returns exact-address incoming sites and filters forwarding
|
||||||
|
aliases. These queries and inventory lookups do not trigger a C export. Indirect
|
||||||
|
targets remain unknown and can make relationship coverage partial. Reference
|
||||||
|
caches expire on close; a fresh CLI process repeats the native reference walk.
|
||||||
|
Typed references, CFG, raw-memory tools and annotations remain unsupported. A cold first
|
||||||
|
answer can come from a single-function run (its limitations say so); assembly
|
||||||
|
waits for the export. For pseudocode and assembly, `raw_result.source` names the
|
||||||
|
cached `.c`/`.asm` file and byte span, which you may search directly instead of
|
||||||
|
calling per function. See the
|
||||||
|
[Kuna provider guide](https://github.com/morluto/rea/blob/main/docs/kuna-provider.md).
|
||||||
|
|
||||||
If the user says only "my app" without a name, path, or target already selected
|
If the user says only "my app" without a name, path, or target already selected
|
||||||
for this task, ask which app before searching for artifacts. Paths in unrelated
|
for this task, ask which app before searching for artifacts. Paths in unrelated
|
||||||
saved reports and nearby test fixtures do not select the user's target. Resolve
|
saved reports and nearby test fixtures do not select the user's target. Resolve
|
||||||
@@ -272,7 +341,8 @@ as complete while required questions remain open.
|
|||||||
[references/javascript-applications.md](references/javascript-applications.md)
|
[references/javascript-applications.md](references/javascript-applications.md)
|
||||||
- Android APK declarations, classes, methods, and static references:
|
- Android APK declarations, classes, methods, and static references:
|
||||||
[references/android-applications.md](references/android-applications.md)
|
[references/android-applications.md](references/android-applications.md)
|
||||||
- Passive browser/Electron observation and static/runtime reconciliation:
|
- Browser/Electron observation (passive and scenario) and static/runtime
|
||||||
|
reconciliation:
|
||||||
[references/runtime-observation.md](references/runtime-observation.md)
|
[references/runtime-observation.md](references/runtime-observation.md)
|
||||||
- Evidence paging, comparisons, residual unknowns, and verification:
|
- Evidence paging, comparisons, residual unknowns, and verification:
|
||||||
[references/evidence-workflows.md](references/evidence-workflows.md)
|
[references/evidence-workflows.md](references/evidence-workflows.md)
|
||||||
|
|||||||
@@ -65,3 +65,7 @@ Use `inspect_analysis_view` with `view: {kind: "api-surface"}` on retained
|
|||||||
method, origin and path template. Format matches are inferences, not handler
|
method, origin and path template. Format matches are inferences, not handler
|
||||||
identity or schemas. Group pages cite parent record ordinals; consult record
|
identity or schemas. Group pages cite parent record ordinals; consult record
|
||||||
coverage and exclusions before claiming completeness. No requests are replayed.
|
coverage and exclusions before claiming completeness. No requests are replayed.
|
||||||
|
When the connected schema advertises `include_query_parameter_names`, select
|
||||||
|
it in the view for distinct observed names with original record ordinals. Query
|
||||||
|
values are omitted; the names establish neither required parameters nor a
|
||||||
|
complete server schema. Keep the original parent Evidence for exact URLs.
|
||||||
@@ -94,6 +94,36 @@ recorded observations, identity, hashes, exports and source ranges; it does not
|
|||||||
include every child syntax observation, related edge or original source file.
|
include every child syntax observation, related edge or original source file.
|
||||||
An absent item fact is not evidence of absence from the whole application.
|
An absent item fact is not evidence of absence from the whole application.
|
||||||
|
|
||||||
|
For canonical identities and retained source-range counts, use a typed reference
|
||||||
|
page instead of reading every full item:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"kind": "retained-evidence",
|
||||||
|
"evidence_id": "ev_ACTUAL_PARENT_ID"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"kind": "javascript",
|
||||||
|
"collection": "modules",
|
||||||
|
"projection": "references",
|
||||||
|
"where": { "path": "main.js", "kind": "javascript-module" },
|
||||||
|
"offset": 0,
|
||||||
|
"limit": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Filters match exact retained strings; omit a filter to include all values, use
|
||||||
|
`path: null` for unavailable paths, and `path: ""` for a recorded empty path.
|
||||||
|
Rows preserve canonical `identity` and distinct observation support states in
|
||||||
|
`basis`. `retained_source_range_count` counts retained source-range observations,
|
||||||
|
not runtime imports or unique reference sites. Use a row's `node_id` with the item
|
||||||
|
view above for exact locations and full supporting observations. Follow
|
||||||
|
`coverage.next_offset`; page exhaustion describes matching retained rows only.
|
||||||
|
Check `source_coverage` for source selection, partial/unknown graph coverage and
|
||||||
|
unknown counts before drawing a negative conclusion.
|
||||||
|
|
||||||
## Trace only the remaining question
|
## Trace only the remaining question
|
||||||
|
|
||||||
Use `trace_application_feature` on the parent application Evidence for one
|
Use `trace_application_feature` on the parent application Evidence for one
|
||||||
|
|||||||
@@ -11,6 +11,18 @@ unparsed module-text line. A missing record is an unknown, not proof of a
|
|||||||
non-Go binary; observed metadata does not authenticate its claims. Function,
|
non-Go binary; observed metadata does not authenticate its claims. Function,
|
||||||
source and type recovery remain a separate native-analysis question.
|
source and type recovery remain a separate native-analysis question.
|
||||||
|
|
||||||
|
## Rust dependency metadata
|
||||||
|
|
||||||
|
Use `inspect_rust_binary` for an explicit local ELF, PE, or thin Mach-O when
|
||||||
|
the question concerns the cargo-auditable crate list embedded in `.dep-v0`
|
||||||
|
(Mach-O: `__DATA,.dep-v0`). No `open_binary`, Rust installation, or target
|
||||||
|
execution is required. The CLI equivalent is `inspect-rust-binary PATH`.
|
||||||
|
Preserve artifact SHA-256, package order, dependency indices, and the section
|
||||||
|
file range. A missing section is null metadata, not proof the binary has no
|
||||||
|
dependencies. An omitted package kind stays null. A rustc commit hash is
|
||||||
|
inferred from a read-only `/rustc/<40-hex>/` path and does not by itself
|
||||||
|
identify the compiler. See [Rust binaries](https://github.com/morluto/rea/blob/main/docs/rust-binaries.md).
|
||||||
|
|
||||||
## SQLite snapshots
|
## SQLite snapshots
|
||||||
|
|
||||||
Use `inspect_sqlite_database` with an explicit local database snapshot for its
|
Use `inspect_sqlite_database` with an explicit local database snapshot for its
|
||||||
@@ -157,6 +169,14 @@ repeat parsing. Returned-row exhaustion
|
|||||||
does not prove complete metadata or absence under partial/unavailable coverage;
|
does not prove complete metadata or absence under partial/unavailable coverage;
|
||||||
preserve build-local tokens and their artifact/MVID scope.
|
preserve build-local tokens and their artifact/MVID scope.
|
||||||
|
|
||||||
|
`inspect_managed_members` always returns the assembly-wide `member_refs`, even
|
||||||
|
for one selected type. For questions about a selected type, select
|
||||||
|
`detail: "summary"` with `type_names`; it returns the selection (with unmatched
|
||||||
|
names), coverage and per-collection counts. Page rows with
|
||||||
|
`view: {kind: "managed-members", collection: "call_edges", offset: 0, limit: 64}`
|
||||||
|
over `types`, `exported_types`, `fields`, `methods`, `member_refs`, `call_edges`,
|
||||||
|
`field_accesses` or `user_strings`.
|
||||||
|
|
||||||
### .NET NativeAOT
|
### .NET NativeAOT
|
||||||
|
|
||||||
NativeAOT binaries contain native machine code, so ordinary CIL decompilers
|
NativeAOT binaries contain native machine code, so ordinary CIL decompilers
|
||||||
@@ -175,7 +195,9 @@ additional evidence only when the provider accepts and matches them.
|
|||||||
REA has a built-in read-only snapshot parser for x64 PE targets with .NET 8 RTR
|
REA has a built-in read-only snapshot parser for x64 PE targets with .NET 8 RTR
|
||||||
9.1 metadata. An optional headless Ghidra adapter adds database annotations and
|
9.1 metadata. An optional headless Ghidra adapter adds database annotations and
|
||||||
broader metadata recovery on Linux; it requires an explicitly configured
|
broader metadata recovery on Linux; it requires an explicitly configured
|
||||||
NativeAOT JAR. These are separate routes. See the
|
NativeAOT JAR. These are separate routes. The built-in parser also runs on
|
||||||
|
Windows Ghidra P0; read frozen literals from its report, because
|
||||||
|
`search_strings` does not include them. See the
|
||||||
[NativeAOT guide](https://github.com/morluto/rea/blob/main/docs/ghidra-nativeaot.md)
|
[NativeAOT guide](https://github.com/morluto/rea/blob/main/docs/ghidra-nativeaot.md)
|
||||||
for setup, supported layouts, and host restrictions. Preserve binary identity
|
for setup, supported layouts, and host restrictions. Preserve binary identity
|
||||||
and distinguish derived metadata from Ghidra database observations. Recovered
|
and distinguish derived metadata from Ghidra database observations. Recovered
|
||||||
@@ -247,3 +269,9 @@ resolution is explicit; missing references and size mismatch remain visible.
|
|||||||
Other payloads are opaque, and this operation does not decode images or simulate
|
Other payloads are opaque, and this operation does not decode images or simulate
|
||||||
Windows resource loading. Consult the budgets and supported tree profile in
|
Windows resource loading. Consult the budgets and supported tree profile in
|
||||||
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).
|
[offline binary diagnostics](https://github.com/morluto/rea/blob/main/docs/binary-diagnostics.md).
|
||||||
|
|
||||||
|
For exact core-module import/export differences, use `compare_wasm_interfaces`
|
||||||
|
with two complete inspection Evidence records (or current-session retained
|
||||||
|
references) and explicit scope. Preserve unknown types/duplicate names; static
|
||||||
|
interface equality is not runtime compatibility. CLI accepts complete inline
|
||||||
|
Evidence in `rea compare-wasm-interfaces input.json --json`.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Passive browser and Electron observation
|
# Browser and Electron runtime observation
|
||||||
|
|
||||||
Supply the existing browser or Electron instance's literal-port loopback CDP
|
Supply the existing browser or Electron instance's literal-port loopback CDP
|
||||||
endpoint in the request. No separate permission configuration or per-call
|
endpoint in the request. No separate permission configuration or per-call
|
||||||
@@ -7,13 +7,16 @@ approval flag is needed. Browser discovery lists eligible HTTP(S) pages;
|
|||||||
selected page's current origin. Electron discovery lists eligible local
|
selected page's current origin. Electron discovery lists eligible local
|
||||||
`file://` targets without a configured file-root allowlist.
|
`file://` targets without a configured file-root allowlist.
|
||||||
|
|
||||||
Start browser work with `list_browser_targets`; start Electron runtime work with
|
Start passive browser work with `list_browser_targets`; start Electron runtime work with
|
||||||
`list_electron_targets`. Inspect the target selected for the user's task. Observation
|
`list_electron_targets`. Before an electron-main Inspector attempt, check
|
||||||
is passive: never claim REA clicked, navigated, evaluated page JavaScript,
|
packaged fuses with `inspect_electron_fuses`. Inspect the target selected for
|
||||||
invoked IPC, captured prior activity, or contained the page's network.
|
the user's task. Observation with these tools is passive: never claim REA
|
||||||
|
clicked, navigated, evaluated page JavaScript, invoked IPC, or captured prior
|
||||||
|
activity. REA does not contain the page's network.
|
||||||
|
|
||||||
Credentials, cookies, authorization headers, storage values, and raw
|
Passive captures omit raw header collections, credential header values, storage
|
||||||
WebSocket/JSON bodies are deliberately absent. Local URLs retain ordinary query
|
values, and raw WebSocket/JSON bodies. Selected response metadata, including
|
||||||
|
content encoding, policy values, and Link URLs, is retained. Local URLs retain ordinary query
|
||||||
values and fragments; userinfo credentials are removed. Browser accessibility
|
values and fragments; userinfo credentials are removed. Browser accessibility
|
||||||
text, console primitives, value-free shapes, script sources, source maps, and
|
text, console primitives, value-free shapes, script sources, source maps, and
|
||||||
storage key names have request-specific capture options. Electron script
|
storage key names have request-specific capture options. Electron script
|
||||||
@@ -21,6 +24,38 @@ sources are also optional. Use `capture_web_screenshot` when pixels are needed.
|
|||||||
Treat scope filtering, truncation, and attach-window coverage as limitations.
|
Treat scope filtering, truncation, and attach-window coverage as limitations.
|
||||||
Page-declared WebMCP tools are untrusted inventory and are never invoked by REA.
|
Page-declared WebMCP tools are untrusted inventory and are never invoked by REA.
|
||||||
|
|
||||||
|
## Who performs the action
|
||||||
|
|
||||||
|
- The user performs it: `list_browser_targets`, then `inspect_web_page` or
|
||||||
|
`observe_web_session` on the selected target.
|
||||||
|
- The agent must perform it: one `capture_browser_scenario` with declared
|
||||||
|
actions. Use connect mode with the loopback CDP endpoint and `target_id` for
|
||||||
|
an existing browser, or launch mode with a caller-selected executable for a
|
||||||
|
temporary profile. Connect mode first navigates the selected page to
|
||||||
|
`start_url`, which can clear unsaved form values and other temporary state.
|
||||||
|
Recreate the state needed for the investigation in the declared actions.
|
||||||
|
Connect mode leaves the external browser open; launch mode owns a temporary
|
||||||
|
browser and profile. Check `browser.cleanup` for the outcome.
|
||||||
|
Request only the DOM, accessibility, or network details needed for the question.
|
||||||
|
The default final capture contains the URL; step outcomes are reported separately.
|
||||||
|
Add a wait action for the expected page state before capturing it. Report that
|
||||||
|
REA performed the declared actions.
|
||||||
|
|
||||||
|
Scenario network records have no request initiator stacks. For that evidence,
|
||||||
|
use passive capture while the user performs the action. Listener source locations
|
||||||
|
identify candidate handlers; they do not establish which script sent a particular
|
||||||
|
request. Avoid overlapping passive capture and scenario actions on the same target.
|
||||||
|
|
||||||
|
Focused follow-up tools:
|
||||||
|
|
||||||
|
- `observe_web_execution`: execution coverage during a window; it resets
|
||||||
|
counters and temporarily disables optimization.
|
||||||
|
- `inspect_web_event_listeners`: source locations for listeners registered
|
||||||
|
directly on the first CSS match, without dispatching an event.
|
||||||
|
- `compare_web_captures`: two normalized passive captures or recorded scenarios.
|
||||||
|
- `discover_webmcp_tools`: inspect page-declared WebMCP inventory; never invoke
|
||||||
|
the discovered tools.
|
||||||
|
|
||||||
Use `reconcile_javascript_runtime` only after static application Evidence and
|
Use `reconcile_javascript_runtime` only after static application Evidence and
|
||||||
passive browser/Electron Evidence exist. Prefer captured-source digest identity;
|
passive browser/Electron Evidence exist. Prefer captured-source digest identity;
|
||||||
report path/digest disagreement as a mismatch and preserve ambiguity. Explicit
|
report path/digest disagreement as a mismatch and preserve ambiguity. Explicit
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: Report a reproducible problem in REA
|
description: Report a problem in REA with observations or source evidence
|
||||||
title: "[Bug] "
|
title: "[Bug] "
|
||||||
labels:
|
labels:
|
||||||
- bug
|
- bug
|
||||||
@@ -8,8 +8,12 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please report one primary behavior per issue. Keep observed behavior
|
Report one primary behavior per issue. Keep answers concise and grounded
|
||||||
separate from suspected causes, and remove credentials and private paths.
|
in available evidence. Distinguish observed behavior, source-derived
|
||||||
|
reasoning, and suspected causes; do not claim a reproduction you did not run.
|
||||||
|
If a narrative answer is not known, write `NOT_ENOUGH_INFO` and describe
|
||||||
|
what is missing. Intermittent failures and clearly labeled source-only
|
||||||
|
reports are welcome. Do not include credentials or sensitive target data.
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: before-submitting
|
id: before-submitting
|
||||||
@@ -42,7 +46,7 @@ body:
|
|||||||
id: observed-behavior
|
id: observed-behavior
|
||||||
attributes:
|
attributes:
|
||||||
label: Observed behavior
|
label: Observed behavior
|
||||||
description: What happened? Include the command, MCP tool, provider, target type, and conditions that matter.
|
description: Describe the actual result, including the exact command or MCP request, relevant provider/target, and supporting evidence below. Label source-only findings instead of claiming execution.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ body:
|
|||||||
id: reproduction
|
id: reproduction
|
||||||
attributes:
|
attributes:
|
||||||
label: Reproduction
|
label: Reproduction
|
||||||
description: Give the smallest realistic command, configuration, input, or event sequence that demonstrates the problem.
|
description: Give the shortest steps you actually ran, with the relevant input or configuration. State how often they reproduced the problem when known. If reproduction is unavailable, say so and provide the observations or source reference you have.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. Configure or start ...
|
1. Configure or start ...
|
||||||
2. Run ...
|
2. Run ...
|
||||||
@@ -62,7 +66,7 @@ body:
|
|||||||
id: expected-behavior
|
id: expected-behavior
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected behavior
|
label: Expected behavior
|
||||||
description: What result, contract, invariant, recovery action, or state transition should have occurred?
|
description: State the expected result, contract, or recovery action. Cite documentation, source, or a known-good version when available; mark an unverified expectation or unknown reference clearly.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@ body:
|
|||||||
id: impact-scope
|
id: impact-scope
|
||||||
attributes:
|
attributes:
|
||||||
label: Impact and scope
|
label: Impact and scope
|
||||||
description: What is affected, what is not affected, and does this affect evidence integrity, process isolation, compatibility, or recovery?
|
description: Describe observed impact and frequency, including effects on evidence integrity, process isolation, compatibility, or recovery when known. Leave untested scope and severity unknown rather than guessing.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: REA version
|
label: REA version
|
||||||
description: Use `rea --version` or provide the source revision.
|
description: Use `rea --version` or provide the source revision.
|
||||||
placeholder: 3.1.0 or git revision
|
placeholder: Exact rea --version output or source commit SHA
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ body:
|
|||||||
id: failure-stage
|
id: failure-stage
|
||||||
attributes:
|
attributes:
|
||||||
label: Earliest failure stage
|
label: Earliest failure stage
|
||||||
description: Select where the incorrect behavior first becomes observable.
|
description: Select where the incorrect behavior first becomes observable. Choose Unknown if this has not been established.
|
||||||
options:
|
options:
|
||||||
- Request parsing or validation
|
- Request parsing or validation
|
||||||
- Setup or provider admission
|
- Setup or provider admission
|
||||||
@@ -133,8 +137,14 @@ body:
|
|||||||
id: relevant-evidence
|
id: relevant-evidence
|
||||||
attributes:
|
attributes:
|
||||||
label: Relevant output or evidence
|
label: Relevant output or evidence
|
||||||
description: Paste the smallest useful CLI output, MCP result, error, trace, or evidence excerpt. Redact sensitive values.
|
description: Include the smallest useful output, MCP result, error, screenshot, artifact, or exact source reference. Say how it was obtained and which claim it supports. If unavailable, write NOT_ENOUGH_INFO and explain what is missing. Redact credentials and sensitive target data.
|
||||||
render: text
|
placeholder: |
|
||||||
|
Command or MCP request actually run:
|
||||||
|
Relevant result, error, or artifact:
|
||||||
|
For source-only findings, link the file/revision and relevant code:
|
||||||
|
Missing evidence or remaining uncertainty:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: typed-outcome
|
id: typed-outcome
|
||||||
@@ -146,10 +156,10 @@ body:
|
|||||||
id: analysis
|
id: analysis
|
||||||
attributes:
|
attributes:
|
||||||
label: Analysis or suspected cause
|
label: Analysis or suspected cause
|
||||||
description: Optional. Clearly mark source-derived reasoning or a hypothesis; do not present an unverified cause as fact.
|
description: Optional. Link supporting source and distinguish source-derived reasoning from a hypothesis. A root-cause analysis or proposed fix is not required.
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add links, reduced fixtures, screenshots, or a proposed regression test.
|
description: Add reduced fixtures, screenshots, or related issues. For observed regressions, include the last known good and first known bad versions when available. Label untested checks as proposals.
|
||||||
@@ -41,7 +41,7 @@ body:
|
|||||||
id: context-evidence
|
id: context-evidence
|
||||||
attributes:
|
attributes:
|
||||||
label: Context and evidence
|
label: Context and evidence
|
||||||
description: Explain the problem or motivation. If you have evidence, distinguish observed behavior from ideas or guesses.
|
description: Explain the problem or motivation and link relevant evidence when available. Distinguish observed behavior, source-derived reasoning, proposed behavior, and unknowns. A question without measurements or a finished design is welcome.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ labels:
|
|||||||
- documentation
|
- documentation
|
||||||
- triage
|
- triage
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Report one documentation problem with the exact location and the smallest
|
||||||
|
useful supporting passage, example, or result. Keep answers concise.
|
||||||
|
If something is unknown, write `NOT_ENOUGH_INFO` and explain what is missing.
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: before-submitting
|
id: before-submitting
|
||||||
attributes:
|
attributes:
|
||||||
@@ -28,7 +35,7 @@ body:
|
|||||||
id: problem
|
id: problem
|
||||||
attributes:
|
attributes:
|
||||||
label: Problem
|
label: Problem
|
||||||
description: What is unclear, missing, outdated, or incorrect, and who is affected?
|
description: Quote or describe the current content, explain what is unclear, missing, outdated, or incorrect, and say how to observe the problem. State who is affected when known.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -36,7 +43,7 @@ body:
|
|||||||
id: expected-content
|
id: expected-content
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected content
|
label: Expected content
|
||||||
description: Describe the accurate behavior or example that should be documented.
|
description: Describe the accurate behavior or example that should be documented, with a reference when available. Label a proposed clarification or unknown behavior clearly.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@@ -44,7 +51,9 @@ body:
|
|||||||
id: evidence
|
id: evidence
|
||||||
attributes:
|
attributes:
|
||||||
label: Supporting evidence
|
label: Supporting evidence
|
||||||
description: Add source, command output, provider documentation, or a link that supports the correction.
|
description: Include an exact passage, source reference, command output, provider documentation, or screenshot supporting the report. For missing or unclear guidance, point to the relevant section and explain the gap. If evidence is unavailable, write NOT_ENOUGH_INFO and explain what is missing.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ body:
|
|||||||
Describe one problem and the outcome you would like. Short answers are
|
Describe one problem and the outcome you would like. Short answers are
|
||||||
welcome. Choose "Not sure yet" wherever needed; you do not need code, a
|
welcome. Choose "Not sure yet" wherever needed; you do not need code, a
|
||||||
finished design, or a complete test plan to propose an idea.
|
finished design, or a complete test plan to propose an idea.
|
||||||
|
Use concrete examples when available, distinguish current limitations
|
||||||
|
from proposed behavior, and leave unmeasured impact or unknowns explicit.
|
||||||
|
|
||||||
Read the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
|
Read the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope).
|
||||||
Maintainers can help resolve scope and technical questions. Unresolved
|
Maintainers can help resolve scope and technical questions. Unresolved
|
||||||
@@ -58,7 +60,7 @@ body:
|
|||||||
id: use-cases
|
id: use-cases
|
||||||
attributes:
|
attributes:
|
||||||
label: Use cases
|
label: Use cases
|
||||||
description: Optional additional examples of applications, targets, or callers. Leave blank if covered above.
|
description: Optional examples of affected workflows, targets, or callers and their practical impact. Include observed frequency or measurements when available; do not invent them. Leave blank if covered above.
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: desired-outcome
|
id: desired-outcome
|
||||||
@@ -138,4 +140,4 @@ body:
|
|||||||
id: references
|
id: references
|
||||||
attributes:
|
attributes:
|
||||||
label: References
|
label: References
|
||||||
description: Link relevant provider documentation, source, measurements, artifacts, issues, or discussions.
|
description: Optional supporting examples, provider documentation, source, measurements, artifacts, issues, or prior art. Distinguish observed results from expected benefits; an idea without evidence is welcome.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Cache npm package downloads
|
||||||
|
description: Restore npm's download cache and save it only from trusted default-branch events.
|
||||||
|
outputs:
|
||||||
|
can-save:
|
||||||
|
description: Whether this run may update the shared npm download cache.
|
||||||
|
value: ${{ steps.context.outputs.can-save }}
|
||||||
|
path:
|
||||||
|
description: The npm download-cache directory.
|
||||||
|
value: ${{ steps.context.outputs.path }}
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- id: context
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||||
|
LOCKFILES_HASH: ${{ hashFiles('package-lock.json', 'npm-shrinkwrap.json', '.npmrc') }}
|
||||||
|
run: |
|
||||||
|
cache_path="$(npm config get cache)"
|
||||||
|
runtime_arch="$(node -p 'process.arch')"
|
||||||
|
can_save=false
|
||||||
|
if [[ -n "${DEFAULT_BRANCH}" && "${GITHUB_REF}" == "refs/heads/${DEFAULT_BRANCH}" && ( "${GITHUB_EVENT_NAME}" == "push" || "${GITHUB_EVENT_NAME}" == "schedule" || "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ) ]]; then
|
||||||
|
can_save=true
|
||||||
|
fi
|
||||||
|
cache_key="npm-download-${RUNNER_OS}-${runtime_arch}-${LOCKFILES_HASH}"
|
||||||
|
{
|
||||||
|
printf 'can-save=%s\n' "${can_save}"
|
||||||
|
printf 'path=%s\n' "${cache_path}"
|
||||||
|
printf 'key=%s\n' "${cache_key}"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
- name: Restore and save npm package downloads
|
||||||
|
if: steps.context.outputs.can-save == 'true'
|
||||||
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: ${{ steps.context.outputs.path }}
|
||||||
|
key: ${{ steps.context.outputs.key }}
|
||||||
|
- name: Restore npm package downloads without saving
|
||||||
|
if: steps.context.outputs.can-save != 'true'
|
||||||
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: ${{ steps.context.outputs.path }}
|
||||||
|
key: ${{ steps.context.outputs.key }}
|
||||||
@@ -12,7 +12,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Download portable build outputs from this workflow run
|
- name: Download portable build outputs from this workflow run
|
||||||
if: inputs.build_cache_artifact != ''
|
if: inputs.build_cache_artifact != ''
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.build_cache_artifact }}
|
name: ${{ inputs.build_cache_artifact }}
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
@@ -39,14 +39,14 @@ runs:
|
|||||||
' >> "$GITHUB_OUTPUT"
|
' >> "$GITHUB_OUTPUT"
|
||||||
- name: Restore portable build outputs
|
- name: Restore portable build outputs
|
||||||
if: inputs.build_cache_artifact == '' && inputs.save != 'true'
|
if: inputs.build_cache_artifact == '' && inputs.save != 'true'
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
key: ${{ steps.plan.outputs.key }}
|
key: ${{ steps.plan.outputs.key }}
|
||||||
restore-keys: ${{ steps.plan.outputs.prefix }}
|
restore-keys: ${{ steps.plan.outputs.prefix }}
|
||||||
- name: Restore and save portable build outputs
|
- name: Restore and save portable build outputs
|
||||||
if: inputs.build_cache_artifact == '' && inputs.save == 'true'
|
if: inputs.build_cache_artifact == '' && inputs.save == 'true'
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
key: ${{ steps.plan.outputs.key }}
|
key: ${{ steps.plan.outputs.key }}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: Prepare qualified release inputs
|
||||||
|
description: Restore and validate the exact qualified tarball and its verifier runtime
|
||||||
|
outputs:
|
||||||
|
version:
|
||||||
|
description: Package version validated against the source and qualified manifest
|
||||||
|
value: ${{ steps.package.outputs.version }}
|
||||||
|
tarball:
|
||||||
|
description: Absolute path to the validated qualified npm tarball
|
||||||
|
value: ${{ steps.package.outputs.tarball }}
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Download the exact qualified package
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: verified-release-package
|
||||||
|
path: .cache/verified-package/
|
||||||
|
- name: Verify qualified package provenance and digest
|
||||||
|
id: package
|
||||||
|
shell: bash
|
||||||
|
run: node scripts/ci/verified-package.mjs validate .cache/verified-package
|
||||||
|
- name: Restore qualified verifier runtime
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: rea-test-runtime
|
||||||
|
path: .
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: Publish MCP Registry metadata
|
||||||
|
description: Validate and publish one released server.json, preserving an identical existing version
|
||||||
|
inputs:
|
||||||
|
directory:
|
||||||
|
description: Directory containing the released server.json
|
||||||
|
default: .
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Install MCP Registry publisher
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.directory }}
|
||||||
|
env:
|
||||||
|
MCP_PUBLISHER_VERSION: v1.8.0
|
||||||
|
MCP_PUBLISHER_SHA256_LINUX_AMD64: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
|
||||||
|
run: |
|
||||||
|
archive="mcp-publisher_linux_amd64.tar.gz"
|
||||||
|
curl -fsSLo "${archive}" \
|
||||||
|
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${archive}"
|
||||||
|
echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${archive}" | sha256sum --check --strict
|
||||||
|
tar xzf "${archive}" mcp-publisher
|
||||||
|
chmod +x mcp-publisher
|
||||||
|
- name: Validate MCP Registry metadata
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.directory }}
|
||||||
|
run: ./mcp-publisher validate
|
||||||
|
- name: Check existing registry version
|
||||||
|
id: registry
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.directory }}
|
||||||
|
run: node "${GITHUB_ACTION_PATH}/registry-state.mjs"
|
||||||
|
- name: Authenticate to MCP Registry
|
||||||
|
if: steps.registry.outputs.exists != 'true'
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.directory }}
|
||||||
|
run: ./mcp-publisher login github-oidc
|
||||||
|
- name: Publish MCP Registry metadata
|
||||||
|
if: steps.registry.outputs.exists != 'true'
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.directory }}
|
||||||
|
run: ./mcp-publisher publish
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export function registryPublicationState(
|
||||||
|
server: Readonly<{ name: string; version: string } & Record<string, unknown>>,
|
||||||
|
request?: (url: URL, init: RequestInit) => Promise<Response>,
|
||||||
|
): Promise<"present" | "absent">;
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { appendFile, readFile } from "node:fs/promises";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
/** Exact-version routes currently return false 404s; enumerate the filtered listing. */
|
||||||
|
export async function registryPublicationState(server, request = fetch) {
|
||||||
|
assert.equal(typeof server?.name, "string");
|
||||||
|
assert.equal(typeof server?.version, "string");
|
||||||
|
const cursors = new Set();
|
||||||
|
let cursor;
|
||||||
|
const matches = [];
|
||||||
|
do {
|
||||||
|
const url = new URL(
|
||||||
|
"https://registry.modelcontextprotocol.io/v0.1/servers",
|
||||||
|
);
|
||||||
|
url.search = new URLSearchParams({
|
||||||
|
search: server.name,
|
||||||
|
version: server.version,
|
||||||
|
include_deleted: "true",
|
||||||
|
limit: "100",
|
||||||
|
}).toString();
|
||||||
|
if (cursor !== undefined) url.searchParams.set("cursor", cursor);
|
||||||
|
const response = await request(url, {
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
throw new Error(`Registry lookup failed: HTTP ${response.status}`);
|
||||||
|
const page = await response.json();
|
||||||
|
assert(Array.isArray(page?.servers), "Registry response omitted servers");
|
||||||
|
assert(
|
||||||
|
page.metadata && typeof page.metadata === "object",
|
||||||
|
"Registry response omitted metadata",
|
||||||
|
);
|
||||||
|
for (const entry of page.servers) {
|
||||||
|
assert(
|
||||||
|
typeof entry?.server?.name === "string" &&
|
||||||
|
typeof entry.server.version === "string",
|
||||||
|
"Malformed registry server record",
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
entry.server.name === server.name &&
|
||||||
|
entry.server.version === server.version
|
||||||
|
)
|
||||||
|
matches.push(entry);
|
||||||
|
}
|
||||||
|
cursor = page.metadata.nextCursor;
|
||||||
|
if (cursor !== undefined) {
|
||||||
|
assert(
|
||||||
|
typeof cursor === "string" && cursor.length > 0 && !cursors.has(cursor),
|
||||||
|
"Registry returned a non-advancing cursor",
|
||||||
|
);
|
||||||
|
cursors.add(cursor);
|
||||||
|
}
|
||||||
|
} while (cursor !== undefined);
|
||||||
|
assert(
|
||||||
|
matches.length <= 1,
|
||||||
|
"Registry returned ambiguous exact-version records",
|
||||||
|
);
|
||||||
|
if (matches.length === 0) return "absent";
|
||||||
|
assert.equal(
|
||||||
|
matches[0]._meta?.["io.modelcontextprotocol.registry/official"]?.status,
|
||||||
|
"active",
|
||||||
|
"Existing registry version is not active",
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
matches[0].server,
|
||||||
|
server,
|
||||||
|
"Existing registry version differs from released metadata",
|
||||||
|
);
|
||||||
|
return "present";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
process.argv[1] !== undefined &&
|
||||||
|
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
||||||
|
) {
|
||||||
|
const state = await registryPublicationState(
|
||||||
|
JSON.parse(await readFile("server.json", "utf8")),
|
||||||
|
);
|
||||||
|
assert(process.env.GITHUB_OUTPUT, "GITHUB_OUTPUT is required");
|
||||||
|
await appendFile(
|
||||||
|
process.env.GITHUB_OUTPUT,
|
||||||
|
`exists=${state === "present"}\n`,
|
||||||
|
);
|
||||||
|
process.stdout.write(
|
||||||
|
`Registry version is ${state}; ${state === "present" ? "preserving identical published metadata" : "publication may proceed"}.\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ Unresolved scope, authority, side effects, or required verification block
|
|||||||
integration. Link the required maintainer scope decision for new providers or
|
integration. Link the required maintainer scope decision for new providers or
|
||||||
capabilities involving target execution, injection, mutation, or protection
|
capabilities involving target execution, injection, mutation, or protection
|
||||||
recovery; ordinary fixes within existing boundaries may state "not applicable".
|
recovery; ordinary fixes within existing boundaries may state "not applicable".
|
||||||
Draft PRs may leave the scope decision pending and the final review checklist
|
Draft PRs may leave the scope decision pending and the readiness and merge checklists
|
||||||
unchecked. Maintainers can help resolve open questions.
|
unchecked. Maintainers can help resolve open questions.
|
||||||
|
|
||||||
## Contract and boundary impact
|
## Contract and boundary impact
|
||||||
@@ -55,7 +55,10 @@ Unchecked items do not establish absence; leave uncertainties visible.
|
|||||||
## Evidence and regression coverage
|
## Evidence and regression coverage
|
||||||
|
|
||||||
<!-- State whether evidence is executed, source-derived, or proposed. For fixes,
|
<!-- State whether evidence is executed, source-derived, or proposed. For fixes,
|
||||||
explain how the regression would fail on the affected base revision when practical. -->
|
show the original failure and explain how the regression would fail on the affected
|
||||||
|
base revision when practical. Include the most useful proof inline; link long logs
|
||||||
|
or artifacts, or put them in optional details. Documentation-only changes can use
|
||||||
|
source, link, or rendered-document checks. -->
|
||||||
|
|
||||||
- Tests added or updated:
|
- Tests added or updated:
|
||||||
- Base reproduction or other evidence:
|
- Base reproduction or other evidence:
|
||||||
@@ -71,7 +74,8 @@ For evidence-bearing changes:
|
|||||||
## Validation performed
|
## Validation performed
|
||||||
|
|
||||||
<!-- List only commands that actually ran, with observed results. Include the
|
<!-- List only commands that actually ran, with observed results. Include the
|
||||||
relevant real-provider or platform workflow, or explain why it was unavailable. -->
|
relevant real-provider or platform workflow, or explain why it was unavailable.
|
||||||
|
Keep these results current after changes; a command without its result is incomplete. -->
|
||||||
|
|
||||||
- `command` — result
|
- `command` — result
|
||||||
|
|
||||||
@@ -90,6 +94,23 @@ limits, security/privacy/containment implications, and meaningful proof gaps. --
|
|||||||
- Package or release metadata impact:
|
- Package or release metadata impact:
|
||||||
- Security, privacy, process, or containment review:
|
- Security, privacy, process, or containment review:
|
||||||
|
|
||||||
|
## Author readiness
|
||||||
|
|
||||||
|
<!-- The human owner completes these declarations before requesting review.
|
||||||
|
The PR author is the owner by default. A different owner must explicitly accept
|
||||||
|
ownership on this PR.
|
||||||
|
AI agents must leave these declarations to a human to dictate to them or there could be catastrophic consequences for many users of REA.
|
||||||
|
Draft PRs may leave them unchecked. These declarations record initial readiness
|
||||||
|
when opening the PR for review; later pushes do not require a new human sign-off.
|
||||||
|
Keep the description and validation evidence current as the work changes.
|
||||||
|
See CONTRIBUTING.md#pull-request-ownership-and-readiness. -->
|
||||||
|
|
||||||
|
Human owner: <!-- @GitHub-handle -->
|
||||||
|
|
||||||
|
- [ ] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
|
||||||
|
- [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
|
||||||
|
- [ ] I declare this PR ready for initial maintainer review.
|
||||||
|
|
||||||
## Before merge
|
## Before merge
|
||||||
|
|
||||||
<!-- Draft PRs may leave pending items unchecked. Resolve material scope and
|
<!-- Draft PRs may leave pending items unchecked. Resolve material scope and
|
||||||
|
|||||||
@@ -12,21 +12,34 @@ concurrency:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
report:
|
report:
|
||||||
name: Collect recent completed workflow attempts
|
name: Collect recent completed workflow attempts
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
# Consume only API data and execute batch code from the trusted default branch.
|
# Consume only API data and execute batch code from the trusted default branch.
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.repository.default_branch }}
|
ref: ${{ github.event.repository.default_branch }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
continue-on-error: true
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- name: Diagnose missing and startup-failed PR CI
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
CI_REPOSITORY: ${{ github.repository }}
|
||||||
|
CI_REPOSITORY_ID: ${{ github.repository_id }}
|
||||||
|
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||||
|
run: node scripts/ci/pr-ci-health.mjs
|
||||||
- name: Record queue, execution, and matched step timing
|
- name: Record queue, execution, and matched step timing
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
@@ -36,7 +49,7 @@ jobs:
|
|||||||
- name: Upload CI timing artifacts
|
- name: Upload CI timing artifacts
|
||||||
if: always() && hashFiles('.cache/ci-job-timings/*') != ''
|
if: always() && hashFiles('.cache/ci-job-timings/*') != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: ci-job-timings-batch
|
name: ci-job-timings-batch
|
||||||
path: .cache/ci-job-timings/
|
path: .cache/ci-job-timings/
|
||||||
@@ -44,3 +57,28 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
- name: Install dependencies for static failure attribution
|
||||||
|
id: failure-deps
|
||||||
|
if: ${{ !cancelled() && hashFiles('.cache/ci-job-timings/batch-manifest.json') != '' }}
|
||||||
|
continue-on-error: true
|
||||||
|
run: npm ci
|
||||||
|
- name: Attribute supported static failures against main
|
||||||
|
if: ${{ !cancelled() && steps.failure-deps.outcome == 'success' && hashFiles('.cache/ci-job-timings/batch-manifest.json') != '' }}
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
CI_REPOSITORY: ${{ github.repository }}
|
||||||
|
CI_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||||
|
run: node scripts/ci/failure-report.mjs
|
||||||
|
- name: Upload CI health and failure diagnostics
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
continue-on-error: true
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: ci-health-diagnostics
|
||||||
|
path: |
|
||||||
|
.cache/ci-pr-health/
|
||||||
|
.cache/ci-failures/
|
||||||
|
include-hidden-files: true
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 14
|
||||||
+233
-77
@@ -33,7 +33,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
changes:
|
changes:
|
||||||
name: Classify changes
|
name: Classify changes
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -42,6 +42,7 @@ jobs:
|
|||||||
tier: ${{ steps.plan.outputs.tier }}
|
tier: ${{ steps.plan.outputs.tier }}
|
||||||
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
|
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
|
||||||
code: ${{ steps.plan.outputs.code }}
|
code: ${{ steps.plan.outputs.code }}
|
||||||
|
reverse_tools: ${{ steps.plan.outputs.reverse_tools }}
|
||||||
sqlite: ${{ steps.plan.outputs.sqlite }}
|
sqlite: ${{ steps.plan.outputs.sqlite }}
|
||||||
runtime: ${{ steps.plan.outputs.runtime }}
|
runtime: ${{ steps.plan.outputs.runtime }}
|
||||||
package: ${{ steps.plan.outputs.package }}
|
package: ${{ steps.plan.outputs.package }}
|
||||||
@@ -63,11 +64,11 @@ jobs:
|
|||||||
matrix: ${{ steps.plan.outputs.matrix }}
|
matrix: ${{ steps.plan.outputs.matrix }}
|
||||||
test_selection: ${{ steps.plan.outputs.test_selection }}
|
test_selection: ${{ steps.plan.outputs.test_selection }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.source_sha || github.sha }}
|
ref: ${{ inputs.source_sha || github.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- name: Bind qualification to the caller revision
|
- name: Bind qualification to the caller revision
|
||||||
@@ -118,15 +119,32 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CHANGES_RESULT: ${{ needs.changes.result }}
|
CHANGES_RESULT: ${{ needs.changes.result }}
|
||||||
run: test "${CHANGES_RESULT}" = "success"
|
run: test "${CHANGES_RESULT}" = "success"
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
- name: Restore static task cache
|
||||||
|
if: needs.changes.outputs.code == 'true'
|
||||||
|
id: static-cache
|
||||||
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: .turbo/cache/
|
||||||
|
key: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-${{ github.sha }}
|
||||||
|
# Turbo validates each task's inputs after restoring this snapshot.
|
||||||
|
restore-keys: rea-static-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc', '.npmrc', 'package-lock.json') }}-
|
||||||
- name: Validate workflow syntax and expressions
|
- name: Validate workflow syntax and expressions
|
||||||
if: needs.changes.outputs.workflows == 'true'
|
if: needs.changes.outputs.workflows == 'true'
|
||||||
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 -shellcheck= -pyflakes=
|
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 -shellcheck= -pyflakes=
|
||||||
|
- name: Record static task source and execution identity
|
||||||
|
continue-on-error: true
|
||||||
|
if: needs.changes.outputs.code == 'true'
|
||||||
|
env:
|
||||||
|
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
run: npm run ci:static-context
|
||||||
- if: needs.changes.outputs.code == 'true'
|
- if: needs.changes.outputs.code == 'true'
|
||||||
run: npm run check:ci
|
run: npm run check:ci
|
||||||
- if: needs.changes.outputs.code == 'true'
|
- if: needs.changes.outputs.code == 'true'
|
||||||
@@ -136,9 +154,18 @@ jobs:
|
|||||||
run: npm run test:python
|
run: npm run test:python
|
||||||
- if: needs.changes.outputs.code != 'true'
|
- if: needs.changes.outputs.code != 'true'
|
||||||
run: npm run format:check
|
run: npm run format:check
|
||||||
|
- name: Save validated static task cache
|
||||||
|
if: >-
|
||||||
|
success() && needs.changes.outputs.code == 'true' &&
|
||||||
|
steps.npm-cache.outputs.can-save == 'true' &&
|
||||||
|
steps.static-cache.outputs.cache-hit != 'true'
|
||||||
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
|
with:
|
||||||
|
path: .turbo/cache/
|
||||||
|
key: ${{ steps.static-cache.outputs.cache-primary-key }}
|
||||||
- name: Upload complete static diagnostics
|
- name: Upload complete static diagnostics
|
||||||
if: always() && needs.changes.outputs.code == 'true'
|
if: always() && needs.changes.outputs.code == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: static-check-reports
|
name: static-check-reports
|
||||||
path: .cache/static-checks/
|
path: .cache/static-checks/
|
||||||
@@ -146,6 +173,34 @@ jobs:
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
test-source:
|
||||||
|
name: Source tests
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'fast'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- run: npm ci
|
||||||
|
- name: Run source tests without waiting for build
|
||||||
|
run: npm run test:fast -- --reporter=default --reporter=blob
|
||||||
|
env:
|
||||||
|
VITEST_BLOB_LABEL: source
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
name: vitest-report-source
|
||||||
|
path: .vitest/blob/*
|
||||||
|
include-hidden-files: true
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
test-shard:
|
test-shard:
|
||||||
name: Test shard (${{ matrix.shard }})
|
name: Test shard (${{ matrix.shard }})
|
||||||
needs: [changes, build]
|
needs: [changes, build]
|
||||||
@@ -153,17 +208,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
# Failed PRs release the remaining matrix slots; qualification retains
|
||||||
|
# every shard's diagnostics and coverage. Passing PRs run the full suite.
|
||||||
|
fail-fast: ${{ github.event_name == 'pull_request' && needs.changes.outputs.tier == 'fast' }}
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1/6, 2/6, 3/6, 4/6, 5/6, 6/6]
|
# Fast feedback spends the same six test jobs on one source group and
|
||||||
|
# five built groups; full qualification retains six complete shards.
|
||||||
|
shard: ${{ fromJSON(needs.changes.outputs.tier == 'fast' && '["1/5","2/5","3/5","4/5","5/5"]' || '["1/6","2/6","3/6","4/6","5/6","6/6"]') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: rea-test-runtime
|
name: rea-test-runtime
|
||||||
path: .
|
path: .
|
||||||
@@ -172,21 +233,55 @@ jobs:
|
|||||||
run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }}
|
run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }}
|
||||||
env:
|
env:
|
||||||
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
||||||
- name: Run complete portable suite for fast feedback
|
- name: Run built-dependent tests for fast feedback
|
||||||
if: needs.changes.outputs.tier == 'fast'
|
if: needs.changes.outputs.tier == 'fast'
|
||||||
run: npm run test:run -- --reporter=default --reporter=blob --shard=${{ matrix.shard }}
|
run: npm run test:built -- --reporter=default --reporter=blob --shard=${{ matrix.shard }}
|
||||||
env:
|
env:
|
||||||
|
VITEST_BLOB_LABEL: built
|
||||||
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
REA_CI_TEST_HISTORY: .cache/ci-test-history/test-results.json
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: vitest-report-${{ strategy.job-index }}
|
name: vitest-report-built-${{ strategy.job-index }}
|
||||||
path: .vitest/blob/*
|
path: .vitest/blob/*
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
reverse-tools-quality:
|
||||||
|
name: Reverse tools quality
|
||||||
|
needs: [changes, static]
|
||||||
|
if: needs.changes.outputs.reverse_tools == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- run: npm ci
|
||||||
|
- name: Run reverse tools tests with coverage
|
||||||
|
run: npm run test:coverage:reverse-tools
|
||||||
|
- name: Preflight both reverse tools mutation configurations
|
||||||
|
run: |
|
||||||
|
node scripts/prepare-reverse-tools-mutation.mjs
|
||||||
|
npx --no-install stryker run stryker.config.mjs --dryRunOnly
|
||||||
|
npx --no-install stryker run stryker.semantics.config.mjs --dryRunOnly
|
||||||
|
- name: Retain available reverse tools diagnostics
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: reverse-tools-quality-${{ needs.changes.outputs.tier }}-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
coverage/reverse-tools/
|
||||||
|
reports/mutation/
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build package
|
name: Build package
|
||||||
needs: changes
|
needs: changes
|
||||||
@@ -208,11 +303,13 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Reuse portable build outputs across runs
|
- name: Reuse portable build outputs across runs
|
||||||
uses: ./.github/actions/portable-build-cache
|
uses: ./.github/actions/portable-build-cache
|
||||||
@@ -231,13 +328,13 @@ jobs:
|
|||||||
- name: Download previous timing report
|
- name: Download previous timing report
|
||||||
if: steps.history.outputs.run-id != ''
|
if: steps.history.outputs.run-id != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: test-timings
|
name: test-timings
|
||||||
run-id: ${{ steps.history.outputs.run-id }}
|
run-id: ${{ steps.history.outputs.run-id }}
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
path: .cache/ci-test-history
|
path: .cache/ci-test-history
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: rea-test-runtime
|
name: rea-test-runtime
|
||||||
path: |
|
path: |
|
||||||
@@ -254,7 +351,7 @@ jobs:
|
|||||||
# test:prepare caches only portable JS, skill, and catalog outputs.
|
# test:prepare caches only portable JS, skill, and catalog outputs.
|
||||||
# Native package jobs still run their build and prepack checks; Turbo
|
# Native package jobs still run their build and prepack checks; Turbo
|
||||||
# restores matching outputs or rebuilds when their inputs differ.
|
# restores matching outputs or rebuilds when their inputs differ.
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: rea-build-cache
|
name: rea-build-cache
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
@@ -271,7 +368,7 @@ jobs:
|
|||||||
|
|
||||||
test:
|
test:
|
||||||
name: Portable tests
|
name: Portable tests
|
||||||
needs: [changes, test-shard]
|
needs: [changes, test-source, test-shard]
|
||||||
if: ${{ !cancelled() && needs.changes.outputs.tier != 'reused' }}
|
if: ${{ !cancelled() && needs.changes.outputs.tier != 'reused' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
@@ -280,24 +377,34 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CHANGES_RESULT: ${{ needs.changes.result }}
|
CHANGES_RESULT: ${{ needs.changes.result }}
|
||||||
run: test "${CHANGES_RESULT}" = "success"
|
run: test "${CHANGES_RESULT}" = "success"
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
if: needs.changes.outputs.code == 'true' && success()
|
if: needs.changes.outputs.code == 'true' && success()
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
if: needs.changes.outputs.code == 'true' && success()
|
if: needs.changes.outputs.code == 'true' && success()
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
if: needs.changes.outputs.code == 'true' && success()
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- if: needs.changes.outputs.code == 'true' && success()
|
- if: needs.changes.outputs.code == 'true' && success()
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: needs.changes.outputs.code == 'true' && success()
|
if: >-
|
||||||
|
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||||
|
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure' ||
|
||||||
|
(needs.changes.outputs.tier == 'fast' &&
|
||||||
|
(needs.test-source.result == 'success' || needs.test-source.result == 'failure')))
|
||||||
with:
|
with:
|
||||||
path: .vitest/blob
|
path: .vitest/blob
|
||||||
pattern: vitest-report-*
|
pattern: vitest-report-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Merge coverage, JUnit, and timing reports
|
- name: Merge coverage, JUnit, and timing reports
|
||||||
id: merge
|
id: merge
|
||||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success()
|
if: >-
|
||||||
|
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||||
|
needs.changes.outputs.tier == 'full' &&
|
||||||
|
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure')
|
||||||
run: >-
|
run: >-
|
||||||
npm run test:ci:merge --
|
npm run test:ci:merge --
|
||||||
--coverage.reportsDirectory=coverage
|
--coverage.reportsDirectory=coverage
|
||||||
@@ -312,7 +419,11 @@ jobs:
|
|||||||
--outputFile.json=test-results.json
|
--outputFile.json=test-results.json
|
||||||
- name: Merge fast-suite JUnit and timing reports
|
- name: Merge fast-suite JUnit and timing reports
|
||||||
id: merge-fast
|
id: merge-fast
|
||||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'fast' && success()
|
if: >-
|
||||||
|
!cancelled() && needs.changes.outputs.code == 'true' &&
|
||||||
|
needs.changes.outputs.tier == 'fast' &&
|
||||||
|
(needs.test-shard.result == 'success' || needs.test-shard.result == 'failure' ||
|
||||||
|
needs.test-source.result == 'success' || needs.test-source.result == 'failure')
|
||||||
run: >-
|
run: >-
|
||||||
npm exec -- vitest --merge-reports
|
npm exec -- vitest --merge-reports
|
||||||
--reporter=default
|
--reporter=default
|
||||||
@@ -322,15 +433,17 @@ jobs:
|
|||||||
--outputFile.junit=test-results.xml
|
--outputFile.junit=test-results.xml
|
||||||
--outputFile.json=test-results.json
|
--outputFile.json=test-results.json
|
||||||
- name: Summarize slowest test files
|
- name: Summarize slowest test files
|
||||||
if: needs.changes.outputs.code == 'true' && !cancelled() && hashFiles('test-timings.json') != ''
|
if: needs.changes.outputs.code == 'true' && !cancelled()
|
||||||
env:
|
env:
|
||||||
SHARD_RESULT: ${{ needs.test-shard.result }}
|
SHARD_RESULT: ${{ needs.test-shard.result }}
|
||||||
|
SOURCE_REQUIRED: ${{ needs.changes.outputs.tier == 'fast' }}
|
||||||
|
SOURCE_RESULT: ${{ needs.test-source.result }}
|
||||||
MERGE_RESULT: ${{ needs.changes.outputs.tier == 'full' && steps.merge.outcome || steps.merge-fast.outcome }}
|
MERGE_RESULT: ${{ needs.changes.outputs.tier == 'full' && steps.merge.outcome || steps.merge-fast.outcome }}
|
||||||
CI_VERIFY_TEST_INVENTORY: "true"
|
CI_VERIFY_TEST_INVENTORY: "true"
|
||||||
CI_TEST_SELECTION: ${{ needs.changes.outputs.test_selection }}
|
CI_TEST_SELECTION: ${{ needs.changes.outputs.test_selection }}
|
||||||
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
run: node scripts/ci/test-report.mjs
|
run: node scripts/ci/test-report.mjs
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: needs.changes.outputs.code == 'true' && !cancelled() && hashFiles('test-results.json') != ''
|
if: needs.changes.outputs.code == 'true' && !cancelled() && hashFiles('test-results.json') != ''
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
@@ -345,7 +458,7 @@ jobs:
|
|||||||
retention-days: 30
|
retention-days: 30
|
||||||
- name: Publish successful timing history
|
- name: Publish successful timing history
|
||||||
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success() && needs.test-shard.result == 'success'
|
if: needs.changes.outputs.code == 'true' && needs.changes.outputs.tier == 'full' && success() && needs.test-shard.result == 'success'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: test-timings
|
name: test-timings
|
||||||
path: .cache/ci-test-history/test-results.json
|
path: .cache/ci-test-history/test-results.json
|
||||||
@@ -357,7 +470,13 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CODE_REQUIRED: ${{ needs.changes.outputs.code }}
|
CODE_REQUIRED: ${{ needs.changes.outputs.code }}
|
||||||
SHARD_RESULT: ${{ needs.test-shard.result }}
|
SHARD_RESULT: ${{ needs.test-shard.result }}
|
||||||
run: test "${CODE_REQUIRED}" != "true" || test "${SHARD_RESULT}" = "success"
|
SOURCE_REQUIRED: ${{ needs.changes.outputs.tier == 'fast' }}
|
||||||
|
SOURCE_RESULT: ${{ needs.test-source.result }}
|
||||||
|
run: |
|
||||||
|
if [ "${CODE_REQUIRED}" = "true" ]; then
|
||||||
|
test "${SHARD_RESULT}" = "success" || exit 1
|
||||||
|
test "${SOURCE_REQUIRED}" != "true" || test "${SOURCE_RESULT}" = "success"
|
||||||
|
fi
|
||||||
|
|
||||||
conformance-fixtures:
|
conformance-fixtures:
|
||||||
name: Source-built conformance fixtures
|
name: Source-built conformance fixtures
|
||||||
@@ -366,7 +485,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Verify fixtures from a clean checkout
|
- name: Verify fixtures from a clean checkout
|
||||||
run: npm run verify:fixtures
|
run: npm run verify:fixtures
|
||||||
|
|
||||||
@@ -377,8 +496,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- name: Prepare cross compiler
|
- name: Prepare cross compiler
|
||||||
@@ -395,12 +514,27 @@ jobs:
|
|||||||
- run: npm run build:windows-native
|
- run: npm run build:windows-native
|
||||||
- run: npm run verify:windows-native:artifact
|
- run: npm run verify:windows-native:artifact
|
||||||
- run: node scripts/build-windows-native.mjs --inspection-fixture
|
- run: node scripts/build-windows-native.mjs --inspection-fixture
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: windows-native-inspection-fixture
|
name: windows-native-inspection-fixture
|
||||||
path: .cache/windows-native-inspection-fixture/
|
path: .cache/windows-native-inspection-fixture/
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- uses: actions/upload-artifact@v4
|
# The process boundary observations are independent of the addon: they
|
||||||
|
# report the caller's and child's own token, environment and breakaway
|
||||||
|
# results from a separate executable. Cross-build it in the lane that
|
||||||
|
# already has MinGW so the Windows job can observe them too.
|
||||||
|
- name: Build Windows process boundary fixture
|
||||||
|
run: |
|
||||||
|
mkdir -p .cache/windows-process-boundary
|
||||||
|
x86_64-w64-mingw32-g++ -municode -std=c++17 -O2 -static -Wall -Wextra \
|
||||||
|
-o .cache/windows-process-boundary/process-boundary.exe \
|
||||||
|
tests/fixtures/windows/processBoundary.cc -ladvapi32
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: windows-process-boundary-fixture
|
||||||
|
path: .cache/windows-process-boundary/
|
||||||
|
if-no-files-found: error
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: windows-native-controls
|
name: windows-native-controls
|
||||||
path: native/windows/build/
|
path: native/windows/build/
|
||||||
@@ -423,11 +557,13 @@ jobs:
|
|||||||
NODE_OPTIONS: "--max-old-space-size=1024"
|
NODE_OPTIONS: "--max-old-space-size=1024"
|
||||||
UV_THREADPOOL_SIZE: "2"
|
UV_THREADPOOL_SIZE: "2"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- name: Verify native runtime identity
|
- name: Verify native runtime identity
|
||||||
env:
|
env:
|
||||||
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
EXPECTED_CI_PLATFORM: ${{ matrix.platform }}
|
||||||
@@ -438,22 +574,17 @@ jobs:
|
|||||||
assert.equal(process.arch, process.env.EXPECTED_CI_ARCHITECTURE);
|
assert.equal(process.arch, process.env.EXPECTED_CI_ARCHITECTURE);
|
||||||
console.log(JSON.stringify({ platform: process.platform, architecture: process.arch, node: process.version }));"
|
console.log(JSON.stringify({ platform: process.platform, architecture: process.arch, node: process.version }));"
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: rea-build-cache
|
name: rea-build-cache
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: windows-native-controls
|
name: windows-native-controls
|
||||||
path: native/windows/build/
|
path: native/windows/build/
|
||||||
- run: npm run verify:windows-native:artifact
|
- run: npm run verify:windows-native:artifact
|
||||||
- name: Verify NativeAOT diagnostic applicability on this host
|
- name: Verify NativeAOT diagnostic applicability on this host
|
||||||
run: npm run test:focused -- tests/evaluation/package/nativeAotDiagnostics.test.ts
|
run: npm run test:focused -- tests/evaluation/package/nativeAotDiagnostics.test.ts
|
||||||
- name: Resolve npm cache before verifier HOME isolation
|
|
||||||
id: npm-cache
|
|
||||||
run: |
|
|
||||||
cache_path="$(npm config get cache)"
|
|
||||||
echo "path=${cache_path}" >> "${GITHUB_OUTPUT}"
|
|
||||||
- run: npm run verify:package
|
- run: npm run verify:package
|
||||||
env:
|
env:
|
||||||
npm_config_cache: ${{ steps.npm-cache.outputs.path }}
|
npm_config_cache: ${{ steps.npm-cache.outputs.path }}
|
||||||
@@ -463,7 +594,7 @@ jobs:
|
|||||||
run: npm run verify:inspector
|
run: npm run verify:inspector
|
||||||
- name: Retain the exact qualified release package
|
- name: Retain the exact qualified release package
|
||||||
if: needs.changes.outputs.tier == 'full' && matrix.platform == 'linux' && matrix.architecture == 'x64'
|
if: needs.changes.outputs.tier == 'full' && matrix.platform == 'linux' && matrix.architecture == 'x64'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: verified-release-package
|
name: verified-release-package
|
||||||
path: .cache/verified-package/
|
path: .cache/verified-package/
|
||||||
@@ -479,13 +610,15 @@ jobs:
|
|||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: rea-build-cache
|
name: rea-build-cache
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
@@ -514,6 +647,7 @@ jobs:
|
|||||||
tests/boundary/cli/nativeSignatureCli.test.ts
|
tests/boundary/cli/nativeSignatureCli.test.ts
|
||||||
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
|
tests/boundary/mcp/nativeSignatureTargetBindingMcp.test.ts
|
||||||
src/process/capture/FilesystemSnapshot.test.ts
|
src/process/capture/FilesystemSnapshot.test.ts
|
||||||
|
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||||
tests/boundary/process/darwinProcessRunTokenReader.test.ts
|
tests/boundary/process/darwinProcessRunTokenReader.test.ts
|
||||||
tests/boundary/process/directAnalysisSignals.test.ts
|
tests/boundary/process/directAnalysisSignals.test.ts
|
||||||
tests/boundary/process/processCaptureLifecycle.test.ts
|
tests/boundary/process/processCaptureLifecycle.test.ts
|
||||||
@@ -527,11 +661,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Run the public CLI/MCP reconstruction journey
|
- name: Run the public CLI/MCP reconstruction journey
|
||||||
env:
|
env:
|
||||||
@@ -539,7 +675,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
npm run verify:readiness | tee readiness-summary.json
|
npm run verify:readiness | tee readiness-summary.json
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
name: reconstruction-readiness
|
name: reconstruction-readiness
|
||||||
@@ -556,18 +692,20 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: rea-build-cache
|
name: rea-build-cache
|
||||||
path: .turbo/cache/
|
path: .turbo/cache/
|
||||||
- run: npm run build:cached
|
- run: npm run build:cached
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: windows-native-controls
|
name: windows-native-controls
|
||||||
path: native/windows/build/
|
path: native/windows/build/
|
||||||
@@ -590,16 +728,20 @@ jobs:
|
|||||||
- name: Run curated Windows capability tests
|
- name: Run curated Windows capability tests
|
||||||
run: >-
|
run: >-
|
||||||
npx vitest run --coverage.enabled=false
|
npx vitest run --coverage.enabled=false
|
||||||
|
src/process/capture/FilesystemSnapshot.test.ts
|
||||||
|
tests/process-global/filesystemSnapshotFallbackCancellation.test.ts
|
||||||
tests/boundary/filesystem/binaryTarget.test.ts
|
tests/boundary/filesystem/binaryTarget.test.ts
|
||||||
tests/boundary/filesystem/safeOutputTree.test.ts
|
tests/boundary/filesystem/safeOutputTree.test.ts
|
||||||
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
|
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
|
||||||
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
|
tests/boundary/providers/native/artifactProvider.extractionCompleteness.test.ts
|
||||||
tests/boundary/filesystem/setupUnavailableProvider.test.ts
|
tests/boundary/filesystem/setupUnavailableProvider.test.ts
|
||||||
tests/boundary/filesystem/windowsNpxRegistration.test.ts
|
tests/boundary/filesystem/windowsNpxRegistration.test.ts
|
||||||
|
tests/boundary/android/workspaceGuardianRetry.test.ts
|
||||||
tests/acceptance/setup/homeDirectoryFallback.test.ts
|
tests/acceptance/setup/homeDirectoryFallback.test.ts
|
||||||
tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts
|
tests/boundary/filesystem/javascriptApplicationWorkflows.test.ts
|
||||||
tests/boundary/filesystem/webScriptManifestPaths.test.ts
|
tests/boundary/filesystem/webScriptManifestPaths.test.ts
|
||||||
tests/boundary/process/ProcessEvidence.host.test.ts
|
tests/boundary/process/ProcessEvidence.host.test.ts
|
||||||
|
tests/boundary/process/windowsOutputCapture.test.ts
|
||||||
tests/acceptance/webModuleCli.test.ts
|
tests/acceptance/webModuleCli.test.ts
|
||||||
tests/acceptance/webSourceLocationCli.test.ts
|
tests/acceptance/webSourceLocationCli.test.ts
|
||||||
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
tests/boundary/cli/referenceSourceImportStatus.test.ts
|
||||||
@@ -622,8 +764,17 @@ jobs:
|
|||||||
src/application/Update.test.ts
|
src/application/Update.test.ts
|
||||||
tests/process-global/updateMaintenance.test.ts
|
tests/process-global/updateMaintenance.test.ts
|
||||||
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
|
tests/conformance/providers/ghidra/realGhidraWindowsWorkflow.test.ts
|
||||||
- run: npm run verify:windows-native
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
- uses: actions/download-artifact@v4
|
with:
|
||||||
|
name: windows-process-boundary-fixture
|
||||||
|
path: .cache/windows-process-boundary/
|
||||||
|
# Passing the fixture adds the breakaway, caller/child token, environment
|
||||||
|
# semantics and ancestor reparse observations, which the lane skips
|
||||||
|
# entirely when it is absent.
|
||||||
|
- run: >-
|
||||||
|
npm run verify:windows-native -- .
|
||||||
|
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
|
||||||
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: windows-native-inspection-fixture
|
name: windows-native-inspection-fixture
|
||||||
path: .cache/windows-native-inspection-fixture/
|
path: .cache/windows-native-inspection-fixture/
|
||||||
@@ -642,7 +793,7 @@ jobs:
|
|||||||
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
|
tests/boundary/filesystem/directoryArtifactReaderOpen.test.ts
|
||||||
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
||||||
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22.19.0
|
node-version: 22.19.0
|
||||||
- name: Verify native path boundaries on Node 22
|
- name: Verify native path boundaries on Node 22
|
||||||
@@ -652,7 +803,7 @@ jobs:
|
|||||||
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
tests/boundary/filesystem/javascriptPackagePrecedence.test.ts
|
||||||
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
tests/boundary/filesystem/javascriptArtifactContentIdentity.test.ts
|
||||||
tests/boundary/process/sourceMapUncHosts.test.ts
|
tests/boundary/process/sourceMapUncHosts.test.ts
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: windows-capabilities
|
name: windows-capabilities
|
||||||
@@ -666,16 +817,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run docs:check
|
- run: npm run docs:check
|
||||||
- name: Generation leaves authored files unchanged
|
- name: Generation leaves authored files unchanged
|
||||||
run: git diff --exit-code
|
run: git diff --exit-code
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: generated-docs
|
name: generated-docs
|
||||||
path: |
|
path: |
|
||||||
@@ -752,13 +905,16 @@ jobs:
|
|||||||
|
|
||||||
required:
|
required:
|
||||||
name: CI required
|
name: CI required
|
||||||
if: ${{ always() }}
|
# Report failed dependencies, but release superseded PR concurrency slots.
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
needs:
|
needs:
|
||||||
[
|
[
|
||||||
changes,
|
changes,
|
||||||
static,
|
static,
|
||||||
build,
|
build,
|
||||||
|
reverse-tools-quality,
|
||||||
test-shard,
|
test-shard,
|
||||||
|
test-source,
|
||||||
sqlite-runtime,
|
sqlite-runtime,
|
||||||
test,
|
test,
|
||||||
conformance-fixtures,
|
conformance-fixtures,
|
||||||
@@ -779,11 +935,11 @@ jobs:
|
|||||||
nativeaot,
|
nativeaot,
|
||||||
website,
|
website,
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- name: Require every selected lane to pass
|
- name: Require every selected lane to pass
|
||||||
@@ -794,7 +950,7 @@ jobs:
|
|||||||
run: node scripts/ci/required.mjs
|
run: node scripts/ci/required.mjs
|
||||||
- name: Upload full qualification receipt artifact
|
- name: Upload full qualification receipt artifact
|
||||||
if: needs.changes.outputs.tier == 'full' && success()
|
if: needs.changes.outputs.tier == 'full' && success()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.gate.outputs.receipt_name }}
|
name: ${{ steps.gate.outputs.receipt_name }}
|
||||||
path: ${{ steps.gate.outputs.receipt_path }}
|
path: ${{ steps.gate.outputs.receipt_path }}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
name: Mutation sweep
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "47 4 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mutation-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
HUSKY: "0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mutation:
|
||||||
|
name: Mutation sweep (${{ matrix.suite }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 2
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- suite: reverse-tools
|
||||||
|
config: stryker.config.mjs
|
||||||
|
- suite: reverse-tools-semantics
|
||||||
|
config: stryker.semantics.config.mjs
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- name: Cache npm package downloads
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run test:prepare
|
||||||
|
- name: Run the complete configuration
|
||||||
|
env:
|
||||||
|
MUTATION_CONFIG: ${{ matrix.config }}
|
||||||
|
run: |
|
||||||
|
node scripts/prepare-reverse-tools-mutation.mjs
|
||||||
|
npx --no-install stryker run "${MUTATION_CONFIG}"
|
||||||
|
- name: Require both reports from the completed sweep
|
||||||
|
env:
|
||||||
|
REPORT_BASE: reports/mutation/${{ matrix.suite }}
|
||||||
|
run: |
|
||||||
|
test -s "${REPORT_BASE}.json"
|
||||||
|
test -s "${REPORT_BASE}.html"
|
||||||
|
- name: Retain available mutation diagnostics
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: mutation-${{ matrix.suite }}-${{ github.run_attempt }}
|
||||||
|
path: |
|
||||||
|
reports/mutation/${{ matrix.suite }}.json
|
||||||
|
reports/mutation/${{ matrix.suite }}.html
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 7
|
||||||
@@ -19,16 +19,16 @@ jobs:
|
|||||||
DOTNET_PROCESSOR_COUNT: "1"
|
DOTNET_PROCESSOR_COUNT: "1"
|
||||||
COMPlus_GCHeapHardLimit: "30000000"
|
COMPlus_GCHeapHardLimit: "30000000"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- uses: actions/setup-dotnet@v4
|
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
||||||
with:
|
with:
|
||||||
dotnet-version: "8.0.416"
|
dotnet-version: "8.0.416"
|
||||||
- name: Build benign fixture and independent byte oracle
|
- name: Build benign fixture and independent byte oracle
|
||||||
run: node scripts/generate-nativeaot-fixtures.mjs
|
run: node scripts/generate-nativeaot-fixtures.mjs
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: nativeaot-win-x64-${{ github.event.pull_request.head.sha || github.sha }}
|
name: nativeaot-win-x64-${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run docs:build
|
- run: npm run docs:build
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: PR readiness
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
branches: [main, "release/*"]
|
||||||
|
types:
|
||||||
|
[
|
||||||
|
opened,
|
||||||
|
edited,
|
||||||
|
synchronize,
|
||||||
|
reopened,
|
||||||
|
ready_for_review,
|
||||||
|
converted_to_draft,
|
||||||
|
]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pull_request_number:
|
||||||
|
description: Open PR to recheck
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pr-readiness-${{ github.event.pull_request.number || inputs.pull_request_number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
readiness:
|
||||||
|
name: Validate author declaration
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
timeout-minutes: 3
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
statuses: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.repository.default_branch }}
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Check initial readiness
|
||||||
|
uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { pathToFileURL } = require("node:url");
|
||||||
|
const entry = pathToFileURL(
|
||||||
|
`${process.env.GITHUB_WORKSPACE}/scripts/ci/pr-readiness.mjs`,
|
||||||
|
);
|
||||||
|
const { runReadinessCheck } = await import(entry.href);
|
||||||
|
await runReadinessCheck({ github, context, core });
|
||||||
@@ -14,7 +14,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
pr-title:
|
pr-title:
|
||||||
name: Conventional PR title
|
name: Conventional PR title
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
steps:
|
steps:
|
||||||
- name: Validate title for Release Please
|
- name: Validate title for Release Please
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Real upstream Cutter verification
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [real-cutter-upstream]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows-native-artifact:
|
||||||
|
name: Build Windows bridge security boundary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- name: Install the cross compiler used by the Windows native lane
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
|
||||||
|
- run: npm run build:windows-native
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cutter-windows-native
|
||||||
|
path: native/windows/build/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
cutter-upstream:
|
||||||
|
name: Cutter upstream smoke (${{ matrix.platform }})
|
||||||
|
needs: windows-native-artifact
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 20
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: linux
|
||||||
|
runner: [self-hosted, cutter-upstream, linux]
|
||||||
|
- platform: macOS
|
||||||
|
runner: [self-hosted, cutter-upstream, macOS]
|
||||||
|
- platform: Windows
|
||||||
|
runner: [self-hosted, cutter-upstream, Windows]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- run: npm ci
|
||||||
|
- if: matrix.platform == 'Windows'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: cutter-windows-native
|
||||||
|
path: native/windows/build/
|
||||||
|
- if: matrix.platform == 'Windows'
|
||||||
|
run: npm run verify:windows-native:artifact
|
||||||
|
- if: matrix.platform == 'Windows'
|
||||||
|
run: npm run build:cached
|
||||||
|
- if: matrix.platform == 'Windows'
|
||||||
|
run: npm run verify:windows-native
|
||||||
|
- name: Verify host and real Cutter bridge
|
||||||
|
env:
|
||||||
|
EXPECTED_PLATFORM: ${{ matrix.platform }}
|
||||||
|
run: npm run verify:cutter
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: cutter-upstream-${{ matrix.platform }}
|
||||||
|
path: cutter-smoke.json
|
||||||
|
if-no-files-found: ignore
|
||||||
|
retention-days: 30
|
||||||
@@ -22,11 +22,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
@@ -43,3 +45,5 @@ jobs:
|
|||||||
echo "REA_VERIFY_SOLC_MODULE=${evm_fixture_root}/node_modules/solc/index.js" >> "${GITHUB_ENV}"
|
echo "REA_VERIFY_SOLC_MODULE=${evm_fixture_root}/node_modules/solc/index.js" >> "${GITHUB_ENV}"
|
||||||
- name: Verify source-owned compiled bytecode through public CLI and MCP
|
- name: Verify source-owned compiled bytecode through public CLI and MCP
|
||||||
run: npm run verify:evm:interface
|
run: npm run verify:evm:interface
|
||||||
|
- name: Verify content-hash-free and CBOR-free owned compiler artifacts
|
||||||
|
run: npm run verify:solidity:metadata
|
||||||
@@ -16,8 +16,8 @@ jobs:
|
|||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- name: Prepare cross compiler
|
- name: Prepare cross compiler
|
||||||
@@ -31,11 +31,25 @@ jobs:
|
|||||||
sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
|
sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
|
||||||
- run: npm run build:windows-native
|
- run: npm run build:windows-native
|
||||||
- run: npm run verify:windows-native:artifact
|
- run: npm run verify:windows-native:artifact
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: real-ghidra-windows-native
|
name: real-ghidra-windows-native
|
||||||
path: native/windows/build/
|
path: native/windows/build/
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
# The boundary observations need a separate Windows executable, which the
|
||||||
|
# native lane skips when it is absent. Cross-build it here, where MinGW is
|
||||||
|
# already installed, so the proof run observes them too.
|
||||||
|
- name: Build Windows process boundary fixture
|
||||||
|
run: |
|
||||||
|
mkdir -p .cache/windows-process-boundary
|
||||||
|
x86_64-w64-mingw32-g++ -municode -std=c++17 -O2 -static -Wall -Wextra \
|
||||||
|
-o .cache/windows-process-boundary/process-boundary.exe \
|
||||||
|
tests/fixtures/windows/processBoundary.cc -ladvapi32
|
||||||
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: real-ghidra-windows-process-boundary
|
||||||
|
path: .cache/windows-process-boundary/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
name: Real Ghidra 12.1.4 Windows x64 PE proof
|
name: Real Ghidra 12.1.4 Windows x64 PE proof
|
||||||
@@ -45,17 +59,23 @@ jobs:
|
|||||||
runs-on: [self-hosted, Windows, x64, ghidra-12-1-2]
|
runs-on: [self-hosted, Windows, x64, ghidra-12-1-2]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: real-ghidra-windows-native
|
name: real-ghidra-windows-native
|
||||||
path: native/windows/build/
|
path: native/windows/build/
|
||||||
- run: npm run verify:windows-native:artifact
|
- run: npm run verify:windows-native:artifact
|
||||||
- run: npm run verify:windows-native
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: real-ghidra-windows-process-boundary
|
||||||
|
path: .cache/windows-process-boundary/
|
||||||
|
- run: >-
|
||||||
|
npm run verify:windows-native -- .
|
||||||
|
"${{ github.workspace }}\.cache\windows-process-boundary\process-boundary.exe"
|
||||||
- name: Verify all admitted Windows Ghidra operations
|
- name: Verify all admitted Windows Ghidra operations
|
||||||
env:
|
env:
|
||||||
REA_ANALYSIS_PROVIDER: ghidra
|
REA_ANALYSIS_PROVIDER: ghidra
|
||||||
@@ -88,7 +108,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REA_ANALYSIS_PROVIDER: ghidra
|
REA_ANALYSIS_PROVIDER: ghidra
|
||||||
run: npm run verify:ghidra:windows:package -- --x86 --dll | Tee-Object -FilePath windows-ghidra-x86-dll-package-proof.log
|
run: npm run verify:ghidra:windows:package -- --x86 --dll | Tee-Object -FilePath windows-ghidra-x86-dll-package-proof.log
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: windows-ghidra-proof
|
name: windows-ghidra-proof
|
||||||
|
|||||||
@@ -44,12 +44,14 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
- uses: actions/setup-go@v5
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.1"
|
go-version: "1.26.1"
|
||||||
cache: false
|
cache: false
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Pack CLI for setup verification
|
- name: Pack CLI for setup verification
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -36,11 +38,13 @@ jobs:
|
|||||||
runs-on: [self-hosted, Linux, hopper]
|
runs-on: [self-hosted, Linux, hopper]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Install packed CLI in isolation
|
- name: Install packed CLI in isolation
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ jobs:
|
|||||||
runs-on: [self-hosted, macOS, hopper]
|
runs-on: [self-hosted, macOS, hopper]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Install packed CLI in isolation
|
- name: Install packed CLI in isolation
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -43,11 +43,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tshark util-linux python3
|
- run: sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tshark util-linux python3
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build:cached
|
- run: npm run build:cached
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -36,11 +36,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: sudo apt-get update && sudo apt-get install -y sigrok-cli util-linux python3
|
- run: sudo apt-get update && sudo apt-get install -y sigrok-cli util-linux python3
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build:cached
|
- run: npm run build:cached
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Download the pinned Termux app and verify its release checksum
|
- name: Download the pinned Termux app and verify its release checksum
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
sudo udevadm control --reload-rules
|
sudo udevadm control --reload-rules
|
||||||
sudo udevadm trigger --name-match=kvm
|
sudo udevadm trigger --name-match=kvm
|
||||||
- name: Build and capture a page inside Termux
|
- name: Build and capture a page inside Termux
|
||||||
uses: reactivecircus/android-emulator-runner@4c44018e59b437e86cdfc41da381398f93ed8808 # v2
|
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
|
||||||
with:
|
with:
|
||||||
api-level: 30
|
api-level: 30
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
script: bash scripts/verify/termux/emulator.sh
|
script: bash scripts/verify/termux/emulator.sh
|
||||||
- name: Upload Termux and Android diagnostics
|
- name: Upload Termux and Android diagnostics
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: termux-browser-diagnostics
|
name: termux-browser-diagnostics
|
||||||
path: ${{ runner.temp }}/rea-termux/diagnostics
|
path: ${{ runner.temp }}/rea-termux/diagnostics
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- name: Use pinned package manager
|
- name: Use pinned package manager
|
||||||
run: npm install --global npm@11.16.0
|
run: npm install --global npm@11.16.0
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|||||||
@@ -17,16 +17,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
build_cache_artifact: ${{ inputs.build_cache_artifact }}
|
||||||
- run: npm run verify:browser:runtime
|
- run: env -u DBUS_SESSION_BUS_ADDRESS npm run verify:browser:runtime
|
||||||
env:
|
env:
|
||||||
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
|
||||||
REA_BROWSER_NO_SANDBOX: "true"
|
REA_BROWSER_NO_SANDBOX: "true"
|
||||||
@@ -17,11 +17,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: ./.github/actions/portable-build-cache
|
- uses: ./.github/actions/portable-build-cache
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
name: Recover published release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Existing npm and GitHub release version
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release_run_id:
|
||||||
|
description: Original Release run with successful exact-source qualification and npm publication
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-recovery-${{ inputs.version }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
name: Verify existing published package
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
outputs:
|
||||||
|
source_sha: ${{ steps.release.outputs.source_sha }}
|
||||||
|
version: ${{ steps.release.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
|
with:
|
||||||
|
node-version-file: .nvmrc
|
||||||
|
package-manager-cache: false
|
||||||
|
- run: npm ci
|
||||||
|
- name: Validate original release authority
|
||||||
|
id: release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RECOVERY_VERSION: ${{ inputs.version }}
|
||||||
|
RECOVERY_RUN_ID: ${{ inputs.release_run_id }}
|
||||||
|
run: node scripts/ci/release-recovery.mjs resolve
|
||||||
|
- name: Check out released metadata
|
||||||
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.release.outputs.source_sha }}
|
||||||
|
path: .cache/release-source
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Download original qualified package
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: verified-release-package
|
||||||
|
run-id: ${{ steps.release.outputs.run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
path: .cache/verified-package
|
||||||
|
- name: Restore original verifier runtime
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: rea-test-runtime
|
||||||
|
run-id: ${{ steps.release.outputs.run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
path: .
|
||||||
|
- name: Match published npm bytes to the qualified package
|
||||||
|
env:
|
||||||
|
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||||
|
RECOVERY_RUN_ID: ${{ steps.release.outputs.run_id }}
|
||||||
|
RECOVERY_RUN_ATTEMPT: ${{ steps.release.outputs.run_attempt }}
|
||||||
|
RECOVERY_SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
||||||
|
RECOVERY_SOURCE_DIRECTORY: .cache/release-source
|
||||||
|
RECOVERY_ARTIFACT_DIRECTORY: .cache/verified-package
|
||||||
|
run: node scripts/ci/release-recovery.mjs verify
|
||||||
|
- 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"
|
||||||
|
- name: Verify published CLI, MCP and update
|
||||||
|
env:
|
||||||
|
RECOVERY_VERSION: ${{ steps.release.outputs.version }}
|
||||||
|
run: node scripts/verify-published-package.mjs "$RECOVERY_VERSION"
|
||||||
|
- name: Validate released registry metadata
|
||||||
|
working-directory: .cache/release-source
|
||||||
|
run: npm run mcp-registry:check
|
||||||
|
|
||||||
|
publish-mcp:
|
||||||
|
name: Finish MCP Registry publication
|
||||||
|
needs: verify
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Check out verified released metadata
|
||||||
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.verify.outputs.source_sha }}
|
||||||
|
path: .cache/release-source
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: ./.github/actions/publish-mcp
|
||||||
|
with:
|
||||||
|
directory: .cache/release-source
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
echo "frozen=true" >> "$GITHUB_OUTPUT"
|
echo "frozen=true" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||||
if: steps.editorial.outputs.frozen != 'true'
|
if: steps.editorial.outputs.frozen != 'true'
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
@@ -70,13 +70,13 @@ jobs:
|
|||||||
sha: ${{ steps.release.outputs.sha }}
|
sha: ${{ steps.release.outputs.sha }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out reviewed release merge
|
- name: Check out reviewed release merge
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
@@ -104,7 +104,7 @@ jobs:
|
|||||||
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
|
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
|
||||||
--source "$SOURCE_SHA" --pr "$RELEASE_PR"
|
--source "$SOURCE_SHA" --pr "$RELEASE_PR"
|
||||||
|
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||||
id: release
|
id: release
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
@@ -191,14 +191,14 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Check out release controller and Git history
|
- name: Check out release controller and Git history
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.sha }}
|
ref: ${{ github.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Node.js for checkpoint validation
|
- name: Set up Node.js for checkpoint validation
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
@@ -226,7 +226,7 @@ jobs:
|
|||||||
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
|
node scripts/release-notes.mjs publication --repository "$GH_REPO" \
|
||||||
--source "$SOURCE_SHA"
|
--source "$SOURCE_SHA"
|
||||||
|
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||||
id: release
|
id: release
|
||||||
with:
|
with:
|
||||||
# A dedicated credential starts PR CI automatically; the built-in
|
# A dedicated credential starts PR CI automatically; the built-in
|
||||||
@@ -255,7 +255,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check out release pull request
|
- name: Check out release pull request
|
||||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
|
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -263,7 +263,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Node.js for generated documentation
|
- name: Set up Node.js for generated documentation
|
||||||
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
@@ -304,22 +304,51 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
source_sha: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
source_sha: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||||
|
|
||||||
publish:
|
rehearsal:
|
||||||
name: Publish to npm
|
name: Rehearse qualified release on a clean runner
|
||||||
needs: [merged-release, release-please, qualification]
|
needs: [merged-release, release-please, qualification]
|
||||||
if: >-
|
if: >-
|
||||||
always() && !cancelled() && needs.qualification.result == 'success'
|
always() && !cancelled() && needs.qualification.result == 'success'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
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: Exercise the exact qualified tarball
|
||||||
|
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"
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish to npm
|
||||||
|
needs: [merged-release, release-please, qualification, rehearsal]
|
||||||
|
if: >-
|
||||||
|
always() && !cancelled() &&
|
||||||
|
needs.qualification.result == 'success' && needs.rehearsal.result == 'success'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
@@ -327,58 +356,36 @@ jobs:
|
|||||||
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|
||||||
- name: Download the exact qualified package
|
- uses: ./.github/actions/prepare-release-package
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: verified-release-package
|
|
||||||
path: .cache/verified-package/
|
|
||||||
|
|
||||||
- name: Verify qualified package provenance and digest
|
|
||||||
id: package
|
id: package
|
||||||
run: node scripts/ci/verified-package.mjs validate .cache/verified-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
|
- name: Publish
|
||||||
|
if: steps.registry.outputs.published == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||||
|
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||||
run: |
|
run: |
|
||||||
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
|
tag="$(node scripts/release-npm-tag.mjs "$VERIFIED_PACKAGE_VERSION")"
|
||||||
published_version="$(npm view "rea-agents@${version}" version --json 2>/dev/null || true)"
|
npm publish "${VERIFIED_PACKAGE_TARBALL}" --access public --tag "${tag}"
|
||||||
if [ "${published_version}" = "\"${version}\"" ]; then
|
|
||||||
echo "rea-agents@${version} is already published; verifying the existing package."
|
- name: Verify published npm bytes and dist-tag
|
||||||
else
|
env:
|
||||||
tag="$(node scripts/release-npm-tag.mjs "${version}")"
|
VERIFIED_PACKAGE_TARBALL: ${{ steps.package.outputs.tarball }}
|
||||||
npm publish "${VERIFIED_PACKAGE_TARBALL}" --access public --tag "${tag}"
|
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||||
fi
|
run: node scripts/ci/npm-release.mjs verify "$VERIFIED_PACKAGE_TARBALL" "$VERIFIED_PACKAGE_VERSION"
|
||||||
|
|
||||||
- name: Verify published CLI from npm
|
- name: Verify published CLI from npm
|
||||||
shell: bash
|
env:
|
||||||
run: |
|
VERIFIED_PACKAGE_VERSION: ${{ steps.package.outputs.version }}
|
||||||
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
|
run: node scripts/verify-published-package.mjs "$VERIFIED_PACKAGE_VERSION"
|
||||||
verification_root="$(mktemp -d)"
|
|
||||||
trap 'rm -rf -- "${verification_root}"' EXIT
|
|
||||||
cd "${verification_root}"
|
|
||||||
max_attempts=120
|
|
||||||
sleep_seconds=15
|
|
||||||
total_window_minutes=$((max_attempts * sleep_seconds / 60))
|
|
||||||
for attempt in $(seq 1 "${max_attempts}"); do
|
|
||||||
published_version="$(npm view --prefer-online "rea-agents@${version}" version --json 2>/dev/null || true)"
|
|
||||||
if [ "${published_version}" = "\"${version}\"" ]; then
|
|
||||||
echo "rea-agents@${version} is visible on npm after attempt ${attempt}/${max_attempts}; verifying the published CLI."
|
|
||||||
npm exec --yes --package="rea-agents@${version}" -- rea --help
|
|
||||||
npm exec --yes --package="rea-agents@${version}" -- rea --llms
|
|
||||||
node "${GITHUB_WORKSPACE}/scripts/verify-published-package.mjs" "${version}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo "rea-agents@${version} is not visible on npm yet (attempt ${attempt}/${max_attempts}; up to ${total_window_minutes} minutes)" >&2
|
|
||||||
fi
|
|
||||||
if [ "${attempt}" -lt "${max_attempts}" ]; then
|
|
||||||
sleep "${sleep_seconds}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "rea-agents@${version} did not become visible on npm within ${total_window_minutes} minutes (${max_attempts} attempts × ${sleep_seconds}s)" >&2
|
|
||||||
echo "This indicates npm registry propagation delay, not a CLI verification failure." >&2
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
publish-mcp:
|
publish-mcp:
|
||||||
name: Publish to MCP Registry
|
name: Publish to MCP Registry
|
||||||
@@ -392,7 +399,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
ref: ${{ needs.merged-release.outputs.sha || needs.release-please.outputs.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -400,23 +407,4 @@ jobs:
|
|||||||
- name: Verify package and registry metadata versions
|
- name: Verify package and registry metadata versions
|
||||||
run: npm run mcp-registry:check
|
run: npm run mcp-registry:check
|
||||||
|
|
||||||
- name: Install MCP Registry publisher
|
- uses: ./.github/actions/publish-mcp
|
||||||
env:
|
|
||||||
MCP_PUBLISHER_VERSION: v1.8.0
|
|
||||||
MCP_PUBLISHER_SHA256_LINUX_AMD64: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
|
|
||||||
run: |
|
|
||||||
archive="mcp-publisher_linux_amd64.tar.gz"
|
|
||||||
curl -fsSLo "${archive}" \
|
|
||||||
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${archive}"
|
|
||||||
echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${archive}" | sha256sum --check --strict
|
|
||||||
tar xzf "${archive}" mcp-publisher
|
|
||||||
chmod +x mcp-publisher
|
|
||||||
|
|
||||||
- name: Validate MCP Registry metadata
|
|
||||||
run: ./mcp-publisher validate
|
|
||||||
|
|
||||||
- name: Authenticate to MCP Registry
|
|
||||||
run: ./mcp-publisher login github-oidc
|
|
||||||
|
|
||||||
- name: Publish MCP Registry metadata
|
|
||||||
run: ./mcp-publisher publish
|
|
||||||
@@ -22,14 +22,16 @@ jobs:
|
|||||||
# The repository-pinned Node 24 version also runs the complete test suite.
|
# 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.15.0", "24.x", "26.x"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: npm
|
- name: Cache npm package downloads
|
||||||
|
id: npm-cache
|
||||||
|
uses: ./.github/actions/npm-cache
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: rea-test-runtime
|
name: rea-test-runtime
|
||||||
path: .
|
path: .
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ jobs:
|
|||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Set up Python for website assets
|
- name: Set up Python for website assets
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Set up Python for website assets
|
- name: Set up Python for website assets
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: pip
|
cache: pip
|
||||||
@@ -74,9 +74,9 @@ jobs:
|
|||||||
)
|
)
|
||||||
PY
|
PY
|
||||||
- name: Configure Pages
|
- name: Configure Pages
|
||||||
uses: actions/configure-pages@v5
|
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
|
||||||
- name: Upload the website
|
- name: Upload the website
|
||||||
uses: actions/upload-pages-artifact@v4
|
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
|
||||||
with:
|
with:
|
||||||
path: website/_site
|
path: website/_site
|
||||||
|
|
||||||
@@ -95,15 +95,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out publishing configuration
|
- name: Check out publishing configuration
|
||||||
if: inputs.target == 'both'
|
if: inputs.target == 'both'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
- name: Set up Node.js for Wrangler
|
- name: Set up Node.js for Wrangler
|
||||||
if: inputs.target == 'both'
|
if: inputs.target == 'both'
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||||
with:
|
with:
|
||||||
node-version-file: .nvmrc
|
node-version-file: .nvmrc
|
||||||
- name: Download the verified website
|
- name: Download the verified website
|
||||||
if: inputs.target == 'both'
|
if: inputs.target == 'both'
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
name: github-pages
|
name: github-pages
|
||||||
path: ${{ runner.temp }}/website-archive
|
path: ${{ runner.temp }}/website-archive
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
tar -xf "$RUNNER_TEMP/website-archive/artifact.tar" -C "$RUNNER_TEMP/rea-website-public"
|
tar -xf "$RUNNER_TEMP/website-archive/artifact.tar" -C "$RUNNER_TEMP/rea-website-public"
|
||||||
- name: Publish the website to Cloudflare
|
- name: Publish the website to Cloudflare
|
||||||
if: inputs.target == 'both'
|
if: inputs.target == 'both'
|
||||||
uses: cloudflare/wrangler-action@v4
|
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
command: deploy --cwd website --env production --assets "${{ runner.temp }}/rea-website-public"
|
command: deploy --cwd website --env production --assets "${{ runner.temp }}/rea-website-public"
|
||||||
- name: Publish to GitHub Pages
|
- name: Publish to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||||
- name: Verify the selected published revisions
|
- name: Verify the selected published revisions
|
||||||
env:
|
env:
|
||||||
PAGES_URL: ${{ steps.deployment.outputs.page_url }}
|
PAGES_URL: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ codedb.snapshot
|
|||||||
htmlcov/
|
htmlcov/
|
||||||
.coverage
|
.coverage
|
||||||
coverage/
|
coverage/
|
||||||
|
.stryker-tmp/
|
||||||
|
reports/mutation/
|
||||||
.vitest/
|
.vitest/
|
||||||
.vitest-reports/
|
.vitest-reports/
|
||||||
test-results.xml
|
test-results.xml
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
".": "6.4.0"
|
".": "6.5.0"
|
||||||
}
|
}
|
||||||
@@ -78,6 +78,37 @@ For test changes or provider claims, use [docs/testing.md](docs/testing.md). Pre
|
|||||||
|
|
||||||
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
|
Use Conventional Commit subjects and PR titles. Mark breaking changes with `!` or a `BREAKING CHANGE:` footer. Describe behavior changes, relevant validation, and real-provider verification scope.
|
||||||
|
|
||||||
|
## Issue Reports
|
||||||
|
|
||||||
|
Follow [issue reporting guidance](CONTRIBUTING.md#reporting-issues) and the matching
|
||||||
|
issue form. Keep reports concise and cite the smallest useful evidence.
|
||||||
|
Distinguish observed results, source-derived reasoning, hypotheses, and proposed
|
||||||
|
checks. Never claim runtime reproduction or measurements you did not perform.
|
||||||
|
Use `NOT_ENOUGH_INFO` with an explanation for missing narrative answers or evidence.
|
||||||
|
Incomplete reports, source-only findings, and early feature or design ideas are
|
||||||
|
welcome when their limitations are explicit; a completed diagnosis or fix is not
|
||||||
|
required to open an issue.
|
||||||
|
|
||||||
|
## Pull Request Readiness
|
||||||
|
|
||||||
|
Follow [human ownership and readiness](CONTRIBUTING.md#pull-request-ownership-and-readiness)
|
||||||
|
when preparing a PR. AI-assisted contributions are welcome; a human must review
|
||||||
|
and own the final diff, check the validation evidence and its limitations, and
|
||||||
|
explicitly declare the PR ready for initial maintainer review.
|
||||||
|
|
||||||
|
Keep the PR body concise and current, with the concrete problem, observable
|
||||||
|
outcome, approach, and evidence. Report only checks that actually ran as executed;
|
||||||
|
distinguish source-derived conclusions, proposed checks, and unavailable coverage.
|
||||||
|
Address actionable automated review findings and explain disagreements.
|
||||||
|
|
||||||
|
Leave the template's **Author readiness** declarations unchecked for the human
|
||||||
|
owner to complete. Never attest to human review on their behalf. Keep the PR in
|
||||||
|
draft until the human declares the PR initially ready. Later pushes do not
|
||||||
|
invalidate that declaration or require another human sign-off. Keep the PR body
|
||||||
|
and affected validation current as work changes. The readiness check validates
|
||||||
|
the filled declaration; CI and automated review results do not establish that
|
||||||
|
human review occurred, maintainer scope approval, or merge permission.
|
||||||
|
|
||||||
<!-- BEGIN:turborepo-agent-rules -->
|
<!-- BEGIN:turborepo-agent-rules -->
|
||||||
|
|
||||||
# This is NOT the Turborepo you know
|
# This is NOT the Turborepo you know
|
||||||
|
|||||||
+113
@@ -1,5 +1,118 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [6.5.0](https://github.com/morluto/rea/compare/rea-agents-6.4.0...rea-agents-6.5.0) (2026-10-13)
|
||||||
|
|
||||||
|
Provider prerequisites and recorded verification scope are listed in [the testing guide](docs/testing.md).
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
- Analyze selected JavaScript files and recover cross-bundle relationships without expanding the selected source scope.
|
||||||
|
- Read compact summaries and page retained Evidence across binary, managed, Android, Dart, WASM and network workflows.
|
||||||
|
- Inspect SQLite snapshots, packet and signal recordings, WebAssembly, Rust dependency metadata and Electron fuses offline.
|
||||||
|
- Keep completed Evidence and partial observations available when analysis or cleanup fails.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- **JavaScript source selection:** analyze exact application paths without expanding imports into the selected scope. Unselected files retain identity and explicit unknown coverage; summary delivery remains separate from source selection. ([#1944](https://github.com/morluto/rea/pull/1944), [issue morluto/rea#1848](https://github.com/morluto/rea/issues/1848)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
- **Code-split recovery:** recover related bundles from multiple selected inputs, retaining per-file provenance and downstream cross-chunk imports. Recorded CLI/MCP coverage is Linux x64 with Wakaru 1.14.0 and source-owned esbuild/webpack fixtures; other hosts, releases and arbitrary application equivalence remain unverified. ([#1686](https://github.com/morluto/rea/pull/1686), [issue morluto/rea#1605](https://github.com/morluto/rea/issues/1605)). Thanks @JingHao-Leon, @pionxzh.
|
||||||
|
- **Package imports:** resolve exact package import aliases using the selected loader and package ancestry, rather than guessing sibling files. ([#1862](https://github.com/morluto/rea/pull/1862), [issue morluto/rea#1838](https://github.com/morluto/rea/issues/1838)). Thanks @AWUQAQ, @mvanhorn.
|
||||||
|
- **Retained analysis views:** opt into compact inventory/WASM, managed metadata, Android manifest, Dart AOT, Mach-O and EVM summaries, then page retained Evidence without rerunning analysis. Summary delivery preserves the complete recorded result and its original coverage. ([#2122](https://github.com/morluto/rea/pull/2122), [#2193](https://github.com/morluto/rea/pull/2193), [#2413](https://github.com/morluto/rea/pull/2413), [#2438](https://github.com/morluto/rea/pull/2438), [#2455](https://github.com/morluto/rea/pull/2455), [#2634](https://github.com/morluto/rea/pull/2634), [#2308](https://github.com/morluto/rea/pull/2308), [9a18251](https://github.com/morluto/rea/commit/9a18251426f891e6a24856b715ef8002ab8b9610)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
- **Managed assemblies:** Select declaring types when inspecting members, decode inspected `ldstr` literals with independent coverage, and page retained member summaries. Call and field targets expose `target_declaring_type` and `field_declaring_type`, including supported generic method targets; unresolved metadata remains unknown. ([#1706](https://github.com/morluto/rea/pull/1706), [#2125](https://github.com/morluto/rea/pull/2125), [issue morluto/rea#2121](https://github.com/morluto/rea/issues/2121), [#2162](https://github.com/morluto/rea/pull/2162), [issue morluto/rea#2158](https://github.com/morluto/rea/issues/2158), [#3061](https://github.com/morluto/rea/pull/3061), [issue morluto/rea#3010](https://github.com/morluto/rea/issues/3010), [#2163](https://github.com/morluto/rea/pull/2163), [issue morluto/rea#2160](https://github.com/morluto/rea/issues/2160)). Thanks @kaoru0822-kitauji, @N0zoM1z0, @Nelglor, @skoodster.
|
||||||
|
- **Packet captures:** inspect saved packets with bounded TShark output, compact capture summaries and retained pages; discover installed protocol/field definitions. Recorded real-engine coverage uses Linux and Wireshark 4.6.6; native Windows/macOS and Bun behavior remain unverified. ([#2318](https://github.com/morluto/rea/pull/2318), [#2340](https://github.com/morluto/rea/pull/2340), [#2496](https://github.com/morluto/rea/pull/2496)). Thanks @3QL, @N0zoM1z0, @Olorinm.
|
||||||
|
- **Recorded signals:** decode existing recordings and discover installed decoders/options through caller-supplied sigrok-cli 0.7.2. This is Linux-only and requires util-linux `prlimit`; it acquires no hardware samples. ([#2339](https://github.com/morluto/rea/pull/2339), [#2487](https://github.com/morluto/rea/pull/2487)). Thanks @N0zoM1z0, @Olorinm.
|
||||||
|
- **Web captures:** derive an inferred method/origin/path-template API surface from retained HAR data including observed query-parameter names while retaining unknowns; query values, headers and bodies remain excluded. ([#1891](https://github.com/morluto/rea/pull/1891), [#2982](https://github.com/morluto/rea/pull/2982), [issue morluto/rea#2913](https://github.com/morluto/rea/issues/2913)). Thanks @mvanhorn, @wunianze666-netizen, @zhangguiping-xydt.
|
||||||
|
- **QUIC captures:** inspect retained aioquic 1.5.0 JSON qlog 0.3 objects and page traces/streams/observations. Other qlog layouts remain unsupported; overlap and loss declarations do not prove retransmission or network loss. ([#2277](https://github.com/morluto/rea/pull/2277)). Thanks @LuisTaffelli, @N0zoM1z0.
|
||||||
|
- **Fiddler captures:** inspect native SAZ archives with retained HTTP provenance and bounded decoding. Recorded producer coverage is the pinned public encoding corpus; current Fiddler Everywhere variants and Bun-native execution remain unverified. ([#2409](https://github.com/morluto/rea/pull/2409)). Thanks @N0zoM1z0, @zahhak.
|
||||||
|
- **SQLite snapshots:** inspect an explicitly selected offline database with table/column selection and bounded Evidence. This operation requires Node 24.x >=24.15 or 26+ with `DatabaseSync.limits`; supported Node 22 and earlier 24 runtimes cannot use it. A live database/WAL copy does not establish an atomic snapshot. ([#2271](https://github.com/morluto/rea/pull/2271)). Thanks @N0zoM1z0, @Y0UW1SH.
|
||||||
|
- **WASM artifacts:** Inspect local modules with bring-your-own WABT 1.0.42 without executing them. Compare imports/exports from two complete core-module inspection Evidence records without requiring an active WABT installation; unsupported signatures and duplicate identities remain unknown, and comparisons do not establish runtime compatibility. Recorded real-tool inspection coverage is macOS arm64. ([#1533](https://github.com/morluto/rea/pull/1533), [issue morluto/rea#884](https://github.com/morluto/rea/issues/884), [#2464](https://github.com/morluto/rea/pull/2464), [issue morluto/rea#2356](https://github.com/morluto/rea/issues/2356)). Thanks @matbrgz, @N0zoM1z0, @thangldw.
|
||||||
|
- **Go binaries:** inspect embedded build metadata, including file-backed unmapped Mach-O debug segments. ([#1363](https://github.com/morluto/rea/pull/1363), [7fb097d](https://github.com/morluto/rea/commit/7fb097d72c9931dd5dfae481ad2b9d3bb6029f15)). Thanks @Y0UW1SH.
|
||||||
|
- **Flutter APKs:** identify snapshot/engine builds and inspect AOT structure without an external engine. Recorded real APK coverage uses the documented Flutter Gallery fixture; macOS/Windows and complete pool semantics remain unverified, and pool coverage stays partial. ([#1582](https://github.com/morluto/rea/pull/1582)). Thanks @3QL.
|
||||||
|
- **HarmonyOS packages:** inventory HAP/HSP/App Pack structures without executing code. The recorded real workflow covers one pinned HAP through repository-built CLI/MCP; installed-package and real HSP/App Pack coverage remain unverified. ([#1447](https://github.com/morluto/rea/pull/1447)). Thanks @BaaRai.
|
||||||
|
- **Ghidra raw inputs and references:** compatible Linux/macOS Ghidra profiles accept caller-selected raw architecture/entry information and expose typed incoming references at exact addresses. Windows Ghidra P0 admits the raw NMOS 6502 profile described below; other raw targets and the incoming-reference facet remain outside Windows P0. ([#1868](https://github.com/morluto/rea/pull/1868), [issue morluto/rea#1836](https://github.com/morluto/rea/issues/1836), [#1715](https://github.com/morluto/rea/pull/1715), [issue morluto/rea#1245](https://github.com/morluto/rea/issues/1245)). Thanks @bugstan, @casesolved-co-uk, @mvanhorn, @N0zoM1z0.
|
||||||
|
- **Ghidra MIPS:** Add bounded MIPS32r2/o32 relocatable objects, soft-float integer-register storage, admitted MIPS16 shared ELF, and a narrowly admitted recordless legacy MIPS32 executable profile. The latter pins an endian-matched 32-bit language and records inferred o32 as an analysis assumption. Admission follows each documented ABI/flag/relocation boundary. ([#1949](https://github.com/morluto/rea/pull/1949), [issue morluto/rea#1937](https://github.com/morluto/rea/issues/1937), [#1950](https://github.com/morluto/rea/pull/1950), [issue morluto/rea#1938](https://github.com/morluto/rea/issues/1938), [#1991](https://github.com/morluto/rea/pull/1991), [issue morluto/rea#1851](https://github.com/morluto/rea/issues/1851), [#1898](https://github.com/morluto/rea/pull/1898), [issue morluto/rea#1620](https://github.com/morluto/rea/issues/1620)). Thanks @AurelioB, @BaaRai, @bqw371, @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
- **Ghidra RISC-V:** admit little-endian RV32/RV64 `ET_EXEC` ELF declaring the supported double-float ABI. Relocatable/shared, big-endian and other ABI profiles remain refused; see [the profile and recorded lane](docs/ghidra-riscv.md). ([#2151](https://github.com/morluto/rea/pull/2151)). Thanks @NIU-123370.
|
||||||
|
- **PSP static ELF profile:** the Ghidra profile is Linux x64, bounded little-endian ELF32 `ET_EXEC` with the documented PSP/ABI declarations and a compatible caller-installed Allegrex extension. PRX, shared/relocatable images, PBP/ISO/CSO and runtime emulation are outside scope. Historical extension experiments are separate from public-provider verification; real public Ghidra PSP coverage remains unverified. ([#1599](https://github.com/morluto/rea/pull/1599)). Thanks @jaelliot.
|
||||||
|
- **Ghidra analysis settings:** apply validated analyzer overrides before analysis while retaining the selected analysis profile. ([#1913](https://github.com/morluto/rea/pull/1913)). Thanks @akushonkamen, @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
- **Attached native IDA MCP:** add an optional native bridge alongside existing legacy/headless profiles. Recorded native runs use Linux IDA Free 9.4 with a navigation correction and Windows IDA Pro 9.3 with a loopback-listener change; stock native switching, native macOS and Windows IDA Free remain unverified. Attached databases remain read-only and are never saved or closed. ([#1544](https://github.com/morluto/rea/pull/1544)). Thanks @imissapixel, @N0zoM1z0.
|
||||||
|
- **Frida instrumentation:** add explicit local/remote target selection, caller-supplied scripts and retained messages, with script-read/input diagnostics and bounded message eviction. Recorded real coverage is the Linux local Node marker workflow; macOS, Windows and remote devices/servers remain unverified. Instrumentation executes foreign code and is destructive, not read-only. ([#1359](https://github.com/morluto/rea/pull/1359), [#2211](https://github.com/morluto/rea/pull/2211), [issue morluto/rea#2209](https://github.com/morluto/rea/issues/2209), [#2217](https://github.com/morluto/rea/pull/2217), [issue morluto/rea#2213](https://github.com/morluto/rea/issues/2213), [#2628](https://github.com/morluto/rea/pull/2628)). Thanks @dandgabr, @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
- **Client setup:** register Qoder, Trae, ZCode, DeepSeek Harness, Kimi Code, Factory Droid and WorkBuddy through the existing scoped setup flow. DeepSeek is included from 6.5.0; the historical npm 6.4 check did not cover it. Live Trae and DeepSeek API integration remain unverified. ([#1564](https://github.com/morluto/rea/pull/1564), [#1572](https://github.com/morluto/rea/pull/1572), [#1573](https://github.com/morluto/rea/pull/1573), [#1929](https://github.com/morluto/rea/pull/1929), [issue morluto/rea#1839](https://github.com/morluto/rea/issues/1839), [#2744](https://github.com/morluto/rea/pull/2744), [#2309](https://github.com/morluto/rea/pull/2309), [#3041](https://github.com/morluto/rea/pull/3041), [issue morluto/rea#3040](https://github.com/morluto/rea/issues/3040)). Thanks @0xRafie, @BaaRai, @cainiao1992, @IvenKooLab, @whoishaha, @ysntony.
|
||||||
|
- **Website:** Add maintained language editions, persistent/system-aware themes, keyboard styling and nested Evidence indentation, plus the reverse-engineering models essay. A simpler home page and beginner journey connect platform prerequisites, client/provider setup and a supplied first investigation. ([#1779](https://github.com/morluto/rea/pull/1779), [issue morluto/rea#1022](https://github.com/morluto/rea/issues/1022), [#1699](https://github.com/morluto/rea/pull/1699), [#1714](https://github.com/morluto/rea/pull/1714), [#1894](https://github.com/morluto/rea/pull/1894), [#2250](https://github.com/morluto/rea/pull/2250), [#2432](https://github.com/morluto/rea/pull/2432), [#2732](https://github.com/morluto/rea/pull/2732), [#3274](https://github.com/morluto/rea/pull/3274)). Thanks @CassieFox23, @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
|
||||||
|
- **Rust dependency metadata:** Inspect embedded cargo-auditable package/dependency records in ELF, PE and thin Mach-O without executing the file. Missing metadata and inferred compiler hashes retain explicit limitations; universal Mach-O requires a selected thin slice. ([#2456](https://github.com/morluto/rea/pull/2456)). Thanks @mvanhorn.
|
||||||
|
- **Electron fuses:** Read packaged fuse bytes and per-slice runtime implications without launching or modifying the app. Missing, truncated and unknown wires remain explicit; fuse observations do not prove other Inspector-enablement paths. ([#2399](https://github.com/morluto/rea/pull/2399)). Thanks @kaoru0822-kitauji, @mvanhorn, @N0zoM1z0.
|
||||||
|
- **Ghidra PowerPC:** Admit bounded ELF32 big-endian `ET_EXEC` PowerPC with an explicit language profile on supported Linux/macOS hosts. PPC64, DOL and other variants remain outside this lane; see [the profile and verification prerequisites](docs/ghidra-ppc.md). ([#1747](https://github.com/morluto/rea/pull/1747)). Thanks @arielfikru.
|
||||||
|
|
||||||
|
- **Reference-source summaries:** Use CLI `import-reference-source --detail summary` for compact manifest, count, state and path-outline output. The complete graph remains available and is still the default; hidden outline entries retain separate coverage counts. ([#2768](https://github.com/morluto/rea/pull/2768), [issue morluto/rea#2442](https://github.com/morluto/rea/issues/2442)). Thanks @matbrgz, @To3akaRin.
|
||||||
|
|
||||||
|
- **Recorded AArch64 crashes:** Decode 34 ABI register values per thread from supported Linux ELF64 little-endian core notes using the documented caller-provided Python dependencies. These are recorded facts, not proof of current process or library identity. ([#2505](https://github.com/morluto/rea/pull/2505), [issue morluto/rea#2353](https://github.com/morluto/rea/issues/2353)). Thanks @matbrgz, @NIU-123370.
|
||||||
|
|
||||||
|
- **Ghidra byte search:** Search initialized analyzed memory using wildcards, bit masks, address bounds and paged results. Unloaded, overlay and uninitialized blocks retain skipped coverage; this is not a raw-file search. ([#2341](https://github.com/morluto/rea/pull/2341)). Thanks @mvanhorn.
|
||||||
|
|
||||||
|
- **Raw NMOS 6502 images:** Analyze explicitly selected headerless images with Ghidra, including Windows P0, using a caller-selected entry and source mapping within the 16-bit address space. Cartridge parsing, reset-vector inference and 65C02/65816 are outside this profile. ([#2079](https://github.com/morluto/rea/pull/2079)). Thanks @Zhong-fan.
|
||||||
|
|
||||||
|
- **Swift String observations:** Ghidra function dossiers derive supported AArch64 Swift String literals from constant-register pairs at recognized use sites, retaining instruction/register provenance and explicit omissions. This is a bounded derivation, not a complete string inventory. ([#2049](https://github.com/morluto/rea/pull/2049), [issue morluto/rea#2030](https://github.com/morluto/rea/issues/2030)). Thanks @thefolahan.
|
||||||
|
|
||||||
|
- **Headless IDA procedure lookup:** Resolve addresses to canonical containing-procedure entries and identify the session’s admitted program document in the modern headless profile. Incomplete surveys remain errors; attached profiles do not advertise this containment capability. ([#2374](https://github.com/morluto/rea/pull/2374), [issue morluto/rea#2335](https://github.com/morluto/rea/issues/2335)). Thanks @nikolay-dementiev.
|
||||||
|
|
||||||
|
- **Passive browser attachment:** Six existing passive browser tools accept an explicit-port loopback browser WebSocket endpoint when HTTP target discovery is unavailable. Existing consent and target/origin checks remain required; this does not launch a browser or enable runtime instrumentation. ([#2346](https://github.com/morluto/rea/pull/2346)). Thanks @all-black-493.
|
||||||
|
|
||||||
|
- **Android DEX references:** Retain exact method descriptors and individual direct-invoke observations with source method, DEX entry, opcode and code-unit offset. These are static references; unavailable instruction facts remain separate from method-level usage totals. ([#2439](https://github.com/morluto/rea/pull/2439)). Thanks @LcokHcak.
|
||||||
|
|
||||||
|
- **Offline EVM metadata:** Retain bounded, lossless facts from plausible terminal Solidity CBOR root-map candidates. Candidates do not prove Solidity origin or source/compiler identity, and linked metadata is not fetched. ([#2790](https://github.com/morluto/rea/pull/2790)). Thanks @matbrgz.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- **JavaScript loader and export identity:** Respect import/require conditions and unavailable package metadata, keep export links separate from writes, and resolve default identifier exports to the value captured at the export statement. ([7e5cffa](https://github.com/morluto/rea/commit/7e5cffa2ae9dfc9afaeca0715110d4dd41c0dcdd), [322506d](https://github.com/morluto/rea/commit/322506d0c0d39d3cd1e2e1c285c2cc23cfa73ee7), [81f7638](https://github.com/morluto/rea/commit/81f763820319a762f003cf95651ed14ce9326a4c), [dae1e55](https://github.com/morluto/rea/commit/dae1e55381f0d6cfee7f2082d5d27b938e55704c)). Thanks @paoValle, @VarunGore36, @xu91102.
|
||||||
|
- **JavaScript traces:** Retain nested bridge calls and surviving early-return predicates. Honor node-ID match options, match route seeds against source values, and keep ambiguous runtime candidates unknown. ([0351328](https://github.com/morluto/rea/commit/0351328e54ce70ea3f2814482e76b132774aff48), [5031099](https://github.com/morluto/rea/commit/503109995f9dcf8a955585d39a344fc846d2f59e), [44c9d29](https://github.com/morluto/rea/commit/44c9d2924d5b95d7a86595e8d7b7cf4fd4f0f6ac), [5e79d46](https://github.com/morluto/rea/commit/5e79d468e5864204cbe79a7ca2898541cee5e012)). Thanks @jpdefo.
|
||||||
|
- **Browser comparison coverage:** Compare only known redirect response fields and retain network-selection recovery guidance alongside independent event changes. Missing response facts do not prove equality or a change. ([2b60e09](https://github.com/morluto/rea/commit/2b60e097f281f7708c69f1f7ba9263694547bcc0)).
|
||||||
|
- **Evidence comparison and ledger input:** Report display-only bundle differences as unchanged while retaining digests, and classify noncanonical bundle records as invalid caller input. ([030c477](https://github.com/morluto/rea/commit/030c47732833c9cedcc31db1309eb3a1ca2c62e8), [3a34ad3](https://github.com/morluto/rea/commit/3a34ad37bdb7b8debba0f6515ba85b80141f2716)).
|
||||||
|
- **Capture and admission cleanup:** Keep deleted-file observations and original bytes when a parent becomes a file, preserve unrelated I/O failures during cancellation, and require explicit pre-launch proof before releasing an unknown LLDB target owner. LLDB failure controls used simulated boundaries. ([e67c364](https://github.com/morluto/rea/commit/e67c3642c8920a2a9c0e231ee2f16f50b245df31), [2daeff4](https://github.com/morluto/rea/commit/2daeff4b243e1cd137c591ca394e18a1c05c8bbc), [45b6a7f](https://github.com/morluto/rea/commit/45b6a7fe8f51a89951aaa823ffa627f6f5453a33)). Thanks @SII-YDD, @xu91102.
|
||||||
|
- **Client configuration:** Validate MCP output-schema profiles before setup writes, ignore untrusted Gemini workspace settings before parsing them, and identify installed skills independently of marker-only client roots. ([0930e94](https://github.com/morluto/rea/commit/0930e94e077ab7fb4d695f62785919a8a25c2e21), [aade9bb](https://github.com/morluto/rea/commit/aade9bbcb79aa8504ab6921b25f8ab082aba78e7), [ba33c70](https://github.com/morluto/rea/commit/ba33c706d26b63e43308be50c0249c72cff62cc6)). Thanks @CmsChase, @paoValle, @sx4im.
|
||||||
|
- **Host tool discovery:** Share macOS Hopper launcher discovery across setup and runtime, preserve explicit paths, and report validated ADB and Apktool availability. ([7582f0d](https://github.com/morluto/rea/commit/7582f0dff29ae0e80c97b885ddd386ac61a92570), [e5deb83](https://github.com/morluto/rea/commit/e5deb832cb09bb524877d0df40f49d5b5bb7dea4)). Thanks @Chessing234, @kaoru0822-kitauji, @viniciusdsandrade.
|
||||||
|
- **SAZ encoding validation:** Reject invalid XML bytes, unsupported encodings and declarations that contradict the archive bytes, including records outside the selected request. ([1250aae](https://github.com/morluto/rea/commit/1250aaedd8b4d1e471d8a382f76418bde65c05d2)).
|
||||||
|
- **Managed member matching:** Keep malformed TypeDef list ownership unknown, distinguish constants during structural method matching, and accept legacy small exception-section sizes. ([3f6b575](https://github.com/morluto/rea/commit/3f6b5755b3e699bd9373641acffe0f2c5d2ab094), [ee5cfbe](https://github.com/morluto/rea/commit/ee5cfbe05cd309a88003c711721e67a3bee36554)).
|
||||||
|
- **Bounded diagnostics:** Bound SDK validation messages and terminal normalization before allocation while retaining diagnostics and explicit unknown final output sizes. ([7fb0a0c](https://github.com/morluto/rea/commit/7fb0a0cc577f7c9033a2674d877d8242b5864556), [1d826f3](https://github.com/morluto/rea/commit/1d826f304f032acfe69dad3ff824b300aacaeb43)). Thanks @CRYPTONIKAV.
|
||||||
|
|
||||||
|
- **Windows artifact classification:** identify `.exe`, `.com`, `.bat` and `.cmd` artifacts as launchable host executables. ([#1823](https://github.com/morluto/rea/pull/1823), [#1824](https://github.com/morluto/rea/pull/1824), [issue morluto/rea#1819](https://github.com/morluto/rea/issues/1819)). Thanks @dollarsmoney, @JasonBuildAI, @kaoru0822-kitauji.
|
||||||
|
|
||||||
|
- **CLI/MCP delivery:** defer command-specific startup work, bundle runtime entries, retain field/input correction details across output formats and preserve shared optional-provider lifetime. MCP selectors, paging continuation and retained partial-error references remain explicit; session liveness/provider-attribution corrections retain their controlled-provider verification limits. ([#1691](https://github.com/morluto/rea/pull/1691), [issue morluto/rea#1685](https://github.com/morluto/rea/issues/1685), [#1705](https://github.com/morluto/rea/pull/1705), [issue morluto/rea#1695](https://github.com/morluto/rea/issues/1695), [#1755](https://github.com/morluto/rea/pull/1755), [issue morluto/rea#1746](https://github.com/morluto/rea/issues/1746), [#1773](https://github.com/morluto/rea/pull/1773), [issue morluto/rea#1771](https://github.com/morluto/rea/issues/1771), [#1780](https://github.com/morluto/rea/pull/1780), [#1814](https://github.com/morluto/rea/pull/1814), [#1910](https://github.com/morluto/rea/pull/1910), [issue morluto/rea#1849](https://github.com/morluto/rea/issues/1849), [#1996](https://github.com/morluto/rea/pull/1996), [#2140](https://github.com/morluto/rea/pull/2140), [issue morluto/rea#2135](https://github.com/morluto/rea/issues/2135), [#2205](https://github.com/morluto/rea/pull/2205), [issue morluto/rea#2195](https://github.com/morluto/rea/issues/2195), [#2230](https://github.com/morluto/rea/pull/2230), [issue morluto/rea#2180](https://github.com/morluto/rea/issues/2180), [#2294](https://github.com/morluto/rea/pull/2294), [#2482](https://github.com/morluto/rea/pull/2482), [a82d14e](https://github.com/morluto/rea/commit/a82d14e4db4dca989ffa2e7dbe78fd43551da256), [a4dd4e8](https://github.com/morluto/rea/commit/a4dd4e8e8700ce350e1a0c612ebd61b91ca5e585), [48c352b](https://github.com/morluto/rea/commit/48c352b6bfa34c820a530272cd36600158c37b1e), [07b36c1](https://github.com/morluto/rea/commit/07b36c148946f46b99a4ce04e55726e97d0ee03a), [570c40c](https://github.com/morluto/rea/commit/570c40cde55d31e455ef9bc484b7b908658ded58), [8fc2043](https://github.com/morluto/rea/commit/8fc2043eb8bcedbf34dce064f977e455438635d6), [7fc0b5e](https://github.com/morluto/rea/commit/7fc0b5ec9c2532bbf789d7783b7a0cab6fc054d8)). Thanks @369Apps, @chqcjq, @cold-atom, @coygeek, @JackOdegard, @kaoru0822-kitauji, @N0zoM1z0, @sx4im.
|
||||||
|
- **Evidence import/replay:** import bundles across session transitions while authenticating selected target and file identities, preserve literal keys, metadata/mutation intent and relationship coverage, and honor cancellation before session mutation. Reuse authenticated snapshots and comparisons within their owned lifetime; reject malformed/non-object snapshots and unsupported metadata without disguising uncertainty. ([#1694](https://github.com/morluto/rea/pull/1694), [#1716](https://github.com/morluto/rea/pull/1716), [issue morluto/rea#1680](https://github.com/morluto/rea/issues/1680), [#1721](https://github.com/morluto/rea/pull/1721), [issue morluto/rea#1718](https://github.com/morluto/rea/issues/1718), [#1733](https://github.com/morluto/rea/pull/1733), [issue morluto/rea#1723](https://github.com/morluto/rea/issues/1723), [issue morluto/rea#1724](https://github.com/morluto/rea/issues/1724), [#1776](https://github.com/morluto/rea/pull/1776), [#1873](https://github.com/morluto/rea/pull/1873), [#2001](https://github.com/morluto/rea/pull/2001), [#2013](https://github.com/morluto/rea/pull/2013), [issue morluto/rea#2009](https://github.com/morluto/rea/issues/2009), [#2083](https://github.com/morluto/rea/pull/2083), [#2303](https://github.com/morluto/rea/pull/2303), [#2685](https://github.com/morluto/rea/pull/2685), [issue morluto/rea#2675](https://github.com/morluto/rea/issues/2675), [c002b89](https://github.com/morluto/rea/commit/c002b89e2d8130a3c07e25e5fd604876be18c703), [9c85114](https://github.com/morluto/rea/commit/9c8511409c0706f9979c2b68dd56aaaf9f21e191), [aada0ca](https://github.com/morluto/rea/commit/aada0ca8126bf14c60750a4ccd4a79667770a1e4), [4a4d36f](https://github.com/morluto/rea/commit/4a4d36f881861bbab3c734b0dd459f805766caec), [1c64fe3](https://github.com/morluto/rea/commit/1c64fe3eff48679c6880f42d2f0a4af46686ade7), [6fb3a34](https://github.com/morluto/rea/commit/6fb3a345fe952b8e8f59221aec993acc37ccbf31), [3bcc68c](https://github.com/morluto/rea/commit/3bcc68c037e9415c78093cb286069c1fcca6a2f8), [6bae774](https://github.com/morluto/rea/commit/6bae774328724403255590aa2ddad4d7a2aa727f), [e997085](https://github.com/morluto/rea/commit/e9970855ba16ba711816ac30212c5ca17ae085cd), [d1dcebc](https://github.com/morluto/rea/commit/d1dcebce2c0820469ed63e33497a14258971b710), [b123b2e](https://github.com/morluto/rea/commit/b123b2e51cc7d6d2157525664be2df88fb1ba3bd), [049132a](https://github.com/morluto/rea/commit/049132ab43fa962d31774f2b258c15a71217148b), [042dcaa](https://github.com/morluto/rea/commit/042dcaa1ee66f1b8668f08b193f38ab01bceaacb), [acf75e6](https://github.com/morluto/rea/commit/acf75e6700ace28bb1db25e54e56673ac15e0ed3), [1f7f808](https://github.com/morluto/rea/commit/1f7f808c4f3bb5eca6b13bcc71d5cceb2ace6614), [26bffb9](https://github.com/morluto/rea/commit/26bffb94bf80355c35e22a98a8fc629c6210307e)). Thanks @coygeek, @DeryFerd, @jiangjiang248, @kaoru0822-kitauji, @khanukov, @N0zoM1z0, @NIU-123370, @rohitg00, @sx4im.
|
||||||
|
- **Ghidra setup and analysis:** preserve launcher/JVM diagnostics, Java-property and extension identity, stale-client recovery, UTF-8 bridge output and pending-startup progress. File-offset decompilation is Ghidra-specific; recorded coverage is Linux x64 with Ghidra 12.1.4. Startup/progress/compiler failure paths that use simulations remain unverified with a real engine. ([#1698](https://github.com/morluto/rea/pull/1698), [#1877](https://github.com/morluto/rea/pull/1877), [#1931](https://github.com/morluto/rea/pull/1931), [#2011](https://github.com/morluto/rea/pull/2011), [issue morluto/rea#1992](https://github.com/morluto/rea/issues/1992), [#2014](https://github.com/morluto/rea/pull/2014), [#2101](https://github.com/morluto/rea/pull/2101), [issue morluto/rea#2078](https://github.com/morluto/rea/issues/2078), [#2206](https://github.com/morluto/rea/pull/2206), [issue morluto/rea#2204](https://github.com/morluto/rea/issues/2204), [2187e16](https://github.com/morluto/rea/commit/2187e16f149a7a7b512a4cb45d5f468cc04091fe), [b953f96](https://github.com/morluto/rea/commit/b953f96c0aae27e1876c317a8f7db1d6f292b8ff)). Thanks @EDMND-SRC, @Ha1baraA11, @kaoru0822-kitauji, @lovecontroing, @lyly-bonbon, @N0zoM1z0, @nosmile99, @Petru101, @sx4im, @Zhong-fan.
|
||||||
|
- **Process and cleanup ownership:** retain private runtime/file owners and detailed incomplete-cleanup observations, bound trace cycles/order state, preserve caller paths, and use monotonic time for budgets while retaining wall dates. Windows affinity/admission ([#2202](https://github.com/morluto/rea/pull/2202), [issue morluto/rea#2181](https://github.com/morluto/rea/issues/2181)) and Bun memory-enforcement paths retain their recorded native-versus-controlled-fixture limits; these fixes are not a general process-ownership guarantee. ([#1700](https://github.com/morluto/rea/pull/1700), [issue morluto/rea#1697](https://github.com/morluto/rea/issues/1697), [#1730](https://github.com/morluto/rea/pull/1730), [#1756](https://github.com/morluto/rea/pull/1756), [issue morluto/rea#1753](https://github.com/morluto/rea/issues/1753), [#1760](https://github.com/morluto/rea/pull/1760), [issue morluto/rea#1757](https://github.com/morluto/rea/issues/1757), [#1826](https://github.com/morluto/rea/pull/1826), [#1907](https://github.com/morluto/rea/pull/1907), [issue morluto/rea#1856](https://github.com/morluto/rea/issues/1856), [#1999](https://github.com/morluto/rea/pull/1999), [issue morluto/rea#1993](https://github.com/morluto/rea/issues/1993), [#2232](https://github.com/morluto/rea/pull/2232), [issue morluto/rea#2170](https://github.com/morluto/rea/issues/2170), [#2252](https://github.com/morluto/rea/pull/2252), [issue morluto/rea#2218](https://github.com/morluto/rea/issues/2218), [#2467](https://github.com/morluto/rea/pull/2467), [issue morluto/rea#2257](https://github.com/morluto/rea/issues/2257), [#2468](https://github.com/morluto/rea/pull/2468), [#2488](https://github.com/morluto/rea/pull/2488), [09b6828](https://github.com/morluto/rea/commit/09b68285166bef2b7633402c8adf7eb25823b47e), [7285e15](https://github.com/morluto/rea/commit/7285e15839a4fecf9940683699b781a60ccc4775), [b5ededd](https://github.com/morluto/rea/commit/b5ededd0f9c28d0f696a205103066928117368ee), [d8c76b6](https://github.com/morluto/rea/commit/d8c76b629639bda4405afa9ae2f7676b694f2f10), [98d8470](https://github.com/morluto/rea/commit/98d8470d36cfbf7cd25820a4ed430aebae20c0c5), [c22111e](https://github.com/morluto/rea/commit/c22111eca2c4bc84a084f12711f405b3e7422a40), [cecc6ec](https://github.com/morluto/rea/commit/cecc6ecbffe84689a173d1634f342e046b0c8ceb), [10935b7](https://github.com/morluto/rea/commit/10935b7b1136accab2a43b308ab380749da0f7ae), [63e796d](https://github.com/morluto/rea/commit/63e796d50ea323ae8dcfef80f86c8154bb106790), [d9e9fb2](https://github.com/morluto/rea/commit/d9e9fb2273543caab4eb21d896c33d462ada6971), [9049dba](https://github.com/morluto/rea/commit/9049dbaf0c3df32357419f9899965df2b611f493), [ed881c6](https://github.com/morluto/rea/commit/ed881c6d2ef6a8adba0c2a241ebc9ab4603b7868)). Thanks @Ashen00001, @charan-rathore, @coygeek, @EDMND-SRC, @JasonBuildAI, @jimpablo, @kaoru0822-kitauji, @N0zoM1z0, @NIU-123370, @Rinkia, @rudycelekli, @sx4im, @xeropresence, @XiaoBinGan, @xthunder0, @hzh666kevin-hue.
|
||||||
|
- **Native image identity:** preserve universal Mach-O containers and selected slice/subtype identity, chained fixup locations and library ordinals, exact signing text, PE resource capacity and ELF/header framing. Mach-O summary selection and independent slice commands avoid unnecessary delivery/work. Controlled routing/parser fixtures do not establish every installed-provider path. ([#1703](https://github.com/morluto/rea/pull/1703), [issue morluto/rea#1702](https://github.com/morluto/rea/issues/1702), [#1708](https://github.com/morluto/rea/pull/1708), [#1709](https://github.com/morluto/rea/pull/1709), [#1713](https://github.com/morluto/rea/pull/1713), [#1772](https://github.com/morluto/rea/pull/1772), [issue morluto/rea#1765](https://github.com/morluto/rea/issues/1765), [#1784](https://github.com/morluto/rea/pull/1784), [issue morluto/rea#1775](https://github.com/morluto/rea/issues/1775), [#1982](https://github.com/morluto/rea/pull/1982), [#1995](https://github.com/morluto/rea/pull/1995), [issue morluto/rea#1990](https://github.com/morluto/rea/issues/1990), [#2040](https://github.com/morluto/rea/pull/2040), [#2107](https://github.com/morluto/rea/pull/2107), [#2161](https://github.com/morluto/rea/pull/2161), [issue morluto/rea#2105](https://github.com/morluto/rea/issues/2105), [#2248](https://github.com/morluto/rea/pull/2248), [issue morluto/rea#2176](https://github.com/morluto/rea/issues/2176), [#2281](https://github.com/morluto/rea/pull/2281), [issue morluto/rea#2274](https://github.com/morluto/rea/issues/2274), [#2292](https://github.com/morluto/rea/pull/2292), [#2466](https://github.com/morluto/rea/pull/2466), [#2506](https://github.com/morluto/rea/pull/2506), [#2733](https://github.com/morluto/rea/pull/2733), [issue morluto/rea#2661](https://github.com/morluto/rea/issues/2661), [f598bab](https://github.com/morluto/rea/commit/f598bab488e38c378d69346fa3c106f9e18529ed), [108b5f9](https://github.com/morluto/rea/commit/108b5f94254281639badd25b1f448cca35dc89a2), [f1924cf](https://github.com/morluto/rea/commit/f1924cf9c1910d5d29d11e7bf634b51af8d738cf), [8bd5ba2](https://github.com/morluto/rea/commit/8bd5ba2d234cbd9a5bb9dd5daeaa191c3a1cad24), [b38429c](https://github.com/morluto/rea/commit/b38429cb6a0de8f4bb5ae01633e24abe2d7cde46), [8d4d631](https://github.com/morluto/rea/commit/8d4d631606c701aac150cf009d3b80ae4a7ae7d5), [ac57c88](https://github.com/morluto/rea/commit/ac57c88efe64c3125850b54d3d833227e6b8ec51), [dc038d1](https://github.com/morluto/rea/commit/dc038d16bba9046afd28970de9c47f6259a43524), [34a4bb6](https://github.com/morluto/rea/commit/34a4bb6508006fa113463ef20ed799a2994e9ee8), [ebc22d9](https://github.com/morluto/rea/commit/ebc22d99d62225505c0a84f6e39823ba4ef2f829), [c02fb0e](https://github.com/morluto/rea/commit/c02fb0ea8055f98def2b12ad557aed0faefd3639)). Thanks @1NormalGuy, @Aliciawque, @baron, @coygeek, @dhl84, @hemanthrayuduu, @JasonBuildAI, @kaoru0822-kitauji, @N0zoM1z0, @thangldw, @viniciusdsandrade, @XiaoBinGan, @Zburgers.
|
||||||
|
- **Archive and artifact provenance:** keep physical ZIP occurrence identity, exact signatures/names and partial inventory coverage; bound decompression and reject malformed keys/integers. Directory/ASAR/DMG reads retain ownership through cleanup and publication cancellation. Duplicate ZIP names remain explicitly ambiguous where the public projection cannot represent them; synthetic archive/cleanup cases do not prove every real Unblob or DMG failure path. ([#1726](https://github.com/morluto/rea/pull/1726), [issue morluto/rea#1725](https://github.com/morluto/rea/issues/1725), [#1854](https://github.com/morluto/rea/pull/1854), [issue morluto/rea#1844](https://github.com/morluto/rea/issues/1844), [#1865](https://github.com/morluto/rea/pull/1865), [issue morluto/rea#1860](https://github.com/morluto/rea/issues/1860), [#2069](https://github.com/morluto/rea/pull/2069), [#2106](https://github.com/morluto/rea/pull/2106), [#2144](https://github.com/morluto/rea/pull/2144), [#2365](https://github.com/morluto/rea/pull/2365), [#2493](https://github.com/morluto/rea/pull/2493), [918713a](https://github.com/morluto/rea/commit/918713af376e307ecf24d75e6a0dd9d1b544545b), [eb97da3](https://github.com/morluto/rea/commit/eb97da33881e3d1472d97a7d788ee35944fb6b07), [2260887](https://github.com/morluto/rea/commit/2260887f2092be6a3d9c79509e4ae4a3e52fce7f), [74d7da6](https://github.com/morluto/rea/commit/74d7da6ced0f869936ee0316b00616931b088066), [ed485a3](https://github.com/morluto/rea/commit/ed485a3dc8cc3a4baf8cabe01acb7aa0490b73b3), [921bb95](https://github.com/morluto/rea/commit/921bb9575505065f6cfd57b14d97d80d0f238546), [d8ceb3f](https://github.com/morluto/rea/commit/d8ceb3f276a65c181b146f68c0729ff4b6f9fa61), [9435c7a](https://github.com/morluto/rea/commit/9435c7aae279ba184937c0dfeb713fd574ba3f29), [bb54082](https://github.com/morluto/rea/commit/bb540826d65316c164d2de8ba4ef10f81a8ad073), [a73a55f](https://github.com/morluto/rea/commit/a73a55f53cc2b2e5b7618effe6ecb5cee6e0cee2), [3c57e1a](https://github.com/morluto/rea/commit/3c57e1a8b21b379fca55032d177078e1d8426e64), [ec0b081](https://github.com/morluto/rea/commit/ec0b081371f118e6e5e042fbef3479359c672ddb), [e7882b3](https://github.com/morluto/rea/commit/e7882b34f4ac4a26f903c5f49264acc2c19bb562), [b955b82](https://github.com/morluto/rea/commit/b955b825fc002a4bb408f2910b78768c566c2cd1)). Thanks @1052326311, @1NormalGuy, @baron, @Bortlesboat, @Chaoqi31, @DeryFerd, @kaoru0822-kitauji, @N0zoM1z0, @XiaoBinGan.
|
||||||
|
- **Setup/uninstall recovery:** preserve unrelated YAML text, caller-selected roots, symlinked data and existing recovery snapshots; explain malformed configuration shapes and retain failed rollback/cleanup owners. Show terminal dry-run plans and each selected skill location. Bunx registration and fault-injected recovery cases remain simulated rather than live-client verification. ([#1731](https://github.com/morluto/rea/pull/1731), [#1754](https://github.com/morluto/rea/pull/1754), [#1821](https://github.com/morluto/rea/pull/1821), [issue morluto/rea#1816](https://github.com/morluto/rea/issues/1816), [#1983](https://github.com/morluto/rea/pull/1983), [#2118](https://github.com/morluto/rea/pull/2118), [issue morluto/rea#2111](https://github.com/morluto/rea/issues/2111), [#2154](https://github.com/morluto/rea/pull/2154), [issue morluto/rea#2152](https://github.com/morluto/rea/issues/2152), [#2157](https://github.com/morluto/rea/pull/2157), [#2182](https://github.com/morluto/rea/pull/2182), [issue morluto/rea#2159](https://github.com/morluto/rea/issues/2159), [#2481](https://github.com/morluto/rea/pull/2481), [bb50813](https://github.com/morluto/rea/commit/bb508134efbba392a404b651d6ec05be8b7eac1e), [c240768](https://github.com/morluto/rea/commit/c2407689cb04df082e8db0b6afa8f8d022f6bcd1), [dcfc5d4](https://github.com/morluto/rea/commit/dcfc5d403ac997c2b08bee4753e57a1a4a8a8538), [21eff3b](https://github.com/morluto/rea/commit/21eff3bae10f58711a35ad44f86e9f96f9bb4790), [94153f1](https://github.com/morluto/rea/commit/94153f1c757e07a6d092e00903aa8b125d4a0afa), [2397137](https://github.com/morluto/rea/commit/23971376158e7815edcbe6c04a8df7c9504650dc), [c339f9e](https://github.com/morluto/rea/commit/c339f9ebe7bd1f30608d6ccb7175da523dd3c485), [f6775a3](https://github.com/morluto/rea/commit/f6775a36694a6a9c620f4248638e8056a6cbcb66)). Thanks @BeqaKs, @Jax367, @kaoru0822-kitauji, @NIU-123370, @shoyann, @viniciusdsandrade, @xeropresence, @zaydiscold.
|
||||||
|
- **Bun updates:** locate global installations before changing directories and reuse parsed client activation during maintenance. The update flow is covered with package-manager/filesystem simulations; a real Bun global installation update remains unverified. ([#1735](https://github.com/morluto/rea/pull/1735), [e90acae](https://github.com/morluto/rea/commit/e90acaea674f5f7f9f17ea472104afdc1422ada6), [b045df1](https://github.com/morluto/rea/commit/b045df100e1489e08a36d6ff04cea7feecf1332c)). Thanks @xeropresence.
|
||||||
|
- **JavaScript relationships:** preserve loader-specific package/TypeScript/CommonJS boundaries, exact file-URL and source-map identity, Electron intrinsic receivers/IPC guards and chunk metadata. Index repeated lookups and retain completed facts through resource limits and cancellation. Real Electron integration, Bun worker integration and failure-path cleanup remain unverified where the recorded lanes use fixtures. ([#1740](https://github.com/morluto/rea/pull/1740), [#1759](https://github.com/morluto/rea/pull/1759), [issue morluto/rea#1748](https://github.com/morluto/rea/issues/1748), [#1762](https://github.com/morluto/rea/pull/1762), [issue morluto/rea#1761](https://github.com/morluto/rea/issues/1761), [#1767](https://github.com/morluto/rea/pull/1767), [#1788](https://github.com/morluto/rea/pull/1788), [issue morluto/rea#1781](https://github.com/morluto/rea/issues/1781), [issue morluto/rea#1782](https://github.com/morluto/rea/issues/1782), [#1789](https://github.com/morluto/rea/pull/1789), [#1799](https://github.com/morluto/rea/pull/1799), [issue morluto/rea#1793](https://github.com/morluto/rea/issues/1793), [#1827](https://github.com/morluto/rea/pull/1827), [issue morluto/rea#1802](https://github.com/morluto/rea/issues/1802), [#1833](https://github.com/morluto/rea/pull/1833), [issue morluto/rea#1831](https://github.com/morluto/rea/issues/1831), [#1857](https://github.com/morluto/rea/pull/1857), [issue morluto/rea#1842](https://github.com/morluto/rea/issues/1842), [#1871](https://github.com/morluto/rea/pull/1871), [#1874](https://github.com/morluto/rea/pull/1874), [#1905](https://github.com/morluto/rea/pull/1905), [#1912](https://github.com/morluto/rea/pull/1912), [issue morluto/rea#1909](https://github.com/morluto/rea/issues/1909), [#1915](https://github.com/morluto/rea/pull/1915), [issue morluto/rea#1911](https://github.com/morluto/rea/issues/1911), [#1916](https://github.com/morluto/rea/pull/1916), [#1921](https://github.com/morluto/rea/pull/1921), [#1934](https://github.com/morluto/rea/pull/1934), [#1939](https://github.com/morluto/rea/pull/1939), [issue morluto/rea#1881](https://github.com/morluto/rea/issues/1881), [issue morluto/rea#1882](https://github.com/morluto/rea/issues/1882), [issue morluto/rea#1883](https://github.com/morluto/rea/issues/1883), [#1941](https://github.com/morluto/rea/pull/1941), [#2062](https://github.com/morluto/rea/pull/2062), [issue morluto/rea#2058](https://github.com/morluto/rea/issues/2058), [#2104](https://github.com/morluto/rea/pull/2104), [#2422](https://github.com/morluto/rea/pull/2422), [#2475](https://github.com/morluto/rea/pull/2475), [#2737](https://github.com/morluto/rea/pull/2737), [issue morluto/rea#2663](https://github.com/morluto/rea/issues/2663), [d3088c3](https://github.com/morluto/rea/commit/d3088c3795f6c37cb3b6aec0bb0c371dd0640ee8), [1dfb933](https://github.com/morluto/rea/commit/1dfb933e47db0156cf1cb61f28715b81e740c57b), [f8513a4](https://github.com/morluto/rea/commit/f8513a42dc44451a621127f06edde5d38bc93f69), [b8fab74](https://github.com/morluto/rea/commit/b8fab7429771bb17a7ca0297661de3c9935212af), [42b0ed1](https://github.com/morluto/rea/commit/42b0ed126df7d5b908ac369715378d5e519051b7), [c0904b4](https://github.com/morluto/rea/commit/c0904b4c1aabfccf2f0ab12f44960a0a1fbbbfae), [9f67e44](https://github.com/morluto/rea/commit/9f67e44e5d451d705bae9a579ad9802d5c9f097c), [e4532b5](https://github.com/morluto/rea/commit/e4532b537663026c3e5b784380eecd009c959a36), [1c37914](https://github.com/morluto/rea/commit/1c37914306b3ff257d7a0360f11c9bf2a3f2cac7), [5ab0cbb](https://github.com/morluto/rea/commit/5ab0cbb48ea5a4f11542eac44479b9af48f1dabd), [b7169c8](https://github.com/morluto/rea/commit/b7169c8b747cbc032913b31f008a3b81c77b6219), [f43fc02](https://github.com/morluto/rea/commit/f43fc0247527aebb8efc7d9db04276fe2d489323), [3ae826c](https://github.com/morluto/rea/commit/3ae826ceba6f25b08bce50bcb51001209bc16b57), [1b95302](https://github.com/morluto/rea/commit/1b953021eadf4cac09e20be388d79536903633b3), [eaa9616](https://github.com/morluto/rea/commit/eaa961658578522cecfbba7c7be1acf1c64da65a), [c923fb0](https://github.com/morluto/rea/commit/c923fb0dc9462e302b84a15770131ff899f89455), [c7a890d](https://github.com/morluto/rea/commit/c7a890d12987d9b2d496119c1fbf1a88c0fc298e)). Thanks @Aurantium-47, @baron, @chqcjq, @coygeek, @cyanseek, @fengdwx, @flitzrrr, @immanuwell, @kaoru0822-kitauji, @N0zoM1z0, @Nelglor, @ranmori, @rudycelekli, @shoyann, @xu91102.
|
||||||
|
- **Browser observations:** preserve effective source-map URLs, empty query/fragment identity, response-body provenance, service-worker policies and explicit uncertainty for malformed events. Bound discovery lifetimes and retain cleanup ownership. Interrupt/retry/failure cases validated with controlled CDP/session fixtures do not establish those paths in real Chromium. ([#1742](https://github.com/morluto/rea/pull/1742), [issue morluto/rea#1729](https://github.com/morluto/rea/issues/1729), [#1829](https://github.com/morluto/rea/pull/1829), [#2074](https://github.com/morluto/rea/pull/2074), [#2099](https://github.com/morluto/rea/pull/2099), [#2239](https://github.com/morluto/rea/pull/2239), [issue morluto/rea#2169](https://github.com/morluto/rea/issues/2169), [#2477](https://github.com/morluto/rea/pull/2477), [486b567](https://github.com/morluto/rea/commit/486b56724c4eaba50719177bcb9eae84d6907297), [a82e37c](https://github.com/morluto/rea/commit/a82e37c8065cc200a0ca3e14e7c4f85e4e468580), [a8d4984](https://github.com/morluto/rea/commit/a8d4984404b611e60b88bc42289c0ba07eee4578), [966f7cc](https://github.com/morluto/rea/commit/966f7cc84b4a8bb0043641cb64d7a7aaf8715e05), [221c856](https://github.com/morluto/rea/commit/221c85648108e9846d9920b0964dafa7f108d581), [951b095](https://github.com/morluto/rea/commit/951b095e11adf9faa9c4a27e212260060aa6b095), [f09bb8a](https://github.com/morluto/rea/commit/f09bb8a99773f79c7306b8799c86f856fe2c0d4b), [6bd2b28](https://github.com/morluto/rea/commit/6bd2b28161411f70ed5dc72e42c2736a1aab758b)). Thanks @1052326311, @chAng-L19, @chqcjq, @coygeek, @evenafterallisaid, @fisforfaheem, @itsF4LCON, @N0zoM1z0, @shayanxcyber.
|
||||||
|
- **Managed metadata/CIL:** preserve ExportedType and pointer-table ownership, generic/array/function-pointer/modifier signatures, ReadyToRun host identity, metadata ExtraData and signed zero. Charge retained facts and decoded instruction counts instead of broad decoding bounds; malformed layouts remain caller input errors. ([#1743](https://github.com/morluto/rea/pull/1743), [issue morluto/rea#1736](https://github.com/morluto/rea/issues/1736), [#1768](https://github.com/morluto/rea/pull/1768), [#1822](https://github.com/morluto/rea/pull/1822), [issue morluto/rea#1801](https://github.com/morluto/rea/issues/1801), [#1828](https://github.com/morluto/rea/pull/1828), [issue morluto/rea#1812](https://github.com/morluto/rea/issues/1812), [#1835](https://github.com/morluto/rea/pull/1835), [issue morluto/rea#1832](https://github.com/morluto/rea/issues/1832), [#1886](https://github.com/morluto/rea/pull/1886), [#1893](https://github.com/morluto/rea/pull/1893), [#1897](https://github.com/morluto/rea/pull/1897), [issue morluto/rea#1878](https://github.com/morluto/rea/issues/1878), [#1943](https://github.com/morluto/rea/pull/1943), [issue morluto/rea#1940](https://github.com/morluto/rea/issues/1940), [#1985](https://github.com/morluto/rea/pull/1985), [#2194](https://github.com/morluto/rea/pull/2194), [issue morluto/rea#2191](https://github.com/morluto/rea/issues/2191), [#2437](https://github.com/morluto/rea/pull/2437), [52e76c5](https://github.com/morluto/rea/commit/52e76c5d39b58d1685bb5e22bd40d19524dfa2cb)). Thanks @ac1982, @chqcjq, @fisHarly0, @flitzrrr, @JasonBuildAI, @JayOfTheKeyboard, @jhuang-tw, @kaoru0822-kitauji, @N0zoM1z0, @Nelglor.
|
||||||
|
- **Hopper failure ownership:** preserve startup/process outcomes, retain cleanup leases across one-shot failures, wait for late bridges, and stop owned Linux demo children when their REA owner exits. Denied/crashed/late-bridge/queued-request failure paths use simulated process or bridge fixtures; they are not fresh installed-Hopper verification. ([#1763](https://github.com/morluto/rea/pull/1763), [issue morluto/rea#1751](https://github.com/morluto/rea/issues/1751), [#1817](https://github.com/morluto/rea/pull/1817), [#1866](https://github.com/morluto/rea/pull/1866), [#1927](https://github.com/morluto/rea/pull/1927), [#2474](https://github.com/morluto/rea/pull/2474), [#2499](https://github.com/morluto/rea/pull/2499), [issue morluto/rea#2459](https://github.com/morluto/rea/issues/2459)). Thanks @coygeek, @Joshwong1908, @kaoru0822-kitauji, @N0zoM1z0, @rudycelekli.
|
||||||
|
- **IDA session boundaries:** retain non-x86 call coverage and unresolved-procedure uncertainty, and bound private universal-Mach-O inputs to the exact selected slice. Transient headless busy recovery was exercised on macOS arm64 IDA Pro 9.1; Linux headless, attached GUI navigation and worker-reaped cleanup paths retain their documented verification gaps. ([#1764](https://github.com/morluto/rea/pull/1764), [issue morluto/rea#1712](https://github.com/morluto/rea/issues/1712), [#1778](https://github.com/morluto/rea/pull/1778), [issue morluto/rea#1766](https://github.com/morluto/rea/issues/1766), [#1785](https://github.com/morluto/rea/pull/1785), [issue morluto/rea#1769](https://github.com/morluto/rea/issues/1769), [#1906](https://github.com/morluto/rea/pull/1906), [issue morluto/rea#1794](https://github.com/morluto/rea/issues/1794), [#2201](https://github.com/morluto/rea/pull/2201), [issue morluto/rea#2139](https://github.com/morluto/rea/issues/2139), [#2291](https://github.com/morluto/rea/pull/2291), [issue morluto/rea#2285](https://github.com/morluto/rea/issues/2285), [#2324](https://github.com/morluto/rea/pull/2324), [12fa8fd](https://github.com/morluto/rea/commit/12fa8fd9f986dbd4ff58165c3ec71a46dc8a8a3b)). Thanks @akushonkamen, @coygeek, @kaoru0822-kitauji, @N0zoM1z0, @nikolay-dementiev, @zecaro.
|
||||||
|
- **Input and capability diagnostics:** identify invalid paths, format refusals, missing host tools and non-retryable errors without losing caller selections or retryable capability facts. Foreign execution/device mutation annotations remain destructive, analysis-target text is untrusted, and output ordering follows code points rather than host locale. ([#1770](https://github.com/morluto/rea/pull/1770), [#1867](https://github.com/morluto/rea/pull/1867), [#1869](https://github.com/morluto/rea/pull/1869), [issue morluto/rea#1855](https://github.com/morluto/rea/issues/1855), [#1872](https://github.com/morluto/rea/pull/1872), [#1879](https://github.com/morluto/rea/pull/1879), [issue morluto/rea#1853](https://github.com/morluto/rea/issues/1853), [#1899](https://github.com/morluto/rea/pull/1899), [#1902](https://github.com/morluto/rea/pull/1902), [#1904](https://github.com/morluto/rea/pull/1904), [#1908](https://github.com/morluto/rea/pull/1908), [#1925](https://github.com/morluto/rea/pull/1925), [#1987](https://github.com/morluto/rea/pull/1987), [issue morluto/rea#1979](https://github.com/morluto/rea/issues/1979), [#2004](https://github.com/morluto/rea/pull/2004), [issue morluto/rea#2002](https://github.com/morluto/rea/issues/2002), [#2026](https://github.com/morluto/rea/pull/2026), [#2091](https://github.com/morluto/rea/pull/2091), [#2133](https://github.com/morluto/rea/pull/2133), [issue morluto/rea#2129](https://github.com/morluto/rea/issues/2129), [#2145](https://github.com/morluto/rea/pull/2145), [issue morluto/rea#2143](https://github.com/morluto/rea/issues/2143), [#2472](https://github.com/morluto/rea/pull/2472), [#2478](https://github.com/morluto/rea/pull/2478), [issue morluto/rea#2454](https://github.com/morluto/rea/issues/2454), [#2494](https://github.com/morluto/rea/pull/2494), [#2507](https://github.com/morluto/rea/pull/2507), [#2509](https://github.com/morluto/rea/pull/2509), [#2708](https://github.com/morluto/rea/pull/2708), [3d83135](https://github.com/morluto/rea/commit/3d8313598785359c424d7305f503cb69fc8639a6), [c9fed50](https://github.com/morluto/rea/commit/c9fed50691ae014890e91a72534773d2fb58ce44), [8b18171](https://github.com/morluto/rea/commit/8b181716aca2cb467be499cba226abfb5ae19141), [7a2cd1e](https://github.com/morluto/rea/commit/7a2cd1ea0a63e8c44166860b7ee9c7a1aa43492b)). Thanks @astralyx0129, @CassieFox23, @DeryFerd, @dollarsmoney, @fisforfaheem, @flitzrrr, @Ha1baraA11, @jimpablo, @kaoru0822-kitauji, @larinyo, @N0zoM1z0, @rudycelekli, @SII-YDD, @viniciusdsandrade, @XiaoBinGan.
|
||||||
|
- **Android analysis:** retain decoded values, duplicate-class inventories and executable static initializers, reject malformed JADX UTF-8, and keep inline search provenance. Apktool accepts `--no-strings` and explains host requirements. Windows batch-launcher selection also resolves PATH/PATHEXT before choosing its launch mode. Recorded Apktool coverage is Linux/OpenJDK, and changed JADX/batch failure paths do not establish native Windows behavior. ([#1777](https://github.com/morluto/rea/pull/1777), [issue morluto/rea#1774](https://github.com/morluto/rea/issues/1774), [#1876](https://github.com/morluto/rea/pull/1876), [issue morluto/rea#1846](https://github.com/morluto/rea/issues/1846), [#1890](https://github.com/morluto/rea/pull/1890), [issue morluto/rea#1850](https://github.com/morluto/rea/issues/1850), [#1946](https://github.com/morluto/rea/pull/1946), [issue morluto/rea#1945](https://github.com/morluto/rea/issues/1945), [#2707](https://github.com/morluto/rea/pull/2707), [issue morluto/rea#2672](https://github.com/morluto/rea/issues/2672), [0677468](https://github.com/morluto/rea/commit/0677468877465c8dfbe25c72f142a7b5bc8a3212), [fe8d0bd](https://github.com/morluto/rea/commit/fe8d0bdd36fe12bc0836468d1a702a34fbdc219b), [#2802](https://github.com/morluto/rea/pull/2802)). Thanks @akushonkamen, @coygeek, @HeadmasterEggy, @kaoru0822-kitauji, @N0zoM1z0, @psipher.
|
||||||
|
- **ADB acquisition:** preserve device paths and directory identity, prevent host destination collisions/overwrites, count UTF-8 logcat bytes and restricted permissions, and evaluate the complete Activity Manager start report. Launch attempts, status, activity and raw failure output remain observations; real-device timeout/mixed-report and Android 8 directory-read paths remain unverified beyond the documented fixtures. ([#1962](https://github.com/morluto/rea/pull/1962), [#2212](https://github.com/morluto/rea/pull/2212), [issue morluto/rea#2175](https://github.com/morluto/rea/issues/2175), [#2221](https://github.com/morluto/rea/pull/2221), [issue morluto/rea#2220](https://github.com/morluto/rea/issues/2220), [#2359](https://github.com/morluto/rea/pull/2359), [c42232f](https://github.com/morluto/rea/commit/c42232f566d55fe7191763649755148c768eef9f), [55b6148](https://github.com/morluto/rea/commit/55b61485e9f7568b69597eef8696ff6abfe53cfd), [3bdc873](https://github.com/morluto/rea/commit/3bdc8730bd98e073522bcb8ba86527be402c0cbe), [c89972c](https://github.com/morluto/rea/commit/c89972c8777d0114566fe76a060849a18bdd2659), [e1cd292](https://github.com/morluto/rea/commit/e1cd292aa6e8f5f13441d408c66126782c8dbb0d)). Thanks @coygeek, @dakjdakd, @DeryFerd, @huiq777, @sx4im.
|
||||||
|
- **WASM names and diagnostics:** preserve debug/custom-section names including NUL and leading BOM, accept unpadded WABT section rows, and avoid irrelevant tool-remediation advice on unrelated runtime-root failures. ([#1963](https://github.com/morluto/rea/pull/1963), [issue morluto/rea#1956](https://github.com/morluto/rea/issues/1956), [#1967](https://github.com/morluto/rea/pull/1967), [#1981](https://github.com/morluto/rea/pull/1981), [#2084](https://github.com/morluto/rea/pull/2084), [#2102](https://github.com/morluto/rea/pull/2102)). Thanks @1052326311, @bugstan, @N0zoM1z0, @sathariels, @STiFLeR7, @viniciusdsandrade.
|
||||||
|
|
||||||
|
- **Source-map and path handling:** retain source-map entries when embedded-content counts differ or an optional `sourceMappingURL` is used, and keep `file:` URLs out of operator-relative path resolution. ([#2873](https://github.com/morluto/rea/pull/2873), [#2886](https://github.com/morluto/rea/pull/2886), [#2882](https://github.com/morluto/rea/pull/2882), [issue morluto/rea#2821](https://github.com/morluto/rea/issues/2821), [issue morluto/rea#2883](https://github.com/morluto/rea/issues/2883), [issue morluto/rea#2577](https://github.com/morluto/rea/issues/2577)). Thanks @JFSAS, @rileygramlich, @shoyann, @shubhamahuja9999, @coygeek.
|
||||||
|
- **Native metadata:** accept declared facts in native dispatch inventories and share native verification input diagnostics. ([#2906](https://github.com/morluto/rea/pull/2906), [#2901](https://github.com/morluto/rea/pull/2901), [issue morluto/rea#2564](https://github.com/morluto/rea/issues/2564), [issue morluto/rea#2621](https://github.com/morluto/rea/issues/2621)). Thanks @ShitK, @coygeek.
|
||||||
|
|
||||||
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
|
- **Runtime attachment state:** `attached` may be `null` in JavaScript, browser and Electron discovery/inspection results. Treat null as unknown; do not coerce it to false. ([e55ef72](https://github.com/morluto/rea/commit/e55ef7213515ba204c3d88a4487b4ee6a2773b06)).
|
||||||
|
- **Source maps:** original-source and mapping rows preserve a null source name when unavailable. Handle null explicitly instead of expecting an invented unknown-source string; new index fields remain optional when reading older Evidence. ([ee50ed1](https://github.com/morluto/rea/commit/ee50ed121e38cc6e1a2ff0dc8b789f15f71c5402)).
|
||||||
|
- **Streamed filtered JSON:** `--filter-output` now emits JSON `null` instead of `{}` when the selected value is null; consumers must distinguish a present null from a missing selection. Use `--full-output` when an object envelope is required. ([#2333](https://github.com/morluto/rea/pull/2333)). Thanks @Jackmeson1.
|
||||||
|
- **Call graphs:** `get_call_graph.normalized_result` is now `{ graph, limitations }`; read traversal-depth groups under `graph` and retain relationship-specific caveats. Contract/fixture validation does not establish new Ghidra or IDA engine coverage. ([5dfb9d0](https://github.com/morluto/rea/commit/5dfb9d050c2cc44a61b183dae765bef4d22a53b7)).
|
||||||
|
- **MCP discovery:** `binary_session` no longer repeats `server_identity.catalog.tools`. Use the connected server tool list, grouped `tool_availability` and provider capabilities; see [MCP contracts](docs/mcp-contracts.md). Origin: [6395e8d](https://github.com/morluto/rea/commit/6395e8d3007d5ee453477460e7ee1c51cff76be0). ([#1988](https://github.com/morluto/rea/pull/1988)). Thanks @kaoru0822-kitauji, @N0zoM1z0.
|
||||||
|
|
||||||
|
- **Provider admission:** MIPS targets with unresolved GNU ABI attributes are refused. Supply supported ABI declarations. Saved Hopper databases on macOS are unavailable because the launcher cannot run the analysis bridge when opening a database; select an executable target instead. Database support requires a script-capable database launch route. ([d4bd65c](https://github.com/morluto/rea/commit/d4bd65c53c0254d0195cc7ab8da163dcadfd3b9b)).
|
||||||
|
- **Segment extents:** `binary_overview` segment length may be null when Hopper coordinates cannot establish a finite extent in one address space. Preserve unknown rather than converting it to zero or a saturated value. ([36db19c](https://github.com/morluto/rea/commit/36db19c59)).
|
||||||
|
- **Invocation identity:** `server.command_path` may be null, and registration/setup statuses or doctor classifications may be unknown. Preserve these states and establish the actual invocation before treating a registration as aligned. ([187f113](https://github.com/morluto/rea/commit/187f113f7)). Thanks @hemv-857, @qorvr.
|
||||||
|
- **ADB installation:** the optional `replace` input is unchanged. Results now require `replace_requested` to record the requested `adb install -r` mode; `replaced` is nullable because prior device package state was not observed. Update result readers to accept null instead of treating reinstall intent as proof of replacement. ([864314a](https://github.com/morluto/rea/commit/864314ac857924a7e6e65bfb37e8dc720fcc21e7)).
|
||||||
|
- **Managed/native verification:** unresolved P/Invoke results may retain `matched_native` and candidate facts when module identity is unknown. Interpret `status`, `basis` and `limitations`; `module-identity-unknown` does not verify correspondence, and unresolved no longer implies empty observations. ([3f6b575](https://github.com/morluto/rea/commit/3f6b5755b3e699bd9373641acffe0f2c5d2ab094)).
|
||||||
|
|
||||||
|
Full contribution record: [893 commits and 529 merged PRs](https://github.com/morluto/rea/blob/rea-agents-6.5.0/docs/releases/6.5.0.contributions.json).
|
||||||
|
|
||||||
|
|
||||||
## [6.4.0](https://github.com/morluto/rea/compare/rea-agents-6.3.0...rea-agents-6.4.0) (2026-10-11)
|
## [6.4.0](https://github.com/morluto/rea/compare/rea-agents-6.3.0...rea-agents-6.4.0) (2026-10-11)
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|||||||
@@ -24,6 +24,82 @@ costs change.
|
|||||||
|
|
||||||
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 MCP tool, follow the [tool design guide](docs/tool-design.md) and preserve the canonical contracts and generated catalog.
|
||||||
|
|
||||||
|
## Reporting issues
|
||||||
|
|
||||||
|
Use the issue form that fits the report and keep it concise. For bugs, include
|
||||||
|
the actual result, the shortest reproduction you ran, the tested version and
|
||||||
|
environment, and the smallest useful output, artifact, screenshot, or source
|
||||||
|
reference. Ground expected behavior in documentation, source, or a known-good
|
||||||
|
version when available. State observed impact and frequency; keep untested scope
|
||||||
|
and suspected causes explicit.
|
||||||
|
|
||||||
|
Incomplete reports and intermittent failures are welcome. If a narrative answer
|
||||||
|
or supporting evidence is unavailable, write `NOT_ENOUGH_INFO` and explain the
|
||||||
|
gap. Clearly label source-only findings and proposed checks; do not claim runtime
|
||||||
|
reproduction or results you did not observe. A root-cause analysis or fix is not
|
||||||
|
required to report a problem. Documentation reports should identify the relevant
|
||||||
|
passage or missing guidance and explain the correction or confusion.
|
||||||
|
|
||||||
|
Feature and design proposals can start with a problem and desired outcome.
|
||||||
|
Examples, prior art, and measurements help when available; ideas without them
|
||||||
|
are welcome. Maintainers can investigate reports and help resolve open questions.
|
||||||
|
Human ownership and readiness declarations apply to PRs as described below.
|
||||||
|
|
||||||
|
## Pull request ownership and readiness
|
||||||
|
|
||||||
|
AI-assisted contributions are welcome. Every PR must have a human owner who
|
||||||
|
takes responsibility for the complete change, including code, tests,
|
||||||
|
documentation, and claims produced with AI assistance. The PR author is the
|
||||||
|
owner unless another human is named in the PR and explicitly accepts ownership.
|
||||||
|
These requirements also apply to maintainer-authored PRs.
|
||||||
|
|
||||||
|
Before requesting maintainer review, the human owner must:
|
||||||
|
|
||||||
|
- Review the final diff and relevant existing code. Be able to explain the
|
||||||
|
problem, the chosen approach, how it fits REA, and meaningful edge cases.
|
||||||
|
- Provide evidence that the change works. Summarize what was checked, the
|
||||||
|
observed results, and meaningful gaps in the PR body. Use focused tests,
|
||||||
|
representative CLI/MCP output, CI artifacts, screenshots, or other checks
|
||||||
|
appropriate to the change. For fixes, show the original failure and why the
|
||||||
|
regression coverage catches it when practical. Documentation-only changes
|
||||||
|
can use source, link, or rendered-document checks. Label source-derived
|
||||||
|
conclusions and proposed checks accurately; never report an unrun check as
|
||||||
|
passing. Follow the [verification requirements](#development-feedback-and-pr-verification)
|
||||||
|
for required checks and real-provider coverage.
|
||||||
|
- Complete the **Author readiness** declaration in the PR template, explicitly
|
||||||
|
stating that the PR is ready for initial review.
|
||||||
|
|
||||||
|
Keep the PR description concise and current. Explain the problem, observable
|
||||||
|
outcome, approach, and useful evidence; link long logs or put them in optional
|
||||||
|
details. Address actionable automated review findings and explain disagreements.
|
||||||
|
Human ownership and review remain required when CI or an automated reviewer
|
||||||
|
reports success. Readiness requests maintainer review; it does not grant scope
|
||||||
|
approval or permission to merge.
|
||||||
|
|
||||||
|
Draft PRs may request early feedback with open questions and incomplete checks.
|
||||||
|
Leave the readiness declaration unchecked until the human owner has completed
|
||||||
|
it. The declaration records initial readiness; later pushes do not invalidate
|
||||||
|
it or require another human sign-off. Keep the PR description and affected
|
||||||
|
validation current as work changes. AI agents must leave human declarations to
|
||||||
|
the human owner and must not attest to a human review on their behalf.
|
||||||
|
|
||||||
|
The `PR readiness` check reports pending for drafts and requires a named human
|
||||||
|
owner and all three template declarations for non-drafts. It checks that the
|
||||||
|
declaration is filled, not whether the code or evidence is correct. Existing PRs
|
||||||
|
can copy the **Author readiness** section from the current template.
|
||||||
|
Open PRs must have distinct head commits because GitHub shares commit statuses.
|
||||||
|
|
||||||
|
Maintainers enable merge enforcement after the workflow is merged: run
|
||||||
|
`gh workflow run pr-readiness.yml -f pull_request_number=NUMBER` for existing
|
||||||
|
open PRs, then add `PR readiness` from GitHub Actions to the branch's required
|
||||||
|
status checks alongside its existing checks. Deploy the workflow to each
|
||||||
|
protected target branch before requiring it there. The workflow reads PR
|
||||||
|
metadata using trusted default-branch code and writes only a commit status.
|
||||||
|
|
||||||
|
Maintainers may return a PR to the author or close it when the problem is
|
||||||
|
unsupported, validation is missing, or the owner cannot explain the change.
|
||||||
|
Contributors are responsible for completing this work before requesting review.
|
||||||
|
|
||||||
## Contribution scope
|
## Contribution scope
|
||||||
|
|
||||||
REA focuses on software structure, runtime behavior, and evidence through
|
REA focuses on software structure, runtime behavior, and evidence through
|
||||||
@@ -181,6 +257,10 @@ The separate GitHub Pages host uses only the manual
|
|||||||
|
|
||||||
## Development feedback and PR verification
|
## Development feedback and PR verification
|
||||||
|
|
||||||
|
For routine merges, wait for the required checks to pass for the current PR
|
||||||
|
revision. Administrators retain an override for deliberate maintainer recovery.
|
||||||
|
If `main` is broken, prioritize a focused repair before updating affected PRs.
|
||||||
|
|
||||||
Select checks for the behavior changed. For a source edit:
|
Select checks for the behavior changed. For a source edit:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -202,6 +282,10 @@ CI diagnosis. Packaging/setup/distribution changes additionally require
|
|||||||
`npm run verify:package` and `npm pack --dry-run`; provider changes require the
|
`npm run verify:package` and `npm pack --dry-run`; provider changes require the
|
||||||
matching real-provider lane. State which host/provider workflows ran.
|
matching real-provider lane. State which host/provider workflows ran.
|
||||||
|
|
||||||
|
For CI/test correctness, drift, or performance investigations, use the
|
||||||
|
[`rea-ci-tests-audit` contributor skill](.agents/skills/rea-ci-tests-audit/SKILL.md).
|
||||||
|
It guides reproduction and comparable measurements before changing checks.
|
||||||
|
|
||||||
`check:pr` reuses build and static-check outputs while executing the test suite
|
`check:pr` reuses build and static-check outputs while executing the test suite
|
||||||
fresh, including the fast Python producer checks. `docs:check` also runs the
|
fresh, including the fast Python producer checks. `docs:check` also runs the
|
||||||
onboarding example consistency suite for README-only changes.
|
onboarding example consistency suite for README-only changes.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
**English** · [简体中文](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)
|
**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)
|
||||||
|
|
||||||
# REA: Reverse Engineer Anything
|
# REA: Reverse Engineer Anything
|
||||||
|
|
||||||
@@ -23,6 +24,16 @@
|
|||||||
|
|
||||||
<code>npx rea-agents setup</code>
|
<code>npx rea-agents setup</code>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Get REA from [morluto/rea](https://github.com/morluto/rea),
|
||||||
|
> [rea.tools](https://rea.tools/), or the [rea-agents npm package](https://www.npmjs.com/package/rea-agents).
|
||||||
|
> Follow the installation and release links from these official sources. A similar
|
||||||
|
> repository name or copied README does not establish that a download is official.
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<img src="docs/assets/rea-hopper-analysis.png" alt="REA launching its analysis bridge inside Hopper while inspecting a native binary" width="1200" />
|
<img src="docs/assets/rea-hopper-analysis.png" alt="REA launching its analysis bridge inside Hopper while inspecting a native binary" width="1200" />
|
||||||
@@ -57,6 +68,11 @@ Setup registers REA with your agent and installs matching workflow instructions.
|
|||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
On Linux, Ghidra, JADX, firmware tool launches and static JavaScript/Electron
|
||||||
|
analysis require a procps-compatible `ps` on REA's `PATH` for process ownership
|
||||||
|
checks. Recovery steps are in `docs/installation.md` under
|
||||||
|
"Linux owned-process prerequisite".
|
||||||
|
|
||||||
### Set up your agent
|
### Set up your agent
|
||||||
|
|
||||||
With Node.js and npm installed, run:
|
With Node.js and npm installed, run:
|
||||||
@@ -152,15 +168,16 @@ Additional tools and host support depend on the target:
|
|||||||
|
|
||||||
| Target | What REA returns | Requirements and guide |
|
| Target | What REA returns | Requirements and guide |
|
||||||
| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](https://rea.tools/guides/native/) |
|
| Native binaries | Pseudocode, assembly, strings, symbols, calls and references | Hopper, Ghidra or IDA; [native analysis](docs/native-investigation.md) |
|
||||||
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
|
| Offline ELF layout | Sections, segments, original symbols/relocations and static mitigation candidates | Caller-supplied pwntools on Linux x64; [binary diagnostics](docs/binary-diagnostics.md) |
|
||||||
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
|
| Go binaries | Embedded compiler version, modules, replacements and build settings | Built-in static ELF/PE/thin Mach-O inspection; no Go installation required |
|
||||||
|
| Rust binaries | Embedded cargo-auditable crate names, versions, sources and dependency edges | Built-in static ELF/PE/thin Mach-O inspection; [Rust binaries](docs/rust-binaries.md) |
|
||||||
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
|
| WASM artifact | Validation, sections, exact import/export WAT forms and decoded WAT | Caller-supplied WABT 1.0.42; `inspect_wasm_artifact` |
|
||||||
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
|
| EVM bytecode | Dispatch selectors, byte offsets, inferred arguments and mutability | Local raw/hex carrier on Linux x64; [offline EVM guide](docs/evm-bytecode.md) |
|
||||||
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
|
| SQLite databases | Tables, columns, indexes, views, triggers and selected ordinary-table rows | Local database/WAL snapshot; Node.js 24.x >=24.15 or 26+ with native SQLite limits |
|
||||||
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
|
| Recorded Linux crashes | Raw notes, every recorded thread's registers/signals and optional mapping candidates | Caller-supplied pwntools on Linux x64; optional GDB/pwndbg; [recorded crashes](docs/recorded-crashes.md) |
|
||||||
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; [application analysis](https://rea.tools/guides/javascript/) |
|
| JavaScript / Electron | Modules, imports, source maps, routes, IPC and native add-on relationships | Node.js and npm; Linux: procps-compatible `ps`; [application analysis](docs/javascript-application-workflows.md) |
|
||||||
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](https://rea.tools/guides/browser/) |
|
| Websites | Page structure, scripts, network observations and requested screenshots | A Chrome-family browser; [browser analysis](docs/browser-observation.md) |
|
||||||
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
|
| Saved network captures | Requests, responses, exposed payloads and source locations | HAR or Fiddler Everywhere SAZ; mitmdump on Linux for native mitmproxy; [capture guide](docs/web-network-captures.md) |
|
||||||
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
|
| .NET assemblies | Metadata, CIL instructions, declared native dependencies and build comparisons | None (static only); [managed-code guide](docs/managed-code-analysis.md) |
|
||||||
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
|
| Android APKs | Manifest declarations, classes, decompiled methods and references | Headless JADX and a full JDK on Linux/macOS/Windows x64; [Android guide](docs/android-analysis.md) |
|
||||||
@@ -179,11 +196,17 @@ Static JavaScript and .NET inspection read the supplied files without running
|
|||||||
the application. Runtime capture runs or interacts with the selected target
|
the application. Runtime capture runs or interacts with the selected target
|
||||||
using your user permissions; each runtime guide describes its effects.
|
using your user permissions; each runtime guide describes its effects.
|
||||||
|
|
||||||
|
For offline native-binary inspection, REA also supports caller-supplied GNU
|
||||||
|
objdump and Rizin. See the [reverse-engineering tools guide](docs/reverse-engineering-tools.md)
|
||||||
|
for provider setup, CLI commands, and Evidence limitations. REA does not install
|
||||||
|
or update either provider.
|
||||||
|
|
||||||
<a id="choosing-a-deep-analysis-provider"></a>
|
<a id="choosing-a-deep-analysis-provider"></a>
|
||||||
|
|
||||||
Native formats and host support vary by provider. See
|
Native formats and host support vary by provider. See
|
||||||
[Hopper and Ghidra setup](docs/installation.md#hopper), the
|
[Hopper and Ghidra setup](docs/installation.md#hopper), the
|
||||||
[IDA guide](docs/ida-provider.md) (including an optional native MCP bridge), and
|
[IDA guide](docs/ida-provider.md) (including an optional native MCP bridge), the
|
||||||
|
[Kuna decompiler guide](docs/kuna-provider.md), and
|
||||||
[experimental Windows Ghidra support](docs/windows-ghidra-p0.md).
|
[experimental Windows Ghidra support](docs/windows-ghidra-p0.md).
|
||||||
Ghidra also supports [16-bit DOS analysis](docs/ghidra-dos.md).
|
Ghidra also supports [16-bit DOS analysis](docs/ghidra-dos.md).
|
||||||
For large binaries, raise the provider's startup deadline with
|
For large binaries, raise the provider's startup deadline with
|
||||||
@@ -322,7 +345,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md).
|
|||||||
|
|
||||||
## Star history
|
## Star history
|
||||||
|
|
||||||
🎉 **170,000 GitHub stars — thank you!**
|
🎉 **250,000 GitHub stars — thank you!**
|
||||||
|
|
||||||
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
|
Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes.
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ pin inspection sources; they are not included in the npm package.
|
|||||||
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
|
| ghidra-nativeaot | [MIT](https://github.com/Washi1337/ghidra-nativeaot/blob/effeb734fc570c32650f88b159608979dc7b423e/LICENSE.md) | Pinned inspection source; derived parser attribution above. |
|
||||||
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
|
| JADX | [Apache-2.0](https://github.com/skylot/jadx/blob/v1.5.6/LICENSE) | The audited jadx-headless-mcp source uses JADX 1.5.6 for Android decompilation; the engine JAR is separately supplied. |
|
||||||
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
|
| jadx-headless-mcp | [Apache-2.0](https://github.com/1013503897/jadx-headless-mcp/blob/5844800a486d2248fa949b74c9896285f5b54de2/LICENSE) | v0.7.1; pinned submodule and user-supplied JAR. |
|
||||||
|
| Kuna | [Apache-2.0](https://github.com/Noelo-Lab/kuna/blob/v1.833/LICENSE), with its [NOTICE](https://github.com/Noelo-Lab/kuna/blob/v1.833/NOTICE) | Bring your own `kuna`; REA verified 1.833 and never downloads, installs or bundles it. REA invokes it as a separate process. |
|
||||||
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
|
| Apktool | [Apache-2.0](https://github.com/iBotPeaches/Apktool/blob/master/LICENSE.md) | User-supplied Android resource decoder. |
|
||||||
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
|
| Binwalk | [MIT](https://github.com/ReFirmLabs/binwalk/blob/4fdab3d464d97b68e0af9088df3f9e2e1545b21c/LICENSE) | v3.1.0; pinned submodule and user-supplied firmware engine. |
|
||||||
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
|
| Unblob | [MIT](https://github.com/onekey-sec/unblob/blob/1fcc7a0a584a70a96c31f5a276c20944d199a089/LICENSE) | 26.6.4; pinned submodule and user-supplied firmware engine. |
|
||||||
|
|||||||
@@ -3,8 +3,14 @@ import com.google.gson.*;
|
|||||||
import io.modelcontextprotocol.kotlin.sdk.server.*;
|
import io.modelcontextprotocol.kotlin.sdk.server.*;
|
||||||
import io.modelcontextprotocol.kotlin.sdk.types.*;
|
import io.modelcontextprotocol.kotlin.sdk.types.*;
|
||||||
import jadx.api.*;
|
import jadx.api.*;
|
||||||
|
import jadx.api.plugins.input.data.ICodeReader;
|
||||||
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
|
import jadx.api.plugins.input.insns.InsnIndexType;
|
||||||
|
import jadx.api.plugins.input.insns.Opcode;
|
||||||
|
import jadx.core.dex.info.MethodInfo;
|
||||||
import jadx.core.dex.nodes.*;
|
import jadx.core.dex.nodes.*;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -79,11 +85,17 @@ class ReaJadxBridge {
|
|||||||
ordered.sort(Comparator.comparing(MethodNode::getAlias));
|
ordered.sort(Comparator.comparing(MethodNode::getAlias));
|
||||||
List<MethodSnapshot> methods = ordered.stream().map(method -> {
|
List<MethodSnapshot> methods = ordered.stream().map(method -> {
|
||||||
String name = method.getAlias();
|
String name = method.getAlias();
|
||||||
|
String shortId = method.getMethodInfo().getShortId();
|
||||||
|
String rawName = method.getMethodInfo().getName();
|
||||||
|
if (!shortId.startsWith(rawName + "("))
|
||||||
|
throw new IllegalStateException("JADX method ID has no raw-name prefix: " + shortId);
|
||||||
|
String descriptor = shortId.substring(rawName.length());
|
||||||
String signature = name + "(" + method.getArgTypes().stream()
|
String signature = name + "(" + method.getArgTypes().stream()
|
||||||
.map(Object::toString).collect(Collectors.joining(", "))
|
.map(Object::toString).collect(Collectors.joining(", "))
|
||||||
+ "): " + method.getReturnType().toString();
|
+ "): " + method.getReturnType().toString();
|
||||||
return new MethodSnapshot(name, method.getMethodInfo().getShortId(), Map.of(
|
return new MethodSnapshot(name, shortId, Map.of(
|
||||||
"name", name, "signature", signature,
|
"name", name, "signature", signature,
|
||||||
|
"dex_descriptor", descriptor,
|
||||||
"is_constructor", method.isConstructor(), "def_pos", 0));
|
"is_constructor", method.isConstructor(), "def_pos", 0));
|
||||||
}).toList();
|
}).toList();
|
||||||
List<Map<String, Object>> fields = node.getFields().stream()
|
List<Map<String, Object>> fields = node.getFields().stream()
|
||||||
@@ -140,6 +152,57 @@ class ReaJadxBridge {
|
|||||||
"smali", true, List.of("static_initializer_disassembly"));
|
"smali", true, List.of("static_initializer_disassembly"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The local transport admits 8 MiB frames. Reserve 1 KiB for the fixed
|
||||||
|
// JSON-RPC/CallToolResult envelope and generated numeric request id, and
|
||||||
|
// account for JSON text being escaped again inside TextContent.
|
||||||
|
private static final int MAX_XREF_TEXT_BYTES = 8 * 1024 * 1024 - 1024;
|
||||||
|
private static int escapedJsonBytes(Object value) {
|
||||||
|
return JSON.toJson(JSON.toJson(value)).getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
}
|
||||||
|
private static final class InvokeBudget {
|
||||||
|
long remaining;
|
||||||
|
InvokeBudget(long remaining) { this.remaining = Math.max(0, remaining); }
|
||||||
|
boolean admit(Map<String, Object> site) {
|
||||||
|
// Check a conservative two-layer escape bound before serialization.
|
||||||
|
long bound = 256;
|
||||||
|
for (Object value : site.values())
|
||||||
|
if (value instanceof String text) bound += 12L * text.length();
|
||||||
|
if (bound > remaining) return false;
|
||||||
|
long bytes = escapedJsonBytes(site) - 2L + 1L;
|
||||||
|
if (bytes > remaining) return false;
|
||||||
|
remaining -= bytes;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private record DirectInvokes(List<Map<String, Object>> sites, boolean exhaustive) {}
|
||||||
|
|
||||||
|
/** Raw DEX invokes within one JADX using method; offsets are code units from its code start. */
|
||||||
|
private static DirectInvokes directInvokes(JavaMethod source, MethodInfo target, InvokeBudget budget) {
|
||||||
|
MethodNode node = source.getMethodNode();
|
||||||
|
ICodeReader code = node.getCodeReader();
|
||||||
|
if (code == null) return null;
|
||||||
|
List<Map<String, Object>> sites = new ArrayList<>();
|
||||||
|
boolean[] exhaustive = {true};
|
||||||
|
code.visitInstructions(insn -> {
|
||||||
|
insn.decode();
|
||||||
|
if (insn.getIndexType() != InsnIndexType.METHOD_REF) return;
|
||||||
|
Opcode opcode = insn.getOpcode();
|
||||||
|
if (!opcode.name().startsWith("INVOKE_")) return;
|
||||||
|
IMethodRef ref = insn.getIndexAsMethod();
|
||||||
|
ref.load();
|
||||||
|
if (!MethodInfo.fromRef(node.root(), ref).getRawFullId().equals(target.getRawFullId())) return;
|
||||||
|
Map<String, Object> site = Map.of(
|
||||||
|
"dex_entry", node.getInputFileName(),
|
||||||
|
"source_raw_method_id", node.getMethodInfo().getRawFullId(),
|
||||||
|
"dex_code_unit_offset", insn.getOffset(),
|
||||||
|
"dex_method_index", insn.getIndex(),
|
||||||
|
"opcode", opcode.name());
|
||||||
|
if (budget.admit(site)) sites.add(site);
|
||||||
|
else exhaustive[0] = false;
|
||||||
|
});
|
||||||
|
return new DirectInvokes(sites, exhaustive[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private CallToolResult handle(String name, JsonObject args) {
|
private CallToolResult handle(String name, JsonObject args) {
|
||||||
if (name.equals("rea_jvm_status")) return json(Map.of(
|
if (name.equals("rea_jvm_status")) return json(Map.of(
|
||||||
"engine_reported_version", BuildInfo.VERSION,
|
"engine_reported_version", BuildInfo.VERSION,
|
||||||
@@ -173,17 +236,52 @@ class ReaJadxBridge {
|
|||||||
}
|
}
|
||||||
List<JavaNode> uses;
|
List<JavaNode> uses;
|
||||||
String target = string(args, "class_name");
|
String target = string(args, "class_name");
|
||||||
|
MethodSnapshot selectedMethod = null;
|
||||||
|
int overloadIndex = 0;
|
||||||
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
|
if (name.equals("get_xrefs_to_class")) uses = cls.node().getUseIn();
|
||||||
else {
|
else {
|
||||||
List<MethodSnapshot> overloads = methods(cls, args);
|
List<MethodSnapshot> overloads = methods(cls, args);
|
||||||
if (overloads.size() != 1) throw new InputFailure("method_name", "Method references require a unique overload");
|
if (overloads.size() != 1 && !args.containsKey("overload_index"))
|
||||||
target += "." + overloads.get(0).name();
|
throw new InputFailure("overload_index", "Select one of " + overloads.size() + " method overloads");
|
||||||
uses = method(cls, overloads.get(0)).getUseIn();
|
overloadIndex = integer(args, "overload_index", 0);
|
||||||
|
if (overloadIndex >= overloads.size())
|
||||||
|
throw new InputFailure("overload_index", "overload_index out of range: " + overloadIndex);
|
||||||
|
selectedMethod = overloads.get(overloadIndex);
|
||||||
|
target += "." + selectedMethod.name();
|
||||||
|
uses = method(cls, selectedMethod).getUseIn();
|
||||||
}
|
}
|
||||||
// REA consumes all references without materializing source line information.
|
// REA consumes all references without materializing source line information.
|
||||||
List<Map<String, Object>> items = uses.stream().map(node -> current.describeUsage(node, false)).toList();
|
MethodInfo targetInfo = selectedMethod == null ? null : method(cls, selectedMethod).getMethodNode().getMethodInfo();
|
||||||
return json(Map.of("target", target, "total", uses.size(), "count", items.size(),
|
List<Map<String, Object>> items = uses.stream().map(node -> {
|
||||||
"resolve_line", false, "items", items));
|
Map<String, Object> item = new LinkedHashMap<>(current.describeUsage(node, false));
|
||||||
|
if (targetInfo != null && node instanceof JavaMethod) {
|
||||||
|
item.put("direct_invokes", List.of());
|
||||||
|
item.put("direct_invokes_exhaustive", false);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).toList();
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>(Map.of("target", target,
|
||||||
|
"total", uses.size(), "count", items.size(), "resolve_line", false, "items", items));
|
||||||
|
if (selectedMethod != null) {
|
||||||
|
result.put("overload_index", overloadIndex);
|
||||||
|
result.put("reported_signature", selectedMethod.summary().get("signature"));
|
||||||
|
}
|
||||||
|
if (targetInfo != null) {
|
||||||
|
InvokeBudget budget = new InvokeBudget(MAX_XREF_TEXT_BYTES - escapedJsonBytes(result));
|
||||||
|
for (int index = 0; index < uses.size(); index++) {
|
||||||
|
if (!(uses.get(index) instanceof JavaMethod source)) continue;
|
||||||
|
DirectInvokes invokes = directInvokes(source, targetInfo, budget);
|
||||||
|
Map<String, Object> item = items.get(index);
|
||||||
|
if (invokes == null) {
|
||||||
|
item.remove("direct_invokes");
|
||||||
|
item.remove("direct_invokes_exhaustive");
|
||||||
|
} else {
|
||||||
|
item.put("direct_invokes", invokes.sites());
|
||||||
|
item.put("direct_invokes_exhaustive", invokes.exhaustive());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
@@ -217,7 +315,7 @@ class ReaJadxBridge {
|
|||||||
List<String> fields = switch (name) {
|
List<String> fields = switch (name) {
|
||||||
case "list_classes" -> List.of("offset", "limit");
|
case "list_classes" -> List.of("offset", "limit");
|
||||||
case "get_method_body" -> List.of("class_name", "method_name", "overload_index");
|
case "get_method_body" -> List.of("class_name", "method_name", "overload_index");
|
||||||
case "get_xrefs_to_method" -> List.of("class_name", "method_name");
|
case "get_xrefs_to_method" -> List.of("class_name", "method_name", "overload_index");
|
||||||
default -> List.of("class_name");
|
default -> List.of("class_name");
|
||||||
};
|
};
|
||||||
Map<String, kotlinx.serialization.json.JsonElement> properties = new LinkedHashMap<>();
|
Map<String, kotlinx.serialization.json.JsonElement> properties = new LinkedHashMap<>();
|
||||||
@@ -226,7 +324,7 @@ class ReaJadxBridge {
|
|||||||
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
|
case "list_classes" -> "Indexed pages of all top-level class names, with offset and limit.";
|
||||||
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
case "get_class_summary" -> "Parsed member metadata for one exact class name; no source generation.";
|
||||||
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
|
case "get_method_body" -> "Source for an exact class and stable same-name overload index; static initializers use smali to preserve folded field initialization.";
|
||||||
default -> "Complete static references to an exact class or unique method; source lines are unresolved.";
|
default -> "Complete static using-node inventory for an exact class or selected method overload; one node may contain multiple invoke instructions, and source lines are unresolved.";
|
||||||
};
|
};
|
||||||
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
|
Tool narrowed = new Tool(name, new ToolSchema(null, new JsonObject(properties),
|
||||||
tool.getInputSchema().getRequired(), null), description, null, null, null, null, null, null);
|
tool.getInputSchema().getRequired(), null), description, null, null, null, null, null, null);
|
||||||
|
|||||||
@@ -0,0 +1,816 @@
|
|||||||
|
"""REA's experimental local bridge plugin for Cutter.
|
||||||
|
|
||||||
|
Install this file in Cutter's user plugin directory. The bridge is REA-owned
|
||||||
|
IPC, not an upstream Cutter transport. It binds to loopback and requires a
|
||||||
|
per-process random token stored in a user-only descriptor file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import socket
|
||||||
|
import stat
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
if os.name == "nt":
|
||||||
|
import ctypes
|
||||||
|
from ctypes import wintypes
|
||||||
|
|
||||||
|
import cutter
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PySide6.QtCore import QObject, Signal
|
||||||
|
except ImportError:
|
||||||
|
from PySide2.QtCore import QObject, Signal
|
||||||
|
|
||||||
|
|
||||||
|
MAX_FRAME_BYTES = 16 * 1024 * 1024
|
||||||
|
SOCKET_TIMEOUT_SECONDS = 20
|
||||||
|
AUTH_FRAME_TIMEOUT_SECONDS = 1
|
||||||
|
MAX_DESCRIPTOR_BYTES = 64 * 1024
|
||||||
|
MAX_COMPLETION_RECEIPTS = 64
|
||||||
|
|
||||||
|
|
||||||
|
def _windows_api() -> tuple[Any, Any]:
|
||||||
|
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||||
|
advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
|
||||||
|
kernel32.GetCurrentProcess.restype = wintypes.HANDLE
|
||||||
|
kernel32.LocalFree.argtypes = [wintypes.HLOCAL]
|
||||||
|
kernel32.LocalFree.restype = wintypes.HLOCAL
|
||||||
|
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
||||||
|
kernel32.CloseHandle.restype = wintypes.BOOL
|
||||||
|
kernel32.GetFileAttributesW.argtypes = [wintypes.LPCWSTR]
|
||||||
|
kernel32.GetFileAttributesW.restype = wintypes.DWORD
|
||||||
|
kernel32.CreateDirectoryW.argtypes = [wintypes.LPCWSTR, wintypes.LPVOID]
|
||||||
|
kernel32.CreateDirectoryW.restype = wintypes.BOOL
|
||||||
|
kernel32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD,
|
||||||
|
wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE]
|
||||||
|
kernel32.CreateFileW.restype = wintypes.HANDLE
|
||||||
|
kernel32.WriteFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD,
|
||||||
|
ctypes.POINTER(wintypes.DWORD), wintypes.LPVOID]
|
||||||
|
kernel32.WriteFile.restype = wintypes.BOOL
|
||||||
|
kernel32.FlushFileBuffers.argtypes = [wintypes.HANDLE]
|
||||||
|
kernel32.FlushFileBuffers.restype = wintypes.BOOL
|
||||||
|
kernel32.MoveFileExW.argtypes = [wintypes.LPCWSTR, wintypes.LPCWSTR, wintypes.DWORD]
|
||||||
|
kernel32.MoveFileExW.restype = wintypes.BOOL
|
||||||
|
kernel32.DeleteFileW.argtypes = [wintypes.LPCWSTR]
|
||||||
|
kernel32.DeleteFileW.restype = wintypes.BOOL
|
||||||
|
advapi32.OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)]
|
||||||
|
advapi32.OpenProcessToken.restype = wintypes.BOOL
|
||||||
|
advapi32.GetTokenInformation.argtypes = [wintypes.HANDLE, ctypes.c_int, wintypes.LPVOID,
|
||||||
|
wintypes.DWORD, ctypes.POINTER(wintypes.DWORD)]
|
||||||
|
advapi32.GetTokenInformation.restype = wintypes.BOOL
|
||||||
|
advapi32.ConvertSidToStringSidW.argtypes = [wintypes.LPVOID, ctypes.POINTER(wintypes.LPWSTR)]
|
||||||
|
advapi32.ConvertSidToStringSidW.restype = wintypes.BOOL
|
||||||
|
advapi32.GetNamedSecurityInfoW.argtypes = [wintypes.LPWSTR, ctypes.c_int, wintypes.DWORD,
|
||||||
|
ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.LPVOID),
|
||||||
|
ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.LPVOID),
|
||||||
|
ctypes.POINTER(wintypes.LPVOID)]
|
||||||
|
advapi32.GetNamedSecurityInfoW.restype = wintypes.DWORD
|
||||||
|
advapi32.GetSecurityDescriptorControl.argtypes = [wintypes.LPVOID, ctypes.POINTER(wintypes.WORD),
|
||||||
|
ctypes.POINTER(wintypes.DWORD)]
|
||||||
|
advapi32.GetSecurityDescriptorControl.restype = wintypes.BOOL
|
||||||
|
advapi32.EqualSid.argtypes = [wintypes.LPVOID, wintypes.LPVOID]
|
||||||
|
advapi32.EqualSid.restype = wintypes.BOOL
|
||||||
|
advapi32.GetAclInformation.argtypes = [wintypes.LPVOID, wintypes.LPVOID, wintypes.DWORD, ctypes.c_int]
|
||||||
|
advapi32.GetAclInformation.restype = wintypes.BOOL
|
||||||
|
advapi32.GetAce.argtypes = [wintypes.LPVOID, wintypes.DWORD, ctypes.POINTER(wintypes.LPVOID)]
|
||||||
|
advapi32.GetAce.restype = wintypes.BOOL
|
||||||
|
advapi32.IsValidSid.argtypes = [wintypes.LPVOID]
|
||||||
|
advapi32.IsValidSid.restype = wintypes.BOOL
|
||||||
|
advapi32.GetLengthSid.argtypes = [wintypes.LPVOID]
|
||||||
|
advapi32.GetLengthSid.restype = wintypes.DWORD
|
||||||
|
advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW.argtypes = [
|
||||||
|
wintypes.LPCWSTR, wintypes.DWORD, ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.DWORD),
|
||||||
|
]
|
||||||
|
advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW.restype = wintypes.BOOL
|
||||||
|
return kernel32, advapi32
|
||||||
|
|
||||||
|
|
||||||
|
def _windows_user_sid_text() -> str:
|
||||||
|
kernel32, advapi32 = _windows_api()
|
||||||
|
token = wintypes.HANDLE()
|
||||||
|
if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
try:
|
||||||
|
required = wintypes.DWORD()
|
||||||
|
advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required))
|
||||||
|
if required.value == 0:
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
buffer = ctypes.create_string_buffer(required.value)
|
||||||
|
if not advapi32.GetTokenInformation(token, 1, buffer, required, ctypes.byref(required)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
|
||||||
|
class SID_AND_ATTRIBUTES(ctypes.Structure):
|
||||||
|
_fields_ = [("Sid", wintypes.LPVOID), ("Attributes", wintypes.DWORD)]
|
||||||
|
|
||||||
|
class TOKEN_USER(ctypes.Structure):
|
||||||
|
_fields_ = [("User", SID_AND_ATTRIBUTES)]
|
||||||
|
|
||||||
|
sid = ctypes.cast(buffer, ctypes.POINTER(TOKEN_USER)).contents.User.Sid
|
||||||
|
text = wintypes.LPWSTR()
|
||||||
|
if not advapi32.ConvertSidToStringSidW(sid, ctypes.byref(text)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
try:
|
||||||
|
return text.value
|
||||||
|
finally:
|
||||||
|
kernel32.LocalFree(text)
|
||||||
|
finally:
|
||||||
|
kernel32.CloseHandle(token)
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_windows_private_path(path: Path, directory: bool) -> None:
|
||||||
|
kernel32, advapi32 = _windows_api()
|
||||||
|
attributes = kernel32.GetFileAttributesW(str(path))
|
||||||
|
if attributes == 0xFFFFFFFF:
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
if attributes & 0x400 or bool(attributes & 0x10) != directory:
|
||||||
|
raise RuntimeError("Cutter bridge path is a reparse point or has the wrong object type")
|
||||||
|
|
||||||
|
class ACL_SIZE_INFORMATION(ctypes.Structure):
|
||||||
|
_fields_ = [("AceCount", wintypes.DWORD), ("AclBytesInUse", wintypes.DWORD), ("AclBytesFree", wintypes.DWORD)]
|
||||||
|
|
||||||
|
class ACE_HEADER(ctypes.Structure):
|
||||||
|
_fields_ = [("AceType", wintypes.BYTE), ("AceFlags", wintypes.BYTE), ("AceSize", wintypes.WORD)]
|
||||||
|
|
||||||
|
class SID_HEADER(ctypes.Structure):
|
||||||
|
_fields_ = [("Revision", wintypes.BYTE), ("SubAuthorityCount", wintypes.BYTE),
|
||||||
|
("IdentifierAuthority", wintypes.BYTE * 6)]
|
||||||
|
|
||||||
|
class ACCESS_ALLOWED_ACE(ctypes.Structure):
|
||||||
|
_fields_ = [("Header", ACE_HEADER), ("Mask", wintypes.DWORD), ("SidStart", wintypes.DWORD)]
|
||||||
|
|
||||||
|
owner = wintypes.LPVOID()
|
||||||
|
dacl = wintypes.LPVOID()
|
||||||
|
descriptor = wintypes.LPVOID()
|
||||||
|
result = advapi32.GetNamedSecurityInfoW(
|
||||||
|
str(path), 1, 0x1 | 0x4, ctypes.byref(owner), None, ctypes.byref(dacl), None, ctypes.byref(descriptor),
|
||||||
|
)
|
||||||
|
if result != 0:
|
||||||
|
raise ctypes.WinError(result)
|
||||||
|
try:
|
||||||
|
control = wintypes.WORD()
|
||||||
|
revision = wintypes.DWORD()
|
||||||
|
if not advapi32.GetSecurityDescriptorControl(descriptor, ctypes.byref(control), ctypes.byref(revision)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
if not control.value & 0x1000 or not dacl:
|
||||||
|
raise RuntimeError("Cutter bridge ACL must be present and protected from inheritance")
|
||||||
|
|
||||||
|
token = wintypes.HANDLE()
|
||||||
|
if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
try:
|
||||||
|
required = wintypes.DWORD()
|
||||||
|
advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required))
|
||||||
|
token_user = ctypes.create_string_buffer(required.value)
|
||||||
|
if not advapi32.GetTokenInformation(token, 1, token_user, required, ctypes.byref(required)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
|
||||||
|
class SID_AND_ATTRIBUTES(ctypes.Structure):
|
||||||
|
_fields_ = [("Sid", wintypes.LPVOID), ("Attributes", wintypes.DWORD)]
|
||||||
|
|
||||||
|
class TOKEN_USER(ctypes.Structure):
|
||||||
|
_fields_ = [("User", SID_AND_ATTRIBUTES)]
|
||||||
|
|
||||||
|
current_sid = ctypes.cast(token_user, ctypes.POINTER(TOKEN_USER)).contents.User.Sid
|
||||||
|
if not advapi32.EqualSid(owner, current_sid):
|
||||||
|
raise RuntimeError("Cutter bridge path owner does not match the current user")
|
||||||
|
finally:
|
||||||
|
kernel32.CloseHandle(token)
|
||||||
|
|
||||||
|
info = ACL_SIZE_INFORMATION()
|
||||||
|
if not advapi32.GetAclInformation(dacl, ctypes.byref(info), ctypes.sizeof(info), 2) or info.AceCount != 2:
|
||||||
|
raise RuntimeError("Cutter bridge ACL must allow only the current user and SYSTEM")
|
||||||
|
expected_user = _windows_user_sid_text()
|
||||||
|
user_allowed = False
|
||||||
|
system_allowed = False
|
||||||
|
for index in range(info.AceCount):
|
||||||
|
ace_pointer = wintypes.LPVOID()
|
||||||
|
if not advapi32.GetAce(dacl, index, ctypes.byref(ace_pointer)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
header = ctypes.cast(ace_pointer, ctypes.POINTER(ACE_HEADER)).contents
|
||||||
|
if header.AceType != 0 or header.AceSize < ctypes.sizeof(ACE_HEADER) + ctypes.sizeof(wintypes.DWORD) + ctypes.sizeof(SID_HEADER):
|
||||||
|
raise RuntimeError("Cutter bridge ACL contains a malformed or unsupported ACE")
|
||||||
|
ace_address = ctypes.cast(ace_pointer, ctypes.c_void_p).value
|
||||||
|
if ace_address is None:
|
||||||
|
raise RuntimeError("Cutter bridge ACL returned a null ACE")
|
||||||
|
sid_pointer = ctypes.c_void_p(ace_address + ctypes.sizeof(ACE_HEADER) + ctypes.sizeof(wintypes.DWORD))
|
||||||
|
sid_header = ctypes.cast(sid_pointer, ctypes.POINTER(SID_HEADER)).contents
|
||||||
|
sid_size = ctypes.sizeof(SID_HEADER) + sid_header.SubAuthorityCount * ctypes.sizeof(wintypes.DWORD)
|
||||||
|
if sid_size > header.AceSize - ctypes.sizeof(ACE_HEADER) - ctypes.sizeof(wintypes.DWORD) or not advapi32.IsValidSid(sid_pointer) or advapi32.GetLengthSid(sid_pointer) != sid_size:
|
||||||
|
raise RuntimeError("Cutter bridge ACL contains an invalid SID")
|
||||||
|
ace = ctypes.cast(ace_pointer, ctypes.POINTER(ACCESS_ALLOWED_ACE)).contents
|
||||||
|
sid_text = wintypes.LPWSTR()
|
||||||
|
if ace.Mask != 0x001F01FF or ace.Header.AceFlags != 0x03:
|
||||||
|
raise RuntimeError("Cutter bridge ACL contains an unsupported ACE")
|
||||||
|
if not advapi32.ConvertSidToStringSidW(sid_pointer, ctypes.byref(sid_text)):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
try:
|
||||||
|
if sid_text.value == expected_user:
|
||||||
|
user_allowed = True
|
||||||
|
elif sid_text.value == "S-1-5-18":
|
||||||
|
system_allowed = True
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Cutter bridge ACL allows an unrelated principal")
|
||||||
|
finally:
|
||||||
|
kernel32.LocalFree(sid_text)
|
||||||
|
if not user_allowed or not system_allowed:
|
||||||
|
raise RuntimeError("Cutter bridge ACL must allow only the current user and SYSTEM")
|
||||||
|
finally:
|
||||||
|
kernel32.LocalFree(descriptor)
|
||||||
|
|
||||||
|
|
||||||
|
def _windows_security_attributes() -> tuple[Any, Any]:
|
||||||
|
kernel32, advapi32 = _windows_api()
|
||||||
|
sid = _windows_user_sid_text()
|
||||||
|
sddl = f"O:{sid}G:{sid}D:P(A;OICI;FA;;;{sid})(A;OICI;FA;;;SY)"
|
||||||
|
descriptor = wintypes.LPVOID()
|
||||||
|
if not advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl, 1, ctypes.byref(descriptor), None):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
|
||||||
|
class SECURITY_ATTRIBUTES(ctypes.Structure):
|
||||||
|
_fields_ = [("nLength", wintypes.DWORD), ("lpSecurityDescriptor", wintypes.LPVOID), ("bInheritHandle", wintypes.BOOL)]
|
||||||
|
|
||||||
|
attributes = SECURITY_ATTRIBUTES(ctypes.sizeof(SECURITY_ATTRIBUTES), descriptor, False)
|
||||||
|
return kernel32, (descriptor, attributes)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_windows_bridge_directory(directory: Path) -> None:
|
||||||
|
directory.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if not directory.exists():
|
||||||
|
kernel32, (descriptor, attributes) = _windows_security_attributes()
|
||||||
|
try:
|
||||||
|
if not kernel32.CreateDirectoryW(str(directory), ctypes.byref(attributes)):
|
||||||
|
error = ctypes.get_last_error()
|
||||||
|
if error != 183:
|
||||||
|
raise ctypes.WinError(error)
|
||||||
|
finally:
|
||||||
|
kernel32.LocalFree(descriptor)
|
||||||
|
_verify_windows_private_path(directory, True)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_posix_bridge_directory(directory: Path) -> None:
|
||||||
|
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_DIRECTORY"):
|
||||||
|
raise RuntimeError("Cutter bridge directory requires no-follow directory handles")
|
||||||
|
descriptor = os.open(directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
||||||
|
try:
|
||||||
|
directory_info = os.fstat(descriptor)
|
||||||
|
if directory_info.st_uid != os.getuid():
|
||||||
|
raise RuntimeError("Cutter bridge directory must be owned by the current user")
|
||||||
|
os.fchmod(descriptor, 0o700)
|
||||||
|
directory_info = os.fstat(descriptor)
|
||||||
|
if stat.S_IMODE(directory_info.st_mode) != 0o700:
|
||||||
|
raise RuntimeError("Cutter bridge directory must have mode 0700")
|
||||||
|
finally:
|
||||||
|
os.close(descriptor)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_windows_descriptor(directory: Path, destination: Path, payload: bytes) -> None:
|
||||||
|
if len(payload) > MAX_DESCRIPTOR_BYTES:
|
||||||
|
raise RuntimeError("Cutter bridge descriptor exceeds its byte limit")
|
||||||
|
kernel32, (security_descriptor, attributes) = _windows_security_attributes()
|
||||||
|
temporary = directory / f".{destination.name}.{uuid.uuid4().hex}.tmp"
|
||||||
|
handle = wintypes.HANDLE()
|
||||||
|
try:
|
||||||
|
handle = kernel32.CreateFileW(str(temporary), 0x40000000 | 0x00020000 | 0x00040000,
|
||||||
|
0x1 | 0x2 | 0x4, ctypes.byref(attributes), 1, 0x80, None)
|
||||||
|
if handle == wintypes.HANDLE(-1).value:
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
_verify_windows_private_path(temporary, False)
|
||||||
|
if payload:
|
||||||
|
data = ctypes.create_string_buffer(payload)
|
||||||
|
written = wintypes.DWORD()
|
||||||
|
if not kernel32.WriteFile(handle, data, len(payload), ctypes.byref(written), None) or written.value != len(payload):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
if not kernel32.FlushFileBuffers(handle):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
kernel32.CloseHandle(handle)
|
||||||
|
handle = wintypes.HANDLE()
|
||||||
|
if not kernel32.MoveFileExW(str(temporary), str(destination), 0x1 | 0x8):
|
||||||
|
raise ctypes.WinError(ctypes.get_last_error())
|
||||||
|
finally:
|
||||||
|
if handle and handle != wintypes.HANDLE(-1).value:
|
||||||
|
kernel32.CloseHandle(handle)
|
||||||
|
kernel32.DeleteFileW(str(temporary))
|
||||||
|
kernel32.LocalFree(security_descriptor)
|
||||||
|
|
||||||
|
|
||||||
|
def _bridge_directory() -> Path:
|
||||||
|
configured = os.environ.get("REA_CUTTER_BRIDGE_DIR")
|
||||||
|
if configured:
|
||||||
|
return Path(configured).expanduser()
|
||||||
|
local_app_data = os.environ.get("LOCALAPPDATA")
|
||||||
|
if os.name == "nt" and local_app_data:
|
||||||
|
return Path(local_app_data) / "REA" / "CutterBridge"
|
||||||
|
return Path.home() / ".cache" / "rea" / "cutter-bridge"
|
||||||
|
|
||||||
|
|
||||||
|
class _RequestDispatcher(QObject):
|
||||||
|
dispatch = Signal(object, object)
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.dispatch.connect(self._handle)
|
||||||
|
self.generation = 0
|
||||||
|
self.completed_commands: dict[str, str] = {}
|
||||||
|
self._receipt_lock = threading.Lock()
|
||||||
|
self.current_file = self._read_current_file()
|
||||||
|
try:
|
||||||
|
self.cutter_version = str(cutter.core().getVersionInformation())
|
||||||
|
except Exception:
|
||||||
|
self.cutter_version = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _read_current_file() -> str | None:
|
||||||
|
try:
|
||||||
|
value = cutter.core().currentlyOpenFile()
|
||||||
|
return str(value) if value else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _handle(self, request: dict[str, Any], reply: dict[str, Any]) -> None:
|
||||||
|
kind = request.get("kind")
|
||||||
|
request_id = request.get("request_id")
|
||||||
|
if kind in ("command", "ack"):
|
||||||
|
try:
|
||||||
|
valid_request_id = (
|
||||||
|
isinstance(request_id, str)
|
||||||
|
and str(uuid.UUID(request_id)) == request_id.lower()
|
||||||
|
)
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
valid_request_id = False
|
||||||
|
if not valid_request_id:
|
||||||
|
reply.update({
|
||||||
|
"ok": False,
|
||||||
|
"error": "invalid-command-request-id",
|
||||||
|
"execution_state": "not_started",
|
||||||
|
})
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
if kind == "ack":
|
||||||
|
with self._receipt_lock:
|
||||||
|
acknowledged = self.completed_commands.pop(request_id, None) is not None
|
||||||
|
reply.update({"ok": True, "acknowledged": acknowledged})
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
if request["_cancelled"].is_set():
|
||||||
|
reply.update({
|
||||||
|
"ok": False,
|
||||||
|
"error": "request-cancelled-before-execution",
|
||||||
|
"execution_state": "not_started",
|
||||||
|
})
|
||||||
|
if kind == "command":
|
||||||
|
self._record_command_completion(request_id, "not_started")
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
self._refresh_document_identity()
|
||||||
|
if kind == "status":
|
||||||
|
with self._receipt_lock:
|
||||||
|
completed_commands = [
|
||||||
|
{"request_id": receipt_id, "execution_state": state}
|
||||||
|
for receipt_id, state in self.completed_commands.items()
|
||||||
|
]
|
||||||
|
reply.update({
|
||||||
|
"ok": True,
|
||||||
|
"session_id": request.get("session_id"),
|
||||||
|
"current_file": self.current_file,
|
||||||
|
"document_generation": self.generation,
|
||||||
|
"cutter_version": self.cutter_version,
|
||||||
|
"identity_status": "partial",
|
||||||
|
"completed_commands": completed_commands,
|
||||||
|
})
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
if kind != "command":
|
||||||
|
reply.update({"ok": False, "error": "unsupported-request-kind"})
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
if request.get("expected_generation") != self.generation:
|
||||||
|
reply.update({
|
||||||
|
"ok": False,
|
||||||
|
"error": "active-document-generation-changed",
|
||||||
|
"execution_state": "not_started",
|
||||||
|
})
|
||||||
|
self._record_command_completion(request_id, "not_started")
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
command = request.get("command")
|
||||||
|
if not isinstance(command, str) or not command.strip():
|
||||||
|
reply.update({
|
||||||
|
"ok": False,
|
||||||
|
"error": "invalid-command",
|
||||||
|
"execution_state": "not_started",
|
||||||
|
})
|
||||||
|
self._record_command_completion(request_id, "not_started")
|
||||||
|
request["_event"].set()
|
||||||
|
return
|
||||||
|
reply["execution_state"] = "running"
|
||||||
|
try:
|
||||||
|
output = cutter.cmdj(command) if request.get("json") is True else cutter.cmd(command)
|
||||||
|
command_error = None
|
||||||
|
except Exception as error:
|
||||||
|
output = None
|
||||||
|
command_error = str(error)
|
||||||
|
self._refresh_document_identity()
|
||||||
|
if command_error is None:
|
||||||
|
reply.update({
|
||||||
|
"ok": True,
|
||||||
|
"output": output,
|
||||||
|
"current_file": self.current_file,
|
||||||
|
"document_generation": self.generation,
|
||||||
|
"cutter_version": self.cutter_version,
|
||||||
|
"identity_status": "partial",
|
||||||
|
"execution_state": "complete",
|
||||||
|
})
|
||||||
|
self._record_command_completion(request_id, "complete")
|
||||||
|
else:
|
||||||
|
reply.update({
|
||||||
|
"ok": False,
|
||||||
|
"error": command_error,
|
||||||
|
"current_file": self.current_file,
|
||||||
|
"document_generation": self.generation,
|
||||||
|
"cutter_version": self.cutter_version,
|
||||||
|
"identity_status": "partial",
|
||||||
|
"execution_state": "unknown",
|
||||||
|
"message": "The command raised an exception; it may have produced partial effects. Do not retry automatically.",
|
||||||
|
})
|
||||||
|
self._record_command_completion(request_id, "failed")
|
||||||
|
request["_event"].set()
|
||||||
|
|
||||||
|
def _record_command_completion(
|
||||||
|
self, request_id: str, execution_state: str
|
||||||
|
) -> None:
|
||||||
|
with self._receipt_lock:
|
||||||
|
if request_id in self.completed_commands or len(self.completed_commands) < MAX_COMPLETION_RECEIPTS:
|
||||||
|
self.completed_commands[request_id] = execution_state
|
||||||
|
|
||||||
|
def can_accept_command(self) -> bool:
|
||||||
|
with self._receipt_lock:
|
||||||
|
return len(self.completed_commands) < MAX_COMPLETION_RECEIPTS
|
||||||
|
|
||||||
|
def _refresh_document_identity(self) -> None:
|
||||||
|
current_file = self._read_current_file()
|
||||||
|
if current_file != self.current_file:
|
||||||
|
self.current_file = current_file
|
||||||
|
self.generation += 1
|
||||||
|
|
||||||
|
|
||||||
|
class _BridgeServer(threading.Thread):
|
||||||
|
def __init__(self, dispatcher: _RequestDispatcher) -> None:
|
||||||
|
super().__init__(name="rea-cutter-bridge", daemon=True)
|
||||||
|
self.dispatcher = dispatcher
|
||||||
|
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
self.server.bind(("127.0.0.1", 0))
|
||||||
|
self.server.listen(8)
|
||||||
|
self.server.settimeout(0.5)
|
||||||
|
self.port = self.server.getsockname()[1]
|
||||||
|
self.session_id = str(uuid.uuid4())
|
||||||
|
self.token = secrets.token_urlsafe(32)
|
||||||
|
self.poisoned = False
|
||||||
|
self.connection_ordinal = 0
|
||||||
|
self.directory = _bridge_directory()
|
||||||
|
self.descriptor = self.directory / f"cutter-{os.getpid()}-{self.session_id}.json"
|
||||||
|
|
||||||
|
def publish(self) -> None:
|
||||||
|
if os.name == "nt":
|
||||||
|
_ensure_windows_bridge_directory(self.directory)
|
||||||
|
else:
|
||||||
|
_ensure_posix_bridge_directory(self.directory)
|
||||||
|
if os.name == "nt" and (self.directory.is_symlink() or not self.directory.is_dir()):
|
||||||
|
raise RuntimeError("Cutter bridge directory must be a real directory")
|
||||||
|
payload = {
|
||||||
|
"session_id": self.session_id,
|
||||||
|
"pid": os.getpid(),
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": self.port,
|
||||||
|
"token": self.token,
|
||||||
|
"document_generation": self.dispatcher.generation,
|
||||||
|
"current_file": self.dispatcher.current_file,
|
||||||
|
"cutter_version": self.dispatcher.cutter_version,
|
||||||
|
"identity_status": "partial",
|
||||||
|
}
|
||||||
|
if os.name == "nt":
|
||||||
|
_write_windows_descriptor(self.directory, self.descriptor, json.dumps(payload).encode("utf-8"))
|
||||||
|
return
|
||||||
|
descriptor, temporary_path = tempfile.mkstemp(prefix=f".{self.descriptor.name}.", dir=self.directory)
|
||||||
|
try:
|
||||||
|
os.fchmod(descriptor, 0o600)
|
||||||
|
descriptor_info = os.fstat(descriptor)
|
||||||
|
if descriptor_info.st_uid != os.getuid() or stat.S_IMODE(descriptor_info.st_mode) & 0o077:
|
||||||
|
raise RuntimeError("Cutter bridge descriptor must be owned by the current user with mode 0600")
|
||||||
|
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
|
||||||
|
stream.write(json.dumps(payload))
|
||||||
|
stream.flush()
|
||||||
|
os.fsync(stream.fileno())
|
||||||
|
os.replace(temporary_path, self.descriptor)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
os.close(descriptor)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.unlink(temporary_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
raise
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
try:
|
||||||
|
self.publish()
|
||||||
|
except Exception:
|
||||||
|
self.server.close()
|
||||||
|
return
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
connection, _ = self.server.accept()
|
||||||
|
except socket.timeout:
|
||||||
|
if getattr(self, "stopping", False):
|
||||||
|
break
|
||||||
|
continue
|
||||||
|
except OSError:
|
||||||
|
break
|
||||||
|
self.connection_ordinal += 1
|
||||||
|
connection_ordinal = self.connection_ordinal
|
||||||
|
with connection:
|
||||||
|
connection.settimeout(AUTH_FRAME_TIMEOUT_SECONDS)
|
||||||
|
authenticated = False
|
||||||
|
try:
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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):
|
||||||
|
self._send(connection, {"ok": False, "error": "authentication-failed"})
|
||||||
|
continue
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
except socket.timeout:
|
||||||
|
if authenticated:
|
||||||
|
self._send_authenticated(connection, {
|
||||||
|
"ok": False,
|
||||||
|
"error": "request-timeout",
|
||||||
|
"execution_state": "unknown",
|
||||||
|
"message": "The command may have produced partial or persistent effects. Do not retry automatically.",
|
||||||
|
}, 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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _receive_frame(connection: socket.socket) -> dict[str, Any]:
|
||||||
|
data = bytearray()
|
||||||
|
deadline = time.monotonic() + AUTH_FRAME_TIMEOUT_SECONDS
|
||||||
|
while b"\n" not in data:
|
||||||
|
if len(data) >= MAX_FRAME_BYTES:
|
||||||
|
raise ValueError("frame-limit-exceeded")
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
raise socket.timeout("authentication frame timed out")
|
||||||
|
connection.settimeout(remaining)
|
||||||
|
part = connection.recv(min(65536, MAX_FRAME_BYTES + 1 - len(data)))
|
||||||
|
if not part:
|
||||||
|
raise ValueError("incomplete authentication frame")
|
||||||
|
data.extend(part)
|
||||||
|
line, remainder = bytes(data).split(b"\n", 1)
|
||||||
|
if len(line) > MAX_FRAME_BYTES or remainder:
|
||||||
|
raise ValueError("invalid framed request")
|
||||||
|
value = json.loads(line)
|
||||||
|
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:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
connection.sendall(encoded)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _encode_authenticated_response(self, value: dict[str, Any], client_nonce: str, server_nonce: str) -> bytes | 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)
|
||||||
|
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'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _truncated_response(value: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
response: dict[str, Any] = {
|
||||||
|
"ok": value.get("ok") is True,
|
||||||
|
"output": None,
|
||||||
|
"execution_state": value.get("execution_state", "unknown"),
|
||||||
|
"output_truncated": True,
|
||||||
|
"message": "The command result exceeded the bridge frame limit; completion state is preserved and output was omitted.",
|
||||||
|
}
|
||||||
|
for key in ("current_file", "document_generation", "cutter_version", "identity_status"):
|
||||||
|
if key in value:
|
||||||
|
response[key] = value[key]
|
||||||
|
if value.get("ok") is not True and isinstance(value.get("error"), str):
|
||||||
|
response["error"] = value["error"]
|
||||||
|
return response
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _send(connection: socket.socket, value: dict[str, Any]) -> None:
|
||||||
|
if not _within_response_limit(value):
|
||||||
|
value = {
|
||||||
|
"ok": False,
|
||||||
|
"error": "response-limit-exceeded",
|
||||||
|
"execution_state": value.get("execution_state", "unknown"),
|
||||||
|
"output_truncated": True,
|
||||||
|
"message": "The command completed or may have completed, but its result exceeded the bridge response limit. Do not retry automatically.",
|
||||||
|
}
|
||||||
|
parts: list[bytes] = []
|
||||||
|
encoded_size = 0
|
||||||
|
for part in json.JSONEncoder(ensure_ascii=False).iterencode(value):
|
||||||
|
encoded_part = part.encode("utf-8")
|
||||||
|
encoded_size += len(encoded_part)
|
||||||
|
if encoded_size > MAX_FRAME_BYTES:
|
||||||
|
fallback = {
|
||||||
|
"ok": False,
|
||||||
|
"error": "response-limit-exceeded",
|
||||||
|
"execution_state": value.get("execution_state", "unknown"),
|
||||||
|
"output_truncated": True,
|
||||||
|
"message": "The command completed or may have completed, but its result exceeded the bridge response limit. Do not retry automatically.",
|
||||||
|
}
|
||||||
|
parts = [json.dumps(fallback, separators=(",", ":")).encode("utf-8")]
|
||||||
|
break
|
||||||
|
parts.append(encoded_part)
|
||||||
|
encoded = b"".join(parts)
|
||||||
|
try:
|
||||||
|
connection.sendall(encoded + b"\n")
|
||||||
|
except OSError:
|
||||||
|
# A disconnected caller must not terminate the bridge thread or
|
||||||
|
# prevent later commands from being served.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.stopping = True
|
||||||
|
try:
|
||||||
|
self.server.close()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReaCutterPlugin(cutter.CutterPlugin):
|
||||||
|
name = "REA Local Bridge"
|
||||||
|
description = "Local REA analysis bridge for the active Cutter session"
|
||||||
|
version = "1.0"
|
||||||
|
author = "REA"
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self._dispatcher: _RequestDispatcher | None = None
|
||||||
|
self._bridge: _BridgeServer | None = None
|
||||||
|
|
||||||
|
def setupPlugin(self) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
|
def setupInterface(self, main: Any) -> None:
|
||||||
|
self._dispatcher = _RequestDispatcher()
|
||||||
|
self._bridge = _BridgeServer(self._dispatcher)
|
||||||
|
self._bridge.start()
|
||||||
|
|
||||||
|
def terminate(self) -> None:
|
||||||
|
if self._bridge is not None:
|
||||||
|
self._bridge.stop()
|
||||||
|
self._bridge.join(timeout=2)
|
||||||
|
self._bridge = None
|
||||||
|
self._dispatcher = None
|
||||||
|
|
||||||
|
|
||||||
|
def create_cutter_plugin() -> ReaCutterPlugin:
|
||||||
|
return ReaCutterPlugin()
|
||||||
|
|
||||||
|
|
||||||
|
def _within_response_limit(value: Any) -> bool:
|
||||||
|
pending = [value]
|
||||||
|
seen: set[int] = set()
|
||||||
|
size = 0
|
||||||
|
while pending:
|
||||||
|
current = pending.pop()
|
||||||
|
if isinstance(current, str):
|
||||||
|
size += 2
|
||||||
|
if current.isascii():
|
||||||
|
size += len(current)
|
||||||
|
size += current.count('"') + current.count("\\")
|
||||||
|
size += sum(5 for character in current if ord(character) < 0x20)
|
||||||
|
else:
|
||||||
|
size += 4 * len(current)
|
||||||
|
size += current.count('"') + current.count("\\")
|
||||||
|
size += sum(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)
|
||||||
|
if size > MAX_FRAME_BYTES:
|
||||||
|
return False
|
||||||
|
pending.extend(current.keys())
|
||||||
|
pending.extend(current.values())
|
||||||
|
elif isinstance(current, (list, tuple)):
|
||||||
|
identity = id(current)
|
||||||
|
if identity in seen:
|
||||||
|
return False
|
||||||
|
seen.add(identity)
|
||||||
|
size += 2 + 2 * len(current)
|
||||||
|
if size > MAX_FRAME_BYTES:
|
||||||
|
return False
|
||||||
|
pending.extend(current)
|
||||||
|
else:
|
||||||
|
size += 16
|
||||||
|
if size > MAX_FRAME_BYTES:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
+700
-148
File diff suppressed because it is too large.
Load diff
+14
-3
@@ -644,7 +644,7 @@ def _analyze_function(document, params):
|
|||||||
"end": _hex(_basic_block_end(procedure, block)),
|
"end": _hex(_basic_block_end(procedure, block)),
|
||||||
"successors": sorted(set(successors), key=lambda value: int(value, 16)),
|
"successors": sorted(set(successors), key=lambda value: int(value, 16)),
|
||||||
})
|
})
|
||||||
pseudo = _pseudocode(document, procedure) or ""
|
pseudo = _pseudocode(document, procedure)
|
||||||
assembly_lines = _assembly(procedure).splitlines()
|
assembly_lines = _assembly(procedure).splitlines()
|
||||||
callers = sorted((_procedure_identity(item) for item in procedure.getAllCallerProcedures()), key=lambda item: int(item["address"], 16))
|
callers = sorted((_procedure_identity(item) for item in procedure.getAllCallerProcedures()), key=lambda item: int(item["address"], 16))
|
||||||
callees = sorted((_procedure_identity(item) for item in procedure.getAllCalleeProcedures()), key=lambda item: int(item["address"], 16))
|
callees = sorted((_procedure_identity(item) for item in procedure.getAllCalleeProcedures()), key=lambda item: int(item["address"], 16))
|
||||||
@@ -706,7 +706,7 @@ def _analyze_function(document, params):
|
|||||||
"signature": procedure.signatureString(),
|
"signature": procedure.signatureString(),
|
||||||
"locals": _procedure_locals(procedure),
|
"locals": _procedure_locals(procedure),
|
||||||
},
|
},
|
||||||
"pseudocode": pseudo,
|
"pseudocode": pseudo or "",
|
||||||
"assembly": assembly_lines,
|
"assembly": assembly_lines,
|
||||||
"comments": comments,
|
"comments": comments,
|
||||||
"callers": callers, "callees": callees,
|
"callers": callers, "callees": callees,
|
||||||
@@ -718,6 +718,12 @@ def _analyze_function(document, params):
|
|||||||
"basic_blocks": blocks,
|
"basic_blocks": blocks,
|
||||||
"native_api": None,
|
"native_api": None,
|
||||||
"native_value_flow": None,
|
"native_value_flow": None,
|
||||||
|
"facet_availability": {
|
||||||
|
"pseudocode": {"available": True} if pseudo is not None else _unavailable("Hopper did not supply pseudocode."),
|
||||||
|
**{facet: {"available": True} for facet in (
|
||||||
|
"assembly", "comments", "callers", "callees", "references", "strings_names", "cfg"
|
||||||
|
)},
|
||||||
|
},
|
||||||
"limitations": [
|
"limitations": [
|
||||||
"Native CallReference classifications are observed where available; detailed reference flags remain unknown.",
|
"Native CallReference classifications are observed where available; detailed reference flags remain unknown.",
|
||||||
"Hopper's public Python API does not expose equivalent external or thunk classification in this dossier.",
|
"Hopper's public Python API does not expose equivalent external or thunk classification in this dossier.",
|
||||||
@@ -730,7 +736,12 @@ def _analyze_function(document, params):
|
|||||||
def _dispatch(method, params):
|
def _dispatch(method, params):
|
||||||
"""Dispatch only the closed operation set implemented by REA's public tools."""
|
"""Dispatch only the closed operation set implemented by REA's public tools."""
|
||||||
if method == "health":
|
if method == "health":
|
||||||
return {"name": "REA Hopper bridge", "version": "1.0.0", "run_id": REA_RUN_ID}
|
return {
|
||||||
|
"name": "REA Hopper bridge",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"run_id": REA_RUN_ID,
|
||||||
|
"process_id": os.getpid(),
|
||||||
|
}
|
||||||
if method in ("shutdown", "shutdown_document"):
|
if method in ("shutdown", "shutdown_document"):
|
||||||
document = _session_document()
|
document = _session_document()
|
||||||
if document is None:
|
if document is None:
|
||||||
|
|||||||
+188
-12
@@ -6,11 +6,13 @@ The containing mitmdump process and its configuration directory are REA-owned.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import resource
|
import resource
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from urllib.parse import unquote_to_bytes
|
||||||
|
|
||||||
from mitmproxy import ctx, version
|
from mitmproxy import ctx, version
|
||||||
from mitmproxy.io import tnetstring
|
from mitmproxy.io import tnetstring
|
||||||
@@ -41,6 +43,167 @@ class BoundedReader:
|
|||||||
return self.handle.read(count)
|
return self.handle.read(count)
|
||||||
|
|
||||||
|
|
||||||
|
SPECIAL_SCHEMES = {"ftp", "file", "http", "https", "ws", "wss"}
|
||||||
|
SCHEME_PREFIX = r"^[\x00-\x20]*([a-z][a-z0-9+.-]*):"
|
||||||
|
NETWORK_PREFIX = r"^[\x00-\x20]*[/\\]{2}"
|
||||||
|
URL_IGNORABLE_CONTROLS = "\t\n\r"
|
||||||
|
URL_CONTROL_TRANSLATION = {ord(char): None for char in URL_IGNORABLE_CONTROLS}
|
||||||
|
C0_CONTROL_OR_SPACE = "".join(chr(code) for code in range(0x21))
|
||||||
|
FORBIDDEN_DOMAIN_CODE_POINTS = set(C0_CONTROL_OR_SPACE) | set("#%/:<>?@[\\]^|\x7f")
|
||||||
|
|
||||||
|
|
||||||
|
def authority_start(text, base_scheme=None):
|
||||||
|
"""Return where an authority begins and whether it is special, or None.
|
||||||
|
|
||||||
|
Follows WHATWG URL parsing as src/domain/recordedTransportUrl.ts does:
|
||||||
|
special schemes (and scheme-relative references) skip every leading "/"
|
||||||
|
or "\\" before the authority, so `https://\\user:pass@host` still has
|
||||||
|
userinfo. File URLs never carry userinfo: an "@" in a file host makes
|
||||||
|
WHATWG reject the whole URL, so there are no credentials to remove.
|
||||||
|
"""
|
||||||
|
scheme = re.match(SCHEME_PREFIX, text, flags=re.I)
|
||||||
|
name = scheme.group(1).lower() if scheme is not None else None
|
||||||
|
if name == "file" or (name is None and base_scheme == "file"):
|
||||||
|
return None
|
||||||
|
if name is not None and name in SPECIAL_SCHEMES:
|
||||||
|
# A same-scheme Location without two slash-equivalent separators is
|
||||||
|
# relative to the recorded request, so its @ belongs to the path.
|
||||||
|
following = text[scheme.end():scheme.end() + 2]
|
||||||
|
if name == base_scheme and (len(following) < 2 or any(char not in "/\\" for char in following)):
|
||||||
|
return None
|
||||||
|
start = scheme.end()
|
||||||
|
else:
|
||||||
|
if scheme is not None:
|
||||||
|
if text[scheme.end():scheme.end() + 2] != "//":
|
||||||
|
return None
|
||||||
|
return scheme.end() + 2, False
|
||||||
|
# A non-special base treats backslashes as authority data and only
|
||||||
|
# recognizes two forward slashes as a network reference.
|
||||||
|
if base_scheme is not None and base_scheme not in SPECIAL_SCHEMES:
|
||||||
|
prefix = re.match(r"^[\x00-\x20]*//", text)
|
||||||
|
return None if prefix is None else (prefix.end(), False)
|
||||||
|
prefix = re.match(NETWORK_PREFIX, text)
|
||||||
|
if prefix is None:
|
||||||
|
return None
|
||||||
|
start = prefix.end()
|
||||||
|
while start < len(text) and text[start] in "/\\":
|
||||||
|
start += 1
|
||||||
|
return start, True
|
||||||
|
|
||||||
|
|
||||||
|
IPV4_DIGITS = {8: "01234567", 10: "0123456789", 16: "0123456789abcdefABCDEF"}
|
||||||
|
|
||||||
|
|
||||||
|
def ipv4_number(part):
|
||||||
|
"""WHATWG IPv4 number parser: decimal, 0x hex or 0-prefixed octal, else None."""
|
||||||
|
if part == "":
|
||||||
|
return None
|
||||||
|
radix, digits = 10, part
|
||||||
|
if part[:2] in ("0x", "0X"):
|
||||||
|
radix, digits = 16, part[2:]
|
||||||
|
elif len(part) > 1 and part[0] == "0":
|
||||||
|
radix, digits = 8, part[1:]
|
||||||
|
if digits == "":
|
||||||
|
return 0
|
||||||
|
if any(char not in IPV4_DIGITS[radix] for char in digits):
|
||||||
|
return None
|
||||||
|
# Bound decimal text before int(): Python limits decimal conversions to
|
||||||
|
# 4300 digits, and anything past 10 significant digits exceeds IPv4.
|
||||||
|
significant = digits.lstrip("0")
|
||||||
|
if radix == 10 and len(significant) > 10:
|
||||||
|
return 2**32
|
||||||
|
return int(digits, radix)
|
||||||
|
|
||||||
|
|
||||||
|
def domain_parses(domain):
|
||||||
|
"""Apply WHATWG's ends-in-a-number check and IPv4 parser to a domain."""
|
||||||
|
parts = domain.split(".")
|
||||||
|
if parts[-1] == "" and len(parts) > 1:
|
||||||
|
parts.pop()
|
||||||
|
last = parts[-1]
|
||||||
|
if not ((last != "" and last.isascii() and last.isdigit()) or ipv4_number(last) is not None):
|
||||||
|
return True
|
||||||
|
numbers = [ipv4_number(part) for part in parts]
|
||||||
|
if len(parts) > 4 or any(number is None for number in numbers):
|
||||||
|
return False
|
||||||
|
return all(number <= 255 for number in numbers[:-1]) and numbers[-1] < 256 ** (5 - len(numbers))
|
||||||
|
|
||||||
|
|
||||||
|
def special_host_parses(host_port):
|
||||||
|
"""Whether WHATWG could accept a special authority's host and port.
|
||||||
|
|
||||||
|
A rejected host fails the whole URL, so it has no credentials to remove.
|
||||||
|
This mirrors the forbidden-code-point, empty-host, IPv4, IPv6 and port
|
||||||
|
rules for ASCII hosts. UTS #46 mapping is not modeled: a non-ASCII host
|
||||||
|
may map to an accepted one (fullwidth digits, ideographic full stops,
|
||||||
|
ignored code points), so it is treated as accepted and still redacts.
|
||||||
|
"""
|
||||||
|
if host_port.startswith("["):
|
||||||
|
host_end = host_port.find("]") + 1
|
||||||
|
literal = host_port[1 : host_end - 1]
|
||||||
|
if host_end == 0 or "%" in literal:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
ipaddress.IPv6Address(literal)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
colon = host_port.find(":")
|
||||||
|
host_end = len(host_port) if colon < 0 else colon
|
||||||
|
host = unquote_to_bytes(host_port[:host_end]).decode("utf-8", errors="replace")
|
||||||
|
# Mapping never removes ASCII, so these rejections hold either way.
|
||||||
|
if host == "" or any(char in FORBIDDEN_DOMAIN_CODE_POINTS for char in host):
|
||||||
|
return False
|
||||||
|
if host.isascii() and not domain_parses(host.lower()):
|
||||||
|
return False
|
||||||
|
port = host_port[host_end:]
|
||||||
|
if port == "":
|
||||||
|
return True
|
||||||
|
digits = port[1:]
|
||||||
|
if port[0] != ":" or not (digits == "" or (digits.isascii() and digits.isdigit())):
|
||||||
|
return False
|
||||||
|
# Bound the text first: int() refuses decimal strings over 4300 digits.
|
||||||
|
significant = digits.lstrip("0")
|
||||||
|
return len(significant) <= 5 and int(significant or "0") <= 65535
|
||||||
|
|
||||||
|
|
||||||
|
def without_userinfo(value, base_scheme=None):
|
||||||
|
"""Remove an authority's userinfo using the WHATWG authority boundary.
|
||||||
|
|
||||||
|
Special schemes and scheme-relative references also end the authority
|
||||||
|
at a backslash, so later path text containing "@" is not userinfo.
|
||||||
|
"""
|
||||||
|
binary = isinstance(value, bytes)
|
||||||
|
text = value.decode("latin-1") if binary else value
|
||||||
|
# WHATWG removes these controls before interpreting scheme/separators.
|
||||||
|
# Keep that parse view separate from the exact source spelling and bytes.
|
||||||
|
# It also drops trailing C0 controls and spaces; offsets are unchanged.
|
||||||
|
parsed_text = text.translate(URL_CONTROL_TRANSLATION).rstrip(C0_CONTROL_OR_SPACE)
|
||||||
|
located = authority_start(parsed_text, base_scheme)
|
||||||
|
if located is None:
|
||||||
|
return value
|
||||||
|
start, special = located
|
||||||
|
terminators = "/?#\\" if special else "/?#"
|
||||||
|
end = start
|
||||||
|
while end < len(parsed_text) and parsed_text[end] not in terminators:
|
||||||
|
end += 1
|
||||||
|
at = parsed_text.rfind("@", start, end)
|
||||||
|
if at < start or (special and not special_host_parses(parsed_text[at + 1 : end])):
|
||||||
|
return value
|
||||||
|
parsed_offset = 0
|
||||||
|
raw_start = 0
|
||||||
|
for raw_offset, char in enumerate(text):
|
||||||
|
if char in URL_IGNORABLE_CONTROLS:
|
||||||
|
continue
|
||||||
|
if parsed_offset == start:
|
||||||
|
raw_start = raw_offset
|
||||||
|
if parsed_offset == at:
|
||||||
|
safe = text[:raw_start] + text[raw_offset + 1 :]
|
||||||
|
return safe.encode("latin-1") if binary else safe
|
||||||
|
parsed_offset += 1
|
||||||
|
raise CaptureFailure("decoder", "Normalized URL authority could not be bound to its source.")
|
||||||
|
|
||||||
|
|
||||||
def project_record(state, secrets):
|
def project_record(state, secrets):
|
||||||
binaries, numbers, redactions = [], [], []
|
binaries, numbers, redactions = [], [], []
|
||||||
byte_secrets = []
|
byte_secrets = []
|
||||||
@@ -58,16 +221,23 @@ def project_record(state, secrets):
|
|||||||
return None
|
return None
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def transport_value(value, path, transport_url):
|
def transport_value(value, path, transport_url, base_scheme):
|
||||||
if not (transport_url or re.fullmatch(r"/(?:backup/)*request/(?:path|authority)", path)):
|
if not (transport_url or re.fullmatch(r"/(?:backup/)*request/(?:path|authority)", path)):
|
||||||
return value, False
|
return value, False
|
||||||
binary = isinstance(value, bytes)
|
binary = isinstance(value, bytes)
|
||||||
pattern = rb"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@" if binary else r"^([\x00-\x20]*(?:[a-z][a-z0-9+.-]*:)?//)[^/?#]*@"
|
|
||||||
replacement = rb"\1" if binary else r"\1"
|
|
||||||
separator = b"@" if binary else "@"
|
separator = b"@" if binary else "@"
|
||||||
safe_url = re.sub(pattern, replacement, value, flags=re.I)
|
if transport_url:
|
||||||
if path.endswith("/authority") and separator in safe_url:
|
safe_url = without_userinfo(value, base_scheme)
|
||||||
safe_url = safe_url.rsplit(separator, 1)[1]
|
elif path.endswith("/authority"):
|
||||||
|
safe_url = value.rsplit(separator, 1)[-1]
|
||||||
|
else:
|
||||||
|
# Native request.path is origin-form unless it explicitly carries
|
||||||
|
# a scheme (absolute-form). Leading separators are path evidence,
|
||||||
|
# not a header URL reference's network authority.
|
||||||
|
text = value.decode("latin-1") if binary else value
|
||||||
|
safe_url = without_userinfo(value) if re.match(
|
||||||
|
SCHEME_PREFIX, text.translate(URL_CONTROL_TRANSLATION), flags=re.I
|
||||||
|
) else value
|
||||||
changed = safe_url != value
|
changed = safe_url != value
|
||||||
if changed:
|
if changed:
|
||||||
redactions.append({"pointer": path, "reason": "transport-credential"})
|
redactions.append({"pointer": path, "reason": "transport-credential"})
|
||||||
@@ -80,7 +250,7 @@ def project_record(state, secrets):
|
|||||||
del numbers[before[1]:]
|
del numbers[before[1]:]
|
||||||
del redactions[before[2]:]
|
del redactions[before[2]:]
|
||||||
|
|
||||||
def visit(value, path, depth=0, credential=False, transport_url=False):
|
def visit(value, path, depth=0, credential=False, transport_url=False, base_scheme=None):
|
||||||
if depth > 64:
|
if depth > 64:
|
||||||
raise CaptureFailure("input-limit", "Native capture exceeds the 64-level evidence nesting budget.", path)
|
raise CaptureFailure("input-limit", "Native capture exceeds the 64-level evidence nesting budget.", path)
|
||||||
if credential:
|
if credential:
|
||||||
@@ -90,7 +260,7 @@ def project_record(state, secrets):
|
|||||||
binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted", "content_base64": None, "bytes": None, "sha256": None})
|
binaries.append({"pointer": path, "representation": "producer-bytes", "state": "redacted", "content_base64": None, "bytes": None, "sha256": None})
|
||||||
return None
|
return None
|
||||||
if isinstance(value, bytes):
|
if isinstance(value, bytes):
|
||||||
safe_bytes, credential_url = transport_value(value, path, transport_url)
|
safe_bytes, credential_url = transport_value(value, path, transport_url, base_scheme)
|
||||||
content_base64 = base64.b64encode(value).decode("ascii")
|
content_base64 = base64.b64encode(value).decode("ascii")
|
||||||
digest = hashlib.sha256(value).hexdigest()
|
digest = hashlib.sha256(value).hexdigest()
|
||||||
hidden = any(secret in value or secret in safe_bytes for secret in byte_secrets) or any(secret in content_base64 or secret in digest or secret in str(len(value)) for secret in secrets)
|
hidden = any(secret in value or secret in safe_bytes for secret in byte_secrets) or any(secret in content_base64 or secret in digest or secret in str(len(value)) for secret in secrets)
|
||||||
@@ -107,7 +277,7 @@ def project_record(state, secrets):
|
|||||||
return None
|
return None
|
||||||
return safe_text
|
return safe_text
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
safe_text, _ = transport_value(value, path, transport_url)
|
safe_text, _ = transport_value(value, path, transport_url, base_scheme)
|
||||||
return redact(safe_text, path, value)
|
return redact(safe_text, path, value)
|
||||||
if isinstance(value, bool) or value is None:
|
if isinstance(value, bool) or value is None:
|
||||||
return value
|
return value
|
||||||
@@ -125,10 +295,16 @@ def project_record(state, secrets):
|
|||||||
for index, field in enumerate(value):
|
for index, field in enumerate(value):
|
||||||
if not isinstance(field, (list, tuple)) or len(field) != 2 or not isinstance(field[0], bytes):
|
if not isinstance(field, (list, tuple)) or len(field) != 2 or not isinstance(field[0], bytes):
|
||||||
raise CaptureFailure("format", "Native header/trailer is not an ordered byte pair.", pointer(path, index))
|
raise CaptureFailure("format", "Native header/trailer is not an ordered byte pair.", pointer(path, index))
|
||||||
result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"})])
|
result.append([visit(field[0], pointer(pointer(path, index), 0), depth + 2), visit(field[1], pointer(pointer(path, index), 1), depth + 2, field[0].lower() in CREDENTIALS, field[0].lower() in {b"location", b"referer", b"origin"}, base_scheme if field[0].lower() == b"location" else None)])
|
||||||
return result
|
return result
|
||||||
return [visit(item, pointer(path, index), depth + 1) for index, item in enumerate(value)]
|
return [visit(item, pointer(path, index), depth + 1, base_scheme=base_scheme) for index, item in enumerate(value)]
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
|
if path == "" or re.fullmatch(r"/backup(?:/backup)*", path):
|
||||||
|
request = value.get("request")
|
||||||
|
scheme = request.get("scheme") if isinstance(request, dict) else None
|
||||||
|
if isinstance(scheme, bytes):
|
||||||
|
scheme = scheme.decode("latin-1")
|
||||||
|
base_scheme = scheme.lower() if isinstance(scheme, str) and re.fullmatch(r"[a-z][a-z0-9+.-]*", scheme, flags=re.I) else None
|
||||||
result = {}
|
result = {}
|
||||||
for key, item in value.items():
|
for key, item in value.items():
|
||||||
if not isinstance(key, str):
|
if not isinstance(key, str):
|
||||||
@@ -139,7 +315,7 @@ def project_record(state, secrets):
|
|||||||
validate_omitted(item, pointer(path, key), depth + 1)
|
validate_omitted(item, pointer(path, key), depth + 1)
|
||||||
redactions.append({"pointer": path, "reason": "explicit-sensitive-value", "scope": "property-name"})
|
redactions.append({"pointer": path, "reason": "explicit-sensitive-value", "scope": "property-name"})
|
||||||
continue
|
continue
|
||||||
result[key] = visit(item, pointer(path, key), depth + 1)
|
result[key] = visit(item, pointer(path, key), depth + 1, base_scheme=base_scheme)
|
||||||
return result
|
return result
|
||||||
raise CaptureFailure("decoder", "Native parser returned an unexpected value type: " + type(value).__name__ + ".", path)
|
raise CaptureFailure("decoder", "Native parser returned an unexpected value type: " + type(value).__name__ + ".", path)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
"""Source-owned tests for the mitmproxy adapter's record projection."""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
def load_capture():
|
||||||
|
"""Load the adapter with fresh mitmproxy stand-ins scoped to its import."""
|
||||||
|
fake_mitmproxy = types.ModuleType("mitmproxy")
|
||||||
|
fake_mitmproxy.ctx = types.SimpleNamespace()
|
||||||
|
fake_mitmproxy.version = types.SimpleNamespace(VERSION="12.2.3")
|
||||||
|
fake_io = types.ModuleType("mitmproxy.io")
|
||||||
|
fake_io.tnetstring = types.ModuleType("mitmproxy.io.tnetstring")
|
||||||
|
stand_ins = {
|
||||||
|
"mitmproxy": fake_mitmproxy,
|
||||||
|
"mitmproxy.io": fake_io,
|
||||||
|
"mitmproxy.io.tnetstring": fake_io.tnetstring,
|
||||||
|
}
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"rea_mitmproxy_capture", os.path.join(os.path.dirname(__file__), "capture.py")
|
||||||
|
)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
with mock.patch.dict(sys.modules, stand_ins):
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
capture = load_capture()
|
||||||
|
|
||||||
|
|
||||||
|
def location(value, scheme=None):
|
||||||
|
state = {"response": {"headers": [[b"Location", value]]}}
|
||||||
|
if scheme is not None:
|
||||||
|
state["request"] = {"scheme": scheme}
|
||||||
|
record = capture.project_record(state, [])
|
||||||
|
sidecar = next(
|
||||||
|
field for field in record["binary_fields"] if field["pointer"] == "/response/headers/0/1"
|
||||||
|
)
|
||||||
|
return record["reported"]["response"]["headers"][0][1], sidecar, record["redactions"]
|
||||||
|
|
||||||
|
|
||||||
|
class TransportUserinfoTests(unittest.TestCase):
|
||||||
|
def test_ignorable_controls_cannot_hide_authority_credentials(self):
|
||||||
|
for control in [b"\t", b"\n", b"\r"]:
|
||||||
|
for value in [b"https://" + control + b"/user:pass@host/path", b"ht" + control + b"tps://user:" + control + b"pass@host/path"]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
text, sidecar, redactions = location(value, b"https")
|
||||||
|
self.assertNotIn("user", text)
|
||||||
|
self.assertNotIn("pass", text)
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
|
||||||
|
def test_mixed_network_separators_remove_credentials(self):
|
||||||
|
for prefix in [b"\\\\", b"/\\", b"\\/"]:
|
||||||
|
with self.subTest(prefix=prefix):
|
||||||
|
text, sidecar, redactions = location(prefix + b"user:pass@host/path", b"https")
|
||||||
|
self.assertEqual(text, prefix.decode("ascii") + "host/path")
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
|
||||||
|
def test_non_special_base_keeps_backslashes_inside_network_authorities(self):
|
||||||
|
text, sidecar, redactions = location(b"//user\\name:pass@host/x", b"foo")
|
||||||
|
self.assertEqual(text, "//host/x")
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
text, sidecar, redactions = location(b"/\\invoice@customer", b"foo")
|
||||||
|
self.assertEqual(text, "/\\invoice@customer")
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_native_request_paths_preserve_origin_form_and_redact_absolute_form(self):
|
||||||
|
for value in [b"/\\invoice@customer", b"//invoice@customer", b"\\\\invoice@customer"]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
record = capture.project_record({"request": {"path": value}}, [])
|
||||||
|
self.assertEqual(record["reported"]["request"]["path"], value.decode("ascii"))
|
||||||
|
self.assertEqual(record["binary_fields"][0]["state"], "retained")
|
||||||
|
self.assertEqual(record["redactions"], [])
|
||||||
|
record = capture.project_record({"request": {"path": b"https://user:pass@host/x"}}, [])
|
||||||
|
self.assertEqual(record["reported"]["request"]["path"], "https://host/x")
|
||||||
|
self.assertEqual(record["binary_fields"][0]["state"], "redacted")
|
||||||
|
|
||||||
|
def test_same_scheme_locations_preserve_relative_path_text_and_bytes(self):
|
||||||
|
for value in [b"https:invoice@customer", b"https:/invoice@customer", b"https:\\invoice@customer"]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
text, sidecar, redactions = location(value, b"https")
|
||||||
|
self.assertEqual(text, value.decode("ascii"))
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_location_authorities_still_remove_credentials_with_a_base_scheme(self):
|
||||||
|
for value, scheme, expected in [
|
||||||
|
(b"https://user:pass@host/path", b"https", "https://host/path"),
|
||||||
|
(b"https:\\\\user:pass@host/path", b"https", "https:\\\\host/path"),
|
||||||
|
(b"https:user:pass@host/path", b"http", "https:host/path"),
|
||||||
|
]:
|
||||||
|
with self.subTest(value=value, scheme=scheme):
|
||||||
|
text, sidecar, redactions = location(value, scheme)
|
||||||
|
self.assertEqual(text, expected)
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
|
||||||
|
def test_backup_location_uses_its_own_request_scheme(self):
|
||||||
|
record = capture.project_record({
|
||||||
|
"request": {"scheme": b"http"},
|
||||||
|
"backup": {
|
||||||
|
"request": {"scheme": b"https"},
|
||||||
|
"response": {"headers": [[b"Location", b"https:invoice@customer"]]},
|
||||||
|
},
|
||||||
|
}, [])
|
||||||
|
self.assertEqual(record["reported"]["backup"]["response"]["headers"][0][1], "https:invoice@customer")
|
||||||
|
self.assertEqual(record["redactions"], [])
|
||||||
|
|
||||||
|
def test_backslash_ends_a_special_scheme_authority(self):
|
||||||
|
text, sidecar, redactions = location(b"https://example.test\\part@tail")
|
||||||
|
self.assertEqual(text, "https://example.test\\part@tail")
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertIsNotNone(sidecar["content_base64"])
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_userinfo_before_a_backslash_is_still_removed(self):
|
||||||
|
text, sidecar, redactions = location(b"https://user:pw@example.test\\path")
|
||||||
|
self.assertEqual(text, "https://example.test\\path")
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(
|
||||||
|
redactions,
|
||||||
|
[{"pointer": "/response/headers/0/1", "reason": "transport-credential"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ordinary_userinfo_is_removed(self):
|
||||||
|
text, _sidecar, _redactions = location(b"https://user:pw@example.test/a@b")
|
||||||
|
self.assertEqual(text, "https://example.test/a@b")
|
||||||
|
|
||||||
|
def test_empty_userinfo_marker_is_removed(self):
|
||||||
|
self.assertEqual(
|
||||||
|
capture.without_userinfo("https://@host"), "https://host"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_special_schemes_skip_leading_slashes_before_userinfo(self):
|
||||||
|
for value, expected in [
|
||||||
|
(b"https://\\user:pass@host/path", b"https://\\host/path"),
|
||||||
|
(b"https:\\\\user:pass@host/path", b"https:\\\\host/path"),
|
||||||
|
(b"https:user:pass@host", b"https:host"),
|
||||||
|
(b"//\\user:pass@host", b"//\\host"),
|
||||||
|
]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
self.assertEqual(capture.without_userinfo(value), expected)
|
||||||
|
text, sidecar, redactions = location(b"https://\\user:pass@host/path")
|
||||||
|
self.assertEqual(text, "https://\\host/path")
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
|
||||||
|
def test_file_base_keeps_paths_after_the_fixed_network_boundary(self):
|
||||||
|
for value in [b"///u@h", b"////u@h"]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
text, sidecar, redactions = location(value, b"file")
|
||||||
|
self.assertEqual(text, value.decode("ascii"))
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertIsNotNone(sidecar["content_base64"])
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_file_urls_keep_path_text(self):
|
||||||
|
self.assertEqual(capture.without_userinfo("file:///a@b"), "file:///a@b")
|
||||||
|
|
||||||
|
def test_file_urls_reject_userinfo_and_keep_their_source(self):
|
||||||
|
# WHATWG rejects an "@" in a file host, so nothing parses as credentials.
|
||||||
|
self.assertEqual(
|
||||||
|
capture.without_userinfo("file://user:pass@host/x"), "file://user:pass@host/x"
|
||||||
|
)
|
||||||
|
for value in [b"\\\\user:pass@host/x", b"/\\user:pass@host/x", b"//user:pass@host/x"]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
text, sidecar, redactions = location(value, b"file")
|
||||||
|
self.assertEqual(text, value.decode("ascii"))
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_rejected_special_hosts_keep_their_source(self):
|
||||||
|
# A rejected host or port fails the whole URL, so it has no userinfo.
|
||||||
|
for value in [
|
||||||
|
"https:user@%",
|
||||||
|
"https://user@",
|
||||||
|
"https://user:pass@h^st/x",
|
||||||
|
"https://user@ho st/x",
|
||||||
|
"https://user@host:99999/x",
|
||||||
|
"https://user@host:12a",
|
||||||
|
"https://user@[::1",
|
||||||
|
]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
self.assertEqual(capture.without_userinfo(value), value)
|
||||||
|
text, sidecar, redactions = location(b"https://user:pass@h^st/x", b"https")
|
||||||
|
self.assertEqual(text, "https://user:pass@h^st/x")
|
||||||
|
self.assertEqual(sidecar["state"], "retained")
|
||||||
|
self.assertEqual(redactions, [])
|
||||||
|
|
||||||
|
def test_accepted_special_hosts_still_remove_userinfo(self):
|
||||||
|
for value, expected in [
|
||||||
|
("https:user@%41", "https:%41"),
|
||||||
|
("https://user@[::1]:8443/x", "https://[::1]:8443/x"),
|
||||||
|
("https://user@host:/x", "https://host:/x"),
|
||||||
|
("wss://user:pass@host:65535", "wss://host:65535"),
|
||||||
|
("https://user@0x7f.1/", "https://0x7f.1/"),
|
||||||
|
("https://user@1.2.3.4./", "https://1.2.3.4./"),
|
||||||
|
("https://user@4294967295/", "https://4294967295/"),
|
||||||
|
("https://user@1.2.3.08a/", "https://1.2.3.08a/"),
|
||||||
|
("https://user@[::ffff:1.2.3.4]/", "https://[::ffff:1.2.3.4]/"),
|
||||||
|
]:
|
||||||
|
with self.subTest(value=value):
|
||||||
|
self.assertEqual(capture.without_userinfo(value), expected)
|
||||||
|
|
||||||
|
def test_unicode_hosts_that_may_map_to_accepted_hosts_remove_userinfo(self):
|
||||||
|
# UTS #46 maps these to 1.2.3.4; mapping is not modeled, so redact.
|
||||||
|
for host in ["%EF%BC%91.2.3.4", "1。2.3.4", "%C2%AD1.2.3.4", "1.2.3.4"]:
|
||||||
|
with self.subTest(host=host):
|
||||||
|
self.assertEqual(
|
||||||
|
capture.without_userinfo(f"https://user:pw@{host}/"), f"https://{host}/"
|
||||||
|
)
|
||||||
|
text, sidecar, redactions = location(b"https://user:pw@%EF%BC%91.2.3.4/", b"https")
|
||||||
|
self.assertEqual(text, "https://%EF%BC%91.2.3.4/")
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
# ASCII rejections still hold for non-ASCII hosts.
|
||||||
|
self.assertEqual(capture.without_userinfo("https://user@1^/"), "https://user@1^/")
|
||||||
|
|
||||||
|
def test_trailing_controls_and_spaces_do_not_hide_userinfo(self):
|
||||||
|
# WHATWG strips trailing C0 controls and spaces before parsing.
|
||||||
|
for suffix in [b" ", b"\x01", b"\x00 \x1f"]:
|
||||||
|
with self.subTest(suffix=suffix):
|
||||||
|
value = b"https://user:pw@example.test" + suffix
|
||||||
|
text, sidecar, redactions = location(value, b"https")
|
||||||
|
self.assertEqual(text, "https://example.test" + suffix.decode("latin-1"))
|
||||||
|
self.assertEqual(sidecar["state"], "redacted")
|
||||||
|
self.assertEqual(len(redactions), 1)
|
||||||
|
|
||||||
|
def test_rejected_ip_literals_keep_their_source(self):
|
||||||
|
for value in [
|
||||||
|
"https://user:pass@256.256.256.256/path",
|
||||||
|
"https://user@1.2.3.4.5/",
|
||||||
|
"https://user@1.2.3.09/",
|
||||||
|
"https://user@4294967296/",
|
||||||
|
"https://user@a.0x/",
|
||||||
|
"https://user@1..2/",
|
||||||
|
"https://user@1." + "9" * 5000 + "/",
|
||||||
|
"https://user@[1::2::3]/",
|
||||||
|
"https://user@[::1%25eth0]/",
|
||||||
|
]:
|
||||||
|
with self.subTest(value=value[:40]):
|
||||||
|
self.assertEqual(capture.without_userinfo(value), value)
|
||||||
|
|
||||||
|
def test_overlong_ports_are_compared_without_numeric_overflow(self):
|
||||||
|
# Python 3.11+ refuses int() on decimal strings over 4300 digits.
|
||||||
|
accepted = "https://user@host:" + "0" * 5000 + "80/x"
|
||||||
|
self.assertEqual(capture.without_userinfo(accepted), "https://host:" + "0" * 5000 + "80/x")
|
||||||
|
rejected = "https://user@host:" + "9" * 5000 + "/x"
|
||||||
|
self.assertEqual(capture.without_userinfo(rejected), rejected)
|
||||||
|
|
||||||
|
def test_non_special_schemes_keep_backslashes_in_the_authority(self):
|
||||||
|
self.assertEqual(capture.without_userinfo("foo://a\\b@host/x"), "foo://host/x")
|
||||||
|
self.assertEqual(capture.without_userinfo(b"//a\\b@host"), b"//a\\b@host")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -567,18 +567,27 @@ def trace(debugger, config):
|
|||||||
before_digest = _hash_file(config["executable"])
|
before_digest = _hash_file(config["executable"])
|
||||||
before_identity = _file_identity(config["executable"])
|
before_identity = _file_identity(config["executable"])
|
||||||
except OSError as error:
|
except OSError as error:
|
||||||
return {"status": "target-integrity-error", "error": str(error)}
|
return {
|
||||||
|
"status": "target-integrity-error",
|
||||||
|
"error": str(error),
|
||||||
|
"target_process_created": False,
|
||||||
|
}
|
||||||
if before_digest != config["expected_sha256"]:
|
if before_digest != config["expected_sha256"]:
|
||||||
return {
|
return {
|
||||||
"status": "target-integrity-error",
|
"status": "target-integrity-error",
|
||||||
"error": "selected executable digest changed before LLDB target creation",
|
"error": "selected executable digest changed before LLDB target creation",
|
||||||
|
"target_process_created": False,
|
||||||
}
|
}
|
||||||
error = lldb.SBError()
|
error = lldb.SBError()
|
||||||
target = debugger.CreateTarget(
|
target = debugger.CreateTarget(
|
||||||
config["executable"], config["architecture"], None, False, error
|
config["executable"], config["architecture"], None, False, error
|
||||||
)
|
)
|
||||||
if not target.IsValid():
|
if not target.IsValid():
|
||||||
return {"status": "target-error", "error": error.GetCString()}
|
return {
|
||||||
|
"status": "target-error",
|
||||||
|
"error": error.GetCString(),
|
||||||
|
"target_process_created": False,
|
||||||
|
}
|
||||||
module_path_before, module_file_identity_before, module_uuid_before = (
|
module_path_before, module_file_identity_before, module_uuid_before = (
|
||||||
_module_identity(target)
|
_module_identity(target)
|
||||||
)
|
)
|
||||||
@@ -589,6 +598,7 @@ def trace(debugger, config):
|
|||||||
return {
|
return {
|
||||||
"status": "target-integrity-error",
|
"status": "target-integrity-error",
|
||||||
"error": "LLDB executable module resolved to a different pre-launch file identity",
|
"error": "LLDB executable module resolved to a different pre-launch file identity",
|
||||||
|
"target_process_created": False,
|
||||||
}
|
}
|
||||||
created = [_create_breakpoints(target, spec) for spec in config["breakpoints"]]
|
created = [_create_breakpoints(target, spec) for spec in config["breakpoints"]]
|
||||||
breakpoint_index = {}
|
breakpoint_index = {}
|
||||||
@@ -622,12 +632,24 @@ def trace(debugger, config):
|
|||||||
if not error.Success() or not process.IsValid():
|
if not error.Success() or not process.IsValid():
|
||||||
stdout.close()
|
stdout.close()
|
||||||
stderr.close()
|
stderr.close()
|
||||||
return {"status": "launch-error", "error": error.GetCString()}
|
return {
|
||||||
|
"status": "launch-error",
|
||||||
|
"error": error.GetCString(),
|
||||||
|
**(
|
||||||
|
{"target_process_created": True}
|
||||||
|
if process.IsValid()
|
||||||
|
else {}
|
||||||
|
),
|
||||||
|
}
|
||||||
launch_failure = _failed_launch_description(process)
|
launch_failure = _failed_launch_description(process)
|
||||||
if launch_failure is not None:
|
if launch_failure is not None:
|
||||||
stdout.close(expected_exit=True)
|
stdout.close(expected_exit=True)
|
||||||
stderr.close(expected_exit=True)
|
stderr.close(expected_exit=True)
|
||||||
return {"status": "launch-error", "error": launch_failure}
|
return {
|
||||||
|
"status": "launch-error",
|
||||||
|
"error": launch_failure,
|
||||||
|
"target_process_created": True,
|
||||||
|
}
|
||||||
stdout.mark_launched()
|
stdout.mark_launched()
|
||||||
stderr.mark_launched()
|
stderr.mark_launched()
|
||||||
# Publish the PID before any post-launch file or module inspection. The
|
# Publish the PID before any post-launch file or module inspection. The
|
||||||
@@ -644,6 +666,7 @@ def trace(debugger, config):
|
|||||||
return {
|
return {
|
||||||
"status": "target-identity-unavailable",
|
"status": "target-identity-unavailable",
|
||||||
"error": "host could not establish the launched process identity; target was stopped",
|
"error": "host could not establish the launched process identity; target was stopped",
|
||||||
|
"target_process_created": True,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
after_digest = _hash_file(config["executable"])
|
after_digest = _hash_file(config["executable"])
|
||||||
@@ -652,7 +675,11 @@ def trace(debugger, config):
|
|||||||
process.Kill()
|
process.Kill()
|
||||||
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
|
stdout.close(expected_exit=process.GetState() == lldb.eStateExited)
|
||||||
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
|
stderr.close(expected_exit=process.GetState() == lldb.eStateExited)
|
||||||
return {"status": "target-integrity-error", "error": str(identity_error)}
|
return {
|
||||||
|
"status": "target-integrity-error",
|
||||||
|
"error": str(identity_error),
|
||||||
|
"target_process_created": True,
|
||||||
|
}
|
||||||
if after_digest != config["expected_sha256"] or after_identity != before_identity:
|
if after_digest != config["expected_sha256"] or after_identity != before_identity:
|
||||||
process.Kill()
|
process.Kill()
|
||||||
_wait_for_exit(listener, process, 5)
|
_wait_for_exit(listener, process, 5)
|
||||||
@@ -661,6 +688,7 @@ def trace(debugger, config):
|
|||||||
return {
|
return {
|
||||||
"status": "target-integrity-error",
|
"status": "target-integrity-error",
|
||||||
"error": "selected executable digest or file identity changed during LLDB launch",
|
"error": "selected executable digest or file identity changed during LLDB launch",
|
||||||
|
"target_process_created": True,
|
||||||
}
|
}
|
||||||
module_path_after, module_file_identity_after, module_uuid_after = (
|
module_path_after, module_file_identity_after, module_uuid_after = (
|
||||||
_module_identity(target)
|
_module_identity(target)
|
||||||
@@ -676,6 +704,7 @@ def trace(debugger, config):
|
|||||||
return {
|
return {
|
||||||
"status": "target-integrity-error",
|
"status": "target-integrity-error",
|
||||||
"error": "LLDB executable module resolved to a different file identity",
|
"error": "LLDB executable module resolved to a different file identity",
|
||||||
|
"target_process_created": True,
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
module_uuid_before is not None
|
module_uuid_before is not None
|
||||||
@@ -689,6 +718,7 @@ def trace(debugger, config):
|
|||||||
return {
|
return {
|
||||||
"status": "target-integrity-error",
|
"status": "target-integrity-error",
|
||||||
"error": "LLDB executable module identity changed during launch",
|
"error": "LLDB executable module identity changed during launch",
|
||||||
|
"target_process_created": True,
|
||||||
}
|
}
|
||||||
context = {
|
context = {
|
||||||
"target": target,
|
"target": target,
|
||||||
@@ -748,7 +778,11 @@ def trace(debugger, config):
|
|||||||
# exit code; preserve the debugger's concrete launch denial.
|
# exit code; preserve the debugger's concrete launch denial.
|
||||||
launch_failure = _failed_launch_description(process)
|
launch_failure = _failed_launch_description(process)
|
||||||
if launch_failure is not None:
|
if launch_failure is not None:
|
||||||
return {"status": "launch-error", "error": launch_failure}
|
return {
|
||||||
|
"status": "launch-error",
|
||||||
|
"error": launch_failure,
|
||||||
|
"target_process_created": True,
|
||||||
|
}
|
||||||
launch_identity_stable = (
|
launch_identity_stable = (
|
||||||
module_path_before is not None
|
module_path_before is not None
|
||||||
and module_path_after == module_path_before
|
and module_path_after == module_path_before
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
fake_lldb = sys.modules.setdefault("lldb", types.ModuleType("lldb"))
|
fake_lldb = sys.modules.setdefault("lldb", types.ModuleType("lldb"))
|
||||||
@@ -22,6 +23,42 @@ spec.loader.exec_module(tracer)
|
|||||||
|
|
||||||
|
|
||||||
class NativeTraceBudgetTests(unittest.TestCase):
|
class NativeTraceBudgetTests(unittest.TestCase):
|
||||||
|
def test_pre_target_integrity_failure_reports_no_process_created(self):
|
||||||
|
original_debugger = getattr(tracer.lldb, "SBDebugger", None)
|
||||||
|
|
||||||
|
class DebuggerApi:
|
||||||
|
@staticmethod
|
||||||
|
def SetInternalVariable(*_arguments):
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Debugger:
|
||||||
|
def SetAsync(self, _enabled):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def GetInstanceName(self):
|
||||||
|
return "fixture"
|
||||||
|
|
||||||
|
def CreateTarget(self, *_arguments):
|
||||||
|
raise AssertionError("integrity failure precedes target creation")
|
||||||
|
|
||||||
|
tracer.lldb.SBDebugger = DebuggerApi
|
||||||
|
try:
|
||||||
|
with patch.object(tracer, "_hash_file", return_value="changed"), patch.object(
|
||||||
|
tracer, "_file_identity", return_value=(1, 2)
|
||||||
|
):
|
||||||
|
result = tracer.trace(
|
||||||
|
Debugger(),
|
||||||
|
{"executable": "fixture", "expected_sha256": "expected"},
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if original_debugger is None:
|
||||||
|
del tracer.lldb.SBDebugger
|
||||||
|
else:
|
||||||
|
tracer.lldb.SBDebugger = original_debugger
|
||||||
|
|
||||||
|
self.assertEqual(result["status"], "target-integrity-error")
|
||||||
|
self.assertIs(result["target_process_created"], False)
|
||||||
|
|
||||||
def test_journal_checks_exact_byte_budget_before_writing(self):
|
def test_journal_checks_exact_byte_budget_before_writing(self):
|
||||||
record = {"kind": "other-stop", "reason": "signal"}
|
record = {"kind": "other-stop", "reason": "signal"}
|
||||||
encoded_size = len((tracer.json.dumps(record) + "\n").encode("utf-8"))
|
encoded_size = len((tracer.json.dumps(record) + "\n").encode("utf-8"))
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import sys
|
|||||||
|
|
||||||
PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
|
PROFILE = "pwntools@4.15.0;pyelftools@0.33;unicorn@2.1.2"
|
||||||
|
|
||||||
|
# Linux ET_CORE machine identifiers whose PRSTATUS layout is interpreted with
|
||||||
|
# unchanged pwntools 4.15.0 ctypes. Both share pr_cursig@12, pr_pid@32 and
|
||||||
|
# pr_reg@112; only the register window differs: amd64 is 27 x 8 bytes for a
|
||||||
|
# 336-byte descriptor, AArch64 is 34 x 8 bytes for a 392-byte descriptor.
|
||||||
|
CORE_MACHINES = (62, 183)
|
||||||
|
|
||||||
|
|
||||||
class CoreFailure(Exception):
|
class CoreFailure(Exception):
|
||||||
def __init__(self, reason, message):
|
def __init__(self, reason, message):
|
||||||
@@ -51,12 +57,27 @@ def inspect_core(path, cache):
|
|||||||
# Other declared OSABIs must never select Linux note ctypes.
|
# Other declared OSABIs must never select Linux note ctypes.
|
||||||
if content[7] not in (0, 3) or content[8] != 0:
|
if content[7] not in (0, 3) or content[8] != 0:
|
||||||
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires System V or GNU/Linux OSABI with ABI version zero; selected OSABI=" + str(content[7]) + ", ABI version=" + str(content[8]) + ".")
|
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires System V or GNU/Linux OSABI with ABI version zero; selected OSABI=" + str(content[7]) + ", ABI version=" + str(content[8]) + ".")
|
||||||
if int.from_bytes(content[16:18], "little") != 4 or int.from_bytes(content[18:20], "little") != 62:
|
machine = int.from_bytes(content[18:20], "little")
|
||||||
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires x86-64 ET_CORE.")
|
if int.from_bytes(content[16:18], "little") != 4 or machine not in CORE_MACHINES:
|
||||||
|
raise CoreFailure("unsupported-target", "Initial recorded-core profile requires ET_CORE for x86-64 or AArch64; selected ET_TYPE=" + str(int.from_bytes(content[16:18], "little")) + ", e_machine=" + str(machine) + ".")
|
||||||
from elftools.common.exceptions import ELFError
|
from elftools.common.exceptions import ELFError
|
||||||
from elftools.common.utils import struct_parse
|
from elftools.common.utils import struct_parse
|
||||||
from elftools.elf.elffile import ELFFile
|
from elftools.elf.elffile import ELFFile
|
||||||
from pwnlib.elf.datatypes import elf_prstatus_amd64, elf_siginfo_64, user_regs_struct_amd64
|
from pwnlib.elf.datatypes import (
|
||||||
|
elf_prstatus_aarch64,
|
||||||
|
elf_prstatus_amd64,
|
||||||
|
elf_siginfo_64,
|
||||||
|
user_regs_struct_aarch64,
|
||||||
|
user_regs_struct_amd64,
|
||||||
|
)
|
||||||
|
if machine == 62:
|
||||||
|
profile_label = "x86-64"
|
||||||
|
architecture = "x86_64-little-endian"
|
||||||
|
prstatus_type, registers_type = elf_prstatus_amd64, user_regs_struct_amd64
|
||||||
|
else:
|
||||||
|
profile_label = "AArch64"
|
||||||
|
architecture = "aarch64-little-endian"
|
||||||
|
prstatus_type, registers_type = elf_prstatus_aarch64, user_regs_struct_aarch64
|
||||||
try:
|
try:
|
||||||
image = ELFFile(io.BytesIO(content))
|
image = ELFFile(io.BytesIO(content))
|
||||||
header = image.header
|
header = image.header
|
||||||
@@ -107,17 +128,17 @@ def inspect_core(path, cache):
|
|||||||
if owner != b"CORE\0":
|
if owner != b"CORE\0":
|
||||||
continue
|
continue
|
||||||
if note.n_type == "NT_PRSTATUS":
|
if note.n_type == "NT_PRSTATUS":
|
||||||
if len(descriptor) < ctypes.sizeof(elf_prstatus_amd64):
|
if len(descriptor) < ctypes.sizeof(prstatus_type):
|
||||||
raise CoreFailure("format", "Linux amd64 NT_PRSTATUS descriptor is truncated.")
|
raise CoreFailure("format", "Linux " + profile_label + " NT_PRSTATUS descriptor is truncated.")
|
||||||
status = elf_prstatus_amd64.from_buffer_copy(descriptor)
|
status = prstatus_type.from_buffer_copy(descriptor)
|
||||||
registers = []
|
registers = []
|
||||||
for name, kind in user_regs_struct_amd64._fields_:
|
for name, kind in registers_type._fields_:
|
||||||
relative = elf_prstatus_amd64.pr_reg.offset + getattr(user_regs_struct_amd64, name).offset
|
relative = prstatus_type.pr_reg.offset + getattr(registers_type, name).offset
|
||||||
registers.append({"name": name, "value": hex(getattr(status.pr_reg, name)), "location": location(descriptor_start + relative, ctypes.sizeof(kind), end)})
|
registers.append({"name": name, "value": hex(getattr(status.pr_reg, name)), "location": location(descriptor_start + relative, ctypes.sizeof(kind), end)})
|
||||||
threads.append({"note_index": note_index, "historical_pid": ctypes.c_int32(status.pr_pid).value, "recorded_current_signal": status.pr_cursig, "registers": registers})
|
threads.append({"note_index": note_index, "historical_pid": ctypes.c_int32(status.pr_pid).value, "recorded_current_signal": status.pr_cursig, "registers": registers})
|
||||||
elif note.n_type == "NT_SIGINFO":
|
elif note.n_type == "NT_SIGINFO":
|
||||||
if len(descriptor) < ctypes.sizeof(elf_siginfo_64):
|
if len(descriptor) < ctypes.sizeof(elf_siginfo_64):
|
||||||
raise CoreFailure("format", "Linux amd64 NT_SIGINFO descriptor is truncated.")
|
raise CoreFailure("format", "Linux " + profile_label + " NT_SIGINFO descriptor is truncated.")
|
||||||
signal = elf_siginfo_64.from_buffer_copy(descriptor)
|
signal = elf_siginfo_64.from_buffer_copy(descriptor)
|
||||||
number = ctypes.c_int32(signal.si_signo).value
|
number = ctypes.c_int32(signal.si_signo).value
|
||||||
code = ctypes.c_int32(signal.si_code).value
|
code = ctypes.c_int32(signal.si_code).value
|
||||||
@@ -125,7 +146,7 @@ def inspect_core(path, cache):
|
|||||||
signals.append({"note_index": note_index, "number": number, "code": code, "errno": ctypes.c_int32(signal.si_errno).value,
|
signals.append({"note_index": note_index, "number": number, "code": code, "errno": ctypes.c_int32(signal.si_errno).value,
|
||||||
"fault_address": hex(signal.sigfault_addr) if known else None,
|
"fault_address": hex(signal.sigfault_addr) if known else None,
|
||||||
"fault_address_meaning": "recorded-sigsegv-address" if known else "unknown", "thread_association": "unknown"})
|
"fault_address_meaning": "recorded-sigsegv-address" if known else "unknown", "thread_association": "unknown"})
|
||||||
return {"format": "elf-core", "architecture": "x86_64-little-endian", "target_execution": "not-performed", "live_process_identity": "unknown",
|
return {"format": "elf-core", "architecture": architecture, "target_execution": "not-performed", "live_process_identity": "unknown",
|
||||||
"segments": segments, "notes": notes, "note_padding": padding, "threads": threads, "signals": signals,
|
"segments": segments, "notes": notes, "note_padding": padding, "threads": threads, "signals": signals,
|
||||||
"note_interpretation_completeness": "unknown",
|
"note_interpretation_completeness": "unknown",
|
||||||
"limitations": ["The initial Linux note profile accepts System V/unspecified or GNU/Linux OSABI with ABI version zero; an unspecified OSABI alone does not prove operating-system origin.",
|
"limitations": ["The initial Linux note profile accepts System V/unspecified or GNU/Linux OSABI with ABI version zero; an unspecified OSABI alone does not prove operating-system origin.",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default defineConfig({
|
|||||||
"Reverse engineer binaries, applications, and runtime behavior with one CLI and MCP server.",
|
"Reverse engineer binaries, applications, and runtime behavior with one CLI and MCP server.",
|
||||||
lang: "en-US",
|
lang: "en-US",
|
||||||
base: "/rea/",
|
base: "/rea/",
|
||||||
|
srcExclude: ["readme/**"],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
nav: [
|
nav: [
|
||||||
{ text: "Get started", link: "/installation" },
|
{ text: "Get started", link: "/installation" },
|
||||||
@@ -28,6 +29,7 @@ export default defineConfig({
|
|||||||
items: [
|
items: [
|
||||||
{ text: "Native binaries", link: "/native-investigation" },
|
{ text: "Native binaries", link: "/native-investigation" },
|
||||||
{ text: "Go build metadata", link: "/go-binaries" },
|
{ text: "Go build metadata", link: "/go-binaries" },
|
||||||
|
{ text: "Rust dependencies", link: "/rust-binaries" },
|
||||||
{
|
{
|
||||||
text: "JavaScript and Electron",
|
text: "JavaScript and Electron",
|
||||||
link: "/javascript-artifact-reconstruction",
|
link: "/javascript-artifact-reconstruction",
|
||||||
@@ -42,6 +44,7 @@ export default defineConfig({
|
|||||||
link: "/web-network-captures",
|
link: "/web-network-captures",
|
||||||
},
|
},
|
||||||
{ text: "Electron observation", link: "/electron-observation" },
|
{ text: "Electron observation", link: "/electron-observation" },
|
||||||
|
{ text: "Electron fuses", link: "/electron-fuses" },
|
||||||
{ text: "Process capture", link: "/process-capture" },
|
{ text: "Process capture", link: "/process-capture" },
|
||||||
{ text: ".NET assemblies", link: "/managed-code-analysis" },
|
{ text: ".NET assemblies", link: "/managed-code-analysis" },
|
||||||
{ text: "Android applications", link: "/android-analysis" },
|
{ text: "Android applications", link: "/android-analysis" },
|
||||||
@@ -57,10 +60,12 @@ export default defineConfig({
|
|||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: "IDA", link: "/ida-provider" },
|
{ text: "IDA", link: "/ida-provider" },
|
||||||
|
{ text: "Kuna", link: "/kuna-provider" },
|
||||||
{ text: "Ghidra on Windows", link: "/windows-ghidra-p0" },
|
{ text: "Ghidra on Windows", link: "/windows-ghidra-p0" },
|
||||||
{ text: "Ghidra DOS analysis", link: "/ghidra-dos" },
|
{ text: "Ghidra DOS analysis", link: "/ghidra-dos" },
|
||||||
{ text: "Ghidra raw binary", link: "/raw-binary" },
|
{ text: "Ghidra raw binary", link: "/raw-binary" },
|
||||||
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
|
{ text: "Ghidra NativeAOT", link: "/ghidra-nativeaot" },
|
||||||
|
{ text: "Ghidra PowerPC", link: "/ghidra-ppc" },
|
||||||
{ text: "Ghidra semantics", link: "/ghidra-provider" },
|
{ text: "Ghidra semantics", link: "/ghidra-provider" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+48
-28
@@ -26,6 +26,9 @@ Readiness is `rea inspect-adb-client`: it runs `adb version` and reports the
|
|||||||
resolved binary, its version, and where it was selected from. A missing
|
resolved binary, its version, and where it was selected from. A missing
|
||||||
binary reports `capability_unavailable` with the `REA_ADB_PATH` remediation
|
binary reports `capability_unavailable` with the `REA_ADB_PATH` remediation
|
||||||
rather than attempting an installation.
|
rather than attempting an installation.
|
||||||
|
The `binary_session` inventory uses the same bounded client-version probe; it
|
||||||
|
does not query devices, start the adb server, or establish that a requested
|
||||||
|
serial is reachable. Device state is checked by each device operation.
|
||||||
|
|
||||||
## Answer an analyst question
|
## Answer an analyst question
|
||||||
|
|
||||||
@@ -73,32 +76,37 @@ existing analysis families directly, for example
|
|||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|
||||||
| Tool | Kind | What it does |
|
| Tool | Kind | What it does |
|
||||||
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `inspect_adb_client` | read | Resolved binary, version, install path; never contacts the server |
|
| `inspect_adb_client` | read | Resolved binary, version, install path; never contacts the server |
|
||||||
| `list_adb_devices` | read | Serial, state, transport, model metadata, inferred emulator/physical kind with basis |
|
| `list_adb_devices` | read | Serial, state, transport, model metadata, inferred emulator/physical kind with basis |
|
||||||
| `inspect_adb_device` | read | Fixed `getprop` whitelist: build, SDK, patch, fingerprint, product, ABIs, debuggable, `ro.kernel.qemu` |
|
| `inspect_adb_device` | read | Fixed `getprop` whitelist: build, SDK, patch, fingerprint, product, ABIs, debuggable, `ro.kernel.qemu` |
|
||||||
| `inspect_adb_display` | read | `wm size` and `wm density` as declared |
|
| `inspect_adb_display` | read | `wm size` and `wm density` as declared |
|
||||||
| `list_adb_features` | read | `pm list features` (hardware/software features, hex GL versions) |
|
| `list_adb_features` | read | `pm list features` (hardware/software features, hex GL versions) |
|
||||||
| `list_adb_services` | read | `service list` binder services and interfaces |
|
| `list_adb_services` | read | `service list` binder services and interfaces |
|
||||||
| `list_adb_processes` | read | `ps -A`: user, pid, ppid, rss, state, name |
|
| `list_adb_processes` | read | `ps -A`: user, pid, ppid, rss, state, name |
|
||||||
| `inspect_adb_window` | read | `dumpsys window` focus declarations (what is on screen) |
|
| `inspect_adb_window` | read | `dumpsys window` focus declarations (what is on screen) |
|
||||||
| `read_adb_setting` | read | One `settings get` value; device-side null is reported as such |
|
| `read_adb_setting` | read | One `settings get` value; device-side null is reported as such |
|
||||||
| `read_adb_logcat` | read | Bounded `logcat -d -t` dump for one buffer, optional pid filter |
|
| `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 |
|
| `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 |
|
| `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, permission count |
|
||||||
| `list_adb_directory` | read | NUL-framed immediate children, including hidden entries: kinds, permissions, owner, group, sizes, symlink targets |
|
| `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_package` | read | Complete split APK set from `pm path`, each file digested |
|
||||||
| `pull_adb_file` | read | One absolute device path pulled and digested |
|
| `pull_adb_file` | read | One absolute device path pulled and digested |
|
||||||
| `capture_adb_screen` | read | One `screencap -p` frame (exec-out, no device-side file) with dimensions |
|
| `capture_adb_screen` | read | One `screencap -p` frame (exec-out, no device-side file) with dimensions |
|
||||||
| `collect_adb_bugreport` | read | Device-generated bugreport zip, digested |
|
| `collect_adb_bugreport` | read | Device-generated bugreport zip, digested |
|
||||||
| `push_adb_file` | **mutates device** | One local file to a device path (e.g. pushing tools into `/data/local/tmp`); refuses existing targets without explicit overwrite; verifies via the device's own sha256sum |
|
| `push_adb_file` | **mutates device** | One local file to a device path (e.g. pushing tools into `/data/local/tmp`); checks for existing targets and refuses them without explicit overwrite (the check is not atomic); compares digests when device `sha256sum` is available |
|
||||||
| `install_adb_package` | **mutates device** | Local APK via `adb install [-r]`; preserves the device's Success/Failure verdict |
|
| `install_adb_package` | **mutates device** | Local APK via `adb install [-r]`; preserves its Success/Failure verdict, records `replace_requested`, and leaves actual replacement `null` because prior package state is not observed |
|
||||||
| `uninstall_adb_package` | **mutates device** | Removes a package and its data for user 0 |
|
| `uninstall_adb_package` | **mutates device** | Removes a package and its data for user 0 |
|
||||||
| `start_adb_app` | **mutates device** | Resolves the package's launcher activity (`cmd package resolve-activity --brief`) and starts it (`am start -n`) |
|
| `start_adb_app` | **mutates device** | Resolves the package's launcher activity (`cmd package resolve-activity --brief`) and starts it (`am start -n`) |
|
||||||
| `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 |
|
| `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 |
|
| `stop_adb_app` | **mutates device** | Force-stops every process of a package, ending a dynamic-analysis run |
|
||||||
|
|
||||||
|
For `install_adb_package`, `replace_requested` records whether `-r` was sent.
|
||||||
|
The `replaced` result is `null`: the successful install response does not say
|
||||||
|
whether the package was installed fresh or replaced, and REA does not query the
|
||||||
|
device first. The install verdict and host APK digest remain available.
|
||||||
|
|
||||||
## Interpret the result
|
## Interpret the result
|
||||||
|
|
||||||
@@ -118,6 +126,16 @@ existing analysis families directly, for example
|
|||||||
while POSIX hosts retain valid POSIX filenames such as names with spaces or
|
while POSIX hosts retain valid POSIX filenames such as names with spaces or
|
||||||
colons. A package pull checks reported names against the destination
|
colons. A package pull checks reported names against the destination
|
||||||
filesystem before pulling, and refuses names that would share a local path.
|
filesystem before pulling, and refuses names that would share a local path.
|
||||||
|
- **Package path refusals are diagnosable.** Refusals from `pull_adb_package`
|
||||||
|
return `unreadable_output` with `retryable: false`, keep the `pm path` output
|
||||||
|
in `details.captured_output`, and name the refusal in `details.reason`.
|
||||||
|
`unsafe_device_path` lists refused entries in
|
||||||
|
`details.diagnostics.rejected_paths` as `{device_path, reason}`;
|
||||||
|
`duplicate_local_destination` reports the shared `file_name` and both
|
||||||
|
`device_paths`; `local_destination_unavailable` reports the `device_path`,
|
||||||
|
`file_name`, and filesystem `error_code`. Diagnostics retain raw device
|
||||||
|
strings, while the caller-facing message escapes invisible and bidirectional
|
||||||
|
characters so they cannot hide text or reorder the path.
|
||||||
- **Splits are part of the answer.** `pm path` enumerates the base APK and
|
- **Splits are part of the answer.** `pm path` enumerates the base APK and
|
||||||
every installed split; pulls cover the whole set. System packages whose
|
every installed split; pulls cover the whole set. System packages whose
|
||||||
APKs keep other names report the `unknown` role rather than guessing.
|
APKs keep other names report the `unknown` role rather than guessing.
|
||||||
@@ -127,8 +145,10 @@ existing analysis families directly, for example
|
|||||||
partial coverage instead of dropping rows.
|
partial coverage instead of dropping rows.
|
||||||
- **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and
|
- **Mutations are explicit.** `push_adb_file`, `install_adb_package`, and
|
||||||
`uninstall_adb_package` declare `mutatesTarget` and never run implicitly:
|
`uninstall_adb_package` declare `mutatesTarget` and never run implicitly:
|
||||||
pushes refuse existing device files unless `overwrite` is set and verify
|
pushes check for existing device files before transfer and refuse them
|
||||||
the transfer with the device's own `sha256sum`; installs preserve the
|
unless `overwrite` is set. The check is not atomic, so a different process
|
||||||
|
can create the path before `adb push` writes it. When available, the device's
|
||||||
|
`sha256sum` verifies the transfer; installs preserve the
|
||||||
device's `Failure [...]` reason verbatim.
|
device's `Failure [...]` reason verbatim.
|
||||||
- **Producer identity stays attached.** Every result embeds the producing
|
- **Producer identity stays attached.** Every result embeds the producing
|
||||||
adb client (`binary_path`, `path_source`, `version`, `installed_path`).
|
adb client (`binary_path`, `path_source`, `version`, `installed_path`).
|
||||||
|
|||||||
+77
-17
@@ -9,6 +9,10 @@ This family is present on repository main and in npm 4.1.0. Check the
|
|||||||
[released package boundary](installation.md#released-package-and-main) and the
|
[released package boundary](installation.md#released-package-and-main) and the
|
||||||
connected server's tool list before selecting it.
|
connected server's tool list before selecting it.
|
||||||
|
|
||||||
|
On Linux, JADX launches an REA-owned process and requires a procps-compatible
|
||||||
|
`ps` on REA's `PATH`; see the shared
|
||||||
|
[Linux process prerequisite](installation.md#linux-owned-process-prerequisite).
|
||||||
|
|
||||||
## Optional package delivery
|
## Optional package delivery
|
||||||
|
|
||||||
The MCP `inspect_android_package` request keeps complete output when `detail` is
|
The MCP `inspect_android_package` request keeps complete output when `detail` is
|
||||||
@@ -33,10 +37,40 @@ unverified.
|
|||||||
## Supply tools
|
## Supply tools
|
||||||
|
|
||||||
Use a **full JDK 17 or newer** (including its compiler module) and
|
Use a **full JDK 17 or newer** (including its compiler module) and
|
||||||
**jadx-headless-mcp 0.7.1**. REA runs its packaged Java metadata bridge in
|
[**jadx-headless-mcp 0.7.1**](https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar).
|
||||||
source-file mode against the unmodified engine JAR. REA does not install Java,
|
Verify the downloaded JAR's SHA-256 against
|
||||||
an Android SDK, JADX, a device service or an emulator. Set the path in the shell
|
[REA's pinned release metadata](https://github.com/morluto/rea/blob/main/src/android/JadxRelease.ts)
|
||||||
or MCP server environment:
|
before using it. The following download and verification commands work for npm
|
||||||
|
package consumers as well as repository checkouts; the repository-only
|
||||||
|
`npm run fixtures:android` helper is not included in the npm package.
|
||||||
|
|
||||||
|
On macOS or Linux, choose a destination and verify the pinned digest:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p "$HOME/.local/share/rea/android" &&
|
||||||
|
cd "$HOME/.local/share/rea/android" &&
|
||||||
|
curl -fL -o jadx-headless-mcp-0.7.1-all.jar https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar &&
|
||||||
|
if command -v sha256sum >/dev/null; then
|
||||||
|
echo '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75 jadx-headless-mcp-0.7.1-all.jar' | sha256sum -c -
|
||||||
|
else
|
||||||
|
echo '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75 jadx-headless-mcp-0.7.1-all.jar' | shasum -a 256 -c -
|
||||||
|
fi &&
|
||||||
|
export REA_JADX_MCP_JAR="$PWD/jadx-headless-mcp-0.7.1-all.jar"
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows, run the equivalent in PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$jar = Join-Path $HOME '.local\share\rea\android\jadx-headless-mcp-0.7.1-all.jar'
|
||||||
|
New-Item -ItemType Directory -Force (Split-Path $jar) | Out-Null
|
||||||
|
Invoke-WebRequest -Uri 'https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar' -OutFile $jar
|
||||||
|
if ((Get-FileHash -Algorithm SHA256 $jar).Hash.ToLowerInvariant() -ne '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75') { throw 'JADX JAR SHA-256 does not match REA pinned metadata' }
|
||||||
|
$env:REA_JADX_MCP_JAR = $jar
|
||||||
|
```
|
||||||
|
|
||||||
|
REA runs its packaged Java metadata bridge in source-file mode against the
|
||||||
|
unmodified engine JAR. REA does not install Java, an Android SDK, JADX, a device
|
||||||
|
service or an emulator. Set the path in the shell or MCP server environment:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
|
export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
|
||||||
@@ -86,6 +120,7 @@ rea inspect-android-method /path/Example.apk example.MainActivity onCreate
|
|||||||
rea inspect-android-method /path/Example.apk example.MainActivity select --overload-index 1
|
rea inspect-android-method /path/Example.apk example.MainActivity select --overload-index 1
|
||||||
rea trace-android-references /path/Example.apk example.MainActivity
|
rea trace-android-references /path/Example.apk example.MainActivity
|
||||||
rea trace-android-references /path/Example.apk example.MainActivity --method-name onCreate
|
rea trace-android-references /path/Example.apk example.MainActivity --method-name onCreate
|
||||||
|
rea trace-android-references /path/Example.apk example.MainActivity --method-name select --overload-index 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Equivalent MCP methods are `inspect_android_package`, `search_android_classes`,
|
Equivalent MCP methods are `inspect_android_package`, `search_android_classes`,
|
||||||
@@ -106,9 +141,10 @@ Every request includes `path`. Class/method operations use `class_name`,
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use `inspect_android_class` to choose an overload. REA rejects an ambiguous
|
Use `inspect_android_class` to choose an overload. REA rejects an ambiguous
|
||||||
method request rather than silently selecting the first match. The engine's
|
method request rather than silently selecting the first match. Method reference
|
||||||
method reference operation cannot select overloads, so REA reports that boundary
|
queries accept the same `overload_index`; the bridge resolves the selected
|
||||||
as unsupported when multiple same-name methods exist.
|
method node and the result reports its index and displayed signature. These
|
||||||
|
indices are tied to the current artifact and engine, not stable across builds.
|
||||||
|
|
||||||
The engine's smali fallback also joins same-name overloads; REA rejects that
|
The engine's smali fallback also joins same-name overloads; REA rejects that
|
||||||
fallback for an overloaded method instead of attributing all bodies to one
|
fallback for an overloaded method instead of attributing all bodies to one
|
||||||
@@ -137,14 +173,28 @@ the same source byte limit and explicit truncation reporting.
|
|||||||
full reported display types, fields and inner class names. Synthetic members
|
full reported display types, fields and inner class names. Synthetic members
|
||||||
that code generation hides can appear in the inventory; it stays consistent
|
that code generation hides can appear in the inventory; it stays consistent
|
||||||
after method decompilation within the retained session.
|
after method decompilation within the retained session.
|
||||||
Exact DEX descriptors are unknown. Overload indices are tied to this artifact
|
Exact DEX prototypes are reported from frozen JADX method metadata as
|
||||||
and engine, not stable identities across different builds.
|
`dex_descriptor`, for example `(I)V`. Display names can be JADX aliases;
|
||||||
|
the descriptor alone is not a complete raw DEX method ID. Overload indices
|
||||||
|
are tied to this artifact and engine, not stable identities across builds.
|
||||||
- Method inspection reports Java or smali, fallback markers and complete/partial
|
- Method inspection reports Java or smali, fallback markers and complete/partial
|
||||||
returned text. Decompiled source is a derived representation. A complete text
|
returned text. Decompiled source is a derived representation. A complete text
|
||||||
result does not establish complete semantics or a byte-identical reconstruction.
|
result does not establish complete semantics or a byte-identical reconstruction.
|
||||||
- Reference results are incoming static relationships between provider nodes.
|
- Reference results are incoming static relationships between provider nodes.
|
||||||
Source lines and DEX instruction offsets remain unknown. They are not observed
|
`reference_unit=using_node` means `total` counts containing JADX nodes, not
|
||||||
runtime calls.
|
individual invoke instructions; two calls from one method can appear as one
|
||||||
|
entry. `coverage=complete` covers this node inventory only. For a method
|
||||||
|
reference whose using node has raw code, `direct_invokes` lists matching DEX
|
||||||
|
invoke instructions with their DEX entry, raw source method ID, code-unit
|
||||||
|
offset, method index and opcode. An empty array means no direct invoke was
|
||||||
|
matched in that node; `null` means raw code was unavailable or the using node
|
||||||
|
was not a method. This list does not establish complete call-site coverage
|
||||||
|
outside JADX's using-node inventory or through reflection and dynamic loading.
|
||||||
|
A virtual or interface invoke also does not establish its runtime implementation.
|
||||||
|
Source lines and the containing node's own `dex_offset` remain unknown.
|
||||||
|
Method-reference results also carry the selected `dex_descriptor`;
|
||||||
|
class-reference results report `null`.
|
||||||
|
These relationships are not observed runtime calls.
|
||||||
- Original requests, APK SHA-256, actual engine JAR SHA-256 and raw producer
|
- Original requests, APK SHA-256, actual engine JAR SHA-256 and raw producer
|
||||||
responses remain in Evidence. Source revision is populated only for bytes
|
responses remain in Evidence. Source revision is populated only for bytes
|
||||||
matching the audited release JAR.
|
matching the audited release JAR.
|
||||||
@@ -210,9 +260,11 @@ REA never writes to the original APK or launches target code.
|
|||||||
|
|
||||||
On Windows, stdin writes preserve stream backpressure and run outside the Node
|
On Windows, stdin writes preserve stream backpressure and run outside the Node
|
||||||
event loop. Cancellation terminates the retained job, including descendants,
|
event loop. Cancellation terminates the retained job, including descendants,
|
||||||
before workspace cleanup. An abrupt CLI/server process termination closes the
|
before workspace cleanup. On Windows, a detached REA helper owns the private
|
||||||
job and stops Java, but cannot run JavaScript workspace cleanup; temporary
|
workspace's native directory handle. An abrupt CLI/server owner termination
|
||||||
workspace files can remain after that forced exit.
|
closes the Java job and the helper's input pipe; the helper then removes the
|
||||||
|
workspace by handle. The real Windows lane checks both effects. If the helper
|
||||||
|
also dies or the host loses power before it finishes, private files can remain.
|
||||||
|
|
||||||
If Java cannot start the bridge, REA reports the selected executable and retained
|
If Java cannot start the bridge, REA reports the selected executable and retained
|
||||||
startup diagnostics. Select a full JDK through `JAVA_HOME` or `PATH`, and use
|
startup diagnostics. Select a full JDK through `JAVA_HOME` or `PATH`, and use
|
||||||
@@ -275,12 +327,20 @@ The original APK and host Java configuration remain unchanged.
|
|||||||
|
|
||||||
The same lane runs on Windows x64 with the bundled native controls and a full
|
The same lane runs on Windows x64 with the bundled native controls and a full
|
||||||
JDK. It checks CLI/MCP results, real MCP cancellation and disconnect, and Java
|
JDK. It checks CLI/MCP results, real MCP cancellation and disconnect, and Java
|
||||||
exit after abrupt CLI owner termination. Windows forced termination is checked
|
exit and workspace removal after abrupt CLI owner termination. Windows forced
|
||||||
separately from the POSIX CLI SIGTERM handler; it does not claim a structured
|
termination is checked separately from the POSIX CLI SIGTERM handler. A
|
||||||
cancellation result or workspace removal after the owner is forcibly killed.
|
synthetic two-owner test confirms that one owner's exit does not remove the
|
||||||
|
other owner's workspace. The lane does not claim a structured cancellation
|
||||||
|
result or power-loss recovery.
|
||||||
|
|
||||||
The download script verifies fixed SHA-256 values, reuses matching files and
|
The download script verifies fixed SHA-256 values, reuses matching files and
|
||||||
refuses to overwrite a different existing file. APK/JAR files live under ignored
|
refuses to overwrite a different existing file. APK/JAR files live under ignored
|
||||||
`_reference/` and are excluded from package contents. The lane exercises actual
|
`_reference/` and are excluded from package contents. The lane exercises actual
|
||||||
REA CLI and MCP results with the real engine, separately from synthetic protocol
|
REA CLI and MCP results with the real engine, separately from synthetic protocol
|
||||||
regressions. It needs no Gradle build, emulator, Android SDK or Ghidra.
|
regressions. It needs no Gradle build, emulator, Android SDK or Ghidra.
|
||||||
|
|
||||||
|
Direct invoke sites retain exact DEX code-unit offsets separately from each
|
||||||
|
containing node's unknown `dex_offset`. `direct_invokes_exhaustive: false`
|
||||||
|
means instruction rows were bounded by the local bridge frame budget; `null`
|
||||||
|
means the producer did not establish instruction coverage. Using-node totals
|
||||||
|
and coverage do not imply complete instruction evidence.
|
||||||
@@ -21,6 +21,8 @@ Readiness is `rea inspect-apktool-client`: it runs `apktool --version` and
|
|||||||
reports the resolved launcher, its selection source, and its version. A
|
reports the resolved launcher, its selection source, and its version. A
|
||||||
missing launcher reports `capability_unavailable` with the
|
missing launcher reports `capability_unavailable` with the
|
||||||
`REA_APKTOOL_COMMAND` remediation rather than attempting an installation.
|
`REA_APKTOOL_COMMAND` remediation rather than attempting an installation.
|
||||||
|
`binary_session` uses the same host probe for tool availability; it does not
|
||||||
|
validate a particular APK or guarantee that the selected input can be decoded.
|
||||||
|
|
||||||
On Windows, a `.bat` or `.cmd` launcher (such as Chocolatey's `apktool.bat`)
|
On Windows, a `.bat` or `.cmd` launcher (such as Chocolatey's `apktool.bat`)
|
||||||
runs through an explicit `cmd.exe /d /s /c` call. That call has no AutoRun,
|
runs through an explicit `cmd.exe /d /s /c` call. That call has no AutoRun,
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ graph TD
|
|||||||
JS_CODEC["src/javascript/sourceMaps/<br/>pinned upstream map codec<br/>owned heap-limited Node process, complete point evidence"]
|
JS_CODEC["src/javascript/sourceMaps/<br/>pinned upstream map codec<br/>owned heap-limited Node process, complete point evidence"]
|
||||||
INSPECTOR["src/inspector/<br/>passive V8 Inspector discovery and capture<br/>script and target locations"]
|
INSPECTOR["src/inspector/<br/>passive V8 Inspector discovery and capture<br/>script and target locations"]
|
||||||
DOTNET["src/dotnet/<br/>Managed static metadata provider<br/>PE/CLI metadata inventory, members, IL"]
|
DOTNET["src/dotnet/<br/>Managed static metadata provider<br/>PE/CLI metadata inventory, members, IL"]
|
||||||
|
OBJDUMP["src/objdump/<br/>GNU objdump command profile"]
|
||||||
|
RIZIN["src/rizin/<br/>Static command profile and persistent debugger sessions"]
|
||||||
|
GDB["src/gdb/<br/>Persistent GDB/MI sessions<br/>owned process lifecycle and target state"]
|
||||||
|
|
||||||
FIRMWARE["src/firmware/<br/>Binwalk / Unblob adapters<br/>owned processes, report normalization, private extraction"]
|
FIRMWARE["src/firmware/<br/>Binwalk / Unblob adapters<br/>owned processes, report normalization, private extraction"]
|
||||||
ANDROID["src/android/<br/>Static APK adapter<br/>owned SDK stdio, pinned headless JADX, normalization"]
|
ANDROID["src/android/<br/>Static APK adapter<br/>owned SDK stdio, pinned headless JADX, normalization"]
|
||||||
REFERENCE["src/reference/<br/>Filesystem reference-source reader<br/>bounded traversal, validation, errors"]
|
REFERENCE["src/reference/<br/>Filesystem reference-source reader<br/>bounded traversal, validation, errors"]
|
||||||
@@ -50,7 +54,7 @@ graph TD
|
|||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Contracts Layer"
|
subgraph "Contracts Layer"
|
||||||
CONTRACTS["src/contracts/<br/>Caller-visible schemas for analysis, managed, browser, Electron, application, and session tools"]
|
CONTRACTS["src/contracts/<br/>Caller-visible schemas for analysis, reverse engineering, managed, browser, Electron, application, and session tools"]
|
||||||
ERRORS["src/contracts/errorSchemas.ts<br/>Shared analysis error schemas<br/>CLI diagnostics, generated error catalog"]
|
ERRORS["src/contracts/errorSchemas.ts<br/>Shared analysis error schemas<br/>CLI diagnostics, generated error catalog"]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -122,6 +126,14 @@ graph TD
|
|||||||
INSPECTOR --> DOMAIN
|
INSPECTOR --> DOMAIN
|
||||||
APP --> DOTNET
|
APP --> DOTNET
|
||||||
DOTNET --> DOMAIN
|
DOTNET --> DOMAIN
|
||||||
|
COMPOSITION --> OBJDUMP
|
||||||
|
OBJDUMP --> DOMAIN
|
||||||
|
COMPOSITION --> RIZIN
|
||||||
|
RIZIN --> DOMAIN
|
||||||
|
CLI --> GDB
|
||||||
|
SERVER --> GDB
|
||||||
|
GDB --> PROCESS
|
||||||
|
GDB --> DOMAIN
|
||||||
HOPPER --> HOPPER_BRIDGE
|
HOPPER --> HOPPER_BRIDGE
|
||||||
GHIDRA --> GHIDRA_BRIDGE
|
GHIDRA --> GHIDRA_BRIDGE
|
||||||
HOPPER --> PROCESS
|
HOPPER --> PROCESS
|
||||||
|
|||||||
+133
-6
@@ -18,8 +18,19 @@ Inspect native SAZ or HAR 1.2 offline with REA, or connect Everywhere's separate
|
|||||||
- `inspect_web_page` / `rea inspect-web-page` captures DOM structure, accessibility nodes, scripts, resources, safe response/DOM metadata, attach-window network and console metadata, WebSocket frame sizes, workers, quota, and optionally selected storage key names, redacted storage-content fingerprints, or script sources.
|
- `inspect_web_page` / `rea inspect-web-page` captures DOM structure, accessibility nodes, scripts, resources, safe response/DOM metadata, attach-window network and console metadata, WebSocket frame sizes, workers, quota, and optionally selected storage key names, redacted storage-content fingerprints, or script sources.
|
||||||
- `analyze_web_bundle` / `rea analyze-web-bundle` parses selected script artifacts without execution and derives chunk edges, route and endpoint candidates, vendor fingerprints, static WebMCP declarations, and optionally source-map/original-source evidence.
|
- `analyze_web_bundle` / `rea analyze-web-bundle` parses selected script artifacts without execution and derives chunk edges, route and endpoint candidates, vendor fingerprints, static WebMCP declarations, and optionally source-map/original-source evidence.
|
||||||
- `observe_web_session` / `rea observe-web-session` arms an observation window of the requested duration for an external user action and records ordered reload, SPA navigation, redirect, failure, lifecycle, and target-termination metadata.
|
- `observe_web_session` / `rea observe-web-session` arms an observation window of the requested duration for an external user action and records ordered reload, SPA navigation, redirect, failure, lifecycle, and target-termination metadata.
|
||||||
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. REA never exposes `WebMCP.invokeTool`; malformed protocol payloads fail explicitly instead of returning incomplete tool metadata.
|
- `discover_webmcp_tools` / `rea discover-webmcp-tools` uses the experimental CDP WebMCP domain to return every in-scope registration and its complete observed `input_schema` declaration and canonical `input_schema_sha256` digest inline. Declarations are untrusted JSON data, including declared types, required fields, enums, examples and reference URLs. REA does not resolve schema references. These fields replace `input_schema_shape`. Registrations with the same name and URL in distinct observed frames retain separate identities. `tool_key` includes the observed frame ID; it is not a stable identity across navigation or browser sessions. Same-frame registration updates replace that owner only, and removal leaves other owners intact. Declared `readOnly`, `untrustedContent`, `consequential`, and `autosubmit` annotation hints are reported per registration: `true`/`false` are declared values, `null` means the declaration did not provide the hint in that capture, and a missing `consequential` key means the hint was not recorded, either in a historical capture or because the observed annotation was invalid. Consult discovery completeness and exclusions to distinguish these cases. Malformed annotation values retain the registration with incomplete coverage and an `invalid_protocol_value` exclusion. Other malformed protocol payloads can fail discovery explicitly. REA never exposes `WebMCP.invokeTool`.
|
||||||
- `compare_web_captures` / `rea compare-web-captures` compares passive captures or exact-step-aligned browser scenarios. Scenario results commit reproducible literal normalization and expose alignment failures plus artifact-level action, screenshot, DOM, accessibility, URL, history, storage, and event differences. Missing or truncated evidence is never treated as equivalence.
|
- `compare_web_captures` / `rea compare-web-captures` compares passive captures or exact-step-aligned browser scenarios. Scenario results commit reproducible literal normalization and expose alignment failures plus artifact-level action, screenshot, DOM, accessibility, URL, history, storage, and event differences. Missing or truncated evidence is never treated as equivalence.
|
||||||
|
|
||||||
|
Passive DOM comparison checks complete node inventories and complete frame
|
||||||
|
parent/child structure independently. Frame IDs and sibling enumeration order
|
||||||
|
are capture-local and do not establish a change; duplicate IDs, missing parents,
|
||||||
|
and cyclic ancestry remain unknown. A complete frame difference can still prove
|
||||||
|
change when DOM nodes are incomplete, and vice versa. Partial DOM or metadata
|
||||||
|
collection digests do not prove modification: a missing node or link can result
|
||||||
|
from a protocol omission. Rejected DOM facts also mark derived URL and agent-hint
|
||||||
|
metadata incomplete, while valid facts from other nodes and documents remain
|
||||||
|
available in the capture.
|
||||||
|
|
||||||
- `capture_web_screenshot` / `rea capture-web-screenshot` returns a content-addressed visible-viewport PNG.
|
- `capture_web_screenshot` / `rea capture-web-screenshot` returns a content-addressed visible-viewport PNG.
|
||||||
- `compare_web_screenshots` / `rea compare-web-screenshots` performs bounded local PNG pixel comparison without OCR or external services.
|
- `compare_web_screenshots` / `rea compare-web-screenshots` performs bounded local PNG pixel comparison without OCR or external services.
|
||||||
- The CLI comparison commands accept each capture, screenshot artifact or normalization policy as inline JSON or a JSON file path. Use files for screenshot artifacts and scenario captures, whose embedded PNG bytes can exceed the host's command-line length limit.
|
- The CLI comparison commands accept each capture, screenshot artifact or normalization policy as inline JSON or a JSON file path. Use files for screenshot artifacts and scenario captures, whose embedded PNG bytes can exceed the host's command-line length limit.
|
||||||
@@ -107,6 +118,47 @@ first navigation. For a dedicated investigation profile, add
|
|||||||
then describe an unloaded page; REA continues to require an allowed, committed
|
then describe an unloaded page; REA continues to require an allowed, committed
|
||||||
URL. See [the startup investigation](https://github.com/morluto/rea/issues/1384#issuecomment-6093781686).
|
URL. See [the startup investigation](https://github.com/morluto/rea/issues/1384#issuecomment-6093781686).
|
||||||
|
|
||||||
|
### Browsers without HTTP discovery
|
||||||
|
|
||||||
|
Some browsers expose a loopback browser WebSocket but no `/json/version` or
|
||||||
|
`/json/list`. Chrome 144 and later can enable remote debugging on a running
|
||||||
|
browser from `chrome://inspect/#remote-debugging`; this mode is reported to
|
||||||
|
serve only the browser socket. Since Chrome 136, `--remote-debugging-port` is
|
||||||
|
ignored for the default profile, so this is the remaining way to inspect a page
|
||||||
|
in an existing profile.
|
||||||
|
|
||||||
|
For such a browser, pass the browser socket instead of the HTTP endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rea list-browser-targets ws://127.0.0.1:9222/devtools/browser/<id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Chrome writes the port and the `/devtools/browser/<id>` path to the
|
||||||
|
`DevToolsActivePort` file in its user data directory. REA does not read that
|
||||||
|
file for you.
|
||||||
|
|
||||||
|
- Only `list_browser_targets`, `inspect_web_page`, `analyze_web_bundle`,
|
||||||
|
`observe_web_session`, `discover_webmcp_tools`, and `capture_web_screenshot`
|
||||||
|
accept a browser socket. Website runtime attribution, Electron inspection,
|
||||||
|
and scenario attach mode still require HTTP discovery.
|
||||||
|
- Each call opens one WebSocket and uses it for discovery
|
||||||
|
(`Browser.getVersion`, `Target.getTargets`) and for the selected page.
|
||||||
|
Discovery must finish within the same 10-second deadline as HTTP discovery.
|
||||||
|
Chrome may ask you to approve each connection.
|
||||||
|
- The browser identifier controls access to the socket. Evidence records the
|
||||||
|
endpoint as `ws://127.0.0.1:9222/devtools/browser/[REDACTED]`.
|
||||||
|
- `browser.revision` is the socket's `Browser.getVersion` value, such as
|
||||||
|
`@e533e98…`. HTTP discovery reports `WebKit-Version`, such as
|
||||||
|
`537.36 (@e533e98…)`.
|
||||||
|
- `Target.getTargets` reports whether each target is attached. Chrome's
|
||||||
|
`/json/list` can omit it, so HTTP discovery may report `attached` as `null`.
|
||||||
|
- An existing profile can hold many unrelated pages. Target listing returns
|
||||||
|
each eligible page's URL and title, so pass `--allowed-origins` to list only
|
||||||
|
the origins you are investigating.
|
||||||
|
|
||||||
|
A browser driven only through `--remote-debugging-pipe` or a vendor's private
|
||||||
|
automation channel has neither endpoint, and REA cannot attach to it.
|
||||||
|
|
||||||
## Request boundary
|
## Request boundary
|
||||||
|
|
||||||
CDP connections enforce the shared 64 MiB limit per WebSocket message. A larger
|
CDP connections enforce the shared 64 MiB limit per WebSocket message. A larger
|
||||||
@@ -116,9 +168,13 @@ decoded strings or JSON objects.
|
|||||||
|
|
||||||
Discovery supplies a literal loopback CDP endpoint and returns all eligible
|
Discovery supplies a literal loopback CDP endpoint and returns all eligible
|
||||||
page targets unless an exact HTTP(S) origin filter narrows the result. Follow-up
|
page targets unless an exact HTTP(S) origin filter narrows the result. Follow-up
|
||||||
inspection requests select one target; their default scope is its current origin. `localhost`, private-LAN
|
inspection requests select one target; their default scope is its current origin.
|
||||||
addresses, HTTPS CDP endpoints, credentials, paths, queries, fragments, and
|
The endpoint is either `http://127.0.0.1:PORT` or a
|
||||||
implicit ports are not valid CDP endpoints. Origin filters contain only scheme,
|
[browser socket](#browsers-without-http-discovery),
|
||||||
|
`ws://127.0.0.1:PORT/devtools/browser/ID`; `[::1]` may replace `127.0.0.1`.
|
||||||
|
`localhost`, private-LAN addresses, HTTPS and `wss:` endpoints, credentials,
|
||||||
|
queries, fragments, implicit ports, and any other path are not valid CDP
|
||||||
|
endpoints. Origin filters contain only scheme,
|
||||||
host, and port; paths, credentials, queries, fragments, and wildcards are not
|
host, and port; paths, credentials, queries, fragments, and wildcards are not
|
||||||
valid origins.
|
valid origins.
|
||||||
|
|
||||||
@@ -145,6 +201,36 @@ rea discover-webmcp-tools http://127.0.0.1:9222 TARGET_ID \
|
|||||||
--json
|
--json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### WebMCP discovery prerequisites
|
||||||
|
|
||||||
|
`discover_webmcp_tools` reports only what the attached browser's experimental
|
||||||
|
`WebMCP` CDP domain exposes. When a browser rejects the unknown domain, the
|
||||||
|
result reports `status: "unavailable"`. Whether a page can register tools at
|
||||||
|
all depends on several conditions that are not interchangeable:
|
||||||
|
|
||||||
|
- A site's own origin-trial token can enable the API for that origin
|
||||||
|
**provided the trial is still active, the browser version and platform
|
||||||
|
support it, and user settings permit it** — token presence alone is not a
|
||||||
|
guarantee. The trial is announced for Chrome 149 and later.
|
||||||
|
- The `WebMCPTesting` feature — `--enable-features=WebMCPTesting` at launch, or
|
||||||
|
`chrome://flags/#enable-webmcp-testing` in a normal profile — enables the
|
||||||
|
API page-side for that browser instance, independent of per-site tokens.
|
||||||
|
In one pilot run, a site whose registration code was present but whose
|
||||||
|
served token was scoped for third-party use registered 0 tools under a
|
||||||
|
default launch and 4 under the flag; that the token did not enroll the
|
||||||
|
first-party document is consistent with this but is an inference, not a
|
||||||
|
measured Chrome behavior.
|
||||||
|
|
||||||
|
In Chrome 154 specifically (one tested version), `WebMCP.enable` answered
|
||||||
|
under a default launch — the debug domain was not flag-gated in that run.
|
||||||
|
Other versions may differ, so an unattributed zero-tool result should be
|
||||||
|
read as registration state only after domain availability is confirmed.
|
||||||
|
|
||||||
|
Discovery is attach-only: the command needs a reachable loopback CDP endpoint
|
||||||
|
and a target ID from `list_browser_targets`, and it never navigates, reloads,
|
||||||
|
or evaluates the page. Observe the page in the state you want to measure;
|
||||||
|
registered tools reflect the document as loaded.
|
||||||
|
|
||||||
Accessibility names/descriptions, console primitive text, JSON body shapes, WebSocket shapes, script content, and storage key names are selected independently:
|
Accessibility names/descriptions, console primitive text, JSON body shapes, WebSocket shapes, script content, and storage key names are selected independently:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -195,9 +281,36 @@ REA retains local observation data and removes only URL userinfo credentials, ex
|
|||||||
- Accessibility structure and roles are retained by default, while names and descriptions require `include_accessibility_text: true`.
|
- 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.
|
- 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. 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.
|
- 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. 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.
|
||||||
|
For normalized passive inputs with complete request inventories on both sides,
|
||||||
|
request additions and removals remain observable even when selected body shapes,
|
||||||
|
response metadata, or WebSocket coverage are unavailable. HTTP request and
|
||||||
|
WebSocket inventory coverage are evaluated separately; complete HTTP coverage
|
||||||
|
cannot prove a missing WebSocket connection. Limited request inventory still
|
||||||
|
prevents request-absence claims. This comparison rule does not make live CDP's
|
||||||
|
attach-limited inventory complete or establish body-shape equality.
|
||||||
|
An unavailable HTTP resource type also leaves request identity incomplete:
|
||||||
|
observations with the same method and URL remain possible matches across types,
|
||||||
|
and identical unknown types cannot establish complete equality. Ambiguous type
|
||||||
|
groups cannot prove membership or content changes either: a missing type may
|
||||||
|
redistribute repeated requests between buckets without changing the requests.
|
||||||
|
Their comparison remains unknown, while independently identified groups retain
|
||||||
|
proven changes. Different methods/URLs and complete empty peer inventories still
|
||||||
|
support absence claims.
|
||||||
|
Relevant dropped-event counts also limit inventory completeness even when no
|
||||||
|
section is listed as truncated: HTTP uses `network_requests`, while WebSocket
|
||||||
|
uses `websocket_connections` and `websocket_frames`. Drops in other event kinds
|
||||||
|
and the aggregate total do not by themselves limit HTTP inventory. Proven
|
||||||
|
modifications remain reportable despite incomplete inventory.
|
||||||
|
Repeated identical HTTP content samples establish multiplicity changes only
|
||||||
|
with complete request inventories; limited inventories retain known content
|
||||||
|
comparisons without interpreting duplicate sample counts as changes.
|
||||||
|
WebSocket stream inventories and connection counts likewise require complete
|
||||||
|
WebSocket coverage. Redirect resource types use only shared known metadata:
|
||||||
|
an omitted type leaves equality unknown rather than proving a modification.
|
||||||
- Console observations with a stack source retain call type, argument types, timestamp, and source location. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded.
|
- Console observations with a stack source retain call type, argument types, timestamp, and source location. When console text capture is selected, already-delivered primitive values are retained verbatim; objects, getters, and remote properties are never expanded.
|
||||||
- WebSocket observations retain direction, opcode, and payload byte length. When shape capture is selected, text frames are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw frames are never retained.
|
- WebSocket observations retain direction, opcode, and payload byte length. When shape capture is selected, text frames are classified as text or complete value-free JSON shape; binary bytes, hashes, prefixes, and raw frames are never retained.
|
||||||
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
|
- Storage observations always redact values. Key names, IndexedDB names, and cache names require `include_storage_keys`; stable content fingerprints require the additional `include_storage_fingerprints` selection. Cookie fingerprints cover cookies applicable to the current main-frame URL, including its path, rather than every cookie path on that origin. Other storage remains scoped to the origin. Cache bodies above 64 KiB and partial IndexedDB remote objects make fingerprint coverage incomplete, so identical observations remain `unknown`.
|
||||||
|
- Unavailable or malformed storage inventories mark `storage_keys` unavailable, even without fingerprints. Comparisons suppress inferred key additions and removals while preserving modifications proven by complete fingerprints on both sides. Previously exported captures are not retroactively corrected; recapture them to obtain these coverage markers.
|
||||||
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Script metadata admission bounds retained identity and source-map strings to 8 MiB and 50,000 records. When a source-map URL cannot fit, the script identity remains and source-map coverage reports `resource_budget_exhausted`; rejected script observations are counted without retaining their IDs. Source content is omitted unless explicitly requested and is returned inline as a self-verifying artifact with its SHA-256 digest.
|
- Script metadata is included only when CDP supplies a URL on an allowed origin. Stable keys exclude transient CDP script IDs, and exact transient raw URLs are used only during script/resource reconciliation. URL-less scripts are excluded because their origin cannot be established. When CDP supplies an execution-context association, the accepted script retains its authorized frame ID for later attribution. Script metadata admission bounds retained identity and source-map strings to 8 MiB and 50,000 records. When a source-map URL cannot fit, the script identity remains and source-map coverage reports `resource_budget_exhausted`; rejected script observations are counted without retaining their IDs. Source content is omitted unless explicitly requested and is returned inline as a self-verifying artifact with its SHA-256 digest.
|
||||||
|
|
||||||
Frames, resources, scripts, events, and workers outside the selected origin scope are excluded. An explicit `allowed_origins` list can include additional origins; excluded target details are counted without being exposed.
|
Frames, resources, scripts, events, and workers outside the selected origin scope are excluded. An explicit `allowed_origins` list can include additional origins; excluded target details are counted without being exposed.
|
||||||
@@ -218,8 +331,22 @@ validation and resource bounds. Omitted script metadata makes its affected
|
|||||||
section explicitly truncated.
|
section explicitly truncated.
|
||||||
Disallowed-origin entries are filtered before retention.
|
Disallowed-origin entries are filtered before retention.
|
||||||
|
|
||||||
CDP discovery, connection, and commands wait for the peer or caller cancellation;
|
Resource-tree inventories distinguish a valid empty `resources` array from a
|
||||||
a stalled peer can leave an operation waiting. WebSocket messages remain subject
|
missing or malformed collection. Invalid resource entries are omitted with
|
||||||
|
incomplete coverage, while valid resources from that frame and other frames
|
||||||
|
remain available. Comparing an incomplete resource inventory cannot prove
|
||||||
|
addition, removal, or equality; a complete empty inventory can prove removal.
|
||||||
|
Resource records require a nonempty type and a string MIME type; absent or
|
||||||
|
malformed required fields are recorded as omissions instead of invented defaults.
|
||||||
|
An explicitly empty MIME type is valid. The optional content size may be absent;
|
||||||
|
an invalid supplied size remains unknown with incomplete resource coverage while
|
||||||
|
the resource's other facts are retained. Zero and nonnegative fractional sizes
|
||||||
|
are preserved.
|
||||||
|
|
||||||
|
CDP discovery has a 10-second deadline. Each WebSocket handshake and command
|
||||||
|
response has its own 20-second deadline, separate from the caller-selected
|
||||||
|
observation window. A stalled handshake or command reports `provider_timeout`;
|
||||||
|
caller cancellation still reports cancellation. WebSocket messages remain subject
|
||||||
to the [64 MiB limit](#request-boundary). REA does not impose a message-count or
|
to the [64 MiB limit](#request-boundary). REA does not impose a message-count or
|
||||||
pending-command quota. On cancellation, it closes only its own WebSocket,
|
pending-command quota. On cancellation, it closes only its own WebSocket,
|
||||||
waiting up to one second before terminating it. Malformed unsolicited events
|
waiting up to one second before terminating it. Malformed unsolicited events
|
||||||
|
|||||||
@@ -15,11 +15,17 @@ there is no separate origin allowlist or request-interception layer. Environment
|
|||||||
settings (including service workers blocked by default), empty storage, and a
|
settings (including service workers blocked by default), empty storage, and a
|
||||||
final sanitized URL capture are supplied by default. Set
|
final sanitized URL capture are supplied by default. Set
|
||||||
`environment.service_workers` to `allow` when the application requires them.
|
`environment.service_workers` to `allow` when the application requires them.
|
||||||
|
For attached browsers, `block` rejects captures when the selected page or its
|
||||||
|
frames are controlled by a service worker or already have a registration,
|
||||||
|
including after navigation. Existing caller registrations are never removed.
|
||||||
|
Navigation can run destination code before a newly discovered worker is rejected;
|
||||||
|
that rejection does not roll back browser effects. REA removes its owned
|
||||||
|
future-document scripts, restores live frame methods, then disconnects.
|
||||||
Add secret declarations, storage seeds, and additional artifact or event
|
Add secret declarations, storage seeds, and additional artifact or event
|
||||||
capture only when needed. Action, secret, and storage counts are not capped.
|
capture only when needed. Action, secret, and storage counts are not capped.
|
||||||
Duration, action, and navigation
|
Duration, action, and navigation timeouts remain fixed provider-owned liveness
|
||||||
timeouts remain fixed provider-owned liveness limits; the request does not
|
limits. `capture.limits` controls retained event and network-content budgets;
|
||||||
accept a caller-controlled `limits` object.
|
it does not change those liveness deadlines.
|
||||||
|
|
||||||
## Minimal launch example
|
## Minimal launch example
|
||||||
|
|
||||||
@@ -138,6 +144,15 @@ distinction: it closes and deletes provider-owned profiles, but only disconnects
|
|||||||
from an external CDP browser. Real-browser verification checks both outcomes and
|
from an external CDP browser. Real-browser verification checks both outcomes and
|
||||||
confirms that an attached external browser remains alive.
|
confirms that an attached external browser remains alive.
|
||||||
|
|
||||||
|
`environment.locale` sets Intl formatting in both modes. Launch also sets
|
||||||
|
`navigator.language`, `navigator.languages`, and the request `Accept-Language`.
|
||||||
|
Connect preserves the caller's page and request languages; every attached
|
||||||
|
capture identifies this locale limitation, including retained partial
|
||||||
|
observations. Use launch mode when the application must select the requested
|
||||||
|
language through navigator values or request headers. Artifact completeness
|
||||||
|
covers the selected observations, not equivalence of launch and connect
|
||||||
|
language environments.
|
||||||
|
|
||||||
HTTP(S) URLs may include ordinary query values and fragments directly. Use
|
HTTP(S) URLs may include ordinary query values and fragments directly. Use
|
||||||
ordered structured query entries when a value needs to reference a declared
|
ordered structured query entries when a value needs to reference a declared
|
||||||
environment-backed secret. Form, storage, and cookie values are either literal
|
environment-backed secret. Form, storage, and cookie values are either literal
|
||||||
@@ -173,12 +188,44 @@ declared action. Each step reports action status, elapsed time, sanitized URLs,
|
|||||||
event bounds, and independently typed capture states for screenshot, DOM,
|
event bounds, and independently typed capture states for screenshot, DOM,
|
||||||
accessibility, URL, history, and storage. Console, page-error, network,
|
accessibility, URL, history, and storage. Console, page-error, network,
|
||||||
WebSocket, frame, worker, popup, and cancelled-download events are attributed to
|
WebSocket, frame, worker, popup, and cancelled-download events are attributed to
|
||||||
steps and all observed events and requested artifacts are returned inline.
|
steps. Retained events and requested artifacts are returned inline; omitted
|
||||||
There are no event, frame, worker, popup, WebSocket, DOM-node,
|
events are counted by kind and identify affected network transactions.
|
||||||
accessibility-node, storage-entry, screenshot-count, or cumulative
|
Event and selected network-content retention have caller-selected limits under
|
||||||
metadata-byte caps. Requested artifacts and observed events are returned
|
`capture.limits`. Omitted values use the defaults below; every supplied value
|
||||||
inline without application-defined size ceilings. Other missing sections
|
must be a positive safe integer, and callers can raise or lower a default.
|
||||||
remain explicit and make the capture ineligible for equality claims.
|
Byte limits are conservative allocation-cost estimates for retained event
|
||||||
|
values, including UTF-16 storage and JSON expansion, rather than serialized
|
||||||
|
bytes or a promise about exact process memory. `events.omissions.by_kind`
|
||||||
|
reports counts and reasons for omitted event kinds. Its
|
||||||
|
`network_transactions` rows identify affected transaction IDs and omitted
|
||||||
|
event kinds; `network_transaction_range` identifies the contiguous IDs beyond
|
||||||
|
the transaction limit. Omission metadata is bounded by the selected
|
||||||
|
transaction limit. Other missing sections remain explicit and make the capture
|
||||||
|
ineligible for equality claims.
|
||||||
|
|
||||||
|
The default limits are 10,000 events, 4 MiB estimated allocation for one
|
||||||
|
event, 16 MiB estimated allocation across retained events, 1,024 network
|
||||||
|
transactions, 64 KiB of body bytes per transaction phase, 1 MiB estimated
|
||||||
|
allocation for body redaction/base64, 1 MiB for retained header values, and
|
||||||
|
1 MiB for network metadata. The same limits are included in the capture
|
||||||
|
Evidence. Playwright materializes response bodies before REA can inspect their
|
||||||
|
byte length; `max_network_body_bytes` bounds subsequent retained copies and
|
||||||
|
does not bound the browser API read itself. A body that exceeds its selected
|
||||||
|
limit has an explicit `body-size-limit` or `body-allocation-limit` unavailable
|
||||||
|
state with the observed and allowed values.
|
||||||
|
|
||||||
|
Additional implementation bounds are 16 simultaneous network-content reads,
|
||||||
|
a 5-second deadline for each provider API read, and 2,048 live event-listener
|
||||||
|
owners. These are not caller-selectable. The concurrency bound queues admitted
|
||||||
|
content jobs and does not itself omit observations; a provider read timeout
|
||||||
|
produces a typed unavailable state, while exceeding listener capacity produces
|
||||||
|
event-kind omission counts. A preflight avoids body redaction and base64 copies
|
||||||
|
when the currently available event budget already rejects the body.
|
||||||
|
Concurrent reads may each pass that check before their events are retained;
|
||||||
|
their retained-copy work is bounded by 16 times the selected per-body
|
||||||
|
allocation limit. The Playwright response Buffer itself is materialized before
|
||||||
|
REA can apply that limit. Requested artifacts and retained events are returned
|
||||||
|
inline within the declared limits.
|
||||||
Attach-mode captures also declare the unavoidable pre-attach event gap.
|
Attach-mode captures also declare the unavoidable pre-attach event gap.
|
||||||
|
|
||||||
Selected event families are observed on the root page and discovered descendant
|
Selected event families are observed on the root page and discovered descendant
|
||||||
@@ -249,8 +296,12 @@ Each observed request object receives a capture-scoped `transaction_id`.
|
|||||||
Requests sharing a URL remain distinct. Redirects link to the preceding
|
Requests sharing a URL remain distinct. Redirects link to the preceding
|
||||||
transaction only when it was observed; a null predecessor does not establish
|
transaction only when it was observed; a null predecessor does not establish
|
||||||
that no earlier hop existed. Request events require both transaction fields;
|
that no earlier hop existed. Request events require both transaction fields;
|
||||||
an absent ID is malformed capture data, while a null redirect predecessor is
|
an absent ID is malformed capture data. `redirected_from_transaction_state`
|
||||||
an explicit unknown.
|
records `root` when Playwright observed no predecessor and `unknown` when a
|
||||||
|
predecessor exists but was outside the capture. A linked predecessor is carried
|
||||||
|
by `redirected_from_transaction_id` with no state field. Older captures without
|
||||||
|
the state field retain the conservative meaning that a null predecessor is
|
||||||
|
unknown.
|
||||||
|
|
||||||
`scenario.network_content` records the resolved content selection. Comparing
|
`scenario.network_content` records the resolved content selection. Comparing
|
||||||
captures with different selections reports network evidence as unknown with a
|
captures with different selections reports network evidence as unknown with a
|
||||||
@@ -278,14 +329,21 @@ retained representation after redaction.
|
|||||||
|
|
||||||
Only completed responses are read. At the end of the final step, observation
|
Only completed responses are read. At the end of the final step, observation
|
||||||
stops; requests still in flight emit `request-unfinished`, and their observed
|
stops; requests still in flight emit `request-unfinished`, and their observed
|
||||||
responses have explicitly unavailable bodies. This cutoff does not wait for
|
responses have explicitly unavailable bodies. Already-admitted content reads
|
||||||
long-lived streaming connections. Already-started content reads settle before
|
drain before browser cleanup. At most 16 reads run at once; queued work is
|
||||||
browser cleanup, with a provider-owned five-second timeout per read and
|
bounded by the selected network-transaction limit and by successful admission
|
||||||
cancellation support. Failed or unexposed reads retain per-content states and make selected
|
of its request or response event. Each provider API read has its own five-second
|
||||||
event coverage incomplete. Body events can arrive during later steps or final
|
timeout once it starts, so queue wait adds to total drain and cleanup time; this
|
||||||
drain; event bounds describe receipt during the step, while the source event
|
is not a five-second total capture cutoff. Cancellation interrupts active reads
|
||||||
reference establishes the transaction phase. Comparison attributes content to
|
and queued jobs produce cancelled states without calling their provider APIs.
|
||||||
its source event's step and excludes the sequence reference from normalization.
|
This drain does not wait for long-lived unfinished streaming connections and
|
||||||
|
does not refetch unfinished response bodies. Failed or unexposed reads retain
|
||||||
|
per-content states and make selected event coverage incomplete. Body events can
|
||||||
|
arrive during later steps or final drain; event bounds describe receipt during
|
||||||
|
the step, while the source event reference establishes the transaction phase.
|
||||||
|
Comparison attributes content, like every other event of its transaction, to
|
||||||
|
the step that sent the request (the receipt step when that request was not
|
||||||
|
observed) and excludes the sequence reference from normalization.
|
||||||
Playwright scenario capture does not supply initiator stacks; passive CDP
|
Playwright scenario capture does not supply initiator stacks; passive CDP
|
||||||
observation retains its own initiator evidence.
|
observation retains its own initiator evidence.
|
||||||
|
|
||||||
@@ -300,7 +358,22 @@ an unchanged claim; observed differences may still prove a changed result.
|
|||||||
The comparison records its full normalization commitment and SHA-256 digest.
|
The comparison records its full normalization commitment and SHA-256 digest.
|
||||||
Built-in rules exclude step elapsed time and event sequence/index fields,
|
Built-in rules exclude step elapsed time and event sequence/index fields,
|
||||||
compare screenshots by content digest, and compare DOM/accessibility captures
|
compare screenshots by content digest, and compare DOM/accessibility captures
|
||||||
by normalized text. Optional caller rules are bounded exact-literal
|
by normalized text. Network events are grouped by transaction without their
|
||||||
|
capture-local transaction IDs, attributed to the step that sent the request,
|
||||||
|
and compared as an unordered collection after caller rules apply, because
|
||||||
|
concurrent responses arrive in any order and can land in a later step; a
|
||||||
|
redirect retains the complete predecessor ancestry, including each hop's own
|
||||||
|
request, response outcome, and selected network content. Each transaction's
|
||||||
|
capture-local IDs, sequence/index fields, and content source-sequence anchor
|
||||||
|
are removed. Caller rules normalize retained strings before hashing. The
|
||||||
|
`preserve_redirect_transaction_ancestry` built-in rule fingerprints each hop
|
||||||
|
as canonical SHA-256 of `{ events, redirected_from }`: `events` is the sorted
|
||||||
|
list of its normalized event SHA-256 digests, and `redirected_from` is its
|
||||||
|
predecessor fingerprint or null for a root. Selected transaction fingerprints
|
||||||
|
are sorted for the step's network comparison. Temporary numeric graph indices
|
||||||
|
are lookup references only and do not enter the final digest. Missing or cyclic
|
||||||
|
predecessor evidence yields unknown comparability instead of an unchanged claim.
|
||||||
|
Other events keep receipt order. Optional caller rules are bounded exact-literal
|
||||||
replacements over named artifact kinds. Rules and artifact-kind lists are
|
replacements over named artifact kinds. Rules and artifact-kind lists are
|
||||||
sorted canonically before application, making the same policy reproducible
|
sorted canonically before application, making the same policy reproducible
|
||||||
regardless of input order. Rules operate only on already-redacted durable
|
regardless of input order. Rules operate only on already-redacted durable
|
||||||
@@ -318,3 +391,7 @@ selection. Captures with different selections report event evidence as unknown
|
|||||||
with a recovery instruction, even when no selected family emitted an event.
|
with a recovery instruction, even when no selected family emitted an event.
|
||||||
Captures written before this field existed omit it, so their event evidence is
|
Captures written before this field existed omit it, so their event evidence is
|
||||||
also unknown.
|
also unknown.
|
||||||
|
|
||||||
|
Network normalization also commits the removal of capture-local transaction IDs,
|
||||||
|
attribution to the requesting step, and unordered transaction comparison. These
|
||||||
|
built-in rule identities participate in the normalization digest.
|
||||||
+111
-12
@@ -83,8 +83,21 @@ The shared CLI/MCP workflow reports compiler version, modules, replacements,
|
|||||||
build settings, original bytes and SHA-256. See [Go binaries](go-binaries.md)
|
build settings, original bytes and SHA-256. See [Go binaries](go-binaries.md)
|
||||||
for supported containers and absent or malformed metadata.
|
for supported containers and absent or malformed metadata.
|
||||||
|
|
||||||
Configure [Hopper or Ghidra](installation.md#hopper), or the
|
Inspect an embedded cargo-auditable Rust dependency list without installing
|
||||||
[IDA adapter](ida-provider.md), before analyzing a native target. Substitute
|
Rust or configuring a disassembler:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rea inspect-rust-binary /absolute/path/to/program --json
|
||||||
|
```
|
||||||
|
|
||||||
|
The same workflow reports package names, versions, sources, the root package,
|
||||||
|
dependency edges, and the section's file range. A missing `.dep-v0` section is
|
||||||
|
null metadata, not proof that the binary has no dependencies. See
|
||||||
|
[Rust binaries](rust-binaries.md).
|
||||||
|
|
||||||
|
Configure [Hopper or Ghidra](installation.md#hopper), the
|
||||||
|
[IDA adapter](ida-provider.md), or [Kuna](kuna-provider.md) before analyzing a
|
||||||
|
native target. Substitute
|
||||||
your target, search text and function name or address in these examples:
|
your target, search text and function name or address in these examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -140,9 +153,13 @@ overrides it. In MCP, pass `provider_id` to `open_binary`:
|
|||||||
The selected provider remains bound to the session until an explicit switch
|
The selected provider remains bound to the session until an explicit switch
|
||||||
or close. Provider failures are returned with their original reason. For an
|
or close. Provider failures are returned with their original reason. For an
|
||||||
`ambiguous` selection error, choose from `details.candidate_ids`; for
|
`ambiguous` selection error, choose from `details.candidate_ids`; for
|
||||||
`provider_unavailable`, run `rea doctor --provider ID --json` to diagnose the
|
`provider_unavailable`, follow `message`, `remediation.action`, and the original
|
||||||
selected engine. See [task readiness](installation.md#check-readiness-for-your-task)
|
reasons in `details.rejections`. For Hopper, Ghidra, IDA, and JADX, run
|
||||||
and [provider selection](mcp-contracts.md#identity-and-discovery).
|
`rea doctor --provider ID --json` to diagnose the selected engine. Other adapters,
|
||||||
|
such as `pwntools-elf`, report their configuration requirements directly and are
|
||||||
|
not accepted by `doctor --provider`. See
|
||||||
|
[task readiness](installation.md#check-readiness-for-your-task) and
|
||||||
|
[provider selection](mcp-contracts.md#identity-and-discovery).
|
||||||
|
|
||||||
The session reports work still in progress through `analysis_activity`.
|
The session reports work still in progress through `analysis_activity`.
|
||||||
A client timeout can end its wait while the provider continues analyzing.
|
A client timeout can end its wait while the provider continues analyzing.
|
||||||
@@ -214,8 +231,36 @@ Import an older source tree to compare with the current artifact:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
rea import-reference-source /absolute/path/to/source
|
rea import-reference-source /absolute/path/to/source
|
||||||
|
rea import-reference-source /absolute/path/to/source --detail summary --outline-depth 2 --json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The default (`--detail full`) returns the complete `HistoricalSourceGraph`.
|
||||||
|
Choose `--detail summary` for a `HistoricalSourceSummary` of that same committed
|
||||||
|
inventory: its `HistoricalSourceManifest` contains the full graph and root
|
||||||
|
digests, with provenance, VCS facts, languages, manifest paths, entry and
|
||||||
|
relationship counts, parse-failure counts, file/directory states, exclusions
|
||||||
|
and limitations. The summary is a presentation view, not a graph accepted by
|
||||||
|
source-to-bundle comparison; keep the full output for that workflow.
|
||||||
|
|
||||||
|
`--outline-depth` requires `--detail summary` and accepts a nonnegative safe
|
||||||
|
integer. Depth 1 shows root children, depth 2 (the default) also shows their
|
||||||
|
children, and depth 0 omits all outline entries. Paths retain canonical Unicode
|
||||||
|
code-point order. `outline.omitted_entry_count` counts only inventoried entries
|
||||||
|
hidden by the chosen depth; it does not count unexamined children of excluded or
|
||||||
|
unreadable directories, or change `inventory_state`. Exclusions and source
|
||||||
|
limitations remain separate from this presentation omission.
|
||||||
|
|
||||||
|
`file_sizes.known_bytes` is the exact sum of known inventoried file sizes as
|
||||||
|
decimal text, so large sums cannot lose integer precision. Unknown sizes count
|
||||||
|
in `file_sizes.unknown_file_count`; excluded or unexamined paths are not included
|
||||||
|
in that total. A zero known-byte sum therefore does not establish an empty source
|
||||||
|
tree. Full output retains individual file hashes, relationships and parse-failure
|
||||||
|
diagnostics. Summary mode performs the same import and cleanup, then projects
|
||||||
|
the committed result without rereading source files. It reduces the selected
|
||||||
|
output, not the amount of source examined. A shallow tree or many exclusions can
|
||||||
|
still produce a large summary; depth 0 removes the outline but preserves the
|
||||||
|
other facts. No fixed output-size or runtime improvement is promised.
|
||||||
|
|
||||||
The import records hashes and metadata for the supplied files, separately
|
The import records hashes and metadata for the supplied files, separately
|
||||||
from observations of the current app. For a directory inside a Git checkout,
|
from observations of the current app. For a directory inside a Git checkout,
|
||||||
it records the HEAD of the nearest enclosing repository; whether the working
|
it records the HEAD of the nearest enclosing repository; whether the working
|
||||||
@@ -223,7 +268,12 @@ tree matches that commit remains unknown. It honors the source tree's `.gitignor
|
|||||||
and applies default exclusions for common generated and dependency paths such
|
and applies default exclusions for common generated and dependency paths such
|
||||||
as `node_modules/`, `dist/`, and `*.log`. Pattern-based exclusions record the
|
as `node_modules/`, `dist/`, and `*.log`. Pattern-based exclusions record the
|
||||||
matched pattern and whether it came from project, default, caller, or sensitive
|
matched pattern and whether it came from project, default, caller, or sensitive
|
||||||
path policy. Set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON array of ignore
|
path policy. Links into excluded paths retain their internal target metadata;
|
||||||
|
the exclusion explains why the target has no inventory entry, and the inventory
|
||||||
|
remains partial. Exclusions record `entry_kind` when the importer observed it;
|
||||||
|
older saved exclusions without that field have unknown kind. An excluded
|
||||||
|
ancestor proves an internal descendant only when its kind is `directory`.
|
||||||
|
Set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON array of ignore
|
||||||
patterns when you want to mark selected paths as sensitive; those patterns are
|
patterns when you want to mark selected paths as sensitive; those patterns are
|
||||||
reported separately as configured-secret exclusions.
|
reported separately as configured-secret exclusions.
|
||||||
|
|
||||||
@@ -247,6 +297,7 @@ Choose the guide for the target and intended interaction:
|
|||||||
- [Browser observation](browser-observation.md): inspect a selected page through CDP.
|
- [Browser observation](browser-observation.md): inspect a selected page through CDP.
|
||||||
- [Browser scenarios](browser-scenario-contract.md): run declared interactions and capture their results.
|
- [Browser scenarios](browser-scenario-contract.md): run declared interactions and capture their results.
|
||||||
- [Electron observation](electron-observation.md): inspect an Electron renderer or capture an application scenario.
|
- [Electron observation](electron-observation.md): inspect an Electron renderer or capture an application scenario.
|
||||||
|
- [Electron fuses](electron-fuses.md): read packaged fuse bytes before expecting `--inspect` to reach an Electron main process.
|
||||||
- [Node/Electron Inspector](javascript-runtime-observation.md): record script locations and execution contexts.
|
- [Node/Electron Inspector](javascript-runtime-observation.md): record script locations and execution contexts.
|
||||||
- [Process capture](process-capture.md): run an executable and compare terminal, exit and filesystem observations.
|
- [Process capture](process-capture.md): run an executable and compare terminal, exit and filesystem observations.
|
||||||
|
|
||||||
@@ -255,17 +306,65 @@ user permissions; consult the chosen guide for host requirements and effects.
|
|||||||
|
|
||||||
## Output and exit status
|
## Output and exit status
|
||||||
|
|
||||||
|
`rea mcp doctor --json` checks production MCP startup and reports child logs in
|
||||||
|
`diagnostics`. It drains stderr while retaining at most its first 64 KiB of raw
|
||||||
|
bytes as text, omitting a final UTF-8 code point if it crosses that byte limit.
|
||||||
|
`stderr_bytes` counts all observed bytes, including omitted output;
|
||||||
|
`stderr_truncated` indicates that output exceeded the retention limit. The same
|
||||||
|
diagnostics appear inside the report when `--full-output` is selected. Log
|
||||||
|
truncation alone does not change the diagnostic health or exit status.
|
||||||
|
|
||||||
The default terminal format is TOON. Use `--json` when saving results for a
|
The default terminal format is TOON. Use `--json` when saving results for a
|
||||||
JSON consumer. `--json` is indented on a terminal and compact when piped or
|
JSON consumer. Valued output flags (`--format`, `--filter-output`,
|
||||||
|
`--token-limit`, `--token-offset`) accept `--flag value` or `--flag=value`; a
|
||||||
|
missing value fails with `MISSING_OUTPUT_VALUE` before the command runs. `--json` is indented on a terminal and compact when piped or
|
||||||
redirected, which keeps large results smaller for agents and scripts; pipe
|
redirected, which keeps large results smaller for agents and scripts; pipe
|
||||||
through `jq .` for indented files. Output selection and formatting do not change
|
through `jq .` for indented files. Output selection and formatting do not change
|
||||||
operation status.
|
operation status.
|
||||||
|
|
||||||
| Status | Meaning |
|
For `cutter-command`, `--json-output` requests command-specific JSON from
|
||||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
Cutter when available. This is separate from `--format json`, which selects the
|
||||||
| `0` | The operation completed. Its result may include partial evidence, warnings or unresolved questions. |
|
CLI result representation; the global `--json` flag selects that same CLI
|
||||||
| `1` | The operation could not complete. Structured output identifies invalid input, permissions, cancellation, timeouts or another failure when available. |
|
representation.
|
||||||
| `128 + N` | Signal `N` ended the process, where the shell or runtime preserves the conventional signal-derived status. |
|
|
||||||
|
Cutter still accepts deprecated `--json=true` and `--json=false` compatibility
|
||||||
|
values. When both forms are present, `--json-output` takes precedence in either
|
||||||
|
argument order. Bare `--json` retains its global meaning of selecting JSON CLI
|
||||||
|
output formatting.
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | The operation completed. Its result may include partial evidence, warnings or unresolved questions. |
|
||||||
|
| `1` | The operation could not complete. Structured output identifies invalid input, permissions, cancellation, timeouts or another failure when available. |
|
||||||
|
| `128 + N` | Signal-derived status. An unhandled signal may have ended the process, with the shell or runtime preserving the conventional status; a command may instead have handled the signal, waited for its operation and cleanup to settle, and assigned the same status (`130` for SIGINT, `143` for SIGTERM). In the handled case, it may also have printed a structured `cancelled` result. |
|
||||||
|
|
||||||
|
A `cancelled` result does not by itself determine the exit status; it depends on
|
||||||
|
the command's signal handling. Commands routed through the shared
|
||||||
|
`withCommandCancellation` handler in the CLI set status `130` for SIGINT or
|
||||||
|
`143` for SIGTERM after their operation settles. Current source registrations
|
||||||
|
use that handler for process capture and comparison (`capture-process`,
|
||||||
|
`compare-process-captures`), JavaScript application analysis (`analyze` when it
|
||||||
|
selects the JavaScript lane, and `analyze-javascript-application`), selected
|
||||||
|
browser, web and Electron commands (including `analyze-web-bundle`,
|
||||||
|
`observe-web-execution`, `capture-browser-scenario` and
|
||||||
|
`capture-electron-scenario`), and the platform and provider command families
|
||||||
|
for Android/ADB, Apktool/JEB/Flutter, firmware, PCAP and signal analysis,
|
||||||
|
WASM/EVM, Go/Rust and binary diagnostics, Frida instrumentation, the Cutter
|
||||||
|
command, objdump, Rizin and GDB. In these paths, a returned structured
|
||||||
|
`cancelled` result can be printed before the wrapper assigns the
|
||||||
|
signal-derived status. This describes the current source routing; it is not a
|
||||||
|
claim that every command or provider runtime has been independently exercised.
|
||||||
|
|
||||||
|
When the reader of piped output closes early, as `rea ... | head` does, the
|
||||||
|
CLI stops writing, prints no error and exits `141` (`128 + 13`, the status of a
|
||||||
|
SIGPIPE-terminated writer). The operation itself may have completed; only its
|
||||||
|
output was declined.
|
||||||
|
|
||||||
|
`inspect-artifact` also returns a structured cancelled result, but its direct
|
||||||
|
analysis cancellation path does not set a signal-derived exit status, so it
|
||||||
|
exits `1`. Setup has its own contract: cancellation exits `0`. Treat the
|
||||||
|
structured result and process status as separate signals when scripting
|
||||||
|
cancellation.
|
||||||
|
|
||||||
`setup --dry-run` returns `planned` and exits `0`; a cancelled setup also exits
|
`setup --dry-run` returns `planned` and exits `0`; a cancelled setup also exits
|
||||||
`0`. Setup returns `1` for `needs_confirmation` or `needs_human`.
|
`0`. Setup returns `1` for `needs_confirmation` or `needs_human`.
|
||||||
|
|||||||
@@ -33,6 +33,71 @@ schema constraints; REA still validates complete canonical inputs. Full-profile
|
|||||||
`gpt-5.4` metadata is also verified locally. Live model API acceptance and
|
`gpt-5.4` metadata is also verified locally. Live model API acceptance and
|
||||||
Windows remain unverified; see [native client verification](testing.md).
|
Windows remain unverified; see [native client verification](testing.md).
|
||||||
|
|
||||||
|
## Claude Desktop
|
||||||
|
|
||||||
|
Setup registers `mcpServers.rea` in `claude_desktop_config.json`: under
|
||||||
|
`~/Library/Application Support/Claude` on macOS, under
|
||||||
|
`$XDG_CONFIG_HOME/Claude` (default `~/.config/Claude`) on Linux, and on Windows
|
||||||
|
in the file the installed app reads:
|
||||||
|
|
||||||
|
- Without Claude Desktop's MSIX package storage, setup uses
|
||||||
|
`%APPDATA%\Claude\claude_desktop_config.json`.
|
||||||
|
- The MSIX build (package family `Claude_pzs8sxrjxfjjc`, installed under
|
||||||
|
`C:\Program Files\WindowsApps\Claude_<version>_x64__pzs8sxrjxfjjc`)
|
||||||
|
virtualizes AppData writes. Once it has saved its configuration, it reads
|
||||||
|
the package-private copy
|
||||||
|
`%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json`
|
||||||
|
and ignores the `%APPDATA%` file. Setup, doctor and uninstall use that
|
||||||
|
private copy when it exists. Before the package has saved one, they use the
|
||||||
|
`%APPDATA%` file it still reads; with neither file, setup creates the
|
||||||
|
private copy.
|
||||||
|
- If `%LOCALAPPDATA%\Packages` holds a `Claude_<publisher ID>` package from
|
||||||
|
another publisher, or cannot be read, REA cannot establish which file Claude
|
||||||
|
Desktop reads. Setup, doctor and uninstall report that instead of falling
|
||||||
|
back to `%APPDATA%`. Doctor then reports `claude_desktop` as invalid, and
|
||||||
|
uninstall stops before removing anything for any client.
|
||||||
|
|
||||||
|
Quit Claude Desktop fully before approving setup; closing its window is not
|
||||||
|
enough. A running Claude Desktop can rewrite its configuration from memory and
|
||||||
|
drop the registration. This was observed with the Windows MSIX build. Start
|
||||||
|
Claude Desktop again after setup. If the package saves its private copy after
|
||||||
|
setup planned to write the `%APPDATA%` file, setup makes no change and asks you
|
||||||
|
to rerun it.
|
||||||
|
|
||||||
|
A process that the MSIX app started, such as a terminal in Claude Desktop's
|
||||||
|
Code tab, sees the package's private files at their `%APPDATA%` paths even
|
||||||
|
though it reports no package identity. Setup compares one of those files at
|
||||||
|
both paths. When it finds that redirected view, the app is running, so setup
|
||||||
|
and uninstall refuse to change Claude Desktop's configuration; rerun them from
|
||||||
|
a terminal that Claude Desktop did not start. If the package has no private
|
||||||
|
file to compare, setup and uninstall cannot rule that out and also refuse; open
|
||||||
|
Claude Desktop once if it has never run, quit it fully, then rerun. Doctor
|
||||||
|
reads the same file from either kind of process.
|
||||||
|
|
||||||
|
The same process also writes everything under `%APPDATA%` and
|
||||||
|
`%LOCALAPPDATA%` into the package's private storage, where other apps never
|
||||||
|
read it. From that redirected view, setup and uninstall refuse to change other
|
||||||
|
clients' files there (VS Code, Trae, Devin and Hermes) and skill directories
|
||||||
|
there. `npm install -g` run from such a process installs into the package's
|
||||||
|
`LocalCache\Roaming\npm`, which other processes cannot see at the `%APPDATA%`
|
||||||
|
path. Setup refuses to register a launcher stored in any MSIX package's
|
||||||
|
private storage. Install REA from an ordinary terminal and rerun setup there.
|
||||||
|
|
||||||
|
Uninstall removes REA's entry from both the private copy and the `%APPDATA%`
|
||||||
|
file, including registrations that earlier releases wrote to `%APPDATA%`.
|
||||||
|
|
||||||
|
REA recognizes only the MSIX package. A non-MSIX Claude Desktop installed
|
||||||
|
beside it, which would read the `%APPDATA%` file, is not detected.
|
||||||
|
|
||||||
|
The package identity, both file locations, the redirected view, and these
|
||||||
|
setup and doctor results were observed on Windows 11 with the MSIX build
|
||||||
|
2.31226.1.0, using read-only `doctor` and `setup --dry-run` runs both inside
|
||||||
|
and outside the app. On that host, setup run from an ordinary terminal with
|
||||||
|
the app quit wrote the package-private copy; after the app restarted, the
|
||||||
|
entry was still present, doctor reported it aligned, and Claude Desktop's MCP
|
||||||
|
log showed REA starting and answering `tools/list`. Uninstall against the real
|
||||||
|
app is unverified.
|
||||||
|
|
||||||
## Claude Code
|
## Claude Code
|
||||||
|
|
||||||
Claude Code normally defers MCP tool definitions through tool search. It
|
Claude Code normally defers MCP tool definitions through tool search. It
|
||||||
@@ -64,6 +129,51 @@ and Claude Code reports that autocompact is thrashing. To avoid this:
|
|||||||
The sizes above come from REA's own `tools/list`. A live Claude Code session
|
The sizes above come from REA's own `tools/list`. A live Claude Code session
|
||||||
against DeepSeek's API is unverified.
|
against DeepSeek's API is unverified.
|
||||||
|
|
||||||
|
For a client whose stdio reader rejects the complete catalog frame, add
|
||||||
|
`"REA_MCP_OUTPUT_SCHEMA_PROFILE": "off"` to REA's `env` object and restart the
|
||||||
|
client. This omits only advertised output schemas; REA still validates complete
|
||||||
|
structured results against its canonical contracts. See [MCP output schema
|
||||||
|
profiles](mcp-contracts.md#output-schemas).
|
||||||
|
|
||||||
|
## Kimi Code
|
||||||
|
|
||||||
|
Setup registers `rea` in `~/.kimi-code/mcp.json`, or
|
||||||
|
`$KIMI_CODE_HOME/mcp.json` when set, while preserving unrelated settings and
|
||||||
|
creating a `.rea.backup` before updating an existing file. Kimi Code infers
|
||||||
|
stdio from the `command` entry, so setup does not add a `type` field. New
|
||||||
|
registrations include `startupTimeoutMs = 30000` and a `toolTimeoutMs` derived
|
||||||
|
from REA's known provider allowance plus 120 seconds of operation
|
||||||
|
headroom. The default is 450000 ms, covering the default 330-second Ghidra
|
||||||
|
startup; an explicit Ghidra startup timeout of 2700000 ms produces a 2820000 ms
|
||||||
|
tool timeout. The startup allowance follows an explicitly selected Ghidra or
|
||||||
|
Hopper provider, and otherwise uses the larger known Ghidra/Hopper deadline.
|
||||||
|
An explicit `REA_JEB_MCP_TIMEOUT_MS` request budget also contributes to that
|
||||||
|
maximum. For `ida` or `auto` selection, a valid IDA registration's parsed
|
||||||
|
`timeoutMs` also contributes to that maximum; its default is 300000 ms.
|
||||||
|
Setup forwards these explicitly selected REA deadlines and the input schema
|
||||||
|
profile into the registration; unrelated process values are omitted.
|
||||||
|
This headroom is a practical default, not a guarantee for every analysis
|
||||||
|
duration. Setup preserves positive timeout values already in the `rea` entry.
|
||||||
|
You can edit `startupTimeoutMs` and `toolTimeoutMs` there; doctor keeps positive
|
||||||
|
caller-selected values aligned and reports a non-blocking advisory when a tool
|
||||||
|
timeout is shorter than the known provider allowance.
|
||||||
|
Setup selects the compact input-schema advertisement for new or refreshed Kimi
|
||||||
|
registrations unless their stored configuration or setup environment explicitly
|
||||||
|
selects `REA_MCP_INPUT_SCHEMA_PROFILE=full` or `compact`. This keeps REA's
|
||||||
|
canonical tool validation unchanged while avoiding a Moonshot Kimi Code 2.2.0
|
||||||
|
failure observed with the full catalog: its request was rejected because a
|
||||||
|
single tool schema exceeded the provider's size limit. The limit is
|
||||||
|
provider-specific and not an MCP protocol limit. `rea doctor --client kimi`
|
||||||
|
reports an older registration with no explicit profile as stale. To restore the
|
||||||
|
compact profile, run `rea setup --client kimi` or add
|
||||||
|
`REA_MCP_INPUT_SCHEMA_PROFILE=compact` to the registration's `env`, then restart
|
||||||
|
Kimi Code. An explicit `full` profile remains respected and can still exceed
|
||||||
|
Moonshot's limit.
|
||||||
|
Kimi Code also discovers personal skills in `~/.agents/skills` and project-local
|
||||||
|
`.agents/skills`, so no separate skill root is required; setup still installs
|
||||||
|
and verifies the matching skill files through the normal shared-skill flow.
|
||||||
|
Start a new Kimi Code session to pick up MCP and skill changes.
|
||||||
|
|
||||||
## Pi
|
## Pi
|
||||||
|
|
||||||
For [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md),
|
For [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md),
|
||||||
@@ -124,8 +234,20 @@ standalone CLI check.
|
|||||||
|
|
||||||
## Codex and Devin
|
## Codex and Devin
|
||||||
|
|
||||||
Codex treats an unset or empty `CODEX_HOME` as `~/.codex`. Devin honors
|
Codex treats an unset or empty `CODEX_HOME` as `~/.codex`. Its MCP server
|
||||||
`XDG_CONFIG_HOME` for its `devin/mcp_config.json` on non-Windows systems.
|
registration supports `startup_timeout_sec` for connection initialization and
|
||||||
|
`tool_timeout_sec` for each tool call. New REA registrations use a 30-second
|
||||||
|
startup timeout and a tool timeout derived from the known provider
|
||||||
|
allowance plus 120 seconds of operation headroom. This yields 450 seconds for
|
||||||
|
the default Ghidra allowance; an explicit Ghidra startup timeout of 2700
|
||||||
|
seconds yields 2820 seconds. Setup preserves positive caller-selected timeout
|
||||||
|
values in the REA entry, and doctor reports a non-blocking advisory when a tool
|
||||||
|
timeout is shorter than the known provider allowance. An explicitly selected
|
||||||
|
JEB request budget also contributes to the allowance. A configured IDA
|
||||||
|
registration's parsed `timeoutMs` contributes for `ida` or `auto` selection.
|
||||||
|
These values are client-side deadlines, so increase them in `~/.codex/config.toml` if a
|
||||||
|
workload needs more time. Devin honors `XDG_CONFIG_HOME` for its
|
||||||
|
`devin/mcp_config.json` on non-Windows systems.
|
||||||
|
|
||||||
## OMP
|
## OMP
|
||||||
|
|
||||||
@@ -188,6 +310,21 @@ The user-level MCP paths follow the editor's VS Code layout. Trae's official
|
|||||||
documentation describes the raw JSON editor without specifying those paths;
|
documentation describes the raw JSON editor without specifying those paths;
|
||||||
loading this registration in Trae remains unverified.
|
loading this registration in Trae remains unverified.
|
||||||
|
|
||||||
|
## WorkBuddy
|
||||||
|
|
||||||
|
For [WorkBuddy](https://www.workbuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/MCP-Guide), setup writes `mcpServers.rea` into the
|
||||||
|
global `~/.workbuddy/mcp.json` (a standard `mcpServers` JSON file at the user
|
||||||
|
home root on every platform). Entries carry `command`, `args`, and `env`;
|
||||||
|
stdio is the default transport. WorkBuddy discovers personal skills from
|
||||||
|
`~/.workbuddy/skills` (`%userprofile%\.workbuddy\skills` on Windows), so
|
||||||
|
setup installs its skill copy there.
|
||||||
|
|
||||||
|
The global MCP path is a plain JSON file; setup preserves every other server
|
||||||
|
and top-level key when it upserts `rea`. WorkBuddy also reads a project-level
|
||||||
|
`<project>/.workbuddy/mcp.json`, which setup does not touch: it registers the
|
||||||
|
user-level file only. Loading this registration and real REA tool calls
|
||||||
|
through WorkBuddy are verified.
|
||||||
|
|
||||||
## ZCode
|
## ZCode
|
||||||
|
|
||||||
For [ZCode](https://zcode.z.ai/en/docs/mcp-services), setup registers `rea` in the nested `mcp.servers` table of the
|
For [ZCode](https://zcode.z.ai/en/docs/mcp-services), setup registers `rea` in the nested `mcp.servers` table of the
|
||||||
@@ -202,7 +339,11 @@ the first native server into an empty table, it copies the fallback's
|
|||||||
`mcpServers` entries into the native table so they keep loading, and leaves
|
`mcpServers` entries into the native table so they keep loading, and leaves
|
||||||
the fallback file untouched. Existing REA environment overrides are preserved
|
the fallback file untouched. Existing REA environment overrides are preserved
|
||||||
when refreshing its command. A malformed or unreadable fallback file fails setup instead of
|
when refreshing its command. A malformed or unreadable fallback file fails setup instead of
|
||||||
being silently suppressed; repair or remove it and rerun. ZCode disables a
|
being silently suppressed; repair or remove it and rerun. Uninstall removes an
|
||||||
|
REA-owned `rea` entry from both the native table and the fallback, backing up
|
||||||
|
each changed file, so an emptied native table cannot load a leftover fallback
|
||||||
|
registration; other fallback servers, settings and a foreign `rea` entry are
|
||||||
|
kept. ZCode disables a
|
||||||
server with `enable: false` on its entry; doctor reports such a registration
|
server with `enable: false` on its entry; doctor reports such a registration
|
||||||
as stale, and rerunning setup removes the flag to repair it. Native configuration
|
as stale, and rerunning setup removes the flag to repair it. Native configuration
|
||||||
uses strict JSON; setup refuses comments, trailing commas and a BOM rather than
|
uses strict JSON; setup refuses comments, trailing commas and a BOM rather than
|
||||||
|
|||||||
@@ -6,6 +6,28 @@ and provider lanes. Each record below states its host, client version, model
|
|||||||
fixture, and remaining coverage; a native-client run with a loopback model does
|
fixture, and remaining coverage; a native-client run with a loopback model does
|
||||||
not establish live model-service acceptance.
|
not establish live model-service acceptance.
|
||||||
|
|
||||||
|
## Published Pi code-mode renderer limit
|
||||||
|
|
||||||
|
A separate probe of the published `@earendil-works/pi-codemode` 1.1.0 and 1.2.0
|
||||||
|
declaration renderers rendered the full `compare_web_captures` input as
|
||||||
|
`args: unknown`: its generated TypeScript type exceeds Pi's 16,000-character
|
||||||
|
default. REA keeps the full advertisement's fields by inlining references when
|
||||||
|
the shared form would exceed Pi's 32-reference expansion limit; retaining that
|
||||||
|
shared form would leave later fields unknown instead. The existing
|
||||||
|
`REA_MCP_INPUT_SCHEMA_PROFILE=compact` profile lets callers stay below
|
||||||
|
its character limit by advertising reduced nested validation structure, while
|
||||||
|
REA continues to validate calls against the complete canonical input schema.
|
||||||
|
Compact applies to the server's advertised inputs and is not an automatic
|
||||||
|
full-profile fallback; see the [compact input schema profile](mcp-contracts.md#compact-input-schema-profile)
|
||||||
|
for details of its reduced forms.
|
||||||
|
|
||||||
|
Custom renderer hosts can raise `inputMaxChars` through `renderToolSignature` or
|
||||||
|
`renderToolSample`; `renderDeclarations` does not expose that override. The
|
||||||
|
probe exercised the published renderer code, not a native Pi host or model, and
|
||||||
|
does not establish broader Pi compatibility. See the pinned upstream
|
||||||
|
[`declarations.ts`](https://github.com/earendil-works/pi/blob/b45e0ebcc5ae2fc42a6f2f23104d10e656e6bedb/packages/codemode/src/declarations.ts)
|
||||||
|
and [renderer usage notes](https://github.com/earendil-works/pi/blob/b45e0ebcc5ae2fc42a6f2f23104d10e656e6bedb/packages/codemode/README.md#declarations-for-the-model).
|
||||||
|
|
||||||
## End-to-end, integration and golden evidence
|
## End-to-end, integration and golden evidence
|
||||||
|
|
||||||
The optional `verify:qwen-client`, `verify:pi-client`, and `verify:hermes-client`
|
The optional `verify:qwen-client`, `verify:pi-client`, and `verify:hermes-client`
|
||||||
@@ -377,6 +399,11 @@ Such a stall leaves the initial page unloaded with empty or placeholder CDP URLs
|
|||||||
it does not establish permission to capture that page. See
|
it does not establish permission to capture that page. See
|
||||||
[the keyring diagnosis](https://github.com/morluto/rea/issues/1384#issuecomment-6093781686).
|
[the keyring diagnosis](https://github.com/morluto/rea/issues/1384#issuecomment-6093781686).
|
||||||
|
|
||||||
|
On Windows, browser verifier lanes that launch an owned fixture require a
|
||||||
|
standard, non-elevated caller token. They inspect the verifier caller before
|
||||||
|
allocating fixture resources and refuse elevated or unobservable tokens; they do
|
||||||
|
not change caller authority or claim to inspect the browser process token.
|
||||||
|
|
||||||
`verify:browser` also captures a source-owned noise canvas as a real PNG above
|
`verify:browser` also captures a source-owned noise canvas as a real PNG above
|
||||||
8 MiB through the CLI and stdio MCP, with complete byte/digest parity and real PNG
|
8 MiB through the CLI and stdio MCP, with complete byte/digest parity and real PNG
|
||||||
decoding. Its SDK client explicitly permits the larger inline JSON response;
|
decoding. Its SDK client explicitly permits the larger inline JSON response;
|
||||||
@@ -458,7 +485,7 @@ artifact path. Its `expectedFirstTool` is `null`, and
|
|||||||
`targetClarificationPassed` grades that limited routing outcome independently
|
`targetClarificationPassed` grades that limited routing outcome independently
|
||||||
of analysis prose heuristics. Analyzing a nearby example cannot pass this case.
|
of analysis prose heuristics. Analyzing a nearby example cannot pass this case.
|
||||||
|
|
||||||
The packaged desktop and parser-comparison scenarios now assess a closed set of
|
The packaged desktop, parser-comparison, and managed scenarios assess a closed set of
|
||||||
known fixture claims. Each final answer must be a strict JSON manifest containing
|
known fixture claims. Each final answer must be a strict JSON manifest containing
|
||||||
exactly the requested claim IDs, values, Evidence IDs, and Evidence authority and
|
exactly the requested claim IDs, values, Evidence IDs, and Evidence authority and
|
||||||
confidence. Golden values are not included in the prompt. The evaluator checks
|
confidence. Golden values are not included in the prompt. The evaluator checks
|
||||||
@@ -475,15 +502,24 @@ analyses and use the requested module and export selectors. Boundary tests packa
|
|||||||
the same source-owned desktop fixture and compare the same source-owned parser
|
the same source-owned desktop fixture and compare the same source-owned parser
|
||||||
files through actual MCP tool results, then verify that fabricated answers fail.
|
files through actual MCP tool results, then verify that fabricated answers fail.
|
||||||
|
|
||||||
|
The managed rubric checks the exact public top-level type set and the CLI entry
|
||||||
|
point's declaring type and method name. The exact type-set claim requires an
|
||||||
|
unfiltered member inventory: `selection` must be null and the Evidence parameters
|
||||||
|
must omit `type_names`. A selected inventory can still ground the entry-point
|
||||||
|
claim. Both inspections must describe the freshly built DLL's SHA-256. Entry-point
|
||||||
|
resolution additionally requires complete artifact and member metadata, matching
|
||||||
|
non-null module MVIDs, and a unique method at the header's metadata token. These are
|
||||||
|
static metadata claims, not runtime behavior;
|
||||||
|
compiler-generated member counts, token values, and MVIDs are not golden answers.
|
||||||
|
|
||||||
Per-scenario `factualCorrectness` is `passed`, `failed`, or `not_assessed` within
|
Per-scenario `factualCorrectness` is `passed`, `failed`, or `not_assessed` within
|
||||||
the `configured_fixture_claims` scope. A pass establishes only the configured
|
the `configured_fixture_claims` scope. A pass establishes only the configured
|
||||||
claims, not unrestricted factual correctness. Native, managed, browser,
|
claims, not unrestricted factual correctness. Native, browser,
|
||||||
navigation-context, and address-context scenarios have no factual rubric and
|
navigation-context, and address-context scenarios have no factual rubric and
|
||||||
remain `not_assessed`; the managed workflow requires both artifact and member
|
remain `not_assessed`.
|
||||||
inspection to answer its type and entry-point question.
|
|
||||||
|
|
||||||
The closed factual scenarios request complete producer Evidence on the initial
|
The closed factual scenarios request complete producer Evidence on the initial
|
||||||
analysis so their source selectors can authenticate all configured facts.
|
inspection or analysis so their source selectors can authenticate all configured facts.
|
||||||
Summary/page/item workflows are exercised separately by `javascript-module-view`;
|
Summary/page/item workflows are exercised separately by `javascript-module-view`;
|
||||||
its heuristic gate does not establish full-producer factual correctness.
|
its heuristic gate does not establish full-producer factual correctness.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Electron fuse state
|
||||||
|
|
||||||
|
`inspect_electron_fuses` / `inspect-electron-fuses` answers whether a packaged
|
||||||
|
Electron app will honor `--inspect` and `--inspect-brk`, `ELECTRON_RUN_AS_NODE`,
|
||||||
|
embedded ASAR integrity validation, and loading application code only from
|
||||||
|
`app.asar`. It reads the fuse wire stored in the Electron binary. It does not
|
||||||
|
launch the app, modify fuse bytes, or use the network.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rea inspect-electron-fuses /Applications/Example.app --json
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI resolves a relative path against the caller's working directory. MCP
|
||||||
|
requires an absolute filesystem path and preserves that exact selection:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "inspect_electron_fuses",
|
||||||
|
"arguments": { "path": "/Applications/Example.app" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On POSIX hosts, the CLI preserves path components so symlinks are followed
|
||||||
|
before parent (`..`) steps. Bundle selections may end in `.` or parent steps
|
||||||
|
through real directories; a terminal parent step through a directory symlink
|
||||||
|
is rejected. Windows CLI paths retain native drive and separator resolution.
|
||||||
|
|
||||||
|
Check this before `observe_javascript_runtime` with `electron-main`. When
|
||||||
|
`EnableNodeCliInspectArguments` is disabled or removed, Electron ignores
|
||||||
|
Inspector flags, and a later target listing can simply show no main-process
|
||||||
|
target.
|
||||||
|
|
||||||
|
## What is read
|
||||||
|
|
||||||
|
On macOS, pass the `.app` bundle or the Electron Framework binary. Bundle and
|
||||||
|
framework selection first tries the top-level `Electron Framework` binary,
|
||||||
|
then `Versions/Current/Electron Framework`, then
|
||||||
|
`Versions/A/Electron Framework`. Symlinks are followed only while they stay
|
||||||
|
inside that framework.
|
||||||
|
On Windows and Linux, pass the main Electron executable. The wire lives in
|
||||||
|
that executable rather than in `app.asar`.
|
||||||
|
|
||||||
|
The inline Evidence result contains:
|
||||||
|
|
||||||
|
- the selected path, the resolved regular file, its SHA-256, size, and container
|
||||||
|
format (`mach-o`, `mach-o-universal`, `pe`, or `elf`);
|
||||||
|
- one slice entry for a thin Mach-O, PE, or ELF file, and one entry per slice of
|
||||||
|
a universal Mach-O, with file-absolute `sentinel_offset` values;
|
||||||
|
- `wire_version`, `declared_length`, and ordered fuses `{ index, name, state, raw_byte }`;
|
||||||
|
- `sentinel_count`, including zero and more than one;
|
||||||
|
- `runtime_implications`, each marked `relationship: "derivation"` and tied to
|
||||||
|
the fuse index it came from.
|
||||||
|
|
||||||
|
Version 1 names, in index order, are `RunAsNode`, `EnableCookieEncryption`,
|
||||||
|
`EnableNodeOptionsEnvironmentVariable`, `EnableNodeCliInspectArguments`,
|
||||||
|
`EnableEmbeddedAsarIntegrityValidation`, `OnlyLoadAppFromAsar`,
|
||||||
|
`LoadBrowserProcessSpecificV8Snapshot`, `GrantFileProtocolExtraPrivileges`,
|
||||||
|
and `WasmTrapHandlers`. An index past that table, or any index on another wire
|
||||||
|
version, keeps `name: null` and the raw byte.
|
||||||
|
|
||||||
|
Wire bytes are `0x30` disabled, `0x31` enabled, and `0x72` removed. Any other
|
||||||
|
byte is `unknown`. A removed fuse is not enabled: Inspector flags and
|
||||||
|
`ELECTRON_RUN_AS_NODE` are not honored, and application code is not restricted
|
||||||
|
to `app.asar`. On macOS and Windows, a removed or disabled
|
||||||
|
`EnableEmbeddedAsarIntegrityValidation` byte means embedded ASAR integrity is
|
||||||
|
not enforced, and an enabled byte means it is enforced. Electron compiles that
|
||||||
|
validation only on macOS and Windows. On a Linux ELF executable the observed
|
||||||
|
byte is preserved and the enforcement outcome is `unknown`.
|
||||||
|
|
||||||
|
## Unknown and multiple sentinels
|
||||||
|
|
||||||
|
No sentinel means the fuse state is unknown. It does not mean the fuses are
|
||||||
|
disabled. A wire that continues past the slice is reported as truncated, with
|
||||||
|
only the fuse bytes that were present. Two or more sentinels in one slice are
|
||||||
|
all reported. REA does not pick one.
|
||||||
|
|
||||||
|
Fuse bytes do not prove that the application enables Inspector some other way,
|
||||||
|
for example through `app.commandLine` or a remote-debugging switch in
|
||||||
|
application code.
|
||||||
|
|
||||||
|
The selected file must be a regular file, or a bundle that resolves to one.
|
||||||
|
Symlinks are followed only inside `Electron Framework.framework`. The reader
|
||||||
|
admits at most 1 GiB, searches with 256 KiB windows, and keeps at most 32
|
||||||
|
sentinels per slice. A real nine-fuse, one-slice result is about 1,200 CLI
|
||||||
|
tokens; a two-slice result is about 1,300 tokens. Exact size varies with path
|
||||||
|
lengths and limitations. This inspection creates no temporary files and does
|
||||||
|
not modify the selected bytes.
|
||||||
@@ -7,6 +7,10 @@ This passive runtime surface is distinct from the target-free static
|
|||||||
workflow. Static analysis reads a local ASAR or extracted directory without
|
workflow. Static analysis reads a local ASAR or extracted directory without
|
||||||
executing application code. Passive observation inspects an existing Electron
|
executing application code. Passive observation inspects an existing Electron
|
||||||
page. The results keep static inferences and runtime observations distinct.
|
page. The results keep static inferences and runtime observations distinct.
|
||||||
|
Packaged fuse state is a separate static read:
|
||||||
|
[`inspect_electron_fuses`](electron-fuses.md) reports whether this binary will
|
||||||
|
honor Inspector flags, `ELECTRON_RUN_AS_NODE`, embedded ASAR integrity, and
|
||||||
|
`app.asar`-only loading.
|
||||||
Use the separate
|
Use the separate
|
||||||
[`reconcile_javascript_runtime`](javascript-runtime-reconciliation.md) workflow
|
[`reconcile_javascript_runtime`](javascript-runtime-reconciliation.md) workflow
|
||||||
when both Evidence sets already exist.
|
when both Evidence sets already exist.
|
||||||
@@ -112,6 +116,12 @@ If cleanup cannot be verified, the error retains collected observations in
|
|||||||
`details.partial_observation` and marks their application cleanup `unverified`.
|
`details.partial_observation` and marks their application cleanup `unverified`.
|
||||||
Cleanup uncertainty does not authorize signaling unrelated processes.
|
Cleanup uncertainty does not authorize signaling unrelated processes.
|
||||||
|
|
||||||
|
### Target fuse prerequisites
|
||||||
|
|
||||||
|
Active scenario capture requires that the target Electron executable honors Node CLI inspector flags (`--inspect` or `--remote-debugging-port`) so that Playwright can establish its DevTools protocol session.
|
||||||
|
|
||||||
|
When an Electron executable explicitly disables the `EnableNodeCliInspectArguments` fuse (or removes the capability), `capture-electron-scenario` fails immediately before process launch with an `AnalysisUnsupportedTargetError` ("unsupported_target") rather than waiting for the startup deadline. For targets with disabled inspector fuses, use passive CDP inspection via `inspect_electron_page` or run against an unrestricted development binary with the extracted `app.asar`.
|
||||||
|
|
||||||
The real verifier exercises compiled CLI and stdio MCP discovery, page/script
|
The real verifier exercises compiled CLI and stdio MCP discovery, page/script
|
||||||
inspection, static/runtime reconciliation, active observation, and CLI SIGTERM
|
inspection, static/runtime reconciliation, active observation, and CLI SIGTERM
|
||||||
cleanup before checking the IPC, renderer crash/reload, utility/child-process,
|
cleanup before checking the IPC, renderer crash/reload, utility/child-process,
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md)
|
|||||||
- Upstream also returns terminal metadata during selector recovery. Its reported
|
- Upstream also returns terminal metadata during selector recovery. Its reported
|
||||||
values remain in `raw_result`; integer precision and non-text-key coverage are
|
values remain in `raw_result`; integer precision and non-text-key coverage are
|
||||||
unknown in the JS binding. Full bytecode retains the original encoded metadata.
|
unknown in the JS binding. Full bytecode retains the original encoded metadata.
|
||||||
|
- `bytecode.metadata` independently interprets a plausible terminal CBOR root
|
||||||
|
map from the selected bytes. Integers use decimal strings; byte strings use hex,
|
||||||
|
and scalar keys retain their types. The recorded offset/length addresses the
|
||||||
|
original bytecode, so summary delivery does not copy an opaque CBOR payload.
|
||||||
|
Nested containers have partial scalar coverage; unsupported or malformed
|
||||||
|
candidates report `not-decoded` with a diagnostic. Decoding is bounded to 64
|
||||||
|
container levels and the terminal payload's encoded size.
|
||||||
|
- An unavailable trailer does not prove metadata absence or Solidity origin.
|
||||||
|
Compiler-version bytes/text are metadata declarations, not verified compiler,
|
||||||
|
source-file or deployed-contract identity. Historical omission parses as a null
|
||||||
|
unknown facet without changing the original Evidence record. Metadata/source
|
||||||
|
URLs are not fetched.
|
||||||
- Selectors, argument strings and state mutability are **inferences**. Function
|
- Selectors, argument strings and state mutability are **inferences**. Function
|
||||||
names/signatures are not looked up. Optimized code may produce different
|
names/signatures are not looked up. Optimized code may produce different
|
||||||
mutability candidates; recovered types are not a verified source ABI.
|
mutability candidates; recovered types are not a verified source ABI.
|
||||||
@@ -125,3 +137,15 @@ The real verification lane traces representative raw/hex CLI workflows and check
|
|||||||
that only declared Node/prlimit launchers and process ownership inspection run.
|
that only declared Node/prlimit launchers and process ownership inspection run.
|
||||||
Socket traces permit local stdio socket pairs and reject attempted network requests.
|
Socket traces permit local stdio socket pairs and reject attempted network requests.
|
||||||
These observations verify those fixtures; they are not a general network sandbox.
|
These observations verify those fixtures; they are not a general network sandbox.
|
||||||
|
|
||||||
|
For the independent metadata parser, `npm run verify:solidity:metadata` uses an
|
||||||
|
existing caller-selected solc 0.8.30 module:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
REA_VERIFY_SOLC_MODULE=/absolute/path/to/solc/index.js npm run verify:solidity:metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
It compiles the owned fixture with unoptimized, viaIR, content-hash-free and
|
||||||
|
CBOR-free settings, verifies the observed trailer facts and reports local decode
|
||||||
|
timings. No contract is executed. This lane does not establish the complete
|
||||||
|
Linux EVM provider's CLI/MCP, resource-limit or cleanup behavior.
|
||||||
@@ -9,6 +9,10 @@ No device, root privilege, mount, emulation or execution of extracted programs
|
|||||||
is needed. Arbitrary firmware bytes do not pass native executable admission;
|
is needed. Arbitrary firmware bytes do not pass native executable admission;
|
||||||
select a returned ELF separately for supported Ghidra analysis.
|
select a returned ELF separately for supported Ghidra analysis.
|
||||||
|
|
||||||
|
On Linux, Binwalk and Unblob run as REA-owned processes and require a
|
||||||
|
procps-compatible `ps` on REA's `PATH`; see the shared
|
||||||
|
[Linux process prerequisite](installation.md#linux-owned-process-prerequisite).
|
||||||
|
|
||||||
## Caller-supplied tools
|
## Caller-supplied tools
|
||||||
|
|
||||||
Provide existing tools using absolute executable paths, scoped to your shell:
|
Provide existing tools using absolute executable paths, scoped to your shell:
|
||||||
@@ -103,6 +107,23 @@ build's source revision, so that field remains unknown.
|
|||||||
engine finished its configured extraction without those reported conditions;
|
engine finished its configured extraction without those reported conditions;
|
||||||
default skip rules and format coverage still apply. It does not establish
|
default skip rules and format coverage still apply. It does not establish
|
||||||
complete firmware reconstruction.
|
complete firmware reconstruction.
|
||||||
|
- `extraction_gaps` makes those conditions recoverable: a row identifies an
|
||||||
|
unextracted region or a failed task/stage. A chunk retains its identity and
|
||||||
|
byte range, plus its original-file range when it belongs to the selected input.
|
||||||
|
A multi-file stage retains its reported ID and handler with unknown byte ranges;
|
||||||
|
a standalone task failure keeps unavailable stage identity null. Rows retain exact dependency names from
|
||||||
|
the producer's missing-dependency reports; an accepted report with no names
|
||||||
|
remains a failure with unknown dependencies; no executable identity or specific installation step is inferred. Each row's `continuation_id`
|
||||||
|
references one instruction in `extraction_continuations`, so many regions
|
||||||
|
share recovery prose without losing any region identity. Instructions use
|
||||||
|
already configured capabilities where one exists. Every extraction rerun
|
||||||
|
requires a fresh absent `output_directory`, preserving the prior evidence.
|
||||||
|
Nested unknown chunks use `inspect_firmware_regions` on the actual published
|
||||||
|
child rather than repeating the same extraction with unchanged handlers.
|
||||||
|
Inspection requires separately configured Binwalk (`REA_BINWALK_COMMAND`);
|
||||||
|
without it, the inspection step is unavailable and the region remains unexamined. A listed gap does not
|
||||||
|
mean the target is corrupt or that a retained region is empty; REA installs
|
||||||
|
nothing.
|
||||||
- The destination must be absent, absolute, with an existing parent. Publication
|
- The destination must be absent, absolute, with an existing parent. Publication
|
||||||
uses exclusive, symlink-resistant writes and durable digest readback. Existing
|
uses exclusive, symlink-resistant writes and durable digest readback. Existing
|
||||||
destinations are never overwritten. This is not an atomic directory rename:
|
destinations are never overwritten. This is not an atomic directory rename:
|
||||||
@@ -167,6 +188,12 @@ records gzip offset/length and extracted file hashes. Real verification checks
|
|||||||
CLI/MCP, full-file and selected-range extraction, unknown chunks, depth limits,
|
CLI/MCP, full-file and selected-range extraction, unknown chunks, depth limits,
|
||||||
content parity and unchanged source bytes. No binary fixture is committed.
|
content parity and unchanged source bytes. No binary fixture is committed.
|
||||||
|
|
||||||
|
The optional missing-extractor lane asserts dependency names and the owning
|
||||||
|
handler in `extraction_gaps`. Those assertions must be run against the pinned
|
||||||
|
Unblob release to establish real-provider coverage; synthetic producer fixtures
|
||||||
|
alone do not verify Unblob's report nesting or stage identities. The gap ownership
|
||||||
|
claims remain unverified against real Unblob until that lane runs.
|
||||||
|
|
||||||
Additional lanes are explicit, so the base lane stays small:
|
Additional lanes are explicit, so the base lane stays small:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ server; older servers keep the complete request after schema discovery.
|
|||||||
[Dart 2.14 layout](https://github.com/dart-lang/sdk/blob/2.14.0/runtime/vm/snapshot.h).
|
[Dart 2.14 layout](https://github.com/dart-lang/sdk/blob/2.14.0/runtime/vm/snapshot.h).
|
||||||
Kind numbers vary across Dart releases; other values are preserved with
|
Kind numbers vary across Dart releases; other values are preserved with
|
||||||
partial coverage, without guessing their interpretation.
|
partial coverage, without guessing their interpretation.
|
||||||
|
Address resolution examines every overlapping `PT_LOAD` interpretation,
|
||||||
|
including partial overlaps. Equivalent mappings are coalesced; conflicting
|
||||||
|
mappings leave affected symbols unresolved and qualify coverage. Dynamic
|
||||||
|
hash, symbol and string tables use the same mapping checks.
|
||||||
A payload without exported snapshot symbols is refused rather
|
A payload without exported snapshot symbols is refused rather
|
||||||
than guessed at.
|
than guessed at.
|
||||||
- **The snapshot hash** from the header at the start of each exported data
|
- **The snapshot hash** from the header at the start of each exported data
|
||||||
@@ -133,8 +137,11 @@ server; older servers keep the complete request after schema discovery.
|
|||||||
`resource_constraint`.
|
`resource_constraint`.
|
||||||
- **Engine notes.** Engine libraries are spooled into the owned root. ELF
|
- **Engine notes.** Engine libraries are spooled into the owned root. ELF
|
||||||
headers, note tables and note payloads share an 8 MiB metadata budget;
|
headers, note tables and note payloads share an 8 MiB metadata budget;
|
||||||
up to 64 distinct build IDs are retained. Exceeding either bound preserves
|
up to 64 distinct build IDs and 1024 distinct nonempty note ranges are retained.
|
||||||
the candidates already examined and reports partial coverage.
|
The range cap bounds bookkeeping and separate positional reads even for tiny
|
||||||
|
overlapping declarations. Exact duplicates share one read and empty ranges
|
||||||
|
consume no inventory slots. Exceeding a bound preserves the candidates examined
|
||||||
|
from retained ranges and reports partial coverage with `build_id: null`.
|
||||||
- **Metadata and candidate capacity.** Both operations refuse duplicate
|
- **Metadata and candidate capacity.** Both operations refuse duplicate
|
||||||
relevant library paths. Inventory is limited to 32 ABIs and 1024 bytes per
|
relevant library paths. Inventory is limited to 32 ABIs and 1024 bytes per
|
||||||
relevant library path, independently of compressed size. Identification
|
relevant library path, independently of compressed size. Identification
|
||||||
@@ -174,3 +181,9 @@ engine build-ids, no labeled Dart version (as expected for a release
|
|||||||
engine), and the Android clang toolchain line; a plain aapt2-built APK
|
engine), and the Android clang toolchain line; a plain aapt2-built APK
|
||||||
reports not-detected with complete coverage. The suite uses self-contained
|
reports not-detected with complete coverage. The suite uses self-contained
|
||||||
synthetic ELF images and scanner payloads; it does not read private local files.
|
synthetic ELF images and scanner payloads; it does not read private local files.
|
||||||
|
|
||||||
|
The Gallery workflow and all 147 focused Flutter tests also pass on macOS Sonoma
|
||||||
|
14.8.9 x86-64 and Windows 11 x64 build 26300.9457 guests with Node 24.18.0,
|
||||||
|
including the conflicting-load and note-range-limit CLI/MCP regressions. Windows
|
||||||
|
verification runs under a non-administrator account. Verifiers use a shared
|
||||||
|
temporary parent so each CLI/MCP process can own and release its own snapshots.
|
||||||
@@ -6,8 +6,8 @@ For remote devices, pass a host with an optional port (`host[:port]`; bracket IP
|
|||||||
|
|
||||||
MCP exposes `list_frida_devices`, `list_frida_processes`, `start_frida_session`, `load_frida_script`, `resume_frida_session`, `unload_frida_script`, `frida_session_status`, and `close_frida_session`. A spawned process starts paused so scripts can load first. Closing unloads session scripts and detaches. If a spawn remains paused, REA resumes it only after script cleanup or session detach confirms the instrumentation is released; if both cleanup paths fail, the target stays paused and the cleanup error is retained. REA never kills the target. MCP sessions end with the server process. The one-shot CLI command `instrument-with-frida` accepts one inline script or script file, observes messages for a bounded duration, and returns Evidence with the source hash and captured messages.
|
MCP exposes `list_frida_devices`, `list_frida_processes`, `start_frida_session`, `load_frida_script`, `resume_frida_session`, `unload_frida_script`, `frida_session_status`, and `close_frida_session`. A spawned process starts paused so scripts can load first. Closing unloads session scripts and detaches. If a spawn remains paused, REA resumes it only after script cleanup or session detach confirms the instrumentation is released; if both cleanup paths fail, the target stays paused and the cleanup error is retained. REA never kills the target. MCP sessions end with the server process. The one-shot CLI command `instrument-with-frida` accepts one inline script or script file, observes messages for a bounded duration, and returns Evidence with the source hash and captured messages.
|
||||||
|
|
||||||
Failed startup cleanup retains acquired Frida handles, including a paused spawn that has not attached, for later cleanup retries. Its remote connection stays leased until session detach and target resume complete. CLI commands await provider cleanup before returning; MCP retains these resources for cleanup when its server closes. A failed cleanup remains reported rather than treating an unregistered session as fully released.
|
Failed startup cleanup retains acquired Frida handles, including a paused spawn that has not attached, for later cleanup retries. Its remote connection stays leased until session detach and target resume complete. CLI commands await provider cleanup before returning; MCP retains these resources for cleanup when its server closes. Server shutdown stops admitting new Frida operations and waits for admitted startup, discovery, and queued work before releasing their resources. A failed cleanup remains reported and can be retried by closing the server again, rather than treating an unregistered session as fully released.
|
||||||
|
|
||||||
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Message capture is bounded to 16 MiB and reports truncation. Script source is not stored in Evidence; its selected path and SHA-256 digest are. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
|
Frida scripts execute inside the selected target with that process's memory, filesystem, and network authority. Message capture is bounded to 16 MiB and reports truncation. Script source is not stored in Evidence; its selected path and SHA-256 digest are. Evidence retains non-authentication target selection such as device or endpoint, mode, program or PID, and spawn arguments. Authentication values remain excluded. Results report the observed session and messages, while unsupported platform metadata remains unknown. A request cancelled before a Frida operation starts has no provider effects. If cancellation arrives during a native Frida call, REA waits for that call to return, then closes any session it created and returns the observed session as a partial observation. A cancelled script load on an existing session releases only its new script before queued operations can run; the session and previously loaded scripts remain available. Once the provider commits a successful load, later request cancellation does not unload that script; use `unload_frida_script` to release it. If script cleanup fails, the script remains owned and a paused target cannot resume until the pending instrumentation is released. Queued script loads, resumes, and unloads check cancellation before dispatching native effects. Cancellation Evidence separately reports whether a target resume was observed, whether instrumentation cleanup completed, and that final target liveness remains unknown. REA does not kill the target. Remote discovery retains its observations and reports a separate cleanup error if the temporary connection cannot be removed.
|
||||||
|
|
||||||
The [Frida Core API](https://frida.re/docs/frida-core-api/) documents device management, sessions, and script loading; the [JavaScript API](https://frida.re/docs/javascript-api/) documents target-side instrumentation. [Luma](https://luma.frida.re/) is Frida's standalone GUI; REA does not automate or control it. `npm run verify:frida` requires a Linux host with the optional Frida binding and uses a short-lived local Node.js fixture created by the verifier. Add `-- --address HOST:PORT --pid PID` to run the separate remote attach lane against a caller-selected target. Mock tests do not establish real device/server compatibility.
|
The [Frida Core API](https://frida.re/docs/frida-core-api/) documents device management, sessions, and script loading; the [JavaScript API](https://frida.re/docs/javascript-api/) documents target-side instrumentation. [Luma](https://luma.frida.re/) is Frida's standalone GUI; REA does not automate or control it. `npm run verify:frida` requires a Linux host with the optional Frida binding and uses a short-lived local Node.js fixture created by the verifier. Add `-- --address HOST:PORT --pid PID` to run the separate remote attach lane against a caller-selected target. Mock tests do not establish real device/server compatibility.
|
||||||
+53
-13
@@ -22,6 +22,28 @@ lane. MIPS16 shared images have the separate bounded profile below; other shared
|
|||||||
or compressed images remain unsupported. Their family may be identified, but provider selection returns the
|
or compressed images remain unsupported. Their family may be identified, but provider selection returns the
|
||||||
specific unsupported constraint rather than guessing a compatible profile.
|
specific unsupported constraint rather than guessing a compatible profile.
|
||||||
|
|
||||||
|
A second admission covers recordless legacy firmware images
|
||||||
|
([#1620](https://github.com/morluto/rea/issues/1620)): ELF32 `ET_EXEC` headers
|
||||||
|
declaring `EF_MIPS_ARCH_32` or `EF_MIPS_ARCH_32R2` with an unspecified ABI
|
||||||
|
field, `EF_MIPS_ABI2` clear, and an _inspected_ absence of any ABI flags
|
||||||
|
record (a target whose record was merely not observed stays refused). For
|
||||||
|
these images o32 is an analysis assumption derived from the ELF32/ET_EXEC
|
||||||
|
structure, recorded as `mips_abi_basis` in the profile and stated as a
|
||||||
|
limitation in Evidence, never a declared fact; floating-point ABI semantics
|
||||||
|
stay unverified. Ghidra's own header auto-selection picks a 64-bit language
|
||||||
|
for exactly these files, so the lane pins `-processor MIPS:<LE|BE>:32:default`
|
||||||
|
through a `provider-pinned-v1` profile selection and checks the
|
||||||
|
bridge-reported `language_id` after import. A caller-selected
|
||||||
|
`REA_GHIDRA_LANGUAGE_ID`/`REA_GHIDRA_COMPILER_SPEC_ID` still wins over the
|
||||||
|
pin; combining the pinned profile with an environment override is a typed
|
||||||
|
refusal. A header that declares an ABI field but carries no record is still
|
||||||
|
refused. Two further boundaries apply: an inspection that could not examine
|
||||||
|
every ABI declaration source (for example a carved ELF whose section table
|
||||||
|
extends past the captured bytes without a program-header record) does not
|
||||||
|
establish the recordless admission, and an `EF_MIPS_ARCH_32` header that
|
||||||
|
_declares_ an ABI is refused rather than relabeled, because the declared-ABI
|
||||||
|
lane is verified for `EF_MIPS_ARCH_32R2` headers only.
|
||||||
|
|
||||||
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
|
Explicit `EF_MIPS_FP64` and `EF_MIPS_NAN2008` declarations are refused. Their
|
||||||
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
|
absence is insufficient: Clang can emit identical `e_flags` for FP32, FP64 and
|
||||||
soft-float targets. Target resolution also reads the 24-byte ABI flags record
|
soft-float targets. Target resolution also reads the 24-byte ABI flags record
|
||||||
@@ -30,22 +52,40 @@ section names or the record being inside the initial 4 KiB header probe.
|
|||||||
Program and section declarations must agree; duplicate, conflicting, truncated
|
Program and section declarations must agree; duplicate, conflicting, truncated
|
||||||
or out-of-file records are rejected. Stripped section tables and section-only
|
or out-of-file records are rejected. Stripped section tables and section-only
|
||||||
records are supported. Extended table numbering is explicitly unsupported.
|
records are supported. Extended table numbering is explicitly unsupported.
|
||||||
For carved ELFs whose section table extends beyond the captured file, REA
|
GNU `SHT_GNU_ATTRIBUTES` sections are located by numeric section type; section
|
||||||
examines every complete captured section header and records the unavailable
|
names never control detection. REA reads at most 4096 payload bytes and parses
|
||||||
headers as a limitation. A valid program-header ABI record can still establish
|
format version `A`, the `gnu` vendor's file-scope `Tag_GNU_MIPS_ABI_FP` (4) and
|
||||||
the supported declaration. Available section declarations must agree with it;
|
`Tag_GNU_MIPS_ABI_MSA` (8) only. Unknown vendors, scopes or tags, repeated
|
||||||
their duplicate, conflicting or unreadable records remain errors. A missing
|
declarations, malformed lengths, and out-of-file or oversized payloads remain
|
||||||
program-header record does not make ABI declarations in uncaptured sections
|
refusals. A nonzero FP tag must match the separate ABI flags `fp_abi` record;
|
||||||
absent or supply a default ABI.
|
value 0 (`ANY`) declares no requirement. The [GNU Object Attributes v1
|
||||||
|
format](https://sourceware.org/binutils/docs/as/Object-Attributes-v1.html) and
|
||||||
|
[GNU MIPS tag definitions](https://sourceware.org/binutils/docs/as/GNU-Object-Attributes.html)
|
||||||
|
are the format references. The MSA tag describes the vector
|
||||||
|
argument ABI, while the ABI flags ASE field describes instruction-set
|
||||||
|
extensions; REA does not treat those as interchangeable. The 128-bit MSA
|
||||||
|
argument ABI and unknown nonzero MSA values remain unsupported. Without a
|
||||||
|
separate ABI flags record, FP attribute values are admitted only when absent,
|
||||||
|
`ANY` (0), double precision (1), or FPXX (5), the values covered by the
|
||||||
|
recordless profile. Parsed values are retained in the MIPS analysis profile.
|
||||||
|
For carved ELFs whose section table extends
|
||||||
|
beyond the captured file, REA examines every complete captured section header
|
||||||
|
and records the unavailable headers as a limitation. The provider refuses an
|
||||||
|
incomplete ABI declaration inspection even when a program-header ABI record is
|
||||||
|
valid, because an unavailable section header could carry an unsupported or
|
||||||
|
conflicting declaration. A missing program-header record does not make ABI
|
||||||
|
declarations in uncaptured sections absent or supply a default ABI.
|
||||||
The field layout follows
|
The field layout follows
|
||||||
[`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h).
|
[`Elf_MIPS_ABIFlags_v0`](https://github.com/bminor/glibc/blob/master/elf/elf.h).
|
||||||
|
|
||||||
Provider admission requires record version 0, MIPS32r2, 32-bit GPRs, no CPR2,
|
Provider admission on the declared-ABI lane above requires record version 0,
|
||||||
no ISA extension, ASE or unknown general flags, and one of two floating-point
|
MIPS32r2, 32-bit GPRs, no CPR2, no ISA extension, ASE or unknown general
|
||||||
declarations: 32-bit CPR1 with a double-precision or FPXX ABI, or no CPR1 with
|
flags, and one of two floating-point declarations: 32-bit CPR1 with a
|
||||||
the soft-float ABI. Other combinations, including single-float, FP64 and
|
double-precision or FPXX ABI, or no CPR1 with the soft-float ABI. Other
|
||||||
unspecified ABIs, are refused. Missing records remain unknown rather than
|
combinations, including single-float, FP64 and unspecified ABIs, are refused.
|
||||||
defaulting to a compatible ABI, and this lane refuses them. Unknown record
|
Missing records remain unknown rather than defaulting to a compatible ABI, and
|
||||||
|
the declared-ABI lane refuses them; the recordless legacy admission above is
|
||||||
|
the only path that admits a recordless image. Unknown record
|
||||||
values are retained for diagnosis but do not gain provider support. These checks
|
values are retained for diagnosis but do not gain provider support. These checks
|
||||||
bind the declared interpretation; the fixtures do not establish full
|
bind the declared interpretation; the fixtures do not establish full
|
||||||
floating-point behavioral conformance.
|
floating-point behavioral conformance.
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ submodule and is a separate, more capable workflow.
|
|||||||
The parser currently handles x64 PE32+ and RTR 9.1, including bounded
|
The parser currently handles x64 PE32+ and RTR 9.1, including bounded
|
||||||
DEHYDRATED_DATA processing, MethodTable relationships, virtual slots, and
|
DEHYDRATED_DATA processing, MethodTable relationships, virtual slots, and
|
||||||
frozen strings. Other RTR versions and PE architectures are reported as
|
frozen strings. Other RTR versions and PE architectures are reported as
|
||||||
unsupported or partial. The matching .NET 8.0.22 fixture is source-built and
|
unsupported or partial. The matching .NET 8.0.22 fixture is source-built.
|
||||||
the parser's Windows-host Ghidra workflow has not yet had a real Windows-host
|
|
||||||
run. This does not establish Windows Ghidra import or decompiler behavior.
|
On a Windows host, Windows Ghidra P0 runs the parser without a JAR. Metadata,
|
||||||
Windows Ghidra P0 has no database mutation authority. A PE/CLI or ReadyToRun
|
MethodTable relationships, virtual slots and frozen strings come from the
|
||||||
|
parser report, and `analyze_function` decompiles slot targets. Tools that read
|
||||||
|
the Ghidra database do not see rehydrated bytes (see below), because only the
|
||||||
|
optional adapter writes them into the database and Windows Ghidra P0 has no
|
||||||
|
database mutation authority. A PE/CLI or ReadyToRun
|
||||||
assembly should use `inspect_managed_artifact`; it is not NativeAOT.
|
assembly should use `inspect_managed_artifact`; it is not NativeAOT.
|
||||||
|
|
||||||
## Build and configure
|
## Build and configure
|
||||||
@@ -57,18 +61,30 @@ returns `observations.metadata_recovery` inline from the captured source snapsho
|
|||||||
including the RTR header, MethodTable addresses, frozen strings, derived overlay
|
including the RTR header, MethodTable addresses, frozen strings, derived overlay
|
||||||
identity, and examined/recovered counts. Its enclosing `unsupported` load-image
|
identity, and examined/recovered counts. Its enclosing `unsupported` load-image
|
||||||
status means independent PE load-image verification was not performed; it does
|
status means independent PE load-image verification was not performed; it does
|
||||||
not mean the metadata parser failed or the executable failed to load.
|
not mean the metadata parser failed or the executable failed to load. Without
|
||||||
|
the optional adapter, each recovered type's `type` is `null`, because no Ghidra
|
||||||
|
DataType exists; select it by `address`.
|
||||||
|
|
||||||
Pass a returned MethodTable address to `inspect_native_data_type`. A direct PE
|
Pass a returned MethodTable address to `inspect_native_data_type`. A direct PE
|
||||||
query also derives the snapshot metadata from the authenticated Ghidra session
|
query also derives the snapshot metadata from the authenticated Ghidra session
|
||||||
handshake, so it does not require a preceding load-image call. In a persistent
|
handshake, so it does not require a preceding load-image call. In a persistent
|
||||||
MCP session, load-image followed by type inspection reuses that same derivation.
|
MCP session, load-image followed by type inspection reuses that same derivation.
|
||||||
|
The bridge resolves the caller's address spelling once; metadata lookup uses
|
||||||
|
its canonical default-space identity. Bare hexadecimal, prefixed hexadecimal,
|
||||||
|
and encoded default-space selectors therefore select the same MethodTable.
|
||||||
|
Other address spaces and unresolved identities are not matched by offset alone.
|
||||||
|
Metadata already supplied by the engine is retained without reading or deriving
|
||||||
|
an additional source snapshot.
|
||||||
The database result remains `unavailable` when Ghidra has no DataType at that
|
The database result remains `unavailable` when Ghidra has no DataType at that
|
||||||
address; its `metadata_recovery.source` is `read-only-derived-overlay` and
|
address; its `metadata_recovery.source` is `read-only-derived-overlay` and
|
||||||
carries related type, interface, slot, and target observations. Generated
|
carries related type, interface, slot, and target observations. Generated
|
||||||
address identities do not claim original managed names. The parser does not
|
address identities do not claim original managed names. The parser does not
|
||||||
merge frozen literals into Ghidra's database string inventory. Follow slot
|
merge frozen literals into Ghidra's database string inventory: read them from
|
||||||
targets with `analyze_function` and ordinary cross-references.
|
`metadata_recovery.frozen_strings`, not `search_strings`. Slot targets come
|
||||||
|
from the parser report; on Windows P0 without the adapter, a `read_bytes` of
|
||||||
|
a slot in the rehydrated region returns no bytes. The Linux adapter route
|
||||||
|
verifies a complete slot read and its target. Follow slot targets with `analyze_function` and ordinary
|
||||||
|
cross-references.
|
||||||
|
|
||||||
The built-in CLI path uses a MethodTable address returned by the parser or
|
The built-in CLI path uses a MethodTable address returned by the parser or
|
||||||
another observation:
|
another observation:
|
||||||
@@ -129,3 +145,11 @@ compiler/linker symbols and raw directory bytes provide the oracle. Stripped
|
|||||||
fixtures exercise heuristic detection. Tests do not execute the resulting
|
fixtures exercise heuristic detection. Tests do not execute the resulting
|
||||||
NativeAOT binaries. Fixture generation and adapter compilation are optional and
|
NativeAOT binaries. Fixture generation and adapter compilation are optional and
|
||||||
never prerequisites of ordinary native analysis or setup.
|
never prerequisites of ordinary native analysis or setup.
|
||||||
|
|
||||||
|
The real MCP lane runs only on a Linux x64 host with the adapter. The
|
||||||
|
Windows-host behavior above was checked manually over MCP on Windows 11 x64
|
||||||
|
(non-elevated, Ghidra 12.1.4, JDK 21, no JAR) with the source-built win-x64
|
||||||
|
symbols fixture. The format, RTR version and frozen strings matched the fixture
|
||||||
|
oracle; the derived MethodTable, its base type, its interface and a virtual
|
||||||
|
slot reaching the overridden method matched the linker map; and pseudocode was
|
||||||
|
readable. No CI lane repeats this check.
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# PowerPC ELF analysis through Ghidra
|
||||||
|
|
||||||
|
This lane extends native ELF analysis to 32-bit big-endian PowerPC; it does
|
||||||
|
not execute PowerPC code. It is the first milestone of
|
||||||
|
[#1741](https://github.com/morluto/rea/issues/1741). GameCube/Wii DOL
|
||||||
|
executables, PPC64 and other PowerPC variants are separate follow-ups.
|
||||||
|
|
||||||
|
## Admission boundary
|
||||||
|
|
||||||
|
ELF `EM_PPC` (20) is retained as the provider-neutral `powerpc` family. The
|
||||||
|
target also retains class, byte order, `e_type` and raw `e_flags`. Recognition
|
||||||
|
does not mean every provider or PowerPC variant is supported. `EM_PPC64` (21)
|
||||||
|
is not recognized as this family.
|
||||||
|
|
||||||
|
The Ghidra lane admits ELF32 `ET_EXEC`, big endian. ELF64, little-endian,
|
||||||
|
relocatable and shared objects are refused with a specific unsupported
|
||||||
|
constraint. `EF_PPC_EMB` declares an embedded variant and is refused; the
|
||||||
|
remaining `e_flags` bits only declare relocation conventions and do not change
|
||||||
|
instruction decoding.
|
||||||
|
|
||||||
|
## Bound language
|
||||||
|
|
||||||
|
Ghidra's ELF loader selects `PowerPC:BE:32:e500:default` for a plain `EM_PPC`
|
||||||
|
header with `e_flags` 0. e500 is the embedded Book-E variant whose SPE unit
|
||||||
|
replaces the classic floating-point unit, so it misinterprets ordinary
|
||||||
|
750-family code. Nothing in the header distinguishes the two.
|
||||||
|
|
||||||
|
The lane therefore imports with `PowerPC:BE:32:default` and compiler spec
|
||||||
|
`default`, commits that choice in the analysis profile as
|
||||||
|
`language_selection: "provider-pinned-v1"`, and checks the language Ghidra
|
||||||
|
reports after import. A different language set through
|
||||||
|
`REA_GHIDRA_LANGUAGE_ID` (and optionally `REA_GHIDRA_COMPILER_SPEC_ID`)
|
||||||
|
replaces the lane default and is admitted against the installed language
|
||||||
|
catalog as usual; for example `PowerPC:BE:32:e500` for genuine e500 code.
|
||||||
|
|
||||||
|
PowerPC header facts are committed in the analysis profile so interpretation
|
||||||
|
changes invalidate profile-bound snapshots. The support-lane identity is
|
||||||
|
versioned independently of file identity. Evidence, lifecycle outputs and saved
|
||||||
|
snapshots preserve the `powerpc` family. Hopper and IDA adapters do not
|
||||||
|
implicitly gain PowerPC support. Windows Ghidra P0 remains PE x86/x86-64 only.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
With a caller-installed Ghidra and compatible JDK on a supported Linux/macOS host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rea analyze /absolute/path/to/ppc.elf --provider ghidra --json
|
||||||
|
rea function /absolute/path/to/ppc.elf function_name --provider ghidra --json
|
||||||
|
```
|
||||||
|
|
||||||
|
MCP uses the existing tools, including `open_binary` with an absolute path and
|
||||||
|
`provider_id: "ghidra"`. No PowerPC-specific tool catalog or emulator is
|
||||||
|
introduced.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
The source-owned freestanding fixture is `tests/conformance/c/ppc.c`. The
|
||||||
|
optional cross-target lane requires a caller-supplied Clang with the PowerPC
|
||||||
|
target, LLD, GNU `readelf`, Ghidra and its compatible JDK; ordinary host-native
|
||||||
|
checks do not acquire these tools. `REA_PPC_CLANG` selects Clang and
|
||||||
|
`REA_PPC_READELF` selects GNU readelf (for example
|
||||||
|
`/opt/homebrew/opt/binutils/bin/readelf` on macOS). These are test-time
|
||||||
|
prerequisites, not REA runtime providers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra npm run verify:ghidra:ppc
|
||||||
|
```
|
||||||
|
|
||||||
|
The lane compiles the fixture for a 750-family core (`-mcpu=750`) and never
|
||||||
|
executes it. GNU readelf independently supplies header facts and symbol
|
||||||
|
addresses, compared with the production REA target resolver before provider
|
||||||
|
startup. Its version, bounded command output and selected tool path are
|
||||||
|
retained in the verification report. The parser deliberately recognizes only
|
||||||
|
this fixture's GNU output, rejecting missing, ambiguous or unexpected fields
|
||||||
|
rather than substituting defaults.
|
||||||
|
|
||||||
|
Existing public MCP operations check Ghidra's **observed** language and
|
||||||
|
compiler specification and loaded-file hashes; an e500 import fails the lane.
|
||||||
|
The source-owned `ppc-probe.S` supplies fixed `addi` and conditional-branch
|
||||||
|
encodings: verification checks bytes, length, immediate and direct destination,
|
||||||
|
independent of decompiler variable names. Global and string bytes are checked at
|
||||||
|
independently reported symbol addresses.
|
||||||
|
|
||||||
|
CLI/MCP discovery and decompilation, direct calls, target/profile/Evidence
|
||||||
|
identity, snapshot round-trip and final process cleanup remain covered.
|
||||||
|
Pseudocode presence is a liveness check, not proof of semantic equivalence.
|
||||||
|
Instruction decoding and reader agreement do not establish runtime or complete
|
||||||
|
floating-point semantics. Parser/profile tests are narrower checks, not
|
||||||
|
substitutes for this real-provider run. Report the actual lanes run and leave
|
||||||
|
unexecuted verification pending.
|
||||||
+68
-10
@@ -7,6 +7,8 @@ Executable bytes remain unchanged.
|
|||||||
|
|
||||||
Use [installation](installation.md#ghidra) for supported hosts, prerequisites,
|
Use [installation](installation.md#ghidra) for supported hosts, prerequisites,
|
||||||
configuration, startup deadlines, resource controls, and cleanup recovery.
|
configuration, startup deadlines, resource controls, and cleanup recovery.
|
||||||
|
[Linux Ghidra launches](installation.md#linux-owned-process-prerequisite) also
|
||||||
|
require a procps-compatible `ps` on REA's `PATH` for process ownership checks.
|
||||||
[Windows P0](windows-ghidra-p0.md) has its own admission and lifecycle boundary;
|
[Windows P0](windows-ghidra-p0.md) has its own admission and lifecycle boundary;
|
||||||
[NativeAOT observations](ghidra-nativeaot.md) include a built-in read-only PE
|
[NativeAOT observations](ghidra-nativeaot.md) include a built-in read-only PE
|
||||||
snapshot parser and a separate optional annotation extension. Check the
|
snapshot parser and a separate optional annotation extension. Check the
|
||||||
@@ -41,6 +43,26 @@ does not prove text is absent. Both list and search Evidence carry this
|
|||||||
limitation. When an address is known, use `read_bytes` and `xrefs` to inspect
|
limitation. When an address is known, use `read_bytes` and `xrefs` to inspect
|
||||||
bytes and references.
|
bytes and references.
|
||||||
|
|
||||||
|
## PE debug-symbol coverage
|
||||||
|
|
||||||
|
The post-analysis handshake reports Ghidra's PDB Program Information attributes:
|
||||||
|
the referenced file, GUID, hexadecimal age, legacy signature, version, and
|
||||||
|
`PDB Loaded` state. Absent attributes remain null. PE Evidence carries a
|
||||||
|
limitation when a PDB reference is present but Ghidra reports it as not loaded,
|
||||||
|
or its load state is unknown. A bridge without this report leaves PDB coverage
|
||||||
|
unknown. A reported loaded state is a Ghidra observation, not an independent
|
||||||
|
PDB identity or complete symbol-coverage attestation, and retains an inline
|
||||||
|
coverage caveat. Handshake PDB strings share a 4096 UTF-16-unit serialization
|
||||||
|
budget (at most 24 KiB after JSON escaping); whole attributes that do not fit
|
||||||
|
are omitted with an explicit resource limitation, rather than truncated.
|
||||||
|
|
||||||
|
Original names and types may be missing even when auto-analysis completes.
|
||||||
|
An empty name search does not establish that a function is absent; use
|
||||||
|
`list_procedures` and address-based analysis to inspect recovered functions.
|
||||||
|
REA imports a private executable copy and does not admit a caller-selected
|
||||||
|
PDB companion. This reporting does not search symbol servers, copy a sibling
|
||||||
|
PDB, or change analyzer settings.
|
||||||
|
|
||||||
## Loaded addresses and file offsets
|
## Loaded addresses and file offsets
|
||||||
|
|
||||||
`rea analyze <path> --provider ghidra --json` and MCP `binary_overview` report
|
`rea analyze <path> --provider ghidra --json` and MCP `binary_overview` report
|
||||||
@@ -131,15 +153,18 @@ overview merely because its source artifact is unchanged.
|
|||||||
|
|
||||||
Function extents and instruction completeness refer to Ghidra's analyzed body.
|
Function extents and instruction completeness refer to Ghidra's analyzed body.
|
||||||
An inferred no-return call can terminate that body before subsequent bytes.
|
An inferred no-return call can terminate that body before subsequent bytes.
|
||||||
Pseudocode, assembly, procedure info, instruction windows, callee and caller
|
Pseudocode, assembly, procedure info, instruction windows, incoming and outgoing
|
||||||
reads, and dossiers report observed terminal call sites in Evidence limitations,
|
references, callee and caller reads, and dossiers report observed terminal call sites in Evidence limitations,
|
||||||
including resolved callee names, addresses, and FunctionManager flags when
|
including resolved callee names, addresses, and FunctionManager flags when
|
||||||
available. Instruction windows
|
available. On x86-32, the same views also report the exact Borland/Delphi-style
|
||||||
and dossiers also retain these warnings in their result limitations. An
|
FS exception-registration pattern when its handler address falls outside the
|
||||||
unresolved callee or unestablished terminal-flow basis remains explicit. These
|
function body. This is a static registration observation; it does not establish
|
||||||
observations qualify the analysis; they do not prove that a no-return flag is
|
handler execution or change the body. Instruction windows and dossiers also
|
||||||
incorrect or recover excluded code. Analysis profiles version this observation
|
retain these warnings in their result limitations. An unresolved callee or
|
||||||
contract so older snapshots cannot replay unqualified results.
|
unestablished terminal-flow basis remains explicit. These observations qualify
|
||||||
|
the analysis; they do not prove that a no-return flag is incorrect or recover
|
||||||
|
excluded code. Analysis profiles version this observation contract so older
|
||||||
|
snapshots cannot replay unqualified results.
|
||||||
|
|
||||||
On non-Windows hosts, the ephemeral import repair clears no-return flags on the
|
On non-Windows hosts, the ephemeral import repair clears no-return flags on the
|
||||||
known returning external ABI names `malloc`, `__tls_get_addr`, `memcpy`, `memset`,
|
known returning external ABI names `malloc`, `__tls_get_addr`, `memcpy`, `memset`,
|
||||||
@@ -168,8 +193,12 @@ these tools before compilation. Keeping this matrix separate lets Linux
|
|||||||
host/provider acceptance run with only the host compiler.
|
host/provider acceptance run with only the host compiler.
|
||||||
|
|
||||||
`npm run verify:ghidra:windows` checks source-owned native x86-64 PE applications;
|
`npm run verify:ghidra:windows` checks source-owned native x86-64 PE applications;
|
||||||
`-- --x86` selects x86 and `-- --dll` selects DLLs, and the flags can be combined.
|
`-- --x86`, `-- --arm`, or `-- --arm64` selects another target architecture;
|
||||||
These lanes check all 25 read-only operations, target/snapshot/import SHA-256 linkage,
|
`-- --dll` selects DLLs. Choose one architecture and optionally combine it with
|
||||||
|
`--dll`. These static fixtures contain source-owned encoded instructions; they
|
||||||
|
are not compiler-produced programs. See the [Windows verification scope](windows-ghidra-p0.md#native-authority-and-target-identity)
|
||||||
|
for real-provider coverage and exclusions.
|
||||||
|
These lanes check all 26 read-only operations, target/snapshot/import SHA-256 linkage,
|
||||||
and project, endpoint, process, and runtime cleanup. The independent native
|
and project, endpoint, process, and runtime cleanup. The independent native
|
||||||
lane checks DACLs, handle admission, cancellation, and Job Object lifecycle.
|
lane checks DACLs, handle admission, cancellation, and Job Object lifecycle.
|
||||||
`npm run verify:ghidra:windows:package` packs and installs REA into an isolated
|
`npm run verify:ghidra:windows:package` packs and installs REA into an isolated
|
||||||
@@ -178,3 +207,32 @@ It accepts the same architecture and DLL flags and verifies the selected PE role
|
|||||||
in the analysis profile.
|
in the analysis profile.
|
||||||
All lanes require matching native controls; package startup alone establishes
|
All lanes require matching native controls; package startup alone establishes
|
||||||
only package compatibility.
|
only package compatibility.
|
||||||
|
|
||||||
|
## Masked byte search
|
||||||
|
|
||||||
|
`search_bytes` (`rea search-bytes PATH '48 8B ?? ?? 89' --provider ghidra`)
|
||||||
|
searches initialized analyzed memory, including loader fixups. Supply hexadecimal
|
||||||
|
byte pairs, with `??` for any byte. An optional equal-length hexadecimal `mask`
|
||||||
|
selects bits: `pattern: "a0"`, `mask: "f0"` matches bytes from `a0` through `af`.
|
||||||
|
Wildcards remain unconstrained even with an explicit mask. At least one bit
|
||||||
|
must be constrained.
|
||||||
|
|
||||||
|
Optional `start` and `end` addresses are inclusive and select one address space.
|
||||||
|
A match must end at or before `end`, so `end` set inside a match excludes it.
|
||||||
|
Without bounds, loaded memory blocks are eligible, in Ghidra address order.
|
||||||
|
Blocks that are not loaded (file parts such as `.symtab`) and overlay blocks are
|
||||||
|
listed in `searched.skipped_not_loaded` and are not searched; pass `start` or
|
||||||
|
`end` in their address space to search them.
|
||||||
|
Overlapping matches are included; matches never cross block boundaries.
|
||||||
|
Each match reports its address, segment, unique source file offset (or null),
|
||||||
|
and containing procedure (or null).
|
||||||
|
|
||||||
|
Pages default to 100 matches and accept 1 to 1000. Pass `next_cursor` as `cursor`
|
||||||
|
with the same target, profile, pattern, mask and bounds to continue. `complete`
|
||||||
|
means no further matches remain. `searched.blocks` and `searched.bytes` describe
|
||||||
|
the inclusive memory ranges examined for this page, including lookahead for the
|
||||||
|
next match. Coverage can overlap between pages; do not sum it as unique memory.
|
||||||
|
`searched.skipped_uninitialized` lists eligible uninitialized blocks on every
|
||||||
|
page. These blocks are never searched. Cancellation returns a typed error,
|
||||||
|
discards the active Ghidra database, and does not return a successful partial page.
|
||||||
|
Hopper byte search remains unavailable until its implementation is verified.
|
||||||
+12
-2
@@ -58,6 +58,12 @@ original stable file snapshot, including pointer-backed strings. It does not
|
|||||||
scan arbitrary byte strings as compiler metadata or interpret a fat Mach-O
|
scan arbitrary byte strings as compiler metadata or interpret a fat Mach-O
|
||||||
container as one selected architecture.
|
container as one selected architecture.
|
||||||
|
|
||||||
|
For ELF images without a named build-info section, discovery checks each
|
||||||
|
nonempty writable, non-executable load segment in program-header order. This
|
||||||
|
includes sectionless PIE images with metadata in a later data segment. Named
|
||||||
|
sections retain priority, and alignment, file mapping and record validation
|
||||||
|
remain required.
|
||||||
|
|
||||||
Historical moduleless Go binaries can store their empty module-string header in
|
Historical moduleless Go binaries can store their empty module-string header in
|
||||||
declared zero-initialized virtual memory rather than file-backed bytes. The reader
|
declared zero-initialized virtual memory rather than file-backed bytes. The reader
|
||||||
recognizes this only when the complete optional module header lies in that memory
|
recognizes this only when the complete optional module header lies in that memory
|
||||||
@@ -78,9 +84,13 @@ host permission denial, changed artifacts and cancellation retain distinct
|
|||||||
errors. The selected file is never modified; this inspection creates no
|
errors. The selected file is never modified; this inspection creates no
|
||||||
temporary files, starts no subprocesses and accesses no network endpoint.
|
temporary files, starts no subprocesses and accesses no network endpoint.
|
||||||
|
|
||||||
Resource guards bound the selected file to 256 MiB, aggregate embedded strings
|
Resource guards bound the selected file and cumulative header searches to 256 MiB
|
||||||
|
each, aggregate embedded strings
|
||||||
to 1 MiB, structural table decoding to 16 MiB and the complete normalized metadata
|
to 1 MiB, structural table decoding to 16 MiB and the complete normalized metadata
|
||||||
report to 8 MiB. Oversized results fail without partial success. MCP also applies
|
report to 8 MiB. Identical search spans with the same virtual alignment are
|
||||||
|
searched once; partially overlapping spans consume the cumulative search budget
|
||||||
|
and fail with a CPU resource constraint when it is exhausted. Oversized results
|
||||||
|
fail without partial success. MCP also applies
|
||||||
its [response transport budget](mcp-contracts.md), which counts both text and
|
its [response transport budget](mcp-contracts.md), which counts both text and
|
||||||
structured content; a report within the metadata limit can still require export
|
structured content; a report within the metadata limit can still require export
|
||||||
from retained Evidence. These limits are not operating-system memory or CPU
|
from retained Evidence. These limits are not operating-system memory or CPU
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
# Verify real HTTP/3 MITM captures
|
||||||
|
|
||||||
|
This optional Linux laboratory checks native mitmproxy captures through REA's
|
||||||
|
public CLI and MCP. It changes no runtime contracts or adapters and establishes
|
||||||
|
only the specific producer, mode and clients exercised here.
|
||||||
|
|
||||||
|
## Prepare a disposable guest
|
||||||
|
|
||||||
|
The exercised image is [Omarchy 4.0.4, build 440](https://nightly.omarchy.org/stable/4.0.4/440/omarchy-4.0.4.qcow2),
|
||||||
|
SHA-256 `43412b896fe3d730e373a51a8acf99343d7c3e6dee78aa9f9bd7fca181127264`.
|
||||||
|
Download it without installing guest packages on the host, check its digest,
|
||||||
|
and use a **new qcow2 overlay** with that file as a read-only backing image.
|
||||||
|
The matching [boot artifacts](https://nightly.omarchy.org/stable/4.0.4/440/unpacked/)
|
||||||
|
include the kernel, initrd, cmdline and upstream vm-boot recipe. Use a fresh SSH
|
||||||
|
key, loopback forwarding and guest disk; do not reuse another investigation's
|
||||||
|
writable overlay or host trust configuration. Shut the guest down before
|
||||||
|
switching overlays. Preserve the image and boot artifact hashes with evidence.
|
||||||
|
|
||||||
|
This sealed guest already supplies cURL 8.22.0 with OpenSSL 3.6.4, ngtcp2 1.25.0,
|
||||||
|
nghttp3 1.18.0 and nghttp2 1.70.0. **No guest rolling package upgrade is needed.**
|
||||||
|
Run `curl --version` and verify HTTP2 and HTTP3 features before starting. A stock
|
||||||
|
cURL from another distribution may omit HTTP/3. For a different guest, building
|
||||||
|
cURL requires a compatible TLS library, ngtcp2 and nghttp3; follow the
|
||||||
|
[official build instructions](https://curl.se/docs/http3.html), install to a guest
|
||||||
|
private prefix, and record exact source revisions and build inputs. That alternative
|
||||||
|
has not been exercised by this laboratory; it is not the pinned reproduction path.
|
||||||
|
|
||||||
|
Use a fresh VM overlay over a sealed Linux image. Use user-mode networking with
|
||||||
|
SSH forwarding bound to host loopback; keep host directories unmounted. Install
|
||||||
|
and build dependencies inside the guest, then remove its default route before
|
||||||
|
generating traffic. Host trust, routes and installed packages stay unchanged.
|
||||||
|
|
||||||
|
Record the image digest, REA source revision and these dependency versions:
|
||||||
|
|
||||||
|
- REA's `.nvmrc`, `package.json#packageManager` and locked npm dependencies.
|
||||||
|
- Official Linux x86_64 mitmproxy **12.2.3** standalone archive. SHA-256:
|
||||||
|
`2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5`.
|
||||||
|
- A private Python virtual environment using the complete pinned graph in
|
||||||
|
`scripts/fixtures/http3-lab-requirements.txt`. Save `pip freeze` and build/download logs.
|
||||||
|
- OpenSSL and cURL advertising HTTP3 and HTTP2 in `curl --version`.
|
||||||
|
|
||||||
|
The verified guest used Python 3.14.7, cURL 8.22.0, ngtcp2 1.25.0 and nghttp3
|
||||||
|
1.18.0. Other dependency profiles need verification. Back up the guest's
|
||||||
|
`/etc/hosts` before adding `127.0.0.1 lab.test`. Reserve guest TCP/UDP loopback
|
||||||
|
ports 18443 and 19443. These changes belong only to the disposable guest.
|
||||||
|
|
||||||
|
From the guest REA checkout, use its pinned Node/npm toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm ci
|
||||||
|
npm run build:cached
|
||||||
|
python3 -m venv /absolute/lab/venv
|
||||||
|
/absolute/lab/venv/bin/pip install --no-deps -r scripts/fixtures/http3-lab-requirements.txt
|
||||||
|
/absolute/lab/venv/bin/pip check
|
||||||
|
/absolute/lab/venv/bin/pip freeze > /absolute/lab/python-requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Before removing the guest default route, download the official mitmproxy archive
|
||||||
|
and verify it, then extract to the guest's private tools directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p /absolute/lab/tools
|
||||||
|
curl --fail --location https://downloads.mitmproxy.org/12.2.3/mitmproxy-12.2.3-linux-x86_64.tar.gz \
|
||||||
|
--output /absolute/lab/mitmproxy.tar.gz
|
||||||
|
printf '%s %s\n' 2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5 \
|
||||||
|
/absolute/lab/mitmproxy.tar.gz | sha256sum --check --strict
|
||||||
|
tar -xzf /absolute/lab/mitmproxy.tar.gz -C /absolute/lab/tools
|
||||||
|
/absolute/lab/tools/mitmdump --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Pin the REA revision with `git rev-parse HEAD` or archive SHA-256. Build using
|
||||||
|
the exact `.nvmrc` and npm version; do not copy a different checkout's `dist`.
|
||||||
|
For stronger offline dependency reproduction, preserve downloaded Python
|
||||||
|
archives, their digests and built wheels along with the image and toolchain.
|
||||||
|
The requirement versions pin the runtime graph, not every transient build tool;
|
||||||
|
source builds and alternate interpreters are not guaranteed byte-identical.
|
||||||
|
|
||||||
|
## Run the narrow profile
|
||||||
|
|
||||||
|
Select absolute executables and a new evidence directory. The producer refuses
|
||||||
|
to reuse a directory. No certificates are installed in a system trust store;
|
||||||
|
clients and proxy receive explicit paths to freshly generated test CAs.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
REA_MITMDUMP_COMMAND=/absolute/lab/tools/mitmdump \
|
||||||
|
REA_HTTP3_PYTHON_COMMAND=/absolute/lab/venv/bin/python \
|
||||||
|
npm run verify:web:http3 -- /absolute/evidence/run-1
|
||||||
|
```
|
||||||
|
|
||||||
|
The producer sends three concurrent binary POSTs on one HTTP/3 connection to the
|
||||||
|
same URL through mitmproxy reverse mode to an aioquic HTTP/3 origin. The origin
|
||||||
|
waits for all complete requests, then dispatches responses in reverse order of
|
||||||
|
completion at the origin. QUIC stream send/receive orders need not coincide. A cURL HTTP/3-only
|
||||||
|
request checks a second client implementation. Mitmproxy's real save addon
|
||||||
|
writes `normal.mitm`; no native flows are synthesized.
|
||||||
|
|
||||||
|
A second proxy run disables HTTP/3. cURL `--http3` falls back to HTTP/2, reaching
|
||||||
|
an HTTP/1.1 TLS test origin. `fallback.mitm` must retain those lower protocols,
|
||||||
|
so a successful HTTP request cannot masquerade as an HTTP/3 success.
|
||||||
|
|
||||||
|
## Acceptance and evidence
|
||||||
|
|
||||||
|
Each execution creates `verifications/<run-UUID>/` with its own diagnostics.
|
||||||
|
`verification-latest.json` belongs to the most recently registered attempt and
|
||||||
|
changes atomically from `running` to `passed` or `failed`. Registration and
|
||||||
|
the ownership check/replacement use the same SQLite transaction lock. An older
|
||||||
|
attempt finishing later keeps its own reports without replacing the marker.
|
||||||
|
The `.verification-lock.sqlite` pathname must remain in place while attempts
|
||||||
|
may run; SQLite releases the lock on connection close or process death.
|
||||||
|
Lock acquisition waits for the current owner without a timeout or lease
|
||||||
|
stealing. The verifier has per-command deadlines, but no overall run deadline;
|
||||||
|
a live stalled lock owner can therefore delay publication until it exits.
|
||||||
|
Only a successful attempt writes its own `verification.json`, after
|
||||||
|
all cleanup and input-integrity checks. `result.json` also records failures and
|
||||||
|
the original error separately from cleanup errors. Older successes remain in
|
||||||
|
identified history; a legacy root `verification.json` is archived before a rerun.
|
||||||
|
A killed verifier may leave `running`, which must never be interpreted as success.
|
||||||
|
|
||||||
|
Capture inputs live in `captures/`. To inspect them again without new traffic:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
REA_MITMDUMP_COMMAND=/absolute/lab/tools/mitmdump \
|
||||||
|
npm run verify:web:http3 -- /absolute/evidence/run-1 --inspect-existing
|
||||||
|
```
|
||||||
|
|
||||||
|
Acceptance for the current run:
|
||||||
|
|
||||||
|
- Every positive native record reports HTTP/3, UDP, ALPN h3 and successful TLS
|
||||||
|
timestamps on both legs. Connection IDs, producer TLS labels, exact TLS setup
|
||||||
|
timestamps and connection errors match the UUID-matched producer observation
|
||||||
|
on each leg. Endpoint handshakes independently observe QUIC v1.
|
||||||
|
- Native flow UUIDs remain distinct. Three endpoint streams share a connection
|
||||||
|
on each leg. Origin dispatch reverses completed receipt. Client and capture
|
||||||
|
ordering is observed, while association is verified independent of their order.
|
||||||
|
- CLI and MCP agree. Request/response bytes, lengths, SHA-256, ordered duplicate
|
||||||
|
headers, method, scheme, authority and path match independent observations.
|
||||||
|
Readable header names and values, request URL fields and ALPN also match;
|
||||||
|
non-UTF-8 header bytes retain a null display value rather than replacement text.
|
||||||
|
- Actual MCP advertised schemas validate, Evidence preserves source digests and
|
||||||
|
provider provenance, and the complete returned Evidence matches the retained MCP session records.
|
||||||
|
- The fallback capture reports client HTTP/2 over TCP and origin HTTP/1.1, with
|
||||||
|
cURL independently reporting version 2. Input files remain unchanged.
|
||||||
|
- Every cleanup action is attempted on success and failure, and final lineage
|
||||||
|
contains no live descendants. Recorded producer groups are inspected separately
|
||||||
|
after token-verified cleanup, including after their launcher has exited.
|
||||||
|
|
||||||
|
Retain captures, endpoint/producer JSONL, proxy diagnostics, cURL output, manifest,
|
||||||
|
CLI/MCP output, advertised schema and session bundle. Export public certificates
|
||||||
|
and dependency/source provenance separately. Do not commit evidence, private
|
||||||
|
keys, dependencies or VM disks. Shut down the guest after exporting evidence.
|
||||||
|
|
||||||
|
The reverse proxy can preserve authority `lab.test:18443` while recording origin
|
||||||
|
port 19443. Mitmproxy's serialized flow/connection UUIDs are not stream IDs.
|
||||||
|
Endpoint stream IDs are scoped observations; this test invents no cross-leg
|
||||||
|
mapping. Mitmproxy 12.2.3's `QUICv1` label alone does not prove negotiation: the
|
||||||
|
fixture checks the negotiated `_quic._version` in pinned aioquic 1.2.0.
|
||||||
|
|
||||||
|
Coverage is native capture, Linux reverse mode, QUIC v1 and these clients.
|
||||||
|
Empty/absent/incomplete/redacted body semantics, cancellations, resets,
|
||||||
|
certificate failures, malformed captures, HAR and qlog are separate increments.
|
||||||
|
Local/WireGuard, browsers, QUIC v2, migration, 0-RTT, mTLS, replay, sustained load
|
||||||
|
and loss tolerance remain unverified by this profile. Passing it does not
|
||||||
|
establish general HTTP/3 support in REA.
|
||||||
|
|
||||||
|
Official references: [mitmproxy protocol support](https://github.com/mitmproxy/mitmproxy/blob/v12.2.3/docs/src/content/concepts/protocols.md),
|
||||||
|
[HTTP/3 implementation](https://github.com/mitmproxy/mitmproxy/blob/v12.2.3/mitmproxy/proxy/layers/http/_http3.py),
|
||||||
|
[serialized HTTP state](https://github.com/mitmproxy/mitmproxy/blob/v12.2.3/mitmproxy/http.py),
|
||||||
|
[aioquic configuration](https://github.com/aiortc/aioquic/blob/1.2.0/src/aioquic/quic/configuration.py).
|
||||||
|
|
||||||
|
## Verifier regressions
|
||||||
|
|
||||||
|
After building REA, run these inside the same isolated guest:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
node --test scripts/lib/http3-verifier-lifecycle.test.mjs scripts/lib/http3-capture-assertions.test.mjs
|
||||||
|
/absolute/lab/venv/bin/python scripts/fixtures/http3-lab.test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
These cover different QUIC stream completion orders, stale success reports,
|
||||||
|
multiple failed closes, separately owned process groups and partially acquired
|
||||||
|
resources, plus producer TLS fact mismatches on both legs and altered readable
|
||||||
|
fields with unchanged binary evidence. They test verifier
|
||||||
|
semantics; they do not add HTTP/3 provider coverage.
|
||||||
+15
-4
@@ -4,6 +4,17 @@ REA adapts an existing IDA MCP server into its native analysis tools. Existing I
|
|||||||
|
|
||||||
Choose one lifecycle mode in an upstream MCP JSON registration. Set `REA_IDA_MCP_CONFIG` to that file, then select `ida` once through `REA_ANALYSIS_PROVIDER`, the CLI's `--provider ida`, or MCP's `open_binary.provider_id`. No provider startup occurs during discovery.
|
Choose one lifecycle mode in an upstream MCP JSON registration. Set `REA_IDA_MCP_CONFIG` to that file, then select `ida` once through `REA_ANALYSIS_PROVIDER`, the CLI's `--provider ida`, or MCP's `open_binary.provider_id`. No provider startup occurs during discovery.
|
||||||
|
|
||||||
|
## Supported upstream versions
|
||||||
|
|
||||||
|
Each mode accepts one upstream tool profile, checked against `tools/list` before any analysis runs. The attached profile requires the legacy GUI tool names; upstream 2.0.0 renamed those tools, and REA has not established an equivalent attached-mode lifecycle and ownership contract for that catalog. It therefore fails closed with the missing tool names in the error diagnostics.
|
||||||
|
|
||||||
|
The headless profile is defined by the database-supervisor API, not by an upstream version string. The upstream source snapshot [`c133c385`](https://github.com/mrexodia/ida-pro-mcp/tree/c133c3853faa111a9b00ee615c013b720d0c4acd) includes `idb_close` (introduced by [upstream PR #495](https://github.com/mrexodia/ida-pro-mcp/pull/495)) alongside `idb_open` and `idb_list`. Earlier builds may report the same `2.0.0` version while lacking that tool, so REA checks the live catalog rather than treating a version number or branch name as a compatibility guarantee.
|
||||||
|
|
||||||
|
| Mode | Upstream profile | Known working |
|
||||||
|
| -------- | ------------------- | ------------------------------------------------------ |
|
||||||
|
| attached | legacy GUI tools | 1.4.0 |
|
||||||
|
| headless | database supervisor | builds serving `idb_open`, `idb_list`, and `idb_close` |
|
||||||
|
|
||||||
## Analyze an already-open GUI target
|
## Analyze an already-open GUI target
|
||||||
|
|
||||||
The attached profile supports upstream **1.4.0 legacy tools**, including `get_metadata`, `list_functions`, and `decompile_function`. Copy the upstream registration's `command`, `args`, and optional `env` into a JSON file. An existing `mcpServers` object with an `ida-pro-mcp` entry is also accepted. For example:
|
The attached profile supports upstream **1.4.0 legacy tools**, including `get_metadata`, `list_functions`, and `decompile_function`. Copy the upstream registration's `command`, `args`, and optional `env` into a JSON file. An existing `mcpServers` object with an `ida-pro-mcp` entry is also accepted. For example:
|
||||||
@@ -120,19 +131,19 @@ Use a package version that actually includes this adapter; repository main can b
|
|||||||
{"name":"close_binary","arguments":{}}
|
{"name":"close_binary","arguments":{}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration's optional `timeoutMs` defaults to 300000 for upstream requests. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads the health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Only expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started once the deadline has passed. That error names the busy tool of the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
|
Opening an MCP target starts its selected provider session; headless opening waits for auto-analysis. Set the agent's MCP request deadline long enough for the target. The registration's optional `timeoutMs` defaults to 300000 for upstream requests. An upstream SDK request that reaches this limit returns `provider_timeout` with its operation and actual request budget. A freshly opened headless worker can briefly report `status: "busy"` from `server_health`; REA re-reads health every 100 ms until the worker answers, the caller cancels, or `timeoutMs` has elapsed since the first read. Each health request is capped by the time remaining in that overall wait, so a stalled read cannot extend the readiness deadline by another full request timeout. Expiry while the worker is still busy produces a retryable `provider_timeout`; no further read is started after the deadline. That error names the busy tool from the last observation, or `unknown` when the worker reported none or a value that is not tool-name-shaped. If a health request itself consumes the remaining wait budget, the same last-busy timeout is returned with the stalled request's captured output. Cancellation during the wait produces `cancelled`. Any other unexpected health payload, including a busy payload with an invalid shape, remains `unreadable_output`. Cancellation drains the current upstream request before cleanup; it does not force-stop GUI analysis. An open request that times out without a completed response retains the private workspace because worker inventory alone cannot prove the request has stopped. `binary_session` reports actual tool availability. Repeated doctor/setup calls are unnecessary when the registration already works.
|
||||||
|
|
||||||
For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <client>`. Setup includes that file reference in its reviewed plan and preserves it after approval; it does not copy upstream credentials into the agent configuration. `rea doctor --provider ida --json` validates the registration without starting IDA and explicitly leaves runtime compatibility and target binding for analysis.
|
For guided registration, set `REA_IDA_MCP_CONFIG` before `rea setup --client <client>`. Setup includes that file reference in its reviewed plan and preserves it after approval; it does not copy upstream credentials into the agent configuration. For `ida` or `auto` selection, Codex/Kimi setup and doctor use the effective client registration's parsed `timeoutMs` (default 300000 ms) when recommending the agent's per-tool deadline. Existing positive client deadlines remain caller choices. `rea doctor --provider ida --json` validates the registration without starting IDA and explicitly leaves runtime compatibility and target binding for analysis.
|
||||||
|
|
||||||
## Coverage and evidence
|
## Coverage and evidence
|
||||||
|
|
||||||
All profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. In the modern headless profile, targets that are not proven x86-only use processor-neutral `callgraph` edges instead of the x86 instruction checks in upstream `callees` (mrexodia/ida-pro-mcp#549). REA requests depth zero: the pinned upstream emits the root's outgoing edges without scanning child functions, then REA batches function-name lookup for those addresses. The graph covers recognized function destinations only; imports and other non-function targets remain unknown. A server without `callgraph` returns an empty relationship list with an explicit coverage limitation. Graph and path workflows retain those limitations alongside observed edges. A failed or inconsistent graph reply remains an error. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
|
All profiles support procedure/string inventory and literal or regex search, procedure resolution, pseudocode, assembly, raw function instructions, resolved callees, incoming address references, and a function dossier. Inventories and modern pseudocode/assembly follow upstream pagination. Upstream limits on modern xrefs and callees are retained as explicit truncation limitations. In the modern headless profile, targets that are not proven x86-only use processor-neutral `callgraph` edges instead of the x86 instruction checks in upstream `callees` (mrexodia/ida-pro-mcp#549). REA requests depth zero: the pinned upstream emits the root's outgoing edges without scanning child functions, then REA batches function-name lookup for those addresses. The graph covers recognized function destinations only; imports and other non-function targets remain unknown. A server without `callgraph` returns an empty relationship list with an explicit coverage limitation. Graph and path workflows retain those limitations alongside observed edges. A failed or inconsistent graph reply remains an error. A transport-level truncated preview is rejected rather than reported as complete evidence; the error preserves upstream retrieval guidance.
|
||||||
|
|
||||||
The modern headless profile also supports `resolve_containing_procedure` and `list_documents`. An address resolves through upstream `lookup_funcs` to the canonical entry of the function that contains it; the body stays unavailable like other IDA identities. When no function contains it, REA reads the database-scoped minimal `survey_binary` and reports `not_in_procedure` for an address inside one of its segments (ends are exclusive) and `outside_segments` otherwise. A missing, truncated or inconsistent survey fails the read instead of guessing a reason. `list_documents` returns the single admitted program path, so `inspect_address_context` and `trace_feature` need no explicit document. `trace_feature` marks a trace truncated, with a residual unknown for that address, when upstream caps the references it returned. The legacy and native profiles do not declare these operations.
|
All profiles expose `list_documents` as the single target admitted for the session. Attached mode verifies its digest against IDA metadata before returning it; headless mode binds the digest-checked private input. `inspect_address_context`, `trace_feature`, and `binary_overview` can therefore resolve that identity without upstream document-listing tools. Segment enumeration is optional: when the selected profile cannot provide it, `binary_overview` still returns document and procedure/string counts and omits `segments` and `segment_count`; omission means unobserved, not empty. The modern headless profile also supports `resolve_containing_procedure`. An address resolves through upstream `lookup_funcs` to the canonical entry of the function that contains it; the body stays unavailable like other IDA identities. When no function contains it, REA reads the database-scoped minimal `survey_binary` and reports `not_in_procedure` for an address inside one of its segments (ends are exclusive) and `outside_segments` otherwise. A missing, truncated or inconsistent survey fails the read instead of guessing a reason. `trace_feature` marks a trace truncated, with a residual unknown for that address, when upstream caps the references it returned. Attached profiles do not declare address containment resolution.
|
||||||
|
|
||||||
The legacy profile additionally supports direct callers. Its producer may report call-site or interior addresses; REA resolves the reported function symbol to its canonical entry. The modern and native profiles do not prove direct callers from its code/data xref labels, so that capability is unavailable and the dossier marks it unknown.
|
The legacy profile additionally supports direct callers. Its producer may report call-site or interior addresses; REA resolves the reported function symbol to its canonical entry. The modern and native profiles do not prove direct callers from its code/data xref labels, so that capability is unavailable and the dossier marks it unknown.
|
||||||
|
|
||||||
Complete body ranges, typed reference edges, CFG, referenced data, source locals, comment kinds, and database revision are unavailable. Empty unsupported dossier facets mean **unknown**, not observed absence. `binary_overview`, whole-symbol inventory, mutation, debugging, arbitrary Python forwarding, and requested GUI navigation are outside this adapter's coverage. Check session availability before composing a workflow that needs those facets.
|
Complete body ranges, typed reference edges, CFG, referenced data, source locals, comment kinds, and database revision are unavailable. Empty unsupported dossier facets mean **unknown**, not observed absence. Whole-symbol inventory, mutation, debugging, arbitrary Python forwarding, and requested GUI navigation are outside this adapter's coverage. Check session availability before composing a workflow that needs those facets.
|
||||||
|
|
||||||
`provider.version` identifies the REA adapter; the profile explicitly records `version_scope: "rea-ida-adapter"`. The MCP handshake's `serverInfo` is separate and does not establish IDA or Python distribution versions. Raw observations preserve producer metadata and output. Attached hashes identify the recorded original input; headless identity uses the digest-verified private copy and observed worker input path. Neither proves source-code recovery or an unmodified analysis database.
|
`provider.version` identifies the REA adapter; the profile explicitly records `version_scope: "rea-ida-adapter"`. The MCP handshake's `serverInfo` is separate and does not establish IDA or Python distribution versions. Raw observations preserve producer metadata and output. Attached hashes identify the recorded original input; headless identity uses the digest-verified private copy and observed worker input path. Neither proves source-code recovery or an unmodified analysis database.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -49,10 +49,10 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
|
|||||||
|
|
||||||
| Task | Guides |
|
| Task | Guides |
|
||||||
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
|
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
|
||||||
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [JEB](jeb-analysis.md) |
|
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
|
||||||
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
|
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
|
||||||
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md) |
|
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
|
||||||
| Inspect assemblies, packages, or firmware | [.NET](managed-code-analysis.md), [Android](android-analysis.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
|
| Inspect assemblies, packages, or firmware | [.NET](managed-code-analysis.md), [Android](android-analysis.md), [Apktool resources](apktool-resource-analysis.md), [ADB acquisition](adb-device-analysis.md), [Apple](apple-application-analysis.md), [Harmony](harmony-analysis.md), [firmware](firmware-analysis.md), [WASM](wasm-artifacts.md), [EVM](evm-bytecode.md) |
|
||||||
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
|
| Observe or compare a browser scenario | [CDP observation](browser-observation.md), [scenarios](browser-scenario-contract.md), [runtime evidence](web-runtime.md), [historical captures](web-network-captures.md) |
|
||||||
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
|
| Trace captured website source | [Source locations](web-source-location.md), [module traces](website-module-trace.md), [script export](website-script-export.md) |
|
||||||
|
|||||||
+333
-33
@@ -3,13 +3,13 @@
|
|||||||
Run REA, connect your selected agents, and check the tools needed for your task.
|
Run REA, connect your selected agents, and check the tools needed for your task.
|
||||||
Setup previews configuration changes and asks for approval before applying them.
|
Setup previews configuration changes and asks for approval before applying them.
|
||||||
|
|
||||||
| Task | Section |
|
| Task | Section |
|
||||||
| --------------------------------- | ------------------------------------------------------------------------- |
|
| --------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
|
| Connect an agent | [Start setup](#start-setup), then [review changes](#review-setup-changes) |
|
||||||
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
|
| Diagnose one task | [Scoped readiness checks](#check-readiness-for-your-task) |
|
||||||
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), or the [IDA guide](ida-provider.md) |
|
| Configure an analysis engine | [Hopper](#hopper), [Ghidra](#ghidra), the [IDA guide](ida-provider.md), or [Kuna](kuna-provider.md) |
|
||||||
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
|
| Update or remove REA | [Maintenance](#diagnose-update-and-remove) |
|
||||||
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
|
| Resolve a client-specific problem | [Client configuration](client-configuration.md) |
|
||||||
|
|
||||||
## Start setup
|
## Start setup
|
||||||
|
|
||||||
@@ -20,7 +20,9 @@ npx rea-agents setup
|
|||||||
Choose the agents to configure, review the proposed paths and changes, and
|
Choose the agents to configure, review the proposed paths and changes, and
|
||||||
approve the plan. npm's package-download approval and REA's setup approval are
|
approve the plan. npm's package-download approval and REA's setup approval are
|
||||||
separate. Setup asks before changing agent configuration or installing Hopper.
|
separate. Setup asks before changing agent configuration or installing Hopper.
|
||||||
Restart the agents named in the completion message.
|
Restart the agents named in the completion message. Quit Claude Desktop fully
|
||||||
|
before approving, because a running app can overwrite the registration; on
|
||||||
|
Windows, see [where Claude Desktop reads its configuration](client-configuration.md#claude-desktop).
|
||||||
|
|
||||||
For the latest published release, use `npx rea-agents@latest setup`.
|
For the latest published release, use `npx rea-agents@latest setup`.
|
||||||
[Package-runner and global-installation options](#package-runners-and-global-installation)
|
[Package-runner and global-installation options](#package-runners-and-global-installation)
|
||||||
@@ -62,11 +64,21 @@ preserves a verified `.rea.backup.<sha256>.recovery` copy of the current bytes.
|
|||||||
Both snapshots remain available; a failed rollback reports the current recovery
|
Both snapshots remain available; a failed rollback reports the current recovery
|
||||||
copy's path and the restore failure.
|
copy's path and the restore failure.
|
||||||
Setup also retains valid environment overrides in REA's existing server entry,
|
Setup also retains valid environment overrides in REA's existing server entry,
|
||||||
such as `REA_MCP_INPUT_SCHEMA_PROFILE` and `REA_MCP_MAX_RESPONSE_BYTES`.
|
such as `REA_MCP_INPUT_SCHEMA_PROFILE`, `REA_MCP_OUTPUT_SCHEMA_PROFILE`, and
|
||||||
|
`REA_MCP_MAX_RESPONSE_BYTES`.
|
||||||
Detected provider settings replace only matching environment keys.
|
Detected provider settings replace only matching environment keys.
|
||||||
Setup and doctor report a malformed REA environment map or invalid runtime
|
Setup and doctor report a malformed REA environment map or invalid runtime
|
||||||
setting before setup writes files. Repair the named setting in the client
|
setting before setup writes files. Repair the named setting in the client
|
||||||
configuration; setup does not silently discard the rest of the map.
|
configuration; setup does not silently discard the rest of the map.
|
||||||
|
This includes malformed loader/exclusion JSON arrays and a Ghidra compiler
|
||||||
|
specification without its required language identifier. Valid overrides keep
|
||||||
|
their original strings, including empty Hopper arguments. DeepSeek Harness
|
||||||
|
`!!js` environment expressions remain unevaluated and preserved; registration
|
||||||
|
alignment does not verify the runtime values those expressions will produce.
|
||||||
|
For layered OpenCode files, each file's values are checked before merging, while
|
||||||
|
setting dependencies are checked against the effective merged environment.
|
||||||
|
Setup preserves each layer's overrides instead of copying inherited settings
|
||||||
|
into the file it updates.
|
||||||
|
|
||||||
After setup, REA reports which agents, analysis tools, and workflow files passed
|
After setup, REA reports which agents, analysis tools, and workflow files passed
|
||||||
its final checks. Restart any agent named in the completion message, then begin
|
its final checks. Restart any agent named in the completion message, then begin
|
||||||
@@ -110,6 +122,16 @@ See [client configuration](client-configuration.md) for client-specific roots,
|
|||||||
profiles, and environment overrides. A mixed selection plans each distinct
|
profiles, and environment overrides. A mixed selection plans each distinct
|
||||||
destination. Selecting the skill without a client uses `~/.agents/skills`.
|
destination. Selecting the skill without a client uses `~/.agents/skills`.
|
||||||
Setup leaves existing skill copies in other locations untouched.
|
Setup leaves existing skill copies in other locations untouched.
|
||||||
|
Setup refuses symbolic links at the selected skill directory or its managed
|
||||||
|
`references` directory, matching uninstall and update's ownership rules. Use a
|
||||||
|
regular skill directory, or select `--skill=false` to configure the client
|
||||||
|
registration without installing the bundled skill.
|
||||||
|
The final selected directories are checked before approval and again before
|
||||||
|
setup changes any client or Hopper installation. Unselected client destinations
|
||||||
|
do not block other setup choices. Doctor reports linked or non-directory skill
|
||||||
|
paths as `unmanaged`, names the affected paths, and provides those alternatives;
|
||||||
|
an unreadable skill identity remains `unknown`. Uninstall retains and reports
|
||||||
|
files it cannot safely remove, including their ownership manifest.
|
||||||
|
|
||||||
`rea setup --skill-client claude_code --yes --json` refreshes and verifies the
|
`rea setup --skill-client claude_code --yes --json` refreshes and verifies the
|
||||||
selected skill destination independently of MCP registration selection. The
|
selected skill destination independently of MCP registration selection. The
|
||||||
@@ -125,7 +147,152 @@ identity remains available at `identity.catalog`.
|
|||||||
|
|
||||||
Setup pins package-runner MCP registrations to the exact installed REA version,
|
Setup pins package-runner MCP registrations to the exact installed REA version,
|
||||||
installs the matching skill and on-demand references in the same plan, and adds
|
installs the matching skill and on-demand references in the same plan, and adds
|
||||||
`startup_timeout_sec = 30` for Codex and Grok Build.
|
`startup_timeout_sec = 30` for Codex and Grok Build. Codex also receives a
|
||||||
|
`tool_timeout_sec` derived from known provider startup plus operation headroom.
|
||||||
|
|
||||||
|
## Limit REA to one project or agent profile
|
||||||
|
|
||||||
|
Setup registers REA for the whole client. For Claude Code it writes
|
||||||
|
`mcpServers.rea` in `~/.claude.json` and installs the bundled skill under
|
||||||
|
`~/.claude/skills`, so every project that Claude Code opens can call REA. To
|
||||||
|
offer REA's tools only where they're wanted, use one of the routes below.
|
||||||
|
|
||||||
|
Both routes control which sessions discover REA's MCP server and skill. They
|
||||||
|
leave shell commands, filesystem access and network access available. An agent
|
||||||
|
under the same OS account can run `rea` from `PATH` or `npx` and read other
|
||||||
|
profiles and REA output. For clean-room separation, use an implementation
|
||||||
|
environment with filesystem and network restrictions that prevent reading or
|
||||||
|
fetching REA, the target and analysis output, including through shared
|
||||||
|
installations and package registries. A separate account or container must
|
||||||
|
enforce those restrictions.
|
||||||
|
|
||||||
|
For Claude Code, check existing default-profile state before choosing either
|
||||||
|
route. Setup is additive: a previous user-level REA registration and personal
|
||||||
|
skill remain active in other projects. Back up the default configuration and
|
||||||
|
REA skill first, then, with `CLAUDE_CONFIG_DIR` unset, remove the REA user
|
||||||
|
registration with `claude mcp remove --scope user rea` and remove the known
|
||||||
|
REA-installed `~/.claude/skills/reverse-engineer-anything` directory. Preserve
|
||||||
|
unrelated registrations and skills. `rea uninstall` applies to other clients
|
||||||
|
as well, so it is broader than this Claude-only migration.
|
||||||
|
|
||||||
|
**A separate Claude Code profile.** Claude Code keeps settings, skills and session
|
||||||
|
history under `CLAUDE_CONFIG_DIR`. Setup follows the same
|
||||||
|
variable: it writes the registration to `$CLAUDE_CONFIG_DIR/.claude.json` and
|
||||||
|
the skill to `$CLAUDE_CONFIG_DIR/skills`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAUDE_CONFIG_DIR=~/.claude-rea npx rea-agents setup --client claude_code
|
||||||
|
CLAUDE_CONFIG_DIR=~/.claude-rea claude
|
||||||
|
```
|
||||||
|
|
||||||
|
A new profile may need authentication. Console sign-ins without an API key are
|
||||||
|
stored outside `CLAUDE_CONFIG_DIR`; see
|
||||||
|
[Claude Code authentication](https://code.claude.com/docs/en/authentication#log-in-with-multiple-accounts).
|
||||||
|
Sessions started without the variable use the default profile's registration,
|
||||||
|
skill and history.
|
||||||
|
|
||||||
|
Inline assignments apply only to that command. Keep the same prefix for every
|
||||||
|
profile check, setup rerun, update, or removal; bare commands use the current
|
||||||
|
environment and can otherwise inspect or recreate default-profile state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAUDE_CONFIG_DIR=~/.claude-rea claude mcp list
|
||||||
|
CLAUDE_CONFIG_DIR=~/.claude-rea npx rea-agents doctor --client claude_code --skill --json
|
||||||
|
CLAUDE_CONFIG_DIR=~/.claude-rea npx -y rea-agents@latest setup --client claude_code --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
After reviewing an update plan, rerun that same prefixed setup command without
|
||||||
|
`--dry-run` and approve its exact changes. Profile removal likewise needs the
|
||||||
|
prefix on `claude mcp remove --scope user rea` and any `rea uninstall` command;
|
||||||
|
keep the broader client scope of uninstall in mind. Restart the selected
|
||||||
|
profile after changing its pinned registration.
|
||||||
|
|
||||||
|
`claude mcp remove` removes only the registration. To remove REA from this
|
||||||
|
profile without running the broader uninstall flow, first back up the profile's
|
||||||
|
REA-managed skill directory, then remove
|
||||||
|
`$CLAUDE_CONFIG_DIR/skills/reverse-engineer-anything` as well. Keep other skills
|
||||||
|
in the profile intact.
|
||||||
|
|
||||||
|
The commands above use POSIX shell assignment syntax. In native Windows
|
||||||
|
PowerShell, select the profile for the whole setup/launch/check session and
|
||||||
|
restore the previous selection afterward:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$previousClaudeConfigDir = $env:CLAUDE_CONFIG_DIR
|
||||||
|
try {
|
||||||
|
$env:CLAUDE_CONFIG_DIR = Join-Path $HOME ".claude-rea"
|
||||||
|
npx rea-agents setup --client claude_code
|
||||||
|
claude mcp list
|
||||||
|
npx rea-agents doctor --client claude_code --skill --json
|
||||||
|
npx -y rea-agents@latest setup --client claude_code --dry-run
|
||||||
|
# After reviewing the plan, repeat setup without --dry-run and approve it.
|
||||||
|
claude
|
||||||
|
# Profile removal, when intended, also belongs inside this selected scope:
|
||||||
|
# claude mcp remove --scope user rea
|
||||||
|
} finally {
|
||||||
|
if ($null -eq $previousClaudeConfigDir) {
|
||||||
|
Remove-Item Env:CLAUDE_CONFIG_DIR -ErrorAction SilentlyContinue
|
||||||
|
} else {
|
||||||
|
$env:CLAUDE_CONFIG_DIR = $previousClaudeConfigDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use PowerShell for these Windows examples rather than pasting POSIX assignments
|
||||||
|
into cmd.exe. Keep profile updates, readiness checks and removal inside the
|
||||||
|
same selected environment; the `finally` block restores the previous profile
|
||||||
|
or unsets the variable when it was absent.
|
||||||
|
|
||||||
|
**One project.** Install without configuring Claude Code: use the curl
|
||||||
|
installer's `--no-setup`, or run setup only for other clients. Then register
|
||||||
|
REA from the project directory at Claude Code's local scope.
|
||||||
|
|
||||||
|
This route uses the default Claude profile. Keep `CLAUDE_CONFIG_DIR` unset for
|
||||||
|
the entire local lifecycle: registration, backup, listing, version replacement
|
||||||
|
and removal. In POSIX shells run `unset CLAUDE_CONFIG_DIR`; in PowerShell run
|
||||||
|
`Remove-Item Env:CLAUDE_CONFIG_DIR -ErrorAction SilentlyContinue` before these
|
||||||
|
commands. Record an existing selection first if you intend to restore it after
|
||||||
|
finishing. The configuration path below is the default profile's
|
||||||
|
`~/.claude.json`; a different active profile uses its own config root instead.
|
||||||
|
|
||||||
|
Register with the variable unset:
|
||||||
|
|
||||||
|
<!-- x-release-please-start-version -->
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude mcp add --scope local rea -- npx -y rea-agents@6.5.0 mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
On native Windows, use the same project directory and wrap the package-runner
|
||||||
|
shim so the stdio client can launch it:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
claude mcp add --scope local rea -- cmd.exe /d /c npx -y rea-agents@6.5.0 mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- x-release-please-end -->
|
||||||
|
|
||||||
|
Local scope stores the entry under that directory's path in `~/.claude.json`,
|
||||||
|
so other projects don't load it. This route doesn't install the bundled skill.
|
||||||
|
Avoid `--scope project`, which writes `.mcp.json` into the repository where it
|
||||||
|
can be committed or copied. `rea doctor` and `rea uninstall` read only the
|
||||||
|
user-level entry, so they don't report or remove a local-scope registration;
|
||||||
|
remove it with `claude mcp remove --scope local rea` from the same directory.
|
||||||
|
`rea update` doesn't refresh it either, so it stays on the version it names.
|
||||||
|
To move it to a newer release, first back up the project entry in
|
||||||
|
`~/.claude.json` and record its environment and other metadata. In particular,
|
||||||
|
preserve caller-selected overrides such as `REA_MCP_INPUT_SCHEMA_PROFILE` and
|
||||||
|
`REA_MCP_MAX_RESPONSE_BYTES`. Run `claude mcp remove --scope local rea` from the
|
||||||
|
same directory, then recreate it with the platform-appropriate command above
|
||||||
|
and the new version. Reapply every recorded environment value with Claude
|
||||||
|
Code's `mcp add --env NAME=value` options and restore any other retained entry
|
||||||
|
metadata before restarting the project session. Removal and re-addition do not
|
||||||
|
preserve these values automatically. Running setup for Claude Code instead
|
||||||
|
would add a user-level entry for every project.
|
||||||
|
|
||||||
|
Check the separate profile with the prefixed `claude mcp list` above and compare
|
||||||
|
with the default profile with `CLAUDE_CONFIG_DIR` unset. For local scope, run
|
||||||
|
`claude mcp list` from the selected project and again from another project.
|
||||||
|
|
||||||
## Check readiness for your task
|
## Check readiness for your task
|
||||||
|
|
||||||
@@ -134,6 +301,15 @@ configuration without changing them. Without options, it audits every detected
|
|||||||
registration, the installed skill and optional analysis engines; its overall
|
registration, the installed skill and optional analysis engines; its overall
|
||||||
`healthy` value can be false while your chosen workflow works.
|
`healthy` value can be false while your chosen workflow works.
|
||||||
|
|
||||||
|
Doctor's `providerInspections` are host-level engine and registration checks,
|
||||||
|
not a readiness row for every advertised MCP adapter surface. `tools/list`
|
||||||
|
remains the complete stable catalog. Call `binary_session` with `{}` before
|
||||||
|
choosing an operation; it works before a target is opened, and
|
||||||
|
`result.tool_availability` reports per-operation availability, reasons and
|
||||||
|
client requirements for the current session state. It does not guarantee that
|
||||||
|
a particular caller-supplied input will be valid. See
|
||||||
|
[MCP discovery](mcp-contracts.md#identity-and-discovery).
|
||||||
|
|
||||||
Select the part you want to check:
|
Select the part you want to check:
|
||||||
|
|
||||||
| Task | Readiness check |
|
| Task | Readiness check |
|
||||||
@@ -155,6 +331,21 @@ on `open_binary`. A `capability_unavailable` failure carries
|
|||||||
`details.candidate_ids`, while `provider_unavailable` asks you to repair the
|
`details.candidate_ids`, while `provider_unavailable` asks you to repair the
|
||||||
selected engine. See [provider selection](cli.md#choose-a-provider).
|
selected engine. See [provider selection](cli.md#choose-a-provider).
|
||||||
|
|
||||||
|
### Linux owned-process prerequisite
|
||||||
|
|
||||||
|
On Linux, REA's shared process supervisor requires a procps-compatible `ps`
|
||||||
|
on REA's `PATH` for ownership checks before launching a child. Ghidra, JADX,
|
||||||
|
firmware tools and static JavaScript/Electron analysis use these checks.
|
||||||
|
Install your distribution's `procps` or `procps-ng` package, or put another
|
||||||
|
procps-compatible `ps` on `PATH` before
|
||||||
|
running those workflows. A missing or incompatible command causes a refusal
|
||||||
|
before the supervised child starts.
|
||||||
|
|
||||||
|
The public error code varies by caller: `capability_unavailable`,
|
||||||
|
`provider_unavailable` or `execution_failure`. Some provider adapters report
|
||||||
|
only a generic startup error. Check this prerequisite before following
|
||||||
|
provider-specific recovery advice.
|
||||||
|
|
||||||
## Diagnose, update, and remove
|
## Diagnose, update, and remove
|
||||||
|
|
||||||
`rea doctor --json` is strictly read-only. `rea update` updates only the npm or Bun global installation that owns the running CLI. Source checkouts and package-runner copies must be updated through the mechanism that owns them; a fresh package-runner invocation can use `npx rea-agents@latest` or `bunx rea-agents@latest`. `rea uninstall` removes only REA-owned agent registrations and skill files; `--purge-data` additionally removes REA cache and state paths.
|
`rea doctor --json` is strictly read-only. `rea update` updates only the npm or Bun global installation that owns the running CLI. Source checkouts and package-runner copies must be updated through the mechanism that owns them; a fresh package-runner invocation can use `npx rea-agents@latest` or `bunx rea-agents@latest`. `rea uninstall` removes only REA-owned agent registrations and skill files; `--purge-data` additionally removes REA cache and state paths.
|
||||||
@@ -207,9 +398,11 @@ running server or its registration.
|
|||||||
The npm release checked on **2026-10-11** was **6.4.0**, published from
|
The npm release checked on **2026-10-11** was **6.4.0**, published from
|
||||||
[`a56f7bb6`](https://github.com/morluto/rea/commit/a56f7bb668ab23fec8d33f7af353ac7ed2fe1895).
|
[`a56f7bb6`](https://github.com/morluto/rea/commit/a56f7bb668ab23fec8d33f7af353ac7ed2fe1895).
|
||||||
The actual registry package accepted dry-run setup plans for the 17 client IDs
|
The actual registry package accepted dry-run setup plans for the 17 client IDs
|
||||||
in that release, including Qwen Code, Pi and Hermes. DeepSeek Harness (`dsh`)
|
in that release, including Qwen Code, Pi and Hermes. Seven client IDs are
|
||||||
is on main and is not in npm 6.4.0; `rea setup --client dsh` against `@latest`
|
included from 6.5.0 and were absent from the checked npm 6.4.0 package: `droid`,
|
||||||
reports an unsupported client until a release containing it is published.
|
`dsh`, `kimi`, `qoder`, `trae`, `workbuddy` and `zcode`.
|
||||||
|
The 6.4.0 registry check does not establish 6.5.0 publication or live DeepSeek
|
||||||
|
API integration.
|
||||||
OpenCode's plan selected
|
OpenCode's plan selected
|
||||||
`OPENCODE_CONFIG_DIR` even when that directory did not yet exist. These checks
|
`OPENCODE_CONFIG_DIR` even when that directory did not yet exist. These checks
|
||||||
used an isolated home on macOS with Node 24; they verify planning and argument
|
used an isolated home on macOS with Node 24; they verify planning and argument
|
||||||
@@ -277,6 +470,7 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
|
|||||||
| GitHub Copilot CLI | `copilot_cli` |
|
| GitHub Copilot CLI | `copilot_cli` |
|
||||||
| Command Code | `commandcode` |
|
| Command Code | `commandcode` |
|
||||||
| Qwen Code | `qwen_code` |
|
| Qwen Code | `qwen_code` |
|
||||||
|
| Kimi Code | `kimi` |
|
||||||
| VS Code | `vscode` |
|
| VS Code | `vscode` |
|
||||||
| Grok Build | `grok_build` |
|
| Grok Build | `grok_build` |
|
||||||
| OMP | `omp` |
|
| OMP | `omp` |
|
||||||
@@ -285,11 +479,12 @@ hosted connector; see [its configuration](client-configuration.md#other-client-s
|
|||||||
| Qoder | `qoder` |
|
| Qoder | `qoder` |
|
||||||
| Factory Droid | `droid` |
|
| Factory Droid | `droid` |
|
||||||
| Trae | `trae` |
|
| Trae | `trae` |
|
||||||
|
| WorkBuddy | `workbuddy` |
|
||||||
| ZCode | `zcode` |
|
| ZCode | `zcode` |
|
||||||
| DeepSeek Harness | `dsh` |
|
| DeepSeek Harness | `dsh` |
|
||||||
|
|
||||||
`dsh` is main-only until a release after 6.4.0. The registry check above does
|
`droid`, `dsh`, `kimi`, `qoder`, `trae`, `workbuddy`, and `zcode` require
|
||||||
not cover it.
|
`rea-agents@6.5.0` or later. The 6.4.0 registry check above does not cover them.
|
||||||
|
|
||||||
See [client configuration and troubleshooting](client-configuration.md) for
|
See [client configuration and troubleshooting](client-configuration.md) for
|
||||||
configuration paths, profiles, trust policies, and schema-limit workarounds.
|
configuration paths, profiles, trust policies, and schema-limit workarounds.
|
||||||
@@ -308,9 +503,12 @@ Hopper is separate commercial software with its own license. Its free demo has
|
|||||||
vendor-defined limits, and a paid license is optional. REA reuses any detected
|
vendor-defined limits, and a paid license is optional. REA reuses any detected
|
||||||
installation and preserves Hopper during uninstall.
|
installation and preserves Hopper during uninstall.
|
||||||
|
|
||||||
The supported native host baseline is macOS 12+, Ubuntu 24.04+, Fedora 41+,
|
The automated Hopper installer supports macOS 12+, Ubuntu 24.04+, Fedora 41+,
|
||||||
Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy. Ghidra and IDA have their own provider-specific
|
Nobara 44+, 64-bit Arch Linux, CachyOS, or Omarchy. REA itself runs on any
|
||||||
host requirements; Windows Ghidra uses the [experimental P0 boundary](windows-ghidra-p0.md).
|
Linux distribution with a recognized apt, dnf, or pacman package family, where
|
||||||
|
an existing Hopper launcher is selected through `HOPPER_LAUNCHER_PATH`. Ghidra
|
||||||
|
and IDA have their own provider-specific host requirements; Windows Ghidra uses
|
||||||
|
the [experimental P0 boundary](windows-ghidra-p0.md).
|
||||||
|
|
||||||
On macOS, approved setup downloads the official DMG, checks its published size
|
On macOS, approved setup downloads the official DMG, checks its published size
|
||||||
and digest, validates the application bundle, and atomically installs it to
|
and digest, validates the application bundle, and atomically installs it to
|
||||||
@@ -328,8 +526,53 @@ document for the same target. REA does not currently identify, focus, or reuse
|
|||||||
that existing GUI document, and the supported launcher exposes no attach or
|
that existing GUI document, and the supported launcher exposes no attach or
|
||||||
reuse action for it.
|
reuse action for it.
|
||||||
|
|
||||||
|
REA currently declines saved Hopper database (`.hop`) targets on macOS because
|
||||||
|
the native `open database` launch route does not run REA's bridge script. To
|
||||||
|
analyze that program with REA, select its original executable as the target and
|
||||||
|
open it through Hopper; this starts the supported bridge route. This does not
|
||||||
|
recover database-only annotations or analysis changes. Saved-database analysis
|
||||||
|
on Linux continues to use its supported demo-launch route.
|
||||||
|
|
||||||
|
Hopper lease directories must be readable and owned by the current user.
|
||||||
|
REA validates and secures an opened directory before reserving a lease. If
|
||||||
|
opening that directory fails with a permission error, the diagnostic identifies
|
||||||
|
the path and recovery steps while leaving its permissions unchanged. Check
|
||||||
|
ownership and parent-directory access, then restore owner-only read, write, and
|
||||||
|
search permissions (`0700`) on a directory you own before retrying.
|
||||||
|
|
||||||
On supported Linux distributions, approved setup verifies Hopper's official
|
On supported Linux distributions, approved setup verifies Hopper's official
|
||||||
`.deb`, `.rpm`, or Arch package before invoking the native package manager.
|
`.deb`, `.rpm`, or Arch package before invoking the native package manager.
|
||||||
|
The official release feed selects the current public demo without a version
|
||||||
|
pin. REA verifies the package's detached PGP signature against Hopper's stable
|
||||||
|
primary fingerprint `7C2D73BAFAF5B9A710A69DA1A26DF815099B1BCD`, then checks
|
||||||
|
that the signed package and installed launcher agree with `main_exe_hash`.
|
||||||
|
Rotating signing subkeys remain bound to that primary identity.
|
||||||
|
The feed row itself is not PGP-signed; doctor labels its display version as
|
||||||
|
`reported_release_version`, separately from the authenticated launcher identity.
|
||||||
|
|
||||||
|
The approval plan includes the caller-selected cache location. Approved setup
|
||||||
|
retains the signed archive, public key and release metadata under
|
||||||
|
`${XDG_CACHE_HOME:-$HOME/.cache}/rea/hopper-releases/<launcher-sha256>`.
|
||||||
|
An empty `XDG_CACHE_HOME` uses `$HOME/.cache`. Approved setup can repair a
|
||||||
|
damaged caller-owned entry after authenticating a fresh package; the previous
|
||||||
|
entry is preserved beside it as `<launcher-sha256>.backup-<id>`. Ordinary doctor
|
||||||
|
and launches reject damaged evidence and never repair it implicitly.
|
||||||
|
Approved publications of the same canonical cache entry share a process-backed
|
||||||
|
lease. Concurrent setup waits without moving another writer's evidence; a
|
||||||
|
failed replacement restores the previous directory when possible and reports
|
||||||
|
any retained backup path. Unavailable reads or native inspection tools do not
|
||||||
|
authorize replacing existing evidence.
|
||||||
|
Doctor and launches authenticate this evidence for offline use; an editable
|
||||||
|
digest receipt alone cannot authorize a launcher. Verification reuses at most
|
||||||
|
eight immutable package proofs per process, keyed by the complete package,
|
||||||
|
key, signature, package format and expected launcher digest. Cached file reads
|
||||||
|
still check identity and content. Without retained evidence, inspecting an
|
||||||
|
existing install requires the matching current official package and network
|
||||||
|
access. Successful live inspection can be reused within that process after
|
||||||
|
rehashing the launcher; it does not write a persistent cache or claim that a
|
||||||
|
previously observed release is still the latest. Older uncached builds require
|
||||||
|
an approved update while the feed exposes only its current release.
|
||||||
|
|
||||||
REA runs the supported demo build on a private Xvfb display and selects Hopper's
|
REA runs the supported demo build on a private Xvfb display and selects Hopper's
|
||||||
offered demo mode for each analysis session; it does not require the user's
|
offered demo mode for each analysis session; it does not require the user's
|
||||||
desktop display. Unattended package-manager access requires
|
desktop display. Unattended package-manager access requires
|
||||||
@@ -340,6 +583,13 @@ host mount and the rest of `/tmp` remain unchanged; this fallback never invokes
|
|||||||
`sudo`. `rea doctor --provider hopper --json` reports the selected
|
`sudo`. `rea doctor --provider hopper --json` reports the selected
|
||||||
strategy and both host and effective mount facts.
|
strategy and both host and effective mount facts.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
The supported Linux demo forwards additional launches into its existing
|
The supported Linux demo forwards additional launches into its existing
|
||||||
application, even across private displays. REA therefore reserves one Linux
|
application, even across private displays. REA therefore reserves one Linux
|
||||||
Hopper application per user across cooperating REA processes. A competing
|
Hopper application per user across cooperating REA processes. A competing
|
||||||
@@ -361,11 +611,19 @@ sessions before removing that recovery directory and retrying.
|
|||||||
|
|
||||||
### Launcher paths and troubleshooting
|
### Launcher paths and troubleshooting
|
||||||
|
|
||||||
On macOS, REA uses
|
On macOS, REA checks executable Hopper launchers in this order: the canonical
|
||||||
`/Applications/Hopper Disassembler.app/Contents/MacOS/hopper` by default.
|
system application, the canonical application installed by `rea setup` under
|
||||||
|
`~/Applications`, then other `Hopper*.app` bundles in `/Applications` and
|
||||||
|
`~/Applications`. The Homebrew cask location is an optional `rea doctor`
|
||||||
|
diagnostic; to use a Homebrew-installed launcher for CLI/MCP startup, set
|
||||||
|
`HOPPER_LAUNCHER_PATH` to the path reported by doctor. REA does not run Homebrew
|
||||||
|
while loading runtime configuration.
|
||||||
|
Automatic discovery accepts executable regular files, including file symlinks,
|
||||||
|
and skips directories.
|
||||||
On Linux, it prefers executable `/opt/hopper/bin/Hopper`, then executable
|
On Linux, it prefers executable `/opt/hopper/bin/Hopper`, then executable
|
||||||
`~/.local/share/rea/hopper/bin/Hopper`; if neither exists, the former remains
|
`~/.local/share/rea/hopper/bin/Hopper`, followed by `/opt/hopper-v4/bin/hopper`
|
||||||
the diagnostic fallback. `HOPPER_LAUNCHER_PATH` overrides those choices:
|
and `/usr/bin/hopper`; if none exists, the former remains the diagnostic
|
||||||
|
fallback. `HOPPER_LAUNCHER_PATH` overrides those choices:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper
|
export HOPPER_LAUNCHER_PATH=/absolute/path/to/Hopper
|
||||||
@@ -409,6 +667,20 @@ If startup reports `Cannot launch Hopper! Connection is invalid.` or waits for
|
|||||||
bridge readiness, check Hopper for a pending demo, license, or loader dialog.
|
bridge readiness, check Hopper for a pending demo, license, or loader dialog.
|
||||||
Complete the dialog and retry. If REA reports `cleanup_incomplete`, follow the
|
Complete the dialog and retry. If REA reports `cleanup_incomplete`, follow the
|
||||||
retained-resource guidance below before reopening the target.
|
retained-resource guidance below before reopening the target.
|
||||||
|
|
||||||
|
On macOS, Hopper can show an `Executing Python Script...` overlay while the REA
|
||||||
|
bridge serves an active MCP connection. Long-lived MCP clients can keep that
|
||||||
|
connection open across agent turns, so the overlay may remain visible after the
|
||||||
|
analysis calls finish; by itself, it does not establish a stalled startup. REA
|
||||||
|
does not automatically idle-close a still-connected target. When the
|
||||||
|
investigation is finished, export an Evidence bundle first if you need to keep
|
||||||
|
the session ledger, then call `close_binary`; set `snapshot_path` to save a
|
||||||
|
provider-neutral analysis snapshot as part of closing. A successful close
|
||||||
|
shuts down the REA bridge and clears its overlay; REA does not control the
|
||||||
|
macOS Hopper application lifetime.
|
||||||
|
The Hopper GUI Cancel button's behavior for this bridge has not been verified;
|
||||||
|
`close_binary` is the supported cleanup path.
|
||||||
|
|
||||||
Hopper serializes analysis requests. Cancelling a wait can leave provider work
|
Hopper serializes analysis requests. Cancelling a wait can leave provider work
|
||||||
running, which the session reports. Successful decompilation is cached until
|
running, which the session reports. Successful decompilation is cached until
|
||||||
a relevant rename or comment changes it.
|
a relevant rename or comment changes it.
|
||||||
@@ -422,12 +694,18 @@ memory has no original file offset. For FAT Mach-O, offsets refer to the origina
|
|||||||
container file. Results retain Hopper's image-relative offset, the observed slice
|
container file. Results retain Hopper's image-relative offset, the observed slice
|
||||||
base, and the source executable path.
|
base, and the source executable path.
|
||||||
Loader selection reads the Mach-O container header, including FAT files with a
|
Loader selection reads the Mach-O container header, including FAT files with a
|
||||||
single architecture. For FAT64, REA validates the architecture table and selected
|
single architecture. For FAT32 and FAT64 containers, REA validates the
|
||||||
Mach-O header, prepares a private thin image, and loads it with Hopper's native
|
architecture table and selected Mach-O header, prepares a private thin image,
|
||||||
Mach-O loader. It checks the entire source's SHA-256 while copying the slice;
|
and loads it with Hopper's native Mach-O loader instead of Hopper's implicit
|
||||||
source identity and reported offsets still refer to the original container.
|
FAT slice choice. For arm64 targets, REA ranks subtype bases 0 (plain arm64) and
|
||||||
An ambiguous architecture subtype requires an explicitly extracted thin image;
|
2 (arm64e), ignoring capability bits for ranking while retaining the raw subtype
|
||||||
REA does not guess. Configured loader arguments remain explicit overrides.
|
for slice identity. These slices' function-entry encodings decode under
|
||||||
|
`--aarch64`, ahead of Apple's arm64e.x1 variant (base subtype 12), which is
|
||||||
|
selected only when it is the arm64 family's only candidate. REA checks the
|
||||||
|
entire source's SHA-256 while copying the slice; source identity and reported
|
||||||
|
offsets still refer to the original container. Any other ambiguous architecture
|
||||||
|
subtype requires an explicitly extracted thin image; REA does not guess.
|
||||||
|
Configured loader arguments remain explicit overrides.
|
||||||
FAT64 is distinct from the CPU architecture: FAT32 containers can contain 64-bit
|
FAT64 is distinct from the CPU architecture: FAT32 containers can contain 64-bit
|
||||||
executable images. This preparation avoids the Raw Binary loader dialog observed
|
executable images. This preparation avoids the Raw Binary loader dialog observed
|
||||||
with native FAT64 loading on Hopper 6.1.0-demo.
|
with native FAT64 loading on Hopper 6.1.0-demo.
|
||||||
@@ -487,7 +765,13 @@ copying license secrets into logs, or killing unrelated Hopper processes.
|
|||||||
REA connects to an existing Ghidra installation on Linux x64/arm64, macOS x64/arm64,
|
REA connects to an existing Ghidra installation on Linux x64/arm64, macOS x64/arm64,
|
||||||
or experimental Windows x64 P0.
|
or experimental Windows x64 P0.
|
||||||
It accepts Ghidra 12.1.x and the 64-bit full JDK declared by that installation's
|
It accepts Ghidra 12.1.x and the 64-bit full JDK declared by that installation's
|
||||||
`application.java.min` and `application.java.max`. Current 12.1 releases require
|
`application.java.min` and `application.java.max`. The JDK's reported platform must also
|
||||||
|
map to the same native decompiler directory as the host: a JDK running under
|
||||||
|
emulation (for example x86_64 under Rosetta on Apple silicon) or reporting a
|
||||||
|
non-host `os.name` fails the doctor
|
||||||
|
even when it is an otherwise accepted 64-bit full JDK. Select a matching JDK
|
||||||
|
through `JAVA_HOME`; adding another decompiler in the JVM-selected directory
|
||||||
|
does not satisfy this host-validated admission rule. Current 12.1 releases require
|
||||||
JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4
|
JDK 21 or newer and set no maximum. The bridge is verified with Ghidra 12.1.4
|
||||||
and JDK 21. Each installation must include the native decompiler for the host
|
and JDK 21. Each installation must include the native decompiler for the host
|
||||||
architecture in `Ghidra/Features/Decompiler/os/<platform>/` or the corresponding
|
architecture in `Ghidra/Features/Decompiler/os/<platform>/` or the corresponding
|
||||||
@@ -499,7 +783,7 @@ reported as incompatible instead of being admitted from its directory name, and
|
|||||||
a header that cannot be read or recognized is reported as unknown. REA does not
|
a header that cannot be read or recognized is reported as unknown. REA does not
|
||||||
build or install native tools, or change Gatekeeper quarantine settings.
|
build or install native tools, or change Gatekeeper quarantine settings.
|
||||||
|
|
||||||
The adapter exposes 25 read-only operations: thirteen inventory/name/search
|
The adapter exposes 26 read-only operations: fourteen inventory/name/search
|
||||||
operations and twelve function-analysis operations. These cover metadata,
|
operations and twelve function-analysis operations. These cover metadata,
|
||||||
decompilation, assembly, resolved calls, typed references, xrefs, function
|
decompilation, assembly, resolved calls, typed references, xrefs, function
|
||||||
dossiers, instructions, recovered data types, measured load mappings, loaded
|
dossiers, instructions, recovered data types, measured load mappings, loaded
|
||||||
@@ -513,7 +797,7 @@ and `set_addresses_names` name a function entry or any other mapped address
|
|||||||
session metadata edits leave executable bytes unchanged and are discarded on close. GUI controls
|
session metadata edits leave executable bytes unchanged and are discarded on close. GUI controls
|
||||||
require Hopper; Windows P0 remains read-only.
|
require Hopper; Windows P0 remains read-only.
|
||||||
|
|
||||||
Windows P0 admits native x86 and x86-64 PE applications and DLLs, plus explicitly
|
Windows P0 admits native x86, x86-64, ARMNT (Thumb-2) and ARM64 PE applications and DLLs, plus explicitly
|
||||||
selected [raw NMOS 6502 images](raw-binary.md#nmos-6502) with a caller-supplied
|
selected [raw NMOS 6502 images](raw-binary.md#nmos-6502) with a caller-supplied
|
||||||
entry at file offset 0, on fixed local NTFS volumes. Other raw CPU families remain
|
entry at file offset 0, on fixed local NTFS volumes. Other raw CPU families remain
|
||||||
unsupported on Windows.
|
unsupported on Windows.
|
||||||
@@ -547,9 +831,22 @@ installation remains unavailable on Windows. Setup never installs Ghidra,
|
|||||||
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
|
Java, or Python. It preserves valid detected Ghidra/JDK settings in agent
|
||||||
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
|
registrations. See [Windows Ghidra P0](windows-ghidra-p0.md) for provider diagnostics.
|
||||||
|
|
||||||
|
When `GHIDRA_INSTALL_DIR` is unset, doctor and the Ghidra provider also search
|
||||||
|
`PATH` for `ghidra` or `ghidraRun`, resolve the selected launcher through its
|
||||||
|
real path, and accept it only when that location belongs to a Ghidra tree with
|
||||||
|
the application metadata and headless launcher. An explicitly supplied
|
||||||
|
`GHIDRA_INSTALL_DIR` always takes precedence, including when it is invalid;
|
||||||
|
REA reports that error instead of silently selecting another installation.
|
||||||
|
This discovery is read-only and does not execute the Ghidra launcher.
|
||||||
|
|
||||||
Doctor validates the platform, architecture, Ghidra 12.1.x application version,
|
Doctor validates the platform, architecture, Ghidra 12.1.x application version,
|
||||||
`support/analyzeHeadless` or `support/analyzeHeadless.bat`, the installation's
|
`support/analyzeHeadless` or `support/analyzeHeadless.bat`, the installation's
|
||||||
Java major range, 64-bit JDK bitness, and the presence of `javac`/`javac.exe`.
|
Java major range, 64-bit JDK bitness, and the presence of `javac`/`javac.exe`.
|
||||||
|
It also checks that the reported JVM `os.arch` and `os.name` map to the same native decompiler
|
||||||
|
directory validated for the host; unrecognized reported architectures or a
|
||||||
|
non-host operating system are refused. LaunchSupport overrides of either
|
||||||
|
property, including through helper-environment JVM option variables, are
|
||||||
|
rejected at launch.
|
||||||
When Java is found through `PATH`, setup records its observed JDK home so GUI
|
When Java is found through `PATH`, setup records its observed JDK home so GUI
|
||||||
MCP clients do not depend on an incidental shell path. Setup shows every exact
|
MCP clients do not depend on an incidental shell path. Setup shows every exact
|
||||||
environment entry in its plan, writes only after approval, and never downloads,
|
environment entry in its plan, writes only after approval, and never downloads,
|
||||||
@@ -758,6 +1055,9 @@ errors expose Error names, messages, and available codes under
|
|||||||
truncation markers. Darwin ownership-helper compilation failures also retain
|
truncation markers. Darwin ownership-helper compilation failures also retain
|
||||||
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
|
the compiler outcome, exit code or signal, and bounded stderr; a timed-out
|
||||||
compiler remains distinguishable from a compiler that exited with an error.
|
compiler remains distinguishable from a compiler that exited with an error.
|
||||||
|
The helper compile deadline defaults to 120,000 ms; set
|
||||||
|
`REA_SWIFTC_COMPILE_TIMEOUT_MS` to an integer between 1 and 2,147,483,647 when
|
||||||
|
a busy host needs longer.
|
||||||
Other rejection values retain their primitive value or an explicit type.
|
Other rejection values retain their primitive value or an explicit type.
|
||||||
Shutdown warnings include the failure
|
Shutdown warnings include the failure
|
||||||
kind, message, and diagnostics, while process and temporary-project cleanup
|
kind, message, and diagnostics, while process and temporary-project cleanup
|
||||||
@@ -779,7 +1079,7 @@ On a controlled Windows x64 runner, use
|
|||||||
`npm run verify:ghidra:windows`. The verifier generates a deterministic native
|
`npm run verify:ghidra:windows`. The verifier generates a deterministic native
|
||||||
PE fixture from source bytes and requires the Windows native authority before
|
PE fixture from source bytes and requires the Windows native authority before
|
||||||
opening the provider. The controls are implemented on main; see the
|
opening the provider. The controls are implemented on main; see the
|
||||||
[release boundary](#released-package-and-main). The lane verifies all 25 admitted
|
[release boundary](#released-package-and-main). The lane verifies all 26 admitted
|
||||||
read-only operations, digest identity, and runtime cleanup. The separate
|
read-only operations, digest identity, and runtime cleanup. The separate
|
||||||
`verify:ghidra:windows:package` lane checks the packed CLI/MCP boundary with an
|
`verify:ghidra:windows:package` lane checks the packed CLI/MCP boundary with an
|
||||||
ordinary user. It requires the built Windows native bundle and an existing
|
ordinary user. It requires the built Windows native bundle and an existing
|
||||||
@@ -804,7 +1104,7 @@ For a client that requires manual configuration, use:
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"rea": {
|
"rea": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "rea-agents@6.4.0", "mcp"]
|
"args": ["-y", "rea-agents@6.5.0", "mcp"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ are `inspect_analysis_view`, `trace_application_feature`,
|
|||||||
`trace_javascript_semantics`, `compare_application_versions`,
|
`trace_javascript_semantics`, `compare_application_versions`,
|
||||||
`compare_source_to_bundle`, and `compare_javascript_export_shapes`; their CLI
|
`compare_source_to_bundle`, and `compare_javascript_export_shapes`; their CLI
|
||||||
equivalents use the same names with hyphens. `inspect_analysis_view` projects a
|
equivalents use the same names with hyphens. `inspect_analysis_view` projects a
|
||||||
summary, module page, or one module from already completed application
|
summary, module page, typed module references, or one module from already completed application
|
||||||
Evidence; it does not walk relationships.
|
Evidence; it does not walk relationships.
|
||||||
|
|
||||||
`inspect_analysis_view` also projects retained `inspect_binary_layout`
|
`inspect_analysis_view` also projects retained `inspect_binary_layout`
|
||||||
@@ -40,6 +40,46 @@ combine its fields with the fact's location. Semantic trace results include the
|
|||||||
canonical context subset referenced by their returned facts, so those facts
|
canonical context subset referenced by their returned facts, so those facts
|
||||||
remain self-contained in the response.
|
remain self-contained in the response.
|
||||||
|
|
||||||
|
## Retained module facts
|
||||||
|
|
||||||
|
For canonical identities and retained source-range counts, query the existing
|
||||||
|
`inspect_analysis_view` tool with a typed module-reference projection:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"kind": "retained-evidence",
|
||||||
|
"evidence_id": "ev_ACTUAL_PARENT_ID"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"kind": "javascript",
|
||||||
|
"collection": "modules",
|
||||||
|
"projection": "references",
|
||||||
|
"where": { "path": "main.js", "kind": "javascript-module" },
|
||||||
|
"offset": 0,
|
||||||
|
"limit": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the parent ID with the actual analysis summary's `parent_evidence_id`.
|
||||||
|
Filters match exact retained paths and kinds. Omission includes all values;
|
||||||
|
`path: null` selects an unavailable path and `path: ""` selects a recorded empty
|
||||||
|
path. Multiple identities can share a path. The default offset is 0 and limit
|
||||||
|
is 64; follow `coverage.next_offset` for later matching rows in retained order.
|
||||||
|
|
||||||
|
Each row contains its `node_id`, kind, path, canonical identity, retained
|
||||||
|
source-range observation count, and distinct supporting authority/state/confidence
|
||||||
|
combinations in `basis`. These counts do not prove unique reference sites or
|
||||||
|
runtime loading. Use the existing module item view with the row's `node_id` for
|
||||||
|
exact source ranges and complete observations. The result retains parent/query
|
||||||
|
Evidence identity, artifact identity, limitations and unknowns. `source_coverage`
|
||||||
|
keeps selected/unselected sources, graph coverage and unknown counts separate
|
||||||
|
from page exhaustion; an exhausted or empty page cannot prove absence from an
|
||||||
|
incompletely analyzed application. CLI `inspect-analysis-view` reads the same
|
||||||
|
request from a JSON file; portable input uses `source.kind: "inline"` with the
|
||||||
|
complete parent Evidence.
|
||||||
|
|
||||||
## Feature tracing
|
## Feature tracing
|
||||||
|
|
||||||
Select one literal seed kind: node ID, route, string, API, IPC channel, module,
|
Select one literal seed kind: node ID, route, string, API, IPC channel, module,
|
||||||
@@ -60,6 +100,8 @@ provider implicitly.
|
|||||||
`trace_javascript_semantics` queries the authenticated companion graph without
|
`trace_javascript_semantics` queries the authenticated companion graph without
|
||||||
rereading mutable files. A seed may identify a semantic or application node,
|
rereading mutable files. A seed may identify a semantic or application node,
|
||||||
literal, function fingerprint, property, endpoint, event, or boundary field.
|
literal, function fingerprint, property, endpoint, event, or boundary field.
|
||||||
|
For a recovered parse, coercion, or validation boundary, its field value is the
|
||||||
|
static operation's method name (for example, `Number`).
|
||||||
The query declares backward provenance, forward influence, callers, or
|
The query declares backward provenance, forward influence, callers, or
|
||||||
ownership and can restrict admitted relation kinds. REA returns every matching
|
ownership and can restrict admitted relation kinds. REA returns every matching
|
||||||
seed and the complete reachable subgraph; callers do not choose node, relation,
|
seed and the complete reachable subgraph; callers do not choose node, relation,
|
||||||
@@ -83,10 +125,22 @@ callee bodies and built-ins such as `Object.assign`, `Reflect.set`, and
|
|||||||
values. This uncertainty does not prove that a call actually mutated a value.
|
values. This uncertainty does not prove that a call actually mutated a value.
|
||||||
Interprocedural aliasing beyond the supported call-site references remains
|
Interprocedural aliasing beyond the supported call-site references remains
|
||||||
outside this mutation pass.
|
outside this mutation pass.
|
||||||
|
For process configuration, only known environment value slots and canonical
|
||||||
|
argument indexes are treated as primitive when passed to calls. Unknown or
|
||||||
|
inherited selections can expose a mutable prototype, so later reads of that
|
||||||
|
configuration container retain uncertainty; earlier top-level reads and unrelated
|
||||||
|
configuration containers remain independent.
|
||||||
|
|
||||||
Function fingerprints commit normalized syntax, control-flow shape, relation
|
Function fingerprints commit normalized syntax, control-flow shape, relation
|
||||||
shape, literal sets, arity, and detected effects without using local names or
|
shape, literal sets, arity, and detected effects without using local names or
|
||||||
source offsets. Equal duplicate fingerprints remain ambiguous. Dynamic
|
source offsets. Function-local directive literals contribute their values while
|
||||||
|
preserving escape spelling: `"use\x20strict"` differs from `"use strict"`.
|
||||||
|
Quote style, formatting, and local names remain normalized. Equal duplicate
|
||||||
|
fingerprints remain ambiguous.
|
||||||
|
Fingerprints are static candidates, not behavioral-equivalence proofs or a
|
||||||
|
version-independent identity. Re-analyze affected sources and obtain new seeds
|
||||||
|
after analyzer corrections; previously retained Evidence remains queryable using
|
||||||
|
its original component commitments. Dynamic
|
||||||
calls/properties/names, parser recovery, unresolved handles, and unsupported API
|
calls/properties/names, parser recovery, unresolved handles, and unsupported API
|
||||||
shapes stay explicit unknowns; a missing relation is never reported as proven
|
shapes stay explicit unknowns; a missing relation is never reported as proven
|
||||||
behavioral absence.
|
behavioral absence.
|
||||||
|
|||||||
@@ -206,6 +206,9 @@ code unit, matching the original bytes identified by the artifact digest.
|
|||||||
|
|
||||||
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
|
Plain `.ts` artifact sources use TypeScript syntax without JSX, including
|
||||||
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
|
angle-bracket type assertions. `.tsx` and `.jsx` retain JSX parsing.
|
||||||
|
JavaScript sources use JavaScript grammar with JSX, never TypeScript syntax,
|
||||||
|
so valid JavaScript that TypeScript reads differently keeps its JavaScript
|
||||||
|
meaning.
|
||||||
|
|
||||||
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
|
Relative TypeScript imports can name their emitted `.js`, `.mjs`, or `.cjs`
|
||||||
counterparts while resolving to inventoried source files. This substitution
|
counterparts while resolving to inventoried source files. This substitution
|
||||||
@@ -220,7 +223,10 @@ Selected bounded text is then parsed as inert data to recover:
|
|||||||
- Webpack and Rspack chunk registrations and module factories represented as
|
- Webpack and Rspack chunk registrations and module factories represented as
|
||||||
AST literals;
|
AST literals;
|
||||||
- static imports, dynamic imports, CommonJS `require` calls, workers, and
|
- static imports, dynamic imports, CommonJS `require` calls, workers, and
|
||||||
service workers;
|
service workers (only `new Worker(new URL(path, import.meta.url))` resolves
|
||||||
|
relative to the calling module; string worker and service-worker paths use
|
||||||
|
the document base URL or Node's working directory, which static analysis
|
||||||
|
does not establish, so they keep their expression and stay unresolved);
|
||||||
- route, network endpoint, storage, and vendor-marker observations;
|
- route, network endpoint, storage, and vendor-marker observations;
|
||||||
- local source-map declarations and original
|
- local source-map declarations and original
|
||||||
source names and content digests;
|
source names and content digests;
|
||||||
@@ -270,8 +276,19 @@ RPC client syntax also contributes network endpoint candidates, in both
|
|||||||
named operation found at the start of a string or cooked template literal —
|
named operation found at the start of a string or cooked template literal —
|
||||||
the name must be followed by variable definitions (`($`), a directive, or a
|
the name must be followed by variable definitions (`($`), a directive, or a
|
||||||
selection set starting with a field name — or in a precompiled
|
selection set starting with a field name — or in a precompiled
|
||||||
`OperationDefinition` document object. A graphql-tag `loc.source.body` copy is
|
`OperationDefinition` document object. A standalone Source object's `body`
|
||||||
not reported twice. Anonymous operations are not reported.
|
remains an operation candidate even when it has `locationOffset` metadata.
|
||||||
|
A graphql-tag `loc.source.body` copy is suppressed only when that source
|
||||||
|
belongs to a recovered document with the same operation and its containing
|
||||||
|
document, location and source have proven private metadata ownership. Writes,
|
||||||
|
escapes and classic-script global bindings retain the source observation.
|
||||||
|
Anonymous operations are not reported.
|
||||||
|
|
||||||
|
Literal link options and precompiled document fields follow object property
|
||||||
|
order: the final definition owns the key. A later spread, dynamic computed key,
|
||||||
|
method, or accessor can make a field unknown, so an overwritten literal is not
|
||||||
|
reported as the endpoint or operation. A final explicit property after a spread
|
||||||
|
or dynamic key remains usable.
|
||||||
|
|
||||||
A procedure path is the server-side procedure name, not a resolved URL; the
|
A procedure path is the server-side procedure name, not a resolved URL; the
|
||||||
transport URL depends on the link configuration and batching. When the proxy is
|
transport URL depends on the link configuration and batching. When the proxy is
|
||||||
@@ -282,9 +299,14 @@ suffix. Utility calls such as `useUtils().invoice.list.fetch()`, `useQueries`,
|
|||||||
and links whose callee was renamed by a minifier are not recognized.
|
and links whose callee was renamed by a minifier are not recognized.
|
||||||
|
|
||||||
Each recovered bundle module retains the exact factory-source digest. A complete
|
Each recovered bundle module retains the exact factory-source digest. A complete
|
||||||
bounded AST also receives a `babel-ast-v1` structural fingerprint that ignores
|
bounded AST also receives a `babel-ast-v2` structural fingerprint that ignores
|
||||||
ordinary identifier names while retaining syntax, literals, operators, object
|
ordinary identifier names while retaining syntax, literals, operators, object
|
||||||
keys, and member properties. If fingerprint construction reaches its structural
|
keys, and member properties. Computed member identifiers retain their access
|
||||||
|
syntax without treating a local variable's spelling as a fixed property; template
|
||||||
|
literal contents remain part of the fingerprint. Version comparison keeps the
|
||||||
|
algorithm in its matching key, so retained `babel-ast-v1` fingerprints cannot match
|
||||||
|
these new fingerprints. Re-analyze both snapshots to compare their new structure.
|
||||||
|
If fingerprint construction reaches its structural
|
||||||
bound, the fingerprint is unavailable and its status is `truncated`; a bounded
|
bound, the fingerprint is unavailable and its status is `truncated`; a bounded
|
||||||
prefix is never presented as a complete stable fingerprint.
|
prefix is never presented as a complete stable fingerprint.
|
||||||
|
|
||||||
@@ -615,7 +637,19 @@ href are read without surrounding whitespace or embedded tabs and newlines, and
|
|||||||
their percent-encoded path bytes are decoded; encoded dot and separator bytes
|
their percent-encoded path bytes are decoded; encoded dot and separator bytes
|
||||||
are still rejected. CommonJS module lookups retain extension and directory
|
are still rejected. CommonJS module lookups retain extension and directory
|
||||||
resolution.
|
resolution.
|
||||||
|
ESM, HTML, and URL references apply URL dot segments to empty path segments:
|
||||||
|
`./assets//../app.js` selects `assets/app.js`, while a remaining empty segment
|
||||||
|
such as `./assets//app.js` stays unavailable. CommonJS keeps filesystem path
|
||||||
|
semantics and selects `app.js` for the same text.
|
||||||
Unresolved HTML references retain their declaration, source range, and resolution
|
Unresolved HTML references retain their declaration, source range, and resolution
|
||||||
reason in the renderer observations.
|
reason in the renderer observations.
|
||||||
HTML script source ranges follow the HTML parser across LF, CRLF, and bare CR
|
HTML script source ranges follow the HTML parser across LF, CRLF, and bare CR
|
||||||
line endings, preserving UTF-16 columns.
|
line endings, preserving UTF-16 columns.
|
||||||
|
Inline JavaScript in local HTML, meaning classic or module `<script>` bodies
|
||||||
|
without a `src` attribute, is not analyzed. Each HTML file that has one gets an
|
||||||
|
unknown `parse-html-inline-scripts` scope with the exact body count and the
|
||||||
|
script type and source range of the first 100 bodies; beyond that, coverage is
|
||||||
|
truncated under the `html_inline_script_locations_per_file` limit, and the
|
||||||
|
scope's evidence counts the omitted locations. The graph names the
|
||||||
|
file and up to 10 starting lines in its limitations, and coverage is partial. Import maps, speculation rules, data blocks, and blank
|
||||||
|
bodies are not JavaScript and are not reported.
|
||||||
@@ -36,6 +36,12 @@ application starts, and REA never resumes it or sends
|
|||||||
`Runtime.runIfWaitingForDebugger`. Resume a paused target with a separate
|
`Runtime.runIfWaitingForDebugger`. Resume a paused target with a separate
|
||||||
caller-owned debugger before requesting application runtime observations.
|
caller-owned debugger before requesting application runtime observations.
|
||||||
|
|
||||||
|
For a packaged Electron app, read
|
||||||
|
[`inspect_electron_fuses`](electron-fuses.md) before starting an `electron-main`
|
||||||
|
target. When `EnableNodeCliInspectArguments` is disabled or removed, Electron
|
||||||
|
ignores `--inspect` and `--inspect-brk`. A missing main-process target after
|
||||||
|
launch does not, by itself, show that fuse.
|
||||||
|
|
||||||
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
|
The caller supplies the literal-loopback HTTP endpoint directly. Selecting an endpoint exposes all targets it serves and their local script locations or renderer origins. A target must match the exact requested target ID and expose a same-port WebSocket reported by that endpoint. Targets explicitly marked attached are rejected rather than displacing another debugger. Node's `/json/list` omits attachment state, so REA preserves it as `null` (unknown), reports that limitation, and permits observation unless the endpoint explicitly reports `attached: true`. Local paths are canonicalized after symlink resolution.
|
||||||
|
|
||||||
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
|
The `attached` field in JavaScript runtime and browser target results is nullable: `true` and `false` are reported only when discovery supplied a boolean; `null` means the endpoint omitted or could not report the state. CDP `TargetInfo` worker records require an `attached` boolean; REA excludes a worker record with a missing or malformed value as an invalid protocol value.
|
||||||
@@ -89,6 +95,11 @@ pauses or resumes execution, calls `Runtime.runIfWaitingForDebugger`, launches
|
|||||||
the target, instruments JavaScript, or invokes Electron APIs. Closing a call
|
the target, instruments JavaScript, or invokes Electron APIs. Closing a call
|
||||||
closes only REA's WebSocket.
|
closes only REA's WebSocket.
|
||||||
|
|
||||||
|
Each WebSocket handshake and command response has its own 20-second deadline,
|
||||||
|
separate from the caller-selected observation window. A stalled handshake or
|
||||||
|
command reports `provider_timeout` and retains observations already received;
|
||||||
|
caller cancellation still reports cancellation.
|
||||||
|
|
||||||
The direct observations are:
|
The direct observations are:
|
||||||
|
|
||||||
- `Debugger.scriptParsed`, establishing that a bounded script location was
|
- `Debugger.scriptParsed`, establishing that a bounded script location was
|
||||||
|
|||||||
@@ -91,17 +91,20 @@ nodes. REA evaluates compatible static candidates conservatively:
|
|||||||
|
|
||||||
1. A captured-source SHA-256 plus one matching mapped location yields
|
1. A captured-source SHA-256 plus one matching mapped location yields
|
||||||
a content-and-location match.
|
a content-and-location match.
|
||||||
2. One unique exact content digest can match even when no location mapping is
|
2. When no static candidate exists at a mapped location, one unique exact
|
||||||
available. Whole-asset digests and reconstructed module-factory source
|
content digest can match. Whole-asset digests and reconstructed module-factory source
|
||||||
digests remain distinguishable in the reported basis.
|
digests remain distinguishable in the reported basis.
|
||||||
3. Without captured bytes, one unique mapped artifact path can produce a
|
3. Without captured bytes, one unique mapped artifact path can produce a
|
||||||
medium-confidence location match.
|
medium-confidence location match.
|
||||||
4. Multiple candidates remain `ambiguous`; no `observed_as` edge is emitted.
|
4. Multiple candidates remain `ambiguous`; no `observed_as` edge is emitted.
|
||||||
|
With captured source and mapped candidates, only mapped content matches
|
||||||
|
qualify; unrelated content matches elsewhere are excluded.
|
||||||
`candidate_static_count` reports the full qualified layer/node count, and
|
`candidate_static_count` reports the full qualified layer/node count, and
|
||||||
`candidate_static_nodes` includes every unique candidate reference.
|
`candidate_static_nodes` includes every unique candidate reference.
|
||||||
5. If captured bytes disagree with the static candidate at the mapped path,
|
5. If captured bytes disagree with the static candidate at the mapped path,
|
||||||
the result is an explicit mismatch. A path never overrides a digest
|
the result is an explicit mismatch. An equal digest elsewhere cannot hide
|
||||||
disagreement.
|
that disagreement. If a mapped candidate's digest is unavailable and no
|
||||||
|
mapped content match is known, the comparison remains `unknown`.
|
||||||
6. Missing authorization mappings and incomplete or truncated input coverage
|
6. Missing authorization mappings and incomplete or truncated input coverage
|
||||||
remain unknown or truncated rather than becoming absence claims.
|
remain unknown or truncated rather than becoming absence claims.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,27 @@ Start a JEB client that serves MCP and point REA at it:
|
|||||||
- In the JEB GUI client, start the MCP server from the _File_ menu (or via the VIBRE assistant).
|
- In the JEB GUI client, start the MCP server from the _File_ menu (or via the VIBRE assistant).
|
||||||
- Headless, use JEB's documented programmatic route (`JebMcpServerInstance` from the JEB API) with the JEB client on your classpath; JEB 5.48's `-c` headless mode does not accept `--mcp` directly.
|
- Headless, use JEB's documented programmatic route (`JebMcpServerInstance` from the JEB API) with the JEB client on your classpath; JEB 5.48's `-c` headless mode does not accept `--mcp` directly.
|
||||||
|
|
||||||
|
For setup registration, set `REA_JEB_MCP_URL` in the REA process environment
|
||||||
|
before running `rea setup`. The approved setup plan preserves the exact selected
|
||||||
|
URL after validating its syntax. That validation does not establish that the
|
||||||
|
JEB service is reachable or that a project is open; use the existing
|
||||||
|
`inspect-jeb-client` command for a read-only service query.
|
||||||
|
|
||||||
|
MCP `binary_session` probes the live JEB service and its advertised tool catalog.
|
||||||
|
Availability is reported per operation: a reachable headless client can support
|
||||||
|
inspection and decompilation while refusing `open_jeb_project`. Catalog discovery
|
||||||
|
uses one 60-second deadline across connection and pagination; a responsive server
|
||||||
|
cannot keep discovery running indefinitely by returning more pages.
|
||||||
|
|
||||||
|
Each upstream tool call has an absolute 60-second request deadline by default.
|
||||||
|
Set `REA_JEB_MCP_TIMEOUT_MS` to a positive decimal number of milliseconds to
|
||||||
|
select a different per-request budget, for example `REA_JEB_MCP_TIMEOUT_MS=300000`
|
||||||
|
for a five-minute decompilation. This budget starts after discovery and applies
|
||||||
|
to each individual `tools/call`; it does not extend the separate catalog deadline
|
||||||
|
or establish one end-to-end deadline across a whole CLI or MCP operation. A
|
||||||
|
request that reaches its budget returns a typed timeout, while caller
|
||||||
|
cancellation remains a cancellation.
|
||||||
|
|
||||||
Verified against JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP.
|
Verified against JEB 5.48.0 (Linux x64, licensed build) serving `jeb-mcp-server` 1.3.0 over streamable HTTP.
|
||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Kuna provider
|
||||||
|
|
||||||
|
[Kuna](https://github.com/Noelo-Lab/kuna) is an Apache-2.0 decompiler distributed as one `kuna` executable. REA uses a `kuna` you supply. It does not download, install, update or bundle Kuna. REA verified this provider with Kuna 1.833; it accepts other releases and reports them as unverified in `rea doctor` and in each analysis profile.
|
||||||
|
|
||||||
|
Kuna is static: it reads the selected image and never executes it.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export REA_KUNA_COMMAND=/absolute/path/to/kuna
|
||||||
|
rea doctor --provider kuna
|
||||||
|
```
|
||||||
|
|
||||||
|
Select it explicitly with `--provider kuna`, MCP's `open_binary.provider_id: "kuna"`, or `REA_ANALYSIS_PROVIDER=kuna`. With Kuna and another provider configured for the same target, `auto` reports `ambiguous` instead of choosing one.
|
||||||
|
|
||||||
|
| Setting | Default | Meaning |
|
||||||
|
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `REA_KUNA_COMMAND` | none (required) | Absolute path of the `kuna` executable. |
|
||||||
|
| `REA_KUNA_CACHE_DIR` | `~/Library/Caches/rea/kuna` (macOS), `$XDG_CACHE_HOME/rea/kuna` (Linux; `~/.cache/rea/kuna` when `XDG_CACHE_HOME` is unset or relative) | Absolute directory REA owns for project exports. Kuna's own spec cache also lives here, not in your Kuna cache. |
|
||||||
|
| `REA_KUNA_CACHE_MAX_BYTES` | 4 GiB | Size cap; least recently used exports are evicted above it. |
|
||||||
|
| `REA_KUNA_PROJECT_TIMEOUT_MS` | 30 minutes | Deadline for whole-program inventory/export, reference and string scans. |
|
||||||
|
| `REA_KUNA_TIMEOUT_MS` | 120 s | Deadline for one single-function decompile, and for the inventory run REA makes when the export ends without one. |
|
||||||
|
| `REA_KUNA_JOBS` | 1 | `decompile-project --jobs`. 1 is Kuna's reference answer; more jobs finish sooner but lose callee-first prototypes, and change about 40–60% of function bodies on the measured binaries. |
|
||||||
|
|
||||||
|
Windows is not supported yet. Supported targets are ELF and thin Mach-O executables for x86-64, AArch64 and ARM; PE and universal Mach-O are rejected with a typed reason.
|
||||||
|
|
||||||
|
## What Kuna answers
|
||||||
|
|
||||||
|
| Tool | Source |
|
||||||
|
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `list_procedures`, `search_procedures`, `procedure_address` | Kuna's callable inventory (`kuna functions --json`), including import slots it does not decompile |
|
||||||
|
| `address_name` | Function entries, then the data labels Kuna's export assigns |
|
||||||
|
| `procedure_pseudo_code`, `batch_decompile` | The project export, or a single-function run while the export builds |
|
||||||
|
| `procedure_assembly` | The project export's labeled listing |
|
||||||
|
| `procedure_callers`, `procedure_callees` | Call-kind edges from a lazy, body-free Kuna graph; synthetic import nodes are accepted by address |
|
||||||
|
| `list_strings`, `search_strings` | Typed string candidates from one cached, reference-free scan; exact-address, literal and ECMAScript regex selection |
|
||||||
|
| `xrefs` | Exact-address incoming reference sites, with forwarding aliases filtered explicitly |
|
||||||
|
| `analyze_function` | Pseudocode and assembly from the export, plus call relationships; typed references, comments, strings and CFG remain unavailable |
|
||||||
|
|
||||||
|
Typed procedure references, basic blocks, raw-memory tools, annotations and type recovery are not available from this provider. Use another provider for those operations.
|
||||||
|
|
||||||
|
## How answers are produced
|
||||||
|
|
||||||
|
The first text or assembly request in a session looks for this target's export in the cache. `list_documents` reads the selected path without initializing analysis. Inventory, function lookup, string queries, call relationships and xrefs do not start a C export. A data-name lookup starts it only when the address is absent from the function inventory. The export is keyed by the target's SHA-256, the Kuna executable's SHA-256, its version and `--jobs`.
|
||||||
|
|
||||||
|
- **Warm cache:** analysis reads reuse the verified export. A fresh server verifies its file digests once, even for concurrent first requests; that cost scales with export size.
|
||||||
|
- **Cold cache:**
|
||||||
|
- REA starts `kuna decompile-project` in the background on a private copy of the target.
|
||||||
|
- Meanwhile, pseudocode comes from one `kuna decompile --addr` run. Name and address lookups, and listings, use the inventory the export takes with `kuna functions --json` before it decompiles. The session runs `kuna functions` itself, under `REA_KUNA_TIMEOUT_MS`, only when no export is building (an inventory-only request does not start one) or the export ends without an inventory.
|
||||||
|
- Requests for the same function share one run; it stops when its last waiting request is cancelled. Each run analyzes the whole image again in its own process, so one REA process runs at most as many at once as the host has cores beyond `REA_KUNA_JOBS` (at least one). Further requests wait in order, and a waiting request is answered from the export if it finishes first. A run whose stop REA cannot prove keeps its slot until `close_binary` proves it stopped.
|
||||||
|
- Assembly and data names wait for the export; progress notifications report the wait every ten seconds. A session does not rebuild a failed export: requests that need it fail with `retryable: false`, the export's failure, its captured Kuna output and what to change before reopening the target. A setting such as `REA_KUNA_PROJECT_TIMEOUT_MS` or `REA_KUNA_JOBS` is read when REA starts, so changing it also needs a restart. Pseudocode keeps using single-function runs.
|
||||||
|
- A single-function answer says so in its limitations. It lacks the export's whole-program prototypes, so a later request can return different text for the same function.
|
||||||
|
|
||||||
|
Pseudocode, assembly, `analyze_function` and data-name answers record where their text came from in `raw_result.source`: `single-function`, or `project-export` with the cache directory. `procedure_pseudo_code` and `procedure_assembly` answers from the export also name the file, byte span and the file's SHA-256. Combined `analyze_function` answers retain both facet records in `raw_result.source.files`, each with its file, digest and byte span. Both texts come from one export; losing that project retries the complete report. Listings and procedure lookups carry no `raw_result`. Agents can open or search the export's `.c` and `.asm` there directly when that is cheaper than calling a tool per function. Text read that way is target-derived, like any decompiler output, but it bypasses the untrusted-content marker that REA's tool results carry.
|
||||||
|
|
||||||
|
The background export belongs to the session. `close_binary`, or the end of a CLI command, cancels it unless another session still waits for it. A one-shot CLI decompile therefore answers from a single-function run; any request that needs the export, such as `procedure_assembly` or `analyze_function`, keeps it.
|
||||||
|
|
||||||
|
The assembly label index is scanned once per export reader. Cancelling a request stops its wait without interrupting other readers; closing the session aborts and drains the shared scan before releasing its files.
|
||||||
|
|
||||||
|
Regex procedure and string search uses ECMAScript Unicode matching in a cancellable worker with a five-second matching deadline. Literal matching remains the default. Cancellation or a matching deadline leaves the session available.
|
||||||
|
|
||||||
|
Single-function subprocess output and each cached source facet share a 16 MiB buffered-text capacity. Cached spans are checked before allocation; larger text remains in the local export and the error identifies the file and range. `analyze_function` initially returns the complete dossier, which can still be large; later retained views do not remove that first transfer. Prefer the individual text tools for a focused question.
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
`list_strings` and `search_strings` share one lazy `kuna strings --json --encoding all --min-length 5 --termination any --no-xrefs` scan of the digest-checked private input. Skipping references avoids program lifting, a call graph and a C export. REA validates the input, scan policy, every row and the total before caching any facts. Encoding, character count, byte length and terminator facts must agree; supplementary UTF-8 characters count as Unicode scalars. Malformed or oversized output is an error, not an empty search. JSON admits at most 16 MiB before parsing. String scans use the whole-program `REA_KUNA_PROJECT_TIMEOUT_MS` deadline; `REA_KUNA_TIMEOUT_MS` applies to single-function decompilation. The parsed inventory belongs to one selected target/executable profile and expires on session close; deterministic output-buffer failures are retained until close, while other failures are not cached.
|
||||||
|
|
||||||
|
Results keep exact u64 virtual addresses, decoded text, native encoding, terminator status and byte length including any terminator. Kuna scans mapped sections or segments for runs of at least five characters; unterminated fragments can include instruction bytes. `all` includes ASCII, UTF-8 and the engine's Latin-range UTF-16LE reading. Other text, including short strings, non-Latin wide text and UTF-16BE, can remain undiscovered. An empty result does not prove absence.
|
||||||
|
|
||||||
|
Use `search_strings` for the matching rows or `list_strings` with an exact address for that location. Literal and regex matching run in REA, preserving the existing case and ECMAScript Unicode semantics instead of substituting Kuna's different regex dialect. Procedure and string regex queries share one worker slot per session. Input projections are materialized only after admission, so queued queries retain no per-query inventory arrays. Close cancels and drains matching. Native metadata queries also share one admission slot, including references; a failed stop retains admission until cleanup is proven.
|
||||||
|
|
||||||
|
`raw_result.strings` reports the source JSON digest/size, scanned scope, extraction policy, complete inventory count and selected count without copying unrequested text into the answer. Omit the address from `list_strings` to retrieve the complete inventory as Evidence; normal session retention and bundle export apply, including transport-capacity errors for oversized inline replies. These operations preserve their complete-result contracts and do not add paging. Function-referenced string facets in `analyze_function` remain unavailable; call `xrefs` explicitly when references to one returned string are needed.
|
||||||
|
|
||||||
|
## Calls and references
|
||||||
|
|
||||||
|
The first call query runs `kuna decompile-graph --functions ''`, admitting no C or assembly bodies. One validated graph and forward/reverse indexes serve later call queries and existing `get_call_graph` / `trace_call_path` workflows. Numeric u64 addresses are parsed from their original JSON tokens. Only `call` edges become callers/callees; jumps and data references retain their distinct meaning. Graph counts, edge endpoints and ordering are validated before any relationship is returned. A forwarding address may be an instruction inside a function, so it is retained separately and never promoted to a function node or call edge. Reported indirect calls make affected relationship coverage partial; no target is invented for them.
|
||||||
|
|
||||||
|
`xrefs` runs native `xrefs --to ADDRESS --json` on demand. Kuna unifies forwarding aliases and suppresses edges internal to their class; REA filters to the requested address and records the excluded-row count. Duplicate source sites appear once. An empty result cannot establish the absence of references. Addresses are explicit u64 values or exact, unambiguous procedure names; Kuna has no cursor.
|
||||||
|
|
||||||
|
Graph JSON admits at most 32 MiB before parsing; xref JSON at most 16 MiB. A large image can exceed the graph budget: an arm64 Firefox `XUL` (404k functions) does, while a 14 MB Sublime Text binary produced 31.8 MB. Output beyond a buffer is reported as a `memory` resource constraint with `retryable: false`, because the same target and executable produce the same output. The session remembers a graph that overflowed, so later call queries fail at once instead of repeating the whole-program walk. The graph is cached for the selected target and executable profile within one session. Xref reuse is an LRU charged by source bytes plus metadata, limited to 16 MiB and 128 entries; at most eight distinct native xref queries may be pending. Reference commands run one at a time per session and share the provider's foreground budget with single-function decompiles. An unproven process stop retains its admission slots until cleanup succeeds. Concurrent callers share work, individual cancellation releases only that waiter, and the last departing waiter cancels its producer and receives any unproven cleanup. Session close aborts and drains producers, releases parsed caches and retries any retained process/file cleanup.
|
||||||
|
|
||||||
|
Evidence includes only selected addresses or dossier neighbors, the producing JSON's SHA-256/byte count, coverage and relevant function facts; unrequested program-wide JSON and instruction text are omitted. Reference caches are in memory and expire on close: a fresh CLI process or MCP server rebuilds them. A first call query therefore costs a whole-program reference walk even when a C export is warm. These operations do not establish full Ghidra/IDA feature parity.
|
||||||
|
|
||||||
|
## Provenance and limitations
|
||||||
|
|
||||||
|
- Addresses and bytes are Kuna's observations of the image. Procedure entries and extents are its derivations: extents are upper bounds clipped at the next entry, and Kuna can split one function at an entry its own analysis infers. Pseudocode is its reconstruction, never original source.
|
||||||
|
- Pseudocode and assembly are exact Kuna text; text from Kuna and from another provider is not comparable as equal or different.
|
||||||
|
- Results are re-read rather than replayed from REA's analysis snapshots, because a cold answer can differ from the export's.
|
||||||
|
|
||||||
|
## Cache ownership
|
||||||
|
|
||||||
|
REA writes Kuna's exports only inside `REA_KUNA_CACHE_DIR`:
|
||||||
|
|
||||||
|
- `projects/<key>/` holds published, immutable exports with a manifest of file digests.
|
||||||
|
- `staging/` holds builds whose processes may still be live.
|
||||||
|
- `trash/` holds retired builds and evicted exports awaiting removal.
|
||||||
|
- `home/` is Kuna's HOME.
|
||||||
|
|
||||||
|
Builds publish by one rename, so a reader sees a complete export or none. A session keeps the export it uses from eviction only within its own REA process. If another REA process sharing the cache evicts it, the session rebuilds it; meanwhile pseudocode falls back to single-function runs. A damaged or edited export is removed and rebuilt. Staging moves to `trash/` for removal only after REA proves its Kuna process stopped. Staging of a process it cannot prove stopped is never swept; REA keeps it owned, retries the stop on `close_binary`, and reports it until the stop is proven. Failed file removal also stays bound to its export and is retried by `close_binary`; closing does not report success while that cleanup remains. `rea doctor --provider kuna` reports the cache directory without creating it.
|
||||||
|
|
||||||
|
## Verify with a real Kuna
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REA_KUNA_COMMAND=/absolute/path/to/kuna npm run verify:kuna
|
||||||
|
# Compare with Ghidra on caller-built binaries
|
||||||
|
REA_KUNA_COMMAND=/absolute/path/to/kuna GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra \
|
||||||
|
npm run verify:kuna -- --targets DIR --functions inflate,crc32 --compare-ghidra
|
||||||
|
```
|
||||||
|
|
||||||
|
The lane compiles a small source-owned fixture with the host C compiler and verifies the MCP workflow end to end. `--targets DIR --functions a,b` adds caller-built binaries. `--compare-ghidra` (with `GHIDRA_INSTALL_DIR`) runs those same functions through Ghidra; it requires `--targets` and `--functions` and refuses empty matrices or requested function names missing from every target. See [testing](testing.md#kuna-lane) for coverage.
|
||||||
|
|
||||||
|
Failed runs also report process-lineage evidence and exit unsuccessfully. If provider or transport cleanup is unconfirmed, the report says `cleanup: "incomplete"` and records `retained_workspace`; that directory is preserved for investigation and cleanup retries.
|
||||||
+148
-23
@@ -81,6 +81,40 @@ including the existing `inspect-analysis-view` CLI. Session record lifetime and
|
|||||||
export/import semantics remain unchanged. A server without Evidence retention
|
export/import semantics remain unchanged. A server without Evidence retention
|
||||||
rejects selected summary before analysis; complete delivery remains available.
|
rejects selected summary before analysis; complete delivery remains available.
|
||||||
|
|
||||||
|
### Member summary and retained member rows
|
||||||
|
|
||||||
|
Complete `inspect_managed_members` delivery always includes the assembly-wide
|
||||||
|
`member_refs` and `exported_types`, which `type_names` does not narrow, so even
|
||||||
|
one selected or unmatched type can return megabytes. Select
|
||||||
|
`{"path":"/absolute/path/Assembly.dll","type_names":["Namespace.Type"],"detail":"summary"}`
|
||||||
|
to keep the complete source in the session and receive identity, metadata
|
||||||
|
status, the selection with its unmatched names, coverage/issues, the separate
|
||||||
|
user-string coverage, limitations and per-collection counts. Then page original
|
||||||
|
rows with view kind `managed-members`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": {
|
||||||
|
"kind": "retained-evidence",
|
||||||
|
"evidence_id": "<parent_evidence_id>"
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"kind": "managed-members",
|
||||||
|
"collection": "call_edges",
|
||||||
|
"offset": 0,
|
||||||
|
"limit": 64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Collections are `types`, `exported_types`, `fields`, `methods`, `member_refs`,
|
||||||
|
`call_edges`, `field_accesses` and `user_strings` (the literals). Each page names
|
||||||
|
its collection, retains original rows, order, tokens, signatures and body facts,
|
||||||
|
and carries the source selection and coverage; `user_strings` pages carry the
|
||||||
|
literal pool's own coverage. Older Evidence that never examined exported types
|
||||||
|
or user strings reports a null count and refuses those pages rather than
|
||||||
|
returning an empty one.
|
||||||
|
|
||||||
## Shipped scope
|
## Shipped scope
|
||||||
|
|
||||||
The canonical parser admits PE/CLI bytes and their metadata/CIL without loading
|
The canonical parser admits PE/CLI bytes and their metadata/CIL without loading
|
||||||
@@ -133,20 +167,53 @@ MCP uses `inspect_managed_members` with
|
|||||||
Repeat `--type` or supply multiple unique `type_names` for several types.
|
Repeat `--type` or supply multiple unique `type_names` for several types.
|
||||||
Names match the reported `full_name` exactly. Top-level names are namespace
|
Names match the reported `full_name` exactly. Top-level names are namespace
|
||||||
qualified; nested types currently report bare names, which can select several
|
qualified; nested types currently report bare names, which can select several
|
||||||
types with that name.
|
types with that name. Each type's `enclosing_type_token` records its enclosing
|
||||||
|
TypeDef. `nesting_read` distinguishes complete source rows (true), incomplete
|
||||||
|
current rows (false), and historical Evidence (null). A null enclosing token
|
||||||
|
alone does not establish that a type is top-level.
|
||||||
|
Each selected type also retains `name_sha256`, a framed digest of its namespace,
|
||||||
|
name and enclosing names. It uses the same bounded, cached owner as signature
|
||||||
|
type operands and remains available when selection omits the enclosing types.
|
||||||
|
It adds one fixed-size digest per returned type rather than repeated name paths.
|
||||||
|
|
||||||
The result preserves the requested names in `selection.type_names` and reports
|
The result preserves the requested names in `selection.type_names` and reports
|
||||||
unmatched names separately. If metadata names are unreadable, limitations retain
|
unmatched names separately. If metadata names are unreadable, limitations retain
|
||||||
that uncertainty. With no selection, `selection` is null and whole-assembly
|
that uncertainty. With no selection, `selection` is null and whole-assembly
|
||||||
inspection keeps its existing resource policy. Selection restricts types,
|
inspection keeps its existing resource policy. Selection restricts types,
|
||||||
fields, methods, call edges and field accesses; member references remain an
|
fields, methods, call edges, field accesses and user strings; member references
|
||||||
assembly-wide inventory, so output size is not guaranteed to be small.
|
remain an assembly-wide inventory, so output size is not guaranteed to be small.
|
||||||
|
|
||||||
Coverage describes the selected members. Comparisons leave members outside the
|
Coverage describes the selected members. Comparisons leave members outside the
|
||||||
opposite observation's selection unknown, and application graphs report their
|
opposite observation's selection unknown, and application graphs report their
|
||||||
selected scope as partial. Neither makes an absence claim about the whole
|
selected scope as partial. Neither makes an absence claim about the whole
|
||||||
assembly. Older member Evidence without `selection` remains readable.
|
assembly. Older member Evidence without `selection` remains readable.
|
||||||
|
|
||||||
|
### User strings
|
||||||
|
|
||||||
|
`ldstr` anchors keep their build-local `0x70` tokens. `user_strings.literals`
|
||||||
|
lists each distinct token loaded by an inspected method body once, in token
|
||||||
|
order, with its `#US` file offset and decoded value. Values keep their stored
|
||||||
|
UTF-16 code units, including unpaired surrogates. Token `0x70000000` names the
|
||||||
|
heap's empty entry at offset 0 and reads as `""`; any other item there is
|
||||||
|
malformed. The terminal flag is checked against ECMA-335 II.24.2.4, and the
|
||||||
|
length prefix against the width II.23.2 assigns. A difference is reported with
|
||||||
|
its byte offset as a user-string issue while preserving the readable literal.
|
||||||
|
Toolchain conventions can differ; this conformance observation does not
|
||||||
|
establish runtime rejection. A missing `#US` stream is one stream-level issue;
|
||||||
|
a malformed item is reported against its token. User strings carry their own
|
||||||
|
coverage: any of these makes `user_strings.state` partial and lists the issue
|
||||||
|
in `user_strings.issues`, while the member `coverage` and `metadata.status` are
|
||||||
|
unaffected, and readable literals remain. The state is also partial when an
|
||||||
|
inspected method's CIL was not fully decoded (a malformed exception section
|
||||||
|
alone does not count) or a method that may belong to the inspected types was
|
||||||
|
not inspected (its row could not be read, or its type's name could not be read
|
||||||
|
under a `type_names` selection), because `ldstr` instructions there were not
|
||||||
|
observed. Normalized CIL digests do not include decoded values.
|
||||||
|
`compare_managed_members` does not decode or compare literals: an unchanged
|
||||||
|
method does not establish unchanged strings, and the comparison says so in its
|
||||||
|
limitations. Older member Evidence without `user_strings` reads as `null`: not
|
||||||
|
examined.
|
||||||
|
|
||||||
### Edge target declaring types
|
### Edge target declaring types
|
||||||
|
|
||||||
`member_refs[].parent_name`, `call_edges[].target_declaring_type`, and
|
`member_refs[].parent_name`, `call_edges[].target_declaring_type`, and
|
||||||
@@ -183,21 +250,74 @@ native/runtime questions retain their own authority and coverage.
|
|||||||
Names and semantic labels do not establish durable member identity. Comparison
|
Names and semantic labels do not establish durable member identity. Comparison
|
||||||
preserves observed differences, structural inferences, and unknowns separately.
|
preserves observed differences, structural inferences, and unknowns separately.
|
||||||
|
|
||||||
Cross-version matching first prefers exact CIL/signature identity. When that
|
Cross-version matching first pairs members of the same declared type and name.
|
||||||
does not match, it pairs an exact declared type, method name, and raw signature
|
A current declared type uses its observed `name_sha256`, so same-named nested
|
||||||
before trying structural body shape. The exact-signature key uses names only
|
types in different enclosing types stay distinct even in selected inventories.
|
||||||
as part of that full tuple; names alone never select a pair. Duplicate tuples
|
Malformed, missing, contradictory, cyclic or over-deep enclosing identity remains unknown;
|
||||||
remain ambiguous rather than being paired by token order. Fields follow the
|
the comparison preserves display names without using them to repair that gap.
|
||||||
same exact tiers. The raw signature is exact whether or not REA decoded it, so
|
Historical observations retain an explicitly limited bare-name mode when either
|
||||||
undecoded signatures pair by that tuple too, but they never enter structural
|
side predates the identity facts. A known current identity gap still remains
|
||||||
rounds. An unmatched member is therefore reported as `unknown`, not added or
|
unknown beside a historical observation. Matching then pairs
|
||||||
removed, when its own signature was not decoded or when the other side has an
|
methods by exact CIL/signature identity, then by signature, then by
|
||||||
unpaired member, one-sided or ambiguous, with the same declared type and name
|
structural body shape; fields by signature. Exact CIL/signature identity
|
||||||
whose signature was not decoded.
|
requires equal raw CIL digests and decoded fingerprints. Equal decoded
|
||||||
|
fingerprints alone cannot establish an exact match: they omit switch targets
|
||||||
|
and collapse distinct NaN encodings. Only then may a remap pair a
|
||||||
|
renamed or moved member across declared types or names: a method by exact
|
||||||
|
CIL/signature identity or structural body shape, a field by exact decoded
|
||||||
|
signature. A remap never takes a member whose own declared type and name remain
|
||||||
|
unpaired on the other side, so a member is never taken from its own changed
|
||||||
|
counterpart. A member whose declared identity is unresolved could belong to any type. It
|
||||||
|
never makes a declared-identity pair, and any remap group it joins stays
|
||||||
|
ambiguous. Unresolved same-named counterparts prevent definitive added/removed
|
||||||
|
claims, while observed names and tokens remain available in the result.
|
||||||
|
The exact-signature key uses names only as part of that
|
||||||
|
full tuple; names alone never select a pair. Duplicate tuples remain ambiguous
|
||||||
|
rather than being paired by token order. Every round compares signatures by
|
||||||
|
`resolved_sha256`: the raw signature with each TypeDef or TypeRef operand
|
||||||
|
replaced by a digest of that type's namespace and name, hashed as separate
|
||||||
|
values so `A` + `B.C` differs from `A.B` + `C`, after its enclosing types for a
|
||||||
|
nested type (`Outer/Inner`). The defining assembly is not part of the name:
|
||||||
|
a TypeDef and a TypeRef with the same namespace and name compare as the
|
||||||
|
same nominal name, and a TypeRef's ResolutionScope assembly is ignored, so a type
|
||||||
|
forwarded from one assembly to another still pairs. Two different types with
|
||||||
|
the same full name in different assemblies therefore have equal name digests.
|
||||||
|
Comparison reports this scope limitation inline; it does not establish CLR
|
||||||
|
type identity or runtime binding equivalence.
|
||||||
|
Nesting deeper than 64 levels is not named, and such a signature compares by
|
||||||
|
its raw digest. Comparison keys keep resolved and raw digests apart, so a raw
|
||||||
|
digest never pairs with an equal resolved one.
|
||||||
|
Raw signature bytes name types by metadata row, and rows are build-local, so
|
||||||
|
the same signature usually has different raw bytes in two builds. A signature
|
||||||
|
that names no types resolves to its raw digest. When an operand cannot be
|
||||||
|
resolved, such as a TypeSpec, or when REA could not parse the signature, the
|
||||||
|
raw digest stands in. Missing or unreadable TypeDef and TypeRef operands are
|
||||||
|
reported as coverage issues. Invalid NestedClass rows retain their failure
|
||||||
|
reasons and withhold TypeDef signature identities; type names and nesting are
|
||||||
|
parsed once by their shared owner. Each current inspection records
|
||||||
|
`signature_resolution: "type-names-v1"`, even when none of its operands can be
|
||||||
|
resolved. Historical Evidence defaults this attestation to `null`. When either
|
||||||
|
observation predates resolution, both sides compare raw digests, which pair only where type rows agree,
|
||||||
|
and the comparison reports that limitation. The signature is exact whether or
|
||||||
|
not REA decoded it, so undecoded signatures pair by that tuple too, but they
|
||||||
|
never enter structural rounds. An unmatched member is therefore reported as
|
||||||
|
`unknown`, not added or removed, when its own signature was not decoded or when
|
||||||
|
the other side has an unpaired member, one-sided or ambiguous, with the same
|
||||||
|
declared type and name whose signature was not decoded. When both observations
|
||||||
|
attest type-name resolution, a decoded signature that still compares by its raw
|
||||||
|
digest is treated the same way: an unmatched member with one, or beside an
|
||||||
|
unpaired same-named member with one, is `unknown`, because its raw bytes name
|
||||||
|
types by build-local row. A matched unresolved signature also remains unknown
|
||||||
|
unless another observed dimension establishes a change; identical row bytes
|
||||||
|
can reference different TypeSpec contents. Its resolution limitation is retained
|
||||||
|
even when another dimension changes.
|
||||||
|
|
||||||
For a matched method, an unavailable or partial body makes body-shape facets
|
For a matched method, an unavailable or partial body makes body-shape facets
|
||||||
unknown while preserving observed signature differences. Structural identity
|
unknown while preserving observed signature differences. For complete bodies,
|
||||||
compares normalized signatures, the limited decoded-CIL tuple fingerprint,
|
a difference in either the raw CIL digest or the decoded fingerprint is reported
|
||||||
|
in the `cil` dimension, including reordered switch targets with equal decoded
|
||||||
|
fingerprints. Structural identity compares resolved signatures, the limited
|
||||||
|
decoded-CIL tuple fingerprint,
|
||||||
constants, and bounded shape context. It reports all candidates at the winning
|
constants, and bounded shape context. It reports all candidates at the winning
|
||||||
score and remains ambiguous when the evidence does not distinguish them. The
|
score and remains ambiguous when the evidence does not distinguish them. The
|
||||||
digest does not itself remap metadata tokens.
|
digest does not itself remap metadata tokens.
|
||||||
@@ -232,9 +352,10 @@ install it or its toolchain. The [NativeAOT guide](ghidra-nativeaot.md) defines
|
|||||||
these routes, their layout/host restrictions, derived-memory provenance, and
|
these routes, their layout/host restrictions, derived-memory provenance, and
|
||||||
verification coverage.
|
verification coverage.
|
||||||
|
|
||||||
REA's experimental Windows Ghidra boundary admits native x86 and x86-64 PE
|
REA's experimental Windows Ghidra boundary admits native x86, x86-64, ARMNT (Thumb-2) and ARM64 PE
|
||||||
applications and DLLs for static analysis. This does not establish Windows
|
applications and DLLs for static analysis. For x64 PE NativeAOT, the read-only
|
||||||
NativeAOT metadata-recovery coverage.
|
parser also runs there; the [NativeAOT guide](ghidra-nativeaot.md) lists what a
|
||||||
|
Windows host covers.
|
||||||
|
|
||||||
For NativeAOT analysis, preserve the selected native artifact's path and digest
|
For NativeAOT analysis, preserve the selected native artifact's path and digest
|
||||||
and report recovered type data with its returned provenance. Linking those types
|
and report recovered type data with its returned provenance. Linking those types
|
||||||
@@ -249,6 +370,9 @@ function implements a specific managed method.
|
|||||||
`verify_managed_native_boundaries` compares declared P/Invoke module and entry
|
`verify_managed_native_boundaries` compares declared P/Invoke module and entry
|
||||||
point identities with supplied native export or function evidence. Name/module
|
point identities with supplied native export or function evidence. Name/module
|
||||||
matches establish declaration links; they do not establish that a call executed.
|
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.
|
||||||
|
|
||||||
Metadata tokens are not native addresses. Native body mapping for C++/CLI,
|
Metadata tokens are not native addresses. Native body mapping for C++/CLI,
|
||||||
ReadyToRun, NativeAOT, and IL2CPP requires separately verified format and provider
|
ReadyToRun, NativeAOT, and IL2CPP requires separately verified format and provider
|
||||||
@@ -286,8 +410,9 @@ these indirections for member ownership and local attribute-constructor types;
|
|||||||
field and method tokens still identify the original definition rows.
|
field and method tokens still identify the original definition rows.
|
||||||
`field_list` and `method_list` retain the metadata's list coordinates, which
|
`field_list` and `method_list` retain the metadata's list coordinates, which
|
||||||
refer to the pointer table when present rather than necessarily contiguous
|
refer to the pointer table when present rather than necessarily contiguous
|
||||||
definition rows. Malformed or ambiguous pointers leave the owner unknown with
|
definition rows. Invalid or nonmonotonic list coordinates and malformed or
|
||||||
partial coverage while preserving readable member and attribute facts.
|
ambiguous pointers leave affected owners unknown with partial coverage while
|
||||||
|
preserving readable member and attribute facts.
|
||||||
The retained inverse indexes are admitted against the existing representation
|
The retained inverse indexes are admitted against the existing representation
|
||||||
budget before allocation.
|
budget before allocation.
|
||||||
|
|
||||||
@@ -333,8 +458,8 @@ Opcode names retain short/long forms and prefix opcodes. This makes the value a
|
|||||||
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
|
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
|
||||||
it does not resolve tokens across MVIDs, does not commit full control flow or
|
it does not resolve tokens across MVIDs, does not commit full control flow or
|
||||||
exception semantics, and does not replace the raw CIL digest. Cross-build
|
exception semantics, and does not replace the raw CIL digest. Cross-build
|
||||||
comparison uses the value only as one unique-only structural tier and reports
|
comparison requires equal raw CIL digests as well for exact CIL/signature
|
||||||
ambiguity when that evidence is insufficient.
|
matches and reports ambiguity when structural evidence is insufficient.
|
||||||
|
|
||||||
For example, a decoded `nop; ret` body serializes exactly as
|
For example, a decoded `nop; ret` body serializes exactly as
|
||||||
`[["nop","none",null],["ret","none",null]]` and has digest
|
`[["nop","none",null],["ret","none",null]]` and has digest
|
||||||
|
|||||||
+153
-26
@@ -21,6 +21,48 @@ directly to `compare_functions`, and `inspect_artifact` Evidence to
|
|||||||
`compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
|
`compare_artifacts`. Use `get_evidence_bundle` when the task needs broader
|
||||||
retained session history or an explicit bundle for transfer.
|
retained session history or an explicit bundle for transfer.
|
||||||
|
|
||||||
|
Artifact inventories declare `coverage.scope` (`root` or `container-entries`)
|
||||||
|
and `coverage.status` (`complete` or `unknown`) independently of diagnostic
|
||||||
|
wording. Comparison requires matching scopes, complete graph members and complete
|
||||||
|
traversal coverage before concluding equality. A fully observed root without
|
||||||
|
container children can be `unchanged`; its scope notice stays in `limitations`
|
||||||
|
without creating a gap. Content and executable metadata changes still produce
|
||||||
|
root deltas. Missing graph members yield `truncated`; unknown traversal coverage
|
||||||
|
or unavailable occurrence hashes retain uncertainty. Integrity mismatches remain
|
||||||
|
contradictions. Static artifact equality does not establish unchanged runtime
|
||||||
|
behavior.
|
||||||
|
|
||||||
|
Exhausted traversals with deliberately omitted filesystem entries retain
|
||||||
|
`unknown` coverage and identify each omitted path/type in `limitations`; FIFO,
|
||||||
|
socket and device entries are not opened. Case-distinct archive members remain
|
||||||
|
separate observations. Their case-insensitive materialization diagnostic does
|
||||||
|
not reduce traversal coverage when both spellings were read.
|
||||||
|
|
||||||
|
Current inventory Evidence requires scoped `coverage` with a `complete` or
|
||||||
|
`unknown` status. Consumers use this producer-owned fact and reject records
|
||||||
|
without it; limitations are explanatory and do not supply or infer coverage.
|
||||||
|
Pages from one inventory must carry the same coverage declaration.
|
||||||
|
|
||||||
|
Function dossiers can declare per-facet coverage in the optional
|
||||||
|
`normalized_result.facet_availability` map. Each supplied facet is either
|
||||||
|
`{ "available": true }` or `{ "available": false, "reason": "..." }`.
|
||||||
|
Supported keys are `pseudocode`, `assembly`, `comments`, `callers`, `callees`,
|
||||||
|
`references`, `strings_names`, and `cfg`. Availability describes whether that
|
||||||
|
facet was observed, not whether its collection contains any entries.
|
||||||
|
|
||||||
|
`compare_functions` keeps a dimension `unknown` with null counts when either
|
||||||
|
side declares a required facet unavailable. An empty legacy facet without a
|
||||||
|
coverage declaration is also unknown. A producer that actually observed an
|
||||||
|
empty facet can declare it available so observed absence remains comparable.
|
||||||
|
Call comparison additionally retains the Evidence relationship coverage
|
||||||
|
checks. Ghidra and Hopper declare their observed dossier facets and complete
|
||||||
|
provider-observed caller/callee sets, including empty sets. Missing decompiler
|
||||||
|
output remains unavailable; complete call coverage does not claim every runtime
|
||||||
|
target. Hopper's unavailable reference-kind metadata and reported unresolved
|
||||||
|
calls still prevent equality conclusions for those dimensions. Analysis profiles
|
||||||
|
version these declarations so older snapshots are not reused as current results.
|
||||||
|
IDA declares the facets its selected adapter cannot provide; an empty
|
||||||
|
unsupported array does not prove observed absence.
|
||||||
Evidence and bundle imports reject unsupported fields in their envelopes,
|
Evidence and bundle imports reject unsupported fields in their envelopes,
|
||||||
identities, manifests and other fixed metadata before authentication. Imports
|
identities, manifests and other fixed metadata before authentication. Imports
|
||||||
leave retained session state unchanged when validation fails. Keep application
|
leave retained session state unchanged when validation fails. Keep application
|
||||||
@@ -51,7 +93,7 @@ unresolved relationship reads. Cancellation remains a tool error.
|
|||||||
REA prepares complete MCP results within the pinned stdio client's 10 MiB
|
REA prepares complete MCP results within the pinned stdio client's 10 MiB
|
||||||
receive-buffer budget, including both text and structured representations and
|
receive-buffer budget, including both text and structured representations and
|
||||||
room for the JSON-RPC envelope. Tool errors carry only their text
|
room for the JSON-RPC envelope. Tool errors carry only their text
|
||||||
representation, so only that text counts against the budget. Response budget settings are captured at startup;
|
representation and `isError` flag, so only those count against the budget. Response budget settings are captured at startup;
|
||||||
restart or recreate the server to apply changes. If a result cannot fit, REA returns
|
restart or recreate the server to apply changes. If a result cannot fit, REA returns
|
||||||
`resource_constraint` with `details.resource: "transport"` before constructing
|
`resource_constraint` with `details.resource: "transport"` before constructing
|
||||||
a document-sized string. Analysis Evidence remains complete in the current
|
a document-sized string. Analysis Evidence remains complete in the current
|
||||||
@@ -148,7 +190,8 @@ after target content was read.
|
|||||||
## Retained application Evidence inputs
|
## Retained application Evidence inputs
|
||||||
|
|
||||||
`analyze_javascript_application`, `inspect_binary_layout`, `inspect_artifact`,
|
`analyze_javascript_application`, `inspect_binary_layout`, `inspect_artifact`,
|
||||||
`inspect_wasm_artifact`, and `inspect_macho` accept `detail`:
|
`inspect_wasm_artifact`, `inspect_macho`, and `decode_interface_builder`
|
||||||
|
accept `detail`:
|
||||||
`complete` (default) returns the complete analysis Evidence; `summary` records
|
`complete` (default) returns the complete analysis Evidence; `summary` records
|
||||||
that complete Evidence in the session and returns the derived
|
that complete Evidence in the session and returns the derived
|
||||||
`inspect_analysis_view` summary Evidence instead. Its
|
`inspect_analysis_view` summary Evidence instead. Its
|
||||||
@@ -231,6 +274,17 @@ symbols (Electron Framework: 2.5 MB of imports and exports, 3.3 KB as a
|
|||||||
summary), so select `detail: "summary"` on the first call. Delivery detail
|
summary), so select `detail: "summary"` on the first call. Delivery detail
|
||||||
never reaches the native tools or changes the source Evidence ID.
|
never reaches the native tools or changes the source Evidence ID.
|
||||||
|
|
||||||
|
Completed `decode_interface_builder` Evidence supports a summary and
|
||||||
|
`view: {kind: "interface-builder", collection: "nodes", offset: 0, limit: 64}`.
|
||||||
|
Collections are `documents`, `nodes`, `edges`, and `coverage`; for nodes and
|
||||||
|
edges an optional `document` keeps rows whose evidence names that exact decoded
|
||||||
|
`relative_path`, and an undecoded path is refused. The summary keeps every
|
||||||
|
decoded document, the provider, node-kind, edge-relation and coverage-status
|
||||||
|
counts, and whether the decoded graph is incomplete. Each page repeats
|
||||||
|
`source_truncated`. A complete app graph often exceeds the default MCP response
|
||||||
|
budget (XLD.app: 6.7 MB), so select `detail: "summary"` on the first call.
|
||||||
|
Delivery detail never reaches the decoder or changes the source Evidence ID.
|
||||||
|
|
||||||
`trace_application_feature`, `trace_javascript_semantics`,
|
`trace_application_feature`, `trace_javascript_semantics`,
|
||||||
`compare_application_versions`, `compare_source_to_bundle`, and
|
`compare_application_versions`, `compare_source_to_bundle`, and
|
||||||
`compare_javascript_export_shapes` accept complete inline application Evidence
|
`compare_javascript_export_shapes` accept complete inline application Evidence
|
||||||
@@ -257,9 +311,11 @@ Evidence remain complete inline, and both input forms pass the same semantic,
|
|||||||
identity, authority, and provenance checks without running the producer again.
|
identity, authority, and provenance checks without running the producer again.
|
||||||
|
|
||||||
References belong to the current connection's Evidence ledger. Opening another
|
References belong to the current connection's Evidence ledger. Opening another
|
||||||
target preserves retained records; `close_binary` clears them, even when no
|
target or restarting the current target preserves retained records, including
|
||||||
binary is active. A fresh connection has its own ledger. A missing reference
|
when provider cleanup fails; records remain available for export and retry.
|
||||||
reports its exact ID and `details.reason: "missing"`; the server cannot infer
|
`close_binary` clears them, even when no binary is active. A fresh connection
|
||||||
|
has its own ledger. A missing reference reports its exact ID and
|
||||||
|
`details.reason: "missing"`; the server cannot infer
|
||||||
whether it was never recorded, cleared, or retained by another connection.
|
whether it was never recorded, cleared, or retained by another connection.
|
||||||
Supply complete inline Evidence, repeat its producer, or import an exported
|
Supply complete inline Evidence, repeat its producer, or import an exported
|
||||||
Evidence bundle before referencing that imported record. Export a bundle before
|
Evidence bundle before referencing that imported record. Export a bundle before
|
||||||
@@ -441,7 +497,10 @@ declared by their tools. Artifact extraction materializes the selected regular
|
|||||||
files into a fresh REA-chosen temporary directory and reports its path.
|
files into a fresh REA-chosen temporary directory and reports its path.
|
||||||
|
|
||||||
`analyze_javascript_application` reads the supplied local directory or ASAR path
|
`analyze_javascript_application` reads the supplied local directory or ASAR path
|
||||||
and returns its result and Evidence inline.
|
and returns its result and Evidence inline. Its owned analysis worker and private
|
||||||
|
temporary source files are declared as `launchesProcess` and `writesFilesystem`,
|
||||||
|
including for a single JavaScript source. This internal analysis does not execute
|
||||||
|
the supplied application code.
|
||||||
|
|
||||||
## Ghidra first-query deadlines and recovery
|
## Ghidra first-query deadlines and recovery
|
||||||
|
|
||||||
@@ -458,8 +517,17 @@ These deadlines have different owners:
|
|||||||
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
|
| MCP initialize | The client bounds transport/REA connection startup, before any target query. |
|
||||||
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
|
| Hopper startup | REA allows 120,000 ms by default; `REA_HOPPER_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. Startup failure is reported by the first provider query. |
|
||||||
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
|
| Ghidra startup | REA allows 330,000 ms by default; `REA_GHIDRA_STARTUP_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid supplied values fail configuration validation. Startup failure is reported by the first provider query. |
|
||||||
|
| Darwin swiftc helper | REA allows 120,000 ms by default for ownership-helper compilation; `REA_SWIFTC_COMPILE_TIMEOUT_MS` accepts decimal integers from 1–2,147,483,647 ms. Invalid values fail configuration validation. |
|
||||||
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
|
| Individual tool call | The client bounds its wait, including cold engine startup. The pinned client SDK 2.3.1 defaults to 60,000 ms and can cancel earlier than REA's startup deadline. |
|
||||||
|
|
||||||
|
For [Codex and Kimi registrations](client-configuration.md), setup supplies
|
||||||
|
missing tool deadlines from the known provider allowance plus 120 seconds of
|
||||||
|
operation headroom: 450 seconds with the default Ghidra allowance, or 2820
|
||||||
|
seconds with `REA_GHIDRA_STARTUP_TIMEOUT_MS=2700000`. Positive caller-selected
|
||||||
|
deadlines remain unchanged. Doctor reports an informational recommendation
|
||||||
|
when a configured deadline is shorter than the known provider allowance. An
|
||||||
|
explicit JEB upstream request budget also contributes to that allowance.
|
||||||
|
|
||||||
For an already connected client using the pinned SDK, request options are the
|
For an already connected client using the pinned SDK, request options are the
|
||||||
**second** argument of `callTool`:
|
**second** argument of `callTool`:
|
||||||
|
|
||||||
@@ -489,10 +557,20 @@ Waiting notifications stop when startup settles or the caller cancels. They do
|
|||||||
not measure engine work completed.
|
not measure engine work completed.
|
||||||
|
|
||||||
The pinned SDK can reset the request's inactivity timeout on progress with
|
The pinned SDK can reset the request's inactivity timeout on progress with
|
||||||
`resetTimeoutOnProgress: true`. Also set `maxTotalTimeout` to bound the complete
|
`resetTimeoutOnProgress: true`. These options belong in the same second
|
||||||
request; the provider's startup deadline still applies. These options belong in
|
argument of `callTool`. Without that explicit client policy, the default
|
||||||
the same second argument of `callTool`. Without that explicit client policy, the
|
60-second request deadline still expires despite waiting notifications.
|
||||||
default 60-second request deadline still expires despite waiting notifications.
|
|
||||||
|
SDK 2.3.1 checks `maxTotalTimeout` when progress resets the timer; it does not
|
||||||
|
schedule a separate absolute timer for that setting. A silent interval can
|
||||||
|
therefore run beyond the declared total. For an absolute caller deadline,
|
||||||
|
also pass `signal: AbortSignal.timeout(totalBudgetMs)`. If the caller already
|
||||||
|
has a cancellation signal, preserve both with
|
||||||
|
`AbortSignal.any([callerSignal, AbortSignal.timeout(totalBudgetMs)])`.
|
||||||
|
`maxTotalTimeout` can still bound progress resets, and REA's provider startup
|
||||||
|
deadline still applies. Choose the total budget for the expected workload;
|
||||||
|
neither server configuration nor progress can override a remote caller's
|
||||||
|
deadline.
|
||||||
|
|
||||||
The 240-second setting is a measured example, not a universal timeout: a
|
The 240-second setting is a measured example, not a universal timeout: a
|
||||||
controlled Linux x64/WSL2 fixture took about 64 seconds for its first overview
|
controlled Linux x64/WSL2 fixture took about 64 seconds for its first overview
|
||||||
@@ -588,8 +666,12 @@ failures for such entries name this cause and the operation to rerun with
|
|||||||
|
|
||||||
Advertised input and output schemas contain no reachable recursive references.
|
Advertised input and output schemas contain no reachable recursive references.
|
||||||
Schemas share repeated definitions through schema-local references while
|
Schemas share repeated definitions through schema-local references while
|
||||||
preserving complete nested fields and validation rules. Input properties retain
|
preserving complete nested fields and validation rules. Input schemas whose
|
||||||
their descriptions and literal examples.
|
shared definitions would exhaust a client's declaration-rendering reference
|
||||||
|
budget (Pi 1.1.0 code-mode shares 32 reference expansions across an input and
|
||||||
|
renders every later reference as `unknown`) are re-projected without shared
|
||||||
|
definitions, so every advertised field renders with complete structure.
|
||||||
|
Input properties retain their descriptions and literal examples.
|
||||||
|
|
||||||
When a host forwards input schemas to the model, their examples consume context.
|
When a host forwards input schemas to the model, their examples consume context.
|
||||||
REA's advertised examples therefore never copy complete producer Evidence.
|
REA's advertised examples therefore never copy complete producer Evidence.
|
||||||
@@ -600,10 +682,14 @@ advertised. Every advertised example is a distinct valid canonical input.
|
|||||||
|
|
||||||
Root object unions are presented as
|
Root object unions are presented as
|
||||||
one object without root `anyOf`, `oneOf`, or `allOf` for model API compatibility.
|
one object without root `anyOf`, `oneOf`, or `allOf` for model API compatibility.
|
||||||
The projection merges properties, retains shared required fields and
|
The projection merges properties, retains shared required fields, and
|
||||||
group-specific field dependencies, and describes the accepted input groups.
|
describes the accepted input groups. Which fields belong together is guidance
|
||||||
Canonical runtime validation enforces the exact union, including exclusions
|
in that description rather than a `dependentRequired` or `dependencies`
|
||||||
that the advertised projection does not express. Tests validate the schemas
|
keyword, because Gemini function declarations reject both and one unknown
|
||||||
|
keyword fails the client's whole tool catalog. Canonical runtime validation
|
||||||
|
enforces the exact union, including partner fields and exclusions that the
|
||||||
|
advertised projection does not express, so a client must not rely on the
|
||||||
|
advertised schema to reject an incomplete group. Tests validate the schemas
|
||||||
after SDK conversion and check advertised validation and actual calls.
|
after SDK conversion and check advertised validation and actual calls.
|
||||||
Individual model APIs can impose additional nesting limits; complete producer
|
Individual model APIs can impose additional nesting limits; complete producer
|
||||||
captures can exceed ten structural levels.
|
captures can exceed ten structural levels.
|
||||||
@@ -619,7 +705,16 @@ advertised input schema within a 13,312-byte budget, measured to sit below
|
|||||||
Moonshot's observed cap (community measurements: a 13.7 KB schema is accepted
|
Moonshot's observed cap (community measurements: a 13.7 KB schema is accepted
|
||||||
while a ~15 KB one is rejected). The setting is read at startup; restart the
|
while a ~15 KB one is rejected). The setting is read at startup; restart the
|
||||||
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. The default
|
server to apply changes, as with `REA_MCP_MAX_RESPONSE_BYTES`. The default
|
||||||
value `full` keeps the complete advertisement unchanged.
|
value `full` keeps the complete advertisement unchanged. Compact presentation
|
||||||
|
also reduces schemas whose expanded JSON tree exceeds ten levels, the limit
|
||||||
|
reported by strict-depth MCP clients in [#2813](https://github.com/morluto/rea/issues/2813).
|
||||||
|
WorkBuddy/CodeBuddy gateways have also rejected full REA catalogs with an
|
||||||
|
opaque `11133` error while accepting the compact profile
|
||||||
|
([#2999](https://github.com/morluto/rea/issues/2999)); compatibility depends on
|
||||||
|
the selected gateway and model. That report used ZCode through a gateway to a
|
||||||
|
Kimi-K3 backend, not the WorkBuddy MCP client. Set the profile on REA's
|
||||||
|
registration in the actual MCP host; the host's name alone does not identify
|
||||||
|
the downstream model or its schema constraints.
|
||||||
|
|
||||||
The compact profile renders the same tool set, output schemas, annotations,
|
The compact profile renders the same tool set, output schemas, annotations,
|
||||||
and canonical server-side validation. It changes only the advertised input
|
and canonical server-side validation. It changes only the advertised input
|
||||||
@@ -636,18 +731,20 @@ JSON Schema:
|
|||||||
objects or arrays.
|
objects or arrays.
|
||||||
- The few schemas whose validation structure alone exceeds the budget
|
- The few schemas whose validation structure alone exceeds the budget
|
||||||
(`compare_web_captures`, `build_reconstruction_obligation_ledger`,
|
(`compare_web_captures`, `build_reconstruction_obligation_ledger`,
|
||||||
`evaluate_reconstruction_coverage`, `project_managed_application_graph`,
|
`evaluate_reconstruction_coverage`,
|
||||||
`compare_application_versions`, `capture_browser_scenario`) are advertised
|
`compare_application_versions`, `capture_browser_scenario`) are advertised
|
||||||
in a reduced form: property names, reference-resolved types (including union
|
in a reduced form: property names, reference-resolved types (including union
|
||||||
alternatives), first-level guidance, and the root's required-field constraints,
|
alternatives), first-level guidance, and the root's required-field constraints,
|
||||||
with nested validation detail elided. The
|
with nested validation detail elided. The
|
||||||
canonical schema still rejects malformed calls server-side, so callers
|
registered canonical schema still rejects malformed calls before invoking a
|
||||||
following the reduced advertisement receive the canonical typed error
|
handler. That rejection can use the MCP SDK's input-validation diagnostic;
|
||||||
instead of silent acceptance.
|
it does not guarantee the application's typed input error.
|
||||||
|
- Schemas deeper than ten nodes use the same reduced projection, with
|
||||||
|
root-property names, types, guidance, and required fields retained. The
|
||||||
|
server continues to enforce the full canonical input contract.
|
||||||
|
|
||||||
Boundary tests pin every advertised compact schema to the budget, keep them
|
Boundary tests pin every advertised compact schema to the budget, keep them
|
||||||
valid JSON Schema that still accepts every canonical example, and hold the
|
valid JSON Schema that still accepts every canonical example. Client verification
|
||||||
set of six reduced presentations as contracts evolve. Client verification
|
|
||||||
also checks provider-facing sizes and canonical examples after conversion by
|
also checks provider-facing sizes and canonical examples after conversion by
|
||||||
the actual Kimi Code CLI; local captures do not establish Moonshot acceptance.
|
the actual Kimi Code CLI; local captures do not establish Moonshot acceptance.
|
||||||
|
|
||||||
@@ -660,6 +757,35 @@ identity. Explicit schema IDs and relative external reference bases are
|
|||||||
preserved. This keeps every tool and validation rule in discovery and does not
|
preserved. This keeps every tool and validation rule in discovery and does not
|
||||||
change the SDK's catalog invalidation or availability checks.
|
change the SDK's catalog invalidation or availability checks.
|
||||||
|
|
||||||
|
The server identity's tool-schema digest includes both the canonical output
|
||||||
|
contracts and the selected output-schema profile. `full` and `off` therefore
|
||||||
|
have distinct identities even though `off` keeps the same canonical output
|
||||||
|
validation on the server.
|
||||||
|
|
||||||
|
Some stdio hosts reject a complete `tools/list` frame before they load any
|
||||||
|
tools. The opt-in `REA_MCP_OUTPUT_SCHEMA_PROFILE=off` setting omits `outputSchema`
|
||||||
|
from the advertised catalog for hosts with a small frame limit (the measured
|
||||||
|
pi-web-ui failure and catalog breakdown are in [#2512](https://github.com/morluto/rea/issues/2512)).
|
||||||
|
The default `full` profile is unchanged. With `off`, REA validates successful
|
||||||
|
structured results against the same named canonical tool contract before
|
||||||
|
delivery; it still returns complete `structuredContent` and text results.
|
||||||
|
Only client-side schema discovery and validation are omitted, so hosts using
|
||||||
|
this profile should rely on REA's validation and retain their usual response
|
||||||
|
size limits.
|
||||||
|
|
||||||
|
### Function comparison limitations
|
||||||
|
|
||||||
|
`compare_functions` compares the `pseudocode` and `assembly` dimensions as
|
||||||
|
exact provider text. A `changed` status establishes a textual difference, not a
|
||||||
|
behavior change. Addresses and generated names are not normalized, so relocated
|
||||||
|
labels can change these dimensions while `cfg` remains unchanged. Both text
|
||||||
|
dimensions carry this limitation inline. Numeric constants remain part of the
|
||||||
|
exact comparison. Reconstruction verification and static/runtime correlation
|
||||||
|
retain limitations from the selected dimension; an overall function claim
|
||||||
|
includes all dimension limitations. Equal CFGs establish only equal observed
|
||||||
|
block sizes and successor relationships. Text from different provider
|
||||||
|
identities remains unknown.
|
||||||
|
|
||||||
### Comparison input shapes
|
### Comparison input shapes
|
||||||
|
|
||||||
`compare_web_captures` accepts exactly one of two input shapes:
|
`compare_web_captures` accepts exactly one of two input shapes:
|
||||||
@@ -672,9 +798,10 @@ change the SDK's catalog invalidation or availability checks.
|
|||||||
`normalization` defaults to `{ "rules": [] }`.
|
`normalization` defaults to `{ "rules": [] }`.
|
||||||
|
|
||||||
The advertised schema includes complete nested capture fields and rejects
|
The advertised schema includes complete nested capture fields and rejects
|
||||||
incomplete pairs and structurally malformed captures. Mixed comparison families
|
incomplete pairs and structurally malformed captures. Mixed comparison families,
|
||||||
can pass advertised validation; the SDK rejects them against the canonical
|
and `normalization` without both scenario captures, can pass advertised
|
||||||
input schema before invoking the handler. Domain validation additionally checks
|
validation; the SDK rejects them against the canonical input schema before
|
||||||
|
invoking the handler. Domain validation additionally checks
|
||||||
relationships such as event sequence references and retained counts; JSON
|
relationships such as event sequence references and retained counts; JSON
|
||||||
Schema does not express those cross-field invariants.
|
Schema does not express those cross-field invariants.
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -111,8 +111,12 @@ list does not establish that the target has no procedures.
|
|||||||
Suggestions are Unicode-normalized, case-insensitive prefix matches. The server
|
Suggestions are Unicode-normalized, case-insensitive prefix matches. The server
|
||||||
deduplicates them and sorts by code point for deterministic results. Procedure
|
deduplicates them and sorts by code point for deterministic results. Procedure
|
||||||
completion derives candidates from the complete inventory, so a name is offered
|
completion derives candidates from the complete inventory, so a name is offered
|
||||||
only when it identifies one address in that inventory. Completion returns all
|
only when it identifies one address in that inventory. A completion reply
|
||||||
matching values; it does not expose paging controls.
|
carries at most 100 values, the MCP limit. `total` counts every match and
|
||||||
|
`hasMore` is `true` when values were omitted. Completion has no paging
|
||||||
|
controls. A longer prefix reaches an omitted value only when it excludes other
|
||||||
|
matches; matching uses normalized, case-insensitive forms, so a value can stay
|
||||||
|
unreachable when more than 100 matches share its normalized prefix.
|
||||||
|
|
||||||
## Lifecycle and safety
|
## Lifecycle and safety
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,10 @@ nodes and edges. Node payloads are bounded to 8 MiB and serialized results to
|
|||||||
32 MiB. Pagination returns edges whose source is on the node page and preserves
|
32 MiB. Pagination returns edges whose source is on the node page and preserves
|
||||||
stable IDs for endpoints outside that page.
|
stable IDs for endpoints outside that page.
|
||||||
|
|
||||||
|
Within one value trace, each exact procedure selector is attempted at most once,
|
||||||
|
including failed analyses. Failures remain unknown observations and do not
|
||||||
|
establish canonical function identity. A new trace can retry those selectors.
|
||||||
|
|
||||||
The raw Java `is_dead` flag can remain set after Ghidra decodes a live block;
|
The raw Java `is_dead` flag can remain set after Ghidra decodes a live block;
|
||||||
`block_membership` reports actual syntax-tree membership separately.
|
`block_membership` reports actual syntax-tree membership separately.
|
||||||
|
|
||||||
@@ -266,7 +270,9 @@ Mach-O image. Architecture
|
|||||||
`file_offset` values are container-relative. Entrypoint, segment and section
|
`file_offset` values are container-relative. Entrypoint, segment and section
|
||||||
`file_offset` values retain their source meaning and are relative to the
|
`file_offset` values retain their source meaning and are relative to the
|
||||||
selected Mach-O slice. Raw load-command fields retain their original values.
|
selected Mach-O slice. Raw load-command fields retain their original values.
|
||||||
Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
|
A field otool prints more than once, such as each `LC_BUILD_VERSION` tool, is
|
||||||
|
keyed `name #n` in print order. Each `LC_LINKER_OPTION` string is keyed
|
||||||
|
`string #n` and checked against the declared `count`. Evidence locations map the LC_MAIN entrypoint and nonempty file-backed segment
|
||||||
ranges to container offsets by adding the observed slice offset. Zero-fill
|
ranges to container offsets by adding the observed slice offset. Zero-fill
|
||||||
segments do not produce file ranges. When the selected slice's container offset
|
segments do not produce file ranges. When the selected slice's container offset
|
||||||
is unknown, those mapped locations are omitted with an explicit limitation;
|
is unknown, those mapped locations are omitted with an explicit limitation;
|
||||||
@@ -319,7 +325,16 @@ complete observation.
|
|||||||
digest are preserved; key interpretation follows the pinned plist decoder.
|
digest are preserved; key interpretation follows the pinned plist decoder.
|
||||||
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
- `decode_interface_builder` / `rea decode-interface-builder <app>` reads
|
||||||
compiled storyboard and nib resources into objects, outlets, actions,
|
compiled storyboard and nib resources into objects, outlets, actions,
|
||||||
connections and controller/class names. `verify:interface-builder` compiles
|
connections and controller/class names. A full app graph is often several
|
||||||
|
megabytes (XLD.app: 6.7 MB, about 2M tokens, which also exceeds the default
|
||||||
|
MCP response budget), so MCP callers should pass `detail: "summary"`. The
|
||||||
|
summary keeps every decoded document and node/edge/coverage counts and
|
||||||
|
retains the complete Evidence; page rows with `inspect_analysis_view`
|
||||||
|
`view: {kind: "interface-builder", collection: "nodes", document:
|
||||||
|
"Contents/Resources/Base.lproj/MainMenu.nib", offset: 0, limit: 64}`.
|
||||||
|
`document` selects rows whose evidence names that exact
|
||||||
|
`documents[].relative_path` (nodes and edges only); `source_truncated` keeps
|
||||||
|
graph incompleteness visible on every page. `verify:interface-builder` compiles
|
||||||
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
|
a source-owned AppKit XIB with `ibtool`. Storyboards require an installed
|
||||||
iOS platform; unsupported archive forms remain explicit.
|
iOS platform; unsupported archive forms remain explicit.
|
||||||
XML plist archives accept UTF-8 and BOM-marked UTF-16 in either byte order.
|
XML plist archives accept UTF-8 and BOM-marked UTF-16 in either byte order.
|
||||||
@@ -365,7 +380,12 @@ complete observation.
|
|||||||
property: `N` means `nonatomic`; a complete typed attribute string without
|
property: `N` means `nonatomic`; a complete typed attribute string without
|
||||||
`N` means the Objective-C default `atomic`;
|
`N` means the Objective-C default `atomic`;
|
||||||
missing or incomplete attribute metadata leaves `atomicity` and any
|
missing or incomplete attribute metadata leaves `atomicity` and any
|
||||||
unestablished read-only value null. `is_meta_class`, `is_root_class`, and
|
unestablished read-only value null. A type attribute whose quoted class name
|
||||||
|
never closes is incomplete and marks `objc_properties_categories` partial
|
||||||
|
with `property_attributes_incomplete`. A property record that cannot be
|
||||||
|
decoded, such as one with an empty or unmapped name, is reported as
|
||||||
|
`property_decode_failed` at its entry address while sibling properties are
|
||||||
|
retained. `is_meta_class`, `is_root_class`, and
|
||||||
`ivar_count` are also nullable when the selected provider exposes only symbol
|
`ivar_count` are also nullable when the selected provider exposes only symbol
|
||||||
names. Class implementation methods have null `is_required` and `is_optional`
|
names. Class implementation methods have null `is_required` and `is_optional`
|
||||||
fields because those facts apply to protocol declarations; decoded protocol
|
fields because those facts apply to protocol declarations; decoded protocol
|
||||||
@@ -468,15 +488,16 @@ user's permissions and may change files, show UI or use the network.
|
|||||||
|
|
||||||
Debugging must be allowed for the user (Developer Tools access). A
|
Debugging must be allowed for the user (Developer Tools access). A
|
||||||
hardened-runtime target without `com.apple.security.get-task-allow` cannot be
|
hardened-runtime target without `com.apple.security.get-task-allow` cannot be
|
||||||
debugged; `inspect_signature` reports this as the debugger-attach facet, and
|
debugged; `inspect_signature` reports the signature's `hardened_runtime` value
|
||||||
the tool fails with a `debugger-attach-denied` reason.
|
and its `entitlements`, including `com.apple.security.get-task-allow` when it
|
||||||
|
is present. A denied trace reports the `debugger-attach-denied` reason.
|
||||||
|
|
||||||
## Provider and verification boundaries
|
## Provider and verification boundaries
|
||||||
|
|
||||||
Install a Ghidra 12.1.x release and the 64-bit full JDK it declares, then configure REA to
|
Install a Ghidra 12.1.x release and the 64-bit full JDK it declares, then configure REA to
|
||||||
use them. Ghidra analysis supports Linux x64/arm64 and macOS x64/arm64; each installation requires
|
use them. Ghidra analysis supports Linux x64/arm64 and macOS x64/arm64; each installation requires
|
||||||
the matching native decompiler. Experimental Windows x64 P0 admits native
|
the matching native decompiler. Experimental Windows x64 P0 admits native
|
||||||
x86 and x86-64 PE applications and DLLs, plus explicitly selected
|
x86, x86-64, ARMNT (Thumb-2) and ARM64 PE applications and DLLs, plus explicitly selected
|
||||||
[raw NMOS 6502 images](raw-binary.md#nmos-6502) with a caller-supplied load address,
|
[raw NMOS 6502 images](raw-binary.md#nmos-6502) with a caller-supplied load address,
|
||||||
on fixed local NTFS volumes. Other raw CPU families remain unsupported on Windows.
|
on fixed local NTFS volumes. Other raw CPU families remain unsupported on Windows.
|
||||||
The boundary uses bundled Job Object ownership, protected runtime DACLs, and
|
The boundary uses bundled Job Object ownership, protected runtime DACLs, and
|
||||||
@@ -523,6 +544,12 @@ unresolved runtime/value semantics remain visible in results.
|
|||||||
|
|
||||||
## Interface Builder hierarchy coverage
|
## Interface Builder hierarchy coverage
|
||||||
|
|
||||||
|
A document's `hierarchy_complete` flag follows its own hierarchy coverage.
|
||||||
|
If graph limits omit a hierarchy source or destination, or prevent a hierarchy
|
||||||
|
edge or traversal from being retained, that document reports partial hierarchy
|
||||||
|
coverage. Retained unresolved edges remain in the graph and do not add to the
|
||||||
|
omitted-edge count. Coverage is evaluated separately for each document.
|
||||||
|
|
||||||
A keyed-archive hierarchy UID without an object-table entry marks the hierarchy
|
A keyed-archive hierarchy UID without an object-table entry marks the hierarchy
|
||||||
as partial while preserving decoded objects and known links. UID 0 remains
|
as partial while preserving decoded objects and known links. UID 0 remains
|
||||||
archived nil and does not count as a missing reference. Archive and evidence
|
archived nil and does not count as a missing reference. Archive and evidence
|
||||||
@@ -549,3 +576,23 @@ Value projection preserves serialized node kinds, IDs, references and pagination
|
|||||||
Exact and ambiguous value counts describe emitted root/object value instances;
|
Exact and ambiguous value counts describe emitted root/object value instances;
|
||||||
a root and an object may contain the same value. Archive digests identify the
|
a root and an object may contain the same value. Archive digests identify the
|
||||||
original bytes.
|
original bytes.
|
||||||
|
|
||||||
|
Dates are reported as `{ "$plist_type": "date", "iso8601": "<timestamp>" }`. A
|
||||||
|
date the decoder cannot represent, such as an XML date with a year beyond 9999
|
||||||
|
or a negative year, is reported as `{ "$plist_type": "date", "iso8601": null }`
|
||||||
|
with a counted limitation; the rest of the plist or archive is still decoded.
|
||||||
|
`inspect_plist` and Interface Builder decoding use the same projection.
|
||||||
|
|
||||||
|
## Locate a byte signature
|
||||||
|
|
||||||
|
Use `search_bytes` after opening a target with Ghidra to locate a known constant
|
||||||
|
or instruction sequence. For example, `{"pattern":"48 8B ?? ?? 89","limit":100}`
|
||||||
|
returns ordered addresses with segments, source file offsets and containing
|
||||||
|
procedures. The CLI equivalent is
|
||||||
|
`rea search-bytes ./program '48 8B ?? ?? 89' --provider ghidra`.
|
||||||
|
Use `start`/`end` to constrain the inclusive address window, and continue with
|
||||||
|
`next_cursor` until `complete` is true. Inspect `searched` for page coverage and
|
||||||
|
skipped uninitialized blocks. A missing match does not establish absence from
|
||||||
|
uninitialized memory or across block boundaries. See
|
||||||
|
[Ghidra byte search](ghidra-provider.md#masked-byte-search) for mask and coverage
|
||||||
|
semantics. This query never executes or modifies target bytes.
|
||||||
@@ -54,6 +54,22 @@ resource limit; use selected fields, a display filter or an input-packet limit
|
|||||||
when dissection output is too large. Each call checks the recording's current
|
when dissection output is too large. Each call checks the recording's current
|
||||||
digest, so pages from different source digests must not be combined.
|
digest, so pages from different source digests must not be combined.
|
||||||
|
|
||||||
|
For a question about a known input prefix, select `packet_limit` for that
|
||||||
|
prefix, the fields needed for the answer, and a matching output window. For
|
||||||
|
example, a question about the first twelve PostgreSQL TCP packets within
|
||||||
|
original frames 1–2,000 can use fields `tcp.srcport`, `tcp.dstport`, `tcp.flags`,
|
||||||
|
filter `tcp.port == 5432`, `packet_limit: 2000` and `match_limit: 12`.
|
||||||
|
The matching count then describes that input prefix. A filter containing
|
||||||
|
`frame.number <= 2000` without an input limit still reads the entire capture;
|
||||||
|
returning twelve rows does not limit producer output. These controls answer a
|
||||||
|
bounded question and must not be used to infer whole-capture absence.
|
||||||
|
|
||||||
|
Discover unfamiliar field names with `inspect_packet_fields`, using an exact
|
||||||
|
protocol and a narrow query. If the installed names are already established,
|
||||||
|
direct field selection avoids an additional glossary probe. Count discovery
|
||||||
|
and any retries when comparing investigation cost; a small page alone does
|
||||||
|
not establish a faster workflow.
|
||||||
|
|
||||||
Additional selections:
|
Additional selections:
|
||||||
|
|
||||||
- `display_filter`: Wireshark display-filter syntax; omitted means no filter.
|
- `display_filter`: Wireshark display-filter syntax; omitted means no filter.
|
||||||
|
|||||||
Loaded 100 of 1921 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user