pr-checkmate 1.23.23 → 2.0.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/README.md +68 -15
- package/cspell.json +3 -8
- package/dist/cli.js +37 -62
- package/dist/config/constants.d.ts +21 -3
- package/dist/config/constants.js +33 -6
- package/dist/config/pr-checkmate-workflow.yml +33 -327
- package/dist/core/checks/dependencies/banned-imports.js +23 -24
- package/dist/core/checks/dependencies/circular-deps.js +1 -0
- package/dist/core/checks/dependencies/dependency-check.js +1 -0
- package/dist/core/checks/dependencies/grype-scan.js +1 -0
- package/dist/core/checks/dependencies/npm-audit.js +6 -1
- package/dist/core/checks/dependencies/vuln-scan.js +1 -0
- package/dist/core/checks/findings.d.ts +198 -19
- package/dist/core/checks/findings.js +474 -22
- package/dist/core/checks/git/custom-rules.js +21 -16
- package/dist/core/checks/git/diff-smell.d.ts +17 -0
- package/dist/core/checks/git/diff-smell.js +43 -24
- package/dist/core/checks/git/large-files.js +14 -2
- package/dist/core/checks/git/leftover-debug.js +23 -14
- package/dist/core/checks/git/lockfile-drift.js +2 -1
- package/dist/core/checks/git/merge-conflict.js +19 -8
- package/dist/core/checks/git/missing-tests.js +22 -6
- package/dist/core/checks/git/todo-fixme.js +10 -9
- package/dist/core/checks/languages/shellcheck.d.ts +8 -0
- package/dist/core/checks/languages/shellcheck.js +55 -0
- package/dist/core/checks/languages/typecheck.js +1 -0
- package/dist/core/checks/pr/pr-size-check.js +2 -1
- package/dist/core/checks/quality/config-validation.d.ts +14 -4
- package/dist/core/checks/quality/config-validation.js +119 -17
- package/dist/core/checks/quality/dead-code.js +1 -0
- package/dist/core/checks/quality/duplicate-check.js +1 -0
- package/dist/core/checks/quality/license-header.js +12 -2
- package/dist/core/checks/quality/spellcheck.d.ts +50 -0
- package/dist/core/checks/quality/spellcheck.js +141 -13
- package/dist/core/checks/quality/symlinks.d.ts +16 -0
- package/dist/core/checks/quality/symlinks.js +62 -0
- package/dist/core/checks/quality/yaml-lint.js +22 -9
- package/dist/core/checks/security/diff-security.js +111 -28
- package/dist/core/checks/security/dockerfile-security.js +23 -20
- package/dist/core/checks/security/migration-safety.js +19 -17
- package/dist/core/checks/security/sensitive-file-guard.js +37 -5
- package/dist/core/checks/security/workflow-security.d.ts +10 -0
- package/dist/core/checks/security/workflow-security.js +48 -26
- package/dist/core/commands.d.ts +27 -0
- package/dist/core/commands.js +91 -0
- package/dist/core/context.d.ts +12 -3
- package/dist/core/context.js +118 -16
- package/dist/core/install/cache.d.ts +39 -0
- package/dist/core/install/cache.js +99 -0
- package/dist/core/install/doctor.d.ts +35 -0
- package/dist/core/install/doctor.js +145 -0
- package/dist/core/install/ensure.d.ts +60 -0
- package/dist/core/install/ensure.js +169 -0
- package/dist/core/install/lock.d.ts +33 -0
- package/dist/core/install/lock.js +99 -0
- package/dist/core/install/profiles.d.ts +64 -0
- package/dist/core/install/profiles.js +170 -0
- package/dist/core/registry.js +4 -0
- package/dist/core/reporters.d.ts +13 -2
- package/dist/core/reporters.js +145 -104
- package/dist/core/run-checks.js +91 -14
- package/dist/core/targets.js +26 -4
- package/dist/core/types.d.ts +49 -1
- package/dist/init.d.ts +29 -14
- package/dist/init.js +204 -262
- package/dist/interfaces/index.d.ts +130 -0
- package/dist/scripts/generate-install-manifests.d.ts +39 -0
- package/dist/scripts/generate-install-manifests.js +121 -0
- package/dist/scripts/publish-github.js +18 -4
- package/dist/utils/detect-language.d.ts +37 -4
- package/dist/utils/detect-language.js +113 -19
- package/dist/utils/http.d.ts +59 -0
- package/dist/utils/http.js +119 -0
- package/dist/utils/logger.d.ts +52 -6
- package/dist/utils/logger.js +125 -33
- package/dist/utils/pool.d.ts +49 -0
- package/dist/utils/pool.js +124 -0
- package/dist/utils/update-notifier.d.ts +9 -0
- package/dist/utils/update-notifier.js +57 -3
- package/eslint.config.mjs +14 -2
- package/install-manifests/commitlint/package-lock.json +1028 -0
- package/install-manifests/commitlint/package.json +9 -0
- package/install-manifests/core/package-lock.json +1335 -0
- package/install-manifests/core/package.json +11 -0
- package/install-manifests/cpp/package-lock.json +29 -0
- package/install-manifests/cpp/package.json +8 -0
- package/install-manifests/duplicate/package-lock.json +113 -0
- package/install-manifests/duplicate/package.json +8 -0
- package/install-manifests/js/package-lock.json +5099 -0
- package/install-manifests/js/package.json +26 -0
- package/install-manifests/markdown/package-lock.json +1281 -0
- package/install-manifests/markdown/package.json +8 -0
- package/install-manifests/python/package-lock.json +21 -0
- package/install-manifests/python/package.json +8 -0
- package/install-manifests/spellcheck/package-lock.json +1047 -0
- package/install-manifests/spellcheck/package.json +8 -0
- package/package.json +13 -28
- package/pr-checkmate.schema.json +1070 -96
- package/src/config/cspell.json +3 -8
- package/src/config/pr-checkmate-workflow.yml +33 -327
- package/src/config/pr-checkmate.schema.json +1070 -96
|
@@ -45,26 +45,13 @@ const DOCKER_PATTERNS = [
|
|
|
45
45
|
severity: 'warn',
|
|
46
46
|
},
|
|
47
47
|
];
|
|
48
|
-
function scanDiff(stdout) {
|
|
49
|
-
const errors = {};
|
|
50
|
-
const warnings = {};
|
|
51
|
-
for (const line of stdout.split('\n')) {
|
|
52
|
-
if (line.includes('pr-checkmate-ignore'))
|
|
53
|
-
continue;
|
|
54
|
-
for (const { pattern, label, severity } of DOCKER_PATTERNS) {
|
|
55
|
-
if (pattern.test(line)) {
|
|
56
|
-
const bucket = severity === 'error' ? errors : warnings;
|
|
57
|
-
(bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { errors, warnings };
|
|
62
|
-
}
|
|
63
48
|
exports.dockerfileSecurityCheck = {
|
|
64
49
|
name: 'Dockerfile Security',
|
|
65
50
|
phase: 'informational',
|
|
51
|
+
// No `requiresDiff`: a secret baked into an ENV line is baked in whether the
|
|
52
|
+
// line is new or inherited (ticket 067).
|
|
66
53
|
applies(ctx) {
|
|
67
|
-
return ctx.config.dockerfileSecurity?.enabled !== false
|
|
54
|
+
return ctx.config.dockerfileSecurity?.enabled !== false;
|
|
68
55
|
},
|
|
69
56
|
async run(ctx) {
|
|
70
57
|
const files = await (0, targets_1.resolveTargetFiles)(ctx, ['*']);
|
|
@@ -74,14 +61,30 @@ exports.dockerfileSecurityCheck = {
|
|
|
74
61
|
if (dockerfiles.length === 0)
|
|
75
62
|
return (0, types_1.pass)();
|
|
76
63
|
try {
|
|
77
|
-
const
|
|
78
|
-
|
|
64
|
+
const cfg = ctx.config.dockerfileSecurity ?? {};
|
|
65
|
+
let stdout = '';
|
|
66
|
+
if (ctx.baseSha) {
|
|
67
|
+
stdout = (await (0, execa_1.execa)('git', ['diff', '--unified=0', ctx.baseSha, ctx.headSha ?? 'HEAD', '--', ...dockerfiles], { cwd: ctx.cwd })).stdout;
|
|
68
|
+
}
|
|
69
|
+
else if (ctx.config.run?.fullScanBehaviour !== 'skip') {
|
|
70
|
+
await (0, findings_1.scanTrackedFiles)(ctx, dockerfiles, asDiff => {
|
|
71
|
+
stdout += `${asDiff}\n`;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// This check runs `git diff` itself rather than going through
|
|
75
|
+
// `diffAddedLines`, so the `pr-checkmate-ignore` opt-out is applied here.
|
|
76
|
+
const { errors, warnings } = (0, findings_1.scanDiffPatterns)(stdout, DOCKER_PATTERNS, {
|
|
77
|
+
skipIgnored: true,
|
|
78
|
+
// Dockerfiles have no extension to key off, and `#` is their comment
|
|
79
|
+
// marker — the `hash` reading, not the language-detected one.
|
|
80
|
+
comments: 'legacy',
|
|
81
|
+
ignorePaths: cfg.ignorePaths,
|
|
82
|
+
});
|
|
79
83
|
return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
|
|
80
84
|
prefix: 'Dockerfile Security',
|
|
81
85
|
errorSummary: n => `${n} issue(s)`,
|
|
82
86
|
warningSummary: n => `${n} warning(s)`,
|
|
83
|
-
|
|
84
|
-
ignore: ctx.config.dockerfileSecurity?.ignore,
|
|
87
|
+
...(0, findings_1.clientFindingOptions)(ctx, cfg),
|
|
85
88
|
});
|
|
86
89
|
}
|
|
87
90
|
catch (err) {
|
|
@@ -65,8 +65,11 @@ function scan(stdout, findings) {
|
|
|
65
65
|
exports.migrationSafetyCheck = {
|
|
66
66
|
name: 'Migration Safety',
|
|
67
67
|
phase: 'informational',
|
|
68
|
+
// No `requiresDiff`: a destructive migration is destructive whenever it was
|
|
69
|
+
// written (ticket 067). Over a whole repository this becomes an inventory of
|
|
70
|
+
// the irreversible migrations already in the history, which is worth having.
|
|
68
71
|
applies(ctx) {
|
|
69
|
-
return ctx.config.migrationSafety?.enabled !== false
|
|
72
|
+
return ctx.config.migrationSafety?.enabled !== false;
|
|
70
73
|
},
|
|
71
74
|
async run(ctx) {
|
|
72
75
|
// Not sourcePath-scoped: migrations commonly live outside `src` (db/, alembic/).
|
|
@@ -79,28 +82,27 @@ exports.migrationSafetyCheck = {
|
|
|
79
82
|
if (migrationFiles.length === 0)
|
|
80
83
|
return (0, types_1.pass)();
|
|
81
84
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'--unified=0',
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
let stdout = '';
|
|
86
|
+
if (ctx.baseSha) {
|
|
87
|
+
stdout = (await (0, execa_1.execa)('git', ['diff', '--unified=0', ctx.baseSha, ctx.headSha ?? 'HEAD', '--', ...migrationFiles], { cwd: ctx.cwd })).stdout;
|
|
88
|
+
}
|
|
89
|
+
else if (ctx.config.run?.fullScanBehaviour !== 'skip') {
|
|
90
|
+
await (0, findings_1.scanTrackedFiles)(ctx, migrationFiles, asDiff => {
|
|
91
|
+
stdout += `${asDiff}\n`;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
90
94
|
const findings = {};
|
|
91
95
|
scan(stdout, findings);
|
|
92
96
|
if ((0, findings_1.bucketTotal)(findings) === 0)
|
|
93
97
|
return (0, types_1.pass)();
|
|
94
|
-
|
|
95
|
-
(0, findings_1.logFindings)(findings, {
|
|
98
|
+
return (0, findings_1.reportCountedFindings)(findings, ctx, {
|
|
96
99
|
prefix: 'Migration Safety',
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
severity: ctx.config.migrationSafety?.severity,
|
|
101
|
+
sampleLimit: ctx.config.log?.sampleLimit ?? 2,
|
|
102
|
+
// The labels carry an explanatory tail ("… — rewrites every row"); the
|
|
103
|
+
// summary is a one-line tally, so only the name of the risk fits.
|
|
104
|
+
summaryLabel: label => label.split(' — ')[0],
|
|
99
105
|
});
|
|
100
|
-
const summary = Object.entries(findings)
|
|
101
|
-
.map(([label, arr]) => `${arr.length}× ${label.split(' — ')[0]}`)
|
|
102
|
-
.join(', ');
|
|
103
|
-
return sev.emit(summary);
|
|
104
106
|
}
|
|
105
107
|
catch (err) {
|
|
106
108
|
utils_1.logger.warn(`[Migration Safety]: ⚠️ Could not analyze diff\n${err}`);
|
|
@@ -72,21 +72,53 @@ function categorizeFiles(changedFiles) {
|
|
|
72
72
|
exports.sensitiveFileGuardCheck = {
|
|
73
73
|
name: 'Sensitive Files',
|
|
74
74
|
phase: 'informational',
|
|
75
|
+
// No `requiresDiff`: a committed `.env` or private key is committed whether
|
|
76
|
+
// this PR touched it or not (ticket 067). In a full scan the question becomes
|
|
77
|
+
// "what sensitive files does this repository contain", which is the more
|
|
78
|
+
// useful one when auditing a repo rather than reviewing a change.
|
|
75
79
|
applies(ctx) {
|
|
76
|
-
return ctx.config.sensitiveFileGuard?.enabled !== false
|
|
80
|
+
return ctx.config.sensitiveFileGuard?.enabled !== false;
|
|
77
81
|
},
|
|
78
82
|
async run(ctx) {
|
|
79
83
|
try {
|
|
80
|
-
const
|
|
81
|
-
|
|
84
|
+
const args = ctx.baseSha
|
|
85
|
+
? ['diff', '--name-only', '--diff-filter=ACMRD', ctx.baseSha, 'HEAD']
|
|
86
|
+
: ['ls-files'];
|
|
87
|
+
if (!ctx.baseSha && ctx.config.run?.fullScanBehaviour === 'skip')
|
|
88
|
+
return (0, types_1.pass)();
|
|
89
|
+
// `reject: false` so a directory that is not a repository is an ordinary
|
|
90
|
+
// outcome rather than a thrown error whose message carries git's raw
|
|
91
|
+
// `fatal: not a git repository` into the log. The check still skips — but
|
|
92
|
+
// a skip with a reason reads as a decision, while a bare `fatal:` reads as
|
|
93
|
+
// a crash, and someone has to spend time proving it was not one.
|
|
94
|
+
const result = await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false });
|
|
95
|
+
if (result.exitCode !== 0)
|
|
96
|
+
return (0, types_1.skip)('git unavailable');
|
|
97
|
+
const cfg = ctx.config.sensitiveFileGuard ?? {};
|
|
98
|
+
// Path exclusions are applied before categorising, so one false positive —
|
|
99
|
+
// a `.pem` test fixture, a committed `.env.example` — is excused by name.
|
|
100
|
+
// Without this the only exclusion a client has is `ignore`, which suppresses
|
|
101
|
+
// the whole CATEGORY: muting "Private keys and certificates" to excuse one
|
|
102
|
+
// fixture would also excuse the next real key someone commits.
|
|
103
|
+
const changedFiles = result.stdout
|
|
104
|
+
.split('\n')
|
|
105
|
+
.filter(Boolean)
|
|
106
|
+
.filter(file => !(0, utils_1.matchesAnyGlob)(file, cfg.ignorePaths ?? []));
|
|
82
107
|
if (changedFiles.length === 0)
|
|
83
108
|
return (0, types_1.pass)();
|
|
84
109
|
const { errors, warnings } = categorizeFiles(changedFiles);
|
|
85
110
|
return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
|
|
86
111
|
prefix: 'Sensitive Files',
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
// "changed" is only true of a diff; a full scan is answering a different
|
|
113
|
+
// question — what the repository contains — and saying otherwise would
|
|
114
|
+
// send someone looking for a change that never happened.
|
|
115
|
+
errorSummary: n => `${n} sensitive file(s) ${ctx.baseSha ? 'changed' : 'in the repo'}`,
|
|
116
|
+
warningSummary: n => `${n} notable file(s) ${ctx.baseSha ? 'changed' : 'in the repo'}`,
|
|
89
117
|
showCount: false,
|
|
118
|
+
...(0, findings_1.clientFindingOptions)(ctx, cfg),
|
|
119
|
+
// Every sensitive path is listed rather than sampled: knowing that eight
|
|
120
|
+
// files are in play but only which three is not an answer anyone can act on.
|
|
121
|
+
sampleLimit: undefined,
|
|
90
122
|
});
|
|
91
123
|
}
|
|
92
124
|
catch (err) {
|
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
import { Check } from '../../types';
|
|
2
|
+
import { DiffPattern } from '../findings';
|
|
3
|
+
/**
|
|
4
|
+
* GitHub Actions workflow risks, matched on added diff lines of workflow files.
|
|
5
|
+
* These are the high-frequency supply-chain / injection footguns.
|
|
6
|
+
*
|
|
7
|
+
* Exported for the corpus in `workflow-security.test.ts`: these rules turn on a
|
|
8
|
+
* context distinction subtle enough that only worked examples can hold it in
|
|
9
|
+
* place.
|
|
10
|
+
*/
|
|
11
|
+
export declare const WORKFLOW_PATTERNS: DiffPattern[];
|
|
2
12
|
export declare const workflowSecurityCheck: Check;
|
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.workflowSecurityCheck = void 0;
|
|
3
|
+
exports.workflowSecurityCheck = exports.WORKFLOW_PATTERNS = void 0;
|
|
4
4
|
const utils_1 = require("../../../utils");
|
|
5
|
+
const targets_1 = require("../../targets");
|
|
5
6
|
const types_1 = require("../../types");
|
|
6
7
|
const findings_1 = require("../findings");
|
|
8
|
+
/**
|
|
9
|
+
* A YAML key binding — `HEAD_REF: ${{ … }}`, `ref: ${{ … }}` — rather than an
|
|
10
|
+
* interpolation inside a script body.
|
|
11
|
+
*
|
|
12
|
+
* This is the distinction the rules below turn on. Binding an untrusted value to
|
|
13
|
+
* an `env:` entry or an action input is *the recommended fix*: the value reaches
|
|
14
|
+
* the shell as data, expanded after parsing. Flagging it made the check punish
|
|
15
|
+
* the exact remediation it recommends, which is worse than not checking at all —
|
|
16
|
+
* a warning on correct code teaches people to ignore the warnings.
|
|
17
|
+
*
|
|
18
|
+
* The risky shape is the same expression inside a `run:` block, where GitHub
|
|
19
|
+
* substitutes it into the script *before* the shell parses it.
|
|
20
|
+
*/
|
|
21
|
+
const YAML_KEY_BINDING = String.raw `(?!\s*[\w.-]+:\s*\$\{\{)`;
|
|
7
22
|
/**
|
|
8
23
|
* GitHub Actions workflow risks, matched on added diff lines of workflow files.
|
|
9
24
|
* These are the high-frequency supply-chain / injection footguns.
|
|
25
|
+
*
|
|
26
|
+
* Exported for the corpus in `workflow-security.test.ts`: these rules turn on a
|
|
27
|
+
* context distinction subtle enough that only worked examples can hold it in
|
|
28
|
+
* place.
|
|
10
29
|
*/
|
|
11
|
-
|
|
30
|
+
exports.WORKFLOW_PATTERNS = [
|
|
12
31
|
{
|
|
13
32
|
// Untrusted PR-controlled input interpolated into an expression — the classic
|
|
14
33
|
// `run:` script-injection vector when it reaches a shell.
|
|
15
|
-
pattern:
|
|
34
|
+
pattern: new RegExp([
|
|
35
|
+
String.raw `^\+(?!\+)${YAML_KEY_BINDING}.*\$\{\{\s*github\.event\.`,
|
|
36
|
+
String.raw `(?:issue|pull_request|comment|review|discussion|head_commit|commits)[^}]*`,
|
|
37
|
+
String.raw `\.(?:title|body|head_ref|ref|label|name|email|message|url)`,
|
|
38
|
+
].join('')),
|
|
16
39
|
label: 'untrusted github.event input in expression (script injection risk)',
|
|
17
40
|
severity: 'error',
|
|
18
41
|
},
|
|
19
42
|
{
|
|
20
|
-
pattern:
|
|
43
|
+
pattern: new RegExp(String.raw `^\+(?!\+)${YAML_KEY_BINDING}.*\$\{\{\s*github\.head_ref\s*\}\}`),
|
|
21
44
|
label: 'github.head_ref interpolation (injection risk — use an env var)',
|
|
22
45
|
severity: 'warn',
|
|
23
46
|
},
|
|
@@ -33,38 +56,37 @@ const WORKFLOW_PATTERNS = [
|
|
|
33
56
|
severity: 'warn',
|
|
34
57
|
},
|
|
35
58
|
];
|
|
36
|
-
function scanDiff(stdout) {
|
|
37
|
-
const errors = {};
|
|
38
|
-
const warnings = {};
|
|
39
|
-
for (const line of stdout.split('\n')) {
|
|
40
|
-
for (const { pattern, label, severity } of WORKFLOW_PATTERNS) {
|
|
41
|
-
if (pattern.test(line)) {
|
|
42
|
-
const bucket = severity === 'error' ? errors : warnings;
|
|
43
|
-
(bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return { errors, warnings };
|
|
48
|
-
}
|
|
49
59
|
exports.workflowSecurityCheck = {
|
|
50
60
|
name: 'Workflow Security',
|
|
51
61
|
phase: 'informational',
|
|
62
|
+
// No `requiresDiff`: an injectable workflow is injectable whether the line
|
|
63
|
+
// arrived in this PR or long before it (ticket 067). Ours had lived there for
|
|
64
|
+
// months when ticket 060 found it — by hand, because no mode looked.
|
|
52
65
|
applies(ctx) {
|
|
53
|
-
return ctx.config.workflowSecurity?.enabled !== false
|
|
66
|
+
return ctx.config.workflowSecurity?.enabled !== false;
|
|
54
67
|
},
|
|
55
68
|
async run(ctx) {
|
|
56
69
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const globs = ['.github/workflows/*.yml', '.github/workflows/*.yaml'];
|
|
71
|
+
const cfg = ctx.config.workflowSecurity ?? {};
|
|
72
|
+
const found = (0, findings_1.emptyBuckets)();
|
|
73
|
+
const scanInto = (stdout) => {
|
|
74
|
+
// `diffAddedLines` already dropped `pr-checkmate-ignore` lines, and
|
|
75
|
+
// `scanTrackedFiles` does the same for the whole-file path.
|
|
76
|
+
(0, findings_1.mergeSeverityBuckets)(found, (0, findings_1.scanDiffPatterns)(stdout, exports.WORKFLOW_PATTERNS, { ignorePaths: cfg.ignorePaths }));
|
|
77
|
+
};
|
|
78
|
+
if (ctx.baseSha) {
|
|
79
|
+
scanInto(await (0, findings_1.diffAddedLines)(ctx, globs));
|
|
80
|
+
}
|
|
81
|
+
else if (ctx.config.run?.fullScanBehaviour !== 'skip') {
|
|
82
|
+
const files = await (0, targets_1.listTrackedFiles)(ctx, globs, ['.']);
|
|
83
|
+
await (0, findings_1.scanTrackedFiles)(ctx, files, scanInto);
|
|
84
|
+
}
|
|
85
|
+
return (0, findings_1.reportSeverityBuckets)(found.errors, found.warnings, {
|
|
63
86
|
prefix: 'Workflow Security',
|
|
64
87
|
errorSummary: n => `${n} issue(s)`,
|
|
65
88
|
warningSummary: n => `${n} warning(s)`,
|
|
66
|
-
|
|
67
|
-
ignore: ctx.config.workflowSecurity?.ignore,
|
|
89
|
+
...(0, findings_1.clientFindingOptions)(ctx, cfg),
|
|
68
90
|
});
|
|
69
91
|
}
|
|
70
92
|
catch (err) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI vocabulary, kept out of `cli.ts` so it can be imported without side
|
|
3
|
+
* effects: that module is an executable entry point — it parses `process.argv`
|
|
4
|
+
* and calls `process.exit` at import time, so anything importing it to read
|
|
5
|
+
* these tables would run the CLI instead.
|
|
6
|
+
*
|
|
7
|
+
* Separating them is what lets `commands.test.ts` assert the invariant that
|
|
8
|
+
* matters here: every command must resolve to a check that actually exists.
|
|
9
|
+
* A command pointing at a name no longer in the registry does not error — the
|
|
10
|
+
* `only` filter simply matches nothing, every check is filtered out, and the run
|
|
11
|
+
* reports success having verified nothing at all.
|
|
12
|
+
*/
|
|
13
|
+
/** Maps short CLI command names to their registry check names. */
|
|
14
|
+
export declare const COMMAND_TO_CHECK: Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Commands that run the whole pipeline (or no pipeline at all) and take no siblings.
|
|
17
|
+
*
|
|
18
|
+
* The three install commands are here rather than in {@link COMMAND_TO_CHECK}
|
|
19
|
+
* because they run no checks at all: they manage the tooling the checks need.
|
|
20
|
+
* `doctor` in particular is the answer to "why did my typecheck not run" once
|
|
21
|
+
* tooling is fetched on demand — a question that must have a command behind it
|
|
22
|
+
* and not a reading of our source.
|
|
23
|
+
*/
|
|
24
|
+
export declare const TOOLING_COMMANDS: readonly string[];
|
|
25
|
+
export declare const STANDALONE_COMMANDS: readonly string[];
|
|
26
|
+
/** Flags accepted anywhere on the command line, with what each one means. */
|
|
27
|
+
export declare const FLAGS: Record<string, string>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI vocabulary, kept out of `cli.ts` so it can be imported without side
|
|
4
|
+
* effects: that module is an executable entry point — it parses `process.argv`
|
|
5
|
+
* and calls `process.exit` at import time, so anything importing it to read
|
|
6
|
+
* these tables would run the CLI instead.
|
|
7
|
+
*
|
|
8
|
+
* Separating them is what lets `commands.test.ts` assert the invariant that
|
|
9
|
+
* matters here: every command must resolve to a check that actually exists.
|
|
10
|
+
* A command pointing at a name no longer in the registry does not error — the
|
|
11
|
+
* `only` filter simply matches nothing, every check is filtered out, and the run
|
|
12
|
+
* reports success having verified nothing at all.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.FLAGS = exports.STANDALONE_COMMANDS = exports.TOOLING_COMMANDS = exports.COMMAND_TO_CHECK = void 0;
|
|
16
|
+
/** Maps short CLI command names to their registry check names. */
|
|
17
|
+
exports.COMMAND_TO_CHECK = {
|
|
18
|
+
// Every check that can fail a run is reachable on its own, so "why does this
|
|
19
|
+
// block me" is answered by running that one check instead of the whole suite.
|
|
20
|
+
'merge-conflict': 'Merge Conflict',
|
|
21
|
+
lint: 'ESLint',
|
|
22
|
+
deps: 'Dependencies',
|
|
23
|
+
dependencies: 'Dependencies',
|
|
24
|
+
'npm-audit': 'NPM Audit',
|
|
25
|
+
prettier: 'Prettier',
|
|
26
|
+
spellcheck: 'Spellcheck',
|
|
27
|
+
security: 'Security Scan',
|
|
28
|
+
circular: 'Circular Deps',
|
|
29
|
+
license: 'License Check',
|
|
30
|
+
'pr-size': 'PR Size',
|
|
31
|
+
'pr-title': 'PR Title',
|
|
32
|
+
outdated: 'Outdated Deps',
|
|
33
|
+
markdown: 'Markdown',
|
|
34
|
+
'yaml-lint': 'YAML Lint',
|
|
35
|
+
symlinks: 'Broken Symlinks',
|
|
36
|
+
shellcheck: 'ShellCheck',
|
|
37
|
+
typecheck: 'TypeScript',
|
|
38
|
+
'diff-smell': 'Diff Smells',
|
|
39
|
+
'diff-security': 'Diff Security',
|
|
40
|
+
'workflow-security': 'Workflow Security',
|
|
41
|
+
'custom-rules': 'Custom Rules',
|
|
42
|
+
'banned-imports': 'Banned Imports',
|
|
43
|
+
'license-header': 'License Header',
|
|
44
|
+
'config-validation': 'Config Validation',
|
|
45
|
+
'dockerfile-security': 'Dockerfile Security',
|
|
46
|
+
'case-collision': 'Case Collision',
|
|
47
|
+
'vuln-scan': 'Vuln Scan',
|
|
48
|
+
'leftover-debug': 'Leftover Debug',
|
|
49
|
+
'lockfile-drift': 'Lockfile Drift',
|
|
50
|
+
'large-files': 'Large Files',
|
|
51
|
+
'todo-fixme': 'TODO/FIXME',
|
|
52
|
+
'missing-tests': 'Missing Tests',
|
|
53
|
+
coverage: 'Coverage',
|
|
54
|
+
'migration-safety': 'Migration Safety',
|
|
55
|
+
'sensitive-files': 'Sensitive Files',
|
|
56
|
+
'pr-body': 'PR Body',
|
|
57
|
+
duplicate: 'Duplicate Code',
|
|
58
|
+
commitlint: 'Commitlint',
|
|
59
|
+
'cpp-format': 'C++ Format',
|
|
60
|
+
'python-lint': 'Ruff Lint',
|
|
61
|
+
'python-format': 'Ruff Format',
|
|
62
|
+
'python-typecheck': 'Python Types',
|
|
63
|
+
'go-lint': 'Go Vet',
|
|
64
|
+
'go-format': 'Go Format',
|
|
65
|
+
'rust-lint': 'Clippy',
|
|
66
|
+
'rust-format': 'Rustfmt',
|
|
67
|
+
'csharp-format': 'C# Format',
|
|
68
|
+
'ruby-lint': 'RuboCop',
|
|
69
|
+
'php-format': 'PHP CS Fixer',
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Commands that run the whole pipeline (or no pipeline at all) and take no siblings.
|
|
73
|
+
*
|
|
74
|
+
* The three install commands are here rather than in {@link COMMAND_TO_CHECK}
|
|
75
|
+
* because they run no checks at all: they manage the tooling the checks need.
|
|
76
|
+
* `doctor` in particular is the answer to "why did my typecheck not run" once
|
|
77
|
+
* tooling is fetched on demand — a question that must have a command behind it
|
|
78
|
+
* and not a reading of our source.
|
|
79
|
+
*/
|
|
80
|
+
exports.TOOLING_COMMANDS = ['install', 'doctor', 'cache-prune'];
|
|
81
|
+
exports.STANDALONE_COMMANDS = [
|
|
82
|
+
'init',
|
|
83
|
+
'all',
|
|
84
|
+
'precommit',
|
|
85
|
+
...exports.TOOLING_COMMANDS,
|
|
86
|
+
];
|
|
87
|
+
/** Flags accepted anywhere on the command line, with what each one means. */
|
|
88
|
+
exports.FLAGS = {
|
|
89
|
+
'--full': 'review every tracked file instead of the diff (the default outside CI)',
|
|
90
|
+
'--all': 'with "install": fetch every profile, not only the ones this repository needs',
|
|
91
|
+
};
|
package/dist/core/context.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ type RangePlan = {
|
|
|
22
22
|
headSha?: string;
|
|
23
23
|
} | {
|
|
24
24
|
kind: 'github-event';
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'branch';
|
|
25
27
|
} | {
|
|
26
28
|
kind: 'ref';
|
|
27
29
|
ref: string;
|
|
@@ -37,9 +39,16 @@ type RangePlan = {
|
|
|
37
39
|
* branch) — a gate exists to judge the diff.
|
|
38
40
|
* 4. A CI build with no PR context still diffs `HEAD^`: it is a push/branch build,
|
|
39
41
|
* and delta is why CI runs are fast.
|
|
40
|
-
* 5. Anything else is a human at a terminal
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* 5. Anything else is a human at a terminal, working on a branch → review what the
|
|
43
|
+
* branch changed, i.e. everything since it diverged from the default branch.
|
|
44
|
+
* That is the same set of changes a pull request would show, which is what
|
|
45
|
+
* someone running the tool before opening one is asking about.
|
|
46
|
+
*
|
|
47
|
+
* `HEAD^` was tried here once and was wrong for a different reason: it judged
|
|
48
|
+
* only the last commit, so a `debugger` committed earlier on the branch passed.
|
|
49
|
+
* The merge base has neither problem — it covers every commit on the branch —
|
|
50
|
+
* and on the default branch itself, where there is nothing to diverge from,
|
|
51
|
+
* {@link resolveDiffRange} falls back to a full scan.
|
|
43
52
|
*
|
|
44
53
|
* @param opts Run options as passed to `runChecks`.
|
|
45
54
|
* @param env Environment to read CI markers from. Defaults to the current process.
|