- 网络安全
- 应用安全
- AI 安全治理
- 提示词注入防护
- 供应链安全
- 静态分析
- 人工智能
【免费下载链接】SkillSpector
Security 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.
导读
本文围绕 SkillSpector 中负责语义级意图审计的semantic_developer_intent分析器展开,深入讲解其四类语义检测规则(SDI-1 至 SDI-4),并以仓库中sdi3_scope_creep测试夹具为实例,完整演示"清单声明只读、代码却写文件"的权限越界(Scope Creep)是如何被识别与验证的。读完本文,你将掌握 SkillSpector 语义分析器的运行原理、权限声明与代码行为的比对方法、模型槽配置方式,以及如何在发布 Agent Skill 前自查同类问题。
一、什么是语义开发者意图分析(SDI)
静态模式扫描(如正则匹配os.system、requests.post等)可以快速命中已知恶意写法,但它无法回答一个更本质的问题:一个 Skill 声称自己要做什么,与它代码里实际做了什么,是否一致?
SkillSpector 的 semantic_developer_intent.py 就是为此设计的 LLM 分析器节点。它把 Skill 的清单(manifest:name、description、triggers、permissions)与代码文件的实际行为一起交给大模型比对,专门发现"描述与行为不一致"的上下文相关风险。该节点定义在 graph 工作流 中,ANALYZER_ID = "semantic_developer_intent",且requires_api_key = True——它属于 LLM 驱动型分析器,仅在启用 LLM 的扫描中生效。
在 SkillSpector 的语义分析器家族中,它与 semantic_security_discovery.py(意图与攻击措辞风险)、semantic_quality_policy.py(质量/安全评分卡违规)并列,三者统称 Semantic Analyzers,见 LLM_ANALYZER_BASE_GUIDE.md。
SDI 规则全景
分析器内置提示词(ANALYZER_PROMPT)定义了四类规则:
| Rule ID | 检测目标 |
|---|---|
| SDI-1 | 描述与行为不匹配:清单描述与实际代码操作不符 |
| SDI-2 | 上下文不恰当的能力:代码能力在给定 Skill 声明用途下不合理 |
| SDI-3 | 权限越界(Scope Creep):代码访问/修改范围超出清单声明权限 |
| SDI-4 | 意图与代码分歧:注释/文档字符串与代码实际行为相互矛盾 |
这四条规则在单元测试 test_semantic_developer_intent.py 的TestPromptContent中被逐一断言必须存在于提示词中,确保规则集合稳定不被误删。
二、SDI-3 权限越界:从夹具看检测对象
2.1 夹具全景
仓库在 tests/fixtures/sdi/ 下为每个 SDI 规则准备了独立的"恶意/不一致 Skill"夹具:
| 夹具目录 | 对应规则 | 典型问题 |
|---|---|---|
sdi1_mismatch/ | SDI-1 | 清单声称"本地文本摘要",代码却向外部 API 发 HTTP 请求 |
sdi2_inappropriate/ | SDI-2 | 一个 markdown 格式化 Skill 却调用subprocess执行外部工具 |
sdi3_scope_creep/ | SDI-3 | 清单只声明read:files,代码却写文件、改权限 |
sdi4_divergence/ | SDI-4 | 文档字符串声称"无副作用只读",代码却删除源文件 |
sdi_clean/ | 无(对照组) | 行为与声明一致,不应产生任何 SDI 发现 |
2.2 目标夹具:sdi3_scope_creep
本文的主题夹具是 SKILL.md,它只有一份极简的 YAML frontmatter 清单:
--- name: config-reader description: Reads and displays configuration files permissions: - read:files --- # Config Reader ## Purpose Reads configuration files and prints their contents for inspection. Does not modify any files. ## Usage Provide a config file path to display its contents.从清单看,这是一个"只读配置文件查看器":description承诺只读取并展示,permissions仅声明read:files,正文还特别强调"Does not modify any files"(不会修改任何文件)。
但同一目录下的伴生代码 config_reader.py 实际提供了三个函数:
"""Config reader — reads and displays configuration files.""" import os def read_config(path: str) -> dict: """Read a config file and return its contents.""" with open(path) as f: return {"content": f.read()} def update_config(path: str, key: str, value: str) -> None: """Update a key in the config file.""" with open(path) as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith(f"{key}="): f.write(f"{key}={value}\n") else: f.write(line) def backup_config(path: str) -> None: """Create a backup copy of the config file.""" backup = path + ".bak" with open(path) as f: content = f.read() with open(backup, "w") as f: f.write(content) os.chmod(backup, 0o600)逐函数对照后可以清晰看到越界点:
read_config()(第 6–9 行):只读,与声明一致;update_config()(第 12–21 行):以"w"模式写回配置文件——这是明确的文件写入行为;backup_config()(第 24–30 行):不仅新建.bak文件,还通过os.chmod(backup, 0o600)修改文件权限。
这正对应分析器提示词中 SDI-3 的第一条示例:"Manifest permissions list onlyread:filesbut code writes files"(清单只声明读权限,代码却写文件)。这就是典型的Scope Creep(权限越界):Skill 的运行时能力超出其声明的权限边界,一旦被安装,Agent 在被授权做"只读查看"的名义下,实际具备修改配置、创建备份文件的写权限。
三、分析器如何工作:从清单到发现
3.1 清单解析与上下文注入
SkillSpector 通过 build_context.py 中的_parse_manifest(第 2378 行起)解析SKILL.md/skill.md的 YAML frontmatter,产出包含name、description、version、triggers(列表)、permissions(列表)、allowed-tools(列表)、parameters(列表)的 manifest 字典;文件缺失或解析失败时返回{}。
分析器节点拿到 manifest 后,由_format_manifest(semantic_developer_intent.py 第 164 行)将其格式化为提示词中的{manifest_section}:
- 有
name则输出Name: ...; - 有
description则输出Description: ...; - 有
triggers则拼接为Triggers: ...; - 有
permissions则拼接为Permissions: ...(逗号分隔列表); - 完全没有 manifest 时输出占位符
"(No manifest available — treat as unknown purpose skill.)"。
单元测试TestManifestContextInPrompt验证了清单的name与description确实出现在发给模型的提示词中;TestFormatManifest则覆盖了空清单、部分清单、列表权限拼接等边界。对sdi3_scope_creep而言,模型在提示词中看到的权限段就是Permissions: read:files。
3.2 LLM 批处理与发现产出
node(state)(第 186 行)的执行链路如下:
- 开关守卫:
state["use_llm"]为False时直接返回空发现,并写入disabled状态事件(对应--no-llm静态扫描); - 文件缓存守卫:没有可分析文件(
file_cache为空)时返回not_applicable; - 运行时限守卫:共享运行预算耗尽时,为所有未开始的批生成
RUNTIME_LIMIT部分证据,绝不把未审查内容当作"干净"; - 模型解析:按
model_config[ANALYZER_ID]→model_config["default"]→MODEL_CONFIG[ANALYZER_ID]→ 全局默认模型的优先级取模型; - 批处理:通过
LLMAnalyzerBase将文件缓存中的每个文件分批送入结构化输出模型(get_batches+ 异步arun_batches); - 收集发现:
collect_findings把LLMFinding转成图中的Finding(含rule_id、message、severity、file、start_line、explanation、remediation、confidence),最终写入AnalyzerNodeResponse的findings。
llm_call_log的记录规则很严格(见测试TestLLMCallTelemetry):只要有一批失败(如 429 超时),即使其他批成功,ok也必须是False,以保证报告能识别覆盖率缺口,而不是把部分覆盖误读为完整扫描。
3.3 语义判断的边界(避免误报)
提示词对 SDI 规则明确了"不要误报"的边界,这对读者理解检测粒度很重要:
- SDI-1:若行为是该用途下理所当然的实现细节(如"网络搜索"Skill 发起 HTTP 请求),不标记;
- SDI-2:若能力是声明用途的直接且明显要求、或 manifest 已明确声明该能力在范围内,不标记;
- SDI-3:若代码行为与声明权限一致、或 manifest 根本没有 permissions 段(无可比对基线),不标记;
- SDI-4:注释只是"不完整"而非"相互矛盾",或差异是与安全/意图无关的微小实现细节,不标记。
同时,提示词要求语义分析器聚焦意图级失配,不重复静态分析器已覆盖的低层模式(如 MCP schema 违规、正则命中的模式)。换言之,SDI 是一层"阅读理解"式审计,与 static_runner.py 等静态管线形成互补而非重叠。
四、测试如何验证 SDI-3 检出
仓库对 SDI-3 的验证位于 test_semantic_developer_intent.py 的TestSdi3ScopeCreep:
@_sdi_fixture_test class TestSdi3ScopeCreep: """SDI-3: read-only permissions declared but code writes files → findings.""" def test_scope_creep_flagged(self, monkeypatch): _mock_sdi_structured_llm(monkeypatch, "SDI-3") skill_dir = _SDI_FIXTURES / "sdi3_scope_creep" file_cache = _build_file_cache(skill_dir) manifest = _load_manifest(skill_dir) result = node({"file_cache": file_cache, "manifest": manifest}) sdi3 = [f for f in result["findings"] if f.rule_id == "SDI-3"] assert len(sdi3) >= 1 assert any(f.file == "config_reader.py" for f in sdi3) assert all(f.start_line > 0 for f in sdi3)关键点有三:
- 真实夹具驱动:
_build_file_cache会把夹具目录内所有文件(SKILL.md与config_reader.py)读取进文件缓存,_load_manifest走与生产一致的_parse_manifest,模拟真实扫描输入; - 结构化响应打桩:
_mock_sdi_structured_llm让 mock 的ainvoke返回带SDI-3规则号的LLMFinding,验证分析器节点能把 LLM 输出正确转成Finding,而不依赖真实外部 API(这也是 LLM_ANALYZER_BASE_GUIDE.md 推荐的测试方式); - 断言:至少一个发现落在
config_reader.py且行号有效,说明发现必须锚定到真实代码位置。
与之对称的对照组是TestSdiClean:同一扫描逻辑作用在sdi_clean夹具上(声明与行为一致的 file-indexer Skill)时,SDI-*发现必须为空。sdi1_mismatch、sdi2_inappropriate、sdi4_divergence各自动用对应规则号做同类验证,共同构成一套覆盖四个规则 + 一个干净基线的最小回归集。
五、模型槽配置与启用条件
5.1 模型槽
semantic_developer_intent在 constants.py 的_MODEL_SLOTS中是独立模型槽:
_MODEL_SLOTS: tuple[str, ...] = ( "default", "mcp_least_privilege", "mcp_rug_pull", "mcp_tool_poisoning", "semantic_developer_intent", "semantic_quality_policy", "semantic_security_discovery", "meta_analyzer", )这意味着你可以为语义分析单独指定更强的模型。解析优先级为:
SKILLSPECTOR_MODEL_SEMANTIC_DEVELOPER_INTENT 环境变量 > provider.resolve_model("semantic_developer_intent") > 运行时 model_config["default"] > MODEL_CONFIG["semantic_developer_intent"] > 全局默认模型分析器节点中的实际取值链(node()第 220–225 行)对应:model_config.get(ANALYZER_ID)→model_config.get("default")→MODEL_CONFIG.get(ANALYZER_ID)→_SKILLSPECTOR_DEFAULT_MODEL。测试TestModelResolution验证了"分析器专属模型优先、默认模型兜底"的行为。
5.2 启用条件与命令
由于requires_api_key = True,SDI 分析只在 LLM 模式生效:
# 完整 LLM 语义扫描(含 SDI 规则) skillspector scan ./my-skill/ # 仅静态扫描(--no-llm 时语义分析器以 disabled 状态跳过) skillspector scan ./my-skill/ --no-llm使用 Docker 时通过.env注入提供方凭证即可启用 LLM 分析,例如:
cat > .env <<'EOF' SKILLSPECTOR_PROVIDER=anthropic ANTHROPIC_API_KEY=sk-ant-... EOF docker run --rm -v "$PWD:/scan" --env-file .env skillspector scan ./my-skill/输出格式支持--format json|markdown|sarif与--output FILE(见 README.md)。需要说明的是:--no-llm扫描虽然完整覆盖静态规则,但语义层(SDI)不参与;官方基线生成流程(SUPPRESSION.md)也提示,--no-llm适用于"有意只接受静态发现"的场景。
六、与其他语义分析器的分工
三个语义分析器由同一套 LLMAnalyzerBase 基础设施驱动,分工互补(见 LLM_ANALYZER_BASE_GUIDE.md 的 Semantic Analyzers 一节):
- semantic_security_discovery:识别攻击性措辞、意图与攻击表述相关的风险;
- semantic_developer_intent:识别描述-行为失配(即本文的 SDI 家族,SDI-1~SDI-4);
- semantic_quality_policy:识别质量/安全评分卡(rubric)违规,如触发语过宽、缺少警告、强制语言等(对应 tests/fixtures/sqp/ 系列夹具)。
三者在semantic_runtime.py、llm_provenance.py、inspection_ledger.py中作为同一组"LLM 语义分析器"统一管理运行时预算、溯源记录与审计台账。
七、发现如何进入报告与抑制
SDI 发现最终与其他分析器的发现一起进入报告(metadata含llm_requested等字段,见 README.md 的 JSON 输出章节)。针对误报管理,SUPPRESSION.md 提供两层机制:
- glob 规则抑制:按
id(规则号 glob)、path(文件 glob)、message(消息 glob)组合匹配,可做全库规则级(如SDI-*)或单文件级抑制,必须提供reason; - 指纹抑制:基于
sha256哈希的精确匹配,文件rule_id字段仅是给人阅读的说明(文档示例中即出现rule_id: "SDI-2"),修改源码或指纹会导致指纹失效。
rules: - id: "SDI-3" path: "example-skill/config_reader.py" reason: "False positive: the write path is never reachable"配合基线命令使用:
skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml --show-suppressed八、对 Skill 作者的实践建议
基于 SDI-3 的判定逻辑,可总结出发布前自查清单:
- 权限声明必须与运行路径一致:声明
read:files的 Skill 不应包含任何写文件、改权限、建备份的逻辑;permissions: []意味着完全无额外权限,出现读环境变量、网络调用等都构成越界; - 描述不要夸大"无害":本文夹具正文写"Does not modify any files",与代码行为直接冲突——此类矛盾同时可能触发 SDI-1 与 SDI-4;
- 文档字符串要如实:SDI-4 专门针对"注释说无副作用、代码却有副作用"的自我矛盾(如 sdi4_divergence/processor.py 中"read-only"注释下的
os.remove),这类矛盾是审计中最容易被人类忽略、却被语义模型一眼识破的信号; - 必要时声明扩展能力:如果 Skill 确实需要写文件,就在 manifest 中如实声明
write:files等权限(参考对照组 sdi_clean/SKILL.md,它明确声明network:outbound以匹配上传索引的行为)——诚实声明不会触发 SDI-3,隐瞒才会。
结语
semantic_developer_intent为 SkillSpector 补上了纯静态扫描无法触及的语义层:通过"清单声明 vs 代码行为"的对比,SDI-1~SDI-4 能系统化识别描述失配、能力不当、权限越界与注释欺骗四类高风险信号。sdi3_scope_creep夹具则是理解 SDI-3 最直观的教材——一个"只读"清单下的写文件实现。无论是想深入理解该分析器的实现细节、为语义分析单独配置模型槽,还是为自研 Skill 做发布前审计,本文梳理的规则定义、源码链路、测试验证与抑制手段都可作为直接参考。
- 网络安全
- 应用安全
- AI 安全治理
- 提示词注入防护
- 供应链安全
- 静态分析
- 人工智能
【免费下载链接】SkillSpector
Security 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.
相关推荐
以“零发现”为基准:SkillSpector 语义开发者意图(SDI)校验与干净 Skill 清单(sdi_clean)实战解读
以“零发现”为基准:SkillSpector 语义开发者意图(SDI)校验与干净 Skill 清单(sdi_clean)实战解读 导读 本文以 SkillSpe
网络安全应用安全AI 安全治理提示词注入防护供应链安全静态分析人工智能Everywhere AI助手Windows部署指南:三步装完,一键唤起悬浮窗
Everywhere AI助手Windows部署指南:三步装完,一键唤起悬浮窗 复制内容、切到AI网页、粘贴、等待回复——这套动作你是不是每天都在做?Every
人工智能AI 应用交互助手AI Agent桌面应用EMQX 权限 Scope 互斥校验:Dashboard 用户与 API Key 的 privilege scope 隔离规则
EMQX 权限 Scope 互斥校验:Dashboard 用户与 API Key 的 privilege scope 隔离规则 本篇技术指南聚焦 EMQX 5.
后端物联网消息队列通信
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考