Bundle
@local/dsh-install-review
Audit a catalog plugin, approve the change plan, apply it, then install.
- Source
- GIN0076
- License
- MIT
- Updated
- Updated 9 days ago
Readme
<div align="center">
<img src="assets/logo.jpg" width="170" alt="Pre-install Review">
# Pre-install Review · 插件装前审查
**Know what it is before you let it in.**
English · [简体中文](./README.zh.md) · [CHANGELOG](./CHANGELOG.md)





</div>
> **Every plugin you install holds your keys.** It can read every session, call every model,
> rewrite your profile. And "the author says it's harmless" —
> **that is exactly what every plugin that ever went bad has said too.**
## Three uncomfortable facts
1. **Being on a list is not a safety endorsement.** When the upstream list carries no capability
scan, this panel says so — *"upstream did not annotate capabilities"* — instead of inventing a
green checkmark for it.
2. **The ecosystem default is "install first, ask later."** No backup, no plan approval, no
post-install verification — when something breaks, your only rollback is the whole profile.
3. **All-green still does not mean harmless.** But a red light always does: one **block** means
the right move is to close the page.
**This plugin does exactly one thing: before you hit *Install*, force the package to show its
face — what it is, what it can touch, what it will change — and let you approve every change
yourself.**
## Screenshots
| 📋 Audit report (seconds, capabilities & red lines included) |
| --- |
|  |
| 🗂 Browsable catalog (4,377-entry snapshot; server-side search / filter / sort) |
| --- |
|  |
| ✅ Change plan · your note · import a revision |
| --- |
|  |
## What it checks (13–14 checks, all read-only)
| Check | Detail |
| --- | --- |
| Local runtime | DSH version and profile (**path auto-masked** — screenshots are safe to share) |
| Official inspect | `pluginManager.inspect`: `dsh.bundle` / `dsh.client` / `dsh.tool` / `dsh.page` / `dsh.configForm` |
| Peer compatibility | Mirrors the host's own resolution (incl. `includePrerelease`) — **1,280 cases match host semver exactly** |
| Catalog entry | Registry, repository, stars, 30-day downloads (**no repo stars passed off as package stars**) |
| Install state | Installed / not installed (recognises 4 loader-id shapes) |
| Loader-id collision | Intersects with every live loader row — a duplicate id crashes host startup; this check is bought with a real lesson |
| Patch override rows | The target patch's override rows: do their targets exist? double-written by another plugin? (self-review never flags itself) |
| Slot audit | Checked one by one against the local slot catalog — the source `slot-catalog.ts`, or on a **packaged Desktop install** the catalog compiled into `app.asar/…/dsh-cordis-client-runner/lib/client.js` — **a slot check that cannot run is a warning**: unknown ≠ pass |
| Install scripts | `prepare` / `postinstall` etc. → enters the "approve & retry" flow automatically |
| Terminal-shaped surface | CLI-flavoured description → warns it may do nothing in a web profile |
| engines | **Both placements read**: `engines.dsh` and `dsh.engines.dsh` (missing one misreports "undeclared") |
| Host-tampering language | Description talks about patching the DSH core → warning |
| Capabilities & red lines | The list's 10 capability tags plus red-line sentences, **translated**; unannotated stays unannotated |
## The loop: audit → plan → approve → change → install → verify
1. **Audit**: type `npm package` / `owner/repo` / `github:owner/repo` / a GitHub URL → report in seconds
2. **Plan**: only **executable change kinds** — nothing runs unless you tick it. **Select all / Clear all**
next to the title ticks every runnable item at once; two kinds are deliberately left alone and the
panel says why — anything that still owes *your* risk acknowledgement, and the download-source lever
(selecting it would re-point the source; it is a fix for a failure, not a plan item)
- **Pin / update the version** (when a newer release exists it offers *"update to X"* — the
dependency version moves, your profile config is left alone)
- **Clear the version check** (`peer-compat` red): grant the host's own exact-version exemption for
*this exact pair* (package@version + your DSH version) and continue installing. You must first tick
**"I have read and accept the risk"** — a human step an imported revision cannot take for you; the
exemption covers that one pair only, expires by itself when either side moves, and can be revoked
from the same panel (a standalone action that installs nothing)
- **Change the download source** (pick a registry; when a registry-shaped failure happens the panel
also offers a one-click *"retry from another source"*)
- **Turn on features it declares but the loader is not running** (enabled row by row, only for ids
this package declares)
- **Approve build scripts** (when pnpm blocks one, it retries once with the **exact package name pnpm
reported** — never a guessed wildcard)
- **Write profile config** (through the official `configEditor` channel: lock, validate, roll back;
the template is **pre-filled from the package's own patch defaults merged with your current
config**, so you only review it)
- **Switch source** (git repository → pinned npm package)
3. **Your note**: disagree with the plan? Write it under *your note* → press **"Copy report for
review"** and paste it into your chat session → the agent returns a **revision JSON** → paste
it into *Import revision* to flip tick boxes and values in one click (a mismatched target is
rejected outright)
4. **Execute**: backs up `package.json`, `cordis.patch.yml`, `pnpm-workspace.yaml`, `cordis.yml`
(plus `compatibility.json` when an exemption record exists) **first** → applies the approved
changes → installs → **verifies**
5. **Verification checks that it really took effect**: not just "is it installed / any duplicate loader
id", but whether the package's rows are actually **active** (`failed` is called out), whether its
bundle is selected, and whether a declared browser half reached the client module graph — with plain
conclusions such as *"row X is not active yet — a dsh restart is usually required"*
6. **Build scripts blocked?** The panel shows an inline *"Approve `<name>` build scripts &
retry"* button — no deadlock, no overreach (a name that is no longer pending is refused by
the official `stale-approval` guard)
## After a red light: not a dead end any more
- **Peer mismatch** (the usual "cannot install"): the host itself leaves one way through — an
exact-version exemption. The panel turns it into a button: off by default, granted only after you
tick and confirm the risk, scoped to that one version pair, auto-expiring when either side moves.
- **`engines.dsh` mismatch**: it stays a **block** (the author's declaration is a real signal), but the
report says plainly that the host checks **`peerDependencies` only** and never refuses a plugin over
`engines` — so you get an "I understand, install anyway" box, plus a "use a version that satisfies it"
option.
- Some findings stay hard blockers with **no safe remedy** (unreadable manifest, no `dsh.bundle`,
a loader-id collision): the report says so and advises against installing.
## Boundaries (trust is built by saying what you will NOT do)
- **Never edits third-party plugin code.** It only touches four kinds of *your own* config:
source, version, build approval, profile config.
- **Never patches host source.** Zero host patches; everything goes through public services and
profile files.
- **Unannotated capabilities ≠ safe.** We show the facts; we do not vouch for upstream.
- **This is a pre-flight check, not a code audit.** It answers *"can I install it, and what
will it touch?"* — not *"is there a backdoor?"*
## Install
```sh
dsh plugin --profile web add github:GIN0076/dsh-install-review
```
- Requires DeepSeek Harness **0.2.0-rc.2** (the version this was tested against), on a web profile **or the Desktop app**
- **Zero runtime dependencies**, no build step, no postinstall
- Add `@local/dsh-install-review` to the profile's `dsh.profile.bundles` and add its insert row to
`cordis.patch.yml`; running only the `add` command puts the repository in `node_modules` but
**does not mount the interface**
- Restart `dsh web`, then hard-refresh (Ctrl+Shift+R) → Settings → **插件装前审查 / Pre-install Review**
**Desktop (packaged app, measured on 0.2.0-rc.2)**: Settings → Plugins → **Add plugin** with the
local directory path (or `install_bundle` from the plugin-manager tooling).
- **There is no "reload page" in the packaged Desktop app**: that menu item (and DevTools) exists only
in development builds (`!app.isPackaged`) — so do not hunt for Ctrl+Shift+R. A changed **client half
(the UI) usually hot-swaps by itself**; to force the new bundle, **quit and reopen
`DeepSeek Harness.exe`** (sessions are durable — pick the conversation up from the sidebar). A changed
**host half (the logic) needs no restart**: `remove_bundle` + `install_bundle` reloads it.
- Two things differ from a browser hitting the Host directly, both handled since v1.1.0:
① the runtime lives inside `resources/app.asar` and ships **no `src/` tree**, so the slot audit reads
the **compiled** catalog (`…/dsh-cordis-client-runner/lib/client.js`);
② the window's origin is `dsh-app://app` and its protocol handler **deletes `Origin` / `Sec-Fetch-Site`
/ `Cookie`** before forwarding, then injects the Host's own session cookie → the panel no longer
compares Origin itself but asks the Host's `connection.requestRejection` (the same channel the
official `@deepseek-ai/dsh-host-open-in-app` uses; no cookie → 401, cross-site Origin → 403, and the
refusal carries a `hint` plus the headers it saw).
> Prefer not to use git? Copy the repository locally and point the plugin manager's
`install_bundle` at the directory, then complete the same two profile configuration steps.
## Uninstall
Settings → Plugins → remove, or `remove_bundle` in the plugin manager.
**Uninstall does not delete your backups** — every pre-execution backup is
written to `<profile>/install-review-backup` (the row's `backupDir` can move it).
## Self-tests
```sh
node semver-selftest.mjs # 1,280 cases, identical to host semver (SKIP when no host semver)
node catalog-view-selftest.mjs # categories / capabilities / red lines / projection
node patch-audit-selftest.mjs # override-row parsing and verdicts
node audit-selftest.mjs # parsing / peers / both engines placements / end-to-end
node remedy-selftest.mjs # remedies: exemption before install, nothing without the human tick,
# an imported revision cannot self-approve, revoke is standalone
node enhance-selftest.mjs # capability proposals: config-template scanner / registry order /
# upgrade keeps config / rows enabled by entryId / post-install activation
node runner-selftest.mjs # backups / config edits / approvedBuilds / stale-approval
node host-selftest.mjs # routes and same-origin fence
node client-selftest.mjs # revision-import loop (+ SSR render when react-dom exists)
```
**Nine self-tests — zero test framework, zero dependencies.** The two end-to-end tests read the
live upstream catalog: when the target declares peers incompatible with the current DSH, that
block is expected. A green run is not an installation approval.
> The self-tests resolve their host packages themselves (`selftest-host.mjs`: source checkout →
> local `node_modules` → packaged runtime) — **no install path is hardcoded any more**. To get the
> real 1,280-case semver comparison on the Desktop build:
> `$env:ELECTRON_RUN_AS_NODE=1; & "…\DeepSeek Harness.exe" semver-selftest.mjs` — it uses the
> semver inside `app.asar`. When node-semver / react-dom cannot be found, that pass prints `SKIP`
> (never FAIL); point `DSH_SELFTEST_SEMVER` / `DSH_SELFTEST_REACT` / `DSH_SELFTEST_REACT_DOM` at an
> exact file to restore it. The username in the fixtures is fake (`localtester`) precisely so the
> tests can prove that local paths in reports are always masked.
## Disclaimer
This tool checks *installability and blast radius* before installation. No automated check
replaces your own judgement of where a plugin comes from. Catalog data is an upstream snapshot
(4,377 entries; downloads window shown in the UI) and reflects the moment it was fetched.
## License
[MIT](./LICENSE) © 2026 GIN0076
Install
dsh plugin --profile web add github:GIN0076/dsh-install-review
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install local-dsh-install-review from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.