mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 22:36:08 +00:00
1ff5a655bc8d97738c4e5a1ff7a6786126e6e3fe
6470
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1ff5a655bc |
fix(native)!: preserve value-flow absence semantics (#8785)
* fix(native)!: preserve value-flow absence semantics * test(native): compare value-flow reasons with their owners Stop freezing the unrecorded and Kuna unavailable sentences. Assert the procedure-scoped unknown and that an explicit provider reason is preserved. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
dd4b83c220 |
fix(ghidra)!: search adjacent initialized memory blocks (#8840)
* fix(ghidra)!: search adjacent initialized memory blocks BREAKING CHANGE: Ghidra analysis snapshots from earlier byte-search policies are refused; reopen the target and create a new snapshot. Historical Evidence remains importable. * test(ghidra): supervise cross-block fixture process * docs(ghidra): clarify contiguous search range * test(ghidra): verify search metadata and cleanup --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
d896ab7bd7 |
fix(mcp)!: distinguish unavailable batch pseudocode (#8737)
* fix(mcp)!: distinguish unavailable batch pseudocode * test(mcp): preserve historical batch error details * test(mcp): retain unavailable items in batch recovery * test: update Ghidra batch unavailable expectation |
||
|
|
30228c9a5f |
fix!: guide snapshot inputs to the restore workflow (#8867)
BREAKING CHANGE: Snapshot-shaped bundle import failures use invalid_request rather than evidence_integrity_mismatch. |
||
|
|
6df2ac1d7e |
fix(evidence): report partial workflow question publication (#8848)
* fix(evidence): report partial workflow question publication * fix: project nested recovery for caller lifetime |
||
|
|
b97023809f |
feat(native): retain decompile export source facts (#8805)
* feat(native): retain decompile export source facts * test(native): verify historical export source facts * test(native): retain source after export conflict |
||
|
|
5a1f75461a |
fix(mcp): guide address context recovery (#8766)
* fix(mcp): guide function comment recovery from address context * fix(mcp): retain specific comment refusal recovery * docs(mcp): advertise paged function comments * fix(mcp): preserve address facet recovery * test(mcp): verify session-specific facet recovery * test(mcp): extract address recovery fixture * fix(mcp): direct provider recovery to complete status * fix(mcp): qualify address recovery across providers * fix(mcp): make coordinate recheck actionable * test(mcp): correct address recovery fixtures * test(ghidra): verify address comment recovery |
||
|
|
5b82a253dc |
fix(evidence): link snapshot replays to original observations (#8734)
* fix(evidence): link snapshot replays to original observations * style(tests): format snapshot replay assertions * test(evidence): cover replay parent metadata refresh * test(evidence): surface snapshot replay failures * test(mcp): check replay source after cleanup recovery |
||
|
|
1a9aa1296b |
fix(kuna): recover overflow when project can answer (#8747)
* fix(kuna): recover overflow when project can answer * fix(kuna): provide actionable CLI overflow recovery * test(kuna): cover shared overflow cleanup * test(kuna): cover cancellation during overflow cleanup * fix(kuna): make overflow recovery usable after handoff |
||
|
|
9196c30f6e |
fix(ghidra): guide packed program import recovery (#8820)
* fix(ghidra): guide packed program import recovery * test(ghidra): verify packed import recovery facts |
||
|
|
fdfc210879 | fix(native): guide unreadable journal recovery (#8809) | ||
|
|
eac28b2047 |
fix(xrefs): guide symbol lookup recovery (#8753)
* fix(xrefs): guide ambiguous name selection * fix(xrefs): guide missing symbol lookup * fix(xrefs): name available symbol recovery paths * fix(xrefs): qualify exported address coordinates * fix(xrefs): point to procedure address lookup * fix(xrefs): keep CLI recovery focused * test(xrefs): cover CLI ambiguity recovery |
||
|
|
9a79ee8e9e |
fix(kuna): clarify single-function fallback evidence (#8742)
* fix(kuna): describe fallback evidence without guessing project state * fix(kuna): qualify export capacity guidance by source |
||
|
|
51e9027d58 | fix(session): preserve availability recovery guidance (#8764) | ||
|
|
78d7bf3ea0 |
docs(native): clarify memory and file byte sources (#8829)
* docs(native): clarify memory and file byte sources * docs(native): qualify Ghidra fixup coverage |
||
|
|
9031adf0b0 | fix(ci): own source-map captures and honor browser startup budget | ||
|
|
a1660bc016 |
fix(pe): reject certificates whose signature algorithms disagree (#8922)
* fix(pe): reject certificates whose signature algorithms disagree * test(pe): keep the signature algorithm case apart from the version case |
||
|
|
0a99c2a580 |
fix(artifacts): give Godot PCK and Node SEA readers the ESP member lifecycle (#8395)
* feat(artifacts): inventory and extract ESP32 firmware images ESP32-family app and bootloader images (magic 0xE9) had no reader, so open_binary refused OTA downloads, app partitions and bootloaders. Add the esp-image container format behind inspect_artifact and extract_artifact. A pure decoder reads each byte once and keeps only fixed-size fields. Derived facts cover the chip, from a table cited to ESP-IDF v6.1; segment classification as ESP-IDF's should_map and should_load; checksum and appended SHA-256 results; app and bootloader descriptors; Secure Boot v2 signature blocks, observed and unverified; and undescribed trailing byte ranges. Truncation and undescribed bytes make coverage unknown. ESP8266 images are refused with a specific reason, and ciphertext stays unrecognized. Add the format to every target, Evidence, snapshot, live-summary and artifact vocabulary, document it, and add an optional verify:esp-images lane that compares esptool-built images with esptool image-info and espsecure signature-info-v2. Refs #3615 * test(artifacts): run the ESP image lane on esptool v5.6.0 The lane compared descriptor fields only when esptool image-info printed one, so a descriptor that REA reports and esptool does not print passed without comparison. Compare the descriptor kind in both directions. Record that the lane passed for all 13 images with esptool and espsecure v5.6.0, the version the layout facts cite, as well as with 4.12.0. * refactor(artifacts): define ESP image layout sizes once The decoder and the derived facts each defined the segment-header, appended-digest and flash-sector sizes, the erased-byte scan and the offset formatter. Define them once with the other fixed-layout structures and import them in both places. The signature slot count is the one constant that drives the slot scan. Correct the observedEnd comment: it is the end of the last chunk read. * fix(artifacts): report ESP-IDF's segment length limit by chip ESP-IDF's verify_segment_header rejects a segment of 16 MiB or more unless the chip maps flash through 32-bit cache addresses (SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP: ESP32-S3, P4, C5, C61, H4 and S31 in v6.1). Record that capability in the chip table and add a limitation for such segments; unknown chips get none. `loaded` repeats should_load. It does not mean the bootloader accepts the segment: verify_load_addresses still rejects one outside the chip's RAM, which REA does not model. Say so in the docs, the skill reference and the inventory limitation. * test(artifacts): check ESP lane tools first and verify extraction The lane started work before checking its tools, so a missing RISC-V target, ld.lld, esptool or espsecure surfaced as a raw spawn or command error. Check each one before building and name the variable to set. Warn when esptool is not v5.6.0, the release the layout facts cite. Assert each segment's class for the ESP32-C3 app, its variants and the bootloader, as the Xtensa image already did. Extract the app and the signed image through the CLI and compare every file with the bytes at esptool's segment offsets or in the appended signature sector; the source's SHA-256 must be unchanged and equal the reported root_sha256. * docs(artifacts): state what the ESP image lane does not establish The Verification section listed the lane's checks but not its gaps: only ESP32-C3 and ESP32 images come from a real producer, no real project build, OTA download or flash dump is inspected, no signature is verified, and the lane does not run in CI. Record the timing of the final lane run on esptool v5.6.0. * fix(artifacts): compare ESP segment declarations Segment entries hash only their data, so compare_artifacts reported a segment that moved to another load address with the same bytes as unchanged; only the image itself changed. Compare each segment's load_addr and data_len as its declaration, as Node SEA and PyInstaller members are compared, and count a changed declaration as a metadata change, also when content is unavailable. Header offsets are layout and the classification is derived, so neither is compared. * test(artifacts): run ESP lane commands as owned process trees The lane ran its tools through execFile. A timeout killed only the direct child, so a linker or assembler that Clang or GCC had started could keep running while the lane removed its workspace. Run every command through createVerifierCommands, check the tools inside withVerifierCleanup, and remove the workspace only after commands.close() has stopped every tree; a failed close keeps the workspace. Only a nonzero exit now counts as a refusal or an expected esptool failure; timeouts and spawn failures propagate. Supervising each command roughly doubles the lane's run time. * test(artifacts): cancel ESP decoding after its first decoded chunk The cancellation test requested cancel or close on setImmediate, which can land while the file is still being opened and checked, so it never showed that decoding had started. It also passed against a reader that ignored both requests while decoding. Acknowledge the decoder's first chunk, then cancel or close, join the operation, and assert that no later chunk reaches the decoder and that the reader ends closed. * docs(artifacts): record which ESP metadata comparison ignores Segments are compared by their declaration. The image's and the signature blocks' format_metadata are not: every field comes from their own bytes, which content comparison already covers, and a block's image_digest_matches follows the image's content. * feat(artifacts): inventory and extract ESP flash layouts A whole-flash dump of an ESP32-family chip was read as its bootloader image, with the partition table, apps and data left as undescribed bytes, or refused when it began with erased flash. Read it as an esp-flash-image container instead. The partition table is decoded as esp_partition_table_verify accepts it and is found at the caller's partition_table_offset, at the default 0x8000, or by scanning 0x1000-aligned sectors for a table whose MD5 verifies, which the inventory reports as derived. The bootloader is located at its chip's ROM bootloader offset. Each partition is an entry with its declaration in flat format_metadata; the bootloader and app partitions carry ESP image facts from the existing streaming decoder; the otadata records are reported with a selection derived without rollback or anti-rollback, and the running partition stays unknown. One unreadable partition does not hide the others, and extraction writes every partition, nvs_keys included, as ordinary data. Flash layouts are detected before single ESP images. The scan runs only when an ESP image sits at a ROM bootloader offset, and is refused when a bootloader at another chip's offset shows that the dump does not start at flash address 0. open_binary gains flash_offset and partition_table_offset, which require format esp-flash-image and are part of the snapshot target identity; the CLI takes them with --target-format esp-flash-image. Comparison treats a partition's label, type, subtype, range and flags as its declaration. * test(artifacts): verify ESP flash layouts with gen_esp32part, merge-bin and otatool Add the optional verify:esp-flash lane. It builds default and relocated partition tables with ESP-IDF's gen_esp32part.py, assembles dumps with esptool merge-bin, including one written from flash 0x1000, and writes OTA records with otatool through flash_file_tool.py, which replaces parttool's esptool calls with reads and writes of the dump file. It compares REA's bootloader and app facts with esptool image-info, every partition with gen_esp32part.py's decoding of the extracted table, and the OTA records and derived slot with otatool read_otadata after each switch. It also checks extracted bytes and source integrity, the scan and caller table offsets, flash_offset on the 0x1000 dump, a corrupted app beside valid partitions and a truncated dump. ESP32 linker scripts place the conformance bootloader and app in ESP32 address ranges. * fix(artifacts): read ESP flash layouts as the bootloader does Read the partition table as bootloader_utility_load_partition_table does: the last factory, otadata and same-subtype OTA declaration wins, and a limitation names repeats. Plan each otadata record within its partition and the dump. An otadata partition smaller than two sectors selects none, the bootloader's INVALID_INDEX; a record the dump lacks leaves the selection unknown while the other record stays reported. The fallbacks follow the bootloader's indexes: ota_0-fallback and factory-fallback name no partition when the table declares none, and the assumptions add GPIO holds and deep-sleep wake. Refuse a dump whose bootloader contradicts its origin before the default table offset is tried, whether flash_offset was omitted or given, and keep the ESP image reader's ESP8266 refusal. A table never lies inside the located bootloader image, so single app images are no longer scanned. An explicit esp-flash-image selection is decided before other container signatures, and the native commands' --target-format no longer takes it. Say when no bootloader confirms the default origin. Exempt the primary bootloader and partition_table entries of ESP-IDF 5.3 from placement limits, report every region inside a longer one, and name partitions by index when labels repeat ignoring case. Keep an undecodable label's bytes in label_hex and compare them. Word undescribed ranges as starting at a byte that is not erased, and treat a short erased region as erased. Add an MCP compare_artifacts test for partition declarations, reader identity and close tests, scan edge cases, the 0x2000 ROM offset and a literal otatool CRC. * test(artifacts): verify ESP primary layout entries and the otadata fallback Build a table with the primary bootloader and partition_table entries that gen_esp32part.py fills from --primary-bootloader-offset, and check that REA reads it with complete coverage, no overlap or misplacement limitation, and a table that agrees with gen_esp32part's decoding. Erase the otadata with otatool after the slot switches and check the records against read_otadata and the factory fallback. * refactor(artifacts): share positional entry lifecycle The ESP image and ESP flash-layout readers carried the same 138 lines of lifecycle code. PositionalArtifactReader now owns it: one preparation under the reader's scope, a carrier recheck before each traversal, refusal of forged entries, range streams that check their length and recheck the carrier when they end, and a close that retires open streams before it releases the carrier. Each reader keeps its location, decoding, entries and metadata, and returns its entries and coverage from prepareEntries(). Entries carry a required byte range, which removes the unreachable null-range checks in open(). Failure reasons, messages, order and cancellation do not change. The Godot PCK reader keeps its own copy. It reports a member that ends early as a format failure, where the ESP readers report integrity, and the helper cannot express that without an option. The Node SEA reader has the same difference. * fix(artifacts): report Godot PCK and Node SEA members that end early as integrity Behavior change: a Godot PCK or Node SEA member stream that delivers fewer bytes than its entry declares now fails with reason "integrity" and the message "<label> entry ended early: <path>". Before, it failed with reason "format" and "<label> is malformed: member ended early: <path>". Callers see the category integrity_mismatch instead of execution_failure. The remediation text does not change. Only a carrier that changes after open() rechecks its identity reaches this path. The directory was valid when the inventory was taken, so a short member means that the carrier changed, not that it is malformed. The identity recheck that runs next also reports integrity, and the ESP and ASAR readers already report a short member as integrity. The new tests end the admitted descriptor's reads inside a member after open() and check the reason and message for each reader. * refactor(artifacts): move Godot PCK and Node SEA onto the positional lifecycle The Godot PCK and Node SEA readers carried their own copies of the positional entry lifecycle. They now extend PositionalArtifactReader and keep only their location, decoding, entries and metadata. prepareEntries() returns the entries and the coverage that each reader reported before. Godot reports unknown coverage with one limitation for an encrypted or sparse-bundle directory. Node SEA reports complete coverage with its fuse contradiction as a limitation. The base class takes two rules from these readers: - open() refuses an encrypted entry as unavailable, after the forged-entry check and before the carrier recheck: "Encrypted <label> member is unsupported: <path>". ESP entries are never encrypted. - Traversal yields to the event loop every 256 entries, starting before the first entry. The ESP readers gain one yield before their first entry. This changes scheduling only. Failure reasons, messages, order and cancellation do not change. A member that ends early has failed as integrity since the previous commit. Godot's directory parse still checks only cancellation, so close does not stop it early. Node SEA's close still drops an admitted decode that no completed preparation released. Each reader now stores its entries only after preparation succeeds; a failed preparation never served them before either. PositionalArtifactEntry requires a byte range, so the unreachable null-range checks in open() are gone. * fix(artifacts): keep ESP flash coverage complete for a dump cut short An esptool merge-bin dump ends after the last part it wrote, usually inside its last partition. Such a partition made the layout's coverage unknown, so artifact comparison of two merge-bin dumps reported every unchanged partition as unknown. The partition table names every partition whatever the file's length, so a partition cut short or left out by the end of the file hides no entry. As the ESP image reader does for a cut-short segment, its entry holds the bytes the file has and an entry limitation gives the declared size; a partition with no bytes stays in partitions_outside_dump. Coverage stays complete unless undescribed bytes remain or no bootloader is located. An MCP compare_artifacts test of two dumps that end inside ota_0 now classifies every partition, and fails on the old rule. * fix(artifacts): report written bytes in ESP partitions, erased slots included An app slot is erased for ESP-IDF's bootloader when its image header bytes are erased flash, and REA reported only that status. A slot can still hold data further in: a dump read from a device held a complete older app image 1 MiB into an OTA slot reported as erased, with no limitation. Each partition entry now records where the bytes the dump holds are written, relative to the partition: written_offset, the first byte that is not 0xFF, written_end, just past the last, and written_bytes, how many are not 0xFF. They are null for a partition outside the dump. The status stays erased, which is the bootloader's view, and a limitation names an erased app slot that holds written bytes and says they are not read as an image. The reader reads every partition byte once more to find them. * fix(artifacts): derive the ESP boot selection for every partition table The boot selection lived inside otadata, so a table without an otadata partition, the common factory-only layout, had none. ESP-IDF's bootloader_utility_get_selected_boot_partition returns the factory index when the table declares no otadata partition. Move it to a top-level boot_selection that every layout has. Its basis names the bootloader's branch: otadata, factory-fallback and ota_0-fallback from the records, no-otadata for a table without otadata, none for the bootloader's INVALID_INDEX, and unknown when the dump lacks a record the bootloader reads, which replaces the null selection. running_partition moves with it, and otadata keeps its partition index and records. The assumptions name what each branch leaves out: no records assumption without otadata, and none for none or unknown. The real-tool lane checks no-otadata on a table gen_esp32part built without otadata. * fix(artifacts): say when an ESP image is a flash dump read from 0x1000 An ESP32 dump read from flash 0x1000, as read-flash 0x1000 or merge-bin --target-offset 0x1000 writes it, opened automatically as its bootloader image with a megabyte of undescribed bytes and no next probe. The flash reader refuses that origin, but nothing told an agent which flash_offset to pass. espFlashDumpOrigin owns the inference: an image of a chip whose ROM loads the bootloader from flash R, at file offset F, is the bootloader of a dump read from R - F when a partition table sits where that origin puts 0x8000. The ESP image reader asks it for a non-app image at file offset 0, so the result does not depend on how the reader was created, and records flash_dump {flash_offset, table_file_offset}, null otherwise, with a limitation that says to reopen the file as esp-flash-image with that flash_offset. inspect_artifact proposes the same open_binary call as a next probe, and the flash reader's misplaced-origin refusal names the offset. * perf(artifacts): summarize nested ESP images in the flash layout summary view The summary view returned an ESP flash layout's root facts whole, so each app partition and the bootloader carried every segment and signature block, about half of a dump's summary. The summary view now gives each nested image a summary instead of its facts: status, chip, entry point, segment count, load ranges (each mapped, loaded or unclassified segment's address and declared length), the checksum and appended SHA-256 statuses, coverage and limitations. Complete detail keeps the full facts, every segment included, and the retained Evidence is unchanged. On a 16 MiB device dump the summary's root falls from 5,141 to 2,657 bytes, and the summary Evidence from 10,038 to 7,554. * test(artifacts): expect the misplaced image the ESP bootloader search reports |
||
|
|
5990ff0324 |
feat(artifacts): read WeChat mini-program wxapkg containers (#8148)
* feat(artifacts): read WeChat mini-program wxapkg containers Add a read-only reader for plaintext wxapkg packages: the 0xBE/0xED 18-byte big-endian header, the bounded name/offset/size member table, and verbatim member bytes at absolute carrier offsets. - Members retain declared names, absolute offsets and sizes, so source_location reports real carrier positions. - V1MMWX PC-client packages are detected as encrypted coverage with null member facts; extraction refuses with a typed error and no decryption is attempted. - Bounded table and directory budgets are admitted before allocation; malformed marks, truncated tables, invalid UTF-8 names, member extents inside the header or beyond the file, and counts that cannot fit their table are typed format or limit failures. - Member reads stream in bounded 64 KiB chunks with carrier freshness checks; the reader joins outstanding streams before close and bounds active streams and repeated member I/O. Refs #8121. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(artifacts): bound wxapkg members to the declared data region Normalize the two indexLength conventions before the table bounds check and the table read. Member ranges must then lie in that declared data region, which has to fit in the file. Record wxapkg format metadata as not compared: header facts repeat the package bytes, and entry source paths keep a producer root prefix. * fix(artifacts): choose the wxapkg index after member ranges fit Four trailing bytes made the rows-only layout end on the file and dropped the count-inclusive layout. Keep every admitted layout until the parsed rows and member ranges select one. --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
63b66fa498 |
fix(search): clarify regex diagnostics and recovery (#8776)
* fix(ghidra): identify invalid regex patterns * docs(native): clarify regex dialects in search contracts * fix(ghidra): guide regex syntax recovery * fix(search): guide regex worker recovery * fix(search): preserve regex parser diagnostics * test(search): update regex boundary expectations * refactor(search): remove unused regex error hook --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
52bca6de41 |
fix(native): retain value trace source evidence (#8384)
* fix(native): retain value trace source evidence * fix(native): build trace error evidence only on failure * test(mcp): expect unowned value trace refusal * test(mcp): avoid freezing trace error wording * test(native): keep value-trace stop coverage with source evidence Main's stop and continuation cases now meet the trace's evidence-owner precondition: direct calls supply a source id, and a completed MCP trace runs in a session that can retain it. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
25b62db838 | test(firmware): cover S-record high address workflows (#8897) | ||
|
|
30a11af923 |
fix(browser)!: keep the Chromium sandbox for REA-launched browsers (#8738)
* fix(browser)!: keep the Chromium sandbox for REA-launched browsers Playwright appends --no-sandbox unless a launch passes chromiumSandbox: true. Neither the browser scenario launch nor the native module resolver passed it, so every REA-launched Chromium ran without its sandbox and REA_BROWSER_NO_SANDBOX had no effect. Both launch sites now take the sandbox decision from ChromiumSandbox.ts: off on Android or when REA_BROWSER_NO_SANDBOX=true, on otherwise. When Chromium reports that it cannot start its sandbox, the launch failure's remediation names the opt-out. Fixes #8722 BREAKING CHANGE: hosts where Chromium cannot create its sandbox, such as root users and Linux containers without user namespaces or a setuid sandbox, now fail to launch until REA_BROWSER_NO_SANDBOX=true is set. * fix(browser): name the sandbox opt-out on any sandboxed launch failure Chromium reports an unusable sandbox through several unrelated startup messages, including a misconfigured setuid helper and a refused namespace move, and Playwright recognizes only some of them. Matching individual messages missed real failures, so a sandboxed launch failure now always names REA_BROWSER_NO_SANDBOX=true as a conditional remediation and the retained startup diagnostics show whether the sandbox was the cause. * fix(browser): keep default recovery for resolver failures after launch The module resolver's outer catch also covers context, navigation and evaluation failures after the browser launched. Attaching the sandbox remediation there replaced their default recovery guidance. Attach the shared launch remediation only when a sandboxed launch itself fails. * docs(browser): note the Android sandbox exception for module traces |
||
|
|
b1b6cb64c2 | fix(analysis-view)!: distinguish unavailable native collections (#8291) | ||
|
|
c13dd36d95 |
fix(artifacts): decide how compare_artifacts compares every format_metadata kind (#8231)
* feat(artifacts): inventory and extract ESP32 firmware images
ESP32-family app and bootloader images (magic 0xE9) had no reader, so
open_binary refused OTA downloads, app partitions and bootloaders.
Add the esp-image container format behind inspect_artifact and
extract_artifact. A pure decoder reads each byte once and keeps only
fixed-size fields. Derived facts cover the chip, from a table cited to
ESP-IDF v6.1; segment classification as ESP-IDF's should_map and
should_load; checksum and appended SHA-256 results; app and bootloader
descriptors; Secure Boot v2 signature blocks, observed and unverified;
and undescribed trailing byte ranges. Truncation and undescribed bytes
make coverage unknown. ESP8266 images are refused with a specific
reason, and ciphertext stays unrecognized.
Add the format to every target, Evidence, snapshot, live-summary and
artifact vocabulary, document it, and add an optional verify:esp-images
lane that compares esptool-built images with esptool image-info and
espsecure signature-info-v2.
Refs #3615
* test(artifacts): run the ESP image lane on esptool v5.6.0
The lane compared descriptor fields only when esptool image-info printed
one, so a descriptor that REA reports and esptool does not print passed
without comparison. Compare the descriptor kind in both directions.
Record that the lane passed for all 13 images with esptool and espsecure
v5.6.0, the version the layout facts cite, as well as with 4.12.0.
* refactor(artifacts): define ESP image layout sizes once
The decoder and the derived facts each defined the segment-header,
appended-digest and flash-sector sizes, the erased-byte scan and the
offset formatter. Define them once with the other fixed-layout structures
and import them in both places. The signature slot count is the one
constant that drives the slot scan.
Correct the observedEnd comment: it is the end of the last chunk read.
* fix(artifacts): report ESP-IDF's segment length limit by chip
ESP-IDF's verify_segment_header rejects a segment of 16 MiB or more
unless the chip maps flash through 32-bit cache addresses
(SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP: ESP32-S3, P4, C5, C61, H4 and
S31 in v6.1). Record that capability in the chip table and add a
limitation for such segments; unknown chips get none.
`loaded` repeats should_load. It does not mean the bootloader accepts the
segment: verify_load_addresses still rejects one outside the chip's RAM,
which REA does not model. Say so in the docs, the skill reference and the
inventory limitation.
* test(artifacts): check ESP lane tools first and verify extraction
The lane started work before checking its tools, so a missing RISC-V
target, ld.lld, esptool or espsecure surfaced as a raw spawn or command
error. Check each one before building and name the variable to set. Warn
when esptool is not v5.6.0, the release the layout facts cite.
Assert each segment's class for the ESP32-C3 app, its variants and the
bootloader, as the Xtensa image already did. Extract the app and the
signed image through the CLI and compare every file with the bytes at
esptool's segment offsets or in the appended signature sector; the
source's SHA-256 must be unchanged and equal the reported root_sha256.
* docs(artifacts): state what the ESP image lane does not establish
The Verification section listed the lane's checks but not its gaps: only
ESP32-C3 and ESP32 images come from a real producer, no real project
build, OTA download or flash dump is inspected, no signature is verified,
and the lane does not run in CI.
Record the timing of the final lane run on esptool v5.6.0.
* fix(artifacts): compare ESP segment declarations
Segment entries hash only their data, so compare_artifacts reported a
segment that moved to another load address with the same bytes as
unchanged; only the image itself changed. Compare each segment's
load_addr and data_len as its declaration, as Node SEA and PyInstaller
members are compared, and count a changed declaration as a metadata
change, also when content is unavailable.
Header offsets are layout and the classification is derived, so neither
is compared.
* test(artifacts): run ESP lane commands as owned process trees
The lane ran its tools through execFile. A timeout killed only the direct
child, so a linker or assembler that Clang or GCC had started could keep
running while the lane removed its workspace. Run every command through
createVerifierCommands, check the tools inside withVerifierCleanup, and
remove the workspace only after commands.close() has stopped every tree;
a failed close keeps the workspace.
Only a nonzero exit now counts as a refusal or an expected esptool
failure; timeouts and spawn failures propagate. Supervising each command
roughly doubles the lane's run time.
* test(artifacts): cancel ESP decoding after its first decoded chunk
The cancellation test requested cancel or close on setImmediate, which
can land while the file is still being opened and checked, so it never
showed that decoding had started. It also passed against a reader that
ignored both requests while decoding. Acknowledge the decoder's first
chunk, then cancel or close, join the operation, and assert that no
later chunk reaches the decoder and that the reader ends closed.
* docs(artifacts): record which ESP metadata comparison ignores
Segments are compared by their declaration. The image's and the
signature blocks' format_metadata are not: every field comes from their
own bytes, which content comparison already covers, and a block's
image_digest_matches follows the image's content.
* fix(artifacts): decide declaration comparison for every metadata kind
compare_artifacts listed the format_metadata kinds it compared by hand,
so a new kind went uncompared without notice; ESP segment load addresses
were first missed this way. One table beside the schemas now gives every
kind a declaration projection or the reason comparison uses none of its
facts, and a kind without an entry does not compile. Consumers read one
result per pair instead of naming formats.
- Godot PCK entries stay uncompared. Godot looks members up by the
simplified path without res://, which logical_path holds apart from
Unicode normalization. Godot 4.3 exports store res:// and 4.4.1
exports do not, so comparing source_path would mark every member
changed. The reader admits only the encryption flag, which the
occurrence's encrypted field records.
- A changed PyInstaller declaration is now a metadata change also when
the member's content is unavailable, as for ASAR, Node SEA and ESP
segments. It was unknown before, for no recorded reason.
- Root kinds and ESP signature blocks stay uncompared: their fields come
from their own bytes, which content comparison covers.
- A declaration that only one side records now adds one generic
limitation for every compared kind. Only Node SEA added one before,
with its own text.
* fix(artifacts): keep content comparison for metadata records with content
The record rule compared two metadata occurrences by their declaration
alone. A hand-built record that names content, with an equal declaration,
then hid a content change; PyInstaller records already did so before the
table. The rule now needs both records without an artifact_id, which is
every metadata record a reader emits.
Review changes in the same area:
- The unknown-declaration limitation also covers declarations of different
kinds and non-container observations, and tests compare it with its
constant.
- docs/godot-pck.md gives each Godot version's lookup path, the backslash
case, and that a member that becomes encrypted compares unknown. The
table points to it instead of restating it.
- The PyInstaller regression moves to an acceptance test whose MCP client
validates the advertised output schema, and the domain tests keep one
representative case per behavior.
* fix(artifacts): compare encryption changes when content is unavailable
An encrypted member has no content. Comparison then took the
content-unavailable fallback, which counted only ASAR storage and format
declarations, so a Godot PCK, ZIP or PYZ member that became encrypted
compared unknown although both sides record `encrypted`. The record rule
compared node-less metadata records by format declaration alone, so a PYZ
namespace record whose encryption flipped compared unchanged.
`OccurrenceMetadata` now owns the encryption comparison beside ASAR
storage and the format declaration. Every occurrence records `encrypted`,
so a pair's comparison is never unknown. The fallback, the record rule and
the metadata dimension read it there; `metadataChanged` no longer compares
`encrypted` itself. The record rule compares all of `OccurrenceMetadata`:
a changed value is changed in metadata, with availability when another is
unknown.
Decision: occurrence limitations still do not count for node-less
records. Readers derive them from the declared facts (the PYZ caveat
follows `encrypted`), and their wording can change between REA versions
while the record stays the same. The fallback does not compare them
either.
Behavior changes:
- A pair with unavailable content whose `encrypted` differs is changed
[metadata, availability], not unknown [availability].
- A node-less record whose `encrypted` differs is changed [metadata], not
unchanged.
- A hand-built node-less record that records ASAR storage now compares
it; no reader emits such records.
Remaining:
- The fallback still ignores `entry_kind` and `executable`, which readers
also declare, so a file that becomes a symlink, or a mode change on an
encrypted ZIP member, still compares unknown. `executable` has its own
dimension, so its fix needs content-free checks split from node checks.
Each is its own behavior decision.
- Node-bearing pairs still compare limitation wording; records do not.
Whether limitation wording is ever a comparison fact needs one decision.
* fix(artifacts): compare entry kind and executable when content is unavailable
When either side lacked verified content, comparison took a fallback that
counted only ASAR storage, encryption and format declarations. It compared
`entry_kind` only in the metadata check of pairs with content, and
`executable` only beside the node checks. A ZIP member that became a
symlink, whose target REA does not read, and an encrypted ZIP member whose
Unix mode gained an execute bit therefore compared unknown, although
readers declare both fields on every occurrence.
`OccurrenceMetadata` now owns the entry kind comparison beside encryption;
it is never unknown. Comparison checks fall into two groups: declared facts
that need no content (`executable` and the metadata declarations), and
facts that need both content nodes (content, kind, format, size, relations
and node metadata). The fallback, the record rule and pairs with content
share the declared checks and one rule for unknown declarations. Kind and
format stay content checks: without content they are guesses from the path.
Behavior changes:
- Unless both sides have content, a pair whose entry kind differs is
changed [metadata, availability], not unknown [availability].
- Such a pair whose `executable` differs is changed [executable,
availability], with metadata when another declared fact also differs.
- A hand-built node-less record whose `executable` differs is changed
[executable]. No reader emits such records.
Remaining: declared sizes and integrity digests still compare only through
observed bytes, so they do not count when content is unavailable.
* fix(artifacts): never mark a symlink executable
On POSIX hosts the directory reader recorded a symlink's own permission
bits as `executable`. Linux reports 0777 for every symlink and macOS
usually 0755, so directory, app bundle and DMG symlinks read true. zip.js,
the PyInstaller reader and the Windows branch record false. The previous
commit compares `executable` without content, so a symlink that two
readers or two hosts record differently compared changed in `executable`,
and a non-executable file that became a symlink also reported `executable`.
`hostExecutableMarking` now gives a symlink no marking on any host:
running a symlink runs its target, and the link's own bits do not control
that.
Behavior change: directory, app bundle and DMG inventories record
`executable: false` for symlinks. Occurrence IDs do not change, but the
graph digests and manifest IDs of inventories with symlinks do. Comparing
an inventory stored before this change with a new one therefore reports
changed [executable, availability] once for each such symlink.
* fix(artifacts): stop comparing limitation wording
Pairs with content compared node and occurrence limitations as metadata,
while node-less records, the content-unavailable fallback and inventory
limitations did not. Limitations explain what an observation could
establish. Readers mostly derive them from fields that comparison already
reads, from other occurrences or from what REA could read, and their
wording changes between REA versions. So adding `A` beside an unchanged
ZIP member `a` marked `a` changed in metadata through its case-collision
note, and an older and a newer inventory of equal bytes could differ
wherever a reader reworded a note.
No comparison path reads limitations now. Inventory limitations still
appear in the result, and occurrence limitations stay in the inventory
Evidence. Readers write no node limitations, so only hand-built inventories
compared them.
Behavior change: for a pair with content, limitation text no longer adds
metadata. A pair that differs only there compares unchanged, or unknown
[availability] when a declaration is unknown.
Remaining: a few facts appear only in limitation text, or also in fields
that comparison does not read, so their changes are no longer reported
while the compared bytes stay equal: an ASAR block integrity table or an
unsupported integrity declaration, a Godot member's all-zero MD5, a ZIP
name's leading `./` (also in `source_path`), and a PyInstaller PYZ image
digest (also in `pyz_sha256`; the archive root still reports the change).
Each needs a field or a comparison decision of its own if it matters.
* fix(artifacts): compare declared digests when content is unavailable
Without observed bytes on both sides, comparison ignored the containers'
declared integrity claims. Two encrypted Godot PCK members therefore
compared unknown even when their declared MD5s showed that the script had
changed. Godot's exporter stores the MD5 of a member's original bytes, so
the MD5 does not depend on the encryption IV. This comes from the 4.3,
4.4.1 and 4.7.1 exporter sources; no real encrypted export was compared.
When either side has no content, claims of one algorithm that differ now
make the path changed in metadata and availability. Equal claims add no
change, because they are unverified, and observed bytes still decide when
both sides have them. Godot PCK and ASAR readers record such claims.
For a readable member, a directory that declares or omits an MD5 only
decides whether REA can check its bytes, so such a member compares
unchanged. An encrypted member with an MD5 on only one side stays unknown.
Remaining: declared sizes still compare only through observed bytes.
* docs(artifacts): check the contract's compared kinds against the table
docs/mcp-contracts.md named the format_metadata kinds that
compare_artifacts compares in prose, by hand, so a kind added to the
declaration table could go unnamed. The integration branch already omits
esp-flash-partition, which the ESP flash layout branch adds to the table.
The paragraph now names each compared kind by its identifier. A
conformance test requires it to name exactly the kinds whose table entry
has a declaration, which artifactFormatMetadata.ts exports as
COMPARED_FORMAT_METADATA_KINDS, and no kind that comparison ignores.
* test(artifacts): share the comparison acceptance MCP fixture
Six artifact comparison acceptance tests each copied the same MCP
steps: open_binary, inspect_artifact, the inventory_artifact substep
Evidence, then compare_artifacts. The coverage test repeated the
inspect steps three times and kept its own inventory lookup.
tests/fixtures/artifactComparisonMcp.ts now owns these steps on the
schema-validating connectLocalToolsMcp client.
connectArtifactComparisonMcp() returns call, inspect(path) and
compare(left, right). inventoryEvidence() reads the inventory Evidence
that an inspection retains, from MCP or CLI output. Each test still
writes its own authored input and keeps its expectations.
The coverage lookup matched evidence.operation, not the substep
operation. The inspection schema fixes both to inventory_artifact, so
both select the same substep.
The plist acceptance test does not change. It compares the inspection
Evidence itself across CLI and MCP and passes the comparison Evidence
to verify_reconstruction and find_changed_behavior.
No assertion or scenario changes.
* fix(artifacts): leave .NET bundle member declarations uncompared
Upstream main compares no .NET bundle member facts. Comparing `file_type`
changed `compare_artifacts` output beyond this branch's fixes, so the kind
stays uncompared until that is decided. A changed manifest still shows as a
content change of the host.
* fix(artifacts): leave HEX region declarations uncompared
The comparison table compared a region's start_address and length. A
region's path names its start and end addresses, and its length follows
from them, so two regions that pair at one path always declare the same
facts: the entry could never find a change. It only made a region with
equal bytes unknown when the other side recorded no metadata, such as a
directory of extracted regions. The kind is not compared, as on main.
The contract paragraph also stops naming .NET bundle members, which
|
||
|
|
89317a39fa |
fix(javascript): remove duplicate childNodes import (#8886)
* fix(javascript): remove duplicate childNodes import
The squash of #7967 re-added the import already introduced in
|
||
|
|
2d16a40a49 |
fix(ghidra): report selected project identity failures (#8379)
* fix(ghidra): report selected project identity failures * fix(ghidra): retain refusal until client shutdown |
||
|
|
85be0f70a6 |
fix(artifacts): guide retries for symlinked files (#8725)
* fix(go): suggest resolved path for rejected symlinks * test(go): cover unresolved symlink recovery paths * fix(go): omit schema recovery for resolved symlinks * fix(artifacts): share resolved-path recovery across inspectors * fix(sqlite): retain resolved database path through snapshot reads * fix(captures): retain symlink retry guidance * fix(artifacts): preserve retry hints through redaction and selected reads --------- Co-authored-by: dhruvac29 <dhruvac29@users.noreply.github.com> |
||
|
|
bd7002b441 |
fix(cli): export source Evidence for native value traces (#8297)
* fix(cli): export source Evidence for native value traces * fix(cli): export source Evidence for native value traces * fix(cli): preserve trace completeness in summaries |
||
|
|
064bb3f713 |
fix(firmware): reject staged files cut off at the per-file limit (#4430)
* fix(firmware): keep producer reports writable under small staging budgets extract_firmware passed max_output_bytes straight to prlimit --fsize. RLIMIT_FSIZE applies to every regular file the producer writes, including the mandatory report.json and unblob.log outside the staging tree, so a staging budget smaller than the report truncated it and failed the extraction even when every staged file fit. Derive the per-file ceiling as the larger of the staging budget and the 8 MiB report budget; polling and the final inventory still enforce the aggregate staging budget. Fixes #3888 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(firmware): keep files stopped at the size ceiling over budget The per-file RLIMIT_FSIZE ceiling equalled the largest size its readers accept, and both the staging inventory and the report reader accept a file of exactly their budget. A write the kernel stopped at the ceiling therefore left a truncated file that passed as complete. Raise the ceiling by one byte so such a file exceeds its budget and is rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(firmware): report the effective file-size ceiling on limit failures The exit path re-derived the staging budget for SIGXFSZ and process-limit recovery, so a small-budget run killed at the 8 MiB + 1 ceiling reported file_size_bytes 64 and said the cap was max_output_bytes. Pass the ceiling runFirmwareCommand installs and describe how it is derived. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(firmware): reject staged files cut off at the per-file limit RLIMIT_FSIZE stops a write at the limit without extending the file, and the staging inventory admits a file of exactly max_output_bytes. With a budget of 8 MiB or more the producer limit equalled the budget, so a staged file the kernel cut short published as a complete extraction; an inherited ceiling below the budget did the same. Derive the per-run limit once in firmwareFileSizeLimit as one byte above the largest budget, still capped by the inherited ceiling, so a file stopped at the limit exceeds its budget. When the inherited ceiling removes that byte, both staging inventories refuse a regular file that reaches the installed limit with a nonretryable file-size constraint naming the inherited limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(firmware): derive limit adjustability from the per-run limit The one byte of headroom makes the per-run file limit equal an inherited ceiling of exactly max_output_bytes + 1, but adjustability still compared that ceiling with the raw budget. A SIGXFSZ then advised raising max_output_bytes, which the same ceiling clamps back to the same limit. Decide adjustability in firmwareFileSizeAdjustable from firmwareFileSizeLimit itself: recommend a larger budget only when it raises that limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(firmware): compare file-size recovery with its owners The SIGXFSZ adjustability cases and the inherited-ceiling workflow matched fragments of remediation prose, so a harmless rewrite would fail them. Compare the emitted guidance with firmwareSignalLimitError for the expected branch, check that the two branches stay distinct, and compare the inventory refusal with firmwareFileSizeLimitReached. The selected limit, resource and retryability stay asserted as structured facts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> |
||
|
|
33e0bf101a |
fix(adb): admit completed path probes after daemon startup (#8858)
* fix(adb): admit completed path probes after daemon startup * test(adb): preserve startup probe and digest evidence |
||
|
|
e416ce1983 |
fix(cli): preserve Markdown validation error cells (#7978)
fix(cli): preserve markdown validation error cells |
||
|
|
61bb02d3df |
fix(artifacts): keep inventorying an ASAR whose member declares an invalid size (#8719)
* fix(artifacts): keep inventorying an ASAR whose member declares an invalid size A member whose declared size is not a non-negative safe integer, such as Figma Desktop's .codesign placeholder (size -1000), made readAsarListing throw and failed inventory of the whole archive without naming the entry. Listing now keeps such a member with a null size and a limitation naming the parsed value, as it already keeps an unreadable offset. Opening a packed member with a null size raises ArtifactEntryContentUnavailable, so inventory records it as unavailable and continues with the other members. ElectronFileScope keeps the strict size rule. Member metadata that still stops inventory now names the member and the rejected fields instead of reporting a generic malformed archive. Fixes #8715 * fix(artifacts): bound and preserve ASAR listing diagnostics Quote an unsafe path component or a string size through a 256-unit prefix plus its length, so one oversized header key cannot inflate the failure or the retained limitation. Render numeric sizes with String so a size that JSON.parse turned into Infinity is reported as Infinity rather than null. * fix(artifacts): keep ASAR listing diagnostics exact and located Report a numeric size beyond the safe-integer range as having no retained exact value instead of the number JSON.parse rounded or overflowed, name object and array sizes correctly, and interpret each declared size once without validating absent directory sizes. Quote member and parent paths through the bounded source quote, name the rejected root fields as member failures already do, and append the archive path to listing failures so a nested archive stays identifiable. * test(artifacts): match ASAR listing archive paths that contain spaces |
||
|
|
abc647bf6c |
feat(desktop): suggest managed members for Unity script assemblies (#5511)
* feat(desktop): suggest managed members for Unity predefined script assemblies Unity Mono triage listed only inspect_managed_artifact probes, mostly for engine and framework assemblies. Assembly-CSharp.dll and Assembly-CSharp-firstpass.dll directly in a managed directory with a UnityEngine reference witness now get an inspect_managed_members probe, ordered before every managed artifact probe. * docs(desktop): describe Unity Mono managed member probes * refactor(desktop): read Unity managed directory from the witness fact * refactor(desktop): reuse the managed directory index for predefined assemblies * feat(desktop): point Unity member probes at summary delivery first * test(desktop): no member probes without a Unity reference witness * docs(desktop): trim Unity member probe note * feat(desktop): suggest managed members in Unity IL2CPP backup folders Unity IL2CPP builds write <Product>_BackUpThisFolder_ButDontShipItWithYourGame next to the player. When shipped, its Managed/ holds pre-IL2CPP assemblies. A path convention now records the folder on Assembly-CSharp*.dll directly in its Managed/, including when the admitted root is the folder itself, and triage offers inspect_managed_members for those files. Correspondence to the shipped native code stays unknown. * docs(desktop): note IL2CPP backup folder member probes * docs(desktop): list IL2CPP metadata versions 24 and 27 for the metadata probe * style(desktop): merge stacked doc comments on desktopPathConventions * test(desktop): cover member probes in a macOS bundle managed directory * docs(desktop): note that member probes need the game folder as root * test(desktop): no member probe when the root is the Unity data folder * test(desktop): check the data-folder root still gets managed probes * test(desktop): check the IL2CPP backup convention against the probe bound --------- Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> |
||
|
|
398eb0d1f1 |
feat(setup): register LM Studio during setup (#8611)
* feat(setup): register LM Studio during setup Register rea in mcp.json in the LM Studio home through a new lm_studio client. The home follows LM Studio's own lookup, read-only: the path in ~/.lmstudio-home-pointer, else an existing ~/.cache/lm-studio, else ~/.lmstudio. An unreadable or relative pointer reports a path error instead of a guessed home. LM Studio parses mcp.json with strict JSON.parse, so comments, trailing commas and a BOM are rejected before any backup or write. The entry carries command, args and env with no type or timeout. Local models have small context windows, so the registration selects REA_MCP_INPUT_SCHEMA_PROFILE=compact unless a profile is set explicitly, and doctor reports an entry without a profile as stale. One predicate now owns that default for Kimi Code and LM Studio. * fix(doctor): resolve provider budgets for LM Studio as setup does |
||
|
|
103684bce1 |
fix(ghidra): guide recovery from unresolved procedure selectors (#8470)
* fix(ghidra): guide recovery from missing procedures * fix(ghidra): preserve mapped offset lookup recovery * fix(ghidra): distinguish unmapped procedure offsets * fix(ghidra): guide ambiguous selector recovery * fix(ghidra): clarify fresh import excludes project edits |
||
|
|
ebea0f009f |
fix(snapshot): retain one-shot Ghidra pseudocode results (#8089)
* fix(snapshot): retain one-shot Ghidra pseudocode results * test(snapshot): cover evidence-only pseudocode migration --------- Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com> |
||
|
|
4d2aff2ec6 |
fix(mcp): report session tool target requirements accurately (#7340)
* fix(mcp): report session tool target requirements accurately - Declare requiresActiveTarget on inspect_address_context in sessionToolContracts.ts so closed-session discovery stops advertising an address query that fails with target_unavailable. - Route session-kind tools through targetAvailability in CapabilityInventory.ts before the available fallback, and report target_unsupported with mode-level remediation for get_navigation_context and inspect_address_context on artifact and archive targets. - Keep each navigation mode's missing_operations and remediation aligned with the target restriction so recommendations name the target instead of a provider. * test(mcp): cover closed-session and artifact target requirements - Add CapabilityInventory controls for closed-session address and explicit-path APK availability, artifact and archive restrictions with per-mode navigation flags, and structured reasons instead of pinned remediation sentences. - Add a production stdio MCP regression in sessionTargetRequirements.test.ts that observes both binary_session entries before and after the calls, the target_unavailable address error, the successful unsigned-ZIP APK call, and the sibling navigation prerequisite. |
||
|
|
09c889b3b5 |
test(javascript): cover Request endpoint identity and provenance (#6365)
fix(javascript): resolve inline Request URLs in fetch calls |
||
|
|
1dc9e8774d |
fix(cli): keep marker-only Markdown paragraph facts literal (#7813)
Fixes #7634 Co-authored-by: Abelo9996 <Abelo9996@users.noreply.github.com> |
||
|
|
7570aabd9d |
docs(skill): route SPA passive capture through allowed_origins (#7252)
Default page-origin scope excludes cross-origin APIs. Point agents at JavaScript analysis for API-origin discovery, require page origin plus API origins in allowed_origins (list replaces default), and bump the packaged skill to version 60. |
||
|
|
b9138a1261 |
fix(native): select versioned arm64e slices for codesign by family name (#8713)
* fix(native): select versioned arm64e slices for codesign by family name Current clang marks arm64e slices with pointer-authentication ABI version 1 (subtype 0x81000002, lipo name arm64e.v1). The codesign selector table accepted only ABI version 0, so inspect-signature failed with capability_unavailable whenever such a slice was selected, for example on any universal arm64e + x86_64 binary built today. codesign has no versioned name: -a arm64e.v1 is "unknown architecture name", and -a arm64e resolves to the first FAT slice with the arm64e base subtype, whatever its ABI version. A selector is therefore exact only for that first slice. Accept any versioned pointer-authentication ABI for arm64e, and return a selector only when the slice is the first in table order to carry that name; a later slice sharing it stays unknown instead of being inspected through the earlier one. This also removes the separate duplicate-name check from signature coverage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(native): keep arm64e selectors unknown behind unmapped variants codesign resolves a name to the first FAT slice with the same CPU type and base subtype, so an earlier capability variant whose own name is unknown may shadow a later arm64e slice. Compare identities rather than successful name mappings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
54d819f382 |
fix(javascript): emit no timer for a literal non-function callback (#8044)
* fix(javascript): emit no timer for a literal non-function callback
Node validates the timer callback before creating a handle:
if (typeof callback !== 'function')
throw new ERR_INVALID_ARG_TYPE('callback', 'function', callback);
The throw is synchronous, before any timer exists, but the analysis reported a
`schedule` operation for such calls.
Measured on Node 24.22.1:
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(`code`, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(1n, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(true, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(void 0, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout([], 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout({}, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout() -> THROWS ERR_INVALID_ARG_TYPE
setImmediate("code") -> THROWS ERR_INVALID_ARG_TYPE
setInterval("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
setTimeout(function(){}, 0) -> OK
setTimeout(() => {}, 0) -> OK
setTimeout(fn, 0) -> OK
setTimeout(class {}, 0) -> OK (fails only when the class is invoked)
So a schedule is dropped only for an exactly readable literal that is not a
function, or for a missing argument. An identifier, a call result or a spread may
still be a function at runtime and keeps its schedule.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): scope the timer callback rule to Node's timers module
greptile flagged two problems with the first version.
P1 - browser timers disappeared. The rule was applied to bare globals, but
browser and Electron renderer sources reach this analysis without a Node/browser
distinction, and there `setTimeout("refresh()", 0)` is a real timer: the host
evaluates the string. The rule now applies only when the timer is imported from
`node:timers`, where that form cannot exist. A bare global schedule keeps its
fact whatever argument 0 holds.
P2 - unused import. `readExactJavaScriptLiteral` was imported but never used; the
helper reads shapes through `unwrapJavaScriptExpression`. Removed.
Measured on Node 24.22.1 (the throw is what makes a node:timers schedule
impossible; the bare-global case is intentionally left alone):
import { setTimeout } from "node:timers";
setTimeout("code", 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(null, 0) -> THROWS ERR_INVALID_ARG_TYPE
setTimeout(fn, 0) -> OK
--- bare global, host may be a browser ---
setTimeout("code", 0) -> kept as a schedule
24 cases cover both sides plus the cancellation link.
* fix(javascript): reject every non-function global and unary timer callback
Reviewing the timer callback guard against Node 24.22.1 found two gaps. Both are
in the same "a call that always throws must not emit a resolved fact" family.
**An unmodified global that is not a function was accepted.** The guard read
literal nodes only, so a bare global identifier passed through even though Node
rejects it before creating a handle:
setTimeout(undefined) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(NaN) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(Infinity) -> THREW ERR_INVALID_ARG_TYPE
`isUnmodifiedGlobalReference` keeps a shadowed binding out of the rule, so a
local `const NaN = () => {}` keeps its schedule.
**Only `void` was treated as a non-function unary operator.** Every unary operator
yields a primitive, and Node rejects a primitive callback:
setTimeout(!0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(~0) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(-1) -> THREW ERR_INVALID_ARG_TYPE
setTimeout(+"1") -> THREW ERR_INVALID_ARG_TYPE
setTimeout(typeof 1) -> THREW ERR_INVALID_ARG_TYPE
A class expression is still kept, because it passes Node's own `typeof` check and
only fails when invoked.
Fifteen regressions cover the rejected forms plus arrow, identifier, class,
spread and shadowed-global cases; ten fail on the previous commit.
* fix(javascript): let sloppy eval shadow the non-function global callback
Review finding. The new global check read `undefined`, `NaN` and `Infinity` as
unmodified globals, but sloppy `eval` can introduce a local function under one of
those names, which then shadows the global. Measured on Node 24.22.1 in script
scope:
function g() { eval("var undefined = () => {}"); setTimeout(undefined, 0); }
-> schedules a timer
function g() { eval("var NaN = () => {}"); setTimeout(NaN, 0); }
-> schedules a timer
`semanticOpaqueNameMayHaveChanged` now has to agree that no visible activation can
introduce such a name before the global reading is conclusive. In a module the
same `eval` is strict and introduces no outer binding, so the global reading still
holds and the timer is still dropped.
Three regressions cover the sloppy forms, and a fourth pins the no-eval cases as
still dropped. The three sloppy cases fail without the guard.
|
||
|
|
848f165417 |
feat(harmony): disclose unclassified inventory files (#4968)
* feat(harmony): disclose unclassified inventory files * test(harmony): let createEvidence own JSON admission in the partial-inventory case * test(harmony): extract the projection assertion from the archive journey * docs(harmony): qualify the unclassified disclosure rules and pin exact-case manifests * test(harmony): replace limitation prose with structured coverage facts The partial-inventory control froze the sentence "Source inventory coverage is partial; absence is unknown." verbatim. docs/testing.md asserts structured limit facts instead of freezing explanatory sentences and puts documentation wording in review, so the sentence is no longer substring-matched. `coverage.status` is a disjunction of independent causes in this projection: incomplete inventory scope, unavailable content, and an exceeded bridge budget. Asserting only `status: "partial"` left that attribution unstated, so the two other causes are now pinned as well. `unavailable_components` being empty and `bridge_candidate_coverage` being complete with zero candidates make the partial result attributable to the missing occurrence alone. Also drops an `artifact_id` comparison that could not fail: the fixture's four files carry the same bytes, the preceding assertion already pins that shared digest, and `artifact_id` is derived from the digest. Distinct paths sharing content stay covered by the path array and the independent digest expectations. Narrows a comment that claimed exact-case matching for every name in the control; only the manifest names are case-sensitive, because the App Pack child suffix match is case-insensitive. * docs(harmony): name the disclosure location for a nested child package The unclassified-disclosure paragraph used "there" to refer back to a clause that had just named `app_pack_children`, so a reader could take a nested `.hap` or `.hsp` member of a HAP or HSP root to be reported as an App Pack child. It is reported in `unclassified_components`: the child list is populated only for an App Pack root. Names the location explicitly. |
||
|
|
b668cea2af |
fix(javascript): emit no fs acquisition for a literal non-flag string (#8125)
* fix(javascript): emit no fs acquisition for a literal non-flag string
`fs.open` and `fs.openSync` validate a literal flag before touching the
filesystem:
if (!isFd(flags) && !FLAGS_SET.has(flags))
throw new ERR_INVALID_ARG_VALUE('flags', flags, ...);
The throw is synchronous, before a descriptor exists, but the analysis reported
an `acquire` operation for those calls.
Measured on Node 24.22.1 against a file that does not exist, so each valid flag
is exercised without an EEXIST masking it:
ACCEPTED as a flags string:
a a+ as as+ ax ax+ r r+ rs rs+ sa sa+ sr sr+ w w+ wx wx+ xa xa+ xw xw+
REJECTED with ERR_INVALID_ARG_VALUE:
"" A R W rb wb s s+ x r,x
A Python-style `"rb"`/`"wb"` is the common case: those are valid in Python and
fatal at the Node call site.
`FS_OPEN_FLAGS` in `javascriptStaticAnalysisHelpers.ts` already holds exactly
those 22 accepted flags. It is now exported and reused so the accepted set has
one owner. A numeric flag is a platform bitmask and a computed value may still be
valid, so only an exactly readable non-flag string is dropped.
`fs.createReadStream` keeps its acquisition: it does not validate the flag
synchronously, it returns a stream and reports the failure on the error event.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject every fs flag literal Node rejects
The first version only decided string flags. Node's check covers every flag
shape, and each one below throws `ERR_INVALID_ARG_VALUE` or `ERR_OUT_OF_RANGE`
synchronously, before a descriptor exists:
openSync(p, 1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, 1e400) -> THREW ERR_OUT_OF_RANGE
openSync(p, NaN) -> THREW ERR_OUT_OF_RANGE
openSync(p, Infinity) -> THREW ERR_OUT_OF_RANGE
openSync(p, 0n) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, true) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, false) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, {}) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, []) -> THREW ERR_INVALID_ARG_VALUE
--- accepted ---
openSync(p, 0) / 1 / 65536 / +5 -> ok
openSync(p) / undefined / null -> ok
openSync(p, "r") / "wx" / "sr" -> ok
`NaN` and `Infinity` are identifiers rather than literals, so they are decided
only through `isUnmodifiedGlobalReference`; a shadowed binding keeps the
acquisition.
* fix(javascript): judge an fs flag by its value, and ignore a shifting spread
Two P1 findings on the first version.
**A unary operator was judged by its operand.** `openSync(p, void 1.5)` passes
`undefined`, so Node applies the default read flag, but the branch saw a
fractional operand and dropped a real acquisition. `openSync(p, -0)` is the valid
zero bitmask and was also dropped. `staticFlagBits` now evaluates the operator:
openSync(p, void 1.5) -> ok (keeps the acquisition)
openSync(p, void 0) -> ok
openSync(p, -0) -> ok
openSync(p, -0.0) -> ok
openSync(p, +0) -> ok
openSync(p, ~-1) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, +1.5) -> THREW ERR_OUT_OF_RANGE
openSync(p, !0) -> THREW ERR_INVALID_ARG_VALUE
openSync(p, ~0) -> THREW EINVAL
openSync(p, typeof 1) -> THREW ERR_INVALID_ARG_VALUE
**A spread before the flag slot shifts the runtime position.** With
`args = [path, "w"]`, `openSync(...args, "0600")` opens with mode `"0600"`, but
argument 1 is then the mode rather than the flag. The check now keeps the
acquisition whenever argument 0 is a spread, because the flag it inspects is not
provably the flag at runtime.
* fix(javascript): leave a signed fs bitmask undecided
greptile caught that the flag check rejected every negative integer, but Node
accepts some signed bitmasks. Measured on Node 24.22.1 against an existing file:
openSync(p, -2147483648) -> ok
openSync(p, -1073741824) -> ok
openSync(p, -1) -> THREW EINVAL
openSync(p, -2) -> THREW EINVAL
openSync(p, 2147483647) -> THREW EINVAL
openSync(p, 2147483648) -> THREW ERR_OUT_OF_RANGE
openSync(p, -2147483649) -> THREW ERR_OUT_OF_RANGE
So the sign does not decide: some negative values open a descriptor and others do
not, and which is which is a platform bitmask question this analysis cannot
settle. The check now rejects only a non-integer or a value outside the signed
32-bit range, and leaves an in-range negative undecided — the same choice
`greptile` asked for, on the principle that a call which may succeed must keep
its acquisition.
`!`, `typeof` and `delete` are still rejected, because those yield a boolean or a
string regardless of the operand.
* fix(javascript): read a no-expression template literal flag
Review finding. A no-expression template literal is exactly readable, so it is the
same flag string Node receives, but the guard only read `StringLiteral`:
fs.openSync(p, ` + "`rb`" + `) -> THREW ERR_INVALID_ARG_VALUE
fs.openSync(p, ` + "`r`" + `) -> ok
The flag slot now accepts an interpolation-free template; an interpolated template
stays undecided.
Four regressions cover both directions; two fail without the change.
|
||
|
|
65311d4992 | fix(process): preserve literal temporary-root absence text (#8859) | ||
|
|
066a23475c |
fix(artifacts): classify bundle executables by their own name (#7365)
* fix(artifacts): classify bundle executables by their own name A directory bundle is selected for its executable, so the executable's canonical name classifies it; the bundle directory spelling is not an artifact alias. Bundle facts now reach artifact, archive and database targets so the admitted-source digest binds to the executable. Fixes #7362 * fix(artifacts): share the artifact identity across every artifact route The trailing-archive fallback also carries the bundle facts now, so a bundle whose executable is a script stub with an appended ZIP keeps its digest bound to the executable. * fix(artifacts): name the classification spelling at the resolver boundary The resolver already knows whether a bundle directory or a file was selected, so it hands the executable or the caller's spelling to the reader instead of inferring it from the Info.plist. Every admitted route now shares one identity, and the .app bundle operations accept a bundle whatever its executable admitted as. * fix(artifacts): route bundle keyed archive reads by bundle facts A bundle whose executable is a plist now carries bundleInfoPlist, so inspect_keyed_archive must read bundle-relative archives instead of the standalone route that compares the selected bundle directory with the executable. * refactor(artifacts): build the selected ESP flash identity from the shared owner The selected ESP flash route from main built path, source path and digest inline. Use the resolver's shared identity so it carries the same facts as every other admitted target. |
||
|
|
2f5c0155dc |
fix(web): omit importer integrity for the trusted resolver stub (#8693)
* fix(web): omit importer integrity for the trusted resolver stub Import-map integrity that covers the selected importer was applied to REA's replacement resolver script. Strip only those integrity entries from the installed map so native resolution can proceed while preserving dependency integrity and the caller-selected map identity. Fixes #8544 * fix(web): keep __proto__ import-map integrity keys on a null prototype |
||
|
|
54146627f0 |
fix(cli): emit compact JSON validation errors for demangle-swift (#7622)
* fix(cli): emit compact JSON validation errors for demangle-swift Piped and redirected --json argument diagnostics went through Incur's always-indented Formatter, bypassing REA's destination-aware writer. Buffer non-TTY SDK JSON like JSONL and rewrite through writeJsonOutput. * fix(cli): keep token-count for piped SDK JSON documents Compact JSON rewrite for non-TTY SDK output returned early before token controls, so --llms/--schema --format json --token-count printed the document instead of its count. * fix(cli): do not token-count non-document JSON captures Completion hooks and other non-JSON --json pipe output must stay verbatim; token-count on non-JSON SDK captures applies only with sdkDocumentControls. * test(cli): assert demangle JSON validation by code, not wording |
||
|
|
f285fd4f82 |
fix(javascript): emit no child process for a literal non-object options slot (#8019)
* fix(javascript): emit no child process for a literal non-object options slot
Node validates the child_process options object before creating anything, so a
literal primitive or a literal array in that slot throws ERR_INVALID_ARG_TYPE
synchronously and no child process exists. The analysis still reported a complete
spawn for them.
Measured on Node 24.22.1:
spawn(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], null) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], []) -> THROWS ERR_INVALID_ARG_TYPE
execFile(cmd, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, "status") -> THROWS ERR_INVALID_ARG_TYPE
fork(worker, [], "pipe") -> THROWS ERR_INVALID_ARG_TYPE
--- accepted by Node ---
spawn(cmd, args, undefined) -> SPAWNED
spawn(cmd, args, {}) -> SPAWNED
execFile(cmd, [], null) -> SPAWNED
fork(worker, [], null) -> SPAWNED
exec(cmd, "inherit") -> SPAWNED
The asymmetries matter: `spawn` rejects `null` and exempts only `undefined`,
while `fork` and `execFile` skip any nullish options, and `exec` spreads its
options and validates none of them. Drop a candidate only when the options slot
is a provable literal primitive or array, so an unresolved or non-literal value
stays a candidate.
`spawn(cmd, [], null)` is the sharpest case: it was indistinguishable from the
valid `undefined` form, including a resolved connects-stdio relation, for a call
that dies before any child exists.
* chore: retrigger title and readiness checks after the title was corrected
* fix(javascript): reject literal options Node shall reject, and drop a stray file
Two review findings on #8019.
greptile: `rejectsOptionsShape` still kept `spawn("tool", [], 0n)`, `-0` and
`1e400`, which Node also rejects before creating a child
(`ERR_INVALID_ARG_TYPE: The "options" argument must be of type object`). Those
forms are exactly the ones `readExactJavaScriptLiteral` refuses to price — a
BigInt, a non-finite numeric literal and a negated numeric literal — so read the
shape directly instead of widening the shared exact-value reader. `NaN` is an
identifier rather than a literal, so it still needs scope resolution and is left
as a documented gap.
Also remove `.rea-hopper-stage-YOryZ6/`, a local Hopper installation stub that an
`add -A` swept into the commit. It is unrelated to this change.
* fix(javascript): keep a void options slot, reject ! and ~
greptile flagged that `isUnpricedPrimitiveLiteral` dropped `spawn("tool", [], void 0)`,
`fork(..., void 0)` and `execFile(..., void 0)`. It checked the unary operand without
the operator, but `void <expr>` evaluates to `undefined`, which is the one options
value Node accepts in place of an object.
Measured on Node 24.22.1:
spawn(cmd, [], void 0) -> SPAWNED
spawn(cmd, [], void 5) -> SPAWNED
spawn(cmd, [], -0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], -5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], +5) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], !0) -> THROWS ERR_INVALID_ARG_TYPE
spawn(cmd, [], ~0) -> THROWS ERR_INVALID_ARG_TYPE
So the operator now decides: `void` keeps the spawn, `!` and `~` reject whatever
their operand, and `+`/`-` reject only when the operand is a numeric literal.
* fix(javascript): reject a non-finite global in the child-process options slot
An unshadowed `NaN` or `Infinity` in the options slot is a number, and Node's
options check rejects a number exactly as it rejects any other primitive. The
guard read only literal nodes, so the global identifier passed through. Measured
on Node 24.22.1:
spawn("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
spawn("e", Infinity) -> THREW ERR_INVALID_ARG_TYPE
execFile("e", NaN) -> THREW ERR_INVALID_ARG_TYPE
fork("x.js", NaN) -> THREW ERR_INVALID_ARG_TYPE
--- accepted ---
exec("e", NaN) -> ok, because exec validates no options
spawn("e", undefined) -> ok, the one accepted global
spawn("e", null) -> ok
spawn("e", []) / ({}) -> ok
`isUnshadowedGlobal` keeps a local binding named `NaN` out of the rule, so a
shadowed value keeps its fact. Seven regressions cover the rejected forms plus
`exec`, `undefined`, `null`, an array, an object and a shadowed `NaN`; all seven
fail on the previous commit.
|