tamperward 1.12.0 → 1.13.0
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 +34 -5
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1660,17 +1660,28 @@ function applyLocalSignoffs(findings, cwd, policy, now = Date.now()) {
|
|
|
1660
1660
|
}
|
|
1661
1661
|
return { findings: remaining, cleared };
|
|
1662
1662
|
}
|
|
1663
|
-
function applyOobSignoffs(findings, oob) {
|
|
1664
|
-
const
|
|
1663
|
+
function applyOobSignoffs(findings, oob, head) {
|
|
1664
|
+
const tokens2 = oob.map((s) => s.trim()).filter(Boolean);
|
|
1665
|
+
const matches = (want) => tokens2.some((t) => {
|
|
1666
|
+
const at = t.lastIndexOf("@");
|
|
1667
|
+
if (at === -1) return head ? false : t === want;
|
|
1668
|
+
const [rule, sha2] = [t.slice(0, at), t.slice(at + 1)];
|
|
1669
|
+
if (rule !== want) return false;
|
|
1670
|
+
return head ? head.startsWith(sha2) && sha2.length >= 7 : true;
|
|
1671
|
+
});
|
|
1665
1672
|
const cleared = [];
|
|
1666
1673
|
const remaining = [];
|
|
1667
1674
|
for (const f of findings) {
|
|
1668
|
-
const ok = f.severity === "block" && (
|
|
1675
|
+
const ok = f.severity === "block" && (matches(f.rule) || (f.file ? matches(`${f.rule}:${f.file}`) : false));
|
|
1669
1676
|
if (ok) cleared.push(f);
|
|
1670
1677
|
else remaining.push(f);
|
|
1671
1678
|
}
|
|
1672
1679
|
return { findings: remaining, cleared };
|
|
1673
1680
|
}
|
|
1681
|
+
function oobHeadFromEnv(env = process.env) {
|
|
1682
|
+
const v = (env.TAMPERWARD_OOB_HEAD ?? "").trim();
|
|
1683
|
+
return v.length >= 7 ? v : void 0;
|
|
1684
|
+
}
|
|
1674
1685
|
function oobFromEnv(env = process.env) {
|
|
1675
1686
|
return (env.TAMPERWARD_OOB_SIGNOFF ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1676
1687
|
}
|
|
@@ -1938,7 +1949,7 @@ function check(opts) {
|
|
|
1938
1949
|
}
|
|
1939
1950
|
const ignoredFiles = changes.filter((c) => isSuppressed(c, policy)).length;
|
|
1940
1951
|
let findings = evaluate(changes, policy, void 0, view);
|
|
1941
|
-
const { findings: remaining, cleared } = layer === "local" ? applyLocalSignoffs(findings, cwd, policy) : applyOobSignoffs(findings, oobFromEnv());
|
|
1952
|
+
const { findings: remaining, cleared } = layer === "local" ? applyLocalSignoffs(findings, cwd, policy) : applyOobSignoffs(findings, oobFromEnv(), oobHeadFromEnv());
|
|
1942
1953
|
findings = remaining;
|
|
1943
1954
|
if (cleared.length) {
|
|
1944
1955
|
const how = layer === "local" ? "local human sign-off (ledger)" : "out-of-band approval";
|
|
@@ -2694,6 +2705,10 @@ jobs:
|
|
|
2694
2705
|
- name: Tamperward gate (diff-time)
|
|
2695
2706
|
env:
|
|
2696
2707
|
TAMPERWARD_OOB_SIGNOFF: \${{ steps.oob.outputs.rules }}
|
|
2708
|
+
# Binds each approval to the commit it was granted for: labels persist
|
|
2709
|
+
# across pushes, so an unbound one would clear every later finding on
|
|
2710
|
+
# the same PR. Labels must read tamperward:allow:<rule>@<head-sha>.
|
|
2711
|
+
TAMPERWARD_OOB_HEAD: \${{ github.event.pull_request.head.sha }}
|
|
2697
2712
|
run: npx --yes tamperward@${TW_VERSION} check --diff "\${{ github.event.pull_request.base.sha }}...\${{ github.event.pull_request.head.sha }}"
|
|
2698
2713
|
# Diff-time detection is spelling-dependent by nature; pristine
|
|
2699
2714
|
# re-execution is not, and it was the layer nothing got past in the
|
|
@@ -2704,7 +2719,7 @@ jobs:
|
|
|
2704
2719
|
# Requires a \`verify:\` block in .tamperward.yml naming the suite command;
|
|
2705
2720
|
# without one this step fails closed (exit 2) rather than passing quietly.
|
|
2706
2721
|
- name: Tamperward verify (pristine re-execution)
|
|
2707
|
-
run: npx --yes tamperward@${TW_VERSION} verify --base "\${{ github.event.pull_request.base.sha }}"
|
|
2722
|
+
run: npx --yes tamperward@${TW_VERSION} verify --require-ancestor --base "\${{ github.event.pull_request.base.sha }}"
|
|
2708
2723
|
`;
|
|
2709
2724
|
function planPolicy(cwd) {
|
|
2710
2725
|
const path = join8(cwd, POLICY_FILE);
|
|
@@ -2853,6 +2868,10 @@ function resolveBase(base, cwd) {
|
|
|
2853
2868
|
return rev;
|
|
2854
2869
|
}
|
|
2855
2870
|
}
|
|
2871
|
+
function baseIsAncestorOfHead(base, cwd) {
|
|
2872
|
+
const r = spawnSync("git", ["merge-base", "--is-ancestor", base, "HEAD"], { cwd });
|
|
2873
|
+
return r.status === 0;
|
|
2874
|
+
}
|
|
2856
2875
|
function materialize(cwd, dest) {
|
|
2857
2876
|
const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
2858
2877
|
for (const rel of listed) {
|
|
@@ -2926,6 +2945,15 @@ function runVerify(opts) {
|
|
|
2926
2945
|
out2(`verify: cannot load policy (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
|
|
2927
2946
|
return 2;
|
|
2928
2947
|
}
|
|
2948
|
+
if (opts.requireAncestor) {
|
|
2949
|
+
const requested = git2(["rev-parse", "--verify", `${opts.base ?? "HEAD"}^{commit}`], cwd).trim();
|
|
2950
|
+
if (!baseIsAncestorOfHead(requested, cwd)) {
|
|
2951
|
+
out2(
|
|
2952
|
+
`verify: --require-ancestor: ${requested.slice(0, 10)} is not an ancestor of HEAD \u2014 the anchor would silently resolve to an older commit. Failing closed.`
|
|
2953
|
+
);
|
|
2954
|
+
return 2;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2929
2957
|
const cmd = opts.cmd ?? policy.verify?.command;
|
|
2930
2958
|
if (!cmd) {
|
|
2931
2959
|
out2("verify: no suite command \u2014 set policy `verify: { command: ... }` or pass --cmd");
|
|
@@ -3008,6 +3036,7 @@ function parseVerify(args) {
|
|
|
3008
3036
|
else if (a === "--budget" && args[i + 1]) o.budget = Number(args[++i]);
|
|
3009
3037
|
else if (a === "--json") o.json = true;
|
|
3010
3038
|
else if (a === "--keep") o.keep = true;
|
|
3039
|
+
else if (a === "--require-ancestor") o.requireAncestor = true;
|
|
3011
3040
|
else if (a === "--cwd" && args[i + 1]) o.cwd = args[++i];
|
|
3012
3041
|
}
|
|
3013
3042
|
return o;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
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",
|