tamperward 2.25.0 → 2.25.2
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 +195 -42
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -526,7 +526,22 @@ function defaultPolicy(version = 1) {
|
|
|
526
526
|
"**/codecov.yml",
|
|
527
527
|
"**/.codecov.yml"
|
|
528
528
|
],
|
|
529
|
-
|
|
529
|
+
// A workflow's checks can live in a composite action it `uses:` (the workflow
|
|
530
|
+
// keeps `uses: ./.github/actions/test` while the action's `run: npm test`
|
|
531
|
+
// becomes `echo ok`), and in the entry file of a CI system that is not GitHub
|
|
532
|
+
// Actions at all. Each is read by ci-tampering: the action's `runs.steps` get
|
|
533
|
+
// the workflow's removal/neutralisation pass, the other systems' entry files
|
|
534
|
+
// the generic check-line pass and no trigger logic (issue #437).
|
|
535
|
+
ci: [
|
|
536
|
+
".github/workflows/**",
|
|
537
|
+
".github/actions/**/action.y?(a)ml",
|
|
538
|
+
".gitlab-ci.yml",
|
|
539
|
+
".circleci/config.yml",
|
|
540
|
+
"Jenkinsfile",
|
|
541
|
+
"azure-pipelines.yml",
|
|
542
|
+
"bitbucket-pipelines.yml",
|
|
543
|
+
".travis.yml"
|
|
544
|
+
],
|
|
530
545
|
// Recorded expected outputs. An assertion stored as data is still an assertion;
|
|
531
546
|
// rewriting it from current output is the snapshot-update move the affordance
|
|
532
547
|
// experiment measured at a 70% attempt / 100% through rate (snapshot-rewrite).
|
|
@@ -3456,9 +3471,62 @@ function assertedType(node) {
|
|
|
3456
3471
|
while (ts.isParenthesizedTypeNode(t)) t = t.type;
|
|
3457
3472
|
return t;
|
|
3458
3473
|
}
|
|
3459
|
-
function
|
|
3474
|
+
function bareKind(type) {
|
|
3475
|
+
let x = type;
|
|
3476
|
+
while (ts.isParenthesizedTypeNode(x)) x = x.type;
|
|
3477
|
+
switch (x.kind) {
|
|
3478
|
+
case ts.SyntaxKind.AnyKeyword:
|
|
3479
|
+
return "any";
|
|
3480
|
+
case ts.SyntaxKind.NeverKeyword:
|
|
3481
|
+
return "never";
|
|
3482
|
+
case ts.SyntaxKind.UnknownKeyword:
|
|
3483
|
+
return "unknown";
|
|
3484
|
+
case ts.SyntaxKind.ObjectKeyword:
|
|
3485
|
+
return "objectish";
|
|
3486
|
+
}
|
|
3487
|
+
if (ts.isTypeLiteralNode(x) && x.members.length === 0) return "empty";
|
|
3488
|
+
if (ts.isTypeReferenceNode(x) && ts.isIdentifier(x.typeName) && !x.typeArguments) {
|
|
3489
|
+
if (x.typeName.text === "Object") return "objectish";
|
|
3490
|
+
if (x.typeName.text === "const") return "const";
|
|
3491
|
+
}
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
function kindOfType(type, resolveName) {
|
|
3495
|
+
const bare = bareKind(type);
|
|
3496
|
+
if (bare) return bare;
|
|
3497
|
+
let x = type;
|
|
3498
|
+
while (ts.isParenthesizedTypeNode(x)) x = x.type;
|
|
3499
|
+
if (ts.isTypeReferenceNode(x) && ts.isIdentifier(x.typeName) && !x.typeArguments) return resolveName(x.typeName.text);
|
|
3500
|
+
return null;
|
|
3501
|
+
}
|
|
3502
|
+
function buildAliasMap(sf) {
|
|
3503
|
+
const decls = /* @__PURE__ */ new Map();
|
|
3504
|
+
const collect2 = (node) => {
|
|
3505
|
+
if (ts.isTypeAliasDeclaration(node) && (!node.typeParameters || node.typeParameters.length === 0)) decls.set(node.name.text, node.type);
|
|
3506
|
+
ts.forEachChild(node, collect2);
|
|
3507
|
+
};
|
|
3508
|
+
collect2(sf);
|
|
3509
|
+
const resolve16 = (name, seen) => {
|
|
3510
|
+
if (seen.has(name)) return null;
|
|
3511
|
+
seen.add(name);
|
|
3512
|
+
const t = decls.get(name);
|
|
3513
|
+
return t ? kindOfType(t, (n) => resolve16(n, seen)) : null;
|
|
3514
|
+
};
|
|
3515
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3516
|
+
for (const name of decls.keys()) {
|
|
3517
|
+
const k = resolve16(name, /* @__PURE__ */ new Set());
|
|
3518
|
+
if (k) resolved.set(name, k);
|
|
3519
|
+
}
|
|
3520
|
+
return resolved;
|
|
3521
|
+
}
|
|
3522
|
+
function assertedLaunderKind(node, aliasMap) {
|
|
3523
|
+
return kindOfType(assertedType(node), (n) => aliasMap.get(n) ?? null);
|
|
3524
|
+
}
|
|
3525
|
+
function isDoubleCast(node, aliasMap) {
|
|
3460
3526
|
const inner = unparenthesized(node.expression);
|
|
3461
|
-
|
|
3527
|
+
if (!(ts.isAsExpression(inner) || ts.isTypeAssertionExpression(inner))) return false;
|
|
3528
|
+
const k = assertedLaunderKind(inner, aliasMap ?? EMPTY_ALIASES);
|
|
3529
|
+
return k != null && DOUBLE_CAST_INNER.has(k);
|
|
3462
3530
|
}
|
|
3463
3531
|
function surfaceOf(path, src) {
|
|
3464
3532
|
const sf = parseSource(path, src, scriptKind(path));
|
|
@@ -3467,20 +3535,26 @@ function surfaceOf(path, src) {
|
|
|
3467
3535
|
if (diagnostics.length > 0) return null;
|
|
3468
3536
|
const surface = { type: 0, nonNull: 0, assertions: [] };
|
|
3469
3537
|
const lines = src.split("\n");
|
|
3538
|
+
const aliasMap = buildAliasMap(sf);
|
|
3470
3539
|
const record = (kind, node) => {
|
|
3471
3540
|
const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
3472
3541
|
surface.assertions.push({ kind, line, text: node.getText(sf).replace(/\s+/g, " "), lineText: (lines[line - 1] ?? "").trim() });
|
|
3473
3542
|
if (kind === "type") surface.type++;
|
|
3474
3543
|
else surface.nonNull++;
|
|
3475
3544
|
};
|
|
3545
|
+
const launderInner = /* @__PURE__ */ new Set();
|
|
3546
|
+
const collectInners = (node) => {
|
|
3547
|
+
if ((ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) && isDoubleCast(node, aliasMap)) launderInner.add(unparenthesized(node.expression));
|
|
3548
|
+
ts.forEachChild(node, collectInners);
|
|
3549
|
+
};
|
|
3550
|
+
collectInners(sf);
|
|
3476
3551
|
const isRow4OrHonest = (node) => {
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3479
|
-
return ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === "const";
|
|
3552
|
+
const k = assertedLaunderKind(node, aliasMap);
|
|
3553
|
+
return k === "any" || k === "never" || k === "unknown" || k === "const";
|
|
3480
3554
|
};
|
|
3481
3555
|
const visit = (node) => {
|
|
3482
3556
|
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
3483
|
-
if (!isDoubleCast(node) && !isRow4OrHonest(node)) record("type", node);
|
|
3557
|
+
if (!isDoubleCast(node, aliasMap) && !isRow4OrHonest(node) && !launderInner.has(node)) record("type", node);
|
|
3484
3558
|
} else if (ts.isNonNullExpression(node)) {
|
|
3485
3559
|
record("non-null", node);
|
|
3486
3560
|
}
|
|
@@ -3515,7 +3589,7 @@ function unmatchedAfter(before, after) {
|
|
|
3515
3589
|
}
|
|
3516
3590
|
return out3;
|
|
3517
3591
|
}
|
|
3518
|
-
var RULE2, DECLARATION, OUT_OF_BUDGET_PATH, GENERATED_HEADER, HEADER_LINES, tsCastGrowth;
|
|
3592
|
+
var RULE2, DECLARATION, OUT_OF_BUDGET_PATH, GENERATED_HEADER, HEADER_LINES, DOUBLE_CAST_INNER, EMPTY_ALIASES, tsCastGrowth;
|
|
3519
3593
|
var init_ts_cast_growth = __esm({
|
|
3520
3594
|
"src/detectors/ts-cast-growth.ts"() {
|
|
3521
3595
|
"use strict";
|
|
@@ -3529,6 +3603,8 @@ var init_ts_cast_growth = __esm({
|
|
|
3529
3603
|
OUT_OF_BUDGET_PATH = /(?:^|\/)(?:node_modules|vendor|third_party|dist|build|generated|__generated__)\/|\.(?:generated|gen)\.[cm]?[jt]sx?$/;
|
|
3530
3604
|
GENERATED_HEADER = /@generated\b|\bAUTO-?GENERATED\b|\bDO NOT EDIT\b/i;
|
|
3531
3605
|
HEADER_LINES = 20;
|
|
3606
|
+
DOUBLE_CAST_INNER = /* @__PURE__ */ new Set(["any", "never", "unknown", "empty", "objectish"]);
|
|
3607
|
+
EMPTY_ALIASES = /* @__PURE__ */ new Map();
|
|
3532
3608
|
tsCastGrowth = {
|
|
3533
3609
|
id: RULE2,
|
|
3534
3610
|
surface: ["file"],
|
|
@@ -3570,13 +3646,51 @@ var init_ts_cast_growth = __esm({
|
|
|
3570
3646
|
});
|
|
3571
3647
|
|
|
3572
3648
|
// src/detectors/ts-any-cast.ts
|
|
3573
|
-
function
|
|
3574
|
-
|
|
3649
|
+
function commentIsLiveDirective(text) {
|
|
3650
|
+
let inner = text;
|
|
3651
|
+
if (inner.startsWith("//")) inner = inner.replace(/^\/+/, "");
|
|
3652
|
+
else inner = inner.replace(/^\/\*+/, "").replace(/\*+\/$/, "");
|
|
3653
|
+
for (const raw of inner.split("\n")) {
|
|
3654
|
+
if (DIRECTIVE_START.test(raw.replace(/^\s*\*+/, "").trim())) return true;
|
|
3655
|
+
}
|
|
3656
|
+
return false;
|
|
3657
|
+
}
|
|
3658
|
+
function countLiveDirectives(src) {
|
|
3659
|
+
let n = 0;
|
|
3660
|
+
try {
|
|
3661
|
+
const scanner = ts.createScanner(
|
|
3662
|
+
ts.ScriptTarget.Latest,
|
|
3663
|
+
/* skipTrivia */
|
|
3664
|
+
false,
|
|
3665
|
+
ts.LanguageVariant.Standard,
|
|
3666
|
+
src
|
|
3667
|
+
);
|
|
3668
|
+
let tok = scanner.scan();
|
|
3669
|
+
while (tok !== ts.SyntaxKind.EndOfFileToken) {
|
|
3670
|
+
if (tok === ts.SyntaxKind.SingleLineCommentTrivia || tok === ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
3671
|
+
if (commentIsLiveDirective(scanner.getTokenText())) n++;
|
|
3672
|
+
}
|
|
3673
|
+
tok = scanner.scan();
|
|
3674
|
+
}
|
|
3675
|
+
} catch {
|
|
3676
|
+
}
|
|
3677
|
+
return n;
|
|
3678
|
+
}
|
|
3679
|
+
function countAny(src, path) {
|
|
3680
|
+
const r = { cast: 0, never: 0, broad: 0, double: 0, suppress: 0, jsdoc: 0 };
|
|
3681
|
+
r.suppress = countLiveDirectives(src);
|
|
3682
|
+
r.jsdoc = JS_FILE.test(path) ? countMatches(src, JSDOC_ANY_CAST) : 0;
|
|
3575
3683
|
try {
|
|
3576
3684
|
const sf = parseSource("f.ts", src);
|
|
3577
3685
|
if (!sf) return r;
|
|
3686
|
+
const aliasMap = buildAliasMap(sf);
|
|
3578
3687
|
const visit = (node) => {
|
|
3579
|
-
if (
|
|
3688
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
3689
|
+
const k = assertedLaunderKind(node, aliasMap);
|
|
3690
|
+
if (k === "any") r.cast++;
|
|
3691
|
+
else if (k === "never") r.never++;
|
|
3692
|
+
if (isDoubleCast(node, aliasMap)) r.double++;
|
|
3693
|
+
}
|
|
3580
3694
|
if (node.kind === ts.SyntaxKind.AnyKeyword) {
|
|
3581
3695
|
let target = node;
|
|
3582
3696
|
let p = target.parent;
|
|
@@ -3585,8 +3699,7 @@ function countAny(src) {
|
|
|
3585
3699
|
p = p.parent;
|
|
3586
3700
|
}
|
|
3587
3701
|
const isCast = p && ts.isAsExpression(p) && p.type === target || p && ts.isTypeAssertionExpression(p) && p.type === target;
|
|
3588
|
-
if (isCast) r.
|
|
3589
|
-
else r.broad++;
|
|
3702
|
+
if (!isCast) r.broad++;
|
|
3590
3703
|
}
|
|
3591
3704
|
ts.forEachChild(node, visit);
|
|
3592
3705
|
};
|
|
@@ -3595,22 +3708,33 @@ function countAny(src) {
|
|
|
3595
3708
|
}
|
|
3596
3709
|
return r;
|
|
3597
3710
|
}
|
|
3598
|
-
function
|
|
3711
|
+
function lineHasLiveDirective(line) {
|
|
3712
|
+
return countLiveDirectives(line) > 0 || FRAGMENT_DIRECTIVE.test(line);
|
|
3713
|
+
}
|
|
3714
|
+
function lineHasRow4Cast(line, aliasMap) {
|
|
3599
3715
|
const sf = parseSource("line.ts", line);
|
|
3600
3716
|
if (!sf) return false;
|
|
3601
3717
|
let found = false;
|
|
3602
3718
|
const visit = (n) => {
|
|
3603
3719
|
if (found) return;
|
|
3604
|
-
if (
|
|
3605
|
-
|
|
3606
|
-
|
|
3720
|
+
if (ts.isAsExpression(n) || ts.isTypeAssertionExpression(n)) {
|
|
3721
|
+
const k = assertedLaunderKind(n, aliasMap);
|
|
3722
|
+
if (k === "any" || k === "never" || isDoubleCast(n, aliasMap)) {
|
|
3723
|
+
found = true;
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3607
3726
|
}
|
|
3608
3727
|
ts.forEachChild(n, visit);
|
|
3609
3728
|
};
|
|
3610
3729
|
visit(sf);
|
|
3611
3730
|
return found;
|
|
3612
3731
|
}
|
|
3613
|
-
|
|
3732
|
+
function aliasMapForAddedLines(c) {
|
|
3733
|
+
const snippet = addedLines(c).map((l) => l.content).join("\n");
|
|
3734
|
+
const sf = parseSource("lines.ts", snippet);
|
|
3735
|
+
return sf ? buildAliasMap(sf) : /* @__PURE__ */ new Map();
|
|
3736
|
+
}
|
|
3737
|
+
var BLOCK_RULE, WARN_RULE, JSDOC_ANY_CAST, JS_FILE, countMatches, DIRECTIVE_START, NARROW_LINE, BROAD_LINE, FRAGMENT_DIRECTIVE, BLOCK_REMEDIATION, WARN_REMEDIATION, tsAnyCast;
|
|
3614
3738
|
var init_ts_any_cast = __esm({
|
|
3615
3739
|
"src/detectors/ts-any-cast.ts"() {
|
|
3616
3740
|
"use strict";
|
|
@@ -3622,13 +3746,13 @@ var init_ts_any_cast = __esm({
|
|
|
3622
3746
|
init_ts_cast_growth();
|
|
3623
3747
|
BLOCK_RULE = "ts-any-cast";
|
|
3624
3748
|
WARN_RULE = "ts-any-launder";
|
|
3625
|
-
|
|
3626
|
-
SUPPRESS = /@ts-(?:ignore|expect-error|nocheck)\b/g;
|
|
3627
|
-
JSDOC_ANY_CAST = /\/\*\*\s*@type\s*\{\s*any\s*\}\s*\*\/\s*\(/g;
|
|
3749
|
+
JSDOC_ANY_CAST = /\/\*\*\s*@type\s*\{\s*(?:any|\*|\?)\s*\}\s*\*\/\s*\(/g;
|
|
3628
3750
|
JS_FILE = /\.(?:js|jsx|mjs|cjs)$/;
|
|
3629
3751
|
countMatches = (s, re) => (s.match(re) || []).length;
|
|
3630
|
-
|
|
3752
|
+
DIRECTIVE_START = /^@ts-(?:ignore|expect-error|nocheck)\b/;
|
|
3753
|
+
NARROW_LINE = /\bas\s+\(*\s*any\b|<\s*any\s*>/;
|
|
3631
3754
|
BROAD_LINE = /:\s*any\b|<[^<>]*\bany\b[^<>]*>/;
|
|
3755
|
+
FRAGMENT_DIRECTIVE = /^\s*\*?\s*@ts-(?:ignore|expect-error|nocheck)\b/;
|
|
3632
3756
|
BLOCK_REMEDIATION = "Fix the underlying type instead of silencing the checker; do not cast to `any`.";
|
|
3633
3757
|
WARN_REMEDIATION = "Prefer a precise type or `unknown` + a guard over `any` here \u2014 flagged for review.";
|
|
3634
3758
|
tsAnyCast = {
|
|
@@ -3642,15 +3766,17 @@ var init_ts_any_cast = __esm({
|
|
|
3642
3766
|
const inTest = protectedCategory(c.path, policy) === "tests";
|
|
3643
3767
|
if (c.after != null && c.op !== "delete") {
|
|
3644
3768
|
const before = c.before ?? "";
|
|
3645
|
-
const a = countAny(c.after);
|
|
3646
|
-
const b = countAny(before);
|
|
3769
|
+
const a = countAny(c.after, c.path);
|
|
3770
|
+
const b = countAny(before, c.path);
|
|
3647
3771
|
const dCast = a.cast - b.cast;
|
|
3772
|
+
const dNever = a.never - b.never;
|
|
3648
3773
|
const dBroad = a.broad - b.broad;
|
|
3649
|
-
const dDouble =
|
|
3650
|
-
const dSuppr =
|
|
3651
|
-
const dJsdoc =
|
|
3774
|
+
const dDouble = a.double - b.double;
|
|
3775
|
+
const dSuppr = a.suppress - b.suppress;
|
|
3776
|
+
const dJsdoc = a.jsdoc - b.jsdoc;
|
|
3652
3777
|
const blockReasons = [];
|
|
3653
3778
|
if (dCast > 0) blockReasons.push("`as any` cast");
|
|
3779
|
+
if (dNever > 0) blockReasons.push("`as never` cast");
|
|
3654
3780
|
if (dDouble > 0) blockReasons.push("`as unknown as` double cast");
|
|
3655
3781
|
if (dSuppr > 0) blockReasons.push("@ts-ignore/@ts-expect-error/@ts-nocheck suppression");
|
|
3656
3782
|
if (dJsdoc > 0) blockReasons.push("JSDoc `@type {any}` cast");
|
|
@@ -3679,8 +3805,9 @@ var init_ts_any_cast = __esm({
|
|
|
3679
3805
|
continue;
|
|
3680
3806
|
}
|
|
3681
3807
|
const jsdocCast = JS_FILE.test(c.path) ? new RegExp(JSDOC_ANY_CAST.source) : null;
|
|
3808
|
+
const aliasMap = aliasMapForAddedLines(c);
|
|
3682
3809
|
for (const l of addedLines(c)) {
|
|
3683
|
-
if (NARROW_LINE.test(l.content) ||
|
|
3810
|
+
if (NARROW_LINE.test(l.content) || lineHasRow4Cast(l.content, aliasMap) || lineHasLiveDirective(l.content) || jsdocCast && jsdocCast.test(l.content)) {
|
|
3684
3811
|
out3.push(
|
|
3685
3812
|
makeFinding(inTest ? WARN_RULE : BLOCK_RULE, policy, {
|
|
3686
3813
|
file: c.path,
|
|
@@ -5839,6 +5966,8 @@ function survives(removedCore, isUses, afterCores, addedCores) {
|
|
|
5839
5966
|
if (w && (canonical(w[1]) === r || canonical(w[1]).startsWith(r + " "))) neutralised ??= raw;
|
|
5840
5967
|
}
|
|
5841
5968
|
}
|
|
5969
|
+
const maskOf = (args) => (" " + args).match(NEUTRALISING_SUFFIX)?.[0]?.trim() ?? null;
|
|
5970
|
+
const priorMask = r.includes(" ") ? maskOf(r.slice(r.indexOf(" ") + 1)) : null;
|
|
5842
5971
|
let respelled = false;
|
|
5843
5972
|
for (const raw of addedCores) {
|
|
5844
5973
|
if (!kind || checkKind(raw) !== kind) continue;
|
|
@@ -5853,7 +5982,9 @@ function survives(removedCore, isUses, afterCores, addedCores) {
|
|
|
5853
5982
|
continue;
|
|
5854
5983
|
}
|
|
5855
5984
|
const args = a.slice(a.indexOf(" ") + 1 || a.length);
|
|
5856
|
-
|
|
5985
|
+
const mask = maskOf(args);
|
|
5986
|
+
const newMask = mask !== null && mask !== priorMask;
|
|
5987
|
+
if (a.includes(" ") && (newMask || narrowed(args) || runnerNarrowed(a, args))) neutralised ??= raw;
|
|
5857
5988
|
else respelled = true;
|
|
5858
5989
|
}
|
|
5859
5990
|
if (respelled) return { state: "kept" };
|
|
@@ -6077,7 +6208,7 @@ function isActiveWorkflow(path) {
|
|
|
6077
6208
|
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
6078
6209
|
}
|
|
6079
6210
|
function commandCore(line) {
|
|
6080
|
-
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
6211
|
+
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses|script|command|before_script|after_script|sh|bash|pwsh|powershell):\s*/, "").replace(/\s+/g, " ").trim();
|
|
6081
6212
|
}
|
|
6082
6213
|
function expressionOf(raw) {
|
|
6083
6214
|
const v = uncommented(raw).replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
@@ -6293,6 +6424,12 @@ function triggerNarrowings(before, after, opts) {
|
|
|
6293
6424
|
}
|
|
6294
6425
|
return out3;
|
|
6295
6426
|
}
|
|
6427
|
+
function localUses(line) {
|
|
6428
|
+
const wf = line.match(REUSED_WORKFLOW)?.[1];
|
|
6429
|
+
if (wf) return [wf];
|
|
6430
|
+
const action = line.match(LOCAL_ACTION)?.[1];
|
|
6431
|
+
return action ? [`${action}/action.yml`, `${action}/action.yaml`] : [];
|
|
6432
|
+
}
|
|
6296
6433
|
function literalValue(raw) {
|
|
6297
6434
|
const v = foldExpressions(uncommented(raw).replace(/^(['"])(.*)\1$/, "$2").trim());
|
|
6298
6435
|
return v.includes(EXPR_TOKEN) || v.includes(MATRIX_TOKEN) ? null : v;
|
|
@@ -6327,7 +6464,7 @@ function ancestorOf(lines, i) {
|
|
|
6327
6464
|
while (h >= 0 && (lines[h].trim() === "" || /^\s*#/.test(lines[h]) || indentOf(lines[h]) >= ind)) h--;
|
|
6328
6465
|
return h >= 0 ? h : null;
|
|
6329
6466
|
}
|
|
6330
|
-
var RULE7, USES, CHECK, YAML_KEY, invokesCheck2, uncommented, SHELL_ERREXIT, indentOf, STEP_START, REPORTER_ACTION, usesCheck, stepHasCheck, jobHasCheck, NEUTRALISERS, LIST_KEYS, DEFAULT_BRANCH, ALL, CODE_EVENTS, PR_CODE_TYPES, pathsMatchSource, ignoresEverySource, CODE_PROBES, REUSED_WORKFLOW, MANIFEST, WORKDIR_LINE, REF_LINE, CHECKOUT, ciTampering;
|
|
6467
|
+
var RULE7, USES, CHECK, YAML_KEY, isCompositeAction, isGithubFile, GH_RUN_KEY, GENERIC_RUN_KEY, invokesCheck2, uncommented, SHELL_ERREXIT, indentOf, STEP_START, REPORTER_ACTION, usesCheck, stepHasCheck, jobHasCheck, NEUTRALISERS, LIST_KEYS, DEFAULT_BRANCH, ALL, CODE_EVENTS, PR_CODE_TYPES, pathsMatchSource, ignoresEverySource, CODE_PROBES, REUSED_WORKFLOW, LOCAL_ACTION, MANIFEST, WORKDIR_LINE, REF_LINE, CHECKOUT, ciTampering;
|
|
6331
6468
|
var init_ci_tampering = __esm({
|
|
6332
6469
|
"src/detectors/ci-tampering.ts"() {
|
|
6333
6470
|
"use strict";
|
|
@@ -6344,6 +6481,10 @@ var init_ci_tampering = __esm({
|
|
|
6344
6481
|
USES = /^\s*-\s*uses:/;
|
|
6345
6482
|
CHECK = /\b(test|tests|lint|typecheck|type-check|tsc|eslint|jest|vitest|playwright|coverage|tamperward)\b/i;
|
|
6346
6483
|
YAML_KEY = /^\s*-?\s*[A-Za-z_][\w-]*:\s*(?:$|\S)/;
|
|
6484
|
+
isCompositeAction = (path) => /(^|\/)\.github\/actions\/.+\/action\.ya?ml$/.test(path);
|
|
6485
|
+
isGithubFile = (path) => /(^|\/)\.github\//.test(path);
|
|
6486
|
+
GH_RUN_KEY = /^\s*-?\s*run:/;
|
|
6487
|
+
GENERIC_RUN_KEY = /^\s*-?\s*(?:run|script|command|before_script|after_script|sh|bash|pwsh|powershell):/;
|
|
6347
6488
|
invokesCheck2 = (line) => invokesCheck(line) || INVOKES_TOOL.test(commandCore(line));
|
|
6348
6489
|
uncommented = (v) => v.replace(/\s+#.*$/, "").trim();
|
|
6349
6490
|
SHELL_ERREXIT = /(?:^|\s)-[a-zA-Z]*e[a-zA-Z]*(?:\s|$)|-o\s+errexit\b/;
|
|
@@ -6374,7 +6515,8 @@ var init_ci_tampering = __esm({
|
|
|
6374
6515
|
pathsMatchSource = (globs4, sources) => sources.some((s) => admits(globs4, s));
|
|
6375
6516
|
ignoresEverySource = (globs4, sources) => sources.length > 0 && sources.every((s) => admits(globs4, s));
|
|
6376
6517
|
CODE_PROBES = ["src/index.ts", "src/index.js", "lib/index.js", "index.ts", "main.go", "src/main.py", "src/lib.rs", "packages/a/src/index.ts", "test/a.test.ts"];
|
|
6377
|
-
REUSED_WORKFLOW = /^\s*uses:\s*\.\/(\.github\/workflows\/[^\s@]+)/;
|
|
6518
|
+
REUSED_WORKFLOW = /^\s*-?\s*uses:\s*\.\/(\.github\/workflows\/[^\s@]+)/;
|
|
6519
|
+
LOCAL_ACTION = /^\s*-?\s*uses:\s*\.\/(\.github\/actions\/[^\s@]+?)\/?\s*(?:#.*)?$/;
|
|
6378
6520
|
MANIFEST = /(?:^|\/)(?:package\.json|pyproject\.toml|setup\.py|setup\.cfg|Cargo\.toml|go\.mod|Makefile|build\.gradle(?:\.kts)?|pom\.xml|Gemfile|deno\.jsonc?)$/;
|
|
6379
6521
|
WORKDIR_LINE = /^\s*-?\s*working-directory:\s*(.+?)\s*$/;
|
|
6380
6522
|
REF_LINE = /^\s*ref:\s*(.+?)\s*$/;
|
|
@@ -6404,7 +6546,8 @@ var init_ci_tampering = __esm({
|
|
|
6404
6546
|
if (!isProtected(c.path, policy, "ci")) continue;
|
|
6405
6547
|
const afterLines = c.after != null ? c.after.split("\n") : null;
|
|
6406
6548
|
const addedAt = new Set(addedLines(c).map((l) => l.newLine).filter((n) => n != null));
|
|
6407
|
-
|
|
6549
|
+
const github = isGithubFile(c.path);
|
|
6550
|
+
for (const l of github ? addedLines(c) : []) {
|
|
6408
6551
|
const coe = l.content.match(/^\s*-?\s*continue-on-error:\s*(.+?)\s*$/);
|
|
6409
6552
|
const cond = l.content.match(/^\s*-?\s*if:\s*(.+?)\s*$/);
|
|
6410
6553
|
if (!coe && !cond) continue;
|
|
@@ -6436,7 +6579,7 @@ var init_ci_tampering = __esm({
|
|
|
6436
6579
|
);
|
|
6437
6580
|
}
|
|
6438
6581
|
}
|
|
6439
|
-
if (afterLines) {
|
|
6582
|
+
if (afterLines && github) {
|
|
6440
6583
|
for (const b of runBlocks(afterLines)) {
|
|
6441
6584
|
if (!b.body.some(invokesCheck2)) continue;
|
|
6442
6585
|
const propagates = b.body.some((l) => /\bexit\s+"?\$/.test(l));
|
|
@@ -6514,7 +6657,7 @@ var init_ci_tampering = __esm({
|
|
|
6514
6657
|
})
|
|
6515
6658
|
);
|
|
6516
6659
|
}
|
|
6517
|
-
if (c.before != null && c.after != null) {
|
|
6660
|
+
if (c.before != null && c.after != null && !isCompositeAction(c.path)) {
|
|
6518
6661
|
triggerOpts ??= { defaultBranch: defaultBranch(ctx), sources: sourceProbes(ctx), hasBranch: (b) => branchExists(b, ctx) };
|
|
6519
6662
|
for (const reason of triggerNarrowings(parseTriggers(c.before), parseTriggers(c.after), triggerOpts)) {
|
|
6520
6663
|
out3.push(
|
|
@@ -6532,17 +6675,27 @@ var init_ci_tampering = __esm({
|
|
|
6532
6675
|
const afterCores = (afterCommands ?? addedLines(c).map((l) => l.content)).map(commandCore);
|
|
6533
6676
|
const addedCores = addedLines(c).map((l) => commandCore(afterCommands && l.newLine != null ? afterCommands[l.newLine - 1] : l.content));
|
|
6534
6677
|
for (const l of afterLines ?? []) {
|
|
6535
|
-
const reused
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6678
|
+
for (const reused of localUses(l)) {
|
|
6679
|
+
const content = afterByPath.get(reused);
|
|
6680
|
+
if (content == null || reused === c.path) continue;
|
|
6681
|
+
const cores = commandLines(content.split("\n")).map(commandCore);
|
|
6682
|
+
afterCores.push(...cores);
|
|
6683
|
+
addedCores.push(...cores);
|
|
6684
|
+
}
|
|
6685
|
+
}
|
|
6686
|
+
if (!github && c.after == null) {
|
|
6687
|
+
for (const o of changes) {
|
|
6688
|
+
if (o.kind !== "file" || o.op !== "add" || o.after == null || !isProtected(o.path, policy, "ci")) continue;
|
|
6689
|
+
const cores = commandLines(o.after.split("\n")).map(commandCore);
|
|
6690
|
+
afterCores.push(...cores);
|
|
6691
|
+
addedCores.push(...cores);
|
|
6692
|
+
}
|
|
6541
6693
|
}
|
|
6694
|
+
const runKey = github ? GH_RUN_KEY : GENERIC_RUN_KEY;
|
|
6542
6695
|
for (const l of removedLines(c)) {
|
|
6543
6696
|
if (/^\s*#/.test(l.content)) continue;
|
|
6544
6697
|
const isUsesLine = usesCheck(l.content);
|
|
6545
|
-
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ?
|
|
6698
|
+
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? runKey.test(l.content) : true) && invokesCheck2(l.content);
|
|
6546
6699
|
if (!isUsesLine && !isCheckCommand) continue;
|
|
6547
6700
|
const s = survives(commandCore(l.content), isUsesLine, afterCores, addedCores);
|
|
6548
6701
|
if (s.state === "kept") continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.25.
|
|
3
|
+
"version": "2.25.2",
|
|
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",
|