面向 AI 编码代理的确定性安全审计事实与结论门禁:euthyna history 把每行被删代码归因到提交并标记安全修复类删除(--origins 追到最初引入者),euthyna coverage 报告哪些改动符号从未被测试调用,euthyna gate 用六道门禁逐条校验结论,拿不出证据的一律降级为观察。
安装
# npm 包(预构建)
dsh plugin --profile web add euthyna
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:slow-stack/euthyna
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
该插件的 README 只有英文版本。
εὔθυνα — in classical Athens, the audit every outgoing official had to submit. You did not get to simply walk away from office. You handed over your accounts and they were examined. Pass, and you left with your standing intact. Fail, and you faced trial.
euthyna is a code security audit framework for AI coding agents. It is not another scanner. It does two things: it produces the facts an agent cannot compute by reading code, and it forces every security claim the agent makes through gates before it counts as a finding.
📖 The problem, in plain words
When an AI coding agent touches security, it fails in two specific ways:
- It reports things that are not real. Code that looks dangerous gets called a vulnerability, without tracing the data. In validation runs on real codebases — a JavaScript one and a Python one — the pattern-matched "vulnerabilities" were mostly false: 5 out of 5 refuted at the gates on the first run; 2 out of 3 refuted, the third unresolved (INCONCLUSIVE, supply-chain-dependent) on the second. See the case studies (Python/crewAI, JavaScript/axe-core).
- Its reassurances cannot be checked. "I'm done." "The tests cover this." "It's safe now." These are assertions. You cannot tell a done-claim from a done-deal.
Neither is fixed by telling the agent to be more careful. euthyna changes the handshake between you and the agent:
The agent saying "I'm done" does not count. The accounts get handed over, and the gates decide.
⚖️ The two things it does
1. It measures what a model cannot
Three fact producers — a zero-dependency Node CLI:
history— for every line a change deletes, it attributes the line to a commit and classifies that commit from its message, its own diff, and the deleted line's content (a deletedif (!authorized)is flagged even under a "tweaks" subject). By default the attribution is blame's "last touched";--originsdigs for the commit that first introduced the content withgit log -S. If the deleted code came from a security fix, that is flagged. This is git archaeology no model can do from reading a diff.coverage— was this symbol ever actually invoked by a test? It has exactly two answers: never invoked (established), or entered but that proves nothing about any specific call site (unknown). It never reports "executed" — V8 coverage marks unreachable code as covered, and "line covered → call ran" is wrong in exactly the direction an audit cannot afford. The reasoning is indocs/fact-contract.md§6.2.deps— what version is a dependency actually pinned to? It reads the lockfile (package-lock.json / Cargo.lock / go.mod) and reports the resolved versions, or that a dependency is absent from the tree entirely. This is the fact that resolves supply-chain claims ("the app uses a vulnerable version of X"): the version-to-CVE mapping is left to the adjudication layer, exactly as the fact contract requires. Seedocs/fact-contract.md§6.3.
2. It gates what the agent claims
The skill is the audit discipline itself, as loadable Markdown. Every security claim must pass six gates — reachability, trust boundary, real impact, and their counterparts. A claim that cannot produce evidence is downgraded to an observation, not reported as a finding. "I'm done" becomes a package: claims, evidence, and the commands that reproduce both.
The gates are not only prose. euthyna gate <report> reads an adjudication report and
mechanically checks every finding against its verdict — evidence down to path:L123, a
reproduce command, an impact statement, consistent gate statuses — and downgrades
whatever does not measure up. --verify re-runs the reproduce commands: git commands
by default, interpreter commands (node/npm/python) only with the explicit
--allow-exec, because an interpreter command from a report is arbitrary code and the
flag is the caller vouching for that report.
🖥️ Which tools it works in, and how to install
Prerequisite for everything: Node >= 20 and git. There is nothing else to install — the project is deliberately zero-dependency.
| Host | The skill (audit discipline) | The CLI (fact producers) |
|---|---|---|
| DSH | dsh plugin --profile web add euthyna — the npm package mounts its own skill; or copy .agents/skills/euthyna/ into ~/.agents/skills/ (user-wide) or <project>/.agents/skills/. Markdown hot-reloads; no restart needed. |
npm install -g euthyna — runs in any terminal |
| Claude Code | Copy the same folder into ~/.claude/skills/ |
Same |
| Codex | The same Markdown layer works; packaging goes through Codex's plugin/marketplace format | Same |
| Hermes | Copy the same folder into ~/.hermes/skills/ under a category folder (Hermes reads the open skill standard; or install from a repo with hermes skills install) |
Same |
| OpenCode | Copy the same folder into ~/.agents/skills/ or ~/.config/opencode/skills/ (OpenCode loads both; unknown frontmatter fields are ignored) |
Same |
| Any terminal | — | npm install -g euthyna, then euthyna … |
Two honest notes:
- The skill is the instructions; the CLI is the measurement. The skill directory
does not contain the CLI. Install the CLI from npm (
npm install -g euthyna) or keep this repository checked out; on a host without the CLI, the skill requires the unmeasurable criteria to be recorded as not evaluated rather than guessed at — that fallback is the design, not a gap. - The skill text and the CLI's reports are currently written in Chinese. The discipline is host-agnostic Markdown, but an English reader should expect Chinese output from the tool itself.
🚀 Quick start
npm install -g euthyna
euthyna history --repo <path> --base main --head HEAD # add --origins to chase the first introducer
euthyna coverage --coverage coverage/coverage-final.json --symbol <name>
euthyna deps --repo <path> --dep <name>
euthyna gate <adjudication-report.md> --verify --cwd <repo> # mechanically check the six gates
Or without a global install: npx euthyna history --repo <path> --base main.
From a checkout instead (development):
git clone https://github.com/slow-stack/euthyna
cd euthyna && npm test # 178 tests; no install step exists
node bin/euthyna.js history --repo <path> --base main --head HEAD
What history reports looks like this:
已确证 (7)
• 本次变更删除了 4 行来自提交 ab877f9d70 的代码,分布在 2 个文件。
提交信息:"fix(link-in-text-block): don't match style or script text (#3775)",分类:fix
证据: lib/checks/color/link-in-text-block-evaluate.js (ab877f9d70)
复现: git blame --porcelain -L 104,104 -L 114,114 <base> -- lib/checks/.../evaluate.js
Every deleted line is blamed back to a commit, and the 复现
(Reproduce) command lets you re-derive the claim yourself without trusting the report.
Add --json for the structured fact report, --pickaxe to detect lines that were
removed and are now being added back, and --origins to attribute deleted lines to the
commit that first introduced their content rather than to blame's last modifier.
Exit codes are part of the contract
Surveying eight measurement plugins in this ecosystem found none of them publishes a process exit code, which makes their output unusable as a CI gate. This one does:
| Code | Meaning |
|---|---|
0 |
Measured; nothing security-classified found — or a gate report fully passes |
10 |
Measured; at least one security-classified fact exists — or a gate report has findings downgraded to observations |
1 |
Usage error |
2 |
Could not measure at all — must not be read as clean (also: a gate report that cannot be read or has no findings) |
2 being distinct from 0 is the whole point: failing to measure and measuring and
finding nothing are different things.
🧱 What is actually built
| Piece | What it is | Status |
|---|---|---|
| Fact producers | A zero-dependency Node CLI that answers three questions deterministically | Working, tested |
| The skill | The audit discipline itself, as loadable Markdown | Working, loadable |
| The benchmark | A blind recall measurement for the adjudication layer | Four rounds complete; the loop is one command (bench/adjudicate.js), with a deterministic golden round on CI |
✅ What has been verified, and what has not
This project tries to be explicit about the difference. Current state:
Verified
historyattribution against real repositories — in two languages. Run against axe-core (JavaScript) and crewAI (Python); deleted lines attributed to the commits that introduced them, then checked by hand againstgit blame. The checks developed for that comparison now run as regression tests in the suite.coverageon real output in three formats — c8/V8 JSON, classic istanbul (jest/nyc, same fnMap/f shape) and coverage.py JSON (format 3) — distinguishing all three states correctly, and refusing anything that is not a recognizable coverage report instead of answering "symbol not located" against it.depsagainst a real lockfile — resolved versions reported with a line-level evidence pointer into the lockfile, and absent dependencies reported as established absences rather than silent skips. Verified against a populated npm v3 lockfile and fixture lockfiles for Cargo.lock and go.mod.- Adjudication recall and specificity, measured blind: 10/10 cases, 4 real
vulnerabilities all caught, 6 non-vulnerabilities all correctly cleared, no abstentions.
Round 2 repeated every case three times — 30 adjudications, zero flips, four of them
on a different model. Round 3 expanded the set to 18 cases (8 real, 10 not) and
reshuffled the blind ids every round: 54 adjudications, 24/24 real-bug claims caught
with no misses and no abstentions, 29/30 non-vulnerabilities correctly cleared — and the
single "false alarm" was the round's finding, not noise: the adjudicator caught a defect
in a fixture's guard, confirmed by reproduction and fixed. Every case is a near-neighbour
pair — same pattern, one guard apart — so the verdicts had to come from reading the guard
rather than recognising the shape. Round 4 re-ran the full set under fresh id shuffles, with
the twice-defeated guard — rebuilt as a bare-name allow-list — facing its first blind
adjudication: 54/54 correct, all 8 pairs separated, and a third run on a second model
family agreed with the first two on every case.
See
bench/RESULTS.md,bench/RESULTS-round2.md,bench/RESULTS-round3.md, andbench/RESULTS-round4.md. - The delivery-gate mechanism, by running the real host plugin: blocking works, and the two
documented ways of getting it wrong do not. See
docs/dsh-stop-gate.md. - The gate discipline, mechanically.
euthyna gateis not a claim in prose: the suite pins the contract per verdict (a TRUE POSITIVE withoutpath:L123evidence, a reproduce command or all six gates passing is downgraded; a FALSE POSITIVE needs a failing gate with a reason), and--verifyis tested to not execute an interpreter command without--allow-exec.test/skill.test.jsfails CI if the skill text stops declaring the six gates, the three verdicts, or the command that enforces them. - The adjudication loop, end to end and on CI.
bench/adjudicate.jsruns a whole round — blind tree, one adjudicator process per case, machine-validated reports, scoring — and a deterministicgoldenround runs on every push, so the pipeline (andscore.jsgoing red on a wrong verdict) is checked without a model.
Not verified
- Whether the method finds vulnerabilities in real code. The benchmark measures whether the discipline reaches the right verdict on a claim. It does not measure whether the claims would be found in the first place. The cases are deliberately constructed. The crewAI case study surfaced one INCONCLUSIVE (pickle deserialization, supply-chain-dependent) and refuted the rest — a directional signal, not a rate.
- Recall in the field. The benchmark's real-bug cases are constructed; whether the discipline helps on code nobody staged for it is unmeasured.
- Source maps, bundlers, monorepos for the coverage producer. Untested.
- A real model round on CI. The golden round is deterministic plumbing, not an
adjudication: it writes the ground truth into the reports by design. A model round needs
credentials and is non-deterministic by nature, so it stays a local/manual step
(
bench/README.mddocuments the command) rather than a CI gate that could flake. --verifyas a sandbox. It is not one, and does not claim to be: it executes the report's commands with your privileges,gitonly unless--allow-execis passed.depsbeyond three formats and version facts only. pnpm/yarn/poetry lockfiles are detected but not parsed (reported as not evaluated, never guessed at); go.mod reports the declared requirement, not the resolved build version; and the producer never maps a version to a CVE — that mapping is deliberately left to the adjudication layer.- Anything about the case-study targets' security — the runs found nothing to endorse or
condemn; that is not a statement about either project. See
docs/case-study-crewai.mdanddocs/case-study-axe-core.md.
📂 Repository layout
euthyna/
├── bin/ src/ test/ Fact producers (zero dependencies, Node >= 20)
├── .agents/skills/euthyna/ The audit discipline, as a portable skill
├── bench/ Blind recall benchmark + results
├── docs/ Design notes and case studies (English and Chinese)
├── tools/ Research and verification scripts
└── data/ DSH plugin catalog snapshot
tools/fetch-references.js downloads the upstream sources this project reads into .refs/
(gitignored). No third-party files are distributed in this repository — see
NOTICE.md for why, and for what is owed to whom.
🧭 Status
Early, and honest about it. Working: the two fact producers, the skill, the benchmark harness. Not yet built: wiring the skill to the CLI so an agent uses them without being told, and the git-history / coverage work needed to close the remaining recall gap.
The design notes are available in English and Chinese; the -zh files are the originals.
🤝 Contributing
Read CONTRIBUTING.md. The short version: no AI attribution in commits,
one change per commit, and do not claim something works until you have run it.
🔒 Security
The fact producers execute git and parse coverage output; their integrity is the product.
What is in scope, and how to report privately: SECURITY.md. Note that the
benchmark fixtures in bench/cases/ are vulnerable by construction and are not vulnerabilities.
📜 License
Apache License 2.0 — see LICENSE.
链接
同类插件
toby-bridges/api-relay-audit★ 851
从 DeepSeek Harness 对 AI API 中转站和 LLM 代理运行本地安全审计,生成 Markdown 报告,覆盖提示词注入、模型替换信号、工具调用改写、错误泄漏、流完整性和按 profile 启用的 Web3 风险。
SeaOf0/dsh-redteam-model★ 561
面向授权安全研究的 DSH 合集:九个工作模式(redteam 总控、渗透测试、代码审计、二进制分析、攻防评估、免杀对抗、应急溯源、云安全攻防、CTF 解题)与十五个运行时插件,设置页管理台支持一键部署、安装、更新与卸载。
howmp/dsh-pentest★ 527
面向 DeepSeek Harness 的授权渗透模式:以探索链路记录目标、线索、资产与漏洞,并在 Web 中可视化展示。
PerryLink/dsh-auto-review★ 192
审批链上的第二模型自动审查:只读审查子代理返回带理由的 allow/deny 结构化裁决,默认 fail-closed。
NanmiCoder/dsh-auto-mode★ 161
在 Workspace Write 与 Full access 之间增加 Auto 权限档:日常操作留在官方 workspace-write 沙箱内,由当前会话模型复核升权与破坏性调用,精确的越界访问按次放行一次,意图不明时询问,命中关键路径则拒绝。
PerryLink/dsh-permission-rules★ 114
Claude Code 风格的声明式权限规则:按序 allow/deny/ask 的 YAML 规则,在 tools/pre-execute 瀑布上匹配工具名、参数、工作区路径与 agent 身份,带完整会话日志审计、干跑模式与热重载。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。