drscrewdriver/dsh-perm-gate ↗★ 1

dsh-perm-gate

提供确定性优先、默认拒绝的DSH权限控制网关 适合需要对AI执行的命令和敏感操作进行严格安全审计与授权拦截的场景。

套件
dsh-perm-gate
相容性
待驗證
Cordis 依賴範圍
^4.0.1
版本
2.1.2
授權
MIT
最近更新
2026年9月15日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:drscrewdriver/dsh-perm-gate

Configuration

Add the plugin to cordis.yml:

- id: dsh-perm-gate
  name: dsh-perm-gate
  config:
    rulesFile: ./permissions.yaml   # optional; defaults to $DSH_HOME/perm-gate/rules.yml
    dshHome: $DSH_HOME              # root pinned for protected-target checks
    defaultAction: ask              # allow | ask | deny
    gatePresets: [permissive, permissive-full]   # tiers where the gate is active (default)
    sessionSweep: true              # hourly cleanup of archived/dead sessions' gate data

Session sweep

On startup and every hour the gate reads DSH's workspace store ($DSH_HOME/storages/workspace.json, read-only) and classifies every session it holds authorization-chain data for. Sessions DSH has archived (global.archivedSessionIds) or no longer tracks at all have their decision events dropped from $DSH_HOME/perm-gate/events.jsonl and their pre-change snapshot files deleted from $DSH_HOME/perm-gate/snapshots/ — history the review page can no longer reach, for data the harness itself considers gone. Live sessions are untouched, unattributable rows (empty session id) are never deleted, and any failure is fail-open: the round is skipped and retried an hour later. Set sessionSweep: false to disable; workspaceStoreFile overrides the store path. Restoring an archived session does not restore its swept history.

Rules file

permissions:
  defaultAction: ask
  deny:
    - command: [rm#recursive]
      reason: no recursive rm
    - command: [ssh]
      reason: no direct ssh
    - paths: [.dsh/**]
      reason: protect harness metadata
  allow:
    - command: [pnpm, node]
      reason: dev tools
    - command: [curl, wget]
      args: ["https://*.example.com/*"]
      reason: allowed endpoint
  ask:
    - command: [bash, sh]
      reason: ask shells

A command entry word#flag matches the command word (word) with the modifier recursive or force — so rm#recursive matches rm -rf, env rm -rf, and sh -c "rm -rf /".

Network policy (opt-in)

A local HTTP/CONNECT proxy that adjudicates the outbound traffic of shell subprocesses against the same rules file, plus an approval path for targets no rule covers. Off by default — enabling it binds a loopback port and rewrites the proxy environment for child processes, so it is never turned on implicitly.

- id: dsh-perm-gate
  config:
    networkEnabled: false          # master switch (default false)
    networkMode: whitelist         # deny-all | whitelist | allow-all
    networkUnlisted: ask           # ask | deny  — unlisted target handling
    networkUnattributed: allow     # allow | deny — traffic with no shell attribution
    networkInjectEnv: true         # rewrite HTTP(S)_PROXY/ALL_PROXY for children
    networkAskTimeoutMs: 120000    # approval wait before failing closed
    networkGrantTtlMs: 1800000     # how long one approval covers its target

Tiered behaviour. Nothing reaches the network without an allow rule. An unlisted target is escalated to the interactive approval seam, raised on behalf of the shell command that opened the connection; approving widens reach for that target for the session. A deny rule is never escalated — approval can widen what an unlisted target may reach, but it can never override a rule that says no.

The boundary — read this before relying on it. The proxy is a cooperative policy layer, not an enforcement boundary. It only sees traffic from clients that read the proxy environment:

ClientCovered?
curl, wget, git, Go net/http, Python requestsyes
Node.js http/https/fetchno — connects directly
Java (without -D proxy flags), .NET HttpClientno
Raw sockets, custom TCPno
DNS, QUIC/HTTP3, non-HTTP protocolsno
Connections to a literal IPno

So a shell command like node -e "require('http').get('http://host/')" is not intercepted. Treat this as a guardrail against accidents and a place to state intent, not as a hermetic sandbox.

DSH's own network traffic — the built-in network tools and the LLM transport — is deliberately left alone. Those connections carry no shell attribution, and networkUnattributed: allow (the default) passes them through unreviewed: reviewing them would let the host block itself, which is a worse failure than a missed block. Set networkUnattributed: deny only if you know your host's clients ignore the proxy environment.

Query the live state at GET /api/dsh-perm-gate/network (mode, bind, port, proxy liveness, env-injection state, block counters, recent blocks).