tamperward 2.29.11 → 2.29.13
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 +173 -676
- package/dist/cli/index.js +144 -42
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3663,32 +3663,57 @@ function kindOfType(type, resolveName) {
|
|
|
3663
3663
|
return null;
|
|
3664
3664
|
}
|
|
3665
3665
|
function buildAliasMap(sf) {
|
|
3666
|
-
const
|
|
3666
|
+
const bindings = [];
|
|
3667
|
+
const shadow = (name, scope) => {
|
|
3668
|
+
if (scope) bindings.push({ kind: "shadow", name, scope });
|
|
3669
|
+
};
|
|
3667
3670
|
const collect2 = (node) => {
|
|
3668
|
-
if (ts.isTypeAliasDeclaration(node)
|
|
3671
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
3672
|
+
if (!node.typeParameters || node.typeParameters.length === 0) {
|
|
3673
|
+
bindings.push({ kind: "alias", name: node.name.text, scope: node.parent, type: node.type });
|
|
3674
|
+
} else {
|
|
3675
|
+
shadow(node.name.text, node.parent);
|
|
3676
|
+
}
|
|
3677
|
+
} else if (ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) {
|
|
3678
|
+
shadow(node.name.text, node.parent);
|
|
3679
|
+
} else if ((ts.isClassDeclaration(node) || ts.isModuleDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
|
|
3680
|
+
shadow(node.name.text, node.parent);
|
|
3681
|
+
} else if (ts.isTypeParameterDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
3682
|
+
shadow(node.name.text, node.parent);
|
|
3683
|
+
}
|
|
3669
3684
|
ts.forEachChild(node, collect2);
|
|
3670
3685
|
};
|
|
3671
3686
|
collect2(sf);
|
|
3672
|
-
const
|
|
3687
|
+
const kindOfBinding = (b, seen) => b.type ? kindOfType(b.type, (n) => resolveLexical(n, b.type, seen)) : null;
|
|
3688
|
+
const resolveLexical = (name, at, seen) => {
|
|
3673
3689
|
if (seen.has(name)) return null;
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3690
|
+
for (let n = at; n; n = n.parent) {
|
|
3691
|
+
const here = bindings.filter((x) => x.name === name && x.scope === n);
|
|
3692
|
+
if (here.length === 0) continue;
|
|
3693
|
+
if (here.some((b) => b.kind !== "alias" || !b.type)) return null;
|
|
3694
|
+
return kindOfBinding(here[0], new Set(seen).add(name));
|
|
3695
|
+
}
|
|
3696
|
+
return null;
|
|
3697
|
+
};
|
|
3698
|
+
const resolveFlat = (name, seen) => {
|
|
3699
|
+
if (seen.has(name)) return null;
|
|
3700
|
+
if (bindings.some((b) => b.name === name && (b.kind !== "alias" || !b.type))) return null;
|
|
3701
|
+
const aliases = bindings.filter((b) => b.kind === "alias" && b.name === name);
|
|
3702
|
+
if (aliases.length === 0) return null;
|
|
3703
|
+
const kinds = new Set(aliases.map((b) => kindOfBinding(b, new Set(seen).add(name))));
|
|
3704
|
+
return kinds.size === 1 ? [...kinds][0] ?? null : null;
|
|
3705
|
+
};
|
|
3706
|
+
return {
|
|
3707
|
+
resolve: (name, at) => at && at.getSourceFile() === sf ? resolveLexical(name, at, /* @__PURE__ */ new Set()) : resolveFlat(name, /* @__PURE__ */ new Set())
|
|
3677
3708
|
};
|
|
3678
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
3679
|
-
for (const name of decls.keys()) {
|
|
3680
|
-
const k = resolve17(name, /* @__PURE__ */ new Set());
|
|
3681
|
-
if (k) resolved.set(name, k);
|
|
3682
|
-
}
|
|
3683
|
-
return resolved;
|
|
3684
3709
|
}
|
|
3685
|
-
function assertedLaunderKind(node,
|
|
3686
|
-
return kindOfType(assertedType(node), (n) =>
|
|
3710
|
+
function assertedLaunderKind(node, aliases) {
|
|
3711
|
+
return kindOfType(assertedType(node), (n) => aliases.resolve(n, node));
|
|
3687
3712
|
}
|
|
3688
|
-
function isDoubleCast(node,
|
|
3713
|
+
function isDoubleCast(node, aliases) {
|
|
3689
3714
|
const inner = unparenthesized(node.expression);
|
|
3690
3715
|
if (!(ts.isAsExpression(inner) || ts.isTypeAssertionExpression(inner))) return false;
|
|
3691
|
-
const k = assertedLaunderKind(inner,
|
|
3716
|
+
const k = assertedLaunderKind(inner, aliases ?? EMPTY_ALIASES);
|
|
3692
3717
|
return k != null && DOUBLE_CAST_INNER.has(k);
|
|
3693
3718
|
}
|
|
3694
3719
|
function surfaceOf(path, src) {
|
|
@@ -3767,7 +3792,7 @@ var init_ts_cast_growth = __esm({
|
|
|
3767
3792
|
GENERATED_HEADER = /@generated\b|\bAUTO-?GENERATED\b|\bDO NOT EDIT\b/i;
|
|
3768
3793
|
HEADER_LINES = 20;
|
|
3769
3794
|
DOUBLE_CAST_INNER = /* @__PURE__ */ new Set(["any", "never", "unknown", "empty", "objectish"]);
|
|
3770
|
-
EMPTY_ALIASES =
|
|
3795
|
+
EMPTY_ALIASES = { resolve: () => null };
|
|
3771
3796
|
tsCastGrowth = {
|
|
3772
3797
|
id: RULE2,
|
|
3773
3798
|
surface: ["file"],
|
|
@@ -3895,7 +3920,7 @@ function lineHasRow4Cast(line, aliasMap) {
|
|
|
3895
3920
|
function aliasMapForAddedLines(c) {
|
|
3896
3921
|
const snippet = addedLines(c).map((l) => l.content).join("\n");
|
|
3897
3922
|
const sf = parseSource("lines.ts", snippet);
|
|
3898
|
-
return sf ? buildAliasMap(sf) :
|
|
3923
|
+
return sf ? buildAliasMap(sf) : EMPTY_ALIASES;
|
|
3899
3924
|
}
|
|
3900
3925
|
var BLOCK_RULE, WARN_RULE, JSDOC_ANY_CAST, JS_FILE, countMatches, DIRECTIVE_START, NARROW_LINE, BROAD_LINE, FRAGMENT_DIRECTIVE, BLOCK_REMEDIATION, WARN_REMEDIATION, tsAnyCast;
|
|
3901
3926
|
var init_ts_any_cast = __esm({
|
|
@@ -15720,6 +15745,21 @@ function renderStageDiagnostics(out3, stage, result) {
|
|
|
15720
15745
|
if (!result.diagnostics) return;
|
|
15721
15746
|
for (const line of diagnosticLines(stage, result.diagnostics)) out3(line);
|
|
15722
15747
|
}
|
|
15748
|
+
function verifyVerdictLine(verdict2, ctx) {
|
|
15749
|
+
const base10 = ctx.base.slice(0, 10);
|
|
15750
|
+
switch (verdict2) {
|
|
15751
|
+
case "VERIFIED":
|
|
15752
|
+
return `verified: the suite passes, and still passes with all ${ctx.restored} protected files restored from ${base10}.`;
|
|
15753
|
+
case "MASKED_FAILURE":
|
|
15754
|
+
return `MASKED FAILURE: the visible suite passes, but with the ${ctx.restored} protected files restored from ${base10} it FAILS (exit ${ctx.pristineExit}). Something weakened the checks; the code does not pass the original suite.`;
|
|
15755
|
+
case "SUITE_RED":
|
|
15756
|
+
return `suite red: the visible suite fails (exit ${ctx.visibleExit}) \u2014 fix the code first (pristine exit ${ctx.pristineExit}).`;
|
|
15757
|
+
case "BUDGET_EXCEEDED":
|
|
15758
|
+
return `budget exceeded (${ctx.budget}s): could not verify \u2014 failing closed, not open.`;
|
|
15759
|
+
default:
|
|
15760
|
+
return verdict2;
|
|
15761
|
+
}
|
|
15762
|
+
}
|
|
15723
15763
|
function runVerify(opts) {
|
|
15724
15764
|
const cwd = opts.cwd ?? process.cwd();
|
|
15725
15765
|
const out3 = opts.silent ? (_s) => {
|
|
@@ -16046,13 +16086,15 @@ function runVerify(opts) {
|
|
|
16046
16086
|
})
|
|
16047
16087
|
);
|
|
16048
16088
|
} else {
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
16089
|
+
out3(
|
|
16090
|
+
`tamperward verify \u2014 ${verifyVerdictLine(verdict2, {
|
|
16091
|
+
restored: restored.length,
|
|
16092
|
+
base,
|
|
16093
|
+
visibleExit: visible.exit,
|
|
16094
|
+
pristineExit: pristine.exit,
|
|
16095
|
+
budget
|
|
16096
|
+
})}`
|
|
16097
|
+
);
|
|
16056
16098
|
out3(`verifier backend: ${verifierBackendSummary(verifierBackend)}`);
|
|
16057
16099
|
out3(
|
|
16058
16100
|
"oracle assurance: suite-exit-only (candidate source executes inside the suite process; execution-domain isolation is not semantic/oracle isolation)"
|
|
@@ -18195,18 +18237,69 @@ var init_trace_verify = __esm({
|
|
|
18195
18237
|
});
|
|
18196
18238
|
|
|
18197
18239
|
// src/runtimes.ts
|
|
18198
|
-
import { existsSync as existsSync17 } from "node:fs";
|
|
18240
|
+
import { existsSync as existsSync17, readFileSync as readFileSync21 } from "node:fs";
|
|
18199
18241
|
import { join as join23 } from "node:path";
|
|
18242
|
+
function isRecord4(x) {
|
|
18243
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
18244
|
+
}
|
|
18245
|
+
function hooksBeyondTamperward(hooks) {
|
|
18246
|
+
if (!isRecord4(hooks)) return hooks != null;
|
|
18247
|
+
for (const entries of Object.values(hooks)) {
|
|
18248
|
+
if (!Array.isArray(entries)) {
|
|
18249
|
+
if (entries != null) return true;
|
|
18250
|
+
continue;
|
|
18251
|
+
}
|
|
18252
|
+
for (const entry of entries) {
|
|
18253
|
+
const hs = isRecord4(entry) ? entry.hooks : null;
|
|
18254
|
+
if (!Array.isArray(hs)) {
|
|
18255
|
+
if (hs != null) return true;
|
|
18256
|
+
continue;
|
|
18257
|
+
}
|
|
18258
|
+
for (const h of hs) {
|
|
18259
|
+
const cmd = isRecord4(h) ? String(h.command ?? "") : "";
|
|
18260
|
+
if (!/tamperward/.test(cmd)) return true;
|
|
18261
|
+
}
|
|
18262
|
+
}
|
|
18263
|
+
}
|
|
18264
|
+
return false;
|
|
18265
|
+
}
|
|
18266
|
+
function claudeSettingsBeyondTamperward(cwd) {
|
|
18267
|
+
const path = join23(cwd, ".claude", "settings.json");
|
|
18268
|
+
let raw;
|
|
18269
|
+
try {
|
|
18270
|
+
if (!existsSync17(path)) return false;
|
|
18271
|
+
raw = readFileSync21(path, "utf8");
|
|
18272
|
+
} catch {
|
|
18273
|
+
return false;
|
|
18274
|
+
}
|
|
18275
|
+
let parsed;
|
|
18276
|
+
try {
|
|
18277
|
+
parsed = JSON.parse(raw);
|
|
18278
|
+
} catch {
|
|
18279
|
+
return raw.trim().length > 0;
|
|
18280
|
+
}
|
|
18281
|
+
if (!isRecord4(parsed)) return raw.trim().length > 0;
|
|
18282
|
+
for (const [key2, value] of Object.entries(parsed)) {
|
|
18283
|
+
if (key2 === "disableAllHooks") continue;
|
|
18284
|
+
if (key2 === "hooks") {
|
|
18285
|
+
if (hooksBeyondTamperward(value)) return true;
|
|
18286
|
+
continue;
|
|
18287
|
+
}
|
|
18288
|
+
return true;
|
|
18289
|
+
}
|
|
18290
|
+
return false;
|
|
18291
|
+
}
|
|
18200
18292
|
function detectRuntimes(cwd) {
|
|
18201
18293
|
const found = [];
|
|
18202
18294
|
for (const rt of KNOWN_RUNTIMES) {
|
|
18203
|
-
const
|
|
18295
|
+
const marker = rt.markers.find((m) => {
|
|
18204
18296
|
try {
|
|
18205
18297
|
return existsSync17(join23(cwd, m));
|
|
18206
18298
|
} catch {
|
|
18207
18299
|
return false;
|
|
18208
18300
|
}
|
|
18209
18301
|
});
|
|
18302
|
+
const matchedMarker = marker ?? rt.detectExtra?.(cwd) ?? void 0;
|
|
18210
18303
|
if (matchedMarker !== void 0) found.push({ ...rt, matchedMarker });
|
|
18211
18304
|
}
|
|
18212
18305
|
return found;
|
|
@@ -18226,10 +18319,14 @@ function detectionHeadline(detected) {
|
|
|
18226
18319
|
return `Detected ${detected.length} agent runtimes: ${parts.join(", ")}.`;
|
|
18227
18320
|
}
|
|
18228
18321
|
function neutralOnlyCaveat(detected) {
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
const
|
|
18232
|
-
|
|
18322
|
+
const neutral = detected.filter((r) => r.steering === "neutral");
|
|
18323
|
+
if (neutral.length === 0) return null;
|
|
18324
|
+
const names = neutral.map((r) => r.label).join(", ");
|
|
18325
|
+
const tracking = [...new Set(neutral.map((r) => r.adapterTracking).filter(Boolean))].join(", ");
|
|
18326
|
+
const claudeAlso = hasInLoopRuntime(detected);
|
|
18327
|
+
const lead = claudeAlso ? `In-loop steering (deny-before-execute) ships for Claude Code only today: Claude Code here gets it, but on ${names} your live protection is ` : `In-loop steering (deny-before-execute) ships for Claude Code only today, so on ${names} your live protection is `;
|
|
18328
|
+
const tail = claudeAlso ? "" : " The Claude hook wiring is still written and is inert unless Claude Code runs in this repository.";
|
|
18329
|
+
return `${lead}the agent-neutral layers: pre-commit and CI.${tail}${tracking ? ` A native in-loop adapter is tracked in ${tracking}.` : ""}`;
|
|
18233
18330
|
}
|
|
18234
18331
|
var KNOWN_RUNTIMES;
|
|
18235
18332
|
var init_runtimes = __esm({
|
|
@@ -18238,8 +18335,13 @@ var init_runtimes = __esm({
|
|
|
18238
18335
|
KNOWN_RUNTIMES = [
|
|
18239
18336
|
{
|
|
18240
18337
|
id: "claude-code",
|
|
18338
|
+
// Markers are Claude Code files `init` never writes, so detection never mistakes
|
|
18339
|
+
// TamperWard's own generated `.claude/settings.json` for evidence a human runs
|
|
18340
|
+
// Claude here (#526). `detectExtra` looks at that generated file's CONTENT for
|
|
18341
|
+
// real use (configuration beyond the two hooks init merges in).
|
|
18241
18342
|
label: "Claude Code",
|
|
18242
|
-
markers: [".claude/settings.json", ".claude"],
|
|
18343
|
+
markers: ["CLAUDE.md", ".claude/settings.local.json", ".claude/commands", ".claude/agents"],
|
|
18344
|
+
detectExtra: (cwd) => claudeSettingsBeyondTamperward(cwd) ? ".claude/settings.json" : null,
|
|
18243
18345
|
steering: "in-loop",
|
|
18244
18346
|
note: "native in-loop steering (PreToolUse deny + Stop sweep) plus the agent-neutral layers."
|
|
18245
18347
|
},
|
|
@@ -18273,7 +18375,7 @@ var init_runtimes = __esm({
|
|
|
18273
18375
|
|
|
18274
18376
|
// src/cli/onboard.ts
|
|
18275
18377
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
18276
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as
|
|
18378
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync22, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
|
|
18277
18379
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
18278
18380
|
import { join as join24, relative as relative7, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18279
18381
|
import { createInterface } from "node:readline";
|
|
@@ -18349,7 +18451,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
18349
18451
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
18350
18452
|
}
|
|
18351
18453
|
const mode = existingMode(path, 420);
|
|
18352
|
-
const original = kind === "file" ?
|
|
18454
|
+
const original = kind === "file" ? readFileSync22(path, "utf8") : null;
|
|
18353
18455
|
let next;
|
|
18354
18456
|
try {
|
|
18355
18457
|
const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
|
|
@@ -18950,7 +19052,7 @@ var init_capture = __esm({
|
|
|
18950
19052
|
|
|
18951
19053
|
// src/research/manifest.ts
|
|
18952
19054
|
import { createHash as createHash13 } from "node:crypto";
|
|
18953
|
-
import { readFileSync as
|
|
19055
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
18954
19056
|
import { dirname as dirname10, resolve as resolve14 } from "node:path";
|
|
18955
19057
|
function taskFrom(raw, index, manifestDir) {
|
|
18956
19058
|
const where = `task #${index + 1}`;
|
|
@@ -18984,7 +19086,7 @@ function readManifest(path) {
|
|
|
18984
19086
|
const abs = resolve14(path);
|
|
18985
19087
|
let bytes;
|
|
18986
19088
|
try {
|
|
18987
|
-
bytes =
|
|
19089
|
+
bytes = readFileSync23(abs);
|
|
18988
19090
|
} catch (e) {
|
|
18989
19091
|
throw new ResearchError(`cannot read task manifest ${abs}: ${e instanceof Error ? e.message : String(e)}`);
|
|
18990
19092
|
}
|
|
@@ -19196,7 +19298,7 @@ var init_record = __esm({
|
|
|
19196
19298
|
|
|
19197
19299
|
// src/research/run.ts
|
|
19198
19300
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
19199
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as
|
|
19301
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync24, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
19200
19302
|
import { join as join25, resolve as resolve15 } from "node:path";
|
|
19201
19303
|
function git6(args, cwd) {
|
|
19202
19304
|
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
@@ -19207,7 +19309,7 @@ function pairRecordPath(ledger, task, pair) {
|
|
|
19207
19309
|
function resumableRecord(path, expected) {
|
|
19208
19310
|
let raw;
|
|
19209
19311
|
try {
|
|
19210
|
-
raw = JSON.parse(
|
|
19312
|
+
raw = JSON.parse(readFileSync24(path, "utf8"));
|
|
19211
19313
|
} catch (e) {
|
|
19212
19314
|
throw new ResearchError(`ledger record ${path} is malformed (${errorMessage(e).split("\n")[0]}); remove it to regenerate the pair, or use a new --out`);
|
|
19213
19315
|
}
|
|
@@ -19630,7 +19732,7 @@ var init_run2 = __esm({
|
|
|
19630
19732
|
});
|
|
19631
19733
|
|
|
19632
19734
|
// src/research/summarize.ts
|
|
19633
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
19735
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync25 } from "node:fs";
|
|
19634
19736
|
import { join as join26, resolve as resolve16 } from "node:path";
|
|
19635
19737
|
function readLedger2(dir) {
|
|
19636
19738
|
const pairsDir = join26(resolve16(dir), "pairs");
|
|
@@ -19645,7 +19747,7 @@ function readLedger2(dir) {
|
|
|
19645
19747
|
const path = join26(pairsDir, n);
|
|
19646
19748
|
let raw;
|
|
19647
19749
|
try {
|
|
19648
|
-
raw = JSON.parse(
|
|
19750
|
+
raw = JSON.parse(readFileSync25(path, "utf8"));
|
|
19649
19751
|
} catch (e) {
|
|
19650
19752
|
throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
|
|
19651
19753
|
}
|
|
@@ -20352,7 +20454,7 @@ var init_main = __esm({
|
|
|
20352
20454
|
// src/cli/index.ts
|
|
20353
20455
|
init_hook_client();
|
|
20354
20456
|
init_exit();
|
|
20355
|
-
import { readFileSync as
|
|
20457
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
20356
20458
|
function loadMain() {
|
|
20357
20459
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
20358
20460
|
}
|
|
@@ -20365,7 +20467,7 @@ async function launch(argv) {
|
|
|
20365
20467
|
if (kind && hookServiceEnabled()) {
|
|
20366
20468
|
let raw = null;
|
|
20367
20469
|
try {
|
|
20368
|
-
raw =
|
|
20470
|
+
raw = readFileSync26(0, "utf8");
|
|
20369
20471
|
} catch {
|
|
20370
20472
|
raw = null;
|
|
20371
20473
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.13",
|
|
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",
|