[GH-ISSUE #4553] [Design] Add a read-only browser extension manifest inspector #2366

Open
opened 2026-10-17 21:03:47 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @abundantbeing on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4553

Before submitting

  • I searched the open and closed issues and pull requests for a related discussion.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This discussion does not include credentials, private samples, or sensitive target data.

Decision needed

Should REA add a read-only inspect_browser_extension for one local CRX2, CRX3, XPI/ZIP, or unpacked manifest.json, modeled on inspect_pe_resources / inspect_sqlite_database?

The question is not whether REA should execute extensions. It should not.

Context and evidence

Browser tracker #39 already names extensions as part of the web-application surface: "Inspect shipped scripts, modules, chunks, source maps, WebAssembly, workers, service workers, manifests, and extensions."

What exists today does not answer the package question.

  • analyze_javascript_application explains modules after someone has already unpacked a tree. It does not know manifest_version, host permissions, content-script matches, or a Gecko id.
  • ZIP inventory lists archive entries. A CRX file starts with Cr24, not PK, so the existing ZIP classifier does not admit it. An .xpi that is a ZIP is inventoried as a generic archive, not as an extension manifest.
  • A search of open issues and pull requests found no CRX, XPI, or WebExtension proposal. #3687 is a VS Code .vsix discussion. It is a different host contract.

The analyst question this leaves open:

For this extension package, what does the manifest declare, and what did we not look at?

That question shows up whenever two builds of the same extension need a surface diff before anyone reads the bundles: Chromium CRX versus Firefox XPI, or a store package versus an unpacked tree. Declared permissions, content-script match patterns, background entry, and browser_specific_settings.gecko.id are the first facts. They are not the implementation.

Expected interaction with targets

  • Read existing files or captured data
  • Launch or attach to a process
  • Inject instrumentation or run scripts/code
  • Change process memory or registers
  • Write or change files/configuration
  • Contact network services
  • Recover protected code or work around protections
  • No target interaction (documentation or usability only)
  • Not sure yet

Boundary details

In scope for a first operation:

  • Admit one regular file: CRX2, CRX3, ZIP/XPI, or a file whose basename is manifest.json.
  • Measure CRX header lengths. Do not decode the CRX3 protobuf and do not treat a signature as verified.
  • Read only the root manifest.json entry. Leave extension JavaScript, HTML, and _locales unopened.
  • Return observed manifest text: permissions, host permissions, content-script matches, background kind, Gecko id, action keys, CSP text, command names, and unknown top-level keys.
  • Keep __MSG_*__ placeholders unresolved.
  • Fail closed on a nested manifest, an encrypted entry, a bad container, or an exhausted budget. No partial manifest.

Out of scope, and not implied by opening this discussion:

  • Installing or loading the extension
  • Launching a browser, injecting a page, or contacting a store
  • Verifying or stripping a CRX signature
  • Resolving locale catalogs
  • Comparing two packages
  • Parsing extension JavaScript (analyze_javascript_application remains that tool, after the caller unpacks)
flowchart TD
  pkg["CRX, XPI, ZIP, or manifest.json"] --> inspect["inspect_browser_extension"]
  inspect --> surface["Declared permissions, matches, background, Gecko id"]
  surface --> code{"Need the implementation?"}
  code -->|yes| unpack["Caller unpacks, then analyze_javascript_application"]
  code -->|no| stop["Stop. Declared text is not runtime behavior."]
  unpack --> live{"Need a live page?"}
  live -->|yes| observe["Observe a browser the caller already opened"]
  live -->|no| evidence["Keep the Evidence id and the limitations"]

Workflow

A self-generated MV3 fixture, not a published extension, projects like this:

  • format crx3, header bytes measured, signature not verified
  • permissions storage, nativeMessaging
  • host permission https://example.com/*
  • background kind service_worker
  • Gecko id taken from browser_specific_settings, with a different legacy applications.gecko.id retained as conflicting_id
  • message keys extName and action left unresolved
  • unknown top-level keys listed rather than dropped

Options and tradeoffs

  1. New inspect_browser_extension tool. Recommended. The analyst question and the result contract are not the ZIP inventory and not the JavaScript graph. Same shape as the portable PE resource inspector: one file, no engine, Evidence inline, fail closed.
  2. Teach ZIP classification to accept Cr24 and stop there. Smaller, but a file list still does not answer the manifest question. CRX admission would remain a private preprocessing step.
  3. Documentation only. Tell callers to strip the CRX header themselves and then run JavaScript analysis. That preserves the gap #39 already named.

Verification available so far

I have a prototype or observed results.

A draft implementation is in abundantbeing/rea@50ef7cbe268169d256c263941f74075b59b1458b, branch feat/browser-extension-manifest. It is a prototype for this decision. It is not a request to merge ahead of a scope answer.

Local evidence, source checkout of morluto/rea main plus that commit:

  • npx vitest run src/browserExtension — 5 passed. Covers CRX3 surface, CRX2 header lengths, ZIP/XPI, unpacked manifest.json, nested-manifest refusal, non-package refusal, entry budget, and one admitted-file provider/Evidence read.
  • npm run check:fast — typecheck and lint passed.
  • npx vitest run tests/conformance/readmeConsistency.test.ts — 5 passed.

Not run: npm run docs:check, real-provider lanes, and a packaged CLI smoke of rea inspect-browser-extension. Linux CI remains the full catalog check. One existing server-identity assertion fails on this Windows host because path.resolve("/packaged/rea.mjs") becomes D:\packaged\rea.mjs; that file was not part of the change.

Success criteria and open questions

A useful result is one Evidence record whose normalized result is the declared surface above, with limitations that say the signature was not verified and the code was not parsed.

Questions for maintainers:

  1. Is this the right tool boundary, or should CRX admission land first as archive classification?
  2. Should a later compare_browser_extensions be a separate contract, or is one inspect tool enough until a second caller shows up?
  3. Are the default budgets right: 64 MiB file, 2 MiB manifest, 20,000 central-directory entries?

References

Originally created by @abundantbeing on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4553 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related discussion. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This discussion does not include credentials, private samples, or sensitive target data. ### Decision needed Should REA add a read-only `inspect_browser_extension` for one local CRX2, CRX3, XPI/ZIP, or unpacked `manifest.json`, modeled on `inspect_pe_resources` / `inspect_sqlite_database`? The question is not whether REA should execute extensions. It should not. ### Context and evidence Browser tracker #39 already names extensions as part of the web-application surface: "Inspect shipped scripts, modules, chunks, source maps, WebAssembly, workers, service workers, manifests, and extensions." What exists today does not answer the package question. - `analyze_javascript_application` explains modules after someone has already unpacked a tree. It does not know `manifest_version`, host permissions, content-script matches, or a Gecko id. - ZIP inventory lists archive entries. A CRX file starts with `Cr24`, not `PK`, so the existing ZIP classifier does not admit it. An `.xpi` that is a ZIP is inventoried as a generic archive, not as an extension manifest. - A search of open issues and pull requests found no CRX, XPI, or WebExtension proposal. #3687 is a VS Code `.vsix` discussion. It is a different host contract. The analyst question this leaves open: > For this extension package, what does the manifest declare, and what did we not look at? That question shows up whenever two builds of the same extension need a surface diff before anyone reads the bundles: Chromium CRX versus Firefox XPI, or a store package versus an unpacked tree. Declared permissions, content-script match patterns, background entry, and `browser_specific_settings.gecko.id` are the first facts. They are not the implementation. ### Expected interaction with targets - [x] Read existing files or captured data - [ ] Launch or attach to a process - [ ] Inject instrumentation or run scripts/code - [ ] Change process memory or registers - [ ] Write or change files/configuration - [ ] Contact network services - [ ] Recover protected code or work around protections - [ ] No target interaction (documentation or usability only) - [ ] Not sure yet ### Boundary details In scope for a first operation: - Admit one regular file: CRX2, CRX3, ZIP/XPI, or a file whose basename is `manifest.json`. - Measure CRX header lengths. Do not decode the CRX3 protobuf and do not treat a signature as verified. - Read only the root `manifest.json` entry. Leave extension JavaScript, HTML, and `_locales` unopened. - Return observed manifest text: permissions, host permissions, content-script matches, background kind, Gecko id, action keys, CSP text, command names, and unknown top-level keys. - Keep `__MSG_*__` placeholders unresolved. - Fail closed on a nested manifest, an encrypted entry, a bad container, or an exhausted budget. No partial manifest. Out of scope, and not implied by opening this discussion: - Installing or loading the extension - Launching a browser, injecting a page, or contacting a store - Verifying or stripping a CRX signature - Resolving locale catalogs - Comparing two packages - Parsing extension JavaScript (`analyze_javascript_application` remains that tool, after the caller unpacks) ```mermaid flowchart TD pkg["CRX, XPI, ZIP, or manifest.json"] --> inspect["inspect_browser_extension"] inspect --> surface["Declared permissions, matches, background, Gecko id"] surface --> code{"Need the implementation?"} code -->|yes| unpack["Caller unpacks, then analyze_javascript_application"] code -->|no| stop["Stop. Declared text is not runtime behavior."] unpack --> live{"Need a live page?"} live -->|yes| observe["Observe a browser the caller already opened"] live -->|no| evidence["Keep the Evidence id and the limitations"] ``` ![Workflow](https://raw.githubusercontent.com/abundantbeing/rea/feat/browser-extension-manifest/docs/assets/browser-extension-workflow.svg) A self-generated MV3 fixture, not a published extension, projects like this: - format `crx3`, header bytes measured, signature not verified - permissions `storage`, `nativeMessaging` - host permission `https://example.com/*` - background kind `service_worker` - Gecko id taken from `browser_specific_settings`, with a different legacy `applications.gecko.id` retained as `conflicting_id` - message keys `extName` and `action` left unresolved - unknown top-level keys listed rather than dropped ### Options and tradeoffs 1. **New `inspect_browser_extension` tool.** Recommended. The analyst question and the result contract are not the ZIP inventory and not the JavaScript graph. Same shape as the portable PE resource inspector: one file, no engine, Evidence inline, fail closed. 2. **Teach ZIP classification to accept `Cr24` and stop there.** Smaller, but a file list still does not answer the manifest question. CRX admission would remain a private preprocessing step. 3. **Documentation only.** Tell callers to strip the CRX header themselves and then run JavaScript analysis. That preserves the gap #39 already named. ### Verification available so far I have a prototype or observed results. A draft implementation is in abundantbeing/rea@`50ef7cbe268169d256c263941f74075b59b1458b`, branch `feat/browser-extension-manifest`. It is a prototype for this decision. It is not a request to merge ahead of a scope answer. Local evidence, source checkout of morluto/rea main plus that commit: - `npx vitest run src/browserExtension` — 5 passed. Covers CRX3 surface, CRX2 header lengths, ZIP/XPI, unpacked `manifest.json`, nested-manifest refusal, non-package refusal, entry budget, and one admitted-file provider/Evidence read. - `npm run check:fast` — typecheck and lint passed. - `npx vitest run tests/conformance/readmeConsistency.test.ts` — 5 passed. Not run: `npm run docs:check`, real-provider lanes, and a packaged CLI smoke of `rea inspect-browser-extension`. Linux CI remains the full catalog check. One existing server-identity assertion fails on this Windows host because `path.resolve("/packaged/rea.mjs")` becomes `D:\packaged\rea.mjs`; that file was not part of the change. ### Success criteria and open questions A useful result is one Evidence record whose normalized result is the declared surface above, with limitations that say the signature was not verified and the code was not parsed. Questions for maintainers: 1. Is this the right tool boundary, or should CRX admission land first as archive classification? 2. Should a later `compare_browser_extensions` be a separate contract, or is one inspect tool enough until a second caller shows up? 3. Are the default budgets right: 64 MiB file, 2 MiB manifest, 20,000 central-directory entries? ### References - Browser tracker: #39 - Tool shape: [docs/tool-design.md](https://github.com/morluto/rea/blob/main/docs/tool-design.md) - Nearest portable inspector: `inspect_pe_resources` - Prototype guide: [docs/browser-extension-analysis.md](https://github.com/abundantbeing/rea/blob/feat/browser-extension-manifest/docs/browser-extension-analysis.md)
Author
Owner

@abundantbeing commented on GitHub (Oct 16, 2026):

Prototype for this decision, kept as a draft until the scope answer and human readiness declaration: https://github.com/morluto/rea/pull/4554

<!-- gh-comment-id:6222191642 --> @abundantbeing commented on GitHub (Oct 16, 2026): Prototype for this decision, kept as a draft until the scope answer and human readiness declaration: https://github.com/morluto/rea/pull/4554
Author
Owner

@uyg7x commented on GitHub (Oct 16, 2026):

Hey @abundantbeing , Can i work on this issue .

<!-- gh-comment-id:6223606163 --> @uyg7x commented on GitHub (Oct 16, 2026): Hey @abundantbeing , Can i work on this issue .
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2366
No description provided.