Releases: LivXue/dsh-plugin-shop
Release list
v0.8.0 — audit remediation: one install identity, a self-verifying harvest, bounded artifacts
English — 中文公告见本页下半部分
The remediation release for the 2026-09-03 defect audit. Eight independent passes went
over the tree at 49db942 — the 0.7.4 release commit — and produced 86 findings
across five subsystems. v0.8.0 closes them, as five independently shippable plans. Both
halves of the repository moved: the plugin the harness loads, and the pipeline that builds
the catalog it reads.
| npm | dsh-plugin-shop@0.8.0 → latest |
| Previous | 0.7.4 (2026-09-03) |
| Proven on | beta tag, 0.8.0-beta.0 … 0.8.0-beta.4 |
| Tag | v0.8.0 @ 0b92017 |
| Catalog | dsh-plugin-shop-catalog + Pages /v1/ — rebuilt daily, so its half has been live since it landed |
| Audit | 86 findings, 5 plans |
Upgrade
# dsh on PATH
dsh plugin --profile web add dsh-plugin-shop@0.8.0
# or straight through npx, nothing installed
npx -y @deepseek-ai/dsh plugin --profile web add dsh-plugin-shop@0.8.0Pin the version — pnpm 11 holds back very recent releases, so a bare
add dsh-plugin-shop can hand you an older build for a while. Restart dsh once
afterwards: a newly added bundle is not applied to a running process.
Upgrade if you install plugins whose bundle name is not unique (177 names in the live
catalog are claimed by more than one entry), you are on Windows, or you have ever read an
install notice and not believed it.
Breaking changes: none for users. The installStart RPC and the done reducer state
each gained a field; both are internal to the plugin and move together with it.
Highlights
- Two same-named plugins no longer overwrite each other. The second install used to
replace the first in the profile manifest, silently. - The install notices report evidence, not inference. The confirm diffs the profile
manifest across the spawn; the log survives success; a failure names the cause. - Windows installs monorepo subpackages instead of silently landing the repository
root — and catalog fields no longer reachcmd.exeunvalidated. - The harvest can prove it enumerated everything, 156 names before the npm search
window would have broken every daily build. - Nothing unbounded reaches a published artifact. One hostile package could produce a
203 MBplugins.jsonfor every reader.
The shop — dsh-plugin-shop@0.8.0
Added
- One install identity,
(source, name, repo, subdir), insrc/shared/identity.ts—
used byvalidateInstall,install(),installed(), the incompatibility map, the pins
file and every client lookup map. name-takenrefusal: an install that would replace a different plugin holding the
same bundle name is refused, naming the repository that holds it. Updating the same
plugin is unaffected. (#19)- Name-conflict badge on the card, before the click, with the holder named — and a
card with two problems at once states both instead of hiding one. (#19) - Post-install loader-collision check: two differently-named bundles may declare one
loader entry id, which makes dsh refuse the whole tree at next boot. The failure names
the id, the package holding it, and the command that undoes it. (#19) - A load-time peer-range warning, once, across the five packages the shop declares
(three harness packages at^0.1.1-rc.2,@deepseek-ai/cordis^4.0.1,
@deepseek-ai/cordis-plugin-include^1.0.6). Prereleases are included on purpose —
the harness ships nothing but-rc. - The install log outlives success:
donecarries its log likefailed, and the panel
renders it.
Changed
- The post-install confirm reads the profile manifest before and after the spawn and
reports only the difference, instead of inferring a cause from a predicate that does not
entail it. (#18, #20) installedNoRestartNoticefires only when the hot mount succeeded.- A failure detail appends the last link of pnpm's causal chain, not just its
ERR_
code. - Catalog
name,version,repoandtarball.urlare validated against a boundary
grammar; an unsafe operand is refused rather than quoted around. - The Incompatible badge sits beside the button it qualifies and carries an accessible
name. - The shelf sorts once per load, through one shared collator.
Fixed
installed()returned two rows for one plugin, one markedoutdated, whose Update
button spawned another repository's commit — gated on that entry's tier.- Re-installing your own plugin failed with a false published detail:
version-mismatch: dsh-foo@bbbb… is not the cataloged version (aaaa…). - A second same-named install silently overwrote the first in the profile manifest.
- A GitHub pin written as a release tag was dropped on every read, so the 162
release-rescued entries could never reportoutdated. - Windows: monorepo-subpackage entries installed the repository root. dsh spawns pnpm
throughcmd.exe, where the&ingithub:owner/repo#sha&path:subdiris a command
separator. Verified end to end on Windows 11 / dsh 0.1.2-rc.1 / pnpm 11.25.0. Upstream
cause reported asdeepseek-ai/deepseek-harnessdiscussion #5815. - Windows: the live-harness e2e could not run at all. Its gate used
spawnSync('dsh')
and npm installs the CLI asdsh.cmdwith no.exe, so the only test that walks the
real shop UI skipped itself on the one platform CI cannot see. 5/5 there now. (#24) - A restricted export counted as a missing module in peer checks.
- An unreadable or half-written profile manifest turned
denied,not-in-catalogand
version-mismatchinto "please retry";undefinednow means "cannot say", never
"nothing is installed". - The boot catalog stash never expired; mutation outcomes were lost through an installed
refresh; the user layer's!!jsspelling was rewritten away.
Security & hardening
- A spawned install is bounded and its process tree is killed. It had no deadline, no
kill, and completion waited on inherited pipes — one stalledpnpmblocked the profile's
whole queue until dsh restarted. - npm tarballs are capped on the wire and at inflate. A 260 KB gzip bomb from a raced
mirror became 268 MB in memory; 32 MB of accepted gzip can demand ~33 GB. - Catalog reads are capped and wrapped as transport failures; bulk reads are budgeted and
abortable; concurrent loads share one request. - A restart is refused while an install is running; a failed helper startup is reported.
- The self-update check is bounded and asks the user's registry first.
- Every name-keyed read is an own-property read — a plugin named
constructoris not
reported as holding a name, and a star entry named__proto__cannot poison a lookup. - Log lines survive stream-chunk boundaries; hot-mount rows are serialised as YAML and
their patch paths confined.
The catalog pipeline
Added
release-asset.ts— the release-asset rescue channel moved out of
github-client.tsand gained the checks it lacked: the asset root is anchored, the
inflate is bounded at every call site, a rescued entry must be the package it claims,
and the check applies retroactively to the 162 entries already rescued. (#21, #22)identity.ts— one module owning(source, name, repo, subdir)in the four shapes
the pipeline needs.http-body.ts— the one place a body cap is enforced. A guard test scans every
module that reads a body and fails on one that bypasses it; a sibling guard scans for an
unboundedgunzipSync/inflateSync.pages-artifacts.ts— the publishable set as policy in the pure core.- Recorded market verdicts in
registry/markets.yml, a build input like
verified.yml; the build report names everyby: llmwithholding for spot-checking. - A report of registry rows that matched no harvested candidate, so a typo in a review
file is visible instead of silent. - 5,924 backfilled GitHub first-seen dates, recovered from the 21 committed snapshots
spanning 2026-08-18 → 2026-09-04.
Changed
- A review pins one exact artifact. A GitHub review binds
(repo, commit), an npm
review the reviewed version and no other, a release-rescued entryrepo+sha256. All
three compare by equality. - Entries, lock lines and rejections sort by the whole identity, so output cannot
depend on the order the harvest returned candidates in; shadowed subpackages are named
by theirowner/slug#subdirunit. addedis stamped from accepted entries, keyed by identity, so a package rejected
for weeks and then listed no longer carries its first-harvest date.- The name heuristic for competing markets is now a candidate selector that decides
nothing. It caught 73 live entries and 20 were innocent — 存茶指南 and 腌菜保存, an
A-share quant plugin whose "market" is the stock market, a session-log plugin whose
"store" is a verb. - The
by: humanmarket gate was reverted the same day it shipped.notAShopis the
CLEARED list, so requiring a signature advertised the 16 shop-like names the heuristic
had been hiding. A mechanism whose only exit is a human is a permanent no-op. The verdict
decides;byrecords who judged it.
(design §4) - Both build reports are published beside the catalog. A rejection's
detailis
written for the author of the package it names and had been reachable only as a zipped CI
artifact. Upstream discussion #5867 is the demonstration: an author searched the build
metadata for their own scope, found nothing, and concluded the harvest could not see
them — one of their six was harvested and rejected asno-repository. (#23) - The stars sidecar is keyed b...