功能概述
Skill 安全检测是腾讯云 WAF 面向 AI Agent Skill 的安全准入检测能力,在上架、接入和运行前对第三方 Skill 包进行自动化安全扫描,识别恶意代码、Prompt 注入、凭证窃取、供应链风险和运行时异常行为。
传统 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、运营后台调用上传与查询接口。 |
安全运营与审计 | 输出可追溯的扫描结论、风险项、评分和报告,便于审核、整改和留痕。 |
检测流程
前提条件
规范要求
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 []stringerr := 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 / Commentheader.Extra = nilheader.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 / Commentzi.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 库 | 设置压缩级别的方式 |
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 进行版本化复扫。
相关文档
日志审计