Bundle
dsh-auto-approver
Configurable auto-approval for DeepSeek Harness: intercepts approval/request and answers allowed-once/rejected by policy (allow-all / allowlist / deny-always), with a full audit log — so the human is only asked when the policy says so.
- Source
- nicecx
- License
- MIT
- Updated
- Updated yesterday
Readme
# dsh-auto-approver
> **Configurable auto-approval for DeepSeek Harness.** Intercepts `approval/request` (and optionally `ask_user_question`) and answers by policy — rule layer, an optional **Hermes Pro semantic verdict**, or the human. Every decision is audit-logged.
## Why
DeepSeek Harness asks for approval before privileged operations (file writes, command execution, `danger-full-access`, …). In a trusted, autonomous setup — or for a known-safe subset of tools — those prompts are pure noise. This host plugin intercepts every approval request **before** the relay/UI push and settles it, with a full audit log and an interactive reject loop (the agent is told *why* it was rejected and can retry).
It is the mirror image of [dsh-reset-handoff](https://github.com/nicecx/dsh-reset-handoff): that plugin delegates restarts to an external ops agent; this one delegates approvals to a local policy and/or Hermes.
## How it works
```
agent requests permission
→ 'approval/request' event (host emits)
→ dsh-auto-approver (prepend, before relay/UI)
│
├─ denyAlways hit → 'rejected' (never prompts; Hermes cannot override)
├─ mode=allow-all → 'allowed-once' (never prompts)
├─ mode=allowlist → hit → 'allowed-once'; miss → next() (human asked)
├─ mode=hermes → allowlist hit → 'allowed-once'
│ else → Hermes Pro semantic verdict
│ (deepseek-v4-pro, 90s timeout, fail-closed → human)
└─ mode=off → next() (all to human, plugin inert)
→ every decision is appended to the audit log
→ 'rejected' also follows up the reason to the requesting session
```
Registering with `{ prepend: true, global: true }` makes the plugin answer **before** dsh-relay pushes the prompt to iMessage/Web — an auto-settled request never disturbs the human.
### QnA takeover (optional)
With `qnaMode: 'hermes'`, `ask_user_question` is also answered by Hermes Pro instead of interrupting the human. Hermes sees the question and its options (or free-form) and returns a choice per the relay answer format; if Hermes is unavailable it falls back to the human.
## Install
```sh
dsh plugin --profile <profile> add github:nicecx/dsh-auto-approver
```
## Configuration
Override in your profile patch (`cordis.patch.yml`):
```yaml
- id: dsh-auto-approver
name: 'dsh-auto-approver'
config:
mode: 'hermes' # allow-all | allowlist | hermes | off (default: allow-all)
allowlist: [] # tools auto-approved (rule layer; hermes mode: direct pass)
denyAlways: [] # tools always rejected (highest priority, Hermes cannot override)
denyReasons: {} # tool → reject reason text fed back to the agent
hermesModel: 'deepseek-v4-pro' # verdict model (Pro = highest capability)
hermesTimeoutSecs: 90 # verdict timeout; on failure → human (fail-closed)
feedbackOnReject: true # followup the reject reason to the requesting session
qnaMode: 'off' # 'hermes' = ask_user_question answered by Hermes Pro; 'off' = human
userGranted: [] # endorsement signal (NOT a bypass card) — see below
logPath: '' # audit log path (default ~/.dsh/auto-approver.log)
```
| mode | behavior |
| --- | --- |
| `allow-all` | auto-approve **everything** (incl. `danger-full-access`). Trusted environments only. |
| `allowlist` | auto-approve only listed tools; everything else asks the human. |
| `hermes` | rule layer (denyAlways / allowlist) + **Hermes Pro semantic verdict** for the rest. |
| `off` | plugin inert; everything goes to the human. |
### `userGranted` — endorsement signal, not a bypass card
`userGranted` is a *soft* endorsement passed into the Hermes verdict prompt ("the user explicitly authorized this tool — lean toward approval when the operation is reasonable and carries no data-destruction / credential-exfiltration risk"). It is **not** a hard allow:
- `denyAlways` still wins over everything.
- Hermes still rejects dangerous operations (data destruction, credential exfiltration, irreversible deletes).
- **Keep it empty by default** — adding broad tools (`bash`, `write`) conflicts with the least-privilege principle. Only list capabilities the user explicitly named.
## Interactive reject loop
When the policy (or Hermes) rejects, the plugin follows the reason back into the requesting session, so the agent knows *what* was wrong and can retry with a corrected request (e.g. narrower permission, concrete path, specific command). Manual approval on iMessage/Web always wins.
## Audit log
Every decision is appended (JSON lines) to `~/.dsh/auto-approver.log`:
```json
{"ts":"...","sessionId":"...","toolName":"bash","reason":"...","callId":"...","decision":"allowed-once"}
```
Hermes verdicts also carry the reason in `note` (e.g. `hermes: ...`).
## Safety notes
- `allow-all` auto-grants everything, including full-access commands. Prefer `allowlist`/`hermes` in anything less than a fully trusted single-user box.
- `hermes` mode is **fail-closed**: if Hermes is unavailable or times out, the request goes to the human — never silently granted.
- The relay/Web double-track is untouched: when the policy says `ask`, the human still decides on iMessage or the Web UI; manual approvals always win.
- The audit log is the complete record of auto-decisions — keep it.
## License
MIT
Install
dsh plugin --profile web add github:nicecx/dsh-auto-approver
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-auto-approver from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.