feat(skill): 技能治理 v0.2 —— 分桶/调用模式/校验/安全/清单/安装矩阵
- 新增 SCOPE.md 收录门槛 + .out-of-scope/ 拒绝档案 - 生命周期分桶 promoted/beta/deprecated,marketplace 只发布 promoted - 调用模式 model-invoked/user-invoked(user-invoked 不占模型目录) - 新增脚本 validate-skills.py / generate-catalog.py / scan-skill.sh(+allowlist) - 新增 beta 技能 skill-creator、ai-proj-helper-entry、verification-before-completion(各带 evals) - install-skills.sh: --agent dsh / --link / --audit / --include-beta / --no-validate - 融合:dev-review 接收评审意见、req-design 依赖边、agent-swarm 并行编排、dev-test TDD - catalog.json 61 个技能(58 promoted / 3 beta);validate 0 错误;scan 0 高危;现有测试通过
This commit is contained in:
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# scan-skill.sh — security scan before a skill enters the team library.
|
||||
#
|
||||
# Usage: ./scripts/scan-skill.sh [plugin-dir ...]
|
||||
# With no arguments scans every skills-*/*-plugin directory.
|
||||
# Exit 0 = clean (warnings allowed), 1 = high severity finding.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HIGH=0
|
||||
WARN=0
|
||||
|
||||
# High severity: destructive, remote-code-execution, secret material.
|
||||
HIGH_PATTERNS=(
|
||||
'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+/($|[[:space:]])'
|
||||
'curl[^|]*\|[[:space:]]*(ba|z)?sh'
|
||||
'wget[^|]*\|[[:space:]]*(ba|z)?sh'
|
||||
'chmod[[:space:]]+777'
|
||||
'BEGIN (RSA|OPENSSH|EC|DSA) PRIVATE KEY'
|
||||
'sk-[A-Za-z0-9]{16,}'
|
||||
'AKIA[0-9A-Z]{16}'
|
||||
'ghp_[A-Za-z0-9]{20,}'
|
||||
)
|
||||
# Warnings: machine-specific paths, plain http endpoints.
|
||||
WARN_PATTERNS=(
|
||||
'/Users/[A-Za-z0-9._-]+/'
|
||||
'http://[A-Za-z0-9.-]+'
|
||||
'password[[:space:]]*=[[:space:]]*[^$]'
|
||||
)
|
||||
|
||||
targets=("$@")
|
||||
if [[ ${#targets[@]} -eq 0 ]]; then
|
||||
while IFS= read -r dir; do
|
||||
targets+=("$(dirname "$(dirname "$dir")")")
|
||||
done < <(find "$ROOT" -path "*/skills-*/*-plugin/.claude-plugin/plugin.json" | sort)
|
||||
fi
|
||||
|
||||
ALLOWLIST="$ROOT/scripts/scan-allowlist.txt"
|
||||
is_allowlisted() {
|
||||
# is_allowlisted <abs_path> <matched_line>
|
||||
local path="$1" line="$2" entry sub needle
|
||||
[[ -f "$ALLOWLIST" ]] || return 1
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" || "$entry" == \#* ]] && continue
|
||||
sub="${entry%%::*}"
|
||||
needle="${entry#*::}"
|
||||
if [[ "$path" == *"$sub"* && "$line" == *"$needle"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < "$ALLOWLIST"
|
||||
return 1
|
||||
}
|
||||
|
||||
for dir in "${targets[@]}"; do
|
||||
rel="${dir#"$ROOT"/}"
|
||||
for pattern in "${HIGH_PATTERNS[@]}"; do
|
||||
while IFS= read -r hit; do
|
||||
abs="${hit%%:*}"
|
||||
detail="${hit#*:}"
|
||||
if is_allowlisted "$abs" "$hit"; then
|
||||
echo "[allow] $rel: $detail"
|
||||
continue
|
||||
fi
|
||||
echo "[HIGH] $rel: $detail"
|
||||
HIGH=$((HIGH + 1))
|
||||
done < <(grep -rnE --include='*.md' --include='*.sh' --include='*.py' --include='*.js' --include='*.ts' --include='*.json' -- "$pattern" "$dir" 2>/dev/null | cut -c1-200)
|
||||
done
|
||||
for pattern in "${WARN_PATTERNS[@]}"; do
|
||||
while IFS= read -r hit; do
|
||||
echo "[warn] $rel: ${hit%%:*}:${hit#*:}"
|
||||
WARN=$((WARN + 1))
|
||||
done < <(grep -rnE --include='*.md' --include='*.sh' --include='*.py' --include='*.js' --include='*.ts' -- "$pattern" "$dir" 2>/dev/null | cut -c1-200)
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "扫描完成:${#targets[@]} 个插件,$HIGH 个高危,$WARN 个警告"
|
||||
if [[ "$HIGH" -gt 0 ]]; then
|
||||
echo "存在高危项,禁止入库,除非逐条确认并记录原因。"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user