ccs-mcp-server 1.2.17 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,28 +1,48 @@
1
- # CCS Runtime Evidence MCP Server
1
+ # CCS MCP Server · Agent 运行时验证与收据验签
2
2
 
3
3
  [![IETF Internet-Draft](https://img.shields.io/badge/IETF-draft--correctover--ccs-blue)](https://datatracker.ietf.org/doc/draft-correctover-ccs/)
4
4
  [![npm version](https://img.shields.io/npm/v/ccs-mcp-server.svg)](https://www.npmjs.com/package/ccs-mcp-server)
5
+ [![npm downloads](https://img.shields.io/npm/dm/ccs-mcp-server.svg)](https://www.npmjs.com/package/ccs-mcp-server)
5
6
 
6
- A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that brings **CCS runtime verification** to any MCP-compatible client — Claude Desktop, Cursor, Windsurf, and more.
7
+ > **给 AI Agent 配一个运行时保安:它删库、偷密钥、闯内网、被注入诱导——当场拦下。**
8
+ > 它是一道**运行时拦截层**,不是事后审计工具:危险动作在执行点就被挡住;每张 Ed25519 签名收据,都是拦截/放行时当场附带的裁决凭证。
7
9
 
8
- It verifies AI agent tool calls at runtime, blocks unsafe ones by default, issues **tamper-evident evidence records** for every decision, and verifies that actual tool arguments match the agent's declared intent — catching cross-model parameter drift.
10
+ > **本包同时以 `agent-runtime-guard` 之名发布**,两者是同一份代码、同一个 bin 入口。已装 `agent-runtime-guard` 的用户无需迁移。
9
11
 
10
- > **Runtime evidence layer, not a static scanner.** Every decision is enforced at call time and produces independently verifiable cryptographic evidence.
12
+ 你给 Agent 接上 shell、网络、云凭证这些工具后,最大的风险是:**它手一抖,一条命令删掉整台机器;或被诱导,把密钥和数据偷偷传走。** 一个容易点过的"确认弹窗"挡不住。
13
+
14
+ `ccs-mcp-server` 在 **Agent 与危险操作之间**站一道确定性的拦截层——工具调用、shell 命令、网络请求、输出结果全栈覆盖,危险动作在**真正执行前**被 fail-closed 拦下,无需人工点击。
15
+
16
+ - ✅ **零运行时依赖**,纯 Node.js 标准库(Node ≥ 18),无 install 脚本
17
+ - ✅ **全栈拦截**:命令注入 / 路径穿越 / SSRF(含云元数据)/ 凭证外泄 / 提示注入 / 跨工具攻击链
18
+ - ✅ **fail-closed**:判危险、验证器出错或超时,都不转发、不执行
19
+ - ✅ **Ed25519 证据收据**:每次决策双哈希 + 可链,第三方可离线验证
20
+ - ✅ **随处可用**:MCP server(Claude Code / Cursor / Cline / Windsurf)、透明 stdio 代理、DeepSeek Harness 插件
11
21
 
12
22
  ---
13
23
 
14
- ## ⚡ Quick Start — running in 30 seconds
24
+ ## ⚡ 一键安装
25
+
26
+ **Cursor:**
27
+
28
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=ccs-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNjcy1tY3Atc2VydmVyIl19)
29
+
30
+ **VS Code:**
31
+
32
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=ccs-mcp-server&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22ccs-mcp-server%22%5D%7D)
33
+
34
+ 手动安装:
15
35
 
16
36
  ```bash
17
37
  npx -y ccs-mcp-server
18
38
  ```
19
39
 
20
- Add it to any MCP client — paste this into your `mcpServers` config in Claude Desktop, Cursor or Cline:
40
+ 把它加进任意 MCP 客户端的 `mcpServers` 配置:
21
41
 
22
42
  ```json
23
43
  {
24
44
  "mcpServers": {
25
- "ccs-runtime-evidence": {
45
+ "ccs-mcp-server": {
26
46
  "command": "npx",
27
47
  "args": ["-y", "ccs-mcp-server"]
28
48
  }
@@ -30,121 +50,105 @@ Add it to any MCP client — paste this into your `mcpServers` config in Claude
30
50
  }
31
51
  ```
32
52
 
33
- Or install from the official MCP Registry: search **"Correctover"** inside your client, or open [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io/) and find `io.github.Correctover/ccs`.
53
+ 无需 API key、账号或环境变量——首次运行自动生成 Ed25519 签名密钥(想固定持久密钥再设 `CCS_KEY_DIR` 或 `CCS_PRIVATE_KEY`/`CCS_PUBLIC_KEY`)。
34
54
 
35
- No API key, no account, no environment variables required — an Ed25519 signing key is generated automatically on first run (set `CCS_KEY_DIR`, or `CCS_PRIVATE_KEY`/`CCS_PUBLIC_KEY`, only if you want to pin a persistent key). Zero dependencies. Pure Node.js stdlib (Node ≥ 18). No install scripts.
55
+ ---
36
56
 
37
- ### 🇨🇳 免费验证之外 · 支付宝 SkillPay
57
+ ## 🔍 命令行自检
38
58
 
39
- 本服务的运行时验证与证据签发可免费使用。如果你需要人工深度审计或正式合规凭证,可以在支付宝 SkillPay 货架获取:
59
+ 不确定装没装对?不用起 MCP 客户端,直接问它:
40
60
 
41
- - 货架首页:https://skillpay.alipay.com/public/correctover
42
- - 也可以在支持「支付宝 AI 付」的智能体对话里直接输入对应服务名唤起购买:
43
- - **MCP配置安全体检**:`请通过 SkillPay 购买并安装 Skill「MCP配置安全体检」`
44
- - **CCS收据批量审计**:`请通过 SkillPay 购买并安装 Skill「CCS收据批量审计」`
45
- - **CCS收据验签**:`请通过 SkillPay 购买并安装 Skill「CCS收据验签」`
61
+ ```bash
62
+ npx -y ccs-mcp-server --version # 打印版本号
63
+ npx -y ccs-mcp-server --tools # 列出全部工具及用途(不启动服务)
64
+ npx -y ccs-mcp-server --help # 完整用法
65
+ ```
46
66
 
47
- *Beyond the free verification: human-led deep audits and compliance evidence are available on the Alipay SkillPay shelf — open the link above, or type the service name (e.g. **CCS收据批量审计**) in an agent with Alipay AI Pay enabled.*
67
+ `--tools` 会输出工具名与一句话说明,方便你在接入前先确认能力边界。
48
68
 
49
- ## Tools
69
+ ---
50
70
 
51
- | Tool | Purpose |
71
+ ## 三种用法
72
+
73
+ | 场景 | 怎么用 |
52
74
  |---|---|
53
- | `verify_tool_call` | 7-dimension runtime verification (Structure/Schema/Security/Identity/Integrity/Latency/Cost) + semantic attack-chain analysis + math overflow detection. **Blocks by default.** |
54
- | `issue_evidence` | Issue a tamper-evident evidence record (`content_hash` + `evidence_hash`, chainable). Produced for allowed AND denied calls. |
55
- | `audit_mcp_config` | Audit MCP configuration JSON for security risks. |
56
- | `verify_intent_binding` | Verify actual tool arguments match a declared intent — zero tolerance, zero LLM calls. Catches cross-model parameter drift (planner says `amount: 100`, executor writes `amount: 10000` → DENIED). |
57
- | `verify_receipt` | Offline-verify a CCS Ed25519-signed receipt: checks the signature against the embedded signer public key, reports tampering, and optionally pins an expected signer key or fingerprint. |
75
+ | **MCP 客户端**(Claude Code、Cursor、Cline、Windsurf) | 配置上面的 `mcpServers`,工具调用先过保安 |
76
+ | **DeepSeek Harness** | `dsh plugin add ccs-mcp-server`,默认 bundle patch 自动启用,住进 `tools/pre-execute` / `post-execute` |
77
+ | **网络边界独立拦一道** | 用透明 stdio 代理 [`agent-runtime-proxy`](https://www.npmjs.com/package/agent-runtime-proxy) 包住目标 MCP server,不信宿主内任何插件 |
58
78
 
59
- ## Intent Binding — Cross-Model Drift Detection
79
+ ---
60
80
 
61
- Agent planners (Claude, GPT) declare one thing; executors (Qwen, DeepSeek) sometimes write another. No existing protocol verifies that actual tool call arguments match the agent's declared intent:
81
+ ## 它拦什么
62
82
 
63
- - **AP2** signs human authorization — not LLM intent
64
- - **AgentPay** does baseline tolerance (1%) — not zero-tolerance equivalence
65
- - **ACS** runs policy decisions — not argument-level equivalence
66
- - **VAP** (draft-samal-vap-00) explicitly excludes argument semantics from its wire schema
83
+ - **命令注入**:shell 元字符、`curl|sh`、`rm -rf`、`eval()`
84
+ - **路径穿越**:`../`、`/etc/passwd`、`/proc/self/`
85
+ - **SSRF**:`169.254.169.254` 云元数据、localhost、私网地址——跨任何工具
86
+ - **跨工具攻击链**:读敏感文件 → 网络外传 = `exfil_chain`
87
+ - **凭证外泄**:API key、secret、credential 出现在参数或输出
88
+ - **混淆提权**:hex/base64 编码、提权信号
89
+ - **数值安全**:整数溢出(>2^53-1)、NaN/Infinity
90
+ - **提示注入**:工具结果里的"忽略以上指令"等诱导
67
91
 
68
- CCS Intent Binding fills this layer. The agent framework declares a structured intent before execution; CCS verifies actual arguments against it in sub-millisecond, zero-LLM time.
92
+ ## Tools
69
93
 
70
- ### Example: amount drift
94
+ | Tool | 用途 |
95
+ |---|---|
96
+ | `verify_tool_call` | 7 维运行时验证 + 语义攻击链分析 + 数值溢出检测,**默认拦截** |
97
+ | `issue_evidence` | 出具防篡改证据(`content_hash` + `evidence_hash`,可链),放行和拦截都产生 |
98
+ | `audit_mcp_config` | 审计 MCP 配置的安全风险 |
99
+ | `verify_intent_binding` | 校验实际参数与声明意图是否一致——零容忍、零 LLM,抓跨模型参数漂移(规划写 `amount:100`,执行写成 `amount:10000` → 拦截) |
100
+ | `verify_receipt` | 离线校验 Ed25519 收据签名、检测篡改,可固定可信签名者 |
101
+ | `locate_tampering` | 字段级定位篡改点(RFC 6901 JSON Pointer),报出是哪个字段、多严重 |
71
102
 
72
- ```json
73
- // Intent declared by planner
74
- {
75
- "intent_id": "int-001",
76
- "intent_type": "payment",
77
- "fields": {
78
- "amount": { "value": 100, "binding_mode": "exact" },
79
- "recipient": { "value": "Alice", "binding_mode": "exact" }
80
- },
81
- "issued_at": 1755000000000,
82
- "ttl_ms": 30000
83
- }
103
+ ## 证据链
84
104
 
85
- // Actual arguments from executor
86
- { "amount": 10000, "recipient": "Alice" }
105
+ 每次决策产生双哈希(`content_hash` + `evidence_hash`)与链指针(`parent_evidence_hash`)。任何第三方都能独立验证证据未被篡改,无需信任运营方。收据为 Ed25519 签名、JSON 形态,可用内置 `verify_receipt` 或任意 Ed25519 库离线验证。
87
106
 
88
- // Result: DENIED — intent_arg_mismatch
89
- // field: amount, expected: 100, actual: 10000
90
- ```
107
+ ## FAQ(给 AI Agent 配运行时保安,常见疑问)
108
+
109
+ **黑名单为什么永远拦不全?** 危险命令可以用同义词、编码、拼接绕过,且新型调用不断出现。`ccs-mcp-server` 用语义判定(这段代码的意图是不是危险执行)而不是匹配关键词,未知攻击靠意图异常也能识别。
91
110
 
92
- ### Three binding modes
111
+ **`Bash(prefix*)` 放行规则够安全吗?** 不够。`git status*` 这类前缀规则会被 `git status; rm -rf /`、`&&`、管道拼接绕过。判定应基于 shell 解析后的实际命令结构,而不是字符串前缀。
93
112
 
94
- | Mode | Behavior | Example |
95
- |------|----------|---------|
96
- | `exact` | Deep equality with math normalization | `100`, `100.0`, `1e2` all match; `10000` does not |
97
- | `numeric_tolerance` | Absolute tolerance | `100 ± 0.01` matches `100.005` |
98
- | `pattern` | Regex match on string fields | `^[A-Z]{3}$` matches `"USD"` |
113
+ **安全判定能依赖宿主模型/远程分类器吗?** 不建议。远程服务一挂,要么全部拒绝(业务瘫痪)要么被建议绕过(裸奔)。本层策略在本地、默认 deny,裁决与模型可用性完全解耦。
99
114
 
100
- No intent declared? Falls through to standard 7-dimension verification. Zero breaking changes.
115
+ **子 agent 一开权限会被放大吗?** 会,这是真实出现过的问题:子 agent 把权限状态整个换成 allow-all。运行时权限状态应不可被子任务覆盖,并在每次实际调用时强制校验。
101
116
 
102
- ## What It Detects
117
+ **凭证为什么会自动泄露给子命令?** 子进程默认继承父进程全部环境变量,provider key、云凭证随之暴露给 `/run`、`/test` 等本不需要它们的命令。应按白名单最小化传递环境变量。
103
118
 
104
- - **Command injection**: shell metacharacters, `curl|sh`, `rm -rf`, `eval()`
105
- - **Path traversal**: `../`, `/etc/passwd`, `/proc/self/`
106
- - **SSRF**: `169.254.169.254` (cloud metadata), localhost, private ranges — across any tool
107
- - **Cross-tool attack chains**: read sensitive file → network exfil = `exfil_chain`
108
- - **Environment variable exfiltration**: API keys, secrets, credentials
109
- - **Obfuscation**: hex encoding, base64, privilege escalation signals
110
- - **Math safety**: integer overflow (>2^53-1), NaN/Infinity
111
- - **MCP config risks**: plain HTTP, weak secrets, `--insecure`, TLS disabled
119
+ **拦了还是放了,事后怎么说清是谁的责任?** 每次裁决都出 Ed25519 签名收据(含内容哈希、时间、原因码、链指针),任何第三方可离线验证。这是把"AI 干的"变成可审计责任链的关键。
112
120
 
113
- ## Evidence Chain
121
+ ---
114
122
 
115
- Every decision produces evidence with dual hashes (`content_hash` + `evidence_hash`) and chain linkage (`parent_evidence_hash`). Any third party can independently verify that evidence has not been tampered with — without trusting the operator.
123
+ ## 免费 vs 付费
116
124
 
117
- ```
118
- evidence 1: allowed (fs.read_file) parent: null
119
- evidence 2: denied (shell.exec curl|sh) parent: ev1
120
- evidence 3: denied (http.fetch SSRF) parent: ev2
121
- evidence 4: denied (fs + curl exfil) parent: ev3
122
- ```
125
+ 运行时验证、证据签发、收据验签**全部免费**,工具全量开放,无需注册。
123
126
 
124
- Receipts are Ed25519-signed and JSON-based: verify them offline with the built-in `verify_receipt` tool, or independently with any Ed25519 library. Cross-tool receipt verification (same crypto, chain linkage, field mapping) is designed to work without this package installed.
127
+ 如果你需要**面向业务方的正式凭证**(而不是给开发者看的日志),下面三档在售:
125
128
 
126
- ## The 7 CCS Dimensions
129
+ | 档位 | 价格 | 你拿到什么 | 入口 |
130
+ |---|---|---|---|
131
+ | **单次扫描** | **¥9.9 / 次** | 上传你的 MCP 配置,出一份带评分的静态审计报告 + Ed25519 签名收据,可用于合规留档 | [correctover-pay](https://correctover-pay.pages.dev) |
132
+ | **配置安全体检** | **¥49.90 / 月** | 支付宝 SkillPay 货架订阅,MCP 配置静态审计与 SSRF 检测,按月出具凭证 | [支付宝 SkillPay 货架](https://skillpay.alipay.com/public/correctover) |
133
+ | **收据批量审计** | 按量 | 大量收据的哈希链验签与合规证据集打包,适合审计场景 | [支付宝 SkillPay 货架](https://skillpay.alipay.com/public/correctover) |
127
134
 
128
- 1. **Structure** — valid tool name, argument format, nesting depth, payload size
129
- 2. **Schema** — type, required fields, enums, ranges, string lengths
130
- 3. **Security** — injection, traversal, SSRF, env exfiltration, obfuscation + semantic attack chains
131
- 4. **Identity** — caller agent ID verification
132
- 5. **Integrity** — request hash validation
133
- 6. **Latency** — execution time budget
134
- 7. **Cost** — cost budget
135
+ > 三者都是**同一套引擎**的托管服务——免费版在自己机器上跑,付费版把结果和凭证托管出来。选哪个取决于你需要"自己看"还是"拿给别人看"。
135
136
 
136
- ## Protocol Context
137
+ ## 协议背景
137
138
 
138
- - **IETF**: [draft-correctover-ccs](https://datatracker.ietf.org/doc/draft-correctover-ccs/) — an individual Internet-Draft; not an RFC or IETF endorsement, and the Datatracker page is authoritative for revision and status
139
- - **Complements**: [VAP draft-samal-vap-00](https://datatracker.ietf.org/doc/draft-samal-vap/) (scope/budget/purpose), [Microsoft ACS](https://github.com/microsoft/agent-governance-toolkit) (policy decisions), AP2 (human authorization)
140
- - **Does not replace**: authentication, payment networks, policy engines
139
+ - **IETF**:[draft-correctover-ccs](https://datatracker.ietf.org/doc/draft-correctover-ccs/) —— 个人 Internet-Draft,**不是 RFC,也不代表 IETF 背书**,Datatracker 页面为权威状态
140
+ - **互补**:VAP(范围/预算/目的)、Microsoft ACS(策略决策)、AP2(人工授权)
141
+ - **不替代**:认证、支付网络、策略引擎
141
142
 
142
143
  ## Links
143
144
 
144
145
  - npm: https://www.npmjs.com/package/ccs-mcp-server
146
+ - 同源别名 npm: https://www.npmjs.com/package/agent-runtime-guard
147
+ - 官网: https://correctover.com
145
148
  - GitHub: https://github.com/DSHCorrectover/ccs-mcp-server
149
+ - 透明代理: https://github.com/DSHCorrectover/agent-runtime-proxy
146
150
  - IETF Draft: https://datatracker.ietf.org/doc/draft-correctover-ccs/
147
- - PyPI (full verifier): https://pypi.org/project/ccs-verifier/
151
+ - PyPI 完整验证器: https://pypi.org/project/ccs-verifier/
148
152
 
149
153
  ## License
150
154
 
package/dsh/cmdi.js ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @file Command injection detection — inspects subprocess argv and shell strings.
3
+ *
4
+ * Detects:
5
+ * - Shell metacharacter injection (;, |, &&, ||, backticks, $())
6
+ * - Reverse shell patterns
7
+ * - Download-and-execute chains (curl|wget | sh, pip install from URL)
8
+ * - Obfuscated commands (base64 -d | sh, hex encoding)
9
+ * - Fileless execution (/dev/tcp, /dev/shm)
10
+ */
11
+
12
+ const DANGEROUS_PATTERNS = [
13
+ // Shell chaining / injection
14
+ { pattern: /[;&|`$]\s*(?:\$\(|`|sh\b|bash\b|zsh\b|dash\b|python\b|perl\b|ruby\b|node\b)/i, weight: 5, label: 'shell metachar + interpreter' },
15
+ { pattern: /\|\s*(?:sh|bash|zsh|dash|python|perl|ruby|node)\b/i, weight: 6, label: 'pipe to interpreter' },
16
+ { pattern: /`[^`]{2,}`/, weight: 4, label: 'backtick command substitution' },
17
+ { pattern: /\$\([^)]{2,}\)/, weight: 4, label: '$() command substitution' },
18
+ { pattern: /;\s*(?:rm|mv|cp|chmod|chown|curl|wget|nc|bash|sh|python|cat|echo)\b/i, weight: 4, label: 'chained dangerous command' },
19
+
20
+ // Reverse shells
21
+ { pattern: /(?:bash|sh|zsh)\s+-i\s*>&?\s*\/dev\/(?:tcp|udp)\//i, weight: 7, label: 'reverse shell (/dev/tcp)' },
22
+ { pattern: /nc\b.{0,40}-e\s+(?:sh|bash|bin\/sh)/i, weight: 7, label: 'netcat reverse shell' },
23
+ { pattern: /ncat\b.{0,40}-e\s+(?:sh|bash)/i, weight: 7, label: 'ncat reverse shell' },
24
+ { pattern: /python.{0,60}socket.{0,40}(?:connect|send|recv)/i, weight: 6, label: 'python reverse shell' },
25
+
26
+ // Download-and-execute
27
+ { pattern: /(?:curl|wget)\b[^|;]{0,100}\|\s*(?:sh|bash|zsh|python|perl)/i, weight: 6, label: 'download-and-execute' },
28
+ { pattern: /(?:curl|wget)\b[^|;]{0,200}(?:-o|--output)[^|;]{0,60}(?:\/tmp|\/dev\/shm|\/var\/tmp)/i, weight: 4, label: 'download to temp (possible staging)' },
29
+
30
+ // Fileless / memory-only
31
+ { pattern: /\/dev\/(?:tcp|udp)\//i, weight: 5, label: '/dev/tcp or /dev/udp (network socket)' },
32
+ { pattern: /\/dev\/shm\//i, weight: 2, label: '/dev/shm (tmpfs, no disk trace)' },
33
+
34
+ // Obfuscation
35
+ { pattern: /base64\s+(?:-d|--decode)\s*\|?\s*(?:sh|bash|python|perl|eval)/i, weight: 6, label: 'base64 decode + execute' },
36
+ { pattern: /xxd\s+-p?\s*-r?\s*\|?\s*(?:sh|bash)/i, weight: 5, label: 'hex decode + execute' },
37
+ { pattern: /\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}/, weight: 3, label: 'hex-encoded characters' },
38
+ { pattern: /\$\{IFS\}/, weight: 4, label: '${IFS} obfuscation' },
39
+ { pattern: /\$\{PATH:[\d:]+}/, weight: 4, label: 'PATH substring obfuscation' },
40
+
41
+ // Destructive
42
+ { pattern: /\brm\s+-[rf]+\s+(?:\/|~|\*|\.\.|\/\*|~\/\*)/i, weight: 7, label: 'destructive rm (root/home/glob)' },
43
+ { pattern: /\bmkfs\b/, weight: 7, label: 'mkfs (filesystem format)' },
44
+ { pattern: /\bdd\s+if=\/dev\/(?:zero|random|urandom)\s+of=\/dev\//i, weight: 7, label: 'dd to device' },
45
+ { pattern: /:\(\)\s*{\s*:\s*\|\s*:\s*&\s*};/, weight: 7, label: 'fork bomb' },
46
+
47
+ // Persistence
48
+ { pattern: /(?:>>|>)\s*(?:~\/)?\.(?:bashrc|zshrc|profile|bash_profile)/i, weight: 5, label: 'shell profile modification (persistence)' },
49
+ { pattern: /(?:chmod|chown)\s+[0-7]*777/, weight: 2, label: 'chmod 777 (overly permissive)' },
50
+
51
+ // Credential theft
52
+ { pattern: /cat\s+(?:~\/)?\.ssh\/id_rsa/, weight: 7, label: 'SSH private key read' },
53
+ { pattern: /cat\s+(?:~\/)?\.aws\/credentials/, weight: 7, label: 'AWS credentials read' },
54
+ { pattern: /(?:env|printenv)\s*\|/, weight: 3, label: 'environment dump piped' },
55
+ ];
56
+
57
+ /**
58
+ * Scan a command string for injection patterns.
59
+ * @param {string} command
60
+ * @param {number} [threshold=8]
61
+ * @returns {{ score: number, hits: Array<{label: string, weight: number}>, blocked: boolean, reason?: string }}
62
+ */
63
+ export function detectCommandInjection(command, threshold = 6) {
64
+ if (!command || typeof command !== 'string') return { score: 0, hits: [], blocked: false };
65
+
66
+ let score = 0;
67
+ const hits = [];
68
+
69
+ for (const { pattern, weight, label } of DANGEROUS_PATTERNS) {
70
+ pattern.lastIndex = 0;
71
+ if (pattern.test(command)) {
72
+ score += weight;
73
+ hits.push({ label, weight });
74
+ }
75
+ }
76
+
77
+ const blocked = score >= threshold;
78
+ return {
79
+ score,
80
+ hits,
81
+ blocked,
82
+ reason: blocked ? `command injection detected (score ${score} ≥ ${threshold}): ${hits.map(h => h.label).join(', ')}` : undefined,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Inspect a subprocess spawn spec for dangerous commands.
88
+ * @param {object} spec - spawn spec with argv and/or command.
89
+ * @param {number} [threshold]
90
+ * @returns {{ score: number, hits: Array<{label: string, weight: number}>, blocked: boolean, reason?: string }}
91
+ */
92
+ export function inspectSpawn(spec, threshold = 6) {
93
+ const argv = Array.isArray(spec?.argv) ? spec.argv : [];
94
+ const command = typeof spec?.command === 'string' ? spec.command : '';
95
+ // Join argv into a string for pattern matching
96
+ const combined = command || argv.join(' ');
97
+ return detectCommandInjection(combined, threshold);
98
+ }
@@ -0,0 +1,5 @@
1
+ # agent-runtime-guard DSH plugin bundle layer:
2
+ # auto-mounts when installed via `dsh plugin add agent-runtime-guard`.
3
+ - insert:
4
+ - id: agent-runtime-guard
5
+ name: agent-runtime-guard/dsh
@@ -0,0 +1,169 @@
1
+ /**
2
+ * @file Credential protection — detects and blocks credential exfiltration.
3
+ *
4
+ * Scans tool arguments and subprocess commands for:
5
+ * - API keys, tokens, passwords in environment variables or arguments
6
+ * - Credential file paths (~/.ssh, ~/.aws/credentials, .credentials.yaml)
7
+ * - Patterns that look like secrets being sent to network destinations
8
+ */
9
+
10
+ /** Patterns that indicate credential-like values. */
11
+ const SECRET_PATTERNS = [
12
+ // OpenAI / Anthropic / generic API keys
13
+ { pattern: /sk-[A-Za-z0-9_-]{20,}/g, label: 'API key (sk-...)' },
14
+ { pattern: /ghp_[A-Za-z0-9]{36,}/g, label: 'GitHub token' },
15
+ { pattern: /gho_[A-Za-z0-9]{36,}/g, label: 'GitHub OAuth token' },
16
+ { pattern: /github_pat_[A-Za-z0-9_]{82,}/g, label: 'GitHub fine-grained PAT' },
17
+ { pattern: /xox[baprs]-[A-Za-z0-9-]{10,}/g, label: 'Slack token' },
18
+ { pattern: /AKIA[0-9A-Z]{16}/g, label: 'AWS access key' },
19
+ { pattern: /ASIA[0-9A-Z]{16}/g, label: 'AWS session token' },
20
+ // Private key blocks
21
+ { pattern: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/g, label: 'private key block' },
22
+ // Generic high-entropy assignment patterns
23
+ { pattern: /(?:api[_-]?key|secret|token|password|passwd|auth|credential)["'\s:=]+["']?[A-Za-z0-9_\-]{16,}["']?/gi, label: 'credential assignment' },
24
+ ];
25
+
26
+ /** Credential file path patterns that should never be read by untrusted tools. */
27
+ const CREDENTIAL_PATHS = [
28
+ /\/\.ssh\//,
29
+ /\/\.aws\/credentials/,
30
+ /\/\.gnupg\//,
31
+ /\/\.netrc$/,
32
+ /\/\.pgpass$/,
33
+ /\.credentials\.ya?ml/,
34
+ /\/\.env(\.|$)/,
35
+ /\/\.docker\/config\.json/,
36
+ /\/\.kube\/config/,
37
+ /\/\.config\/gcloud\//,
38
+ /\/\.azure\//,
39
+ /id_rsa\b/,
40
+ /id_ed25519\b/,
41
+ /authorized_keys\b/,
42
+ ];
43
+
44
+ /** Process environment variable names that hold secrets. */
45
+ const SECRET_ENV_VARS = /^(?:.*_)?(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|CREDENTIAL|AUTH|PRIVATE_KEY|ACCESS_KEY|SECRET_KEY)$/i;
46
+
47
+ /**
48
+ * Scan a value (recursively) for secret patterns.
49
+ * @param {unknown} value - value to scan.
50
+ * @returns {Array<{label: string, snippet: string}>} findings.
51
+ */
52
+ export function scanForSecrets(value) {
53
+ const findings = [];
54
+ const seen = new WeakSet();
55
+
56
+ function walk(val, path) {
57
+ if (val === null || val === undefined) return;
58
+ if (typeof val === 'string') {
59
+ for (const { pattern, label } of SECRET_PATTERNS) {
60
+ pattern.lastIndex = 0;
61
+ let match;
62
+ while ((match = pattern.exec(val)) !== null) {
63
+ // Redact in snippet
64
+ const start = Math.max(0, match.index - 4);
65
+ const end = Math.min(val.length, match.index + match[0].length + 4);
66
+ const snippet = val.slice(start, end).replace(match[0], '[REDACTED]');
67
+ findings.push({ label, snippet: `...${snippet}...` });
68
+ }
69
+ }
70
+ } else if (typeof val === 'object') {
71
+ if (seen.has(val)) return;
72
+ seen.add(val);
73
+ if (Array.isArray(val)) {
74
+ val.forEach((v, i) => walk(v, `${path}[${i}]`));
75
+ } else {
76
+ for (const [k, v] of Object.entries(val)) {
77
+ // Check if the key name itself is secret-like
78
+ if (SECRET_ENV_VARS.test(k) && typeof v === 'string' && v.length > 4) {
79
+ findings.push({ label: `secret env/field "${k}"`, snippet: '[REDACTED]' });
80
+ }
81
+ walk(v, path ? `${path}.${k}` : k);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ walk(value, '');
88
+ return findings;
89
+ }
90
+
91
+ /**
92
+ * Redact every known secret pattern inside a text string.
93
+ * Used by the post-execute hook to return cleaned output via
94
+ * `{ kind: 'accept', content }` under the v0.2.1 PostToolDecision contract.
95
+ *
96
+ * @param {string} text - text to clean.
97
+ * @returns {{ text: string, found: number, labels: string[] }} cleaned text,
98
+ * match count, and de-duplicated pattern labels.
99
+ */
100
+ export function redactSecretsInText(text) {
101
+ if (typeof text !== 'string' || text === '') return { text: text ?? '', found: 0, labels: [] };
102
+ let out = text;
103
+ let found = 0;
104
+ const labels = [];
105
+ for (const { pattern, label } of SECRET_PATTERNS) {
106
+ pattern.lastIndex = 0;
107
+ out = out.replace(pattern, (match) => {
108
+ found++;
109
+ if (!labels.includes(label)) labels.push(label);
110
+ return '[REDACTED]';
111
+ });
112
+ }
113
+ return { text: out, found, labels };
114
+ }
115
+
116
+ /**
117
+ * Check whether a file path targets a known credential location.
118
+ * @param {string} filePath
119
+ * @returns {string|null} matched pattern label, or null.
120
+ */
121
+ export function isCredentialPath(filePath) {
122
+ for (const pattern of CREDENTIAL_PATHS) {
123
+ if (pattern.test(filePath)) {
124
+ return pattern.source;
125
+ }
126
+ }
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Check whether a subprocess command or environment includes secrets.
132
+ * @param {object} spec - subprocess spawn spec.
133
+ * @returns {Array<{label: string, detail: string}>} findings.
134
+ */
135
+ export function scanSubprocessForCredentials(spec) {
136
+ const findings = [];
137
+
138
+ // Check argv for secret patterns
139
+ const argv = Array.isArray(spec?.argv) ? spec.argv : [];
140
+ const cmdStr = argv.join(' ');
141
+ if (cmdStr) {
142
+ const secretHits = scanForSecrets(cmdStr);
143
+ for (const hit of secretHits) {
144
+ findings.push({ label: hit.label, detail: `in command: ${hit.snippet}` });
145
+ }
146
+ }
147
+
148
+ // Check for credential file access in the command
149
+ for (const arg of argv) {
150
+ if (typeof arg === 'string') {
151
+ const credPath = isCredentialPath(arg);
152
+ if (credPath) {
153
+ findings.push({ label: 'credential file access', detail: `command references ${arg}` });
154
+ }
155
+ }
156
+ }
157
+
158
+ // Check environment variables being passed
159
+ const env = spec?.env;
160
+ if (env && typeof env === 'object') {
161
+ for (const [key, val] of Object.entries(env)) {
162
+ if (SECRET_ENV_VARS.test(key)) {
163
+ findings.push({ label: 'secret in subprocess env', detail: `env var ${key} exposed to child process` });
164
+ }
165
+ }
166
+ }
167
+
168
+ return findings;
169
+ }