euthyna 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/euthyna-en/SKILL.md +285 -0
- package/.agents/skills/euthyna-en/references/bug-classes.md +130 -0
- package/.agents/skills/euthyna-en/references/change-audit.md +212 -0
- package/.agents/skills/euthyna-en/references/dependency-audit.md +116 -0
- package/.agents/skills/euthyna-en/references/fact-contract.md +129 -0
- package/.agents/skills/euthyna-en/references/fact-producers.md +258 -0
- package/.agents/skills/euthyna-en/references/meta-mechanisms.md +174 -0
- package/.agents/skills/euthyna-en/references/verification-gates.md +291 -0
- package/.claude/commands/euthyna.md +18 -0
- package/.opencode/commands/euthyna.md +8 -0
- package/README.md +54 -7
- package/__init__.py +91 -0
- package/bin/euthyna.js +13 -3
- package/package.json +6 -1
- package/plugin/index.js +30 -1
- package/plugin.yaml +8 -0
- package/src/cli.js +189 -41
- package/src/contract.js +20 -13
- package/src/facts/coverage.js +69 -23
- package/src/facts/deps.js +81 -24
- package/src/facts/history.js +89 -25
- package/src/gate.js +171 -43
- package/src/lang.js +55 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: euthyna-en
|
|
3
|
+
description: 'The adjudication discipline and delivery gate for code security audits. Use when the user asks to audit the security of a piece of code, a pull request, a change, or to verify an existing vulnerability claim. It does three things: supply the deterministic facts a model cannot compute (callers, test coverage, git history origins), judge every claim against six gates, and downgrade any claim that cannot produce evidence to "observation" rather than a finding. Not for: asking about the details of a single CVE, running an existing scanner once, pure documentation or formatting changes, or when the user explicitly wants a quick summary and accepts the risk.'
|
|
4
|
+
whenToUse: Use when the user asks to audit the security surface of code or a repository, verify whether a suspected vulnerability is real, decide whether a finding is exploitable, review whether a change introduces a security regression, or asks to "audit before delivery". For single-topic checks such as only searching for secrets or only checking dependency versions, use the dedicated specialist skill instead and do not trigger this one.
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
user-invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# euthyna — the adjudication discipline for code security audits
|
|
10
|
+
|
|
11
|
+
> εὔθυνα: the audit every Athenian official had to submit on leaving office; you did not
|
|
12
|
+
> get to walk away without handing over your accounts. That is this skill's core
|
|
13
|
+
> mechanism: **saying "the audit is done" does not count. Evidence is handed over and the
|
|
14
|
+
> gates decide.**
|
|
15
|
+
|
|
16
|
+
**This skill does not find bugs; it polices conclusions.** Finding bugs is the model's and
|
|
17
|
+
the existing tools' job. This skill's job: every conclusion must pass the gates, and a
|
|
18
|
+
conclusion that cannot pass is downgraded to "observation".
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## When to use / when not to use
|
|
23
|
+
|
|
24
|
+
**Use**:
|
|
25
|
+
|
|
26
|
+
- The user asks to audit the security surface of code, a repository, or a change (PR / commit / diff)
|
|
27
|
+
- The user brings a suspected vulnerability and asks "is this real" or "is it exploitable"
|
|
28
|
+
- The user asks to review whether a change weakened an existing security control
|
|
29
|
+
- The user asks to "verify before delivery"
|
|
30
|
+
|
|
31
|
+
**Do not use** (refuse directly or route to another skill):
|
|
32
|
+
|
|
33
|
+
- Only asking about a specific CVE or dependency detail → use a dependency audit tool
|
|
34
|
+
- Only asking to run an existing scanner and paste its output → that is a tool call, not an audit
|
|
35
|
+
- Pure documentation, formatting, or lint changes → no security surface
|
|
36
|
+
- The user explicitly wants a quick summary and accepts the risk → state that this is
|
|
37
|
+
"non-audit mode" and do not apply this skill's adjudication format
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Three non-negotiable rules
|
|
42
|
+
|
|
43
|
+
These three rules are the entire difference between this skill and "tell the model to be
|
|
44
|
+
more careful". No stage, no path may skip them.
|
|
45
|
+
|
|
46
|
+
### Rule 1: measurable quantities come from commands, the model only interprets
|
|
47
|
+
|
|
48
|
+
Caller counts, test coverage, dependency versions, line numbers, commit hashes — **none of
|
|
49
|
+
these may be answered from impression**. The model may say "not measurable" when a command
|
|
50
|
+
is unavailable, but it must not estimate and then write the estimate into a conclusion as a
|
|
51
|
+
fact.
|
|
52
|
+
|
|
53
|
+
> This comes from a measured failure: a package showed "5+ maintainers" on the code hosting
|
|
54
|
+
> platform while the package manager's actual permission list had 1 person; another package
|
|
55
|
+
> with 160 million weekly downloads showed zero downloads in one data source. **Estimating
|
|
56
|
+
> numbers by feel is always wrong.**
|
|
57
|
+
|
|
58
|
+
### Rule 2: evidence is mandatory down to `file:line`; placeholders are banned
|
|
59
|
+
|
|
60
|
+
- Every claim cites a concrete location in the form `path:L123`, and `path:L123 (commit abc1234)` when a version exists
|
|
61
|
+
- `probably` / `likely` / "roughly" are banned — chase the real code, and if you cannot, say you cannot
|
|
62
|
+
- PoCs must not contain `TODO`, `...`, or `// attacker does something here`
|
|
63
|
+
- **Once a PoC is found to use an artificial bypass** (mock, stub, disabled check), that PoC is **invalid**
|
|
64
|
+
|
|
65
|
+
### Rule 3: missing data is not clean
|
|
66
|
+
|
|
67
|
+
Every criterion falls into exactly one of three states: **evaluated-clean / evaluated-flagged /
|
|
68
|
+
not evaluable for a stated reason**.
|
|
69
|
+
|
|
70
|
+
- A run that measured nothing produces "not evaluable", **not** "no problems found"
|
|
71
|
+
- Before citing any "clean" conclusion, read the coverage table first
|
|
72
|
+
- **Numbers must be quoted verbatim** — no re-deriving, rounding, or dressing up
|
|
73
|
+
- Finding nothing is not an endorsement
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Entry decision tree
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
What did the user give?
|
|
81
|
+
│
|
|
82
|
+
├─ An existing suspected-vulnerability claim ──► Stage C: claim verification
|
|
83
|
+
│ ("is this real", "is it exploitable") read references/verification-gates.md
|
|
84
|
+
│ ★ if the conclusion depends on "was it tested" → run coverage
|
|
85
|
+
│
|
|
86
|
+
├─ A change (PR / diff / commit) ───────────────► Stage B: change audit
|
|
87
|
+
│ ("is this PR safe") read references/change-audit.md
|
|
88
|
+
│ ★ deleted lines in the diff → run history first
|
|
89
|
+
│ ★ if test coverage matters → run coverage first
|
|
90
|
+
│
|
|
91
|
+
├─ A repository / dependency surface ───────────► Stage A: dependency audit
|
|
92
|
+
│ ("do the dependencies have problems") read references/dependency-audit.md
|
|
93
|
+
│
|
|
94
|
+
└─ A full repository security audit ────────────► Stage B as the core, with A and C as sub-steps
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
★ = **run before reading code by hand**, not as a follow-up after reading. The deterministic
|
|
98
|
+
facts from the producers decide where the manual reading must be directed.
|
|
99
|
+
|
|
100
|
+
**Multiple stages may run, but stages must not substitute for each other.**
|
|
101
|
+
In particular: every suspected finding reported by Stage B must go through Stage C one by
|
|
102
|
+
one — never write it straight into the report as a conclusion.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## The two deterministic measurements this skill ships
|
|
107
|
+
|
|
108
|
+
Callers, coverage, dependency vulnerabilities and secrets are **not re-built** (see the
|
|
109
|
+
division of labor at the end). But two things nobody else does — and a model cannot compute
|
|
110
|
+
reliably — are shipped by this skill as a zero-dependency CLI:
|
|
111
|
+
|
|
112
|
+
| Measurement | Question it answers | When it is **mandatory** |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `history` | Which commit does the code this change **deleted** come from? Was that commit a security fix? | when the diff has deleted lines (Stage B) |
|
|
115
|
+
| `coverage` | Was this symbol **ever actually invoked** by a test run? | when the report claims "tested / not tested" (Stage B / C) |
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --repo <audited repo>
|
|
119
|
+
node <euthyna repo>/bin/euthyna.js coverage --coverage <absolute path to coverage file> --symbol <symbol name>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
⚠️ **A relative `node bin/euthyna.js` always fails.** During an audit the current directory
|
|
123
|
+
is the **audited project**, not the euthyna repo, and it will report `Cannot find module`.
|
|
124
|
+
Use the euthyna repo's path and pass the audited repo explicitly with `--repo` (`--base` /
|
|
125
|
+
`--head` resolve against it).
|
|
126
|
+
|
|
127
|
+
**Exit codes** — the difference between `2` and `0` is the reason this whole thing exists:
|
|
128
|
+
|
|
129
|
+
| Code | Meaning | What to do |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `0` | measured, no security-related finding | continue, but **read the "criteria not evaluated" section** |
|
|
132
|
+
| `10` | measured, at least one fact classified `security` | **raise priority**, run each through Stage C; do not treat as a conclusion |
|
|
133
|
+
| `1` | usage error | fix the command. This is not a measurement result |
|
|
134
|
+
| **`2`** | **could not measure at all** | **must not be read as clean**. Change the measurement approach, or mark the criterion "not evaluable" |
|
|
135
|
+
|
|
136
|
+
When a command is unavailable (not installed, no checkout): **mark the criterion "not
|
|
137
|
+
evaluable" — never fill the gap with a hand estimate.**
|
|
138
|
+
|
|
139
|
+
Operations manual (commands, real output samples, failure modes): `references/fact-producers.md`.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## The fact contract (the interface between measurement and adjudication)
|
|
144
|
+
|
|
145
|
+
This skill defines the interface between the measurement layer and the adjudication layer:
|
|
146
|
+
`docs/fact-contract.md`.
|
|
147
|
+
|
|
148
|
+
Key points:
|
|
149
|
+
|
|
150
|
+
- The contract carries only **facts** — no scores, no severity levels, no remediation advice
|
|
151
|
+
- Every fact must carry a **reproduce command** or **producing tool + version**
|
|
152
|
+
- **Scores must not be used as gates** (learned from `dsh-trust-check`: its contract explicitly says "do not use `score` or `band` as a gate")
|
|
153
|
+
- `approximate` facts **must not participate in gates**
|
|
154
|
+
- **`unknown` is not "clean"** — it is the most misused of the three states
|
|
155
|
+
|
|
156
|
+
How to read a fact (what it lets you infer, and **what it does not**): `references/fact-contract.md`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## The adjudication format
|
|
161
|
+
|
|
162
|
+
Whatever path was taken, the output must land in this shape:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
BUG #N TRUE POSITIVE — [one-line claim]
|
|
166
|
+
All gates passed. Evidence: path:L123 (commit abc1234)
|
|
167
|
+
Reproduce: <re-runnable command or PoC location>
|
|
168
|
+
Exploitability: EASY / MEDIUM / HARD
|
|
169
|
+
Impact: specific to funds / elevated privilege / exposed data
|
|
170
|
+
|
|
171
|
+
BUG #N FALSE POSITIVE — [reason for rejection]
|
|
172
|
+
Gate N (gate name) FAIL: <specific evidence>
|
|
173
|
+
e.g. line 98's check guarantees packet_size >= 16, so (packet_size - header_size) >= 8.
|
|
174
|
+
Underflow is mathematically impossible.
|
|
175
|
+
|
|
176
|
+
BUG #N INCONCLUSIVE — [reason it cannot be decided]
|
|
177
|
+
Gate N (gate name) NOT EVALUATED: <why>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**The third state `INCONCLUSIVE` must exist.** Trail of Bits' original fp-check has only two
|
|
181
|
+
states because it uses a Stop hook to force infinite continuation until completion;
|
|
182
|
+
**DSH cannot do that** (the Stop hook must rate-limit itself or it loops forever, see
|
|
183
|
+
`docs/dsh-stop-gate.md`). So this honest exit must exist — "not finished" must never be
|
|
184
|
+
written as `FALSE POSITIVE`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## The programmatic gate check (euthyna gate)
|
|
189
|
+
|
|
190
|
+
The format above is not decoration: `euthyna gate <report file>` mechanically checks every
|
|
191
|
+
finding —
|
|
192
|
+
|
|
193
|
+
- **TRUE POSITIVE**: evidence must reach `path:L123`, a reproduce command is mandatory, impact
|
|
194
|
+
must be stated, and all six gates must pass;
|
|
195
|
+
- **FALSE POSITIVE**: at least one gate FAIL with specific evidence;
|
|
196
|
+
- **INCONCLUSIVE**: at least one gate not evaluated, and no FAIL.
|
|
197
|
+
|
|
198
|
+
A finding that lacks evidence, lacks reproduction, or whose gates contradict its verdict
|
|
199
|
+
(such as a TRUE POSITIVE carrying a FAIL gate) is **downgraded to "observation"** and
|
|
200
|
+
reported with exit code `10` — the checker does the policing, no self-awareness required.
|
|
201
|
+
|
|
202
|
+
```powershell
|
|
203
|
+
node <euthyna repo>/bin/euthyna.js gate <report file>
|
|
204
|
+
# re-run each reproduce command to verify (allowlisted tools, argv execution, no shell):
|
|
205
|
+
node <euthyna repo>/bin/euthyna.js gate <report file> --verify --cwd <audited repo>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- Exit code `0` = everything passed; `10` = at least one finding downgraded; `2` = the report
|
|
209
|
+
cannot be read / has no checkable finding.
|
|
210
|
+
- Under `--verify`, a reproduce command that fails to run is also a downgrade — "I can
|
|
211
|
+
reproduce it" is not a claim until the run actually succeeds.
|
|
212
|
+
|
|
213
|
+
**Run `euthyna gate` on the report before handing it over.** It validates the report's own
|
|
214
|
+
self-declared claims; it does not audit the target repository — so it cannot replace
|
|
215
|
+
adjudication, it only guarantees that a conclusion without evidence cannot leave as
|
|
216
|
+
"established".
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## The report must be written to disk
|
|
221
|
+
|
|
222
|
+
- Outputting only to chat is a **failure**. A report file is mandatory
|
|
223
|
+
- Filename: `<PROJECT>_EUTHYNA_AUDIT_<YYYY-MM-DD>.md`
|
|
224
|
+
- Fallback order when writing fails: current working directory → user desktop → temp
|
|
225
|
+
directory → **last resort** only then output to chat and ask the user to save it manually
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Pre-delivery self-check (tick every item; without all of them you may not say "audit done")
|
|
230
|
+
|
|
231
|
+
- [ ] Every suspected finding went through **complete** Stage C, not "looked at it and it seems false"
|
|
232
|
+
- [ ] **Ran `history` when the diff has deleted lines**; code deleted from a security-fix commit handled at highest risk
|
|
233
|
+
- [ ] Every "tested / not tested" sentence in the report is backed by `coverage` output, or marked "not evaluable"
|
|
234
|
+
- [ ] **No criterion with exit code `2` appears in a "clean" column**
|
|
235
|
+
- [ ] Every conclusion carries a `file:line`
|
|
236
|
+
- [ ] Every conclusion carries one of the three states (clean / flagged / not evaluable)
|
|
237
|
+
- [ ] The coverage table states **which criteria were not evaluated and why**
|
|
238
|
+
- [ ] All 13 devil's advocate questions answered (including the false-negative 12 and 13)
|
|
239
|
+
- [ ] The report file has been written to disk
|
|
240
|
+
- [ ] The report has passed `euthyna gate` (exit code `0`; if there are downgrades, read why)
|
|
241
|
+
- [ ] "Missing data" was not written as "clean"
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Reference files
|
|
246
|
+
|
|
247
|
+
Load on demand. **Do not read them all at once** — progressive disclosure is why this stays
|
|
248
|
+
effective in long sessions.
|
|
249
|
+
|
|
250
|
+
| File | Contents | When to read |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| `references/verification-gates.md` | **Stage C**: routing, the six gates, false-positive list, devil's advocate, PoC rules | when judging an existing claim (**most used**) |
|
|
253
|
+
| `references/change-audit.md` | **Stage B**: baseline, depth vs risk, origin attribution, blast radius, adversarial modeling | when auditing a change |
|
|
254
|
+
| `references/dependency-audit.md` | **Stage A**: manifests/lockfiles, the three states, report style, prohibitions | when auditing the dependency surface |
|
|
255
|
+
| `references/bug-classes.md` | Per-class checklists for 9 defect classes and their **default assumption direction** | once the defect class is chosen |
|
|
256
|
+
| `references/meta-mechanisms.md` | The six meta-mechanisms that keep the process from degrading into a formality | **read once at the start of an audit** |
|
|
257
|
+
| `references/fact-producers.md` | **Operations manual**: when to run the two measurements, how to write the commands, how to handle exit codes, real output samples, failure modes | **before running a producer** (Stage B / C) |
|
|
258
|
+
| `references/fact-contract.md` | Once you have a fact: what it lets you infer, **and what it does not** | after reading a producer's output |
|
|
259
|
+
|
|
260
|
+
> **The skill text is self-contained**: every file above lives inside `references/`, depends
|
|
261
|
+
> on nothing outside the skill directory, and the whole directory can be copied into any
|
|
262
|
+
> skill root.
|
|
263
|
+
>
|
|
264
|
+
> ⚠️ **But the producer is a separate program, not inside the skill directory.** Copying the
|
|
265
|
+
> skill does not carry the CLI along. A new environment must install it separately, or ship a
|
|
266
|
+
> checkout of this project; otherwise the related criteria can only be marked "not
|
|
267
|
+
> evaluable" — which is an honest result, better than a hand estimate. See
|
|
268
|
+
> `references/fact-producers.md` section 1.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Division of labor with the existing ecosystem (no duplicated work)
|
|
273
|
+
|
|
274
|
+
| Already built by someone else | What euthyna does |
|
|
275
|
+
|---|---|
|
|
276
|
+
| Call graph / blast radius (`dsh-tool-lens`, `dsh-blast-radius`) | **does not rebuild**, consumes their output |
|
|
277
|
+
| Coverage (`dsh-code-coverage`) | does not rebuild, consumes its output |
|
|
278
|
+
| Dependency vulnerabilities (`dsh-dep-vuln-scan` etc.) | does not rebuild, consumes its output |
|
|
279
|
+
| Secret scanning (`dsh-code-security`) | does not rebuild, consumes its output |
|
|
280
|
+
| Delivery gate mechanisms (`dsh-doublecheck`, `formalswarm`) | mechanisms worth borrowing; euthyna's gate content is the **security-specific** six gates |
|
|
281
|
+
| Evidence lifecycle and PoC lab (`dsh-omv`) | can coexist: `dsh-omv` can be a **consumer** of the fact contract |
|
|
282
|
+
|
|
283
|
+
**euthyna only supplies the two deterministic measurements nobody else does** (see
|
|
284
|
+
`docs/positioning.md` §7.2): the mechanical adjudication of git security regressions, and
|
|
285
|
+
the "symbol × real execution coverage" join.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Defect-class specific requirements
|
|
2
|
+
|
|
3
|
+
> When adjudicating a claim, **first fix the class, then apply that class's default assumptions**.
|
|
4
|
+
>
|
|
5
|
+
> Because across classes, "what counts as suspicious" and "what counts as a false positive" are **opposite**. Judging every class
|
|
6
|
+
> with the same intuition guarantees systematic errors in some classes.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. Default assumptions flip by class
|
|
11
|
+
|
|
12
|
+
This is the most important rule in this file, and the most counter-intuitive:
|
|
13
|
+
|
|
14
|
+
| Class | Direction of the default assumption |
|
|
15
|
+
|---|---|
|
|
16
|
+
| **memory corruption** | default to **false positive**. In memory-safe languages it is almost always a false positive, unless there is a compiler defect or a type-system hole |
|
|
17
|
+
| **logic defect** | default to **not a false positive**. "Logic defects pass every boundary check" — **do not let clean static analysis convince you it is a false positive** |
|
|
18
|
+
| remaining classes | see below, class by class |
|
|
19
|
+
|
|
20
|
+
Flip these two rows and you flood memory corruption with false positives while missing real logic-defect vulnerabilities.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Memory corruption
|
|
25
|
+
|
|
26
|
+
**Do the language check first**: memory corruption in safe Rust, in Go without `unsafe.Pointer`/cgo, and in every managed language
|
|
27
|
+
(Java / C# / Python / JS) is **almost always a false positive**. Passing this gate first saves most of the work.
|
|
28
|
+
|
|
29
|
+
After the language check, examine:
|
|
30
|
+
|
|
31
|
+
- what exactly is corrupted (stack / heap / global / object)
|
|
32
|
+
- whether the size and offset of the corruption **are attacker-controlled**
|
|
33
|
+
- whether it is a useful primitive (arbitrary read, arbitrary write, vtable overwrite) or merely a crash
|
|
34
|
+
- whether allocator hardening is in place
|
|
35
|
+
- the object lifetime of a use-after-free (UAF): can it actually be referenced again after being freed
|
|
36
|
+
- type confusion: give **proof of the type mismatch**, not "it looks like one"
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. Logic defect
|
|
41
|
+
|
|
42
|
+
**The default assumption is reversed: do not let "static analysis is clean" convince you this is a false positive.**
|
|
43
|
+
|
|
44
|
+
- judge against the **spec / RFC / design documents**, not by reading code alone
|
|
45
|
+
- draw **every state transition** — can an unexpected state be reached?
|
|
46
|
+
- find **implicit assumptions that are never enforced**
|
|
47
|
+
- for authentication-class defects, verify **all** authentication and authorization paths, not just the main path
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 3. Race condition
|
|
52
|
+
|
|
53
|
+
- is the real race window **nanoseconds or seconds**? The two are entirely different problems
|
|
54
|
+
- can the attacker **widen the window** (slow filesystem, large allocations, CPU contention)
|
|
55
|
+
- verify the threading model: single-threaded / event loop / multi-threaded — the conclusions are entirely different
|
|
56
|
+
- check every synchronization primitive, not just whether a lock exists
|
|
57
|
+
- for filesystem classes, look at **symlink races** (the classic TOCTOU form)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 4. Integer issues
|
|
62
|
+
|
|
63
|
+
- the **exact type and value range** at every point, not "roughly an integer"
|
|
64
|
+
- signed overflow (undefined behavior in C/C++) vs unsigned wraparound (**defined**) — they must not be conflated
|
|
65
|
+
- trace every cast / conversion / promotion
|
|
66
|
+
- **is the result actually used for something dangerous**: allocation size, array index, loop bound. An overflow used for log printing is not a vulnerability
|
|
67
|
+
- enable checks such as `-Wconversion` and `-Wsign-compare`, and let the compiler find them for you
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 5. Crypto weaknesses
|
|
72
|
+
|
|
73
|
+
- check parameters against NIST / IETF standards and known attacks
|
|
74
|
+
- verify the randomness source: is it cryptographically secure
|
|
75
|
+
- **nonce reuse must be shown to actually occur twice in practice**, not "theoretically possible"
|
|
76
|
+
- for timing side channels, look at two things: whether the attacker can reach it, and whether network jitter makes a remote attack impractical
|
|
77
|
+
- compare against the reference implementation's test vectors
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 6. Injection
|
|
82
|
+
|
|
83
|
+
- trace the attacker's input along its **full path** from entry to sink, and whether it is sanitized or escaped along the way
|
|
84
|
+
- is the framework's automatic escaping enabled, **and not bypassed**
|
|
85
|
+
- XSS depends on context: HTML body / attribute / JS / URL — **each needs different escaping**; one escaping cannot cover all
|
|
86
|
+
- path traversal: was the path normalized **before** the access check
|
|
87
|
+
- run all intermediate processing with **real payloads**; do not verify in hand-written examples
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 7. Information disclosure
|
|
92
|
+
|
|
93
|
+
- **what exactly is leaked**: a stack leak (can yield an ASLR base or canary) is serious; a static string is worthless
|
|
94
|
+
- is the leaked data **actually useful** for further exploitation
|
|
95
|
+
- for uninitialized memory, **prove that the read point is really uninitialized**
|
|
96
|
+
- for timing side channels, look at measurement precision and noise level
|
|
97
|
+
- can the error information **actually reach the attacker** — being written to a log does not count
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 8. Denial of service
|
|
102
|
+
|
|
103
|
+
- is the resource consumption ratio or amplification factor **meaningful**
|
|
104
|
+
- are resources recoverable or permanently exhausted — the two differ greatly in severity
|
|
105
|
+
- **a complexity claim must show that a real worst-case input triggers it**, not just assert O(n²):
|
|
106
|
+
name the concrete input and give the measured time
|
|
107
|
+
- is the crash triggered **reliably**
|
|
108
|
+
- does the service restart automatically: a 100-millisecond restart and a human having to intervene are two entirely different problems
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 9. Deserialization
|
|
113
|
+
|
|
114
|
+
- does the attacker **actually control** the data that reaches the deserialization point
|
|
115
|
+
- is there a **usable gadget chain** in the classpath / import graph —
|
|
116
|
+
**without a gadget chain, unsafe deserialization is a design smell, not an exploitable defect**
|
|
117
|
+
- the library and version, and the chains known for that version
|
|
118
|
+
- are type restrictions / allowlist filters in place
|
|
119
|
+
- language-specific forms: Java `ObjectInputStream`, Python `pickle`, PHP `unserialize`, .NET `BinaryFormatter`
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Usage
|
|
124
|
+
|
|
125
|
+
1. Classify the assertion at Stage C's **step zero**
|
|
126
|
+
2. The class sets the direction of the default assumption (see §0)
|
|
127
|
+
3. When running data-flow and exploitability analysis, **layer on** that class's specific checks (table above)
|
|
128
|
+
4. When evaluating the gates, that class's particular "what counts as prevented" is judged by the definitions above
|
|
129
|
+
|
|
130
|
+
> Classify wrong and everything after is wrong. **When unsure, say you are unsure — do not pick a plausible-looking one to fill in.**
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Stage B: change audit
|
|
2
|
+
|
|
3
|
+
> Audit one change (PR / commit / diff) and answer one question: **did this change make the system weaker?**
|
|
4
|
+
>
|
|
5
|
+
> Prerequisite: the three non-skippable rules in `../SKILL.md`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## B.0 Establish the baseline first
|
|
10
|
+
|
|
11
|
+
**This is the first action, not prep work.** Without a baseline, every judgment that follows is guesswork.
|
|
12
|
+
|
|
13
|
+
Record these on the **pre-change** revision:
|
|
14
|
+
|
|
15
|
+
| What to record | Why |
|
|
16
|
+
|---|---|
|
|
17
|
+
| system-level invariants | "what must always be true" — without them you cannot tell whether this change broke one |
|
|
18
|
+
| trust boundaries and privilege levels | which data is trusted, which is not; what identity the code runs as |
|
|
19
|
+
| existing validation patterns | what validation this codebase habitually does, and where |
|
|
20
|
+
| call relationships of key functions | the change's blast radius is judged through them |
|
|
21
|
+
| state flow | where data comes from, what it passes through, where it goes |
|
|
22
|
+
| **what the code is supposed to do** | not what it currently does — its design intent |
|
|
23
|
+
|
|
24
|
+
Miss the last item and you misjudge "the implementation diverges from the design" as "the implementation has a bug" — or the reverse.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## B.1 Keep two things apart: analysis depth vs risk level
|
|
29
|
+
|
|
30
|
+
These two are routinely conflated, and conflating them guarantees mistakes.
|
|
31
|
+
|
|
32
|
+
**Analysis depth** is set by **change size** — it only decides how much effort you spend:
|
|
33
|
+
|
|
34
|
+
| Change size | Strategy | Approach |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| < 20 files | deep read | read all related code, full blame |
|
|
37
|
+
| 20–200 | focused | follow only one-hop dependencies, prioritize high-risk files |
|
|
38
|
+
| 200+ | surgical | only the critical paths |
|
|
39
|
+
|
|
40
|
+
**Risk level** is set by **what changed** — it is **unrelated** to size:
|
|
41
|
+
|
|
42
|
+
| Level | Trigger |
|
|
43
|
+
|---|---|
|
|
44
|
+
| high | authentication, crypto, external calls, money movement, **any validation deleted** |
|
|
45
|
+
| medium | business logic, state changes, new public interfaces |
|
|
46
|
+
| low | comments, tests, UI, logs |
|
|
47
|
+
|
|
48
|
+
> **A one-line change can be the highest risk; two thousand lines can be zero risk.** Judging risk by size is the most common shortcut.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## B.2 Read both versions, file by file
|
|
53
|
+
|
|
54
|
+
For every changed file, do not look at the diff alone. **Read the pre-change and post-change versions side by side**, then answer four questions per section:
|
|
55
|
+
|
|
56
|
+
| Question | What it answers |
|
|
57
|
+
|---|---|
|
|
58
|
+
| What was it before | the original logic |
|
|
59
|
+
| What is it after | the new logic |
|
|
60
|
+
| **What the change does** | the intent in one sentence |
|
|
61
|
+
| **What it means for security** | does the attack surface grow or shrink? does validation get stronger or weaker? |
|
|
62
|
+
|
|
63
|
+
A diff only shows "which lines changed", not "how the behavior changed". Deleting one line may delete nothing (the line may already have been dead code); adding one line may change the entire execution order.
|
|
64
|
+
|
|
65
|
+
### Deleted code must be traced to its origin separately
|
|
66
|
+
|
|
67
|
+
**This is the step most easily skipped in this stage, and the most valuable one.**
|
|
68
|
+
|
|
69
|
+
For every block of deleted code, trace its origin with this project's producer:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --repo <audited repo>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
⚠️ **Do not write `node bin/euthyna.js`** — during an audit the current directory is **the audited project**, not the euthyna repo, and a relative path fails with `Cannot find module`. `--repo` refers to **the audited repo**, and `--base` resolves against it.
|
|
76
|
+
|
|
77
|
+
It tells you which commit introduced the deleted code and whether that commit's message carries security keywords. **Code deleted from a `fix` / `CVE` / `security` commit defaults to the highest risk**, unless it can be shown to be superfluous.
|
|
78
|
+
|
|
79
|
+
Do not run `git blame` yourself and read commit messages by eye — that is exactly why this producer exists: a machine adjudicating the same diff twice gives the same result; a human reading twice may not.
|
|
80
|
+
|
|
81
|
+
When reading the output, watch the "⚠ criteria not evaluated" section: when it is non-empty, this run **cannot** be read as "checked, no problem". Exit code `2` means it could not measure at all — **must not be read as clean**. Commands and exit codes: see `fact-producers.md`.
|
|
82
|
+
|
|
83
|
+
### Three red lines
|
|
84
|
+
|
|
85
|
+
| Symptom | Default handling |
|
|
86
|
+
|---|---|
|
|
87
|
+
| deleted code came from a security-fix commit | treat as **highest** risk, unless shown to be no longer necessary |
|
|
88
|
+
| validation deleted with no equivalent replacement | treat as **highest** risk |
|
|
89
|
+
| code was added → removed for security reasons → **added back now** | treat as a **regression** |
|
|
90
|
+
|
|
91
|
+
The third one is mechanically detectable with the producer:
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
node <euthyna repo>/bin/euthyna.js history --base <pre-change revision> --pickaxe --repo <audited repo>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## B.3 Test coverage: a gap escalates risk, it is not a footnote
|
|
100
|
+
|
|
101
|
+
**Missing tests are not "worth a mention" — they are grounds for escalating risk.**
|
|
102
|
+
|
|
103
|
+
| Situation | Handling |
|
|
104
|
+
|---|---|
|
|
105
|
+
| new function with no tests | medium risk escalates to **high** |
|
|
106
|
+
| validation modified but tests did not change | **high risk** |
|
|
107
|
+
| complex logic (> 20 lines) with no tests | **high risk** |
|
|
108
|
+
|
|
109
|
+
To claim "covered", you need real execution evidence, not an inference from "this file has a test file":
|
|
110
|
+
|
|
111
|
+
```powershell
|
|
112
|
+
node <euthyna repo>/bin/euthyna.js coverage --coverage <absolute path to coverage file> --symbol <changed function>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With no coverage file: **either produce one by running the project's own tests, or mark the criterion "not evaluable"** — `coverage` tells you explicitly with exit code `2` that it "could not measure"; that is not "not covered".
|
|
116
|
+
|
|
117
|
+
> ⚠️ This producer can **only refute, never confirm**. When it says "never invoked", that is trustworthy;
|
|
118
|
+
> when it says "invoked N times", it **cannot** imply "the related call sites were executed".
|
|
119
|
+
> See `fact-contract.md`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## B.4 Blast radius
|
|
124
|
+
|
|
125
|
+
Count how many callers each modified function has:
|
|
126
|
+
|
|
127
|
+
| Number of callers | Tier |
|
|
128
|
+
|---|---|
|
|
129
|
+
| 1–5 | low |
|
|
130
|
+
| 6–20 | medium |
|
|
131
|
+
| 21–50 | high |
|
|
132
|
+
| 50+ | extreme |
|
|
133
|
+
|
|
134
|
+
**Priority is set by "risk × radius" together**:
|
|
135
|
+
|
|
136
|
+
| | radius extreme | radius high/medium | radius low |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| **risk high** | P0 — deep analysis + all dependencies | P1 | P2 |
|
|
139
|
+
| risk medium/low | P1 | P2 | P3 |
|
|
140
|
+
|
|
141
|
+
Numbers must come from tooling, not from eyeballing. Optional upstream producers: see the ecosystem division-of-labor section in `../SKILL.md`.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## B.5 High-risk changes require adversarial modeling
|
|
146
|
+
|
|
147
|
+
For every high-risk change, answer:
|
|
148
|
+
|
|
149
|
+
**1. Who is the attacker**
|
|
150
|
+
|
|
151
|
+
| Dimension | Values |
|
|
152
|
+
|---|---|
|
|
153
|
+
| WHO | unauthenticated external user / authenticated ordinary user / malicious admin / compromised dependency / race winner |
|
|
154
|
+
| WHAT | can only use public interfaces / needs an authenticated role / needs specific privileges |
|
|
155
|
+
| WHERE | the specific endpoint or function |
|
|
156
|
+
|
|
157
|
+
**2. Attack path**: entry point → concrete calls and arguments → how the vulnerable code is reached → what happens inside the code → what is achieved.
|
|
158
|
+
**A reachability proof is mandatory**: the function really is publicly reachable, the attacker really has that privilege, and the path really goes through a real interface.
|
|
159
|
+
|
|
160
|
+
**3. Exploitability tiers**
|
|
161
|
+
|
|
162
|
+
| Tier | Condition |
|
|
163
|
+
|---|---|
|
|
164
|
+
| easy | public interface, no special privilege, single call |
|
|
165
|
+
| medium | needs a specific condition or elevated privilege, multi-step |
|
|
166
|
+
| hard | needs privileges or rare conditions, needs significant resources |
|
|
167
|
+
|
|
168
|
+
**4. Full exploitation scenario**: starting point → step by step (commands and arguments / why it works / how the system state changes) → **a concrete, testable impact**.
|
|
169
|
+
|
|
170
|
+
> Impact must **never** be written as "may cause problems". It must land on exact data, specific privileges, concrete quantities.
|
|
171
|
+
> If you cannot write it, you usually have not chased the impact far enough.
|
|
172
|
+
|
|
173
|
+
**5. Cross-check against the baseline**
|
|
174
|
+
|
|
175
|
+
- Does it violate a system-level invariant recorded in B.0?
|
|
176
|
+
- Does it cross or break a trust boundary?
|
|
177
|
+
- Does it bypass this codebase's habitual validation patterns?
|
|
178
|
+
- Is it a regression of a previously fixed issue?
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## B.6 Report
|
|
183
|
+
|
|
184
|
+
**Output to chat only counts as failure.** It must be written to disk, or the findings are lost.
|
|
185
|
+
|
|
186
|
+
The report must cover at least:
|
|
187
|
+
|
|
188
|
+
| Section | Content |
|
|
189
|
+
|---|---|
|
|
190
|
+
| conclusion summary | severity distribution + overall judgment + key metrics |
|
|
191
|
+
| change overview | what changed, how big, which surfaces it touches |
|
|
192
|
+
| findings | each with location, origin commit, blast radius, test coverage, historical context, attack scenario |
|
|
193
|
+
| test coverage analysis | where the gaps are, why they count as gaps |
|
|
194
|
+
| blast radius analysis | the numbers and tiers |
|
|
195
|
+
| historical context | where deleted code came from, whether it was a security fix, whether there is a regression |
|
|
196
|
+
| recommendations | three tiers: block immediately / before production / technical debt |
|
|
197
|
+
| analysis method | which strategy was used, **what the limitations are**, confidence |
|
|
198
|
+
| appendix | evidence |
|
|
199
|
+
|
|
200
|
+
> **The "limitations" section is not a formality.** What was not tested and why must be written clearly —
|
|
201
|
+
> that is what lets the reader judge how far this report's conclusions reach.
|
|
202
|
+
|
|
203
|
+
**Scenarios where this stage does not apply** (say so explicitly, do not force it): brand-new code (no baseline to compare against), pure documentation changes, formatting/lint, and when the requester explicitly wants only a quick summary and accepts the risk.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## B.7 Handoff to Stage C
|
|
208
|
+
|
|
209
|
+
**Every suspected finding this stage reports must go through Stage C verification one by one — none may be written into the report as a conclusion directly.**
|
|
210
|
+
|
|
211
|
+
Stage B produces "suspicions"; only Stage C produces "verdicts".
|
|
212
|
+
Treating a suspicion as a conclusion is exactly the problem this project exists to solve.
|