tamperward 2.23.1 → 2.23.3
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/dist/cli/index.js +116 -36
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -9934,7 +9934,7 @@ var init_verifier_limits = __esm({
|
|
|
9934
9934
|
// src/cli/init.ts
|
|
9935
9935
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
9936
9936
|
import { createHash as createHash6 } from "node:crypto";
|
|
9937
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync2 } from "node:fs";
|
|
9937
|
+
import { existsSync as existsSync10, lstatSync as lstatSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync2 } from "node:fs";
|
|
9938
9938
|
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve3 } from "node:path";
|
|
9939
9939
|
function gitConfig(cwd, key2) {
|
|
9940
9940
|
try {
|
|
@@ -10112,6 +10112,65 @@ function planPolicy(cwd) {
|
|
|
10112
10112
|
apply: () => writeFileSync7(path, POLICY_CONTENT)
|
|
10113
10113
|
};
|
|
10114
10114
|
}
|
|
10115
|
+
function planGitignore(cwd) {
|
|
10116
|
+
const rel = ".gitignore";
|
|
10117
|
+
const path = join14(cwd, rel);
|
|
10118
|
+
if (!existsSync10(join14(cwd, "node_modules"))) return null;
|
|
10119
|
+
let existing = "";
|
|
10120
|
+
if (existsSync10(path)) {
|
|
10121
|
+
let st;
|
|
10122
|
+
try {
|
|
10123
|
+
st = lstatSync7(path);
|
|
10124
|
+
} catch (e) {
|
|
10125
|
+
return { item: "gitignore", path: rel, status: "error", detail: "cannot inspect \u2014 " + errText3(e) };
|
|
10126
|
+
}
|
|
10127
|
+
if (!st.isFile()) {
|
|
10128
|
+
return {
|
|
10129
|
+
item: "gitignore",
|
|
10130
|
+
path: rel,
|
|
10131
|
+
status: "error",
|
|
10132
|
+
detail: st.isSymbolicLink() ? "is a symbolic link \u2014 refusing to follow it" : "exists but is not a regular file \u2014 refusing to write"
|
|
10133
|
+
};
|
|
10134
|
+
}
|
|
10135
|
+
existing = readFileSync12(path, "utf8");
|
|
10136
|
+
}
|
|
10137
|
+
try {
|
|
10138
|
+
const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
|
|
10139
|
+
cwd,
|
|
10140
|
+
encoding: "utf8",
|
|
10141
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10142
|
+
}).trim();
|
|
10143
|
+
if (tracked) {
|
|
10144
|
+
return {
|
|
10145
|
+
item: "gitignore",
|
|
10146
|
+
path: rel,
|
|
10147
|
+
status: "skip",
|
|
10148
|
+
detail: "node_modules is already tracked/staged \u2014 left unchanged; TamperWard will scan it as repository content"
|
|
10149
|
+
};
|
|
10150
|
+
}
|
|
10151
|
+
} catch {
|
|
10152
|
+
}
|
|
10153
|
+
const meaningful = existing.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
|
|
10154
|
+
if (meaningful.some((line) => line.startsWith("!") && line.includes("node_modules"))) {
|
|
10155
|
+
return {
|
|
10156
|
+
item: "gitignore",
|
|
10157
|
+
path: rel,
|
|
10158
|
+
status: "skip",
|
|
10159
|
+
detail: "contains an explicit node_modules negation \u2014 left untouched; review the repository ignore policy before committing dependencies"
|
|
10160
|
+
};
|
|
10161
|
+
}
|
|
10162
|
+
if (meaningful.some((line) => /^(?:\/|\*\*\/)?node_modules\/?$/.test(line))) {
|
|
10163
|
+
return { item: "gitignore", path: rel, status: "ok", detail: "node_modules is already ignored by the repository" };
|
|
10164
|
+
}
|
|
10165
|
+
const next = existing.length === 0 ? "node_modules/\n" : existing.replace(/\n?$/, "\n") + "node_modules/\n";
|
|
10166
|
+
return {
|
|
10167
|
+
item: "gitignore",
|
|
10168
|
+
path: rel,
|
|
10169
|
+
status: existsSync10(path) ? "update" : "create",
|
|
10170
|
+
detail: "exclude installed Node dependencies from repository diffs",
|
|
10171
|
+
apply: () => writeFileSync7(path, next)
|
|
10172
|
+
};
|
|
10173
|
+
}
|
|
10115
10174
|
function planClaudeHooks(cwd) {
|
|
10116
10175
|
const rel = ".claude/settings.json";
|
|
10117
10176
|
const path = join14(cwd, rel);
|
|
@@ -10425,8 +10484,10 @@ function planned(item, path, plan) {
|
|
|
10425
10484
|
}
|
|
10426
10485
|
function planInit(cwd, opts = {}) {
|
|
10427
10486
|
requireShippedVersion();
|
|
10487
|
+
const gitignore = planGitignore(cwd);
|
|
10428
10488
|
return [
|
|
10429
10489
|
planned("policy", POLICY_FILE, () => planPolicy(cwd)),
|
|
10490
|
+
...gitignore ? [gitignore] : [],
|
|
10430
10491
|
planned("agent", ".claude/settings.json", () => planClaudeHooks(cwd)),
|
|
10431
10492
|
planned("pre-commit", "(hooks)", () => planPreCommit(cwd)),
|
|
10432
10493
|
planned("ci", ".github/workflows/tamperward.yml", () => planWorkflow(cwd, opts.forceWorkflow ?? false)),
|
|
@@ -10660,7 +10721,7 @@ var init_fingerprint = __esm({
|
|
|
10660
10721
|
import { createHash as createHash8 } from "node:crypto";
|
|
10661
10722
|
import {
|
|
10662
10723
|
existsSync as existsSync11,
|
|
10663
|
-
lstatSync as
|
|
10724
|
+
lstatSync as lstatSync8,
|
|
10664
10725
|
readdirSync as readdirSync3,
|
|
10665
10726
|
readFileSync as readFileSync13,
|
|
10666
10727
|
readlinkSync as readlinkSync2,
|
|
@@ -10685,7 +10746,7 @@ function inside(root, candidate) {
|
|
|
10685
10746
|
return rel === "" || rel !== ".." && !rel.startsWith(".." + sep2) && !isAbsolute9(rel);
|
|
10686
10747
|
}
|
|
10687
10748
|
function modeKind(path) {
|
|
10688
|
-
const st =
|
|
10749
|
+
const st = lstatSync8(path);
|
|
10689
10750
|
if (st.isFile()) return { kind: "file", mode: st.mode };
|
|
10690
10751
|
if (st.isDirectory()) return { kind: "dir", mode: st.mode };
|
|
10691
10752
|
if (st.isSymbolicLink()) return { kind: "symlink", mode: st.mode };
|
|
@@ -10695,7 +10756,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
10695
10756
|
let rootStat;
|
|
10696
10757
|
let currentReal;
|
|
10697
10758
|
try {
|
|
10698
|
-
rootStat =
|
|
10759
|
+
rootStat = lstatSync8(root.path);
|
|
10699
10760
|
currentReal = realpathSync4(root.path);
|
|
10700
10761
|
} catch (e) {
|
|
10701
10762
|
throw new Unattestable(
|
|
@@ -11865,7 +11926,7 @@ var init_verifier_backend = __esm({
|
|
|
11865
11926
|
// src/cli/verify.ts
|
|
11866
11927
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
11867
11928
|
import { createHash as createHash10 } from "node:crypto";
|
|
11868
|
-
import { chmodSync as chmodSync3, cpSync, lstatSync as
|
|
11929
|
+
import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync5, symlinkSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
11869
11930
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
11870
11931
|
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve6, sep as sep3 } from "node:path";
|
|
11871
11932
|
function oracleAssuranceReport() {
|
|
@@ -11938,7 +11999,7 @@ function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
|
11938
11999
|
const current = join18(base, ...stack);
|
|
11939
12000
|
let st;
|
|
11940
12001
|
try {
|
|
11941
|
-
st =
|
|
12002
|
+
st = lstatSync9(current);
|
|
11942
12003
|
} catch {
|
|
11943
12004
|
continue;
|
|
11944
12005
|
}
|
|
@@ -11974,7 +12035,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
11974
12035
|
current = join18(current, part);
|
|
11975
12036
|
let st;
|
|
11976
12037
|
try {
|
|
11977
|
-
st =
|
|
12038
|
+
st = lstatSync9(current);
|
|
11978
12039
|
} catch {
|
|
11979
12040
|
continue;
|
|
11980
12041
|
}
|
|
@@ -11991,7 +12052,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
11991
12052
|
rejectLinkedParent(cwd, rel);
|
|
11992
12053
|
let st;
|
|
11993
12054
|
try {
|
|
11994
|
-
st =
|
|
12055
|
+
st = lstatSync9(src);
|
|
11995
12056
|
} catch {
|
|
11996
12057
|
continue;
|
|
11997
12058
|
}
|
|
@@ -12016,7 +12077,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
12016
12077
|
}
|
|
12017
12078
|
function dropSymlink(p) {
|
|
12018
12079
|
try {
|
|
12019
|
-
if (
|
|
12080
|
+
if (lstatSync9(p).isSymbolicLink()) rmSync5(p, { force: true });
|
|
12020
12081
|
} catch {
|
|
12021
12082
|
}
|
|
12022
12083
|
}
|
|
@@ -12058,7 +12119,7 @@ function overlayDigest(dest, paths) {
|
|
|
12058
12119
|
h.update("\0");
|
|
12059
12120
|
const p = join18(dest, rel);
|
|
12060
12121
|
try {
|
|
12061
|
-
const st =
|
|
12122
|
+
const st = lstatSync9(p);
|
|
12062
12123
|
h.update(String(st.mode));
|
|
12063
12124
|
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
|
|
12064
12125
|
} catch {
|
|
@@ -14619,7 +14680,7 @@ var init_trace_verify = __esm({
|
|
|
14619
14680
|
// src/cli/onboard.ts
|
|
14620
14681
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
14621
14682
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
14622
|
-
import { lstatSync as
|
|
14683
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync9, renameSync as renameSync2, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
14623
14684
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
14624
14685
|
import { join as join22, relative as relative7, resolve as resolve10, sep as sep5 } from "node:path";
|
|
14625
14686
|
import { createInterface } from "node:readline";
|
|
@@ -14699,7 +14760,7 @@ function atomicReplaceFile(path, content, mode) {
|
|
|
14699
14760
|
}
|
|
14700
14761
|
function writeVerifyCommand(cwd, command) {
|
|
14701
14762
|
const path = join22(cwd, POLICY_FILE);
|
|
14702
|
-
const st =
|
|
14763
|
+
const st = lstatSync10(path, { throwIfNoEntry: false });
|
|
14703
14764
|
if (st && (!st.isFile() || st.isSymbolicLink())) {
|
|
14704
14765
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
14705
14766
|
}
|
|
@@ -14818,26 +14879,35 @@ async function runOnboard(opts, io = {}) {
|
|
|
14818
14879
|
status("LIMITED", platformLabel(platform) + ": check works here; local verify and `run` are unavailable. Use the container verifier for final verification.", "warn");
|
|
14819
14880
|
}
|
|
14820
14881
|
const head = git5(cwd, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])?.trim() ?? null;
|
|
14821
|
-
if (!head) status("ACTION", "No commit yet. Commit once before pristine verification or the safe demo.", "warn");
|
|
14822
14882
|
const treeStatus = git5(cwd, ["status", "--porcelain", "--untracked-files=all"]) ?? "";
|
|
14823
14883
|
const dirty = treeStatus.split("\n").filter(Boolean).map((line) => line.slice(3).split(" -> ").at(-1) ?? "");
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14884
|
+
if (!head) {
|
|
14885
|
+
const count = dirty.length;
|
|
14886
|
+
status(
|
|
14887
|
+
"ACTION",
|
|
14888
|
+
count ? "No first commit yet; " + count + " path(s) are not part of a committed baseline. Create the initial commit before pristine verification or the safe demo." : "No first commit yet. Create the initial commit before pristine verification or the safe demo.",
|
|
14889
|
+
"warn"
|
|
14890
|
+
);
|
|
14830
14891
|
} else {
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
|
|
14892
|
+
const owned = new Set(planInit(cwd).map((a) => a.path));
|
|
14893
|
+
const foreign = dirty.filter((p) => !owned.has(p));
|
|
14894
|
+
if (dirty.length === 0) {
|
|
14895
|
+
status("OK", "Working tree is clean.", "ok");
|
|
14896
|
+
} else if (foreign.length === 0) {
|
|
14897
|
+
status("ACTION", dirty.length + " uncommitted setup file(s) from TamperWard; commit them when setup is complete.", "warn");
|
|
14898
|
+
} else {
|
|
14899
|
+
status("ACTION", dirty.length + " existing changed/untracked path(s). TamperWard will not stash or reset them.", "warn");
|
|
14900
|
+
if (!await confirm("Continue with the existing working-tree changes?", false, true)) {
|
|
14901
|
+
status("STOPPED", "Commit or stash your work, then re-run `tamperward onboard`.", "warn");
|
|
14902
|
+
return 2;
|
|
14903
|
+
}
|
|
14835
14904
|
}
|
|
14836
14905
|
}
|
|
14837
14906
|
section(2);
|
|
14838
14907
|
const plan = planInit(cwd);
|
|
14839
14908
|
const names = {
|
|
14840
14909
|
policy: "Policy",
|
|
14910
|
+
gitignore: "Git ignore",
|
|
14841
14911
|
agent: "Claude hooks",
|
|
14842
14912
|
"pre-commit": "Pre-commit",
|
|
14843
14913
|
ci: "CI workflow",
|
|
@@ -14950,11 +15020,11 @@ async function runOnboard(opts, io = {}) {
|
|
|
14950
15020
|
status("SKIP", "Verification skipped. Run `tamperward verify` when ready.", "dim");
|
|
14951
15021
|
}
|
|
14952
15022
|
if (!opts.skipDemo) {
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
15023
|
+
if (!head) {
|
|
15024
|
+
status("SKIP", "Safe demo becomes available after the first commit.", "dim");
|
|
15025
|
+
} else {
|
|
15026
|
+
const wanted = scripted ? Boolean(opts.demo) : opts.demo ? true : await confirm("Run the optional safe tamper demo? (temporary worktree only)", false, false);
|
|
15027
|
+
if (wanted) runDemo(cwd, head, runners, (line) => out3(terminalText(line)));
|
|
14958
15028
|
}
|
|
14959
15029
|
}
|
|
14960
15030
|
section(4);
|
|
@@ -14982,8 +15052,10 @@ async function runOnboard(opts, io = {}) {
|
|
|
14982
15052
|
if (check2.state === "OK" || check2.id === "observer") continue;
|
|
14983
15053
|
seenFailure.add(check2.id);
|
|
14984
15054
|
if (check2.id === "platform" && platform !== "linux") {
|
|
14985
|
-
|
|
14986
|
-
|
|
15055
|
+
continue;
|
|
15056
|
+
}
|
|
15057
|
+
if (check2.id === "verifier" && !verifyCommand && check2.state === "BROKEN") {
|
|
15058
|
+
status("ACTION", "Verification is not configured; choose the trusted test command.", "warn");
|
|
14987
15059
|
} else {
|
|
14988
15060
|
renderCheck(check2, out3, colour);
|
|
14989
15061
|
}
|
|
@@ -14998,15 +15070,24 @@ async function runOnboard(opts, io = {}) {
|
|
|
14998
15070
|
else status("BLOCKED", "Fix the broken item(s) above before relying on the gate.", "bad");
|
|
14999
15071
|
if (doctorOutcome.github) {
|
|
15000
15072
|
status("OK", "GitHub authority enforced for " + doctorOutcome.github.repo + "#" + doctorOutcome.github.branch + ".", "ok");
|
|
15073
|
+
} else if (!repo) {
|
|
15074
|
+
status("ACTION", "GitHub authority is not configured for this repository.", "warn");
|
|
15075
|
+
out3(paint2(" If GitHub will provide repository authority, run: " + terminalText(doctorCommand), DIM2, colour));
|
|
15076
|
+
out3(paint2(" If this repository will use GitHub CI authority, configure:", DIM2, colour));
|
|
15077
|
+
for (const line of MANUAL_CONTROLS) out3(paint2(" " + line, DIM2, colour));
|
|
15001
15078
|
} else {
|
|
15002
15079
|
status("ACTION", "GitHub authority is not verified yet.", "warn");
|
|
15003
15080
|
out3(paint2(" " + terminalText(doctorCommand), DIM2, colour));
|
|
15004
|
-
|
|
15081
|
+
out3(paint2(" GitHub authority requires all three controls:", DIM2, colour));
|
|
15082
|
+
for (const line of MANUAL_CONTROLS) out3(paint2(" " + line, DIM2, colour));
|
|
15005
15083
|
}
|
|
15006
15084
|
const commitPaths = [...new Set(wrote)].filter((path) => committableSetupPath(cwd, path));
|
|
15007
15085
|
if (commitPaths.length) {
|
|
15008
15086
|
status("NEXT", "Commit repository setup files: " + commitPaths.join(", "), "info");
|
|
15009
15087
|
}
|
|
15088
|
+
if (!head) {
|
|
15089
|
+
status("NEXT", "Create the initial repository commit before verify or the safe demo.", "info");
|
|
15090
|
+
}
|
|
15010
15091
|
if (!verifyCommand) {
|
|
15011
15092
|
status("NEXT", "Choose the test command TamperWard should trust for verification.", "info");
|
|
15012
15093
|
}
|
|
@@ -15071,7 +15152,7 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
15071
15152
|
return;
|
|
15072
15153
|
}
|
|
15073
15154
|
const targetPath = join22(wt, target.path);
|
|
15074
|
-
const targetStat =
|
|
15155
|
+
const targetStat = lstatSync10(targetPath, { throwIfNoEntry: false });
|
|
15075
15156
|
if (!targetStat || !targetStat.isFile() || targetStat.isSymbolicLink()) {
|
|
15076
15157
|
out3("Demo skipped: the selected test is not a regular file in the disposable worktree.");
|
|
15077
15158
|
return;
|
|
@@ -15136,10 +15217,9 @@ var init_onboard = __esm({
|
|
|
15136
15217
|
"Summary"
|
|
15137
15218
|
];
|
|
15138
15219
|
MANUAL_CONTROLS = [
|
|
15139
|
-
"
|
|
15140
|
-
"
|
|
15141
|
-
"
|
|
15142
|
-
" dismiss stale approvals when new commits are pushed"
|
|
15220
|
+
"required check: tamperward",
|
|
15221
|
+
"Code Owner review",
|
|
15222
|
+
"dismiss stale approvals when new commits are pushed"
|
|
15143
15223
|
];
|
|
15144
15224
|
ESC2 = "\x1B";
|
|
15145
15225
|
RESET2 = `${ESC2}[0m`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.3",
|
|
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",
|