dsh-human-intent
Human intent verification and cryptographic action authorization for AI agents.
AI can propose an action.
Only a human can authorize it.
Agent proposes Action
↓
Canonicalize Action
↓
Hash Exact Intent
↓
Human sees exact Action
↓
Human verifies (WebAuthn: Touch ID / Windows Hello / passkey)
↓
Cryptographically sign Intent
↓
Generate Intent Receipt
↓
Verify exact Action binding
↓
Execute
Why
AI agents can increasingly execute real-world actions: delete data, deploy
services, spend money, send messages. An agent having the capability to
run a tool does not mean a human granted the authority to run it.
Capability != Authority.
Existing human-in-the-loop mechanisms usually verify a fuzzy fact — "a human
touched the sensor" — and then hand the agent a blanket verified = true,
often valid for a window of time. Nothing binds the approval to the exact
action. An approval obtained for rm -rf ./test-data silently authorizes
rm -rf ./production-data.
dsh-human-intent closes that gap. Every authorization is:
- Exact-action-bound — the approval is hashed and signed over the precise
tool, target and arguments; any mutation invalidates it.
- Cryptographically signed — WebAuthn (platform authenticator, passkey,
or security key) with full server-side verification.
- One-shot — an authorization authorizes exactly one execution.
- Expiring — requests and receipts carry a short validity window.
- Auditable — every transition is recorded in a local append-only log.
This project is not a fingerprint-identification system and does not
identify a specific natural person. WebAuthn platform authenticators
(Touch ID / Windows Hello / passkeys) prove that the holder of a registered
credential performed a user-verification gesture. That proof is what gets
bound to the action. See Security Model.
How It Works
- The agent (or a
tools/pre-execute policy hook) proposes an
IntentRequest: tool, operation, target, arguments, risk, reason.
- The request is canonically serialized (RFC 8785 JCS) and hashed:
intentHash = SHA-256(canonical intent).
- The human opens the approval page and sees the exact action.
- Approving triggers a WebAuthn ceremony whose challenge is the
intentHash itself — the authenticator signs the bytes of the action.
- The server fully verifies the assertion (challenge, origin, RP ID,
public key signature, counter, user-verification flag) and issues an
IntentReceipt.
- Before execution, the receipt is consumed: the action is re-hashed and
must match, the signature is re-verified against the stored credential,
and the one-shot consumption marker is checked. Only then does the tool
run — exactly once, for exactly that action.
Quick Start
Requirements: Node.js ≥ 20.11.
npm install
npm run verify # build + lint + tests + http smoke test
npm run demo # interactive acceptance scenarios A–E
Demo walkthrough (the part you show people):
npm run demo
- No passkey yet? The browser opens and you register one (Touch ID on macOS,
Windows Hello on Windows).
- Pick a scenario; the agent proposes a destructive command, the browser
shows the exact action, you approve or deny, and the terminal prints the
cryptographic outcome — including the mutation, replay and expiry attacks
being rejected.
Run the approval server standalone:
npm run serve # http://localhost:8787
node bin/dsh-human-intent.js serve --port 9000 --data-dir ./data
Inspect a receipt (validates structure, integrity and signature):
node bin/dsh-human-intent.js inspect receipt.json
Read the audit log:
node bin/dsh-human-intent.js audit
DSH Plugin
Inside DeepSeek Harness (DSH), the plugin provides:
human_intent_request — propose an action, wait for the human, receive
an IntentReceipt (or an explicit denial).
human_intent_verify — verify a receipt authorizes one exact action
before executing it.
human_intent_status — enforcement status.
tools/pre-execute policy enforcement for protectedTools.
- A settings slot and a Better Sidebar approval panel (WebAuthn in the DSH
web UI), plus a loopback API under
/human-intent/api.
{
"protectedTools": ["shell.exec", "kubectl.*"],
"rules": [{ "tool": "shell.exec", "risk": "high" }]
}
Glob semantics: shell.* matches one dot-segment (shell.exec, not
shell.exec.sub); shell.** spans segments. Exact names always work.
Local development:
npm install
npm run build:client
dsh plugin --profile web add link:/Users/you/dsh-human-intent
The DSH client UI must be served from localhost for WebAuthn to be
available, and the DSH host origin must be listed in allowedOrigins.
Configuration
| Option | Default | Meaning |
|---|
enabled | true | Enforce human intent for protected tools. |
protectedTools | [] | Tool names / dot-globs requiring authorization. |
rules | [] | { tool, risk } rules combining protection and risk level. |
rpID | localhost | WebAuthn relying-party ID. |
allowedOrigins | http://localhost:<port> | WebAuthn origin allowlist. |
requestTtlMs | 120000 | Human decision window. |
dataDir | (memory) | Directory for credentials.json + audit.jsonl. |
IntentRequest
interface IntentRequest {
version: "0.1"
requestId: string // unique per request
intentHash: string // SHA-256 over the canonical intent
nonce: string // 256-bit unguessable, one per request
action: {
tool: string // e.g. "shell.exec"
operation?: string
target?: string // e.g. "namespace/prod/pod/foo"
arguments: object // exact arguments
}
context?: {
description?: string
reason?: string
risk?: "low" | "medium" | "high" | "critical"
}
agent?: { id?: string; name?: string }
session?: { id?: string }
issuedAt: string // ISO 8601
expiresAt: string // ISO 8601
}
IntentReceipt
interface IntentReceipt {
kind: "IntentReceipt"
version: "0.1"
requestId: string
intentHash: string // the action this receipt authorizes
decision: "approved" | "denied"
intent: IntentRequest // embedded, integrity-checked
authenticator: { type: "webauthn"; credentialId: string }
verification: { method: "webauthn"; userVerified: boolean }
signedAt: string
expiresAt: string // same window as the request
nonce: string
assertion?: { /* raw WebAuthn assertion for re-verification */ }
deniedReason?: string
}
Receipts serialize, verify and audit (docs/PROTOCOL.md).
Security Model
What this project guarantees, precisely:
- Action binding. Approving
tool=A, args=X produces a receipt that
cannot authorize tool=A, args=Y or tool=B, args=X. The intentHash
covers tool, operation, target, arguments, nonce, requestId and the
validity window.
- Signature coverage. The WebAuthn challenge is the intentHash, so
the authenticator's signature covers the exact action bytes.
- Full server-side verification. Challenge, origin allowlist, RP ID,
credential public key (COSE/ES256 or RS256), signature, signature
counter (clone detection) and the user-verification flag are all
verified with
@simplewebauthn/server. No client-provided
verified: true is ever trusted.
- One-shot consumption. A consumed receipt is rejected on reuse — in
memory and, when a data dir is configured, across restarts via the
audit log.
- Expiry. Requests and receipts share a short window; after it, only a
fresh human authorization works.
- Explicit denial. Denials are first-class outcomes the agent can read.
What this project does not claim:
- It does not identify which natural person is enrolled on the device.
WebAuthn proves a registered credential holder verified — see
docs/THREAT_MODEL.md for the full model.
- It cannot stop a human from approving a misleading reason string if the
arguments are honest; the UI shows raw arguments and the hash, never just
a summary.
- The local audit log is append-only JSONL; it is not tamper-proof against
a host compromise (future work: signed logs).
Architecture
Agent
↓ IntentRequest
Canonicalizer (RFC 8785 JCS)
↓ CanonicalIntent
Hasher (SHA-256)
↓ IntentHash
Human Intent UI (exact action, risk, arguments, expiry)
↓ WebAuthn ceremony (challenge = intentHash)
Verifier (@simplewebauthn/server: origin, RP, key, signature, counter, UV)
↓ IntentReceipt
Policy / Consumption Gate (action re-hash, one-shot, expiry)
↓
Tool Execution (exactly the authorized action)
Details: docs/ARCHITECTURE.md ·
Protocol: docs/PROTOCOL.md ·
Threats: docs/THREAT_MODEL.md
Demo
npm run demo runs the five acceptance scenarios:
| Scenario | Demonstrates |
|---|
| A | Destructive command → human approves → exact action executes once. |
| B | Human approves ./test-data; agent swaps to ./production-data → rejected (action_mismatch). |
| C | Approved receipt replayed a second time → rejected (already_consumed). |
| D | Request window elapses → expired, nothing executes. |
| E | Human presses Deny → agent receives an explicit denied decision. |
Migration from dsh-fingerprint-signature
dsh-human-intent evolved from dsh-fingerprint-signature, which gated
protected tools behind a platform user-verification popup and granted a
30-second blanket pass. That design verified human presence but nothing
about which action the human was approving.
This project keeps the plugin structure and the loopback-API pattern that
worked there, and replaces the security model:
| dsh-fingerprint-signature | dsh-human-intent |
|---|
| Verified fact | "a human is present" | "a human authorized this exact action" |
| Binding | none (blanket grant) | intentHash (tool + target + arguments) |
| Proof | helper exit code | WebAuthn signature (server-verified) |
| Validity | 30s window | one-shot, request-window-bounded |
| Denial | timeout only | explicit denied receipt |
| Audit | none | append-only audit log |
The signature-variable feature (names, identity IDs, custom variables
injected into the agent context) is not migrated: it is a different use
case. Keep using dsh-fingerprint-signature for it; both plugins can be
installed side by side.
See docs/MIGRATION.md.
Roadmap
- v0.1 — WebAuthn human intent, exact action binding, intent receipts,
protected tools, audit log, DSH plugin, CLI + demo.
- v0.2 — passkey sync/hardware-key profiles, richer policy rules, MCP
adapter.
- v0.3 — multi-party approval, organization policies, remote approval,
receipt transparency.
- Future — cross-agent human-intent protocol, SDKs, browser and
infrastructure integrations.
Limitations
- v0.1 stores credentials in a local JSON file; there is no per-user
account model or encrypted keystore yet.
- The DSH client approval panel requires a
localhost-served UI and a
WebAuthn-capable browser.
- Single-host enforcement: receipts are consumed on the host that issued
them (no distributed consumption ledger).
- Tests use a software authenticator with real EC P-256 keys; physical
authenticator behavior (resident keys, hybrid transport) is exercised in
the demo, not the test suite.
License
MIT — see LICENSE.