ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SkillSpector 嵌套制品检测(Nested Artifact Inspection)完整指南:ZIP 容器的本地化安全检查与 SC9 隐藏可执行制品识别

SkillSpector 嵌套制品检测(Nested Artifact Inspection)完整指南:ZIP 容器的本地化安全检查与 SC9 隐藏可执行制品识别 SkillSpector 嵌套制品检测Nested Artifact Inspection完整指南ZIP 容器的本地化安全检查与 SC9 隐藏可执行制品识别【免费下载链接】SkillSpectorSecurity scanner for AI agent skills. Detect vulnerabilities, malicious patterns, security risks, prompt injection, data exfiltration, and supply-chain risks in Claude Code, Codex, and MCP skills before you install them.项目地址: https://gitcode.com/GitHub_Trending/sk/SkillSpectorSkillSpector 是面向 Claude Code、Codex 与 MCP 等 AI Agent 技能Skill包的安全扫描器。当攻击者把可执行脚本藏进 DOCX/XLSX/PPTX 文档或嵌套 ZIP 中时仅凭文件扩展名的人工审查往往无法发现。本文基于docs/NESTED_ARTIFACT_INSPECTION.md的系统设计结合 nested_artifacts.py 源码与测试用例完整讲解 SkillSpector 如何对隐藏的普通文件与 ZIP 兼容容器进行零提取、零执行、本地化的深度检查包括累积资源预算、失败即不完整fail-closed行为、SC9 确定性 HIGH 发现以及--fail-on-incomplete等 CLI 门禁用法。读完本文你将理解嵌套制品检测的完整安全模型并能根据输出判断一个技能包是否存在隐蔽攻击面。一、问题背景为什么 AI 技能包需要嵌套制品检测Agent 技能包通常以目录或归档形式分发其中可能包含普通文本文件、脚本也可能嵌入 ZIP 兼容的文档容器。攻击者可以利用以下手法绕过传统的扩展名审查将可执行脚本藏入.docx、.xlsx、.pptx等 Office Open XMLOOXML容器内将脚本伪装成图片、数据文件或文档使其在人工浏览时看起来无害利用嵌套 ZIP 将恶意载荷藏到多层容器深处。SkillSpector 的应对策略是对隐藏的普通文件hidden regular files进行完整盘点并就地locally检查所有 ZIP 兼容内容。其关键设计在于——一个对象是否被当作容器处理不依赖文件名扩展名而是从文件字节内容bytes与内部结构进行识别。从源码看这一判断由 nested_artifacts.py 的_is_zip_signature()完成只要数据以PK\x03\x04、PK\x05\x06、PK\x07\x08任一 ZIP 签名开头即按容器处理。当前支持的文档容器类型为DOCX、XLSX、PPTX通用 ZIP 以及嵌套的 ZIP 兼容成员遵循相同的遍历策略。二、稳定虚拟路径完整保留来源溯源的寻址机制嵌套成员通过一个稳定的虚拟路径virtual path暴露给上层分析器该路径完整保留了从外层文件到内层成员的来源链outer-file!/nested.zip!/scripts/setup.sh!/是容器与成员之间的分隔符语义上等价于归档内部路径路径自外向内逐层拼接即使多层嵌套也能准确回溯每一个祖先容器该虚拟路径在 nested_artifacts.py 的_nested_path()与 L686 的虚拟路径拼接逻辑 中构造virtual_path f{container_virtual_path}!/{safe_name}。SC9 发现以及各报告输出均使用该虚拟路径作为成员标识读者可以据此精确定位嵌套链路中的具体文件。三、安全不变量宁可不完整绝不不安全嵌套制品检查遵循一组严格的安全不变量Security invariants这是整个模块的安全底线成员仅在内存中读取。SkillSpector 从不对外解压extract、渲染render、导入import、安装install或执行execute归档内容。这一点在 nested_artifacts.py 的模块 docstring 中明确声明并在读取成员时使用io.BytesIO(data)包装内存字节L616全程没有写盘操作。不跟随危险路径。绝对路径/...、//...、父目录穿越..、盘符限定路径如C:以及符号链接成员均不会被跟随。安全化函数_safe_member_name()L326-L340会拒绝空名、含\x00、含!/、以/或//开头、第二字符为冒号盘符、以及含空/./..路径段的成员名_zip_member_is_link()L343-L345通过stat.S_ISLNK检查external_attr高位来识别链接成员。一切内容本地化。隐藏文件、已识别容器及其全部嵌套内容仅用于本地分析绝不进入任何外部 LLM 请求。外层容器元数据L386与每个成员元数据L971都显式标记local_only: True。确定性 HIGH 发现不受可选的 LLM 元分析影响。SC9 等由确定性静态分析器产出的发现属于主要证据可选的语义分析只能丰富enrich而不能剔除它们详见 ANALYSIS_RESOURCE_BOUNDS.md 中确定性分析器产出的发现仍是主要证据的说明。零发现 ≠ 完整覆盖。当扫描结果为 0 个发现时如果存在不透明或未检查内容分析完整性仍然为 false绝不将未检查内容当作已成功分析。四、累积资源预算一份预算约束整个技能包嵌套制品检查对整个技能包skill bundle共享同一个预算one shared budget。打开另一个外层容器不会重置成员数、展开字节数或时间预算。更重要的是bundle 扫描器会把自身剩余的资源额度传下来因此嵌套成员不可能在普通文件消耗了部分 bundle 预算后重新获得一份新配额。4.1 嵌套容器专属上限预算项Bound上限Limit作用域Scope容器深度Container depth3一条外层到内层的来源链成员数Members1,000所有外层容器与递归嵌套容器合计展开后成员字节Expanded member bytes25 MiB所有外层容器与递归嵌套容器合计中央目录Central directory4 MiB每个容器在创建 ZIP 元数据对象之前检查物化成员Materialized member1,000,000 字节每个成员压缩比Compression ratio100:1每个成员检查墙钟时间Inspection wall time5 秒所有外层容器与递归嵌套容器合计这些常量在 nested_artifacts.py 中定义为ARCHIVE_MAX_DEPTH 3、ARCHIVE_MAX_MEMBERS 1_000、ARCHIVE_MAX_UNCOMPRESSED_BYTES 25 * 1024 * 1024、ARCHIVE_MAX_CENTRAL_DIRECTORY_BYTES 4 * 1024 * 1024、ARCHIVE_MAX_COMPRESSION_RATIO 100、ARCHIVE_MAX_SECONDS 5.0。单成员 1,000,000 字节上限来自共享常量MAX_FILE_BYTESconstants.py。说明MiB 表示 1,048,576 字节。4.2 与 bundle 级预算的关系1,000 成员与 25 MiB 上限同时会被压缩到 bundle 扫描器的剩余额度之内剩余制品数bundle 级已发现条目上限为 10,000见 ANALYSIS_RESOURCE_BOUNDS.md剩余规范字节bundle 级规范缓存源字节上限为 64 MiB普通文件与展开的嵌套成员合计见 ANALYSIS_RESOURCE_BOUNDS.md。也就是说嵌套成员在读取外层归档后必须从这 64 MiB 中扣账。在 build_context.py 的调用处可以看到inspect_nested_artifacts()接收max_membersremaining_artifacts、max_uncompressed_bytesremaining_bytes并把nested_deadline与 bundle 处理期限取较小值传入——bundle 的期限始终优先。该模块签名L1002-L1012中max_members、max_uncompressed_bytes、max_seconds、absolute_deadline均为可选参数缺省时保留模块默认值。4.3 中央目录预检Preflight防止伪造条目数导致元数据无界膨胀在调用 Python 的zipfile读取器之前SkillSpector 会先手工验证L217-L264终端 EOCD 或 ZIP64 记录声明的中央目录条目数与字节大小实际中央目录头的序列。预检逻辑由_central_directory_bounds()与_count_central_directory_entries()实现前者在不构造ZipInfo对象的前提下解析 EOCD/ZIP64 的条目计数、目录大小与偏移后者以恒定内存逐条扫描中央目录头一旦超过stop_after上限立即停止。伪造的条目计数因此不可能造成无界的元数据列表。此外已受外层预算约束的字节会直接从 bundle 缓存复用而不是被二次读取。这正是inspect_nested_artifacts(..., raw_file_cacheraw_file_cache, ...)参数的意义——调用方build_context传入已按 64 MiB 限额读取过的原始字节避免重复的磁盘 I/O同时不削弱归档专属的成员、展开与期限约束。4.4 资源限制是安全边界不是信任配置文档特别强调以上预算属于资源安全限制resource-safety limits而非信任配置。它们不是可由用户管理的白名单allowlist。命中任一相关上限时扫描器会记录该限制并报告部分分析而不是默认该部分内容干净。关于外层 bundle、解析器、台账ledger与发现的整体上限参见 Analysis Resource Bounds。五、失败与完整性行为遇到坏归档绝不含糊5.1 例外分类与不透明盘点以下类型的成员会被记录为检查台账例外inspection-ledger exceptions格式损坏malformed加密encrypted截断truncated不可读unreadable不安全路径unsafe-path链接成员link不支持的压缩unsupported compression超出预算over-budget可读成员保留其精确原始字节raw bytes以及一个仅本地的解码视图local-only decoded view。不可读成员则保留一条不透明盘点记录opaque inventory record处置disposition标记为partial或failed——它们绝不会被表示为已成功分析。在源码中这两类处置分别对应_PARTIAL_INVENTORY_REASONS与_FAILED_INVENTORY_REASONS两个集合L145-L166部分检查类原因如成员/大小/压缩比/时间/深度/不安全路径/格式不匹配映射为PARTIAL失败类原因加密、链接成员、损坏、截断、不支持的压缩映射为FAILED。_add_unreadable_component()L493-L546为不可读成员写入一条ContentKind.OPAQUE的盘点记录并在文件缓存中放入\x00二进制哨兵使普通分析器能记账该组件却不会假装这些不可访问字节已被当作文本检查过。5.2 Fail-closed不完整即不安全扫描在可能的情况下会安全继续但只要存在相关的遗漏或部分检查内容整个分析就被标记为不完整incomplete本应为SAFE的结果至少升级为CAUTIONMCP 判定将safe_to_install置为falseCLI 用户可通过--fail-on-incomplete将不完整分析变成失败门禁。异常及其受影响的外层/嵌套路径会在终端terminal、JSON、Markdown 和 SARIF四种输出中全部暴露。CLI 的对应实现位于 cli.py其帮助文本为Exit 1 when relevant analysis is partial or incomplete.当analysis_completeness.is_complete为 false 且开启该选项时命令以退出码 1 结束cli.py。5.3 完整性汇总最终化阶段通过analysis_completeness暴露分析完整性包含覆盖计数、台账例外、分析器状态、引用与限制等信息。一个低风险分数或零发现绝不能在analysis_completeness.is_complete false时被解释为完整覆盖。六、SC9Concealed Executable Artifact隐藏可执行制品6.1 触发条件与严重级别SC9 是一个确定性 HIGH 发现deterministic HIGH finding当可执行内容被隐藏在以下对象中时触发Office 文档容器docx/xlsx/pptx内部隐藏制品或伪装制品hidden/disguised artifact内部。可执行性executability的判定依据三者之一可执行后缀executable suffixshebang#!开头的脚本标记归档模式位archive mode bits即 Unix 可执行权限位。一个不含可执行成员的良性文档不会产生 SC9。6.2 源码级判定逻辑判定函数是 nested_artifacts.py 的is_executable_content()def is_executable_content(path: str, data: bytes, mode: int 0) - bool: suffix Path(path).suffix.lower() executable_magic data.startswith( (b#!, bMZ, b\x7fELF, b\xfe\xed\xfa, b\xcf\xfa\xed\xfe) ) return suffix in _EXECUTABLE_SUFFIXES or executable_magic or bool(mode 0o111)可执行后缀集合_EXECUTABLE_SUFFIXESL51-L77覆盖了.app、.bash、.bat、.bin、.cmd、.com、.dll、.dylib、.exe、.go、.js、.msi、.pl、.ps1、.py、.pyc、.pyo、.rb、.rs、.sh、.so、.ts、.zsh等 23 类。可执行魔数则检测#!脚本、MZPE、\x7fELFELF、\xfe\xed\xfaMach-O等二进制头。隐藏/伪装的判定链路为成员路径任一段以.开头 →hidden_is_hidden_path()L301-L303容器识别类型与期望后缀类型不符 →disguised外层元数据与_container_type()结合判定L305-L323祖先容器为文档容器docx/xlsx/pptx→document_container继承原因。当某个成员既可执行又带上述任一隐藏原因时concealed_executable True并以concealment_reasons记录全部成因L944-L975。6.3 SC9 发现的生成与证据SC9 的生成位于 static_patterns_supply_chain.py 的_analyze_concealed_executables()遍历组件元数据凡concealed_executable为真即生成一条rule_idSC9、severityHIGH、confidence1.0的发现并携带完整证据字段evidence{ outer_path: outer_path, nested_path: nested_path, container_type: container_type, container_ancestry: ..., container_depth: ..., concealment: concealment, concealment_reasons: ..., local_only: True, }发现消息为Executable content is concealed inside a document, hidden, or disguised artifact.解释说明藏在文档或隐藏/伪装制品中的可执行内容可以绕过普通的基于扩展名的审查同时仍可能在运行时被技能使用修复建议是审查制品来源说明可执行内容打包于此位置的原因保持可执行文件显式且可直接审查。SC9 的展示名、默认消息与修复建议同时登记在 pattern_defaults.py消息、L290名称与 L387修复处。需要特别强调的是SC9 只报告证据与风险绝不执行该成员也不代替安装决策。整个流程对归档内容零解压、零执行。七、实现剖析一次嵌套检查的完整调用链从图节点到成员输出嵌套制品检查的主链路为入口inspect_nested_artifacts(skill_dir, components, raw_file_cache..., max_members..., max_uncompressed_bytes..., absolute_deadline...)L1002-L1012为整个 bundle 构造_Budget对象统一管理成员数、展开字节、中央目录字节、单成员字节、深度、压缩比与截止时间。外层组件循环L1055-L1155对每个组件先检查预算是否已耗尽若调用方在raw_file_cache中提供了字节则直接复用否则以不跟随符号链接的安全打开方式_open_regular_file_no_follow读取只有数据以 ZIP 签名开头才进入容器解析否则记录ARCHIVE_FORMAT_MISMATCH当扩展名声称是容器时。容器解析_inspect_zip_bytes()L549-L999先做中央目录预检再实例化zipfile.ZipFile(io.BytesIO(data))随后对每个成员依次检查目录条目 → 跳过、安全路径 → 链接成员 → 加密标志位flag_bits 0x1→ 压缩比file_size compressed * ratio→ 累计展开字节 → 单成员字节 → 实际读取 → 二次校验 → 写缓存与盘点。成员若本身又是 ZIP 且未达深度上限则递归调用_inspect_zip_bytes进入下一层。结果合并build_context.py 将嵌套结果并入本地文件缓存、原始字节缓存与制品盘点应用inventory_overrides覆盖外层容器的处置并把nested.components合并进结构化候选集合。下游分析静态供应链分析器消费component_metadata中的concealed_executable标记产出 SC9各报告输出终端/JSON/Markdown/SARIF统一暴露台账例外与完整性状态。对应的回归测试集中在 test_nested_artifacts.py例如test_transitive_limit_caps_nested_uncompressed_bytes验证调用方传入的子预算会阻止归档展开超过其配额test_build_context_accounts_nested_bytes_to_transitive_budget验证展开的子成员与普通文件共享同一预算。八、实操建议如何把嵌套制品检查用起来8.1 安装与扫描SkillSpector 提供 CLI 与 MCP Server 两种使用形态详见 README.md。对嵌套制品检查而言无需额外配置——检测是默认静态分析流程的一部分# 扫描一个技能包目录/归档 skillspector scan /path/to/skill_bundle # 将不完整分析提升为失败门禁退出码 1 skillspector scan /path/to/skill_bundle --fail-on-incomplete当任一相关分析不完整时未开启该选项的 CLI 保留兼容行为并仍按普通风险分数退出策略执行开启后以退出码 1 结束执行失败则退出码 2见 ANALYSIS_RESOURCE_BOUNDS.md 的 Fail-closed partial behavior 一节。8.2 如何解读输出报告中出现outer-file!/nested.zip!/scripts/setup.sh形式的路径 → 该成员位于嵌套容器内部应重点审查其来源链出现SC9Concealed Executable ArtifactHIGH→ 存在藏在文档/隐藏/伪装制品中的可执行内容即便它看起来是文档的一部分analysis_completeness.is_complete为 false或出现partial/failed处置 → 本次分析覆盖不完整零发现不能被当作安全结论查看 JSON/SARIF 输出中的台账例外与output_limit记录可确认是否有成员因预算被跳过。8.3 定制整体工作流期限虽然嵌套预算固定但整个工作流有一个可配置的聚合期限默认 600 秒可通过环境变量SKILLSPECTOR_MAX_WORKFLOW_SECONDS改为任意正的有限秒数字节与制品上限不受影响无效、零、负、无穷或 NaN 值会安全地保留 600 秒默认值见 ANALYSIS_RESOURCE_BOUNDS.md。九、小结SkillSpector 的嵌套制品检测在检查恶意内容与防御恶意输入之间取得了平衡通过字节级容器识别、稳定虚拟路径、共享累积预算、中央目录预检、内存内零提取读取以及 fail-closed 完整性语义它能够在不可信的技能包中可靠地发现藏在 DOCX/XLSX/PPTX 与嵌套 ZIP 里的可执行载荷SC9同时保证任何归档内容都永远不会被解压、渲染、导入、安装或执行也永远不会被发送到外部 LLM。理解这些预算与完整性语义是正确解读扫描结果、避免零发现即安全误判的前提。【免费下载链接】SkillSpectorSecurity scanner for AI agent skills. Detect vulnerabilities, malicious patterns, security risks, prompt injection, data exfiltration, and supply-chain risks in Claude Code, Codex, and MCP skills before you install them.项目地址: https://gitcode.com/GitHub_Trending/sk/SkillSpector创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进