SHIPPED, AND TURNED OFF.
dsh-vn-master— the pack's final layer — disables thebrowserrow, so a profile loads no Browser tab, no/api/dsh-browser/*routes, nobrowser_render/browser_query/browser_textand no client bundle: a feature nobody uses is not worth its attack surface. The code, these docs andscripts/checks/check-browser-node.mjsstay in step, so re-enabling it is deleting two lines from that layer.
The pack's own Browser tab, and the replacement for the one the harness ships: it renders a page on the host, in a throwaway engine behind an egress gate, and shows the picture, the rendered text and the page's own numbers — with three tools over the same pipeline, so what you see and what the agent was told cannot disagree.
The shipped @deepseek-ai/dsh-client-ui-sidebar-browser renders a remote page in an <iframe src=...> and its host half is function apply() {} — no proxy, no route — so the site's own framing policy decides whether anything appears: google.com answers X-Frame-Options: SAMEORIGIN. Nothing client-side can lift that, which is why the shipped package is desktop-only upstream; this pack targets the raw web profile, so it renders instead of framing.
url ──policy.js──► https, 443, a qualified public host, never an IP literal
──gate.js───► loopback CONNECT proxy: resolve once, refuse the whole answer
set unless every address is public, connect to the address it
validated, bound the tunnel, RECORD the decision
──engine.js─► throwaway Chrome/Edge profile, driven over CDP
──here──────► page.png + meta.json under $DSH_HOME/dsh-browser/artifacts/
One function (runRender) serves both callers: GET /api/dsh-browser/state, POST /api/dsh-browser/render, GET /api/dsh-browser/image.
| Tool | Back comes |
|---|---|
browser_render |
a PNG whose true size is read from the file's own IHDR, the page's numbers, the gate's verdict, the page's destinations |
browser_query |
per CSS selector: count, box, computed style, text, attrs, html |
browser_text |
the post-script text and the link inventory |
Each labels page content as data, never instructions; a bundled web-render skill covers when to render instead of using web_fetch.
The tab draws four views: Visual (the PNG on a zoom ladder that moves the layout box, never a CSS transform), Reader (the text, links clickable through the same policy), Metrics (viewport, content size, counts, timing) and Policy (the engine, the gate's tunnels/challenges/refusals by name, the parked live mode). Plus viewport presets (390–1920), full-page and dark toggles, back/forward over render history, and a permanent banner saying the picture is a host render, not a live page.
cordis.patch.yml hard-disables the shipped ui-sidebar-browser row and inserts the browser row, keeping the replaced package's tab id and kind so a profile with Browser tabs already persisted resolves them here. The disable stays with the package that replaces the row, so a partial -Plugin dsh-browser install leaves exactly one Browser tab — the shipped one — rather than none.
- The page never runs in your browser, and the engine has no direct network access. It is launched with the gate as its proxy and
--proxy-bypass-list=<-loopback>, so every socket — document, scripts, fonts, XHRs — is aCONNECTthe gate policed. The profile is a fresh--user-data-dirper render, deleted afterwards; the engine is never disarmed (no--no-sandbox, no--disable-web-security, no--ignore-certificate-errors— the tracked check fails if one appears). The gate's credential never reaches a page: it demandsProxy-Authorizationand the host answers over CDP (Fetch.authRequired). - Every run is bounded: one deadline, a timeout on every CDP command, an idle timeout and a byte cap per tunnel, and the browser process (found through CDP, not the launcher Windows exits immediately) is killed at the end. And so is the machine — at most
MAX_CONCURRENT_RENDERS(2) run at once, a queue of 8 waits its turn, and beyond that a call is refused withBUSY(DSH_BROWSER_MAX_RENDERSmoves the ceiling). - The policy: https only, port 443 only, no credentials in a URL, no IP literals, a qualified public hostname (so
localhostnever resolves), public unicast addresses only for the whole answer set, and an optional allow/block list. Every refusal carries a code and a sentence naming the rule; pages behind a login are out of scope by design. - The parked live mode refuses with
LIVE_PARKED— it is the one thing that would put a remote page back inside this origin, which this package exists to avoid. Nothing here reads a caller-supplied path, only its own artifacts under$DSH_HOME/dsh-browser/, so it has no path policy at all to get wrong.
node scripts/checks/check-browser-node.mjsThe pure halves and the gate always run (no network); the live half skips loudly without an engine or the internet.
lib/policy.js lib/gate.js lib/cdp.js lib/engine.js policy, gate, CDP, engine
lib/index.js config, the lazy gate, three tools, three routes
lib/client.js the tab: Visual / Reader / Metrics / Policy, tool cards (no build)
skills/web-render/SKILL.md