dsh-plugin
DayuanJiang/next-ai-draw-iodsh 的 draw.io 绘图插件:通过 next-ai-drawio MCP 服务器作图,点回复里的链接在右侧栏打开实时预览,可以手动修改图,AI 的每次修改都是可以恢复的版本。需 Node.js 18+,首次使用由 npx 下载 MCP 服务器。
dsh安全模式插件,通过构建空白profile实现零第三方插件启动。
One job: keep dsh --profile safemode clean — force-restore on startup,
continuously guard at runtime, so the profile only ever contains the
whitelisted core bundles.
dsh --profile safemode # boot with zero third-party plugins (suggest: dsh --profile safemode --port 3081)
Every time DSH boots and loads this plugin row, it unconditionally rewrites
$DSH_HOME/profiles/safemode/ back to the whitelist template:
| File | Forced content |
|---|---|
package.json | dsh.profile.bundles = whitelist (default @deepseek-ai/dsh-base + @deepseek-ai/dsh-web-app), dependencies emptied |
cordis.patch.yml | empty user patch layer ([] template) |
pnpm-workspace.yaml | same pnpm settings as an official profile |
If the files already match the template, nothing is written (avoids a self-triggering watch loop); anything that drifted is rewritten.
While the plugin row is alive, two channels watch the profile for modification:
package.json / cordis.patch.yml /
pnpm-workspace.yaml → debounced 300ms → immediate restore;detectDrift() comparison (covers
unreliable watch mounts and files replaced wholesale externally).On drift (bundle added/removed, patch layer written to, dependencies added)
the guard restores the profile and logs a warning:
safemode profile was modified (...); restored to whitelist template.
This package has no postinstall / no build scripts of any kind — pnpm
never blocks it, dsh plugin add succeeds with exit code 0 and reconcile runs
normally. No approve-builds / allowBuilds configuration needed.
All work is done by the plugin row (lib/index.js → lib/guard.js):
every DSH start force-restores once, then the guard stays resident.
Optional manual tool: node scripts/ensure-safemode.mjs force-restores
immediately (handy without a DSH restart, or for troubleshooting).
Prerequisites: DSH (@deepseek-ai/dsh) and Node.js (>=18) installed.
Method A (recommended, one command from npm) — the package is published on
npm with a dsh.bundle manifest; install directly with the official plugin
command:
dsh plugin --profile web add dsh-safemode-profile
Method B (from GitHub) — install straight from the source repository
(commit-ish pinned to the default branch main, works with both npm and pnpm):
dsh plugin --profile web add github:jinsiyu/dsh-safemode-profile#main
Method C (local tarball / unpublished) — pack and install a local build:
# pack inside the plugin directory
npm pack # → dsh-safemode-profile-0.3.5.tgz
# install into a target profile (e.g. web)
dsh plugin --profile web add .\dsh-safemode-profile-0.3.5.tgz
Restart DSH; once the plugin row is active, safemode enters "force-restore + resident guard" mode. On startup the plugin prints a short usage banner (guard status, start command, whitelist, repo link).
The whitelist is the only customization entry point — set it via the environment variable (do not hand-edit the safemode profile files; any edit is restored by the guard):
# pure CLI only (no GUI)
$env:DSH_SAFEMODE_BUNDLES = "@deepseek-ai/dsh-base"
The variable is read from process.env by both the guard and the restore
logic, and takes effect the next time dsh starts with it set.
Recommended: set the three managed files under
~/.dsh/profiles/safemode/ read-only to block
dsh plugin --profile safemode add <pkg> at the filesystem level — pnpm
fails when it cannot write them, so the plugin never gets in (DSH startup
is unaffected: it only reads them and writes cordis.yml):
package.json (dependencies + bundles — the choke point of any install)cordis.patch.yml (the user patch layer)pnpm-workspace.yaml (pnpm-managed file)⚠️ Never lock the whole directory: DSH rewrites
cordis.ymlon every boot (prepareProfileunconditionally writeFileSync), so a read-only directory or read-onlycordis.ymlmakes safemode fail to start (verified: exit 1). Windowsattrib +R <dir> /Srecursively locks every file includingcordis.yml— do not use it.
# Windows: lock only the three files (no /S recursion)
attrib +R "$env:USERPROFILE\.dsh\profiles\safemode\package.json" `
"$env:USERPROFILE\.dsh\profiles\safemode\cordis.patch.yml" `
"$env:USERPROFILE\.dsh\profiles\safemode\pnpm-workspace.yaml"
# POSIX: the three files to 444 (the directory still needs r-x and must stay writable)
chmod 444 ~/.dsh/profiles/safemode/package.json \
~/.dsh/profiles/safemode/cordis.patch.yml \
~/.dsh/profiles/safemode/pnpm-workspace.yaml
To unlock:
attrib -R "$env:USERPROFILE\.dsh\profiles\safemode\package.json" `
"$env:USERPROFILE\.dsh\profiles\safemode\cordis.patch.yml" `
"$env:USERPROFILE\.dsh\profiles\safemode\pnpm-workspace.yaml" # Windows
chmod 644 ~/.dsh/profiles/safemode/package.json \
~/.dsh/profiles/safemode/cordis.patch.yml \
~/.dsh/profiles/safemode/pnpm-workspace.yaml # POSIX
Notes:
package.json to detect drift and only writes when a drift is found.The attrib +R / chmod 444 above is "basic read-only": the two
platforms treat it differently, and neither can stop deletion (deleting
a file depends on the parent directory's permissions; root ignores all
permission bits anyway). The following are "hard locks", pick as needed:
# Deny the current user write access to the three files (W = write data)
icacls "$env:USERPROFILE\.dsh\profiles\safemode\package.json" /deny "$env:USERNAME:(W)"
icacls "$env:USERPROFILE\.dsh\profiles\safemode\cordis.patch.yml" /deny "$env:USERNAME:(W)"
icacls "$env:USERPROFILE\.dsh\profiles\safemode\pnpm-workspace.yaml" /deny "$env:USERNAME:(W)"
# Stronger: also deny deletion (DE = delete; WD = write data / create file)
icacls "...\package.json" /deny "$env:USERNAME:(WD,DE)"
To unlock (remove that user's deny entries):
icacls "...\package.json" /remove:d "$env:USERNAME"
Key points:
(W) blocks content modification, (DE) blocks deletion, (WD) blocks
write + create — more precise than attrib: you can block writes while
leaving admins able to delete;BUILTIN\Administrators as well — but admins can still
bypass via "take ownership";# Lock: file cannot be modified, deleted, or renamed (root included)
sudo chattr +i ~/.dsh/profiles/safemode/package.json \
~/.dsh/profiles/safemode/cordis.patch.yml \
~/.dsh/profiles/safemode/pnpm-workspace.yaml
# Inspect
lsattr ~/.dsh/profiles/safemode/package.json # output containing "i" = locked
# Unlock
sudo chattr -i ~/.dsh/profiles/safemode/package.json \
~/.dsh/profiles/safemode/cordis.patch.yml \
~/.dsh/profiles/safemode/pnpm-workspace.yaml
Key points:
+i (immutable) is the strongest lock: nobody (root included) can
modify, delete, or rename — you must -i first for any operation,
harder than chmod or ACL;sudo (root), and the filesystem must support the attribute
(ext4/xfs do; some network/container filesystems do not);+i blocks even that;
upgrading the plugin or changing the whitelist also needs -i first. Use
it only when "no account on this machine (root included) may touch the
safemode config" is a real requirement; basic read-only
(attrib / chmod 444) is enough for everyday use.| Option | Blocks content edit | Blocks delete | Blocks root | Requires | Impact on guard self-heal |
|---|---|---|---|---|---|
attrib +R / chmod 444 (basic) | ✅ for normal users | ❌ | ❌ | none | none (guard skips writes when clean) |
| icacls deny (ACL) | ✅ | ✅ (with DE) | ❌ (admins can bypass) | file owner | none |
chattr +i (immutable) | ✅ | ✅ | ✅ | sudo | ⚠️ blocks guard rebuild; -i first |
Suggestion: basic read-only is enough for everyday use (pnpm runs as a
normal user, 444 already blocks installs completely); add ACL (DE) if you
want to guard against accidental deletion; only choose chattr +i when
"no account (root included) may touch the safemode config" is a hard
requirement.
EADDRINUSE fails the boot) — use
--port to separate them: dsh --profile safemode --port 3081.settings.yaml and
.env are home-level shared; safemode only isolates plugins.~/.dsh/cordis.patch.yml applies to every profile
— don't mount plugins there (the guard only watches the safemode directory
itself, not the home patch).cordis.yml: DSH rewrites it on every boot; edit
cordis.patch.yml instead — but safemode's patch layer is restored to empty
by this plugin, so customize through the whitelist env var.