@yottameta/yotta-verify-mcp-plugin 0.4.2 → 0.4.3

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.
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Security",
15
- "version": "0.4.2",
15
+ "version": "0.4.3",
16
16
  "description": "YuanXinMCP (元信MCP) - the pre-install security scanner for Agent skills, exposed as a stdio MCP server: scan_skill (dir/package -> verdict + findings), generate_badge (audited badge), gate_check (CI gate), get_report (JSON/Markdown). Local offline static scan; no upload of scanned content, no execution of scanned code; human confirmation required.",
17
17
  "interface": {
18
18
  "displayName": "元信MCP yotta-verify-mcp",
@@ -12,7 +12,7 @@
12
12
  "authentication": "ON_INSTALL"
13
13
  },
14
14
  "category": "Security",
15
- "version": "0.4.2",
15
+ "version": "0.4.3",
16
16
  "description": "YuanXinMCP (元信MCP) - the pre-install security scanner for Agent skills, exposed as a stdio MCP server: scan_skill (dir/package -> verdict + findings), generate_badge (audited badge), gate_check (CI gate), get_report (JSON/Markdown). Local offline static scan; no upload of scanned content, no execution of scanned code; human confirmation required.",
17
17
  "interface": {
18
18
  "displayName": "元信MCP yotta-verify-mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-verify-mcp-plugin",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "YuanXinMCP (元信MCP) — pre-install security scanning for Agent skills, packaged as a YottaMeta Agent Plugin.",
5
5
  "license": "MIT",
6
6
  "keywords": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "yotta-verify-mcp",
4
- "version": "0.4.2",
4
+ "version": "0.4.3",
5
5
  "description": "YuanXinMCP (元信MCP) - the pre-install security scanner for Agent skills, exposed as a stdio MCP server: scan_skill (dir/package -> verdict + findings), generate_badge (audited badge), gate_check (CI gate), get_report (JSON/Markdown). Local offline static scan; no upload of scanned content, no execution of scanned code; human confirmation required.",
6
6
  "author": {
7
7
  "name": "YottaMeta",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-verify-mcp
3
3
  description: 元信MCP(yotta-verify-mcp)—— 把元信(yotta-verify)装前安全扫描暴露为 stdio MCP server,提供 scan_skill(目录/包 → verdict+发现)、generate_badge(audited 徽章)、gate_check(CI 闸门)、get_report(JSON/Markdown 双视角综合报告)四个 MCP 工具。触发:给 MCP 客户端配置元信MCP、把装前信任扫描接入智能体/工作流、调用上列 MCP 工具做扫描;或用户说 MCP元信/元信MCP/trust-mcp/scan-mcp 等。边界:本地离线静态扫描,不上传被测内容、不执行被测代码、不联网;结论需人工确认。MCP 配置与全局/永久记忆写入均为可选,必须先获得用户明确同意;未获同意前不写任何文件,可直接降级 CLI。
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -30,6 +30,7 @@ exit code 语义(与元安/元审一致):
30
30
  """
31
31
  import argparse
32
32
  import base64
33
+ import hashlib
33
34
  import json
34
35
  import re
35
36
  import sys
@@ -52,7 +53,7 @@ sys.path.insert(0, str(_HERE))
52
53
  import verify_rules # noqa: E402
53
54
  import threat_engine # noqa: E402
54
55
 
55
- VERSION = "0.4.2"
56
+ VERSION = "0.4.3"
56
57
  TOOL_NAME = "yotta-verify"
57
58
  CN_NAME = "元信"
58
59
 
@@ -74,8 +75,49 @@ DOTFILE_NAMES = {
74
75
  MAX_FILE_SIZE = 1_000_000
75
76
  MAX_LINE_LEN = verify_rules.MAX_LINE_LEN
76
77
  MAX_FILES = 2000
77
- # 签名数据文件:规则表是扫描器自身的签名数据库,不是被测技能行为,扫描时跳过
78
+ # 签名数据文件:规则表是扫描器自身与家族检测技能的签名数据库,不是被测技能行为。
79
+ # v0.3.2 收紧(旧行为 = 按文件名全局豁免,`scripts/<同名>.py` 可被随意伪造以逃避扫描):
80
+ # ① 相对路径必须恰为 scripts/<规则表名>.py;
81
+ # ② 文件 SHA-256(CRLF 归一化)必须命中「已发布规则表摘要表」。
82
+ # 任一条不满足 → 正常扫描(fail-closed),杜绝「改名即豁免」与「放标记文件即降级」。
78
83
  SIGNATURE_DATA_FILES = {"verify_rules.py", "audit_rules.py", "vetter_rules.py", "hardening_rules.py"}
84
+ SIGNATURE_DATA_DIGESTS = {
85
+ "verify_rules.py": {"8d8c128911b05b24413cc3aec8a2d1c36c1ccf66f656330f723cd68718bda8bd"},
86
+ "vetter_rules.py": {"fc5bad6a7705f9fde60f4fdaf540a3aaa6490448d1fe22ed264e3f812c8ba092"},
87
+ "audit_rules.py": {"475ba1daee436589997260291917126218ae7cae572d16eb59459ca5a2192d23"},
88
+ "hardening_rules.py": {"6b9cbdaa106ae7f5827e60f83c016c237d4cd28785426930abc24cde00bcf5c7"},
89
+ "guardian_rules.py": {"a9fc0da3e0344d485b20c55378b58bc81d78ecbb28a7f2b3bd44bcbed06aebe3"},
90
+ }
91
+
92
+
93
+ def _sha256_normalized(path):
94
+ """CRLF 归一化后的 SHA-256(跨 Windows / Linux 检出结果一致)。"""
95
+ try:
96
+ data = path.read_bytes().replace(b"\r\n", b"\n")
97
+ except OSError:
98
+ return None
99
+ return hashlib.sha256(data).hexdigest()
100
+
101
+
102
+ def is_signature_data(rel, path):
103
+ """是否为「已发布」的家族规则表:路径 + 内容摘要双绑定。"""
104
+ known = SIGNATURE_DATA_DIGESTS.get(path.name)
105
+ if not known:
106
+ return False
107
+ parts = str(rel).replace("\\", "/").split("/")
108
+ if len(parts) != 2 or parts[0] != "scripts":
109
+ return False
110
+ digest = _sha256_normalized(path)
111
+ return bool(digest) and digest in known
112
+
113
+
114
+ def has_published_signature_data(root):
115
+ """目标包是否持有已发布签名数据(用于测试夹具跳过与文档降级判定)。"""
116
+ for name in SIGNATURE_DATA_DIGESTS:
117
+ p = Path(root) / "scripts" / name
118
+ if p.is_file() and is_signature_data("scripts/" + name, p):
119
+ return True
120
+ return False
79
121
 
80
122
  # ── 严重级 / verdict ───────────────────────────────────────────────────────
81
123
  _SEVERITY_VALUE = verify_rules.SEVERITY_VALUE
@@ -143,19 +185,26 @@ def is_text_file(name):
143
185
  return Path(p).suffix in TEXT_EXTENSIONS
144
186
 
145
187
 
146
- def walk_files(root, base=""):
147
- """递归收集可扫描文本文件(跳过 SKIP_DIRS / 签名数据 / 超限)。"""
188
+ def walk_files(root, base="", trusted_tests=None, scan_root=None):
189
+ """递归收集可扫描文本文件(跳过 SKIP_DIRS / 已发布签名数据 / 超限)。
190
+
191
+ trusted_tests 只在「目标包持有已发布签名数据」时为真——此时 `test_*.py`
192
+ 视为自家测试夹具跳过;第三方包的同名文件照常扫描(v0.3.2 起)。
193
+ """
148
194
  out = []
195
+ if scan_root is None:
196
+ scan_root = root
197
+ trusted_tests = has_published_signature_data(scan_root)
149
198
  try:
150
199
  entries = sorted(root.iterdir())
151
200
  except OSError:
152
201
  return out
153
202
  for entry in entries:
154
- if entry.name in SKIP_DIRS or entry.name in SIGNATURE_DATA_FILES:
203
+ if entry.name in SKIP_DIRS:
155
204
  continue
156
205
  rel = entry.name if not base else base + "/" + entry.name
157
206
  if entry.is_dir():
158
- out.extend(walk_files(entry, rel))
207
+ out.extend(walk_files(entry, rel, trusted_tests, scan_root))
159
208
  elif entry.is_file():
160
209
  try:
161
210
  size = entry.stat().st_size
@@ -163,8 +212,11 @@ def walk_files(root, base=""):
163
212
  continue
164
213
  if size > MAX_FILE_SIZE:
165
214
  continue
166
- # 测试文件为签名/测试数据(含构造的恶意样例),非被测技能行为,跳过
167
- if entry.name.startswith("test_") and entry.name.endswith(".py"):
215
+ # 已发布规则表(路径 + 摘要绑定)不是被测技能行为,跳过
216
+ if is_signature_data(rel, entry):
217
+ continue
218
+ # 自家包的测试夹具(含构造样例)跳过;第三方包不豁免
219
+ if trusted_tests and entry.name.startswith("test_") and entry.name.endswith(".py"):
168
220
  continue
169
221
  if is_text_file(entry.name):
170
222
  out.append((entry, rel))
@@ -420,11 +472,12 @@ _DOC_EXT = {".md", ".txt", ".markdown", ".rst"}
420
472
 
421
473
 
422
474
  def is_detector_skill(root):
423
- """目标目录是否含检测器签名文件(audit_rules/verify_rules/vetter_rules/hardening_rules)。"""
424
- for name in _DETECTOR_SIG_FILES:
425
- if (root / name).is_file() or (root / "scripts" / name).is_file():
426
- return True
427
- return False
475
+ """目标是否为家族检测技能(v0.3.2:须持「已发布」规则表,路径 + 摘要绑定)。
476
+
477
+ 旧行为 = 只看目录里有没有同名文件(放一个标记文件即可把整包文档命中降级为
478
+ info)。现改为内容摘要绑定:未验证的同名文件不构成检测器签名。
479
+ """
480
+ return has_published_signature_data(root)
428
481
 
429
482
 
430
483
  def downgrade_detector_docs(findings, root):
@@ -27,7 +27,7 @@ sys.path.insert(0, str(_HERE))
27
27
 
28
28
  import yotta_verify as yv # noqa: E402
29
29
 
30
- VERSION = "0.4.2"
30
+ VERSION = "0.4.3"
31
31
  TOOL_NAME = "yotta-verify-mcp"
32
32
  CN_NAME = "元信"
33
33
  MCP_PROTOCOL_MODERN = "2026-07-28"