@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.
- package/LICENSE +21 -0
- package/README.md +273 -2
- package/dist/action.d.ts +52 -0
- package/dist/action.js +182 -0
- package/dist/action.js.map +1 -0
- package/dist/checks/action-pins.d.ts +52 -0
- package/dist/checks/action-pins.js +196 -0
- package/dist/checks/action-pins.js.map +1 -0
- package/dist/checks/autosquash.d.ts +23 -0
- package/dist/checks/autosquash.js +85 -0
- package/dist/checks/autosquash.js.map +1 -0
- package/dist/checks/bidi-controls.d.ts +58 -0
- package/dist/checks/bidi-controls.js +172 -0
- package/dist/checks/bidi-controls.js.map +1 -0
- package/dist/checks/binary-files.d.ts +53 -0
- package/dist/checks/binary-files.js +157 -0
- package/dist/checks/binary-files.js.map +1 -0
- package/dist/checks/config-changes.d.ts +57 -0
- package/dist/checks/config-changes.js +175 -0
- package/dist/checks/config-changes.js.map +1 -0
- package/dist/checks/credentials.d.ts +35 -0
- package/dist/checks/credentials.js +138 -0
- package/dist/checks/credentials.js.map +1 -0
- package/dist/checks/install-scripts.d.ts +39 -0
- package/dist/checks/install-scripts.js +173 -0
- package/dist/checks/install-scripts.js.map +1 -0
- package/dist/checks/lint-rules.d.ts +49 -0
- package/dist/checks/lint-rules.js +184 -0
- package/dist/checks/lint-rules.js.map +1 -0
- package/dist/checks/path-policy.d.ts +40 -0
- package/dist/checks/path-policy.js +195 -0
- package/dist/checks/path-policy.js.map +1 -0
- package/dist/checks/raised-timeouts.d.ts +66 -0
- package/dist/checks/raised-timeouts.js +248 -0
- package/dist/checks/raised-timeouts.js.map +1 -0
- package/dist/checks/script-changes.d.ts +51 -0
- package/dist/checks/script-changes.js +199 -0
- package/dist/checks/script-changes.js.map +1 -0
- package/dist/checks/secret-files.d.ts +58 -0
- package/dist/checks/secret-files.js +165 -0
- package/dist/checks/secret-files.js.map +1 -0
- package/dist/checks/strictness.d.ts +59 -0
- package/dist/checks/strictness.js +260 -0
- package/dist/checks/strictness.js.map +1 -0
- package/dist/checks/submodules.d.ts +51 -0
- package/dist/checks/submodules.js +172 -0
- package/dist/checks/submodules.js.map +1 -0
- package/dist/checks/suppressions.d.ts +53 -0
- package/dist/checks/suppressions.js +203 -0
- package/dist/checks/suppressions.js.map +1 -0
- package/dist/checks/workflow-permissions.d.ts +56 -0
- package/dist/checks/workflow-permissions.js +237 -0
- package/dist/checks/workflow-permissions.js.map +1 -0
- package/dist/checks/workflow-weakening.d.ts +38 -0
- package/dist/checks/workflow-weakening.js +172 -0
- package/dist/checks/workflow-weakening.js.map +1 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +300 -0
- package/dist/cli.js.map +1 -0
- package/dist/commit-range.d.ts +28 -0
- package/dist/commit-range.js +152 -0
- package/dist/commit-range.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +235 -0
- package/dist/config.js.map +1 -0
- package/dist/conflict-markers.d.ts +40 -0
- package/dist/conflict-markers.js +234 -0
- package/dist/conflict-markers.js.map +1 -0
- package/dist/dependency-sources.d.ts +32 -0
- package/dist/dependency-sources.js +260 -0
- package/dist/dependency-sources.js.map +1 -0
- package/dist/figure-claims.d.ts +35 -0
- package/dist/figure-claims.js +139 -0
- package/dist/figure-claims.js.map +1 -0
- package/dist/figure-text.d.ts +22 -0
- package/dist/figure-text.js +119 -0
- package/dist/figure-text.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/large-blobs.d.ts +35 -0
- package/dist/large-blobs.js +163 -0
- package/dist/large-blobs.js.map +1 -0
- package/dist/lockfile-drift.d.ts +36 -0
- package/dist/lockfile-drift.js +206 -0
- package/dist/lockfile-drift.js.map +1 -0
- package/dist/lowered-thresholds.d.ts +70 -0
- package/dist/lowered-thresholds.js +281 -0
- package/dist/lowered-thresholds.js.map +1 -0
- package/dist/measurement-rule.d.ts +39 -0
- package/dist/measurement-rule.js +213 -0
- package/dist/measurement-rule.js.map +1 -0
- package/dist/portable-paths.d.ts +37 -0
- package/dist/portable-paths.js +215 -0
- package/dist/portable-paths.js.map +1 -0
- package/dist/registry.d.ts +43 -0
- package/dist/registry.js +144 -0
- package/dist/registry.js.map +1 -0
- package/dist/replay-plan.d.ts +36 -0
- package/dist/replay-plan.js +196 -0
- package/dist/replay-plan.js.map +1 -0
- package/dist/replayed-figures.d.ts +36 -0
- package/dist/replayed-figures.js +159 -0
- package/dist/replayed-figures.js.map +1 -0
- package/dist/snapshot-refreshes.d.ts +36 -0
- package/dist/snapshot-refreshes.js +202 -0
- package/dist/snapshot-refreshes.js.map +1 -0
- package/dist/stray-files.d.ts +52 -0
- package/dist/stray-files.js +234 -0
- package/dist/stray-files.js.map +1 -0
- package/dist/symlinks.d.ts +28 -0
- package/dist/symlinks.js +193 -0
- package/dist/symlinks.js.map +1 -0
- package/dist/trailers.d.ts +36 -0
- package/dist/trailers.js +110 -0
- package/dist/trailers.js.map +1 -0
- package/dist/weakened-tests.d.ts +94 -0
- package/dist/weakened-tests.js +458 -0
- package/dist/weakened-tests.js.map +1 -0
- package/package.json +31 -4
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ladder commands changed.
|
|
3
|
+
*
|
|
4
|
+
* A repository's checks run through its manifest: `npm test` runs whatever the
|
|
5
|
+
* `test` entry of `scripts` says. This check reads every package.json the range
|
|
6
|
+
* adds, deletes, changes or renames, at the root or below it, whole at the
|
|
7
|
+
* merge base and at the candidate through git, and reports each entry of
|
|
8
|
+
* `scripts` the range adds (`script-added`), removes (`script-removed`) or
|
|
9
|
+
* changes (`script-changed`) for a human to sanction. Another field, the order
|
|
10
|
+
* of the keys and white space say nothing. A manifest that cannot be read as a
|
|
11
|
+
* JSON object at either end, or whose `scripts` is not an object of strings, is
|
|
12
|
+
* a failure that names it.
|
|
13
|
+
*
|
|
14
|
+
* A finding is sanctioned by a `Sanctioned-Weakening: <id> <reason>` line in a
|
|
15
|
+
* commit message of the range or by the caller's `sanctions`. Its id is
|
|
16
|
+
* `<category>:<file>:<key>`; `<category>:<file>` sanctions every finding of
|
|
17
|
+
* that category in that file.
|
|
18
|
+
*/
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
import { SANCTION_KEY } from "../weakened-tests.js";
|
|
21
|
+
export const name = "RT-33";
|
|
22
|
+
function runGit(cwd, args) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
25
|
+
if (error)
|
|
26
|
+
reject(new Error(stderr.trim() || error.message));
|
|
27
|
+
else
|
|
28
|
+
resolve(stdout);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
|
|
33
|
+
function escapeText(text) {
|
|
34
|
+
let escaped = "";
|
|
35
|
+
for (const char of text) {
|
|
36
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
37
|
+
escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
|
|
38
|
+
}
|
|
39
|
+
return escaped;
|
|
40
|
+
}
|
|
41
|
+
/** A string in double quotes, with everything outside printable ASCII escaped. */
|
|
42
|
+
const quoted = (text) => `"${escapeText(text).replace(/"/g, '\\"')}"`;
|
|
43
|
+
async function resolveCommit(cwd, revision) {
|
|
44
|
+
const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
|
|
45
|
+
// A revision that begins with a dash would be read as an option.
|
|
46
|
+
if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
|
|
47
|
+
throw fail("");
|
|
48
|
+
let hash = "";
|
|
49
|
+
try {
|
|
50
|
+
hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw fail(`: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
if (hash.length === 0)
|
|
56
|
+
throw fail("");
|
|
57
|
+
return hash;
|
|
58
|
+
}
|
|
59
|
+
const NO_SCRIPTS = { scripts: new Map() };
|
|
60
|
+
async function readScripts(cwd, mode, sha) {
|
|
61
|
+
if (/^0+$/.test(sha))
|
|
62
|
+
return NO_SCRIPTS;
|
|
63
|
+
if (!mode.startsWith("100"))
|
|
64
|
+
return { problem: "is not a regular file" };
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse((await runGit(cwd, ["cat-file", "blob", sha])).replace(/^/, ""));
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return { problem: `is not JSON: ${error.message}` };
|
|
71
|
+
}
|
|
72
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
73
|
+
return { problem: "is not a JSON object" };
|
|
74
|
+
if (!Object.hasOwn(parsed, "scripts"))
|
|
75
|
+
return NO_SCRIPTS;
|
|
76
|
+
const scripts = parsed["scripts"];
|
|
77
|
+
if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts))
|
|
78
|
+
return { problem: "has a scripts field that is not an object" };
|
|
79
|
+
const entries = Object.entries(scripts);
|
|
80
|
+
if (!entries.every((entry) => typeof entry[1] === "string")) {
|
|
81
|
+
return { problem: "has a scripts field whose values are not all strings" };
|
|
82
|
+
}
|
|
83
|
+
return { scripts: new Map(entries) };
|
|
84
|
+
}
|
|
85
|
+
const isManifest = (path) => path === "package.json" || path.endsWith("/package.json");
|
|
86
|
+
async function compare(cwd, from, to) {
|
|
87
|
+
const out = await runGit(cwd, ["diff", "--raw", "-z", "--no-abbrev", "--find-renames", "--no-relative", from, to, "--"]);
|
|
88
|
+
const fields = out.split("\0");
|
|
89
|
+
const drafts = [];
|
|
90
|
+
const problems = [];
|
|
91
|
+
for (let i = 0; i < fields.length; i++) {
|
|
92
|
+
const head = fields[i] ?? "";
|
|
93
|
+
if (!head.startsWith(":"))
|
|
94
|
+
continue;
|
|
95
|
+
const [oldMode = "", newMode = "", oldSha = "", newSha = "", status = ""] = head.slice(1).split(" ");
|
|
96
|
+
const first = fields[++i] ?? "";
|
|
97
|
+
const renamed = status.startsWith("R") || status.startsWith("C");
|
|
98
|
+
const second = renamed ? (fields[++i] ?? "") : first;
|
|
99
|
+
const oldPath = status.startsWith("A") ? null : first;
|
|
100
|
+
const newPath = status.startsWith("D") ? null : second;
|
|
101
|
+
const oldHere = oldPath !== null && isManifest(oldPath);
|
|
102
|
+
const newHere = newPath !== null && isManifest(newPath);
|
|
103
|
+
if (!oldHere && !newHere)
|
|
104
|
+
continue;
|
|
105
|
+
const file = newHere ? newPath : (oldPath ?? "");
|
|
106
|
+
const before = oldHere ? await readScripts(cwd, oldMode, oldSha) : NO_SCRIPTS;
|
|
107
|
+
const after = newHere ? await readScripts(cwd, newMode, newSha) : NO_SCRIPTS;
|
|
108
|
+
if ("problem" in before)
|
|
109
|
+
problems.push(`${escapeText(oldPath ?? file)}: at the merge base the manifest ${escapeText(before.problem)}`);
|
|
110
|
+
if ("problem" in after)
|
|
111
|
+
problems.push(`${escapeText(file)}: at the candidate the manifest ${escapeText(after.problem)}`);
|
|
112
|
+
if ("problem" in before || "problem" in after)
|
|
113
|
+
continue;
|
|
114
|
+
for (const [key, command] of after.scripts) {
|
|
115
|
+
const old = before.scripts.get(key);
|
|
116
|
+
if (old === undefined)
|
|
117
|
+
drafts.push({ category: "script-added", file, key, before: null, after: command });
|
|
118
|
+
else if (old !== command)
|
|
119
|
+
drafts.push({ category: "script-changed", file, key, before: old, after: command });
|
|
120
|
+
}
|
|
121
|
+
for (const [key, command] of before.scripts) {
|
|
122
|
+
if (!after.scripts.has(key))
|
|
123
|
+
drafts.push({ category: "script-removed", file, key, before: command, after: null });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const order = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
127
|
+
drafts.sort((a, b) => order(a.file, b.file) || order(a.key, b.key));
|
|
128
|
+
return { drafts, problems };
|
|
129
|
+
}
|
|
130
|
+
/** The text after the sanction key on every such line of the range's commit messages. */
|
|
131
|
+
async function readSanctionLines(cwd, from, to) {
|
|
132
|
+
const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
|
|
133
|
+
const prefix = `${SANCTION_KEY}:`;
|
|
134
|
+
return out
|
|
135
|
+
.split(/[\n\0]/)
|
|
136
|
+
.map((line) => line.trim())
|
|
137
|
+
.filter((line) => line.startsWith(prefix))
|
|
138
|
+
.map((line) => line.slice(prefix.length).trim());
|
|
139
|
+
}
|
|
140
|
+
function sanctionFor(category, file, id, lines, given) {
|
|
141
|
+
for (const key of [id, `${category}:${file}`]) {
|
|
142
|
+
const reason = Object.hasOwn(given, key) ? given[key] : undefined;
|
|
143
|
+
if (typeof reason === "string" && reason.trim().length > 0)
|
|
144
|
+
return reason.trim();
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
const rest = line.slice(key.length);
|
|
147
|
+
if (line.startsWith(key) && /^\s+\S/.test(rest))
|
|
148
|
+
return rest.trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function describeFinding(finding) {
|
|
154
|
+
const key = quoted(finding.key);
|
|
155
|
+
let text;
|
|
156
|
+
if (finding.category === "script-added")
|
|
157
|
+
text = `added ${key}: ${quoted(finding.after ?? "")}`;
|
|
158
|
+
else if (finding.category === "script-removed")
|
|
159
|
+
text = `removed ${key}: ${quoted(finding.before ?? "")}`;
|
|
160
|
+
else
|
|
161
|
+
text = `changed ${key}: ${quoted(finding.before ?? "")} -> ${quoted(finding.after ?? "")}`;
|
|
162
|
+
const tail = finding.sanction === null ? "" : ` - sanctioned: ${escapeText(finding.sanction)}`;
|
|
163
|
+
return `${escapeText(finding.file)}: ${text}${tail}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Report each `scripts` entry `base..candidate` adds, removes or changes.
|
|
167
|
+
* Fails while any finding is unsanctioned or a manifest cannot be read, and
|
|
168
|
+
* fails closed: a revision that names no commit, or any git invocation that
|
|
169
|
+
* fails, is a `fail` record naming the cause - never a `pass`, never a throw.
|
|
170
|
+
*/
|
|
171
|
+
export async function check(options) {
|
|
172
|
+
try {
|
|
173
|
+
const { cwd, base, candidate } = options;
|
|
174
|
+
const baseCommit = await resolveCommit(cwd, base);
|
|
175
|
+
const candidateCommit = await resolveCommit(cwd, candidate);
|
|
176
|
+
let ancestor;
|
|
177
|
+
try {
|
|
178
|
+
ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
const { drafts, problems } = await compare(cwd, ancestor, candidateCommit);
|
|
184
|
+
const lines = drafts.length === 0 ? [] : await readSanctionLines(cwd, baseCommit, candidateCommit);
|
|
185
|
+
const findings = drafts.map((draft) => {
|
|
186
|
+
const id = `${draft.category}:${draft.file}:${draft.key}`;
|
|
187
|
+
return { id, ...draft, sanction: sanctionFor(draft.category, draft.file, id, lines, options.sanctions ?? {}) };
|
|
188
|
+
});
|
|
189
|
+
if (findings.length === 0 && problems.length === 0) {
|
|
190
|
+
return { name, status: "pass", evidence: ["no scripts entry added, removed or changed"], findings };
|
|
191
|
+
}
|
|
192
|
+
const open = problems.length > 0 || findings.some((finding) => finding.sanction === null);
|
|
193
|
+
return { name, status: open ? "fail" : "pass", evidence: [...problems, ...findings.map(describeFinding)], findings };
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))], findings: [] };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=script-changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-changes.js","sourceRoot":"","sources":["../../src/checks/script-changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret files added or changed.
|
|
3
|
+
*
|
|
4
|
+
* Some files hold secrets by what they are, not by how a line of them looks. A
|
|
5
|
+
* file is judged by its last path segment, compared exactly (letter case
|
|
6
|
+
* included) at any depth, and never by its content. The exported
|
|
7
|
+
* `SECRET_FILE_NAMES` holds the regular expressions tested against that
|
|
8
|
+
* segment, read each time the check runs so a consumer can append to it. As
|
|
9
|
+
* shipped it matches `.env`, `.env.<suffix>`, `.netrc`, `_netrc`, `.pgpass`,
|
|
10
|
+
* `.git-credentials`, `.htpasswd`, `id_rsa`, `id_dsa`, `id_ecdsa`,
|
|
11
|
+
* `id_ed25519` and any name ending `.tfstate` or `.tfstate.backup`. It does not
|
|
12
|
+
* match a `.env` suffix whose last dot-separated part is `example`, `sample`,
|
|
13
|
+
* `template` or `dist` (so `.env.local.example` is left alone), nor `.pub`
|
|
14
|
+
* files, `.ENV`, `env`, `my.env` or `.envrc`.
|
|
15
|
+
*
|
|
16
|
+
* The range is read through git from the merge base of `base` and `candidate`
|
|
17
|
+
* to `candidate`, renames and copies detected. A path at the candidate that is
|
|
18
|
+
* a regular file whose name matches is `secret-file-added` when the range adds,
|
|
19
|
+
* copies or renames it into place, or turns a symbolic link or a submodule into
|
|
20
|
+
* it, and `secret-file-changed` when the range modifies it. A deletion, or a
|
|
21
|
+
* rename away to a name that does not match, is no finding.
|
|
22
|
+
*
|
|
23
|
+
* No evidence line holds any text of a file. A finding's id is
|
|
24
|
+
* `<category>:<path>`; a `Sanctioned-Weakening: <id> <reason>` line in a commit
|
|
25
|
+
* message of the range, or the caller's `sanctions`, sanctions it. The check
|
|
26
|
+
* declares no options.
|
|
27
|
+
*/
|
|
28
|
+
import type { CheckRecord } from "../index.js";
|
|
29
|
+
export declare const name = "RT-89";
|
|
30
|
+
export type SecretFileCategory = "secret-file-added" | "secret-file-changed";
|
|
31
|
+
export interface SecretFilesOptions {
|
|
32
|
+
readonly cwd: string;
|
|
33
|
+
readonly base: string;
|
|
34
|
+
readonly candidate: string;
|
|
35
|
+
/** Ids mapped to reasons. */
|
|
36
|
+
readonly sanctions?: Readonly<Record<string, string>> | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface SecretFileFinding {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly category: SecretFileCategory;
|
|
41
|
+
readonly path: string;
|
|
42
|
+
/** The source of a rename, else `null`. */
|
|
43
|
+
readonly from: string | null;
|
|
44
|
+
/** The sanction's reason, or `null` while unsanctioned. */
|
|
45
|
+
readonly sanction: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface SecretFilesRecord extends CheckRecord {
|
|
48
|
+
readonly findings: readonly SecretFileFinding[];
|
|
49
|
+
}
|
|
50
|
+
/** Tested with `RegExp.prototype.test` against the last path segment; read on every run. */
|
|
51
|
+
export declare const SECRET_FILE_NAMES: RegExp[];
|
|
52
|
+
/**
|
|
53
|
+
* Report each secret-named file `base..candidate` adds or changes. Fails while
|
|
54
|
+
* any finding is unsanctioned, and fails closed: a revision that names no
|
|
55
|
+
* commit, or any git invocation that fails, is a `fail` record naming the
|
|
56
|
+
* cause - never a `pass`, never a throw.
|
|
57
|
+
*/
|
|
58
|
+
export declare function check(options: SecretFilesOptions): Promise<SecretFilesRecord>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret files added or changed.
|
|
3
|
+
*
|
|
4
|
+
* Some files hold secrets by what they are, not by how a line of them looks. A
|
|
5
|
+
* file is judged by its last path segment, compared exactly (letter case
|
|
6
|
+
* included) at any depth, and never by its content. The exported
|
|
7
|
+
* `SECRET_FILE_NAMES` holds the regular expressions tested against that
|
|
8
|
+
* segment, read each time the check runs so a consumer can append to it. As
|
|
9
|
+
* shipped it matches `.env`, `.env.<suffix>`, `.netrc`, `_netrc`, `.pgpass`,
|
|
10
|
+
* `.git-credentials`, `.htpasswd`, `id_rsa`, `id_dsa`, `id_ecdsa`,
|
|
11
|
+
* `id_ed25519` and any name ending `.tfstate` or `.tfstate.backup`. It does not
|
|
12
|
+
* match a `.env` suffix whose last dot-separated part is `example`, `sample`,
|
|
13
|
+
* `template` or `dist` (so `.env.local.example` is left alone), nor `.pub`
|
|
14
|
+
* files, `.ENV`, `env`, `my.env` or `.envrc`.
|
|
15
|
+
*
|
|
16
|
+
* The range is read through git from the merge base of `base` and `candidate`
|
|
17
|
+
* to `candidate`, renames and copies detected. A path at the candidate that is
|
|
18
|
+
* a regular file whose name matches is `secret-file-added` when the range adds,
|
|
19
|
+
* copies or renames it into place, or turns a symbolic link or a submodule into
|
|
20
|
+
* it, and `secret-file-changed` when the range modifies it. A deletion, or a
|
|
21
|
+
* rename away to a name that does not match, is no finding.
|
|
22
|
+
*
|
|
23
|
+
* No evidence line holds any text of a file. A finding's id is
|
|
24
|
+
* `<category>:<path>`; a `Sanctioned-Weakening: <id> <reason>` line in a commit
|
|
25
|
+
* message of the range, or the caller's `sanctions`, sanctions it. The check
|
|
26
|
+
* declares no options.
|
|
27
|
+
*/
|
|
28
|
+
import { execFile } from "node:child_process";
|
|
29
|
+
import { SANCTION_KEY } from "../weakened-tests.js";
|
|
30
|
+
export const name = "RT-89";
|
|
31
|
+
/** Tested with `RegExp.prototype.test` against the last path segment; read on every run. */
|
|
32
|
+
export const SECRET_FILE_NAMES = [
|
|
33
|
+
/^\.env$/,
|
|
34
|
+
/^\.env\.(?:.*\.)?(?!(?:example|sample|template|dist)$)[^.]*$/,
|
|
35
|
+
/^\.netrc$/,
|
|
36
|
+
/^_netrc$/,
|
|
37
|
+
/^\.pgpass$/,
|
|
38
|
+
/^\.git-credentials$/,
|
|
39
|
+
/^\.htpasswd$/,
|
|
40
|
+
/^id_(?:rsa|dsa|ecdsa|ed25519)$/,
|
|
41
|
+
/\.tfstate(?:\.backup)?$/,
|
|
42
|
+
];
|
|
43
|
+
function runGit(cwd, args) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
execFile("git", args, { cwd, maxBuffer: 1024 * 1024 * 1024, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
46
|
+
if (error)
|
|
47
|
+
reject(new Error(stderr.trim() || error.message));
|
|
48
|
+
else
|
|
49
|
+
resolve(stdout);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Escape everything outside printable ASCII, so evidence cannot replay a control sequence. */
|
|
54
|
+
function escapeText(text) {
|
|
55
|
+
let escaped = "";
|
|
56
|
+
for (const char of text) {
|
|
57
|
+
const codePoint = char.codePointAt(0) ?? 0;
|
|
58
|
+
escaped += codePoint >= 0x20 && codePoint <= 0x7e ? char : `\\u{${codePoint.toString(16).padStart(4, "0")}}`;
|
|
59
|
+
}
|
|
60
|
+
return escaped;
|
|
61
|
+
}
|
|
62
|
+
async function resolveCommit(cwd, revision) {
|
|
63
|
+
const fail = (cause) => new Error(`revision "${escapeText(String(revision))}" does not name a commit${cause}`);
|
|
64
|
+
// A revision that begins with a dash would be read as an option.
|
|
65
|
+
if (typeof revision !== "string" || revision.length === 0 || revision.startsWith("-"))
|
|
66
|
+
throw fail("");
|
|
67
|
+
let hash = "";
|
|
68
|
+
try {
|
|
69
|
+
hash = (await runGit(cwd, ["rev-parse", "--verify", "--quiet", `${revision}^{commit}`])).trim();
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw fail(`: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
if (hash.length === 0)
|
|
75
|
+
throw fail("");
|
|
76
|
+
return hash;
|
|
77
|
+
}
|
|
78
|
+
function isSecretName(path) {
|
|
79
|
+
const segment = path.slice(path.lastIndexOf("/") + 1);
|
|
80
|
+
return SECRET_FILE_NAMES.some((pattern) => {
|
|
81
|
+
pattern.lastIndex = 0;
|
|
82
|
+
return pattern.test(segment);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function compare(cwd, from, to) {
|
|
86
|
+
const out = await runGit(cwd, ["diff", "--raw", "-z", "--no-abbrev", "--find-renames", "--find-copies", "--find-copies-harder", "--no-relative", from, to, "--"]);
|
|
87
|
+
const fields = out.split("\0");
|
|
88
|
+
const drafts = [];
|
|
89
|
+
for (let i = 0; i < fields.length; i++) {
|
|
90
|
+
const head = fields[i] ?? "";
|
|
91
|
+
if (!head.startsWith(":"))
|
|
92
|
+
continue;
|
|
93
|
+
const [, newMode = "", , , status = ""] = head.slice(1).split(" ");
|
|
94
|
+
const first = fields[++i] ?? "";
|
|
95
|
+
const moved = status.startsWith("R") || status.startsWith("C");
|
|
96
|
+
const path = moved ? (fields[++i] ?? "") : first;
|
|
97
|
+
if (status.startsWith("D") || !newMode.startsWith("100") || !isSecretName(path))
|
|
98
|
+
continue;
|
|
99
|
+
if (status.startsWith("M"))
|
|
100
|
+
drafts.push({ category: "secret-file-changed", path, from: null });
|
|
101
|
+
else
|
|
102
|
+
drafts.push({ category: "secret-file-added", path, from: status.startsWith("R") ? first : null });
|
|
103
|
+
}
|
|
104
|
+
return drafts.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
105
|
+
}
|
|
106
|
+
/** The text after the sanction key on every such line of the range's commit messages. */
|
|
107
|
+
async function readSanctionLines(cwd, from, to) {
|
|
108
|
+
const out = await runGit(cwd, ["log", "--format=%B%x00", `${from}..${to}`, "--"]);
|
|
109
|
+
const prefix = `${SANCTION_KEY}:`;
|
|
110
|
+
return out
|
|
111
|
+
.split(/[\n\0]/)
|
|
112
|
+
.map((line) => line.trim())
|
|
113
|
+
.filter((line) => line.startsWith(prefix))
|
|
114
|
+
.map((line) => line.slice(prefix.length).trim());
|
|
115
|
+
}
|
|
116
|
+
function sanctionFor(id, lines, given) {
|
|
117
|
+
const reason = Object.hasOwn(given, id) ? given[id] : undefined;
|
|
118
|
+
if (typeof reason === "string" && reason.trim().length > 0)
|
|
119
|
+
return reason.trim();
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
const rest = line.slice(id.length);
|
|
122
|
+
if (line.startsWith(id) && /^\s+\S/.test(rest))
|
|
123
|
+
return rest.trim();
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function describeFinding(finding) {
|
|
128
|
+
const source = finding.from === null ? "" : ` from ${escapeText(finding.from)}`;
|
|
129
|
+
const tail = finding.sanction === null ? "" : ` - sanctioned: ${escapeText(finding.sanction)}`;
|
|
130
|
+
return `${escapeText(finding.path)}: ${finding.category}${source}${tail}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Report each secret-named file `base..candidate` adds or changes. Fails while
|
|
134
|
+
* any finding is unsanctioned, and fails closed: a revision that names no
|
|
135
|
+
* commit, or any git invocation that fails, is a `fail` record naming the
|
|
136
|
+
* cause - never a `pass`, never a throw.
|
|
137
|
+
*/
|
|
138
|
+
export async function check(options) {
|
|
139
|
+
try {
|
|
140
|
+
const { cwd, base, candidate } = options;
|
|
141
|
+
const baseCommit = await resolveCommit(cwd, base);
|
|
142
|
+
const candidateCommit = await resolveCommit(cwd, candidate);
|
|
143
|
+
let ancestor;
|
|
144
|
+
try {
|
|
145
|
+
ancestor = (await runGit(cwd, ["merge-base", baseCommit, candidateCommit])).trim();
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
throw new Error(`no merge base of "${escapeText(base)}" and "${escapeText(candidate)}": ${error.message}`);
|
|
149
|
+
}
|
|
150
|
+
const drafts = await compare(cwd, ancestor, candidateCommit);
|
|
151
|
+
if (drafts.length === 0)
|
|
152
|
+
return { name, status: "pass", evidence: ["no secret file added or changed"], findings: [] };
|
|
153
|
+
const lines = await readSanctionLines(cwd, baseCommit, candidateCommit);
|
|
154
|
+
const findings = drafts.map((draft) => {
|
|
155
|
+
const id = `${draft.category}:${draft.path}`;
|
|
156
|
+
return { id, ...draft, sanction: sanctionFor(id, lines, options.sanctions ?? {}) };
|
|
157
|
+
});
|
|
158
|
+
const open = findings.some((finding) => finding.sanction === null);
|
|
159
|
+
return { name, status: open ? "fail" : "pass", evidence: findings.map(describeFinding), findings };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return { name, status: "fail", evidence: [escapeText(error instanceof Error ? error.message : String(error))], findings: [] };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=secret-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-files.js","sourceRoot":"","sources":["../../src/checks/secret-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiler strictness lowered.
|
|
3
|
+
*
|
|
4
|
+
* Reads every tsconfig the range adds, deletes, changes or renames, whole
|
|
5
|
+
* through git, at each end at which the path is a tsconfig, as the compiler
|
|
6
|
+
* reads one (JSON with comments and trailing commas). Reports each strict
|
|
7
|
+
* option that held its strict value at the merge base and does not at the
|
|
8
|
+
* candidate (`strictness-lowered`) and each change of `extends`
|
|
9
|
+
* (`extends-changed`). A file the range adds is compared with nothing; one it
|
|
10
|
+
* deletes is compared with a file that sets nothing. `extends` is not followed.
|
|
11
|
+
* A file that cannot be read at an end at which it is read fails the record.
|
|
12
|
+
*
|
|
13
|
+
* A finding is sanctioned by a `Sanctioned-Weakening: <id> <reason>` line in a
|
|
14
|
+
* commit message of the range or by the caller's `sanctions`. Its id is
|
|
15
|
+
* `<category>:<file>:<option>`; `<category>:<file>` sanctions every finding of
|
|
16
|
+
* that category in that file.
|
|
17
|
+
*/
|
|
18
|
+
import type { CheckRecord } from "../index.js";
|
|
19
|
+
export declare const name = "RT-34";
|
|
20
|
+
/** An option of `compilerOptions`, its strict value, and whether `strict` sets it by default. */
|
|
21
|
+
export interface StrictOption {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly strict: boolean;
|
|
24
|
+
readonly fromStrict: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** The options the check judges. A consumer appends to it; it is read at every run. */
|
|
27
|
+
export declare const STRICT_OPTIONS: StrictOption[];
|
|
28
|
+
export type StrictnessCategory = "strictness-lowered" | "extends-changed";
|
|
29
|
+
export interface StrictnessOptions {
|
|
30
|
+
readonly cwd: string;
|
|
31
|
+
readonly base: string;
|
|
32
|
+
readonly candidate: string;
|
|
33
|
+
/** Ids (or `<category>:<file>`) mapped to reasons. */
|
|
34
|
+
readonly sanctions?: Readonly<Record<string, string>> | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface StrictnessFinding {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly category: StrictnessCategory;
|
|
39
|
+
readonly file: string;
|
|
40
|
+
/** The option, or `extends`. */
|
|
41
|
+
readonly option: string;
|
|
42
|
+
/** The value at the merge base: a boolean, or the `extends` text. */
|
|
43
|
+
readonly before: boolean | string;
|
|
44
|
+
/** The value at the candidate. */
|
|
45
|
+
readonly after: boolean | string;
|
|
46
|
+
/** The sanction's reason, or `null` while unsanctioned. */
|
|
47
|
+
readonly sanction: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface StrictnessRecord extends CheckRecord {
|
|
50
|
+
readonly findings: readonly StrictnessFinding[];
|
|
51
|
+
}
|
|
52
|
+
/** JSON with comments and trailing commas, as the compiler reads a tsconfig. */
|
|
53
|
+
export declare function parseTsconfig(text: string): unknown;
|
|
54
|
+
/**
|
|
55
|
+
* Report each strict option `base..candidate` lowers and each change of
|
|
56
|
+
* `extends`. Fails closed: a revision that names no commit, or a git
|
|
57
|
+
* invocation that fails, is a `fail` record naming the cause.
|
|
58
|
+
*/
|
|
59
|
+
export declare function check(options: StrictnessOptions): Promise<StrictnessRecord>;
|