* 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>
4.4 KiB
WeChat mini-program wxapkg archives
inspect-artifact and extract-artifact read plaintext WeChat mini-program
package (.wxapkg) containers without running WeChat or an unpacker. The
plaintext form starts with a 0xBE mark and an 18-byte big-endian header:
an unknown info field producers leave zero, the member-table byte length,
the member-data byte length, a 0xED mark and the member count. Producers
either count only the name/offset/size rows in that table length or include
the four-byte member count as well; both declarations are accepted. The table
region then carries one name/offset/size row per member; member bytes are
stored verbatim at absolute file offsets.
Inspection retains the header facts: info, index and data lengths, member
count and directory status. Members retain declared source paths, absolute
carrier offsets and declared sizes, so source_location reports real file
positions. Member reads only examine their declared range in bounded 64 KiB
chunks. Carrier freshness is checked when a member is opened and after its
stream completes. Reader close cancels and joins outstanding streams before
releasing its descriptor.
Encrypted PC-client packages start with the V1MMWX magic instead of the
plain header. They keep their format identity but report encrypted
directory status with unknown coverage: member identities and bytes remain
unknown and no decryption is attempted, so extraction refuses with a typed
error rather than producing ciphertext. Declared tables or data regions
that exceed the file, member extents outside the declared data region,
truncated rows, invalid UTF-8 names, and counts that cannot fit their table
are malformed-input errors.
Resource bounds
These are resource policies, not format maxima. The encoded member table is limited to 16 MiB and its buffer, parsed rows, retained entries and escaped path projections share a conservative 64 MiB admission budget, reserved before allocation; a declared member name is limited to 64 KiB. A reader admits four active member streams. The shared archive plaintext-work ceiling is the larger of 256 MiB and 500 times carrier size: every declared member extent is charged, including aliases that overlap one stored range. A separate member-I/O budget allows four times the larger of 1,024 and the sum of the member count and declared 64 KiB plaintext chunks, bounding repeated tiny-member reads independently of output bytes.
CLI
rea inspect-artifact app.wxapkg --detail summary \
--evidence_output evidence/package.json --json
rea extract-artifact app.wxapkg --json
Detection is byte-based on the 0xBE/0xED marks or the V1MMWX magic, so
.wxapkg, __APP__.wxapkg and extensionless cache files all classify as
wxapkg containers. Compact delivery keeps complete Evidence in the
requested bundle.
Verification scope
Synthetic fixtures cover plaintext headers, multiple members, empty members, chunked reads, unicode and dotted names, malformed marks, truncated tables, invalid UTF-8, table and directory-budget admission, member extents inside the header, the index, or beyond the declared data region, a data region declared past the file, a one-byte member whose table length includes the member count, an overstated first row consuming its successor, V1MMWX packages, cancellation inside member reads, paused iteration, close during I/O, cleanup retry, stream capacity and carrier freshness. Compiled full/compact MCP tests cover inventory and extraction.
Real producer verification used plaintext 0xBE packages from a local macOS
WeChat mini-program cache (__APP__.wxapkg main packages and
__PLUGINCODE__.wxapkg plugin packages across three distinct app
directories): every member extracted byte-identical to its declared carrier
range, including __plugin__/-namespaced members and the producer's
root-prefixed (/name) member convention. A cached V1MMWX PC package
reports the encrypted directory state with unknown member facts and refuses
extraction.
wxml/wxss source recovery, compiled bundle semantics, subpackage
relationships and runtime behavior are outside this container reader's
verification scope.
Artifact comparison pairs members by their package-relative paths and
compares their bytes. It does not compare wxapkg or wxapkg-entry
metadata: header fields repeat the package bytes, index is layout, and
source_path keeps a producer root prefix that the occurrence path strips.