ext-dev-loopBLOCK
Agent skill + zero-dependency CDP harness for the browser-extension dev loop: live SW console, scratch isolation, host auto-discovery/download
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
A skill + zero-dependency harness that closes the dev loop for Chrome/Edge (MV3) browser extensions: the agent launches a disposable browser, loads the unpacked extension, reads the service worker's console live, drives the extension's own pages, sends messages to the worker, and reproduces issues on real sites — without the user copying anything out of DevTools, and without ever touching the user's own browser sessions.
you: "the extension is broken on this gallery site again" agent: doctor → probe --headless --script steps.json → real SW console + real answers
What it does
- `scripts/probe.mjs` — launch/reload/watch/live-SW-console/message/screenshot in one
invocation (--script batches N steps against one browser). Isolation is enforced: a scratch --user-data-dir under .ext-dev-loop/ roots is the only profile it will ever start, downloads are redirected out of the user's folders, shutdown goes through CDP Browser.close then the PID tree — never by image name.
- `scripts/doctor.mjs` — will the loop work HERE, for THIS extension: hosts and their
measured ability to load an unpacked extension, scratch-root hygiene, the probe's own safety guards, every file the manifest names. --live adds one headless launch.
- `scripts/get-browser.mjs` — find a usable host, or download Chrome for Testing
(official Google feed) into ~/.ext-dev-loop/browsers/ when no installed browser can host an unpacked extension.
- `scripts/firefox.mjs` — a second engine (WebDriver BiDi): load the tree as a
temporary add-on, page-level measurements. Reached as probe.mjs --browser firefox.
- `scripts/ports.mjs` / `instances.mjs` — lab hygiene: sweep/close leftover lab
browsers through their own debug endpoints; report live ports with no registry record.
Install (any agent harness that reads SKILL.md folders)
git clone https://github.com/Sucotasch/ext-dev-loop ~/.agents/skills/ext-dev-loop
then junct
e7eae652d5eeOBSERVED · 2026-10-10Install
Commands as the repository documents them. They are shown, not run.
git clone https://github.com/Sucotasch/ext-dev-loop ~/.agents/skills/ext-dev-loop
Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| codex | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: ext-dev-loop
description: Use when developing a browser extension.
---
# ext-dev-loop
Closes the extension dev loop. Instead of the user reloading the extension, copying
SW console output and pasting errors back, the agent owns the cycle — launch, reload,
live service-worker logs, reproduce on a page — in a browser instance that cannot
touch the user's own sessions.
## Safety first: you are a guest on this machine
Three rules, all ENFORCED by `scripts/probe.mjs` rather than trusted to memory:
1. **Every launch carries `--user-data-dir` inside a `.ext-dev-loop` scratch root** —
`./.ext-dev-loop/<ext>/profile` by default (findable, deletable, gitignored),
`~/.ext-dev-loop/<name>/profile` when one profile should serve several projects.
A `--profile` outside those roots is refused (exit 2) with the reason, before
anything is spawned. Without this, a launch lands in the user's real, logged-in
session.
2. **Never launch a browser to ask it something.** Measured 2026-09-19 on Windows:
`msedge.exe --version` does not print a version — it *starts a browser*, with the
default profile, i.e. the user's own Edge session. Read versions from the install
directory instead (`versionOf()` in probe.mjs); measured: Edge `153.0.4234.32`,
Chrome `141.0.7390.76`.
3. **Never kill by image name.** The user's own 100+ browser processes look exactly
like the ones you started. Shutdown is CDP `Browser.close` on the instance's own
debugging endpoint, with `taskkill /F /T /PID <spawned pid>` as fallback.
`--dry-run` prints host, version, profile and flags and exits BEFORE any spawn
(verified: no spawn on any path, refusals included) — use it as the cheap first step
and as the way to check the guards.
## Verified live
- 2026-09-19, Edge 153.0.4234.32, Windows, this project's extension: the unpacked
extension loads with `--load-extension`, and the SW is found by evaluating
`chrome.runtime.getManifest().name` per service_worker target ("SunoGrab - Suno
Audio Downloader", v3.0.0) — among ~17 unrelated `msedge.exe` processes and several
component-extension SWs.
- Chrome ≥137 is refused up front (Chrome 141 here): it ignores `--load-extension`.
`--allow-chrome` overrides knowingly. The preferred host changed to **Chrome for Testing 137**
after an Edge 154 update silently broke unpacked-extension loading — see fact 1.
- An idle MV3 SW produced **0 console lines in 70 s**. Nothing happens until work is
triggered, so "I streamed the console" is not by itself evidence of anything.
- **An attached SW does not go idle.** With `Runtime.enable` attached it was still the
same target after 70 s of silence (0 console lines, 0 exceptions), twice. This is the
practical reason to attach first and ask questions later: the world you are watching
is the world that stays alive.
- `/json/new?<url>` opens **both** a site page and an extension page:
`chrome-extension://<id>/options.html` (HTTP 200, target created, and the page really
rendered — title plus its 5 format radios). `Target.createTarget` was not needed.
- **A content script lives in an isolated world, and it is reachable.** On suno.com the
page target exposed, next to several default contexts, one with
`auxData.isDefault === false`, `type=isolated`, `origin=chrome-extension://<id>` and
`name` = the extension's name. In THAT context `chrome.runtime.id` and the manifest's
own globals exist (`typeof lamejs === "object"`, the vendored encoder); the very same
expression in a default context answers `runtimeId: null`, `lamejs: "undefined"`.
`--eval ... --in isolated` does this from the command line.
- **Console attribution has a catch.** `Runtime.consoleAPICalled` carries
`executionContextId` (the older `context` field is absent in this build): a log issued
in the isolated world arrived with the isolated context's id. But a page has SEVERAL
default contexts (top frame, about:blank iframes), so match an id against the context
list (`isDefault`/`origin`) instead of assuming one context per page.
- **The loop can drive the product.** From the options page target,
`chrome.runtime.sendMessage({action:"ui:queueState"})` returned the SW's real summary
— so a page you opened is not just readable, it can be used to make the extension do
something and then read the answer.
- **Headless loads unpacked extensions.** `--headless=new` + `--load-extension` found the
SW (same id, v3.0.0) and rendered extension pages normally — a visible window is needed
only for what a human must do (a login). Measured with `doctor.mjs --live`.
- **Screenshots work headless too**, at the viewport size (`--window-size=1100,820` →
a 1076×728 PNG, ~60 KB): `--shot` on `options.html` and on a heavier page (a DOM-built
tab) both produced valid PNGs.
- The extension id an unpacked extension gets is **stable for the same path + profile**
(`chrome-extension://<id>/...` kept working across runs), so pages can be addressed by
URL in scripts once you have seen the id once.
- **A scratch profile is not an empty profile.** Edge installs registry/managed
extensions into it (measured: a third-party security extension's content script was
running on suno.com in a profile that had never seen a human, and its stderr noise
`external_registry_loader_win.cc` appears on every launch). Consequences: identify the
SW by manifest name, and pick the isolated world by YOUR extension's id — "the first
non-default context" can be somebody else's. `probe.mjs --in isolated` does that, and
says so when it sees a foreign world.
- A single-page app may render nothing 2.5 s in. `--delay <ms>` waits longer before the
`--eval`/`--shot` step (measured need: an SPA's own content, not the extension's).
- **A content script (or page) cannot construct a Worker from `chrome-extension://`.**
Measured on suno.com with a WAR-declared worker: `new Worker(extUrl)` and
`new Worker(extUrl, {type:"module"})` both throw `cannot be accessed fromTrust audit
BLOCKgrade D · trust 69/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | FAIL |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (1 observation(s))
- Network
- declared (6 observation(s))
- Shell
- declared (3 observation(s))
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (7)
console.log(` action: ${MSG ? "--msg" : EVAL ? `--eval (--target ${TARGET})` : SHOT ? "--shot" : RELOAD ? "--reload" : WATCH_GIVEN ? "--watch" : "(state only)"}`);if (!WATCH_GIVEN) { closeAll(); await sleep(80); reportStderr(); await stop("after --eval (sw)"); process.exit(process.exitCode || 0); }a scratch profile, poll `http://127.0.0.1:<port>/json/list` for `service_worker`
"prove" a false defect). Open with `{"target":"page","url":"http://127.0.0.1:9811/index.html","delay":2500}`.`http://127.0.0.1:<port>/json/version` on 9793-9831 and nothing else. The Imagus project learned
const v = await (await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(900) })).json();`http://127.0.0.1:<port>/json/version` on 9793-9831 and nothing else. The Imagus project learned
Gates applied: no_behavioural_pass.
e7eae652d5eefull audit observations/trust-audit/skill/sucotasch__ext-dev-loop.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-10 | e7eae652d5ee | BLOCK | D | 69 | first audit |
Questions
What does the ext-dev-loop skill do?
Agent skill + zero-dependency CDP harness for the browser-extension dev loop: live SW console, scratch isolation, host auto-discovery/download
Is ext-dev-loop safe to install?
No — not without reading the findings first. The audit graded it D (69/100) and found 2 critical or high issues in the source. Each one is listed on this page with the file and line it is on.
What can ext-dev-loop access on my machine?
The audit observed that it reaches the network, runs shell commands and reads or writes files. Each of those is consistent with what it says it does. Secrets in the source: none found.
Which assistants does ext-dev-loop work with?
Its documentation mentions codex. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (e7eae652d5ee), read on 2026-10-10. The repository is watched, and a new audit runs when it changes — this is the first audit.