请与 skill 一起提交进 Git,或用 --lock-out 保存到 skill 目录之外。
August 30, 2026 · View on GitHub
Read a Skill before it reads your machine. 在让一个 Agent Skill 碰你的电脑之前,先读懂它。
skill-sentry is the trust layer for AI agents: a local, static, offline
tool that covers the whole life-cycle of a third-party Agent Skill.
skill-sentry 是面向 AI Agent 的信任层:一个本地、静态、离线的工具,覆盖第三方
Agent Skill 的整个生命周期。
| Stage / 阶段 | Command | What you get / 你得到什么 |
|---|---|---|
| Audit before install / 安装前审计 | audit | A risk report with file:line evidence |
| Lock before run / 运行前锁定 | lock | A SHA-256 evidence baseline (skill-sentry.lock.json) |
| Verify on hand-off / 交接时可验证 | verify | A per-file diff against the baseline, every finding carries a path |
| Trace after change / 改动后可追溯 | verify | Before/after hashes for every drifted entry |
It never executes the scanned Skill, never opens a network connection, never reads environment variables, and never uploads anything.
它不会执行被扫描的 Skill、不会联网、不读环境变量、不上传任何内容。
3-minute quick start / 3 分钟快速开始
git clone https://github.com/whaojie797-design/skill-sentry
cd skill-sentry
python --version # 3.9 or newer / 需要 3.9 及以上
There is nothing to install — zero third-party dependencies, pure Python standard library.
无需安装任何依赖——零第三方依赖,纯 Python 标准库。
# 1) Audit a Skill you are about to install / 审计一个你正准备安装的 Skill
python scripts/skill_sentry.py audit ./some-skill
# 2) If it looks acceptable, lock it / 觉得没问题就锁定基线
python scripts/skill_sentry.py lock ./some-skill
# 3) Re-check it after hand-off, an update, or a week / 交接后、更新后、一周后再校验
python scripts/skill_sentry.py verify ./some-skill
audit output goes to SKILL_AUDIT.md / skill-sbom.json / policy-result.json.
lock writes skill-sentry.lock.json. verify writes VERIFY_REPORT.md and
verify-report.json, and prints the same report to stdout.
The legacy v0.1.0 entry point still works exactly as before:
python scripts/audit_skill.py ./some-skill # unchanged behaviour / 行为不变
Real example / 真实示例
The commands below were run on a 4-file demo Skill
(SKILL.md, scripts/run.py, scripts/helper.sh, assets/logo.png) with an
in-skill policy.yml. The output below is a verbatim capture, not a mock-up.
以下命令在一份 4 文件的 demo Skill 上真实执行(SKILL.md、scripts/run.py、
scripts/helper.sh、assets/logo.png + skill 内的 policy.yml)。输出为逐字捕获,
不是编造的示例。
Capture note / 捕获说明:输出来自 Windows(
/tmp对应本机的C:\temp)。 Linux / macOS 上只有路径分隔符与路径不同,其余内容一致。
lock
$ python scripts/skill_sentry.py lock demo-skill --policy demo-skill/policy.yml
skill-sentry 0.2.0 - lock
========================================================================
Lock file : C:\temp\demo-skill\skill-sentry.lock.json
Skill : demo-skill
Files : 4 (text=3 binary=1 unknown=0 symlink=0)
Rule-scanned : 3
Empty dirs : 0
Warnings : 0
Policy : source=explicit present=True external=False
Audit : policy_passed=True max_level=INFO findings=0
Integrity : sha256 files_hash=cceb3b2961f160df65453a277dcbbc7902280daddd4e93a3eb82df9fb65e60a6
Limits : max_file_size=26214400 follow_symlinks=False
This lock file is a baseline, NOT a safety guarantee.
该锁文件是基线证据,不是安全保证。
Commit it with the skill, or store it outside the skill directory.
请与 skill 一起提交进 Git,或用 --lock-out 保存到 skill 目录之外。
========================================================================
echo $?
0
lock writes the file even when the baseline is incomplete, so verify always
has something to compare against. Running lock twice on an unchanged Skill
produces byte-identical output apart from generated_at.
即使基线不完整,lock 也会落盘(这样 verify 永远有可比对象)。对未改动的 Skill
连续执行两次 lock,除 generated_at 外字节完全一致。
verify — unchanged / 未改动
$ python scripts/skill_sentry.py verify demo-skill
skill-sentry 0.2.0 - verify
========================================================================
Result : OK (exit 0) / 与基线一致(退出码 0)
Lock file : C:\temp\demo-skill\skill-sentry.lock.json
Skill dir : demo-skill
Findings : error=0 review=0 info=0 unverifiable=0 fatal=0
What "verify passed" means / "verify 通过"到底意味着什么
verify PASS = "this skill still matches the recorded baseline".
verify PASS != "this skill is safe".
If any entry is UNVERIFIABLE, it is NOT counted as unchanged.
verify 通过 = 「与锁定时的记录一致」。
verify 通过 ≠ 「这个 Skill 是安全的」。
若有条目无法判定(UNVERIFIABLE),本报告不会把它算作「未漂移」。
Findings / 发现(0 条)
No findings: this skill matches the recorded baseline.
未发现差异:当前内容与锁定基线一致。
========================================================================
wrote: C:\temp\demo-skill\VERIFY_REPORT.md
wrote: C:\temp\demo-skill\verify-report.json
echo $?
0
verify — after someone edits one file / 有人改了一个文件之后
Changing total = sum(range(10)) to sum(range(11)) in scripts/run.py:
$ python scripts/skill_sentry.py verify demo-skill --no-write
skill-sentry 0.2.0 - verify
========================================================================
Result : DRIFT (exit 1) / 检测到漂移(退出码 1)
Lock file : C:\temp\demo-skill\skill-sentry.lock.json
Skill dir : C:/temp/demo-skill
Findings : error=1 review=0 info=0 unverifiable=0 fatal=0
What "verify passed" means / "verify 通过"到底意味着什么
verify PASS = "this skill still matches the recorded baseline".
verify PASS != "this skill is safe".
If any entry is UNVERIFIABLE, it is NOT counted as unchanged.
verify 通过 = 「与锁定时的记录一致」。
verify 通过 ≠ 「这个 Skill 是安全的」。
若有条目无法判定(UNVERIFIABLE),本报告不会把它算作「未漂移」。
Findings / 发现(1 条)
[ERROR] FILE_MODIFIED scripts/run.py
content differs from the locked baseline
expected: 24b1ff2afa92d9bbcd0b6c43399aa5463d49600b59895e40dc7180b94e15ca72
actual: 1ebcbc3173f3a41516833b37ccd41a7079abea772de71a909452ed1dfa34ea5f
------------------------------------------------------------------------
Every finding above carries a path so it can be located in the diff.
上面每一条发现都带有 path,可在 diff 中直接定位。
========================================================================
echo $?
1
Exit codes / 退出码
Every command uses the same four codes. Treat any non-zero code as "do not trust this Skill".
| Code | Meaning / 含义 | Trigger |
|---|---|---|
0 | OK | no drift, everything judged / 无漂移且全部可判定 |
1 | DRIFT | confirmed drift, or policy failure with --fail-on-policy |
2 | ERROR | could not complete: missing dir, unreadable/corrupt/incompatible lock file, IO failure |
3 | UNVERIFIABLE | no drift, but some entries could not be judged |
Priority is always 2 > 1 > 3 > 0. Code 3 exists because "we could not tell"
must never be reported as "nothing changed".
优先级恒为 2 > 1 > 3 > 0。退出码 3 存在的意义是:「判不出来」绝不能被报成「没变」。
What audit checks / audit 检查什么
| Domain | Rules | Catches |
|---|---|---|
| Destructive | DEST | rm -rf /, wipe home, fork bomb, disk overwrite |
| Network | NET | outbound requests, download-and-execute pipes (curl … | sh) |
| Secrets | SECRET | SSH dir, private keys, AWS creds, token patterns, env collection |
| Obfuscation | OBF | Base64 decode, dynamic evaluation, reflection |
| Injection | INJ | "ignore previous instructions", "bypass safety", "don't tell the user" |
| Persistence | PERSIST | shell startup files, cron, launch agents, systemd |
| Filesystem | FS | writes into /etc, /System, personal dirs |
| Domains | DOMAIN | every external host referenced (SBOM) |
Every hit reports file:line, a rule_id, a reason, a confidence and a level
(INFO / REVIEW / HIGH). A heuristic hit is not a confirmed
vulnerability — HIGH hits always need human review.
Threat model summary / 威胁模型摘要
The full model is in references/threat-model.md.
What it protects against / 能防什么
| ID | Threat | How |
|---|---|---|
| T1 | Silent code added or replaced after install | lock records a hash baseline; verify reports FILE_ADDED / FILE_MODIFIED with paths |
| T2 | Malicious code smuggled in an "update" | Same, plus --reaudit to compare the audit summary |
| T3 | TOCTOU: the audited version ≠ the version that runs | The lock file binds the audit conclusion to the file hashes in one artifact |
| T4 | Policy quietly loosened, invalidating an old PASS | policy.sha256 is recorded; verify reports POLICY_CHANGED |
| T5 | No way to prove "this is what I reviewed" | The lock file is diffable, committable evidence |
| T6 | New undeclared network destinations | audit.domains is locked; --reaudit compares the list |
| T7 | Symlinks dragging host files into scope | Symlinks are never followed by default; link_target is recorded for review |
| T8 | Huge or binary files used to exhaust the tool | Streaming hashes, a 25 MiB rule-scan threshold, binaries are hashed but not parsed |
What it does NOT protect against / 不防什么 (abridged — read the full file)
| ID | Not protected | Why | Mitigation outside this tool |
|---|---|---|---|
| N1 | A compromised host | An attacker with equal file rights can edit the Skill, the lock file and the tool itself | OS integrity protection, read-only mounts, least privilege |
| N2 | The lock file being edited together with the Skill | The lock file sits next to the Skill by default and is not signed; it is not a root of trust | lock --lock-out outside the Skill; protect the Git branch; external signing (under evaluation for v3) |
| N3 | Runtime behaviour | Purely static: no sandboxing, no syscall filtering, no network monitoring | Container / sandbox / seccomp |
| N4 | Intent | It can say "differs from the baseline", never "this change is malicious" | Human review of the diff |
| N5 | Author identity and provenance | No signatures, no certificates | Sigstore / minisign under evaluation for v3 |
| N6 | Contents of archives and binaries | A payload inside a .zip is invisible; only the .zip hash changes | Unpack and review manually |
| N7 | Cryptographic absolutes | SHA-256 collision resistance is an engineering assumption, and there is no signature | — |
| N8 | Live reputation | It cannot tell whether a domain is malicious now | External threat intelligence |
| N9 | Secrets injected at runtime | It deliberately never reads the environment | Audit the runtime environment separately |
| N10 | Rule-engine false positives/negatives | A hit is not a vulnerability; a miss is not a proof of safety | Human review; see the false-positive playbook |
The one sentence to remember / 务必记住的一句话
verify 通过 = 「与锁定时的记录一致」
verify 通过 ≠ 「这个 Skill 是安全的」
若有条目无法判定(UNVERIFIABLE),本报告不会把它算作「未漂移」。
Every verify report prints this, in both languages, every time.
Honest limitations / 诚实的局限
- It does not guarantee that any Skill is safe. / 它不保证任何 Skill 的安全性。
- It does not execute the scanned Skill. / 它不会执行被扫描的 Skill。
- It does not upload anything, ever. / 它从不上传任何内容。
- A heuristic hit is not a confirmed vulnerability. / 启发式命中不等于已确认的漏洞。
- A clean
verifymeans "matches the baseline", not "is safe". /verify通过只意味着「与基线一致」,不意味着「安全」。 - The lock file is not signed in v2; anyone who can edit the Skill can edit it. See N2 above. / v2 的锁文件未签名,能改 Skill 的人就能改它(见上文 N2)。
- Symlink targets are recorded but not compared, because they are environment specific (see the threat model). / 符号链接的目标会被记录但不参与比对,因为它与环境相关。
Command reference / 命令参考
python scripts/skill_sentry.py audit <skill_dir> [--policy P] [--out DIR]
python scripts/skill_sentry.py lock <skill_dir>
[--policy P] policy file to record (its SHA-256 is stored)
[--lock-out PATH] lock file path (default: <skill>/skill-sentry.lock.json)
[--max-file-size N] rule-scan threshold in bytes (default 26214400 = 25 MiB)
[--follow-symlinks] follow symlinks that stay inside the Skill (default: off)
[--no-audit] skip the audit section (audit = null)
[--fail-on-policy] exit 1 when the audit fails the policy
[--indent N] JSON indent (default 2; 0 = compact)
[--quiet] errors only
python scripts/skill_sentry.py verify <skill_dir>
[--lock PATH] lock file path (default: <skill>/skill-sentry.lock.json)
[--policy PATH] policy that overrides the one recorded in the lock
[--out DIR] report directory (default: the lock file's directory)
[--md-out PATH] [--json-out PATH]
[--no-write] print only; write nothing
[--check-audit] treat audit-summary changes as drift (exit 1)
[--reaudit] re-run the rule engine and compare the summary
[--ignore-empty-dirs] ignore added/removed empty directories
[--allow-unverifiable] record unverifiable entries but keep exit code 0
[--quiet]
--follow-symlinks is documented as "only use it when you fully trust this
Skill": following a symlink means hashing content that lives outside the Skill
directory. The containment check (the resolved target must stay under the Skill
root) still applies.
--follow-symlinks 的文档说明是**「仅在你能完全信任该 Skill 时使用」**:跟随符号链接
意味着哈希 Skill 目录之外的内容。即使开启,仍会强制「解析后目标必须仍在 Skill 根目录内」。
Install as an Agent Skill / 作为 Agent Skill 安装
Each host installs Skills on its own / 各宿主自行安装:
# Claude Code
git clone https://github.com/whaojie797-design/skill-sentry ~/.claude/skills/skill-sentry
# Cursor
git clone https://github.com/whaojie797-design/skill-sentry ~/.cursor/skills/skill-sentry
# Codex / OpenAI
git clone https://github.com/whaojie797-design/skill-sentry ~/.codex/skills/skill-sentry
# Gemini CLI
git clone https://github.com/whaojie797-design/skill-sentry ~/.gemini/skills/skill-sentry
CI / 持续集成
- name: Verify Skills have not drifted
run: |
python scripts/skill_sentry.py lock ./skills/my-skill --lock-out build/my-skill.lock.json
python scripts/skill_sentry.py verify ./skills/my-skill --lock build/my-skill.lock.json
Storing the lock file outside the Skill directory (--lock-out) is the
stronger configuration: a change to the Skill cannot silently rewrite its own
baseline. See SECURITY.md.
把锁文件存到 Skill 目录之外(--lock-out)是更强的配置:对 Skill 的改动无法顺手
改写它自己的基线。详见 SECURITY.md。
Zero dependencies / 零依赖
Runtime dependencies: none. Only the Python standard library is used
(argparse, dataclasses, hashlib, json, os, sys, stat, datetime,
platform, typing), and the code targets Python 3.9+. pytest is a
test-time-only dependency.
运行时依赖:无。只用标准库,目标 Python 3.9+。pytest 仅为测试期依赖。
The source is guarded by tests/test_guards.py, which statically asserts that
no module imports socket / urllib.request / http.client / subprocess,
never calls eval( / exec(, and never reads os.environ / os.getenv.
Resources / 资源
references/lock-file-schema.md— field-by-field schema, evolution rules, compatibility promisereferences/threat-model.md— full threat modelreferences/rule-catalog.md— every rule ID and what it meansreferences/false-positive-playbook.md— how to triage hitsassets/skill-sentry.lock.example.json— a real lock fileassets/VERIFY_REPORT.example.md— a real verify reportassets/policy.example.yml— team policy templateCHANGELOG.md— release historySECURITY.md— vulnerability disclosuredocs/DESIGN-v2.md— the v2 design document
License
MIT — Copyright (c) 2026 whaojie797-design.