@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 +9 -0
- package/SKILL.md +1 -1
- package/install.sh +23 -4
- package/package.json +1 -1
- package/scripts/yotta_vetter.py +51 -3
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.
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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.
|
|
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": [
|
package/scripts/yotta_vetter.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
129
|
-
|
|
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:
|