tamperward 2.24.4 → 2.24.5
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 +155 -32
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1536,11 +1536,86 @@ function gitSubcommand(toks) {
|
|
|
1536
1536
|
}
|
|
1537
1537
|
return null;
|
|
1538
1538
|
}
|
|
1539
|
-
|
|
1539
|
+
function shellWord(t) {
|
|
1540
|
+
let out3 = "";
|
|
1541
|
+
let single = false;
|
|
1542
|
+
let double = false;
|
|
1543
|
+
for (let i = 0; i < t.length; i++) {
|
|
1544
|
+
const ch = t[i];
|
|
1545
|
+
if (single) {
|
|
1546
|
+
if (ch === "'") single = false;
|
|
1547
|
+
else out3 += ch;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (ch === "\\" && i + 1 < t.length && (!double || /["\\$`]/.test(t[i + 1]))) {
|
|
1551
|
+
out3 += t[++i];
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (ch === "'" && !double) {
|
|
1555
|
+
single = true;
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (ch === '"') {
|
|
1559
|
+
double = !double;
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
out3 += ch;
|
|
1563
|
+
}
|
|
1564
|
+
return out3;
|
|
1565
|
+
}
|
|
1566
|
+
function expandBraces(t) {
|
|
1567
|
+
const m = t.match(/^([^{]*)\{([^{}]*)\}(.*)$/);
|
|
1568
|
+
if (!m) return [t];
|
|
1569
|
+
return m[2].split(",").flatMap((alt) => expandBraces(`${m[1]}${alt}${m[3]}`));
|
|
1570
|
+
}
|
|
1571
|
+
function globToRegExp(pattern) {
|
|
1572
|
+
const classes = [];
|
|
1573
|
+
const src = pattern.replace(/^\.\//, "").replace(/\[([^\][]*)\]/g, (_, body) => {
|
|
1574
|
+
classes.push(`[${body.replace(/^!/, "^").replace(/\\/g, "\\\\")}]`);
|
|
1575
|
+
return "";
|
|
1576
|
+
}).replace(/[.+^${}()|\\[\]]/g, "\\$&").replace(/\*\*\/?/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\0/g, "(?:.*/)?").replace(//g, () => classes.shift() ?? "");
|
|
1577
|
+
return new RegExp(`^(?:\\./)?${src}/?$`);
|
|
1578
|
+
}
|
|
1579
|
+
function expandGlob(t, listing) {
|
|
1580
|
+
const alts = expandBraces(t);
|
|
1581
|
+
if (!alts.some((a) => GLOB_CHARS.test(a))) return alts;
|
|
1582
|
+
const out3 = /* @__PURE__ */ new Set();
|
|
1583
|
+
for (const alt of alts) {
|
|
1584
|
+
if (!GLOB_CHARS.test(alt)) {
|
|
1585
|
+
out3.add(alt);
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
let re;
|
|
1589
|
+
try {
|
|
1590
|
+
re = globToRegExp(alt);
|
|
1591
|
+
} catch {
|
|
1592
|
+
out3.add(alt);
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
const depth = alt.replace(/^\.\//, "").replace(/\/+$/, "").split("/").length;
|
|
1596
|
+
for (const f of listing) {
|
|
1597
|
+
if (re.test(f)) {
|
|
1598
|
+
out3.add(f);
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const parts = f.split("/");
|
|
1602
|
+
if (parts.length > depth && !alt.includes("**")) {
|
|
1603
|
+
const dir = parts.slice(0, depth).join("/");
|
|
1604
|
+
if (re.test(dir)) out3.add(dir);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return [...out3];
|
|
1609
|
+
}
|
|
1610
|
+
function isGlob(t) {
|
|
1611
|
+
return GLOB_CHARS.test(t) || /\{[^{}]*\}/.test(t);
|
|
1612
|
+
}
|
|
1613
|
+
var GIT_OPT_WITH_ARG, GLOB_CHARS;
|
|
1540
1614
|
var init_command = __esm({
|
|
1541
1615
|
"src/detectors/command.ts"() {
|
|
1542
1616
|
"use strict";
|
|
1543
1617
|
GIT_OPT_WITH_ARG = /* @__PURE__ */ new Set(["-c", "-C", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
|
|
1618
|
+
GLOB_CHARS = /[*?[]/;
|
|
1544
1619
|
}
|
|
1545
1620
|
});
|
|
1546
1621
|
|
|
@@ -2850,16 +2925,20 @@ function shellWritesHook(seg, toks, policy, ctx) {
|
|
|
2850
2925
|
return null;
|
|
2851
2926
|
}
|
|
2852
2927
|
}
|
|
2853
|
-
function
|
|
2928
|
+
function xargsCommand(toks) {
|
|
2854
2929
|
const at = toks.findIndex((t) => t === "xargs" || t.endsWith("/xargs"));
|
|
2855
2930
|
if (at === -1) return null;
|
|
2931
|
+
let i = at + 1;
|
|
2932
|
+
while (i < toks.length && toks[i].startsWith("-")) i += /^-(?:I|n|L|P|d|a|s)$/.test(toks[i]) ? 2 : 1;
|
|
2933
|
+
return { cmd: (toks[i] ?? "").replace(/^.*\//, ""), args: toks.slice(i + 1), at: i };
|
|
2934
|
+
}
|
|
2935
|
+
function xargsWritesHook(toks, fed, policy, ctx) {
|
|
2936
|
+
const run = xargsCommand(toks);
|
|
2937
|
+
if (run === null) return null;
|
|
2856
2938
|
const { hookish } = hookTests(policy, ctx);
|
|
2857
2939
|
const fedHook = fed.some(hookish);
|
|
2858
2940
|
if (!fedHook) return null;
|
|
2859
|
-
|
|
2860
|
-
while (i < toks.length && toks[i].startsWith("-")) i += /^-(?:I|n|L|P|d|a|s)$/.test(toks[i]) ? 2 : 1;
|
|
2861
|
-
const cmd = (toks[i] ?? "").replace(/^.*\//, "");
|
|
2862
|
-
const args = toks.slice(i + 1);
|
|
2941
|
+
const { cmd, args, at: i } = run;
|
|
2863
2942
|
if (["rm", "unlink", "shred", "truncate", "mv", "tee", "sponge"].includes(cmd)) return `xargs ${cmd} rewrites or removes a protected hook named earlier in the line`;
|
|
2864
2943
|
if (cmd === "chmod" && chmodDropsExec(toks.slice(i))) return "xargs chmod removes execute permission from a hook named earlier in the line";
|
|
2865
2944
|
if ((cmd === "sed" || cmd === "perl") && args.some((a) => /^-[A-Za-z]*i|^--in-place/.test(a))) return `xargs ${cmd} -i rewrites a protected hook named earlier in the line`;
|
|
@@ -8547,6 +8626,7 @@ var init_test_deletion = __esm({
|
|
|
8547
8626
|
init_policy();
|
|
8548
8627
|
init_finding();
|
|
8549
8628
|
init_command();
|
|
8629
|
+
init_hook_wiring();
|
|
8550
8630
|
init_files();
|
|
8551
8631
|
init_repo();
|
|
8552
8632
|
init_suite_config();
|
|
@@ -8683,16 +8763,31 @@ var init_test_deletion = __esm({
|
|
|
8683
8763
|
}
|
|
8684
8764
|
} else {
|
|
8685
8765
|
let root = "";
|
|
8766
|
+
const fed = [];
|
|
8686
8767
|
for (const seg of segments(c.raw)) {
|
|
8687
|
-
const toks = tokens(seg).map(
|
|
8768
|
+
const toks = tokens(seg).map(shellWord);
|
|
8688
8769
|
const cdTo = toks[0] === "cd" ? toks[1] : null;
|
|
8689
8770
|
if (cdTo !== null) {
|
|
8690
8771
|
root = cdTo === void 0 || /^[/~$]/.test(cdTo) || root === null ? null : joinRoot(root, cdTo);
|
|
8772
|
+
fed.push(...toks);
|
|
8691
8773
|
continue;
|
|
8692
8774
|
}
|
|
8693
8775
|
const inRepo = (t) => root ? `${root}/${t}` : t;
|
|
8694
8776
|
const listing = trackedFiles(ctx);
|
|
8695
|
-
const
|
|
8777
|
+
const whole = (t) => /^(?:\.|\.\/|\*|\.\/\*)$/.test(t);
|
|
8778
|
+
const expand = (raw) => {
|
|
8779
|
+
const t = raw.replace(/^(?:of|if)=/, "");
|
|
8780
|
+
if (!isGlob(t) || whole(t)) return [t];
|
|
8781
|
+
if (listing && root !== null) return expandGlob(inRepo(t), listing).map((p) => root ? p.slice(root.length + 1) : p);
|
|
8782
|
+
return expandBraces(t).flatMap((alt) => {
|
|
8783
|
+
if (!isGlob(alt)) return [alt];
|
|
8784
|
+
const base = alt.split("/").pop() ?? "";
|
|
8785
|
+
if (/test|spec/i.test(base.replace(/[*?]|\[[^\]]*\]/g, ""))) return [alt.replace(/\*\*\//g, "").replace(/\*/g, "a").replace(/\?/g, "t")];
|
|
8786
|
+
const dir = alt.includes("/") ? alt.replace(/\/[^/]*$/, "") : "";
|
|
8787
|
+
return dir && !isGlob(dir) && containsProtected(dir, policy, "tests", ctx, "snapshots") ? [dir] : [];
|
|
8788
|
+
});
|
|
8789
|
+
};
|
|
8790
|
+
const specPath = (t) => {
|
|
8696
8791
|
const resolved = root !== null && /(?:^|\/)\.{1,2}(?:\/|$)/.test(t) ? joinRoot(root, t) : null;
|
|
8697
8792
|
const probe = resolved ?? t;
|
|
8698
8793
|
if (!isSpec(probe)) return false;
|
|
@@ -8700,56 +8795,82 @@ var init_test_deletion = __esm({
|
|
|
8700
8795
|
const looksLikeFile = /\.[A-Za-z0-9]+$/.test(probe.split("/").pop() ?? "");
|
|
8701
8796
|
if (looksLikeFile || listing.includes(resolved ?? inRepo(t))) return true;
|
|
8702
8797
|
return containsProtected(resolved ?? inRepo(t), policy, "tests", ctx, "snapshots");
|
|
8703
|
-
}
|
|
8798
|
+
};
|
|
8704
8799
|
const rootHasProtected = () => listing ? listing.some(isSpec) : true;
|
|
8705
|
-
const
|
|
8706
|
-
if (t.startsWith("-")
|
|
8707
|
-
const
|
|
8708
|
-
if (!
|
|
8709
|
-
if (root === null) return
|
|
8710
|
-
const dir =
|
|
8800
|
+
const dirPath = (t) => {
|
|
8801
|
+
if (t.startsWith("-")) return false;
|
|
8802
|
+
const isWhole = whole(t);
|
|
8803
|
+
if (!isWhole && isGlob(t)) return false;
|
|
8804
|
+
if (root === null) return isWhole ? false : containsProtected(t, policy, "tests", ctx, "snapshots");
|
|
8805
|
+
const dir = isWhole ? root : joinRoot(root, t);
|
|
8711
8806
|
if (dir === null) return false;
|
|
8712
8807
|
if (dir === "") return rootHasProtected();
|
|
8713
8808
|
return containsProtected(dir, policy, "tests", ctx, "snapshots");
|
|
8714
|
-
}
|
|
8809
|
+
};
|
|
8810
|
+
const xargs = xargsCommand(toks);
|
|
8811
|
+
const source = xargs ? [...fed, ...toks] : toks;
|
|
8812
|
+
fed.push(...toks);
|
|
8813
|
+
const testToks = source.filter((t) => expand(t).some(specPath));
|
|
8814
|
+
const dirToks = source.filter((t) => !t.startsWith("-") && !testToks.includes(t) && expand(t).some(dirPath));
|
|
8715
8815
|
if (testToks.length === 0 && dirToks.length === 0) continue;
|
|
8716
8816
|
const named = [...testToks, ...dirToks];
|
|
8717
|
-
|
|
8817
|
+
let at = 0;
|
|
8818
|
+
while (at < toks.length && WRAPPERS.test(toks[at])) at++;
|
|
8819
|
+
const cmdToks = toks.slice(at);
|
|
8820
|
+
const cmd = xargs ? xargs.cmd : (cmdToks[0] ?? "").replace(/^.*\//, "");
|
|
8821
|
+
const args = xargs ? xargs.args : cmdToks.slice(1);
|
|
8822
|
+
const inPlace = (a) => /^-[A-Za-z]*i|^--in-place/.test(a);
|
|
8823
|
+
const isDir = (p) => listing ? listing.some((f) => f.startsWith(inRepo(p).replace(/\/+$/, "") + "/")) : /\/$/.test(p);
|
|
8824
|
+
const exists = (p) => !listing || root === null || listing.includes(inRepo(p.replace(/^\.\//, "")));
|
|
8825
|
+
const ARG_TAKING = /^-(?:t|m|o|g|S|e)$|^--(?:target-directory|mode|owner|group|suffix|rsh|exclude|include|files-from)$/;
|
|
8826
|
+
const positional = args.filter((a, i) => a !== "--" && !a.startsWith("-") && !ARG_TAKING.test(args[i - 1] ?? ""));
|
|
8718
8827
|
let why = null;
|
|
8719
8828
|
let evidence = seg;
|
|
8720
|
-
const
|
|
8721
|
-
const redirectsOntoTest = redirectTarget ? isSpec(unquote(redirectTarget)) : false;
|
|
8829
|
+
const redirectsOntoTest = [...seg.matchAll(REDIRECT_TARGET)].some((m) => isSpec(shellWord(m[1])));
|
|
8722
8830
|
const findDeletes = cmd === "find" && /\s(?:-delete\b|-exec\s+(?:rm|unlink)\b|-execdir\s+(?:rm|unlink)\b)/.test(seg);
|
|
8723
|
-
const
|
|
8831
|
+
const gitSub = cmd === "git" ? gitSubcommand(cmdToks) : null;
|
|
8832
|
+
const gitRestoresOld = (gitSub === "checkout" || gitSub === "restore") && (() => {
|
|
8724
8833
|
const src = seg.match(/--source[= ]\s*(\S+)/)?.[1];
|
|
8725
|
-
const revIdx =
|
|
8726
|
-
const rev2 = src ?? (revIdx >= 0 ?
|
|
8834
|
+
const revIdx = cmdToks.findIndex((t, i) => i >= 2 && !t.startsWith("-") && !named.includes(t) && t !== "--");
|
|
8835
|
+
const rev2 = src ?? (revIdx >= 0 ? cmdToks[revIdx] : void 0);
|
|
8727
8836
|
if (!rev2 || rev2 === "HEAD" || rev2 === "@" || revIsHead(rev2, ctx) === true) return false;
|
|
8728
8837
|
if (src || seg.includes(" -- ")) return true;
|
|
8729
|
-
return revIdx >= 0 &&
|
|
8838
|
+
return revIdx >= 0 && cmdToks.some((t, i) => i > revIdx && named.includes(t));
|
|
8730
8839
|
})();
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8840
|
+
const dests = cmd === "cp" || cmd === "install" || cmd === "rsync" || cmd === "ln" || cmd === "mv" ? destinations(cmd, args, isDir) : [];
|
|
8841
|
+
const overwrites = dests.some((d) => isSpec(d) && exists(d)) && positional.some((p) => !dests.includes(p) && !isSpec(p));
|
|
8842
|
+
if (xargs) {
|
|
8843
|
+
if (cmd === "rm" || cmd === "unlink" || cmd === "shred") why = `xargs ${cmd} deletes a test file named earlier in the line`;
|
|
8844
|
+
else if (cmd === "truncate") why = "xargs truncate empties a test file named earlier in the line";
|
|
8845
|
+
else if (cmd === "mv") why = "xargs mv moves a test file named earlier in the line";
|
|
8846
|
+
else if (cmd === "tee" || cmd === "sponge") why = `xargs ${cmd} rewrites a test file named earlier in the line`;
|
|
8847
|
+
else if ((cmd === "sed" || cmd === "perl") && args.some(inPlace)) why = `xargs ${cmd} -i rewrites a test file named earlier in the line`;
|
|
8848
|
+
} else if (cmd === "rm" || cmd === "unlink" || cmd === "shred") {
|
|
8849
|
+
why = `${cmd} deletes a test file`;
|
|
8850
|
+
} else if (gitSub === "rm") {
|
|
8734
8851
|
why = "git rm deletes a test file";
|
|
8735
8852
|
} else if (cmd === "sed" && SED_INPLACE.test(seg)) {
|
|
8736
8853
|
why = "sed -i rewrites a test file in place";
|
|
8737
8854
|
} else if (cmd === "perl" && /(?:^|\s)-[a-zA-Z]*i\b|\s-i\S*/.test(seg)) {
|
|
8738
8855
|
why = "perl -i rewrites a test file in place";
|
|
8739
|
-
} else if (
|
|
8856
|
+
} else if (cmd === "truncate") {
|
|
8740
8857
|
why = "truncate empties a test file";
|
|
8741
8858
|
} else if (redirectsOntoTest) {
|
|
8742
8859
|
why = "a redirect overwrites/empties a test file";
|
|
8743
|
-
} else if (cmd
|
|
8744
|
-
why =
|
|
8745
|
-
} else if (cmd === "tee" && !
|
|
8860
|
+
} else if (cmd !== "mv" && overwrites) {
|
|
8861
|
+
why = `${cmd} overwrites a test file with something else`;
|
|
8862
|
+
} else if (cmd === "tee" && !args.includes("-a") && !args.includes("--append")) {
|
|
8746
8863
|
why = "tee overwrites a test file";
|
|
8864
|
+
} else if (cmd === "sponge" && !args.includes("-a")) {
|
|
8865
|
+
why = "sponge rewrites a test file in place";
|
|
8866
|
+
} else if (cmd === "dd" && args.some((a) => a.startsWith("of=") && isSpec(a.slice(3)))) {
|
|
8867
|
+
why = "dd rewrites a test file";
|
|
8747
8868
|
} else if (findDeletes) {
|
|
8748
8869
|
why = "find -delete removes test files";
|
|
8749
8870
|
} else if (gitRestoresOld) {
|
|
8750
8871
|
why = "git checkout <rev> replaces a test file with an older version";
|
|
8751
|
-
} else if (
|
|
8752
|
-
const dest =
|
|
8872
|
+
} else if (cmd === "mv") {
|
|
8873
|
+
const dest = positional[positional.length - 1];
|
|
8753
8874
|
if (dest && testToks.length > 0 && !isSpec(dest)) {
|
|
8754
8875
|
why = "mv renames a test file out of the test glob";
|
|
8755
8876
|
evidence = `${testToks[0]} \u2192 ${dest}`;
|
|
@@ -8759,6 +8880,8 @@ var init_test_deletion = __esm({
|
|
|
8759
8880
|
} else if (dest && testToks.length === 0 && dirToks[0] !== dest && leavesGlob(dirToks[0] ?? "", dest, policy, ctx)) {
|
|
8760
8881
|
why = "mv renames a test directory so its specs leave the test glob";
|
|
8761
8882
|
evidence = `${dirToks[0]} \u2192 ${dest}`;
|
|
8883
|
+
} else if (overwrites) {
|
|
8884
|
+
why = "mv overwrites a test file with something else";
|
|
8762
8885
|
}
|
|
8763
8886
|
}
|
|
8764
8887
|
if (why) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.24.
|
|
3
|
+
"version": "2.24.5",
|
|
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",
|