@yottameta/yotta-vetter 0.2.5 → 0.2.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # 更新日志
2
2
 
3
+ ## v0.2.6 (2026-09-25)
4
+
5
+ 安全修复:规则表豁免改为「路径 + 摘要绑定」。
6
+
7
+ - 背景:`collect_files()` 按文件名全局跳过 `audit_rules.py / vetter_rules.py / verify_rules.py / hardening_rules.py`——把恶意文件改成这些名字即可免扫(ClawHub T09 High)。
8
+ - 修复:豁免必须同时满足「相对路径为 `scripts/<规则表名>`(或扫描根就是 scripts 目录)」与「CRLF 归一化 SHA-256 命中已发布摘要表」;任一条不满足一律正常扫描(fail-closed)。
9
+ - 安装器加固:拒绝对符号链接目标写入、不做整目录删除。
10
+ - 回归:新增 4 项绑定用例,测试 16/16 通过。
11
+
3
12
  ## v0.2.5 (2026-09-19)
4
13
 
5
14
  版本对齐维护:修复历史发布元数据与包内版本不一致;无功能变更。
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yotta-vetter
3
- version: 0.2.5
3
+ version: 0.2.6
4
4
  description: 元审 —— 安装任何技能前的安全审查协议:四阶段 checklist(来源→代码→权限→风险)+ 轻量 checker,与元安联动深度扫描。触发:安装/评估任何技能前、从市场或 GitHub 获取技能、审查他人分享的技能、任何「要装未知代码」的场景;或用户说 审查/审查技能/vetting/技能安全审查/检查技能 等。边界:checker 只做初审与报告,结论需人工确认;绝不代替最终决策。
5
5
  license: MIT
6
6
  ---
package/install.sh CHANGED
@@ -61,10 +61,29 @@ resolve_user() {
61
61
  }
62
62
 
63
63
  install_to() {
64
- mkdir -p "$1/$SKILL_NAME"
65
- cp -r "$SOURCE_DIR/." "$1/$SKILL_NAME/"
66
- rm -rf "$1/$SKILL_NAME/.git"
67
- echo "installed -> $1/$SKILL_NAME"
64
+ local base="$1"
65
+ local dest
66
+ case "$base" in
67
+ ""|"/") echo "安装失败:拒绝不安全的目标目录:'$base'" >&2; return 1 ;;
68
+ esac
69
+ if [ -L "$base" ]; then
70
+ echo "安装失败:目标目录是符号链接,拒绝跟随:$base" >&2; return 1
71
+ fi
72
+ mkdir -p "$base"
73
+ dest="$base/$SKILL_NAME"
74
+ if [ -L "$dest" ]; then
75
+ echo "安装失败:技能目录是符号链接,拒绝跟随:$dest" >&2; return 1
76
+ fi
77
+ if [ -e "$dest" ] && [ ! -d "$dest" ]; then
78
+ echo "安装失败:技能路径已存在且不是目录:$dest" >&2; return 1
79
+ fi
80
+ mkdir -p "$dest"
81
+ cp -RP "$SOURCE_DIR/." "$dest/"
82
+ # 只清理副本内部的开发残留(固定子路径),不做整目录删除、不跟随符号链接
83
+ if [ -d "$dest/.git" ] && [ ! -L "$dest/.git" ]; then rm -rf "$dest/.git"; fi
84
+ find "$dest" -type d -name '__pycache__' -prune -exec rm -rf {} + 2>/dev/null || true
85
+ find "$dest" -type f -name '*.pyc' -delete 2>/dev/null || true
86
+ echo "installed -> $dest"
68
87
  }
69
88
 
70
89
  list() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-vetter",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Yuanshen (元审) — the security review protocol before installing any skill: a four-phase checklist (source → code → permissions → risk) + a lightweight checker, with deep-scan handoff to yotta-security-audit. Triggers before installing/evaluating any skill or reviewing unknown code. Boundaries: initial review & report only; human confirmation required.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -24,6 +24,7 @@ exit code 语义(与元安一致):
24
24
  python3 yotta_vetter.py source github:YottaMeta/yotta-memory
25
25
  """
26
26
  import argparse
27
+ import hashlib
27
28
  import json
28
29
  import os
29
30
  import re
@@ -48,7 +49,7 @@ _HERE = Path(__file__).resolve().parent
48
49
  sys.path.insert(0, str(_HERE))
49
50
  import vetter_rules # noqa: E402
50
51
 
51
- VERSION = "0.2.5"
52
+ VERSION = "0.2.6"
52
53
  TOOL_NAME = "yotta-vetter"
53
54
  MAX_FILE_SIZE = 1_000_000
54
55
  MAX_LINE_LEN = vetter_rules.MAX_LINE_LEN
@@ -116,6 +117,51 @@ def _is_binary(head):
116
117
  return b"\x00" in head[:8192]
117
118
 
118
119
 
120
+ # v0.2.6:签名数据文件(家族规则表)白名单 —— 相对路径 + CRLF 归一化 SHA-256 双绑定。
121
+ # 任一条不满足即正常扫描(fail-closed),杜绝「改名即豁免」。
122
+ PUBLISHED_SIGNATURES = {
123
+ "scripts/verify_rules.py":
124
+ "8d8c128911b05b24413cc3aec8a2d1c36c1ccf66f656330f723cd68718bda8bd",
125
+ "scripts/vetter_rules.py":
126
+ "fc5bad6a7705f9fde60f4fdaf540a3aaa6490448d1fe22ed264e3f812c8ba092",
127
+ "scripts/audit_rules.py":
128
+ "475ba1daee436589997260291917126218ae7cae572d16eb59459ca5a2192d23",
129
+ "scripts/hardening_rules.py":
130
+ "6b9cbdaa106ae7f5827e60f83c016c237d4cd28785426930abc24cde00bcf5c7",
131
+ "scripts/guardian_rules.py":
132
+ "a9fc0da3e0344d485b20c55378b58bc81d78ecbb28a7f2b3bd44bcbed06aebe3",
133
+ }
134
+
135
+
136
+ def _sha256_normalized(path):
137
+ """CRLF 归一化后的 SHA-256(跨 Windows / Linux 检出结果一致)。"""
138
+ try:
139
+ data = path.read_bytes().replace(b"\r\n", b"\n")
140
+ except OSError:
141
+ return ""
142
+ return hashlib.sha256(data).hexdigest()
143
+
144
+
145
+ def is_published_signature_data(root, path):
146
+ """判断文件是否为「已发布家族规则表」:路径受限 + 摘要一致(摘要为最终判据)。
147
+
148
+ 路径接受 `scripts/<规则表名>`,以及「扫描根就是 scripts 目录」时的 `<规则表名>`
149
+ 形式(自扫 scripts/ 子目录的场景)。两条路径都必须命中已发布摘要,改名 / 改内容
150
+ 一律照常扫描。
151
+ """
152
+ try:
153
+ rel = path.relative_to(root).as_posix()
154
+ except ValueError:
155
+ rel = path.name
156
+ expected = PUBLISHED_SIGNATURES.get(rel)
157
+ if not expected and Path(root).name.lower() == "scripts":
158
+ # 直接扫描 scripts/ 子目录(自扫场景):按 scripts/<规则表名> 复核
159
+ expected = PUBLISHED_SIGNATURES.get("scripts/" + path.name)
160
+ if not expected:
161
+ return False
162
+ return _sha256_normalized(path) == expected
163
+
164
+
119
165
  def collect_files(root):
120
166
  files = []
121
167
  for dirpath, dirnames, filenames in os.walk(str(root)):
@@ -125,8 +171,10 @@ def collect_files(root):
125
171
  try:
126
172
  if p.suffix.lower() not in TEXT_EXTENSIONS and p.name.lower() not in DOTFILE_NAMES:
127
173
  continue
128
- if p.name in ("audit_rules.py", "vetter_rules.py", "verify_rules.py", "hardening_rules.py"):
129
- continue # 签名数据文件(规则表)
174
+ # v0.2.6:签名数据文件豁免改为「路径 + 摘要绑定」。旧行为只按文件名跳过,
175
+ # 攻击方把恶意文件命名成 audit_rules.py / verify_rules.py 即可免扫(T09)。
176
+ if is_published_signature_data(Path(root), p):
177
+ continue
130
178
  if p.stat().st_size > MAX_FILE_SIZE:
131
179
  continue
132
180
  except OSError: