@trofimyukdev/repo-truth 0.0.0-stage → 0.1.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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -2
  3. package/dist/action.d.ts +52 -0
  4. package/dist/action.js +182 -0
  5. package/dist/action.js.map +1 -0
  6. package/dist/checks/action-pins.d.ts +52 -0
  7. package/dist/checks/action-pins.js +196 -0
  8. package/dist/checks/action-pins.js.map +1 -0
  9. package/dist/checks/autosquash.d.ts +23 -0
  10. package/dist/checks/autosquash.js +85 -0
  11. package/dist/checks/autosquash.js.map +1 -0
  12. package/dist/checks/bidi-controls.d.ts +58 -0
  13. package/dist/checks/bidi-controls.js +172 -0
  14. package/dist/checks/bidi-controls.js.map +1 -0
  15. package/dist/checks/binary-files.d.ts +53 -0
  16. package/dist/checks/binary-files.js +157 -0
  17. package/dist/checks/binary-files.js.map +1 -0
  18. package/dist/checks/config-changes.d.ts +57 -0
  19. package/dist/checks/config-changes.js +175 -0
  20. package/dist/checks/config-changes.js.map +1 -0
  21. package/dist/checks/credentials.d.ts +35 -0
  22. package/dist/checks/credentials.js +138 -0
  23. package/dist/checks/credentials.js.map +1 -0
  24. package/dist/checks/install-scripts.d.ts +39 -0
  25. package/dist/checks/install-scripts.js +173 -0
  26. package/dist/checks/install-scripts.js.map +1 -0
  27. package/dist/checks/lint-rules.d.ts +49 -0
  28. package/dist/checks/lint-rules.js +184 -0
  29. package/dist/checks/lint-rules.js.map +1 -0
  30. package/dist/checks/path-policy.d.ts +40 -0
  31. package/dist/checks/path-policy.js +195 -0
  32. package/dist/checks/path-policy.js.map +1 -0
  33. package/dist/checks/raised-timeouts.d.ts +66 -0
  34. package/dist/checks/raised-timeouts.js +248 -0
  35. package/dist/checks/raised-timeouts.js.map +1 -0
  36. package/dist/checks/script-changes.d.ts +51 -0
  37. package/dist/checks/script-changes.js +199 -0
  38. package/dist/checks/script-changes.js.map +1 -0
  39. package/dist/checks/secret-files.d.ts +58 -0
  40. package/dist/checks/secret-files.js +165 -0
  41. package/dist/checks/secret-files.js.map +1 -0
  42. package/dist/checks/strictness.d.ts +59 -0
  43. package/dist/checks/strictness.js +260 -0
  44. package/dist/checks/strictness.js.map +1 -0
  45. package/dist/checks/submodules.d.ts +51 -0
  46. package/dist/checks/submodules.js +172 -0
  47. package/dist/checks/submodules.js.map +1 -0
  48. package/dist/checks/suppressions.d.ts +53 -0
  49. package/dist/checks/suppressions.js +203 -0
  50. package/dist/checks/suppressions.js.map +1 -0
  51. package/dist/checks/workflow-permissions.d.ts +56 -0
  52. package/dist/checks/workflow-permissions.js +237 -0
  53. package/dist/checks/workflow-permissions.js.map +1 -0
  54. package/dist/checks/workflow-weakening.d.ts +38 -0
  55. package/dist/checks/workflow-weakening.js +172 -0
  56. package/dist/checks/workflow-weakening.js.map +1 -0
  57. package/dist/cli.d.ts +31 -0
  58. package/dist/cli.js +300 -0
  59. package/dist/cli.js.map +1 -0
  60. package/dist/commit-range.d.ts +28 -0
  61. package/dist/commit-range.js +152 -0
  62. package/dist/commit-range.js.map +1 -0
  63. package/dist/config.d.ts +35 -0
  64. package/dist/config.js +235 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/conflict-markers.d.ts +40 -0
  67. package/dist/conflict-markers.js +234 -0
  68. package/dist/conflict-markers.js.map +1 -0
  69. package/dist/dependency-sources.d.ts +32 -0
  70. package/dist/dependency-sources.js +260 -0
  71. package/dist/dependency-sources.js.map +1 -0
  72. package/dist/figure-claims.d.ts +35 -0
  73. package/dist/figure-claims.js +139 -0
  74. package/dist/figure-claims.js.map +1 -0
  75. package/dist/figure-text.d.ts +22 -0
  76. package/dist/figure-text.js +119 -0
  77. package/dist/figure-text.js.map +1 -0
  78. package/dist/index.d.ts +46 -0
  79. package/dist/index.js +38 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/large-blobs.d.ts +35 -0
  82. package/dist/large-blobs.js +163 -0
  83. package/dist/large-blobs.js.map +1 -0
  84. package/dist/lockfile-drift.d.ts +36 -0
  85. package/dist/lockfile-drift.js +206 -0
  86. package/dist/lockfile-drift.js.map +1 -0
  87. package/dist/lowered-thresholds.d.ts +70 -0
  88. package/dist/lowered-thresholds.js +281 -0
  89. package/dist/lowered-thresholds.js.map +1 -0
  90. package/dist/measurement-rule.d.ts +39 -0
  91. package/dist/measurement-rule.js +213 -0
  92. package/dist/measurement-rule.js.map +1 -0
  93. package/dist/portable-paths.d.ts +37 -0
  94. package/dist/portable-paths.js +215 -0
  95. package/dist/portable-paths.js.map +1 -0
  96. package/dist/registry.d.ts +43 -0
  97. package/dist/registry.js +144 -0
  98. package/dist/registry.js.map +1 -0
  99. package/dist/replay-plan.d.ts +36 -0
  100. package/dist/replay-plan.js +196 -0
  101. package/dist/replay-plan.js.map +1 -0
  102. package/dist/replayed-figures.d.ts +36 -0
  103. package/dist/replayed-figures.js +159 -0
  104. package/dist/replayed-figures.js.map +1 -0
  105. package/dist/snapshot-refreshes.d.ts +36 -0
  106. package/dist/snapshot-refreshes.js +202 -0
  107. package/dist/snapshot-refreshes.js.map +1 -0
  108. package/dist/stray-files.d.ts +52 -0
  109. package/dist/stray-files.js +234 -0
  110. package/dist/stray-files.js.map +1 -0
  111. package/dist/symlinks.d.ts +28 -0
  112. package/dist/symlinks.js +193 -0
  113. package/dist/symlinks.js.map +1 -0
  114. package/dist/trailers.d.ts +36 -0
  115. package/dist/trailers.js +110 -0
  116. package/dist/trailers.js.map +1 -0
  117. package/dist/weakened-tests.d.ts +94 -0
  118. package/dist/weakened-tests.js +458 -0
  119. package/dist/weakened-tests.js.map +1 -0
  120. package/package.json +31 -4
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Suppression directives.
3
+ *
4
+ * A line that no longer type-checks, a rule the change breaks, a branch no test
5
+ * reaches: each passes once a comment tells the gate not to look. This check
6
+ * reads the lines `base..candidate` removes and adds in source files, pairs each
7
+ * directive the range removes with one it adds in the same file, and reports an
8
+ * added directive left without a pair (`suppression-added`) for a human to
9
+ * sanction. Only the diff is read, through git.
10
+ *
11
+ * Which files and directives count lives in the exported
12
+ * `SUPPRESSION_DIRECTIVES`, read each time the check runs: a consumer appends
13
+ * its own entry. A directive only counts right after a comment opener.
14
+ */
15
+ import { execFile } from "node:child_process";
16
+ import { SANCTION_KEY } from "../weakened-tests.js";
17
+ export const name = "RT-31";
18
+ const SOURCE_FILE = /\.(?:[cm]?[jt]s|[jt]sx)$/;
19
+ /** A line comment opener, or a block one with any further asterisks, then white space. */
20
+ const OPENER = "(?:\\/\\/|\\/\\*+)\\s*";
21
+ function directive(label, words) {
22
+ return { name: label, files: SOURCE_FILE, pattern: new RegExp(`${OPENER}${words}(?![\\w-])`) };
23
+ }
24
+ export const SUPPRESSION_DIRECTIVES = [
25
+ directive("@ts-ignore", "@ts-ignore"),
26
+ directive("@ts-nocheck", "@ts-nocheck"),
27
+ directive("@ts-expect-error", "@ts-expect-error"),
28
+ directive("eslint-disable", "eslint-disable(?:-next-line|-line)?"),
29
+ directive("istanbul ignore", "istanbul\\s+ignore"),
30
+ directive("c8 ignore", "c8\\s+ignore"),
31
+ directive("v8 ignore", "v8\\s+ignore"),
32
+ ];
33
+ function runGit(cwd, args) {
34
+ return new Promise((resolve, reject) => {
35
+ execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
36
+ if (error)
37
+ reject(new Error(stderr.trim() || error.message));
38
+ else
39
+ resolve(stdout);
40
+ });
41
+ });
42
+ }
43
+ /** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
44
+ function escapeText(text) {
45
+ let escaped = "";
46
+ for (const char of text) {
47
+ const codePoint = char.codePointAt(0) ?? 0;
48
+ escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
49
+ }
50
+ return escaped;
51
+ }
52
+ async function resolveCommit(cwd, revision) {
53
+ const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
54
+ // A revision that begins with a dash would be read as an option.
55
+ if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
56
+ throw fail("");
57
+ let hash = "";
58
+ try {
59
+ hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
60
+ }
61
+ catch (error) {
62
+ throw fail(`: ${error.message}`);
63
+ }
64
+ if (hash.length === 0)
65
+ throw fail("");
66
+ return hash;
67
+ }
68
+ const NO_LINES = { removed: [], added: [] };
69
+ function wholeFile(text) {
70
+ return text.replace(/\n$/, "").split("\n").map((t, i) => ({ number: i + 1, text: t.replace(/\r$/, "") }));
71
+ }
72
+ /** The lines changed between two blobs; a missing side (`null`) is a file that is not there. */
73
+ async function diffBlobs(cwd, oldBlob, newBlob) {
74
+ const blobs = new Map();
75
+ for (const blob of [oldBlob, newBlob])
76
+ if (blob !== null)
77
+ blobs.set(blob, await runGit(cwd, ["cat-file", "blob", blob]));
78
+ if ([...blobs.values()].some((text) => text.includes("\0")))
79
+ return NO_LINES;
80
+ if (oldBlob === null || newBlob === null) {
81
+ const only = oldBlob ?? newBlob;
82
+ const lines = only === null ? [] : wholeFile(blobs.get(only) ?? "");
83
+ return oldBlob === null ? { removed: [], added: lines } : { removed: lines, added: [] };
84
+ }
85
+ const out = await runGit(cwd, ["diff", "-U0", "--no-color", "--no-ext-diff", "--no-textconv", oldBlob, newBlob]);
86
+ const removed = [];
87
+ const added = [];
88
+ let oldLine = 0;
89
+ let newLine = 0;
90
+ let inHunk = false;
91
+ for (const raw of out.split("\n")) {
92
+ const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(raw);
93
+ const text = raw.slice(1).replace(/\r$/, "");
94
+ if (hunk !== null) {
95
+ oldLine = Number(hunk[1]);
96
+ newLine = Number(hunk[2]);
97
+ inHunk = true;
98
+ }
99
+ else if (inHunk && raw.startsWith("-"))
100
+ removed.push({ number: oldLine++, text });
101
+ else if (inHunk && raw.startsWith("+"))
102
+ added.push({ number: newLine++, text });
103
+ }
104
+ return { removed, added };
105
+ }
106
+ async function readChanges(cwd, from, to) {
107
+ const out = await runGit(cwd, ["diff", "--raw", "-z", "--no-abbrev", "--find-renames", "--no-relative", from, to, "--"]);
108
+ const fields = out.split("\0");
109
+ const changes = [];
110
+ for (let i = 0; i < fields.length; i++) {
111
+ const head = fields[i] ?? "";
112
+ if (!head.startsWith(":"))
113
+ continue;
114
+ const [oldMode = "", newMode = "", oldSha = "", newSha = "", status = ""] = head.slice(1).split(" ");
115
+ const first = fields[++i] ?? "";
116
+ const second = status.startsWith("R") || status.startsWith("C") ? (fields[++i] ?? "") : first;
117
+ const blob = (mode, sha) => (mode.startsWith("100") ? sha : null);
118
+ const lines = await diffBlobs(cwd, blob(oldMode, oldSha), blob(newMode, newSha));
119
+ changes.push({ oldPath: status.startsWith("A") ? null : first, newPath: status.startsWith("D") ? null : second, ...lines });
120
+ }
121
+ return changes;
122
+ }
123
+ /** The line number of every match the entry finds in the lines, one per match. */
124
+ function hitsOf(entry, path, lines) {
125
+ if (path === null || !new RegExp(entry.files.source, entry.files.flags.replace(/[gy]/g, "")).test(path))
126
+ return [];
127
+ const pattern = new RegExp(entry.pattern.source, entry.pattern.flags.replace("g", "") + "g");
128
+ return lines.flatMap((line) => [...line.text.matchAll(pattern)].map(() => line.number));
129
+ }
130
+ function draftsOf(changes, entries) {
131
+ const drafts = [];
132
+ for (const change of changes) {
133
+ for (const entry of entries) {
134
+ const removed = hitsOf(entry, change.oldPath, change.removed);
135
+ const added = hitsOf(entry, change.newPath, change.added);
136
+ for (const line of added.slice(removed.length)) {
137
+ drafts.push({ category: "suppression-added", file: change.newPath ?? "", line, name: entry.name });
138
+ }
139
+ }
140
+ }
141
+ return drafts.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line));
142
+ }
143
+ /** The text after the sanction key on every such line of the range's commit messages. */
144
+ async function readSanctionLines(cwd, from, to) {
145
+ const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
146
+ const prefix = `${SANCTION_KEY}:`;
147
+ return out
148
+ .split(/[\n\0]/)
149
+ .map((line) => line.trim())
150
+ .filter((line) => line.startsWith(prefix))
151
+ .map((line) => line.slice(prefix.length).trim());
152
+ }
153
+ function sanctionFor(draft, id, lines, given) {
154
+ for (const key of [id, `${draft.category}:${draft.file}`]) {
155
+ const reason = Object.hasOwn(given, key) ? given[key] : undefined;
156
+ if (typeof reason === "string" && reason.trim().length > 0)
157
+ return reason.trim();
158
+ for (const line of lines) {
159
+ const rest = line.slice(key.length);
160
+ if (line.startsWith(key) && /^\s+\S/.test(rest))
161
+ return rest.trim();
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function describeFinding(finding) {
167
+ const tail = finding.sanction === null ? "" : ` - sanctioned: ${finding.sanction}`;
168
+ return `${escapeText(finding.file)}:${finding.line}: ${escapeText(`added ${finding.name}${tail}`)}`;
169
+ }
170
+ /**
171
+ * Report the suppression directives `base..candidate` adds without a pair.
172
+ * Fails while any finding is unsanctioned, and fails closed: a revision that
173
+ * names no commit, or any git invocation that fails, is a `fail` record naming
174
+ * the cause - never a `pass`, and never a thrown error.
175
+ */
176
+ export async function check(options) {
177
+ try {
178
+ const { cwd, base, candidate } = options;
179
+ const baseCommit = await resolveCommit(cwd, base);
180
+ const candidateCommit = await resolveCommit(cwd, candidate);
181
+ let ancestor;
182
+ try {
183
+ ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
184
+ }
185
+ catch (error) {
186
+ throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
187
+ }
188
+ const drafts = draftsOf(await readChanges(cwd, ancestor, candidateCommit), SUPPRESSION_DIRECTIVES);
189
+ const lines = drafts.length === 0 ? [] : await readSanctionLines(cwd, baseCommit, candidateCommit);
190
+ const findings = drafts.map((draft) => {
191
+ const id = `${draft.category}:${draft.file}:${draft.line}`;
192
+ return { id, ...draft, sanction: sanctionFor(draft, id, lines, options.sanctions ?? {}) };
193
+ });
194
+ if (findings.length === 0)
195
+ return { name, status: "pass", evidence: ["no suppression directive added"], findings };
196
+ const open = findings.some((finding) => finding.sanction === null);
197
+ return { name, status: open ? "fail" : "pass", evidence: findings.map(describeFinding), findings };
198
+ }
199
+ catch (error) {
200
+ return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))], findings: [] };
201
+ }
202
+ }
203
+ //# sourceMappingURL=suppressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.js","sourceRoot":"","sources":["../../src/checks/suppressions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Raised workflow permissions.
3
+ *
4
+ * A workflow's `permissions:` decide what the job's token may do, and the
5
+ * `pull_request_target` trigger runs a workflow with the base repository's token
6
+ * and secrets for a pull request from anyone. This check reads every workflow
7
+ * file the range adds, changes or renames onto - `.github/workflows/<name>.yml`
8
+ * or `.yaml` directly in that directory, no deeper and nowhere else - whole and
9
+ * through git at the merge base of `base` and `candidate` and at `candidate`,
10
+ * renames detected; the working tree is never read. The YAML is not parsed.
11
+ *
12
+ * A line's text is the line with white space at both ends removed; its
13
+ * value-text is that text with a trailing comment (a `#` after white space)
14
+ * removed. A line is added when the candidate's file holds its text more often
15
+ * than the source file held it - the file at the merge base, the one a rename
16
+ * came from, or none for a file the range adds. A line moved or re-indented is
17
+ * not added, and a text added more than once is one finding.
18
+ *
19
+ * Three categories, each judged on an added line's value-text with an opening
20
+ * `- ` removed; a value is the same plain, in double quotes or in single quotes,
21
+ * and white space around a colon does not matter:
22
+ * - `write-all`: the key `permissions` with the value `write-all`.
23
+ * - `write-permission`: a scope with the value `write`, as the whole value-text
24
+ * (`contents: write`) or as one entry of a one-line mapping that is the value
25
+ * of `permissions` (`permissions: { contents: write }`). The scopes are
26
+ * `actions`, `attestations`, `checks`, `contents`, `deployments`,
27
+ * `discussions`, `id-token`, `issues`, `models`, `packages`, `pages`,
28
+ * `pull-requests`, `repository-projects`, `security-events` and `statuses`.
29
+ * - `pull-request-target`: the key `pull_request_target` with or without a
30
+ * value, the list item `pull_request_target`, or the key `on` whose value is
31
+ * `pull_request_target` or a one-line list (`[...]`) holding it.
32
+ *
33
+ * Not a finding: `read`, `none`, `read-all`, `{}`, a scope not listed,
34
+ * `pull_request`, `workflow_run`, the words inside a comment line or inside a
35
+ * `run:` line's command.
36
+ *
37
+ * A finding's id is `<category>:<file>`; it is sanctioned by a
38
+ * `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range
39
+ * or by the caller's `sanctions`. The check declares no options.
40
+ */
41
+ import type { CheckRecord } from "../index.js";
42
+ export declare const name = "RT-91";
43
+ export interface WorkflowPermissionsOptions {
44
+ readonly cwd: string;
45
+ readonly base: string;
46
+ readonly candidate: string;
47
+ /** Ids (`<category>:<file>`) mapped to reasons. */
48
+ readonly sanctions?: Readonly<Record<string, string>> | undefined;
49
+ }
50
+ /**
51
+ * Report each line `base..candidate` adds to a workflow that raises the token's
52
+ * permissions or runs it on `pull_request_target`. Fails while any finding is
53
+ * unsanctioned, and fails closed: a revision that names no commit, or a git that
54
+ * fails, is a `fail` record naming the cause - never a `pass`, never a throw.
55
+ */
56
+ export declare function check(options: WorkflowPermissionsOptions): Promise<CheckRecord>;
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Raised workflow permissions.
3
+ *
4
+ * A workflow's `permissions:` decide what the job's token may do, and the
5
+ * `pull_request_target` trigger runs a workflow with the base repository's token
6
+ * and secrets for a pull request from anyone. This check reads every workflow
7
+ * file the range adds, changes or renames onto - `.github/workflows/<name>.yml`
8
+ * or `.yaml` directly in that directory, no deeper and nowhere else - whole and
9
+ * through git at the merge base of `base` and `candidate` and at `candidate`,
10
+ * renames detected; the working tree is never read. The YAML is not parsed.
11
+ *
12
+ * A line's text is the line with white space at both ends removed; its
13
+ * value-text is that text with a trailing comment (a `#` after white space)
14
+ * removed. A line is added when the candidate's file holds its text more often
15
+ * than the source file held it - the file at the merge base, the one a rename
16
+ * came from, or none for a file the range adds. A line moved or re-indented is
17
+ * not added, and a text added more than once is one finding.
18
+ *
19
+ * Three categories, each judged on an added line's value-text with an opening
20
+ * `- ` removed; a value is the same plain, in double quotes or in single quotes,
21
+ * and white space around a colon does not matter:
22
+ * - `write-all`: the key `permissions` with the value `write-all`.
23
+ * - `write-permission`: a scope with the value `write`, as the whole value-text
24
+ * (`contents: write`) or as one entry of a one-line mapping that is the value
25
+ * of `permissions` (`permissions: { contents: write }`). The scopes are
26
+ * `actions`, `attestations`, `checks`, `contents`, `deployments`,
27
+ * `discussions`, `id-token`, `issues`, `models`, `packages`, `pages`,
28
+ * `pull-requests`, `repository-projects`, `security-events` and `statuses`.
29
+ * - `pull-request-target`: the key `pull_request_target` with or without a
30
+ * value, the list item `pull_request_target`, or the key `on` whose value is
31
+ * `pull_request_target` or a one-line list (`[...]`) holding it.
32
+ *
33
+ * Not a finding: `read`, `none`, `read-all`, `{}`, a scope not listed,
34
+ * `pull_request`, `workflow_run`, the words inside a comment line or inside a
35
+ * `run:` line's command.
36
+ *
37
+ * A finding's id is `<category>:<file>`; it is sanctioned by a
38
+ * `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range
39
+ * or by the caller's `sanctions`. The check declares no options.
40
+ */
41
+ import { execFile } from "node:child_process";
42
+ import { SANCTION_KEY } from "../weakened-tests.js";
43
+ export const name = "RT-91";
44
+ const SCOPES = new Set([
45
+ "actions", "attestations", "checks", "contents", "deployments", "discussions", "id-token", "issues",
46
+ "models", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses",
47
+ ]);
48
+ function runGit(cwd, args) {
49
+ return new Promise((resolve, reject) => {
50
+ execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "buffer" }, (error, stdout, stderr) => {
51
+ if (error)
52
+ reject(new Error(stderr.toString("utf8").trim() || error.message));
53
+ else
54
+ resolve(stdout.toString("utf8"));
55
+ });
56
+ });
57
+ }
58
+ /** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
59
+ function escapeText(text) {
60
+ let escaped = "";
61
+ for (const char of text) {
62
+ const codePoint = char.codePointAt(0) ?? 0;
63
+ escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
64
+ }
65
+ return escaped;
66
+ }
67
+ async function resolveCommit(cwd, revision) {
68
+ const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
69
+ // A revision that begins with a dash would be read as an option.
70
+ if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
71
+ throw fail("");
72
+ let hash = "";
73
+ try {
74
+ hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
75
+ }
76
+ catch (error) {
77
+ throw fail(`: ${error.message}`);
78
+ }
79
+ if (hash.length === 0)
80
+ throw fail("");
81
+ return hash;
82
+ }
83
+ const isWorkflowPath = (path) => /^\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
84
+ const isNull = (sha) => /^0+$/.test(sha);
85
+ /** A blob read whole through git; an absent blob is an empty file. */
86
+ const readBlob = async (cwd, sha) => isNull(sha) ? "" : runGit(cwd, ["cat-file", "blob", sha]);
87
+ /** How many times each line's text occurs, lines being split at line feeds, one CR dropped, white space trimmed. */
88
+ function countLines(content) {
89
+ const counts = new Map();
90
+ for (const raw of content.split("\n")) {
91
+ const text = (raw.endsWith("\r") ? raw.slice(0, -1) : raw).trim();
92
+ if (text.length > 0)
93
+ counts.set(text, (counts.get(text) ?? 0) + 1);
94
+ }
95
+ return counts;
96
+ }
97
+ /** A scalar without the quotes around it, when it is wholly quoted. */
98
+ function unquote(value) {
99
+ const v = value.trim();
100
+ const first = v.charAt(0);
101
+ return v.length >= 2 && (first === '"' || first === "'") && v.endsWith(first) ? v.slice(1, -1).trim() : v;
102
+ }
103
+ /** The entries of a one-line `[...]` or `{...}`, or null when the value is not wrapped so. */
104
+ function flowEntries(value, open, close) {
105
+ const v = value.trim();
106
+ if (!v.startsWith(open) || !v.endsWith(close))
107
+ return null;
108
+ return v.slice(1, -1).split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
109
+ }
110
+ /** Splits `key : value`; the key is unquoted. */
111
+ function splitPair(text) {
112
+ const match = /^("[^"]*"|'[^']*'|[^\s:"'][^:]*?)\s*:(?:\s+(.*)|(.*))$/.exec(text);
113
+ if (match === null)
114
+ return null;
115
+ const rest = match[2] ?? match[3] ?? "";
116
+ // A colon with no space after it belongs to a scalar (`a:b`), unless nothing follows.
117
+ if (match[2] === undefined && rest.length > 0)
118
+ return null;
119
+ return { key: unquote(match[1] ?? ""), value: rest.trim() };
120
+ }
121
+ /** The category of an added line, or null. */
122
+ function categorize(text) {
123
+ const valueText = text.replace(/(?:^|\s)#.*$/, "").trimEnd().replace(/^-\s+/, "").trim();
124
+ if (valueText.length === 0)
125
+ return null;
126
+ if (unquote(valueText) === "pull_request_target")
127
+ return "pull-request-target";
128
+ const pair = splitPair(valueText);
129
+ if (pair === null)
130
+ return null;
131
+ const value = unquote(pair.value);
132
+ if (pair.key === "pull_request_target")
133
+ return "pull-request-target";
134
+ if (pair.key === "on") {
135
+ const list = flowEntries(pair.value, "[", "]");
136
+ const holds = value === "pull_request_target" || list?.some((entry) => unquote(entry) === "pull_request_target");
137
+ return holds ? "pull-request-target" : null;
138
+ }
139
+ if (pair.key === "permissions") {
140
+ if (value === "write-all")
141
+ return "write-all";
142
+ for (const entry of flowEntries(pair.value, "{", "}") ?? []) {
143
+ const inner = splitPair(entry);
144
+ if (inner !== null && SCOPES.has(inner.key) && unquote(inner.value) === "write")
145
+ return "write-permission";
146
+ }
147
+ return null;
148
+ }
149
+ return SCOPES.has(pair.key) && value === "write" ? "write-permission" : null;
150
+ }
151
+ async function compare(cwd, from, to) {
152
+ const out = await runGit(cwd, ["diff", "--raw", "-z", "-M", "--no-abbrev", "--no-ext-diff", "--no-relative", from, to, "--"]);
153
+ const fields = out.split("\0");
154
+ const drafts = [];
155
+ for (let i = 0; i < fields.length; i++) {
156
+ const head = fields[i] ?? "";
157
+ if (!head.startsWith(":"))
158
+ continue;
159
+ const [, , oldSha = "", newSha = "", status = ""] = head.slice(1).split(" ");
160
+ const moved = status.startsWith("R") || status.startsWith("C");
161
+ i++;
162
+ const newPath = moved ? (fields[++i] ?? "") : (fields[i] ?? "");
163
+ const letter = status.charAt(0);
164
+ if (!["A", "M", "R", "C", "T"].includes(letter) || !isWorkflowPath(newPath))
165
+ continue;
166
+ // A rename is read against the file it came from, whatever that path was.
167
+ const hasSource = letter === "M" || letter === "T" || letter === "R";
168
+ const before = countLines(hasSource ? await readBlob(cwd, oldSha) : "");
169
+ const after = countLines(await readBlob(cwd, newSha));
170
+ for (const [text, count] of after) {
171
+ if (count <= (before.get(text) ?? 0))
172
+ continue;
173
+ const category = categorize(text);
174
+ if (category !== null)
175
+ drafts.push({ category, file: newPath, line: text });
176
+ }
177
+ }
178
+ return drafts;
179
+ }
180
+ /** The text after the sanction key on every such line of the range's commit messages. */
181
+ async function readSanctionLines(cwd, from, to) {
182
+ const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
183
+ const prefix = `${SANCTION_KEY}:`;
184
+ return out
185
+ .split(/[\n\0]/)
186
+ .map((line) => line.trim())
187
+ .filter((line) => line.startsWith(prefix))
188
+ .map((line) => line.slice(prefix.length).trim());
189
+ }
190
+ function sanctionFor(id, lines, given) {
191
+ const reason = Object.hasOwn(given, id) ? given[id] : undefined;
192
+ if (typeof reason === "string" && reason.trim().length > 0)
193
+ return reason.trim();
194
+ for (const line of lines) {
195
+ const rest = line.slice(id.length);
196
+ if (line.startsWith(id) && /^\s+\S/.test(rest))
197
+ return rest.trim();
198
+ }
199
+ return null;
200
+ }
201
+ /**
202
+ * Report each line `base..candidate` adds to a workflow that raises the token's
203
+ * permissions or runs it on `pull_request_target`. Fails while any finding is
204
+ * unsanctioned, and fails closed: a revision that names no commit, or a git that
205
+ * fails, is a `fail` record naming the cause - never a `pass`, never a throw.
206
+ */
207
+ export async function check(options) {
208
+ try {
209
+ const { cwd, base, candidate } = options;
210
+ const baseCommit = await resolveCommit(cwd, base);
211
+ const candidateCommit = await resolveCommit(cwd, candidate);
212
+ let ancestor;
213
+ try {
214
+ ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
215
+ }
216
+ catch (error) {
217
+ throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
218
+ }
219
+ const drafts = await compare(cwd, ancestor, candidateCommit);
220
+ if (drafts.length === 0)
221
+ return { name, status: "pass", evidence: ["no workflow permission raised"] };
222
+ const lines = await readSanctionLines(cwd, baseCommit, candidateCommit);
223
+ let open = false;
224
+ const evidence = drafts.map((draft) => {
225
+ const reason = sanctionFor(`${draft.category}:${draft.file}`, lines, options.sanctions ?? {});
226
+ if (reason === null)
227
+ open = true;
228
+ const tail = reason === null ? "" : ` - sanctioned: ${escapeText(reason)}`;
229
+ return `${escapeText(draft.file)}: ${draft.category} "${escapeText(draft.line)}"${tail}`;
230
+ });
231
+ return { name, status: open ? "fail" : "pass", evidence };
232
+ }
233
+ catch (error) {
234
+ return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))] };
235
+ }
236
+ }
237
+ //# sourceMappingURL=workflow-permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-permissions.js","sourceRoot":"","sources":["../../src/checks/workflow-permissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Weakened workflows.
3
+ *
4
+ * The cheapest way to turn a red check green is to change the workflow rather
5
+ * than the code. This check reads every workflow file the range deletes, adds,
6
+ * changes or renames, whole and through git at the merge base and at the
7
+ * candidate, and reports each workflow the range removes (`workflow-removed`)
8
+ * and each line of a weakening shape (`WEAKENING_SHAPES`) that the candidate's
9
+ * file holds more often than its source did. A line moved or re-indented is not
10
+ * a new line. The YAML is not parsed, and the working tree is never read.
11
+ *
12
+ * A finding's id is `<category>:<file>`; it is sanctioned by a
13
+ * `Sanctioned-Weakening: <id> <reason>` line in a commit message of the range
14
+ * or by the caller's `sanctions`.
15
+ */
16
+ import type { CheckRecord } from "../index.js";
17
+ export declare const name = "RT-56";
18
+ export interface WeakeningShape {
19
+ readonly category: string;
20
+ /** Tested against a line's text with white space at both ends removed. */
21
+ readonly pattern: RegExp;
22
+ }
23
+ /** The shapes a line may have. A consumer appends to this array; it is read at each run. */
24
+ export declare const WEAKENING_SHAPES: WeakeningShape[];
25
+ export interface WorkflowWeakeningOptions {
26
+ readonly cwd: string;
27
+ readonly base: string;
28
+ readonly candidate: string;
29
+ /** Ids (`<category>:<file>`) mapped to reasons. */
30
+ readonly sanctions?: Readonly<Record<string, string>> | undefined;
31
+ }
32
+ /**
33
+ * Report each workflow `base..candidate` removes and each weakening line it adds.
34
+ * Fails while any finding is unsanctioned, and fails closed: a revision that
35
+ * names no commit, or any git invocation that fails, is a `fail` record naming
36
+ * the cause - never a `pass`, never a throw.
37
+ */
38
+ export declare function check(options: WorkflowWeakeningOptions): Promise<CheckRecord>;