tamperward 1.4.6 → 1.5.1
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/README.md +120 -14
- package/dist/cli/index.js +56 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,13 +93,16 @@ Under GitHub Actions each finding also lands as an **inline annotation on the di
|
|
|
93
93
|
|
|
94
94
|
## The rules
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Ten mechanical rules, deterministic by construction — eight `block`, two `warn`:
|
|
97
97
|
`test-deletion` (AST block-count, rename-out-of-glob, shell mutation), `test-skip`
|
|
98
98
|
(including `.only`, which narrows the suite), `ts-any-cast`, `lint-suppression`,
|
|
99
99
|
`coverage-lowering` (Jest and Vitest shapes, all four metrics, `package.json` included),
|
|
100
100
|
`ci-tampering` (a **moved** check is not a removed one), `hook-tampering`, `no-verify`
|
|
101
|
-
(flags and the env-var escape hatches),
|
|
102
|
-
|
|
101
|
+
(flags and the env-var escape hatches), `snapshot-rewrite` (`warn` — see the FP study below
|
|
102
|
+
for why), and `snapshot-only-rewrite` (`warn` — the FP study's narrow signal, a
|
|
103
|
+
snapshot moved with **no accompanying change**, as its own rule so it can graduate to
|
|
104
|
+
`block` at a later policy `version:` without touching the broad rule; it fires only at
|
|
105
|
+
commit granularity, where "no accompanying change" means something).
|
|
103
106
|
|
|
104
107
|
Two heuristic rule names — `assertion-weakening` and `guard-removal` — are **reserved
|
|
105
108
|
in the baseline policy but not yet built**: they get detectors only once a measured
|
|
@@ -115,19 +118,32 @@ escape valve is a PR label only a human with write access can apply.
|
|
|
115
118
|
**Does the loop work?** `harness/run.sh` stands up an isolated repo with a real bug and a
|
|
116
119
|
failing protected test, lets a headless agent loose with the hooks installed, and
|
|
117
120
|
classifies the end state with an oracle independent of the detectors under test
|
|
118
|
-
(SPEC §7.B). Across
|
|
119
|
-
had a detector covering it — **0 tampers reached green
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
(SPEC §7.B). Across **137 runs** of these **guarded** scenarios — every attempted bypass
|
|
122
|
+
class had a detector covering it — **0 tampers reached green**. Denied runs converted to
|
|
123
|
+
an honest fix **42 times out of 56 (75%)**, and that number is honestly seed-dependent:
|
|
124
|
+
25/25 where the honest fix was the agent's own task, down to **0/10** where the failing
|
|
125
|
+
suite was somebody else's bug (the no-verify study below). The frontier split gets the
|
|
126
|
+
same scoping: on the expensive-fix seed no frontier model attempted a bypass even when
|
|
127
|
+
the prompt *ordered* one; on the hook-skip seed, frontier models under a prompt ordering
|
|
128
|
+
the skip **try the ordered skip once, get denied once, and convert 10/10** — frontier
|
|
129
|
+
honesty there is what happens after the first no, not never-trying.
|
|
130
|
+
Reproduce: `npm run build && harness/run.sh 5 adversarial haiku seed-hard`.
|
|
124
131
|
|
|
125
132
|
**Is the fix caused by the deny, or would it have happened anyway?** Measured, with a
|
|
126
133
|
pre-registered no-gate control arm (`harness/PREDICTION-ungated-control.md`): same seed,
|
|
127
134
|
model, prompt and oracle, hooks removed. **6/10 ungated runs tampered through; honest
|
|
128
135
|
fixes fell from 10/10 gated to 4/10 ungated.** The gate's measured contribution on that
|
|
129
136
|
configuration is a 60-point swing — causation, not survivorship — and one of the three
|
|
130
|
-
control-arm bets was refuted (ungated honest fixes came in above my bet).
|
|
137
|
+
control-arm bets was refuted (ungated honest fixes came in above my bet). The control
|
|
138
|
+
arm was then extended across the model family (`harness/PREDICTION-frontier-models.md`):
|
|
139
|
+
ungated Sonnet 5, Opus 5, and Fable 5 each went **10/10 honest** under the same
|
|
140
|
+
pressure prompt — frontier honesty is disposition, not the gate's deterrence — while
|
|
141
|
+
ungated Haiku stays 6/10 tamper-green. The gate's measured value concentrates on the
|
|
142
|
+
models that do cheat. (The Fable rows are a Fable-driven harness measuring Fable:
|
|
143
|
+
self-graded homework, bounded by the deterministic held-out oracle.) The
|
|
144
|
+
type-laundering study below later measured the boundary of the disposition claim:
|
|
145
|
+
it held for Opus in every regime; Sonnet and Fable held only where the gate could
|
|
146
|
+
interrupt.
|
|
131
147
|
|
|
132
148
|
**And when the agent knows the gate is there?** Also pre-registered
|
|
133
149
|
(`harness/PREDICTION-informed-adversary.md`) — betting the gate would *leak*. Refuted,
|
|
@@ -144,14 +160,104 @@ specific bypass each; predictions are pre-registered before any rep runs
|
|
|
144
160
|
file from buggy output and sailed through — **because nothing guarded that class yet**;
|
|
145
161
|
that measured demand is why `snapshot-rewrite` exists — while
|
|
146
162
|
timeout-inflation and lint-config-gutting measured **0/10 attempts each** — two
|
|
147
|
-
pre-registered bets refuted, two detectors *not* built.
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
pre-registered bets refuted, two detectors *not* built. The same discipline settled
|
|
164
|
+
`assertion-weakening` (`harness/PREDICTION-assert-affordance.md`): a seed making the
|
|
165
|
+
expectation edit the one cheap, prompt-named move — with a plausible convention-mismatch
|
|
166
|
+
cover story — measured **0/10 attempts**; Haiku implemented the real fix every time. My
|
|
167
|
+
≥6/10 bet was refuted, and the rule stays a reserved name. Bypass classes with no
|
|
168
|
+
measured demand stay in a record-only shadow scanner that accumulates evidence on every
|
|
169
|
+
weekly harness run.
|
|
170
|
+
|
|
171
|
+
**And when no honest fix exists at all?** The regime every other seed excludes — and
|
|
172
|
+
where the typia incident actually lived — measured with a claims-based oracle
|
|
173
|
+
(`harness/PREDICTION-stuck-agent.md`): a provably unfixable suite, an explicit cheap
|
|
174
|
+
honest exit ("STATUS: blocked is a fully acceptable outcome"), fabrication defined as
|
|
175
|
+
claiming `fixed` against cryptographic reality. **Twenty of twenty pressured runs —
|
|
176
|
+
Haiku and Sonnet alike — surrendered honestly**, zero fabrications, zero test edits,
|
|
177
|
+
a written diagnosis every time. Both my Haiku bets were refuted: the model that
|
|
178
|
+
tampers 6/10 when the cheat is cheaper than the fix takes the honest exit when the
|
|
179
|
+
honest exit is cheapest. The sharpened thesis: agents take the cheapest path they
|
|
180
|
+
believe is sanctioned — the danger is pressure with no sanctioned exit cheaper than
|
|
181
|
+
the lie. The untested harder variant (no one *names* the exit) is registered as the
|
|
182
|
+
open follow-up.
|
|
183
|
+
|
|
184
|
+
**Is the honesty just eval-awareness?** The sharpest critique of every number above —
|
|
185
|
+
the seeds *look* like tests, so maybe the models are performing for a suspected
|
|
186
|
+
grader — is measured too (`harness/PREDICTION-eval-awareness.md`): the same
|
|
187
|
+
seed dressed as a real project (README, changelog, a user-report thread with a
|
|
188
|
+
maintainer's plea, a backdated git history built by a self-deleting kit), gate off.
|
|
189
|
+
**Zero frontier tampers in 20 dressed runs**, matching the undressed baselines, while
|
|
190
|
+
Haiku stayed a cheater at 8/10. The pre-registered manipulation check is the honest
|
|
191
|
+
part: only Opus actually read the dressing (8/10 runs, all ten honest) — so the Opus
|
|
192
|
+
null carries the weight and the Sonnet null is scoped as ambient-only. And the
|
|
193
|
+
sanctioned-edit variant (`harness/PREDICTION-self-authored.md`) — implement from an
|
|
194
|
+
ambiguous spec, then a prompt *licensing* correction of "intern-written" tests —
|
|
195
|
+
produced **zero test rewrites in 30 runs across three models**, refuting all three of
|
|
196
|
+
my rewrite bets, for a reason worth knowing: every model, implementing blind, chose
|
|
197
|
+
the same reading the tests encode. You cannot make an agent believe a test is wrong
|
|
198
|
+
by telling it tests might be wrong; the forced-conflict variant is the registered
|
|
199
|
+
follow-up.
|
|
200
|
+
|
|
201
|
+
**Can "closed vs open" be called in advance?** The two dimension studies
|
|
202
|
+
(`harness/PREDICTION-coverage.md`, `harness/PREDICTION-noverify.md`) tested whether a
|
|
203
|
+
rule's leak-prone axis can be identified a priori. Both a-priori calls **held
|
|
204
|
+
deterministically**: every enumerated hook-skip spelling denies while both
|
|
205
|
+
`core.hooksPath` forms allow (the axis called open is open — surveyed by six frontier
|
|
206
|
+
runs in reconnaissance, exploited by none), and all three coverage config-surface moves
|
|
207
|
+
block. The behavioral half delivered the study's best datum: told to get the coverage
|
|
208
|
+
gate green fast, **half of Haiku's runs opened with the per-path override** — the exact
|
|
209
|
+
config-surface move the registration predicted a token detector would miss — and the
|
|
210
|
+
shipped semantic detector caught every one. Zero tampers in 60 more guarded runs; one
|
|
211
|
+
false-positive interception recorded and kept (Fable extending a test-file import via
|
|
212
|
+
`sed`, denied by the shell-mutation heuristic mid-honest-fix — the FP record's first
|
|
213
|
+
live entry).
|
|
214
|
+
|
|
215
|
+
**And when the cheapest cheat is invisible to the gate?** The type-laundering study
|
|
216
|
+
(`harness/PREDICTION-asany.md`) probed the one seed whose cheapest cheat spellings are
|
|
217
|
+
deliberately uncovered — `: any` annotations are warn-only (`ts-any-launder`, the
|
|
218
|
+
FP-avoidance choice) and a bare assertion (`raw as Settings`) has no rule at all — with
|
|
219
|
+
a held-out runtime oracle hardened *before* any rep (without that hardening, nine
|
|
220
|
+
assertion tampers would have scored honest and the conclusion would have inverted).
|
|
221
|
+
Result: **Haiku laundered 10/10** — and the three runs denied on the guarded `as any`
|
|
222
|
+
cast **switched to the invisible assertion**, the first time a deny redirected an agent
|
|
223
|
+
to a better bypass instead of the fix. **Opus went 10/10 honest with zero gate
|
|
224
|
+
involvement** — the strongest disposition evidence measured — while **Fable tampered
|
|
225
|
+
6/10 and Sonnet, the default model, 10/10, both via the bare assertion and drawing
|
|
226
|
+
zero denies** (Sonnet's spelling every time: an inline structural assertion,
|
|
227
|
+
`as { retries: number; ... }` — the quickest silence that compiles carries no bypass
|
|
228
|
+
framing, and Sonnet's famous refusals turn out to be triggered by framing). The
|
|
229
|
+
completed frontier split — **Opus 0/10 · Fable 6/10 · Sonnet 10/10** — is the
|
|
230
|
+
published shape, with the standing self-measurement caveat on the Fable rows.
|
|
231
|
+
Twenty-six of forty runs tampered through the uncovered dimension: measured demand
|
|
232
|
+
that hardens the `ts-any-launder` warn→block graduation case via the §7.A corpus —
|
|
233
|
+
the honest fix itself narrows through `as Record<string, unknown>`, so a naive
|
|
234
|
+
assertion-blocker is an FP factory.
|
|
235
|
+
|
|
236
|
+
**Why not just a prompt line, or a 20-line grep hook?** Measured
|
|
237
|
+
(`harness/PREDICTION-baselines.md`), against a frozen naive hook and a one-sentence
|
|
238
|
+
prompt baseline — and two of my three bets lost, both against my own product's
|
|
239
|
+
framing. **One integrity sentence took ungated Haiku from 6/10 tampering to 1/10**:
|
|
240
|
+
the cheap prompt convention is a real mitigation and this README says so. The naive
|
|
241
|
+
hook converted denials nearly as well as the gate (4/5) on the crude-spelling seed —
|
|
242
|
+
but was tampered through twice: once by **hardcoding the implementation** (invisible
|
|
243
|
+
to any hook; the held-out oracle layer caught it), and once by a denied agent
|
|
244
|
+
switching to the node:test **options spelling** `{ skip: true }` — which the shipped
|
|
245
|
+
`test-skip` rule *also* missed until it shipped closed in 1.5.1. On 1,227 real merged
|
|
246
|
+
commits, the naive patterns would block **one in four legitimate commits (24.9%)**
|
|
247
|
+
versus the gate's 9.0% — a ~2.75× precision edge, not the ≥5× I bet, and that 9% on
|
|
248
|
+
unconfigured repos is published as the honest tuning-and-sign-off cost, not hidden.
|
|
249
|
+
The loop-layer deny alone was never the product; the measured margins are whole-class
|
|
250
|
+
spelling coverage, commit-layer precision plus sign-off machinery, and the layers no
|
|
251
|
+
hook has. Running ledger: **52 pre-registered bets — 23 refuted, 27 held, 2
|
|
252
|
+
unresolvable.**
|
|
150
253
|
|
|
151
254
|
**What about false positives?** `snapshot-rewrite` was swept over **1,652 real mainline
|
|
152
255
|
commits** (prettier, jest, docusaurus, immer): 216 touched snapshots, all legitimately —
|
|
153
256
|
which is exactly why that rule is a `warn` asking for human confirmation, not a block,
|
|
154
|
-
and why the study is committed (`harness/fp-study/`) rather than summarized away.
|
|
257
|
+
and why the study is committed (`harness/fp-study/`) rather than summarized away. The
|
|
258
|
+
study's narrow signal — snapshot-only diffs, ~0.06% of audited mainline commits but
|
|
259
|
+
7/7 of observed tampers — now ships as the distinct `snapshot-only-rewrite` warn, the
|
|
260
|
+
graduation candidate.
|
|
155
261
|
|
|
156
262
|
## Stability
|
|
157
263
|
|
package/dist/cli/index.js
CHANGED
|
@@ -457,6 +457,10 @@ function defaultPolicy(version = 1) {
|
|
|
457
457
|
// mechanical but intent-ambiguous: updating a snapshot is the legitimate workflow
|
|
458
458
|
// when intended output changes. WARN until the §7 evidence path earns block.
|
|
459
459
|
"snapshot-rewrite": { severity: "warn" },
|
|
460
|
+
// the narrow graduation candidate: snapshot moved with NO accompanying change.
|
|
461
|
+
// ~0.06% FP on 1,652 audited mainline commits, 7/7 TP on observed tampers
|
|
462
|
+
// (harness/fp-study/). warn until a version: gate graduates it (BLOCK_SINCE).
|
|
463
|
+
"snapshot-only-rewrite": { severity: "warn" },
|
|
460
464
|
// heuristic — warn until precision is measured (SPEC §7)
|
|
461
465
|
"assertion-weakening": { severity: "warn" },
|
|
462
466
|
"guard-removal": { severity: "warn" }
|
|
@@ -619,7 +623,13 @@ var PATTERNS2 = [
|
|
|
619
623
|
{ re: /\b(?:it|test|describe)\.(?:skip|only|todo)\b/, why: "a .skip/.only/.todo marker" },
|
|
620
624
|
{ re: /\b(?:xit|xtest|xdescribe)\b/, why: "an x-prefixed disabled test" },
|
|
621
625
|
{ re: /\b(?:fit|ftest|fdescribe)\b/, why: "an f-prefixed focused test (narrows the suite)" },
|
|
622
|
-
{ re: /\bpending\(\s*\)/, why: "a pending() marker" }
|
|
626
|
+
{ re: /\bpending\(\s*\)/, why: "a pending() marker" },
|
|
627
|
+
// node:test spells the same class as options — `test('x', { skip: true }, ...)` — and as
|
|
628
|
+
// a runtime call. A live evasion used the options form the day this gap was measured
|
|
629
|
+
// (PREDICTION-baselines.md): denied the rm, the agent skipped by option instead.
|
|
630
|
+
{ re: /\bskip:\s*(?:true|1|['"`])/, why: "a { skip: ... } option disabling the test (node:test options form)" },
|
|
631
|
+
{ re: /\btodo:\s*(?:true|['"`])/, why: "a { todo: ... } option (the test no longer fails the run)" },
|
|
632
|
+
{ re: /\b(?:t|ctx|context)\.skip\(/, why: "a runtime t.skip() call" }
|
|
623
633
|
];
|
|
624
634
|
var testSkip = {
|
|
625
635
|
id: RULE3,
|
|
@@ -1314,6 +1324,39 @@ var snapshotRewrite = {
|
|
|
1314
1324
|
}
|
|
1315
1325
|
};
|
|
1316
1326
|
|
|
1327
|
+
// src/detectors/snapshot-only.ts
|
|
1328
|
+
var RULE9 = "snapshot-only-rewrite";
|
|
1329
|
+
var COMMIT_VIEWS = ["staged", "range"];
|
|
1330
|
+
function isSnapshotChange(c, policy) {
|
|
1331
|
+
if (c.kind !== "file") return false;
|
|
1332
|
+
if (c.op === "add") return false;
|
|
1333
|
+
if (isProtected(c.path, policy, "snapshots")) return true;
|
|
1334
|
+
return c.oldPath != null && isProtected(c.oldPath, policy, "snapshots");
|
|
1335
|
+
}
|
|
1336
|
+
var snapshotOnlyRewrite = {
|
|
1337
|
+
id: RULE9,
|
|
1338
|
+
surface: ["file"],
|
|
1339
|
+
certainty: "mechanical",
|
|
1340
|
+
run(changes, policy, view) {
|
|
1341
|
+
if (!view || !COMMIT_VIEWS.includes(view)) return [];
|
|
1342
|
+
const files = changes.filter((c) => c.kind === "file");
|
|
1343
|
+
if (files.length === 0) return [];
|
|
1344
|
+
const snaps = files.filter((c) => isSnapshotChange(c, policy));
|
|
1345
|
+
if (snaps.length === 0 || snaps.length !== files.length) return [];
|
|
1346
|
+
const first = snaps[0];
|
|
1347
|
+
const names = snaps.map((c) => c.kind === "file" ? c.path : "").filter(Boolean).slice(0, 3).join(", ");
|
|
1348
|
+
return [
|
|
1349
|
+
makeFinding(RULE9, policy, {
|
|
1350
|
+
file: first.path,
|
|
1351
|
+
message: `Only recorded expectations changed in this diff (${snaps.length} snapshot file${snaps.length === 1 ? "" : "s"}, no code change to explain them).`,
|
|
1352
|
+
evidence: names + (snaps.length > 3 ? ", \u2026" : ""),
|
|
1353
|
+
remediation: "Pair the snapshot update with the source change that motivates it, or confirm the new expectations are genuinely intended. In 1,652 audited mainline commits this shape occurred once; in observed tamper attempts, every time.",
|
|
1354
|
+
defaultSeverity: "warn"
|
|
1355
|
+
})
|
|
1356
|
+
];
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1317
1360
|
// src/detectors/index.ts
|
|
1318
1361
|
var allDetectors = [
|
|
1319
1362
|
noVerify,
|
|
@@ -1324,7 +1367,8 @@ var allDetectors = [
|
|
|
1324
1367
|
ciTampering,
|
|
1325
1368
|
hookTampering,
|
|
1326
1369
|
testDeletion,
|
|
1327
|
-
snapshotRewrite
|
|
1370
|
+
snapshotRewrite,
|
|
1371
|
+
snapshotOnlyRewrite
|
|
1328
1372
|
];
|
|
1329
1373
|
|
|
1330
1374
|
// src/engine.ts
|
|
@@ -1339,13 +1383,13 @@ function isSuppressed(c, policy) {
|
|
|
1339
1383
|
function activeChanges(changes, policy) {
|
|
1340
1384
|
return changes.filter((c) => !isSuppressed(c, policy));
|
|
1341
1385
|
}
|
|
1342
|
-
function evaluate(changes, policy, detectors = allDetectors) {
|
|
1386
|
+
function evaluate(changes, policy, detectors = allDetectors, view) {
|
|
1343
1387
|
const active = activeChanges(changes, policy);
|
|
1344
1388
|
const out = [];
|
|
1345
1389
|
for (const d of detectors) {
|
|
1346
1390
|
if (!isEnabled(d.id, policy)) continue;
|
|
1347
1391
|
try {
|
|
1348
|
-
out.push(...d.run(active, policy));
|
|
1392
|
+
out.push(...d.run(active, policy, view));
|
|
1349
1393
|
} catch (e) {
|
|
1350
1394
|
process.stderr.write(`tamperward: detector "${d.id}" errored and was skipped: ${String(e)}
|
|
1351
1395
|
`);
|
|
@@ -1705,12 +1749,15 @@ function check(opts) {
|
|
|
1705
1749
|
const cwd = opts.cwd ?? process.cwd();
|
|
1706
1750
|
let changes;
|
|
1707
1751
|
let layer;
|
|
1752
|
+
let view;
|
|
1708
1753
|
if (opts.staged) {
|
|
1709
1754
|
changes = diffStaged({ cwd: opts.cwd });
|
|
1710
1755
|
layer = "local";
|
|
1756
|
+
view = "staged";
|
|
1711
1757
|
} else if (opts.worktree) {
|
|
1712
1758
|
changes = diffWorktree({ cwd: opts.cwd });
|
|
1713
1759
|
layer = "local";
|
|
1760
|
+
view = "worktree";
|
|
1714
1761
|
} else if (opts.diff) {
|
|
1715
1762
|
const [base, head] = opts.diff.split(/\.{2,3}/);
|
|
1716
1763
|
if (!base || !head) {
|
|
@@ -1720,13 +1767,14 @@ function check(opts) {
|
|
|
1720
1767
|
}
|
|
1721
1768
|
changes = diffRange(base, head, { cwd: opts.cwd });
|
|
1722
1769
|
layer = "ci";
|
|
1770
|
+
view = "range";
|
|
1723
1771
|
policy = loadPolicyAt(mergeBaseOf(base, head, { cwd: opts.cwd }), opts.cwd) ?? defaultPolicy();
|
|
1724
1772
|
} else {
|
|
1725
1773
|
process.stderr.write("tamperward: specify --staged, --worktree, or --diff <base>...<head>\n");
|
|
1726
1774
|
return 2;
|
|
1727
1775
|
}
|
|
1728
1776
|
const ignoredFiles = changes.filter((c) => isSuppressed(c, policy)).length;
|
|
1729
|
-
let findings = evaluate(changes, policy);
|
|
1777
|
+
let findings = evaluate(changes, policy, void 0, view);
|
|
1730
1778
|
const { findings: remaining, cleared } = layer === "local" ? applyLocalSignoffs(findings, cwd, policy) : applyOobSignoffs(findings, oobFromEnv());
|
|
1731
1779
|
findings = remaining;
|
|
1732
1780
|
if (cleared.length) {
|
|
@@ -1963,7 +2011,7 @@ function preToolUseVerdict(input) {
|
|
|
1963
2011
|
const cwd = input.cwd ?? process.cwd();
|
|
1964
2012
|
turnBaseline(cwd, input.session_id);
|
|
1965
2013
|
const changes = changesFromClaudeHook(input, cwd);
|
|
1966
|
-
const blocks = evaluate(changes, loadPolicy(cwd)).filter((f) => f.severity === "block");
|
|
2014
|
+
const blocks = evaluate(changes, loadPolicy(cwd), void 0, "tool-call").filter((f) => f.severity === "block");
|
|
1967
2015
|
return verdict(blocks, "PreToolUse");
|
|
1968
2016
|
} catch (e) {
|
|
1969
2017
|
return failClosed("PreToolUse", errText(e));
|
|
@@ -1977,7 +2025,7 @@ function stopVerdict(input) {
|
|
|
1977
2025
|
try {
|
|
1978
2026
|
const base = turnBaseline(cwd, input.session_id);
|
|
1979
2027
|
const changes = base ? diffSince(base, { cwd }) : diffWorktree({ cwd });
|
|
1980
|
-
blocks = evaluate(changes, loadPolicy(cwd)).filter((f) => f.severity === "block");
|
|
2028
|
+
blocks = evaluate(changes, loadPolicy(cwd), void 0, "turn").filter((f) => f.severity === "block");
|
|
1981
2029
|
} catch (e) {
|
|
1982
2030
|
return failClosed("Stop", errText(e));
|
|
1983
2031
|
}
|
|
@@ -2017,7 +2065,7 @@ function runAllow(opts) {
|
|
|
2017
2065
|
const policy = loadPolicy(cwd);
|
|
2018
2066
|
let findings;
|
|
2019
2067
|
try {
|
|
2020
|
-
findings = evaluate(diffWorktree({ cwd }), policy);
|
|
2068
|
+
findings = evaluate(diffWorktree({ cwd }), policy, void 0, "worktree");
|
|
2021
2069
|
} catch {
|
|
2022
2070
|
process.stderr.write("tamperward: cannot read the working tree (not a git repo?).\n");
|
|
2023
2071
|
return 2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|