dsh-fence
v0.1.0
Published
Declarative tool fencing for DeepSeek Harness: allow/deny/ask rules over tool invocations with argument globs, wired to the native approval flow, with an audit trail.
Maintainers
Readme
dsh-fence
Declarative tool fencing for DeepSeek Harness (dsh).
dsh gives you tools/pre-execute waterfalls and approval policies, but keeping agents away from rm -rf, .env writes, or internal URLs still means hand-writing listeners. dsh-fence turns that into config:
# in your profile's cordis.patch.yml
- id: fence
name: dsh-fence
config:
rules:
- match: { tool: bash, arguments: { command: "*rm -rf*" } }
decision: deny
reason: "no recursive deletes"
- match: { tool: write, arguments: { path: ".env*" } }
decision: ask
- match: { tool: web_fetch, arguments: { url: "**internal.example.com**" } }
decision: deny
defaultDecision: allow- First match wins — rules are evaluated top to bottom; the first matching rule decides.
allow/deny/ask—askhands off to dsh's native approval flow (UI prompt or headless responder). dsh-fence never auto-approves anything.- Argument globs — constrain any argument path (
command,path, nestedoptions.mode, …) with*patterns.*matches any characters including separators, so*rm -rf*catchesrm -rf /tmp/x. Over-matching is the safe default for a fence. - Audit trail — every decision is recorded (tool, decision, matched rule, reason) and shown via the
/fencecommand.
Unmatched tools fall through to defaultDecision (allow unless you say otherwise).
Install
Requires a dsh profile (see the dsh plugin docs):
dsh plugin --profile <name> add dsh-fence
# or from git:
dsh plugin --profile <name> add github:leesama/dsh-fenceRule reference
| Field | Type | Notes |
|---|---|---|
| match.tool | glob | Tool name, e.g. bash, write, * |
| match.arguments | map of path → glob | Dot paths into the parsed tool arguments; missing value ⇒ rule doesn't match |
| decision | allow | deny | ask | ask delegates to the native approval waterfall |
| reason | string | Shown on deny/ask and in the audit report |
Commands
/fence— decision counts and the most recent 20 audited calls.
How it works
tools/pre-execute (waterfall)
├─ evaluateRules(config, toolName, arguments) ← pure engine, first match wins
├─ allow → next() ← tool proceeds through the pipeline
├─ deny → { kind: "deny", reason } ← tool never runs
└─ ask → preserve downstream denial, otherwise { kind: "ask", reason }
(allowed-once runs; otherwise rejected)The rule engine (src/engine.ts) is pure and unit-tested; src/index.ts provides the Cordis wiring. The suite named test:e2e drives apply() against a mock DSH pipeline. These are simulated integration tests; they do not start a real DSH process or verify its approval UI.
test:package separately builds the release, packs and installs the tarball into a temporary directory, and imports its entry in a fresh Node process. This verifies the published module layout, not full DSH behavior.
Development
npm install
npm run typecheck # tsc --noEmit (strict)
npm test # unit + e2e
npm run test:coverage
npm run test:package # pack, install and load the built module
npm run build # tsc → lib/License
MIT
