tamperward 1.10.2 → 1.10.4
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 +42 -15
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -329,7 +329,16 @@ var LITERAL = [
|
|
|
329
329
|
{ re: /(?:^|\s)--no-verify(?:\s|$)/, why: "--no-verify skips the pre-commit/pre-push hooks" },
|
|
330
330
|
{ re: /(?:^|\s)--no-hooks(?:\s|$)/, why: "--no-hooks disables hook execution" },
|
|
331
331
|
{ re: /(?:^|[\s;&|=])HUSKY=0(?:\s|$)/, why: "HUSKY=0 disables all Husky hooks" },
|
|
332
|
-
{ re: /\bHUSKY_SKIP_HOOKS=1\b/, why: "HUSKY_SKIP_HOOKS=1 skips Husky hooks" }
|
|
332
|
+
{ re: /\bHUSKY_SKIP_HOOKS=1\b/, why: "HUSKY_SKIP_HOOKS=1 skips Husky hooks" },
|
|
333
|
+
// git-native hook disabling. core.hooksPath is strictly MORE general than the
|
|
334
|
+
// flags above: it turns off husky, lefthook, plain .git/hooks and pre-commit
|
|
335
|
+
// alike, and it was reachable in three spellings the literal list missed
|
|
336
|
+
// (-c, `git config`, and GIT_CONFIG_* env injection). (P1-9, external review.)
|
|
337
|
+
{ re: /\bcore\.hooksPath\b/i, why: "core.hooksPath redirects git away from the installed hooks" },
|
|
338
|
+
{ re: /\bGIT_CONFIG_KEY_\d+\s*=\s*["']?core\.hooksPath/i, why: "GIT_CONFIG_* injection sets core.hooksPath" },
|
|
339
|
+
{ re: /(?:^|[\s;&|=])LEFTHOOK=0(?:\s|$)/, why: "LEFTHOOK=0 disables Lefthook hooks" },
|
|
340
|
+
{ re: /\bSKIP_SIMPLE_GIT_HOOKS=1\b/, why: "SKIP_SIMPLE_GIT_HOOKS=1 skips simple-git-hooks" },
|
|
341
|
+
{ re: /\bPRE_COMMIT_ALLOW_NO_CONFIG=1\b/, why: "PRE_COMMIT_ALLOW_NO_CONFIG=1 lets pre-commit no-op" }
|
|
333
342
|
];
|
|
334
343
|
var SHORT_N = /^-[a-z]*n[a-z]*$/i;
|
|
335
344
|
var noVerify = {
|
|
@@ -880,6 +889,9 @@ var CHECK = /\b(test|tests|lint|typecheck|type-check|tsc|eslint|jest|vitest|play
|
|
|
880
889
|
var YAML_KEY = /^\s*-?\s*[A-Za-z_][\w-]*:\s*(?:$|\S)/;
|
|
881
890
|
var INVOKES_TOOL = /(?:^\s*|[;&|`]\s*|\$\(\s*|\b(?:npx|yarn|pnpm|bunx?)\s+)(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward)\b/;
|
|
882
891
|
var INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
|
|
892
|
+
function isActiveWorkflow(path) {
|
|
893
|
+
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
894
|
+
}
|
|
883
895
|
var invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line);
|
|
884
896
|
function commandCore(line) {
|
|
885
897
|
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
@@ -910,6 +922,17 @@ var ciTampering = {
|
|
|
910
922
|
const out2 = [];
|
|
911
923
|
for (const c of changes) {
|
|
912
924
|
if (c.kind !== "file") continue;
|
|
925
|
+
if (c.op === "rename" && c.oldPath && isActiveWorkflow(c.oldPath) && !isActiveWorkflow(c.path)) {
|
|
926
|
+
out2.push(
|
|
927
|
+
makeFinding(RULE5, policy, {
|
|
928
|
+
file: c.path,
|
|
929
|
+
message: `A CI workflow was renamed so it no longer runs (${c.oldPath} \u2192 ${c.path}).`,
|
|
930
|
+
evidence: `${c.oldPath} \u2192 ${c.path}`,
|
|
931
|
+
remediation: "Restore the path and extension. GitHub runs only *.yml/*.yaml directly under .github/workflows \u2014 renaming outside that disables the checks as surely as deleting them."
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
913
936
|
if (!isProtected(c.path, policy, "ci")) continue;
|
|
914
937
|
for (const l of addedLines(c)) {
|
|
915
938
|
const coe = l.content.match(/^\s*-?\s*continue-on-error:\s*(.+?)\s*$/);
|
|
@@ -1260,7 +1283,7 @@ var testDeletion = {
|
|
|
1260
1283
|
remediation: "Restoring the path. Renaming a spec out of the glob silently removes it from the suite."
|
|
1261
1284
|
})
|
|
1262
1285
|
);
|
|
1263
|
-
} else if (c.op === "modify" && isTest && c.before != null && c.after != null) {
|
|
1286
|
+
} else if ((c.op === "modify" || c.op === "rename") && isTest && c.before != null && c.after != null) {
|
|
1264
1287
|
const before = countTestBlocks(c.before);
|
|
1265
1288
|
const after = countTestBlocks(c.after);
|
|
1266
1289
|
if (after < before) {
|
|
@@ -1339,7 +1362,7 @@ var testContentRemoval = {
|
|
|
1339
1362
|
}
|
|
1340
1363
|
}
|
|
1341
1364
|
for (const c of changes) {
|
|
1342
|
-
if (c.kind !== "file" || c.op !== "modify" || c.before == null || c.after == null) continue;
|
|
1365
|
+
if (c.kind !== "file" || c.op !== "modify" && c.op !== "rename" || c.before == null || c.after == null) continue;
|
|
1343
1366
|
if (!isProtected(c.path, policy, "tests")) continue;
|
|
1344
1367
|
if (isProtected(c.path, policy, "snapshots")) continue;
|
|
1345
1368
|
if (countTestBlocks(c.after) < countTestBlocks(c.before)) continue;
|
|
@@ -1665,8 +1688,17 @@ function wrap(text, width) {
|
|
|
1665
1688
|
if (cur) out2.push(cur);
|
|
1666
1689
|
return out2.length ? out2 : [""];
|
|
1667
1690
|
}
|
|
1691
|
+
function stripControl(v) {
|
|
1692
|
+
let outStr = "";
|
|
1693
|
+
for (const ch of v) {
|
|
1694
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
1695
|
+
const control = cp < 32 && cp !== 9 && cp !== 10 && cp !== 13 || cp === 127 || cp >= 128 && cp <= 159;
|
|
1696
|
+
outStr += control ? "\uFFFD" : ch;
|
|
1697
|
+
}
|
|
1698
|
+
return outStr;
|
|
1699
|
+
}
|
|
1668
1700
|
function clip(s, max) {
|
|
1669
|
-
const one = s.replace(/\s+/g, " ").trim();
|
|
1701
|
+
const one = stripControl(s).replace(/\s+/g, " ").trim();
|
|
1670
1702
|
return one.length > max ? one.slice(0, Math.max(1, max - 1)) + "\u2026" : one;
|
|
1671
1703
|
}
|
|
1672
1704
|
function orderFindings(findings) {
|
|
@@ -2107,18 +2139,13 @@ function snapshotProtected(cwd, policy, prev) {
|
|
|
2107
2139
|
}
|
|
2108
2140
|
if (st.isDirectory()) walk(r);
|
|
2109
2141
|
else if (st.isFile() && isProtected(r, policy)) {
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
try {
|
|
2116
|
-
hash = sha(readFileSync6(join6(cwd, r)));
|
|
2117
|
-
} catch {
|
|
2118
|
-
hash = "unreadable";
|
|
2119
|
-
}
|
|
2120
|
-
out2[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
|
|
2142
|
+
let hash = "";
|
|
2143
|
+
try {
|
|
2144
|
+
hash = sha(readFileSync6(join6(cwd, r)));
|
|
2145
|
+
} catch {
|
|
2146
|
+
hash = "unreadable";
|
|
2121
2147
|
}
|
|
2148
|
+
out2[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
|
|
2122
2149
|
}
|
|
2123
2150
|
}
|
|
2124
2151
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.4",
|
|
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",
|