tamperward 2.29.10 → 2.29.11
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 +2 -0
- package/dist/cli/index.js +62 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,6 +311,8 @@ used for CI-wiring and token-permission posture. A custom verifier workflow ther
|
|
|
311
311
|
does not get judged against an unrelated/missing generated file, and a second verifier
|
|
312
312
|
workflow with a write-scoped job cannot hide behind a safe canonical workflow.
|
|
313
313
|
|
|
314
|
+
For this repository, `tamperward doctor --github --repo hexrift/tamperward --branch main` also verifies the active main ruleset's Code Owner requirement, stale-review dismissal, every direct CI check named by `ci.yml`, and the absence of bypass actors. A missing setting is an authority failure, not a warning; run it with a token that can read repository rulesets.
|
|
315
|
+
|
|
314
316
|
Local early-layer gaps remain posture findings rather than silently changing the
|
|
315
317
|
existing generated-CI exit contract; hard CI/GitHub validation failures still exit 2.
|
|
316
318
|
|
package/dist/cli/index.js
CHANGED
|
@@ -17386,10 +17386,10 @@ function emitReport(opts, checks, authoritative) {
|
|
|
17386
17386
|
}
|
|
17387
17387
|
}
|
|
17388
17388
|
}
|
|
17389
|
-
function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
|
|
17390
|
-
let hasRequiredCheck = false;
|
|
17389
|
+
function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward", requiredChecks = [requiredCheck]) {
|
|
17391
17390
|
let hasCodeOwnerReview = false;
|
|
17392
17391
|
let dismissesStaleReviews = false;
|
|
17392
|
+
const presentChecks = /* @__PURE__ */ new Set();
|
|
17393
17393
|
if (Array.isArray(snapshot2.rules)) {
|
|
17394
17394
|
for (const raw of snapshot2.rules) {
|
|
17395
17395
|
const rule = asMapping(raw);
|
|
@@ -17402,10 +17402,10 @@ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
|
|
|
17402
17402
|
if (rule.type === "required_status_checks") {
|
|
17403
17403
|
const checks = params.required_status_checks;
|
|
17404
17404
|
if (Array.isArray(checks)) {
|
|
17405
|
-
|
|
17405
|
+
for (const rawCheck of checks) {
|
|
17406
17406
|
const check2 = asMapping(rawCheck);
|
|
17407
|
-
|
|
17408
|
-
}
|
|
17407
|
+
if (typeof check2?.context === "string") presentChecks.add(check2.context);
|
|
17408
|
+
}
|
|
17409
17409
|
}
|
|
17410
17410
|
}
|
|
17411
17411
|
}
|
|
@@ -17418,20 +17418,26 @@ function evaluateGitHubProtection(snapshot2, requiredCheck = "tamperward") {
|
|
|
17418
17418
|
if (status) {
|
|
17419
17419
|
const contexts = status.contexts;
|
|
17420
17420
|
if (Array.isArray(contexts)) {
|
|
17421
|
-
|
|
17421
|
+
for (const context of contexts) if (typeof context === "string") presentChecks.add(context);
|
|
17422
17422
|
}
|
|
17423
17423
|
const checks = status.checks;
|
|
17424
17424
|
if (Array.isArray(checks)) {
|
|
17425
|
-
|
|
17425
|
+
for (const rawCheck of checks) {
|
|
17426
17426
|
const check2 = asMapping(rawCheck);
|
|
17427
|
-
|
|
17428
|
-
}
|
|
17427
|
+
if (typeof check2?.context === "string") presentChecks.add(check2.context);
|
|
17428
|
+
}
|
|
17429
17429
|
}
|
|
17430
17430
|
}
|
|
17431
17431
|
const findings = [];
|
|
17432
|
-
|
|
17432
|
+
const missing = requiredChecks.filter((check2) => !presentChecks.has(check2));
|
|
17433
|
+
if (missing.length) findings.push(
|
|
17434
|
+
missing.length === 1 ? "require the " + missing[0] + " status check" : "require status checks: " + missing.join(", ")
|
|
17435
|
+
);
|
|
17433
17436
|
if (!hasCodeOwnerReview) findings.push("require Code Owner review");
|
|
17434
17437
|
if (!dismissesStaleReviews) findings.push("dismiss stale pull request approvals on new pushes");
|
|
17438
|
+
if (Array.isArray(snapshot2.bypassActors) && snapshot2.bypassActors.length > 0) {
|
|
17439
|
+
findings.push("remove unintended ruleset bypass actors");
|
|
17440
|
+
}
|
|
17435
17441
|
return findings;
|
|
17436
17442
|
}
|
|
17437
17443
|
function gitText(cwd, args) {
|
|
@@ -17510,7 +17516,32 @@ function githubAuthority(opts, cwd) {
|
|
|
17510
17516
|
} catch (e) {
|
|
17511
17517
|
rulesError = e instanceof Error ? e : new Error(String(e));
|
|
17512
17518
|
}
|
|
17513
|
-
let
|
|
17519
|
+
let bypassActors;
|
|
17520
|
+
let rulesetInspectionError = null;
|
|
17521
|
+
try {
|
|
17522
|
+
const rulesets = githubApi(cwd, "repos/" + repo + "/rulesets");
|
|
17523
|
+
const active = Array.isArray(rulesets) ? rulesets.find((raw) => {
|
|
17524
|
+
const item = asMapping(raw);
|
|
17525
|
+
return item?.name === "main" && item?.target === "branch" && item?.enforcement === "active";
|
|
17526
|
+
}) : null;
|
|
17527
|
+
const id = asMapping(active)?.id;
|
|
17528
|
+
if (typeof id !== "number") {
|
|
17529
|
+
rulesetInspectionError = new Error("no active main branch ruleset was found");
|
|
17530
|
+
} else {
|
|
17531
|
+
const detail = asMapping(githubApi(cwd, "repos/" + repo + "/rulesets/" + id));
|
|
17532
|
+
bypassActors = detail?.bypass_actors;
|
|
17533
|
+
}
|
|
17534
|
+
} catch (e) {
|
|
17535
|
+
rulesetInspectionError = e instanceof Error ? e : new Error(String(e));
|
|
17536
|
+
}
|
|
17537
|
+
let findings = evaluateGitHubProtection(
|
|
17538
|
+
{ rules, bypassActors },
|
|
17539
|
+
"tamperward",
|
|
17540
|
+
REPOSITORY_REQUIRED_CHECKS
|
|
17541
|
+
);
|
|
17542
|
+
if (rulesetInspectionError) {
|
|
17543
|
+
findings.push("active main ruleset bypass actors could not be verified: " + rulesetInspectionError.message);
|
|
17544
|
+
}
|
|
17514
17545
|
if (findings.length === 0) return { repo, branch, findings };
|
|
17515
17546
|
let branchProtection;
|
|
17516
17547
|
let classicError = null;
|
|
@@ -17725,7 +17756,7 @@ function runDoctor(opts = {}) {
|
|
|
17725
17756
|
emitReport(opts, outcome.checks, outcome.authoritative);
|
|
17726
17757
|
return outcome.code;
|
|
17727
17758
|
}
|
|
17728
|
-
var VERIFY_COMMAND, GITHUB_API_SCRIPT;
|
|
17759
|
+
var VERIFY_COMMAND, REPOSITORY_REQUIRED_CHECKS, GITHUB_API_SCRIPT;
|
|
17729
17760
|
var init_doctor = __esm({
|
|
17730
17761
|
"src/cli/doctor.ts"() {
|
|
17731
17762
|
"use strict";
|
|
@@ -17741,6 +17772,25 @@ var init_doctor = __esm({
|
|
|
17741
17772
|
init_narrow();
|
|
17742
17773
|
init_repo_context();
|
|
17743
17774
|
VERIFY_COMMAND = /\btamperward(?:@\S+)?\s+verify\b/;
|
|
17775
|
+
REPOSITORY_REQUIRED_CHECKS = [
|
|
17776
|
+
"typecheck",
|
|
17777
|
+
"test (20)",
|
|
17778
|
+
"test (22)",
|
|
17779
|
+
"test (24)",
|
|
17780
|
+
"build",
|
|
17781
|
+
"gate",
|
|
17782
|
+
// platform-contract is a matrix over os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17783
|
+
// (see .github/workflows/ci.yml). GitHub publishes one check context per matrix cell,
|
|
17784
|
+
// named "<job> (<os>)"; the bare job name is never a real context, so it must be
|
|
17785
|
+
// enumerated per cell or the required-status check reports a false authority failure.
|
|
17786
|
+
"platform-contract (ubuntu-latest)",
|
|
17787
|
+
"platform-contract (macos-latest)",
|
|
17788
|
+
"platform-contract (windows-latest)",
|
|
17789
|
+
"harness-core",
|
|
17790
|
+
"round4-harness",
|
|
17791
|
+
"pilot-provisioning",
|
|
17792
|
+
"verifier-container"
|
|
17793
|
+
];
|
|
17744
17794
|
GITHUB_API_SCRIPT = [
|
|
17745
17795
|
"const endpoint = process.argv[1];",
|
|
17746
17796
|
"const token = process.env.TAMPERWARD_GITHUB_TOKEN || '';",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.11",
|
|
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",
|