一、功能概述
传统 Web 漏洞扫描解决的是 Web 应用层面的漏洞问题,而 Skill 安全检测解决的是 AI Agent 生态中更特殊的一类风险:第三方 Skill 既包含自然语言说明、Prompt 指令,也包含代码、脚本、依赖、远程链接和工具调用逻辑。
攻击者可以将恶意行为隐藏在说明文档、安装脚本、依赖包、Prompt 诱导或运行时工具链中,而传统 SAST/DAST 很难完整判断声明功能与实际行为是否一致。
Skill 安全检测将以下三种检测能力统一在一条工程链路中,对上传的 Skill 包进行自动化检测:
| 检测能力 | 说明 |
| AI 语义审计 | 对 Skill 的自然语言说明、Prompt 指令进行语义分析,判断声明功能与实际行为是否一致。 |
| 静态规则扫描 | 对 Skill 包内的代码、脚本、依赖、配置文件进行静态分析,识别已知恶意模式。 |
| 动态沙箱验证 | 在隔离沙箱环境中运行 Skill,观察其运行时行为,识别文件操作、网络外传、命令执行等异常行为。 |
检测完成后,系统输出统一的风险等级、评分、风险项和报告链接,供审核、整改和留痕。
二、检测类型
Skill 安全检测覆盖以下主要威胁类型:
| 风险维度 | 说明 |
| 供应链风险 | 检测 Skill 依赖的第三方包、远程链接是否存在已知漏洞或恶意组件。 |
| 命令注入 | 检测 Skill 代码或 Prompt 中是否包含恶意命令执行逻辑。 |
| 数据外传 | 检测 Skill 运行时是否将用户数据、系统信息发送到外部地址。 |
| Prompt 注入 | 检测 Skill 的说明或指令中是否包含劫持模型行为的恶意 Prompt。 |
| 运行时异常行为 | 检测 Skill 运行时的文件操作、网络访问等异常行为。 |
注意:说明:
注意:具体风险维度的完整清单及评级标准以控制台实际展示为准,各维度会随检测能力迭代持续更新。
三、适用场景
| 场景 | 客户价值 |
| Skill 市场上架审核 | 在第三方 Skill 上架前识别恶意代码、Prompt 注入、凭证窃取、供应链风险和运行时异常行为。 |
| 企业内部 SkillHub 准入 | 对内部员工或外部供应商提交的 Skill 建立统一安全基线。 |
| 存量 Skill 批量复扫 | 规则、模型或沙箱能力升级后,对历史 Skill 进行版本化重扫。 |
| OpenAPI 自动化接入 | 支持客户平台、CI/CD、运营后台调用上传与查询接口。 |
| 安全运营与审计 | 输出可追溯的扫描结论、风险项、评分和报告,便于审核、整改和留痕。 |
四、前提条件
已通过 SDK/API 接入 方式接入大模型安全功能并获取接入凭证。
五、规范要求
Skill 安全检测会基于文件内容的 SHA256 Hash 复用相同 Skill 的历史检测结果,避免重复扫描。但 ZIP 文件格式会在文件头中记录修改时间、文件系统属性等元信息,即使源文件内容完全相同,不同环境下打包产出的 ZIP 二进制也可能不同,导致 Hash 不一致、无法复用结果。因此,调用上传接口前必须按以下规范打包 ZIP,确保同一份 Skill 目录始终产出一致的 ContentHash。
打包规范:
| 配置项 | 要求 |
| 打包方式 | 使用编程语言内置的 ZIP 库进行打包,如 Go archive/zip、Python zipfile。 |
| 文件排序 | 按路径字节序排序,其效果等价于在 Shell 中执行 LC_ALL=C sort |
| 时间戳 | 固定为 2000-01-01 00:00:00 UTC。 |
| 压缩算法 | DEFLATE,压缩级别固定为 6。 |
| 文件权限 | 固定为 0644。 |
| Extra Field / Comment | 不写入。 |
| 目录条目 | 不单独写入目录条目,目录结构由文件路径自身体现。 |
| 文件过滤 | Skill 目录下的所有文件均需打包,不进行任何过滤或排除。 |
六、Go 语言
以下示例展示如何使用 Go 将 Skill 目录按规范打包为 ZIP,并生成可作为 UploadSkillSecScan 接口 FileData 参数的 Base64 编码字符串。
1. 将 /path/to/skill_dir 替换为待检测的 Skill 目录路径。
2. 运行程序,获取 Base64 编码字符串及 ContentHash。
3. 将 Base64 字符串作为 FileData 参数传入 UploadSkillSecScan 接口。
package mainimport ( "archive/zip" "bytes" "compress/flate" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "io" "os" "path/filepath" "sort" "strings" "time")// 固定时间戳:2000-01-01 00:00:00 UTCvar fixedModTime = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)func packSkillDir(skillDir string) ([]byte, error) { // 1. 收集所有文件的相对路径 var files []string err := filepath.Walk(skillDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil // 不写入目录条目 } rel, err := filepath.Rel(skillDir, path) if err != nil { return err } // 统一使用 / 作为 ZIP 内路径分隔符 files = append(files, filepath.ToSlash(rel)) return nil }) if err != nil { return nil, err } // 2. 按路径字节序排序(等价于 LC_ALL=C sort) sort.Strings(files) // 3. 写入 ZIP(压缩级别 6,固定时间戳,固定权限 0644) buf := new(bytes.Buffer) zw := zip.NewWriter(buf) // 显式注册压缩级别 6 的 deflate writer,确保跨 Go 版本输出稳定 zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { return flate.NewWriter(out, 6) }) for _, rel := range files { header := &zip.FileHeader{ Name: rel, Method: zip.Deflate, Modified: fixedModTime, } header.SetMode(0644) // 不写入 Extra Field / Comment header.Extra = nil header.Comment = "" w, err := zw.CreateHeader(header) if err != nil { return nil, err } f, err := os.Open(filepath.Join(skillDir, filepath.FromSlash(rel))) if err != nil { return nil, err } if _, err := io.Copy(w, f); err != nil { f.Close() return nil, err } f.Close() } if err := zw.Close(); err != nil { return nil, err } return buf.Bytes(), nil}func main() { if len(os.Args) < 2 { fmt.Println("Usage: skill_package_to_base64 <skill_dir>") os.Exit(1) } skillDir := strings.TrimRight(os.Args[1], string(os.PathSeparator)) zipBytes, err := packSkillDir(skillDir) if err != nil { fmt.Fprintf(os.Stderr, "pack failed: %v\\n", err) os.Exit(1) } sum := sha256.Sum256(zipBytes) fmt.Println("ContentHash:", hex.EncodeToString(sum[:])) fmt.Println("FileData (Base64):") fmt.Println(base64.StdEncoding.EncodeToString(zipBytes))}
七、Python 语言
以下示例展示如何使用 Python 将 Skill 目录按规范打包为 ZIP,并生成可作为 UploadSkillSecScan 接口 FileData 参数的 Base64 编码字符串。
注意:说明:
注意:运行环境要求 Python 3.7 或更高版本。本示例仅依赖 Python 标准库,无需安装任何第三方包。
1. 将命令行参数替换为待检测的 Skill 目录路径:
python3 skill_package_to_base64.py /path/to/skill_dir
2. 运行程序,获取 Base64 编码字符串和 ContentHash。
3. 将 Base64 字符串作为 FileData 参数传入 UploadSkillSecScan 接口。
#!/usr/bin/env python3# skill_package_to_base64.py# 按规范打包 Skill 目录为 ZIP,并输出 Base64 与 ContentHash(SHA256)import base64import hashlibimport ioimport osimport sysimport zipfile# 固定时间戳:2000-01-01 00:00:00 UTCFIXED_DATE_TIME = (2000, 1, 1, 0, 0, 0)# 文件权限 0644UNIX_FILE_MODE = 0o644def collect_files(skill_dir: str): """递归收集 skill_dir 下所有文件的相对路径,按字节序排序。""" files = [] for root, _dirs, filenames in os.walk(skill_dir): for name in filenames: abs_path = os.path.join(root, name) rel = os.path.relpath(abs_path, skill_dir) # 统一使用 / 作为 ZIP 内路径分隔符 rel = rel.replace(os.sep, "/") files.append(rel) # 按路径字节序排序(等价于 LC_ALL=C sort) files.sort(key=lambda s: s.encode("utf-8")) return filesdef pack_skill_dir(skill_dir: str) -> bytes: buf = io.BytesIO() # 压缩级别固定为 6(Python 3.7+ 支持 compresslevel) with zipfile.ZipFile( buf, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=6 ) as zf: for rel in collect_files(skill_dir): abs_path = os.path.join(skill_dir, rel.replace("/", os.sep)) with open(abs_path, "rb") as f: data = f.read() zi = zipfile.ZipInfo(filename=rel, date_time=FIXED_DATE_TIME) zi.compress_type = zipfile.ZIP_DEFLATED # 文件权限 0644 写入 external_attr 高 16 位 zi.external_attr = (UNIX_FILE_MODE & 0xFFFF) << 16 # 不写入 Extra Field / Comment zi.extra = b"" zi.comment = b"" zf.writestr(zi, data) return buf.getvalue()def main(): if len(sys.argv) < 2: print("Usage: python3 skill_package_to_base64.py <skill_dir>", file=sys.stderr) sys.exit(1) skill_dir = sys.argv[1].rstrip(os.sep) if not os.path.isdir(skill_dir): print(f"Not a directory: {skill_dir}", file=sys.stderr) sys.exit(1) zip_bytes = pack_skill_dir(skill_dir) content_hash = hashlib.sha256(zip_bytes).hexdigest() print("ContentHash:", content_hash) print("FileData (Base64):") print(base64.b64encode(zip_bytes).decode("ascii"))if __name__ == "__main__": main()
八、其他语言
如果你使用其他编程语言,请参考下表对应的 ZIP 库与压缩级别设置方式,并严格遵守 打包规范。
| 语言 | ZIP 库 | 设置压缩级别的方式 |
| Node.js | fflate | zipSync(data, { level: 6 }) |
| Java | java.util.zip | ZipOutputStream.setLevel(6) |
| Rust | zip crate + flate2 crate | ZipWriter::start_file(name, FileOptions::default().compression_level(Some(6))) |
九、操作步骤
Skill 安全检测为异步检测,整体流程如下:
1. 按照 规范要求 将 Skill 目录打包为按规范生成的 ZIP 文件。
2. 调用 UploadSkillSecScan 接口上传 ZIP 文件(需先进行 Base64 编码)。上传成功后接口立即返回该文件的 ContentHash(SHA256),后台异步执行扫描。
注意:说明:
注意:当前 UploadSkillSecScan 接口限制单个 Skill ZIP 文件大小上限为 10 MB,超过限制将被拒绝。建议精简 Skill 包内容,例如剔除非必要的二进制资源、模型权重等,将文件控制在 10 MB 以内。
3. 使用上一步返回的 ContentHash,调用 DescribeSkillSecScanResult 接口轮询查询检测结果。
·一般情况下,单次检测可在 5 分钟以内完成,建议轮询间隔设置为 5~10 秒。
·对于可疑或恶意样本,因需进行更深度的分析,检测耗时通常为 5~10 分钟,最长约 90 分钟。
·如果检测失败,可重新触发检测。
4. 检测完成后,系统将返回:
·风险等级:malicious(恶意)/ suspicious(可疑)/ benign(可信)。
·命中规则详情:按检测引擎分组,展示每条风险的文件位置、行为特征等具体信息。
·处置建议:针对检测结果的综合修复建议。
十、注意事项
·Skill 安全检测解决的是 AI Agent Skill 生态中的特殊风险,与传统 Web 漏洞扫描(SAST/DAST)定位不同,建议二者配合使用。
·检测结果为安全准入提供依据,建议结合企业自身的安全规范制定处置策略。
·检测规则、模型和沙箱能力会持续升级,建议定期对存量 Skill 进行版本化复扫。
十一、相关文档
·SDK/API 接入
·日志审计