dsh-permission-rules
安全与治理PerryLink/dsh-permission-rules
为 DeepSeek Harness 提供 Claude Code 风格的声明式权限规则,支持有序的允许/拒绝/询问规则、工具名和参数匹配、会话日志审计及热重载。
- ai-safety
- allow-deny-ask
- approval
- cordis
- deepseek
- deepseek-harness
- dsh
- dsh-plugin
- network
- network-policy
- permission
- policy
- proxy
- safety
README
🛡️ dsh-permission-rules
Claude Code-style declarative permission rules for DeepSeek Harness.
Rules decide what is known. A reviewer model decides what is not.
English · 简体中文 · Español · Português · हिन्दी
Compatibility
| Surface | Status |
|---|---|
| Harness | DeepSeek Harness 0.1.1-rc.2 |
| Node | `^22.19.0 |
| Platforms | All (host + web settings client) |
| Model | Any (deny/ask reasons surface through tool results) |
What you get
dsh-permission-rules puts an ordered allow / deny / ask rule list in front of every tool call on the tools/pre-execute waterfall — deterministic, instant, auditable, and written by you in plain YAML:
denyblocks the call; the rule'sreasonbecomes the model-visible error.askrides the official approval seam (mountdsh-auto-reviewfor a second-model answerer, or a human answers; with neither, the harness fails closed).allow(and no-match) strictly delegates vianext()— downstream listeners are never short-circuited.
Every hit and every passthrough is audit-logged as a permissionRules/decision session event (log-only — nothing extra is injected into the model context).
- Rich matching — tool-name globs (including
mcp__*), agent-identity selectors (main/subagent/preset:*), argument key/value globs or regexes (with!patternnegation and anabsentkey dimension), workspace-relative path globs at any nesting depth, andwhenhost conditions (env vars, platform). - Hierarchical rule files — optional
searchUpmerges every.dsh/rules.yamlfrom the session cwd to the filesystem root, nearest first. - Dry-run rollout —
enforce: falseaudits what the policy would do while passing every call through. - Hot reload — Chokidar watch with debounce; a broken edit keeps the previous rules, never crashes.
- Fail loud — invalid YAML, unknown actions/fields, bad globs/regexes, backtracking-prone patterns, or more than
maxRulesrules fail the load.
Rule syntax
# <project>/.dsh/rules.yaml
rules:
- match: { tools: [bash, pwsh], params: { command: "git push*" }, paths: ["**/secrets/**"] }
action: deny
reason: "No pushes from protected paths"
- match: { tools: [edit, write] }
action: ask
reason: "File writes need confirmation"
- Match dimensions —
tools(globs, incl.mcp__*),agents(main/subagent/preset:<name>; unknown identity never matches — fail closed),params(key/value globs or regexes,!patternnegation,absentkey dimension),paths(workspace-relative globs extracted at any nesting depth),when(envvar globs/regexes + a closedplatformlist), andnetwork(domains/ips/ports/schemes— globs, wildcards, CIDRs, port ranges). - Actions —
allow/deny/ask, evaluated in file order, first match wins. - Rule metadata —
enabled: false(visible but inert),description,tags; unknown fields fail the load. - Schema — a JSON Schema ships at docs/rules-format.schema.json (editor completion via
# yaml-language-server: $schema=...); the full vocabulary and a 5-rule security baseline live in docs/rules-format.en.md.
Network policy
A Codex-style process-level network policy: shell subprocess traffic flows through a built-in local HTTP/CONNECT proxy, and every connection is decided by ordered network rules or by three modes mapped onto the official sandbox presets:
-
deny-all— the read-only sandbox preset: block all outbound. -
whitelist— the workspace-write preset: allow listed targets,unlisted: ask(ordeny) for the rest. -
allow-all— the danger-full-access preset: allow everything. -
auto(default) — follows the sandbox preset; on hosts without the sandbox-policy service it resolves toautoFallback(allow-all). -
Matching —
match.networkwithdomains/ips/ports/schemes(globs, wildcards, CIDRs, port ranges; numeric YAML ports are accepted). URL-candidate extraction on thetools/pre-executehot path fires on web-tool arguments and URLs embedded in bash/pwsh command text; loopback targets can short-circuit rules perloopbackpolicy. -
Audit — denied connections append
permissionRules/networkto the owning session (same adaptiveignorablegate), with block counters and recent interceptions in/rules networkand the settings page.
Quick start
# 1. install the bundle into your profile
dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"
# or from npm (published releases)
dsh plugin --profile web add dsh-permission-rules
# 2. restart and verify the row
dsh --profile web --dump-config | grep -A4 'id: permission-rules'
Install & uninstall
- git channel (latest
main):dsh plugin --profile web add "github:PerryLink/dsh-permission-rules#main"— thepreparescript builds with production dependencies only. - npm channel (published releases):
dsh plugin --profile web add dsh-permission-rules. - tarball channel:
pnpm packin this repo, thendsh plugin --profile web add ./dsh-permission-rules-<version>.tgz. - uninstall:
dsh plugin --profile web remove dsh-permission-rules.
Configuration
All tunables are Schemastery Config fields (changeable from cordis.yml). An id-targeted override replaces the whole row — restate every key you need.
| Key | Default | Meaning |
|---|---|---|
rulesFile | .dsh/rules.yaml | Rule file location; relative = resolved against the calling session's cwd, absolute = global and validated at mount |
fallbackPath | (none) | Rule file used when per-cwd discovery finds nothing; validated at mount |
badFilePolicy | fail | Bad rule file: fail errors the pending tool call loudly; ignore-with-warning warns and continues empty |
maxRules | 256 | Hard cap on rule count across the effective source chain |
maxCachedWorkspaces | 512 | Hard cap on cached per-workspace rule loads (LRU eviction) |
patternMode | glob | params/paths/when.env pattern flavor: glob or regex (tool names are always globs) |
watch | true | Chokidar watch + reload on change |
watchStabilityThresholdMs | 200 | Reload debounce window (ms) |
language | en | /rules output language: en, zh, es, pt, hi |
caseInsensitivePaths | (win32) | paths patterns and workspace-root comparison ignore ASCII case; true on Windows |
audit | all | Audit granularity: all logs every hit AND passthrough; hits skips passthrough events |
searchUp | false | Walk parent directories from the session cwd and merge every found rule file, nearest first |
maxGlobStars | 2 | Hard cap on unbounded */** quantifiers per glob pattern |
enforce | true | false = dry-run mode: deny/ask hits are audit-logged with a dryRun marker and every call passes through |
allowUnmarkedAudit | false | Pre-marker hosts drop the ignorable marker; the plugin disables session-log audit with a warning. Set true to opt back in |
network.enabled | true | Master switch for the proxy, env injection, and web-tool mode defaults |
network.mode | auto | Policy mode: auto follows the sandbox preset, or deny-all / whitelist / allow-all |
network.autoFallback | allow-all | Mode used when auto has no sandbox-policy service |
network.unlisted | ask | Whitelist-mode handling of targets no rule matched: ask or deny |
network.proxyBind | 127.0.0.1 | Local proxy bind address (loopback only) |
network.proxyPort | 0 | Local proxy port; 0 picks a free ephemeral port |
network.proxyMaxRecent | 100 | Cap on recent-block records kept for the settings page |
network.loopback | allow | Loopback targets: allow (Codex parity) or policy |
network.injectEnv | true | Whether proxy environment variables are injected for subprocesses |
network.noProxy | clear | Subprocess NO_PROXY handling: clear enforces the policy or preserve |
Tools & surfaces
| Surface | Kind | Notes |
|---|---|---|
tools/pre-execute | listener | First-match allow/deny/ask rules + network URL-candidate extraction |
/rules | command | list · reload · decisions [n] · test <tool> <json> |
permissionRules/decision | event | Log-only audit for every hit and passthrough |
permissionRules/network | event | Proxy-layer audit for blocked connections |
| HTTP/CONNECT proxy | service | Built-in local proxy governing shell subprocess traffic |
| settings page | client | Network-mode editor, rule editor, block counters, recent interceptions |
/rules list the active rules, their source files, and any last-reload error
/rules list explicit alias for the bare listing
/rules reload re-read the rule-file chain for this workspace
/rules decisions [n] show the last n permission decisions of this session (default 10)
/rules test <tool> <json> dry-evaluate the rules against a hypothetical call
/rules test also accepts leading flags: --cwd <dir>, --env KEY=VALUE (repeatable), --agent <selector> (repeatable), and --platform <name>. In multi-file chains (e.g. searchUp), every listed rule line is attributed to its own source file.
Permissions & data
- Permissions: declares
files:read,files:watch,files:write,session:append, andnetwork:outboundin its workshop manifest.askdecisions ride the official approval seam — nothing is re-implemented or bypassed. - Data: rule files are read from disk; no rule data is written. No model calls, no reviewer subagents.
- Session log:
permissionRules/decisionis never injected into the model context and is appended with the envelope'signorable: truemarker so any harness build loads the log.
Security boundaries
- Policy, not a kernel.
pathscandidates come only from a documented set of argument keys (at any nesting depth, depth-capped), and only workspace-relative paths match. - No reviewer here. The plugin never spawns subagents or calls models — producing an
askdecision is the end of its work. - No sandbox changes. OS-level sandbox policy belongs to the sandbox seam, not this plugin.
- Loud misconfiguration. Unknown YAML fields, unknown actions, and bad patterns are rejected at load.
- Backtracking bounds. Glob patterns are capped at
maxGlobStarsunbounded star expansions; regex-mode patterns reject nested unbounded quantifiers and quantified overlapping literal alternations.
Known limitations
- Audit marker on pre-marker hosts.
permissionRules/decisionis appended withignorable: true; hosts whoseSession.appendpredates the marker (the0.1.0-rc.6line) silently drop it, so the runtime disables session-log audit with a one-time warning. SetallowUnmarkedAudit: trueto opt back in; repair already-written logs withscripts/repair-session-logs.mjs. - Path candidates are heuristic. Only the documented argument keys feed path matching, and workspace-relative matching is ASCII-case-insensitive only when
caseInsensitivePathsis on. - Globs are a conservative subset. No brace expansion — write two patterns, or use regex mode.
- The regex backtracking guard is structural, not exhaustive. Prefer glob mode for untrusted files.
Collaborating with dsh-auto-review
dsh-permission-rulesproducesask;dsh-auto-reviewanswers on theapproval/requestwaterfall with a read-only second-model verdict (or delegates to humans). Mount both for the full closed loop.- Integration-tested:
permissionRules/decision→approval/asked→autoReview/verdict→approval/decided, with the reviewer replaced by a scripted mock. - The
neverapproval policy and every fail-closed guarantee of the official harness stay untouched.
Session log repair
Session logs written before the ignorable marker existed can be refused by newer harness builds (SessionFormatUnsupportedError). The shipped scripts/repair-session-logs.mjs rewrites only the targeted audit rows to carry ignorable: true, frame-preserving, with backups:
node scripts/repair-session-logs.mjs scan [--home DIR] # report foreign rows, change nothing
node scripts/repair-session-logs.mjs repair [--home DIR] [--dry-run]
--home defaults to $DSH_HOME/sessions (or ~/.dsh/sessions).
Development
pnpm install # node ^22.19 || >=24
pnpm run typecheck # tsc, src + tests
pnpm run lint # eslint, src + tests + scripts
pnpm test # vitest: 236 tests, 20 files
pnpm run test:coverage # coverage gate (90/80/90/90)
pnpm run build # tsc declarations + tsdown bundles (lib/)
pnpm run pack:check # build + pack (the published artifact)
node scripts/check-readme-sync.mjs # five-language README sync gate (also in CI)
See VERIFICATION.md for the headless end-to-end verification record.
Topics
dsh, dsh-plugin, deepseek-harness, permission, policy, allow-deny-ask, approval, safety, network, network-policy, proxy
Contributors
- @PerryLink — creator and maintainer: rule vocabulary and evaluation, runtime, HMR watch, session-log audit, network policy + proxy, and the five-language docs.
- @22xuan — the detailed report on rc.6 hosts silently dropping the audit event's
ignorablemarker (#2) and the upstream harness discussion; the v0.4.1 runtime host-capability detection and the documentation correction drew directly from that analysis. - @sjh9714 — proposed the shared rule-syntax test-vector corpus (#4, #5), shipped in v0.5.1 as
docs/rule-test-vectors/, and supplied the AST-decomposition boundary cases on the design discussion. - @weipeng1999 — the AST-based command-decomposition feature proposal (#8) behind the design discussion.
- @alexchenzl — the DSH Directory listing request (#7).
- @zl190 — reported and verified the
0.1.0-rc.7harness compatibility gap (PR #9).
PerryLink DSH Plugin Family
This project is one of the 15 DeepSeek Harness plugins maintained by PerryLink. If this one helps you, the others likely will too:
| Plugin | One-liner |
|---|---|
| dsh-mcp-panel | Read-only MCP runtime panel: /mcp command + Settings tab with status, tools and errors |
| dsh-doublecheck | Engineering-discipline guard: requirements grill, test gates, adversary review |
| dsh-background-agents | Durable background child agents with a Web UI sidebar, messaging and interrupt |
| dsh-lsp-actions | LSP diagnostics, formatting, completion, code actions and rename over language servers |
| dsh-output-styles | Claude Code outputStyles-equivalent runtime style switching |
| dsh-checkpoint-rewind | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore |
| dsh-permission-rules | Claude Code-style declarative allow/deny/ask permission rules with audit |
| dsh-auto-review | Second-model auto-review on the approval chain, fail-closed by default |
| dsh-memento | Approval-gated cross-session memory: ctx.memory seam + SQLite + memory tool |
| dsh-skill-pack-security | Security-audit skill pack: secret scan, dependency and supply-chain review |
| dsh-session-pin | Pin sessions in the Web sidebar with durable ordering |
| dsh-composer-history | Terminal-style input history for the web composer: arrows, Ctrl+R search |
| dsh-github | GitHub PR/issues integration for DSH, every write gated by approval |
| dsh-plugin-guide | Plugin-development knowledge base as an on-demand agent skill |
| dsh-claude-move | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH |
License
Apache License 2.0 © 2026 dsh-permission-rules contributors