wangxing-git
dsh-autogate
DeepSeek Harness 自动审批插件:在 workspace-write 沙箱之上叠加确定性规则 + LLM 安全审批,自动模式不放宽沙箱、fail-closed。 Safe auto-approval for DeepSeek Harness — deterministic rules + LLM review on top of the workspace-write sandbox. Auto mode without ever granting full-access.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
dsh-autogate
DeepSeek Harness auto-approval plugin: adds two permission presets — semi-auto (auto-ask) + full-auto (auto) — on top of the workspace-write sandbox, using layered decisions: deterministic rules + LLM safety approval + (in semi-auto) proactive human approval initiated by the denied party. It keeps the workspace sandbox boundary and never relaxes to full-access.
Layered design
| Layer | Decision | Description |
|---|---|---|
| L0 Deterministic rules | allow / deny | Zero cost, zero LLM: read-only ops, session state, in-workspace edits and deletes, build/test, and the run_code container pass directly; reads of ordinary paths outside the workspace pass directly; writes/deletes outside the workspace (except writes to sensitive shell/credential config files) pass and rely on the workspace-write sandbox to intercept + escalation popup; writes to sensitive config files outside the workspace go to LLM review; empty commands, dynamic command names, and missing arguments fall through to the sandbox; privilege escalation, self-destruction, credential exfiltration, and deletion of critical paths are hard-denied |
| L1 LLM safety approval | allow / deny | Operations the sandbox doesn't intercept but are semantically dangerous (unrecognized tools, ambiguous shell, sensitive path reads, dynamic targets, block devices, persistent terminals, git state changes, network/database operations, writes to protected in-workspace paths) go to a two-state LLM decision: operations explicitly authorized by the user are allowed, reducing manual approvals |
| L2 Human approval | ask | Two channels: ① the AI uses ask_user_question to confirm the operation is legitimate, then re-runs it and passes the LLM again; ② the AI retries with sandbox_permissions + justification to go through DSH's built-in sandbox escalation — this plugin runs the LLM first: a reasonable escalation is approved directly without a popup, dangerous/uncertain cases show a human popup |
Two modes
| Preset key | Mode | escalation approval fallback |
|---|---|---|
auto-ask | Semi-auto (default) | LLM deny/error → delegate to a human popup (L2 fallback) |
auto | Full-auto | LLM deny/error → deny directly, no human popup (LLM decision is final) |
Both modes share the same L0 deterministic rules and L1 LLM classifier; the only difference is the L2 human fallback: semi-auto keeps the human popup, full-auto treats the LLM decision as final. Hard deny (L0 guard) and the preflight switch behave identically in both modes.
Key differences from similar plugins
- Sandbox stays workspace-write: even if the LLM misjudges and allows, file writes remain confined to the workspace by the sandbox (unlike similar plugins that use danger-full-access).
- Unrecognized tools go to LLM classification by default instead of being allowed — but
run_codepasses directly as a code-execution container; every tool call inside it is still evaluated by this policy and the sandbox. - fail-closed: classifier errors / timeouts / no route / malformed output always deny; the denied party (AI) proactively escalates to human approval as appropriate.
Install
# Install from GitHub (compiled lib/ is committed)
dsh plugin --profile web add github:wangxing-git/dsh-autogate
# Restart dsh
Configuration
Configuration is wired through the DSH settings service (ctx.settings): write an autogate: section in $DSH_HOME/settings.yaml and it hot-reloads immediately; when the settings service is not mounted, it falls back to the entry config in cordis.patch.yml (config: {}).
About the settings UI: DSH 0.1.0-rc.6's Web settings page hard-codes an allowlist for third-party plugin namespaces (
WEB_SETTINGS_NAMESPACESindsh-host-apiproxy), andautogateis not in it by default, so the settings card may not show. To make the card show, append"autogate"to that array and restart dsh (a change to the official package that must be redone after a DSH upgrade); otherwise configure manually viasettings.yamlbelow — functionally equivalent.
autogate:
preflight: false # pre-sandbox interception switch: true runs deterministic rules + LLM classification, false (default) relies entirely on the sandbox
presetName: auto-ask # semi-auto preset key (default auto-ask): delegates to a human fallback popup after LLM deny
fullAutoPresetName: auto # full-auto preset key (default auto): LLM decision is final, no human popup
classifierTimeoutMs: 8000 # classifier timeout (100–60000ms), fail-closed on timeout
classifierMaxOutputTokens: 1024 # classifier max output tokens (64–4096)
# classifierPrompt: | # review (classification) system prompt; empty uses the built-in default
# (custom review prompt judging intent / type / reversibility / impact)
# Fixed classifier model (defaults to the current session's provider/model; both fields must be set together)
# classifierProvider: deepseek
# classifierModel: deepseek-chat
# Standalone OpenAI-compatible classification endpoint (optional; must be HTTPS, loopback may use http)
# classifierEndpoint: https://api.example.com/v1/chat/completions
# classifierApiKeyEnv: DEEPSEEK_API_KEY # environment variable name for the HTTP endpoint API key
# workspaceRoot: /path/to/ws # override workspace root (default: session cwd)
# dshHome: /path/to/.dsh # override DSH_HOME (default: ~/.dsh or $DSH_HOME)
# tempRoots: [/tmp] # trusted temporary directories (default: system temp dir)
Decision flow
preflightswitch (defaultfalse): controls whether the two pre-sandbox steps ("ordinary deterministic rules + LLM classification") run. Whenfalse, steps 3 and 4 below are skipped and tool calls go straight into the workspace-write sandbox (fully relying on the sandbox policy); step 2 hard deny and step 5 escalation approval always apply, regardless of this switch. Settrueto restore the full pre-sandbox interception.
-
Non-Auto session: pass through unchanged, official behavior untouched.
-
Auto session: synchronous hard deny (privilege escalation, self-destruction, credential exfiltration, deletion of root/home/DSH_HOME/system-critical paths) → cannot be overridden by later listeners or the LLM.
-
Deterministic allow (read-only, session state, in-workspace edits and deletes, read-only shell, build/test, version probing,
run_codecontainer; reads of ordinary paths outside the workspace pass directly; writes/deletes outside the workspace (except writes to sensitive shell/credential config files) pass and rely on the workspace-write sandbox + escalation, writes to sensitive config files outside the workspace go to LLM review; empty commands, dynamic command names, missing arguments fall through to the sandbox). -
Operations the sandbox doesn't intercept but are semantically dangerous (ambiguous shell, sensitive path reads, dynamic targets, block devices, persistent terminals, git state changes, network/database, writes to protected in-workspace paths) → LLM two-state decision (allow / deny).
-
After an LLM deny or classifier error, the AI has two human-approval channels (semi-auto
auto-askmode only): a. use ask_user_question to confirm the operation is legitimate, then re-run after the user confirms and pass LLM approval again; b. retry with sandbox_permissions + justification on bash/pwsh to go through DSH's built-in sandbox escalation — this plugin runs the LLM first: a reasonable escalation (explicitly authorized by the user) is approved directly without a popup; dangerous/uncertain cases show a human popup.Full-auto
automode: escalation approval is decided by the LLM as final — allow approves directly, deny / classifier error denies directly, no human popup.
Directory structure
src/
index.ts Entry: guard + tools/pre-execute two-state decision + escalation retry allow + approval-trail RPC
policy.ts Tool-level deterministic rules (L0) and danger detection
shell.ts bash/pwsh static analysis (L0 hard deny + dangerous shell detection)
classifier.ts LLM classifier (DSH built-in LLM / optional HTTP endpoint) + sanitization + system prompt
paths.ts Path normalization, dangerous-path detection, workspace-root resolution
trail.ts Approval trail (process-level ring buffer, append-only, not persisted)
types.ts Shared types
client.tsx Settings UI card + approval-trail panel (client bundle)
client-logic.ts Client UI logic (form controller / trail controller / i18n strings)
tests/ Tests (paths / shell / policy / classifier / trail / settings / client-logic / index)
scripts/
build-client.mjs Client bundle build script
fix-session-zstd.py Session zstd repair script
cordis.patch.yml Permission preset table (inserts auto-ask semi-auto + auto full-auto, sandbox=workspace-write)
lib/ Compiled output (generated by build and version-controlled; do not edit by hand)
Known limitations
- Static path checks do not follow symlinks; a symlink inside the workspace pointing to a sensitive external path has a TOCTOU limitation (the initial
ln -sitself must pass the classifier). - Deleting files inside the workspace is allowed directly, relying on the workspace-write sandbox as the fallback; session artifact tracking is not performed, and deletes outside the workspace also pass and rely on sandbox interception + escalation.
- The classifier defaults to reusing the current session model; if the session uses a third-party provider, classification requests go to that provider (sanitized and bounded).
License
MIT