- 新增 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 高危;现有测试通过
83 lines
2.6 KiB
Bash
Executable File
83 lines
2.6 KiB
Bash
Executable File
#!/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
|