# Ext Desk > Paste a browser extension's `manifest.json` — and the service worker, content scripts and > extension pages beside it — and work that same bundle through four lanes: a Manifest V3 and > least-privilege permission audit, a service-worker lifecycle pass that replays an eviction against > every listener, a content-script pass that costs what the injection does to every page it runs on, > and a Chrome Web Store submission pack that writes the single-purpose statement and one > justification per permission. A free in-browser reader parses the manifest the way Chrome's loader > does before any model runs. Live at https://ext-desk.skillsafe.ai/ · API tutorial at https://ext-desk.skillsafe.ai/api.html ## What problem it solves An extension that loads unpacked on your own machine can still fail three different ways: Chrome's loader rejects the manifest, the service worker stops working once it has been evicted, or the Chrome Web Store rejects the submission over permissions you cannot justify. Those failures show up at different times — the last one after a week of waiting — and none of them is visible from "it works here". This app reads the bundle for all three before you upload. ## The work object One extension bundle, pasted or dropped as files. `manifest.json` is the anchor; the service worker, content scripts, injected CSS and extension pages are read as evidence about whether the manifest matches what the extension actually does. Separate files are marked with a line reading `// file: ` (the folder picker writes those markers for you). A bare JSON object with a `manifest_version` key is treated as `manifest.json` with no marker at all. ## The four lanes | Lane id | What it answers | Postures | | --- | --- | --- | | `manifest` | Does this load, does it have the access it needs, and can it be uploaded? | `mv3-clean`, `mv3-fixable`, `mv3-broken` | | `worker` | Does the background logic still work after Chrome evicts the worker? | `wakes-reliably`, `wakes-with-gaps`, `dies-between-events` | | `inject` | What does the content script cost every page it runs on? | `light-touch`, `needs-trimming`, `page-hostile` | | `submit` | Will a store reviewer accept these permissions? | `submission-ready`, `needs-rewrite`, `will-be-rejected` | Every lane takes the same bundle. The handoff is a button: a manifest audit hands its permission list to the submission pack, and the manifest's `background` and `content_scripts` entries are what make the worker and inject lanes available at all. A lane whose subject is not in the paste is disabled with the reason, rather than being run against nothing. ## The free lane — what runs in the browser with no account and no credits The reader is not a preview of the paid lanes; it is the accountability record for them. Every flag it raises is sent into the run and must come back addressed, and the result page shows any flag the model failed to mention. - **Parses `manifest.json` the way Chrome's loader does.** Chrome reads it as strict JSON: one comment or one trailing comma and the extension does not load. Both are reported with their line, then stripped so the rest of the read still happens. - **Validates every match pattern.** The missing trailing path that silently voids a host permission, the host wildcard that is not a leading label, the wildcard over a whole TLD that quietly means every site. - **Validates the version string.** One to four dot-separated integers, 0-65535, no leading zeros — the rule behind an upload rejection whose message does not say which part was wrong. - **Cross-references permissions against code, both directions.** A permission nothing in your code calls, and a `chrome.*` namespace you call with no permission declared behind it. - **Shows the install dialog.** Not a count — the actual warning strings the user reads, per permission. - **Replays an eviction against the worker.** Listeners registered inside a callback (a restart never sees them), top-level state written after startup, a timer longer than the eviction window, a keepalive loop, an `async` `onMessage` handler that closes the port before it replies, MV2 APIs that throw under MV3. - **Costs the content scripts.** Injection timing against match breadth, unthrottled subtree observers, message chatter, `innerHTML` from page data, a `message` listener with no origin check, `MAIN`-world scripts calling APIs that are not there. - **Derives the Chrome floor.** The highest `minimum_chrome_version` your own API calls require, checked against the one you declared. - **Checks fetch reachability.** An origin the code fetches that no `host_permissions` pattern covers — including the common case where the pattern is sitting in `permissions`, where MV3 ignores it. - **Scans for secrets.** A published extension can be unzipped by anyone, so a hard-coded key or an OAuth client secret in the bundle is a published one. - **Counts the delta as you fix things.** Which flags cleared, which are still open, which are new since the reviewed baseline. ## The API contract Base URL `https://api.skillsafe.ai/v1/app-api`. The run input is the object itself: ```json { "task": "manifest", "bundle": "// file: manifest.json\n{ ... }\n\n// file: sw.js\n...", "notes": "submitting the MV3 version this week", "prescan": { "facts": {}, "flags": [], "keys": [], "permissions": [] } } ``` `task` selects the lane and decides the shape of the reply. `prescan` is optional but strongly recommended: without it the model has nothing to reconcile against and the reconciliation table comes back empty. Every lane returns one JSON object with the same envelope — `task`, `title`, `posture`, `confidence`, `verdict`, `exec_summary`, `findings[]`, `coverage_check[]`, `artifacts[]`, `assumptions[]`, `open_questions[]`, `next_steps[]`, `summary` — plus exactly one lane array: `keys` for `manifest`, `events` for `worker`, `scripts` for `inject`, `justifications` for `submit`. Full field lists, allowed enum values and one worked example per lane are in [api.html](https://ext-desk.skillsafe.ai/api.html). ## Model and cost `gpt-terra` (currently `gpt-5.6-terra`), publisher markup 1000 bps. `estimate` is free and per-lane; the hold prices the full output cap and the actual charge is usually far lower. Signed-in users only for a run; the whole free reader, both bundled examples and every saved example result work before sign-in. ## Limits worth knowing - Nothing here loads, packs or runs your extension. There is no browser profile and no store API behind the page — every judgement is a read of the text you pasted. - Store policy is summarised as it stood when this app was written. It is not a substitute for the Chrome Web Store program policies, and a clean result here is not an approval. - The reader is a pattern reader with no knowledge of your intent. Whether a permission is genuinely needed usually depends on a feature it cannot see, which is why the paid lanes prefer "narrow it to this" over "delete it". - Chrome and Chromium extensions are the subject. Firefox and Safari differences are mentioned where they matter but are not the target. ## Source skills A derived work built on four published agent skills about building browser extensions, credited in the app, in its footer and in its system prompt: - `@xenitv1/browser-extension` — Manifest V3 depth, service-worker persistence through alarms and the offscreen API, the Side Panel API, cross-browser compatibility. - `@sickn33/chrome-extension-developer` — background scripts, service workers, content scripts and cross-context communication. - `@pproenca/chrome-extension` — MV3 performance and code-quality guidelines for workers, content scripts, message passing and storage. - `@davila7/browser-extension-builder` — extension architecture, popup UI, monetisation and Chrome Web Store publishing. None of them is republished here and none of them is executed.