pm-ops 2026.8.31 → 2026.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/attestation.d.ts +177 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +593 -0
- package/dist/attestation.js.map +1 -0
- package/dist/index.d.ts +101 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/shell-scan.d.ts +438 -0
- package/dist/shell-scan.d.ts.map +1 -0
- package/dist/shell-scan.js +1516 -0
- package/dist/shell-scan.js.map +1 -0
- package/manifest.json +1 -1
- package/package.json +12 -3
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proves this package has no publish path that omits `--provenance`.
|
|
3
|
+
*
|
|
4
|
+
* The release step used to fall back to `npm publish` without the flag after
|
|
5
|
+
* three failed provenance attempts, reporting success and leaving only a
|
|
6
|
+
* warning annotation. That makes a transient registry failure downgrade the
|
|
7
|
+
* published artifact's supply-chain attestation permanently for that version,
|
|
8
|
+
* and consumers cannot tell such a publish apart from one that never had
|
|
9
|
+
* provenance at all. An unattested publish is not a degraded success; it is a
|
|
10
|
+
* different artifact.
|
|
11
|
+
*
|
|
12
|
+
* A workflow edit is easy to make and easy to lose, so the contract is executed
|
|
13
|
+
* rather than assumed: every `npm publish` this repository can run is found and
|
|
14
|
+
* required to carry the flag. The analysis is separated from the I/O so the
|
|
15
|
+
* rules are driven by the suite against fixtures rather than only against this
|
|
16
|
+
* repository, which happens to satisfy them.
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
import { execFileSync } from "node:child_process";
|
|
21
|
+
import { closeSync, openSync, readFileSync, readSync } from "node:fs";
|
|
22
|
+
import { resolve } from "node:path";
|
|
23
|
+
import { bashArrays, blockDepthChange, caseDepthChange, commandArguments, commandCandidates, commandName, dedentRunBlocks, expandArrays, expandScalars, heredocBodyLines, heredocExpansionLines, joinContinuations, literalScalarAssignments, segmentShellLine, startsEnclosingCaseArm, tokenizeCommands, unsetNames, } from "./shell-scan.js";
|
|
24
|
+
/** The flag that attaches a build attestation to the published tarball. */
|
|
25
|
+
export const ATTESTATION_FLAG = "--provenance";
|
|
26
|
+
/** Publishers other than npm, which this repository has no attested path for. */
|
|
27
|
+
export const FOREIGN_PUBLISHERS = new Set(["yarn", "pnpm", "bun"]);
|
|
28
|
+
/** Repository subtrees whose contents are build output rather than a publish path. */
|
|
29
|
+
const GENERATED_PREFIXES = ["dist/", "coverage/", "node_modules/", ".agents/pm/runtime/"];
|
|
30
|
+
/**
|
|
31
|
+
* The workflow-shaped path patterns whose `run:` blocks GitHub Actions
|
|
32
|
+
* executes as shell, defined once so the workflow entries in
|
|
33
|
+
* {@link EXECUTABLE_PATHS} and the {@link isWorkflowYaml} test can never
|
|
34
|
+
* drift apart. A workflow file and a composite action are the two shapes a
|
|
35
|
+
* YAML parser hands to bash with the block indentation removed.
|
|
36
|
+
*/
|
|
37
|
+
const WORKFLOW_PATHS = [
|
|
38
|
+
/^\.github\/workflows\/[^/]+\.ya?ml$/,
|
|
39
|
+
// A composite action is recognised by its FILE NAME, wherever it sits - a
|
|
40
|
+
// repository that is itself an action carries `action.yml` at its root, and
|
|
41
|
+
// matching only `.github/actions/**` excludes it. Its `run:` blocks are shell
|
|
42
|
+
// GitHub executes, so leaving them unscanned lets an unattested publish hide
|
|
43
|
+
// there while an attested sibling keeps the audit non-vacuous.
|
|
44
|
+
/(^|\/)action\.ya?ml$/,
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* Whether a repository-relative path is a workflow file whose `run:` blocks
|
|
48
|
+
* GitHub Actions executes as shell. These are the tracked files whose shell
|
|
49
|
+
* text a YAML parser hands to bash with the block indentation removed, so they
|
|
50
|
+
* are the files whose `run:` blocks are dedented before scanning. A plain
|
|
51
|
+
* shell script is not on this list: its indentation is the shell's own, and a
|
|
52
|
+
* heredoc terminator indented there is not a terminator in bash either.
|
|
53
|
+
*/
|
|
54
|
+
const isWorkflowYaml = (path) => WORKFLOW_PATHS.some((pattern) => pattern.test(path));
|
|
55
|
+
/** Tracked paths that can execute a command, matched against the repository-relative path. */
|
|
56
|
+
const EXECUTABLE_PATHS = [
|
|
57
|
+
...WORKFLOW_PATHS,
|
|
58
|
+
/(^|\/)package\.json$/,
|
|
59
|
+
/\.(sh|bash|zsh|ksh)$/,
|
|
60
|
+
/(^|\/)(Makefile|makefile|GNUmakefile)$/,
|
|
61
|
+
/\.mk$/,
|
|
62
|
+
/(^|\/)Dockerfile([.-][^/]*)?$/,
|
|
63
|
+
/(^|\/)docker-compose([.-][^/]*)?\.ya?ml$/,
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Yield the command text held inside a package manifest.
|
|
67
|
+
*
|
|
68
|
+
* A manifest is JSON, so its script bodies are string values rather than lines
|
|
69
|
+
* of shell. Handing the raw file to a shell tokeniser would read the JSON
|
|
70
|
+
* punctuation as commands and the script bodies as quoted words. Parsing the
|
|
71
|
+
* manifest and returning the bodies restores them to the shape the scanner
|
|
72
|
+
* expects, which matters because a publish moved into an npm script is entirely
|
|
73
|
+
* real and would otherwise be invisible to this gate.
|
|
74
|
+
*
|
|
75
|
+
* A manifest that will not parse yields nothing rather than throwing, so a
|
|
76
|
+
* malformed sibling file cannot take the gate down; the manifest's own tooling
|
|
77
|
+
* reports that far better than a publish audit can.
|
|
78
|
+
*
|
|
79
|
+
* @param text - The manifest's contents.
|
|
80
|
+
* @returns Each script body in manifest order.
|
|
81
|
+
*/
|
|
82
|
+
function manifestCommandBodies(text) {
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(text);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
91
|
+
return [];
|
|
92
|
+
const scripts = parsed.scripts;
|
|
93
|
+
if (typeof scripts !== "object" || scripts === null)
|
|
94
|
+
return [];
|
|
95
|
+
return Object.values(scripts)
|
|
96
|
+
.filter((value) => typeof value === "string");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Join manifest script bodies for legacy text consumers without continuations.
|
|
100
|
+
*
|
|
101
|
+
* The auditor itself analyzes each returned body in a fresh scope; this text
|
|
102
|
+
* form remains for callers that need the earlier newline-joined contract.
|
|
103
|
+
*
|
|
104
|
+
* @param text - The manifest's contents.
|
|
105
|
+
* @returns One sanitized line per script body, newline joined.
|
|
106
|
+
*/
|
|
107
|
+
export function manifestCommandLines(text) {
|
|
108
|
+
// Preserve the text-returning compatibility contract while ensuring a body
|
|
109
|
+
// cannot continue into the next one when callers tokenize the joined result.
|
|
110
|
+
return manifestCommandBodies(text)
|
|
111
|
+
.map((value) => value.replace(/\\+$/, ""))
|
|
112
|
+
.join("\n");
|
|
113
|
+
}
|
|
114
|
+
/** Subcommands that run something else, so a later `publish` word is its argument. */
|
|
115
|
+
// npm's runner subcommands, which take a script or package name rather than
|
|
116
|
+
// publishing. `workspace` is deliberately absent: it is not a subcommand at all
|
|
117
|
+
// -- npm selects a workspace with the `-w`/`--workspace` FLAG -- and listing it
|
|
118
|
+
// here only meant that a `publish` written after the word was never audited.
|
|
119
|
+
const RUNNER_SUBCOMMANDS = new Set(["run", "run-script", "exec", "explore", "x"]);
|
|
120
|
+
/**
|
|
121
|
+
* npm flags that consume the word after them.
|
|
122
|
+
*
|
|
123
|
+
* Their values sit where a subcommand would, so a value that happens to spell a
|
|
124
|
+
* runner word (`npm --tag run publish`) otherwise reads as `npm run` and the
|
|
125
|
+
* publish beside it is discarded. Joined forms (`--tag=run`) need no entry,
|
|
126
|
+
* because they are one word.
|
|
127
|
+
*/
|
|
128
|
+
const VALUE_TAKING_FLAGS = new Set([
|
|
129
|
+
"--tag", "--access", "--registry", "--otp", "--workspace", "-w",
|
|
130
|
+
"--userconfig", "--globalconfig", "--cache", "--prefix", "--loglevel",
|
|
131
|
+
"--provenance-file", "--auth-type", "--before", "--omit", "--include",
|
|
132
|
+
]);
|
|
133
|
+
/**
|
|
134
|
+
* Decide whether one command is a direct `npm publish`.
|
|
135
|
+
*
|
|
136
|
+
* `publish` does not have to follow `npm` immediately: npm accepts its
|
|
137
|
+
* configuration flags anywhere on the line, so `npm --access public publish` is
|
|
138
|
+
* a real publish that an adjacency test discards silently, leaving an attested
|
|
139
|
+
* sibling elsewhere in the file to carry the audit to a pass.
|
|
140
|
+
*
|
|
141
|
+
* Reading the first non-flag word as the subcommand does not work either,
|
|
142
|
+
* because npm has flags that take a separate value (`--access public`) and
|
|
143
|
+
* flags that do not (`--ignore-scripts`), and telling them apart needs npm's
|
|
144
|
+
* own option table. So the word is looked for anywhere in the arguments, and
|
|
145
|
+
* only a preceding runner subcommand rules it out -- `npm run publish` runs a
|
|
146
|
+
* package script whose body is scanned from the manifest, and requiring the
|
|
147
|
+
* flag on the runner would report a defect that is not there.
|
|
148
|
+
*
|
|
149
|
+
* The residual imprecision is `npm --tag publish ...`, a dist-tag named after
|
|
150
|
+
* the subcommand, which this reads as a publish. That direction is deliberate:
|
|
151
|
+
* a false positive is a report line to argue with, a false negative is an
|
|
152
|
+
* unattested artifact on the registry.
|
|
153
|
+
*
|
|
154
|
+
* The program is checked in command position by the caller, so `echo npm
|
|
155
|
+
* publish` and `notnpm publish` never reach here.
|
|
156
|
+
*
|
|
157
|
+
* @param command - One simple command's tokens.
|
|
158
|
+
* @returns True when the command publishes.
|
|
159
|
+
*/
|
|
160
|
+
export function isPublishCommand(command) {
|
|
161
|
+
const args = commandArguments(command);
|
|
162
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
163
|
+
const token = args[index];
|
|
164
|
+
// A flag's separate value is not the subcommand. `npm --tag run publish`
|
|
165
|
+
// configures the tag as "run" and publishes; reading `run` as a runner
|
|
166
|
+
// subcommand discarded a real publish and let an attested sibling carry the
|
|
167
|
+
// audit. Only npm's own value-taking flags are skipped, so an unknown flag
|
|
168
|
+
// still leaves its value in subcommand position rather than being ignored.
|
|
169
|
+
if (VALUE_TAKING_FLAGS.has(token.value)) {
|
|
170
|
+
index += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (token.value.startsWith("-"))
|
|
174
|
+
continue;
|
|
175
|
+
if (RUNNER_SUBCOMMANDS.has(token.value))
|
|
176
|
+
return false;
|
|
177
|
+
if (token.value === "publish")
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Decide whether one publish command actually enables the attestation.
|
|
184
|
+
*
|
|
185
|
+
* A substring test is not enough. `--provenance=false`, `--provenance false` and
|
|
186
|
+
* `--no-provenance` all contain the flag's spelling and all turn the
|
|
187
|
+
* attestation off, so a containment check accepts precisely the regression this
|
|
188
|
+
* gate exists to catch -- while reporting the file as attested. `--provenance-file`
|
|
189
|
+
* is a different flag entirely and must not be read as this one.
|
|
190
|
+
*
|
|
191
|
+
* Tokens are judged in order and the last one wins, which is how npm resolves a
|
|
192
|
+
* flag given more than once: `--provenance --no-provenance` publishes without an
|
|
193
|
+
* attestation, so this must answer false for it.
|
|
194
|
+
*
|
|
195
|
+
* Quoting is irrelevant to the shell and so is irrelevant here: `npm publish
|
|
196
|
+
* "--provenance"` is attested, and the scan this replaces read it as bare.
|
|
197
|
+
*
|
|
198
|
+
* @param command - One simple command's tokens.
|
|
199
|
+
* @returns True when the command publishes with an attestation.
|
|
200
|
+
*/
|
|
201
|
+
export function attestationEnabled(command) {
|
|
202
|
+
const args = commandArguments(command);
|
|
203
|
+
let enabled = false;
|
|
204
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
205
|
+
const token = args[index].value;
|
|
206
|
+
if (token === `--no-${ATTESTATION_FLAG.slice(2)}`) {
|
|
207
|
+
enabled = false;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (token === ATTESTATION_FLAG) {
|
|
211
|
+
const next = args[index + 1]?.value;
|
|
212
|
+
if (next === "true" || next === "false") {
|
|
213
|
+
enabled = next === "true";
|
|
214
|
+
index += 1;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
enabled = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (token.startsWith(`${ATTESTATION_FLAG}=`)) {
|
|
221
|
+
enabled = token.slice(ATTESTATION_FLAG.length + 1) === "true";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return enabled;
|
|
225
|
+
}
|
|
226
|
+
/** Decide whether a segment enters an `else` or `elif` sibling arm. */
|
|
227
|
+
function startsIfSiblingArm(segment) {
|
|
228
|
+
const first = tokenizeCommands(segment)[0]?.[0]?.value;
|
|
229
|
+
return first === "else" || first === "elif";
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Find every publish invocation in one file's contents.
|
|
233
|
+
*
|
|
234
|
+
* Continuations are joined and shared arrays expanded before tokenising, for
|
|
235
|
+
* the same reason the changelog-date scan does it: a multi-line invocation
|
|
236
|
+
* otherwise looks like fragments, none of which carries the flag.
|
|
237
|
+
*
|
|
238
|
+
* @param source - The file's path and contents.
|
|
239
|
+
* @returns The publish invocations found, in file order.
|
|
240
|
+
*/
|
|
241
|
+
function publishInvocationsInShell(source, raw) {
|
|
242
|
+
const text = joinContinuations(raw);
|
|
243
|
+
const arrays = bashArrays(text);
|
|
244
|
+
const lines = text.split("\n");
|
|
245
|
+
const bodies = heredocBodyLines(lines);
|
|
246
|
+
const bodyExpansion = heredocExpansionLines(lines);
|
|
247
|
+
const scopes = [new Map()];
|
|
248
|
+
let caseDepth = 0;
|
|
249
|
+
const visibleScalars = () => {
|
|
250
|
+
const visible = new Map();
|
|
251
|
+
for (const scope of scopes) {
|
|
252
|
+
for (const [name, binding] of scope) {
|
|
253
|
+
if (binding === undefined)
|
|
254
|
+
visible.delete(name);
|
|
255
|
+
else
|
|
256
|
+
visible.set(name, binding);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return visible;
|
|
260
|
+
};
|
|
261
|
+
const expanded = lines.map((line, lineIndex) => {
|
|
262
|
+
// Heredoc bodies cannot mutate the parent binding state. Unquoted bodies
|
|
263
|
+
// expand variables before being written; quoted bodies retain references,
|
|
264
|
+
// which fail closed if a generated script later executes them.
|
|
265
|
+
if (bodies[lineIndex]) {
|
|
266
|
+
return bodyExpansion[lineIndex]
|
|
267
|
+
? expandScalars(expandArrays(line, arrays), visibleScalars())
|
|
268
|
+
: line;
|
|
269
|
+
}
|
|
270
|
+
const segments = segmentShellLine(line);
|
|
271
|
+
let assignmentEligible = true;
|
|
272
|
+
let pendingAssignments = new Map();
|
|
273
|
+
let pendingAssignmentEligible = false;
|
|
274
|
+
const rendered = segments.map((segment) => {
|
|
275
|
+
if (/^(?:;|&&?|\|\|?)$/.test(segment)) {
|
|
276
|
+
const currentScope = scopes[scopes.length - 1];
|
|
277
|
+
for (const [name, binding] of pendingAssignments) {
|
|
278
|
+
// Only a sequential separator persists the assignment in the current
|
|
279
|
+
// scope. Ambiguous execution shadows prior evidence fail-closed.
|
|
280
|
+
currentScope.set(name, pendingAssignmentEligible && segment === ";" ? binding : undefined);
|
|
281
|
+
}
|
|
282
|
+
pendingAssignments = new Map();
|
|
283
|
+
assignmentEligible = segment === ";";
|
|
284
|
+
return segment;
|
|
285
|
+
}
|
|
286
|
+
// Scope transitions are applied per segment, not per physical line. A
|
|
287
|
+
// one-line compound command can open, assign, publish, and close without
|
|
288
|
+
// any net line-level depth change, yet each command still needs the
|
|
289
|
+
// binding state at the point where it runs.
|
|
290
|
+
const caseChange = caseDepthChange(segment);
|
|
291
|
+
// The `case` nesting depth *before* this segment. A segment that opens a
|
|
292
|
+
// `case` is the first arm of the block it opens and has no earlier
|
|
293
|
+
// sibling; a segment that starts an arm while already inside a `case` is
|
|
294
|
+
// a sibling of a previous arm even when it also opens a nested one.
|
|
295
|
+
// `insideCase` folds this segment's own opener in and so cannot tell the
|
|
296
|
+
// two apart — see the sibling reset below.
|
|
297
|
+
const caseDepthBefore = caseDepth;
|
|
298
|
+
const insideCase = caseDepth > 0 || caseChange > 0;
|
|
299
|
+
const change = blockDepthChange(segment, insideCase);
|
|
300
|
+
if (change < 0) {
|
|
301
|
+
for (let count = 0; count > change && scopes.length > 1; count -= 1) {
|
|
302
|
+
const closed = scopes.pop();
|
|
303
|
+
const parent = scopes[scopes.length - 1];
|
|
304
|
+
// Assignments inside a conditional remain arm-local and are dropped,
|
|
305
|
+
// but an unset is destructive evidence. Forgetting it could restore
|
|
306
|
+
// an attesting outer value that the real shell discarded.
|
|
307
|
+
for (const [name, binding] of closed) {
|
|
308
|
+
if (binding === undefined)
|
|
309
|
+
parent.set(name, undefined);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
caseDepth = Math.max(0, caseDepth + caseChange);
|
|
314
|
+
// A sibling arm receives a fresh map at the same numeric depth. Clearing
|
|
315
|
+
// it is the branch identity: evidence from a mutually exclusive arm can
|
|
316
|
+
// never be visible to this path. Outer scopes remain visible.
|
|
317
|
+
//
|
|
318
|
+
// Keyed on the depth BEFORE this segment, never on whether the segment
|
|
319
|
+
// opens a `case`. A segment that opens one is the first arm of the block
|
|
320
|
+
// it opens: the push below has not happened yet, so clearing would erase
|
|
321
|
+
// the OUTER scope and refuse a publish whose flag was bound before the
|
|
322
|
+
// block, and there is no earlier sibling to hide anyway.
|
|
323
|
+
//
|
|
324
|
+
// But "opens a `case`" is NOT the same question. A later arm may itself
|
|
325
|
+
// open a nested `case` (`b) case "$Y" in`), and it is still a sibling of
|
|
326
|
+
// the previous arm. Skipping the reset for it leaves that arm's binding
|
|
327
|
+
// visible and attests a publish the shell would run unattested from a
|
|
328
|
+
// mutually exclusive arm. `else`/`elif` are siblings unconditionally, for
|
|
329
|
+
// the same reason, including when they open a nested block.
|
|
330
|
+
if (startsIfSiblingArm(segment) || (caseDepthBefore > 0 && startsEnclosingCaseArm(segment))) {
|
|
331
|
+
const currentScope = scopes[scopes.length - 1];
|
|
332
|
+
const discarded = [...currentScope].filter((entry) => entry[1] === undefined);
|
|
333
|
+
currentScope.clear();
|
|
334
|
+
// A destructive operation in either possible arm makes the later value
|
|
335
|
+
// unprovable. Retain only unsets when moving to a sibling arm; retaining
|
|
336
|
+
// assignments here would re-open the sibling-evidence defect.
|
|
337
|
+
for (const [name] of discarded)
|
|
338
|
+
currentScope.set(name, undefined);
|
|
339
|
+
}
|
|
340
|
+
if (change > 0) {
|
|
341
|
+
for (let count = 0; count < change; count += 1)
|
|
342
|
+
scopes.push(new Map());
|
|
343
|
+
}
|
|
344
|
+
const resolved = expandScalars(expandArrays(segment, arrays), visibleScalars());
|
|
345
|
+
pendingAssignments = literalScalarAssignments(segment);
|
|
346
|
+
pendingAssignmentEligible = assignmentEligible;
|
|
347
|
+
const currentScope = scopes[scopes.length - 1];
|
|
348
|
+
for (const command of tokenizeCommands(segment)) {
|
|
349
|
+
for (const name of unsetNames(command))
|
|
350
|
+
currentScope.set(name, undefined);
|
|
351
|
+
}
|
|
352
|
+
assignmentEligible = false;
|
|
353
|
+
return resolved;
|
|
354
|
+
});
|
|
355
|
+
const currentScope = scopes[scopes.length - 1];
|
|
356
|
+
for (const [name, binding] of pendingAssignments) {
|
|
357
|
+
currentScope.set(name, pendingAssignmentEligible ? binding : undefined);
|
|
358
|
+
}
|
|
359
|
+
return rendered.join("");
|
|
360
|
+
}).join("\n");
|
|
361
|
+
const found = [];
|
|
362
|
+
for (const command of tokenizeCommands(expanded)) {
|
|
363
|
+
// Every reading, not just the command's own: a wrapper option that takes a
|
|
364
|
+
// value (`sudo -u root npm publish`) moves the program past where naming it
|
|
365
|
+
// once would look. Missing a publish is a failed audit; offering one that no
|
|
366
|
+
// shell would run is noise an operator dismisses.
|
|
367
|
+
for (const candidate of commandCandidates(command)) {
|
|
368
|
+
const program = commandName(candidate);
|
|
369
|
+
if (program === undefined)
|
|
370
|
+
continue;
|
|
371
|
+
const unresolvedProgram = program.startsWith("$");
|
|
372
|
+
if (program !== "npm" && !FOREIGN_PUBLISHERS.has(program) && !unresolvedProgram)
|
|
373
|
+
continue;
|
|
374
|
+
if (!isPublishCommand(candidate))
|
|
375
|
+
continue;
|
|
376
|
+
// Not de-duplicated: two identical publish lines are two invocations, and
|
|
377
|
+
// collapsing them would report one of them as if the other did not exist.
|
|
378
|
+
found.push({ file: source.file, program, command: candidate });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return found;
|
|
382
|
+
}
|
|
383
|
+
/** A path whose final segment is exactly `package.json`.
|
|
384
|
+
*
|
|
385
|
+
* Anchored at a segment boundary rather than matched as a suffix: a tracked
|
|
386
|
+
* executable named `fake-package.json` ends with the same characters, and
|
|
387
|
+
* routing it to the manifest reader parses it as JSON, fails, and yields no
|
|
388
|
+
* bodies — hiding every publish it contains from the audit. Because the
|
|
389
|
+
* non-vacuity guard is repository-wide, one attested publish elsewhere then
|
|
390
|
+
* satisfies it and the whole run passes clean. */
|
|
391
|
+
const MANIFEST_PATH = /(?:^|\/)package\.json$/;
|
|
392
|
+
/**
|
|
393
|
+
* Find every publish invocation in one source, isolating package scripts.
|
|
394
|
+
*
|
|
395
|
+
* npm launches every manifest script in a fresh shell, so scalar evidence from
|
|
396
|
+
* one body can never attest a publish in another body. A workflow file's `run:`
|
|
397
|
+
* blocks are dedented first — before {@link joinContinuations} and heredoc
|
|
398
|
+
* detection — because that is what happens to them before bash sees the text:
|
|
399
|
+
* YAML strips the block indentation when it delivers the value. The order
|
|
400
|
+
* matters: `dedentRunBlocks` reads the raw YAML line structure to find block
|
|
401
|
+
* headers and strip their indentation, and `joinContinuations` would merge a
|
|
402
|
+
* `run:` header with the next line when a backslash continuation sits between
|
|
403
|
+
* them, destroying the header the dedent needs to see. Heredoc detection runs
|
|
404
|
+
* after both, because a heredoc terminator must match at the start of the line
|
|
405
|
+
* bash receives — which is the dedented, continuation-joined text. The one
|
|
406
|
+
* scanner rule that is whitespace-sensitive is the heredoc terminator, so
|
|
407
|
+
* dedenting before that rule runs is what makes a `run: |2` block's heredoc
|
|
408
|
+
* terminator match instead of swallowing the rest of the file.
|
|
409
|
+
*
|
|
410
|
+
* @param source - The file's path and contents.
|
|
411
|
+
* @returns The publish invocations found, in file and script order.
|
|
412
|
+
*/
|
|
413
|
+
export function publishInvocationsIn(source) {
|
|
414
|
+
const bodies = MANIFEST_PATH.test(source.file)
|
|
415
|
+
? manifestCommandBodies(source.text)
|
|
416
|
+
: isWorkflowYaml(source.file)
|
|
417
|
+
? [dedentRunBlocks(source.text)]
|
|
418
|
+
: [source.text];
|
|
419
|
+
return bodies.flatMap((body) => publishInvocationsInShell(source, body));
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Render an invocation back to a readable command for a report line.
|
|
423
|
+
*
|
|
424
|
+
* @param command - The invocation's tokens.
|
|
425
|
+
* @returns The command as a single space-separated string.
|
|
426
|
+
*/
|
|
427
|
+
export function renderCommand(command) {
|
|
428
|
+
return command.map((token) => token.value).join(" ").slice(0, 160);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Audit every publish invocation across the given files.
|
|
432
|
+
*
|
|
433
|
+
* An absent invocation is a failure rather than a pass: a scan that finds
|
|
434
|
+
* nothing has either been pointed at the wrong files or outlived the workflow
|
|
435
|
+
* it guards, and both look identical to a clean result unless said out loud.
|
|
436
|
+
*
|
|
437
|
+
* A publisher other than npm fails outright rather than being checked for a
|
|
438
|
+
* flag. This repository's attested path is npm's `--provenance`; no equivalent
|
|
439
|
+
* is configured for yarn, pnpm or bun, so such an invocation is an unattested
|
|
440
|
+
* publish path regardless of the flags it carries, and guessing at another
|
|
441
|
+
* tool's spelling would be a gate that only looked strict.
|
|
442
|
+
*
|
|
443
|
+
* @param sources - The tracked files to scan.
|
|
444
|
+
* @returns Failures and per-file notes.
|
|
445
|
+
*/
|
|
446
|
+
export function auditPublishAttestation(sources) {
|
|
447
|
+
const invocations = sources.flatMap(publishInvocationsIn);
|
|
448
|
+
const failures = [];
|
|
449
|
+
const counted = new Map();
|
|
450
|
+
for (const invocation of invocations) {
|
|
451
|
+
const tally = counted.get(invocation.file) ?? { total: 0, unflagged: 0 };
|
|
452
|
+
tally.total += 1;
|
|
453
|
+
if (invocation.program !== "npm") {
|
|
454
|
+
tally.unflagged += 1;
|
|
455
|
+
failures.push(`${invocation.file}: \`${invocation.program} publish\` is a publish path with no attested`
|
|
456
|
+
+ ` equivalent configured in this repository: ${renderCommand(invocation.command)}`);
|
|
457
|
+
}
|
|
458
|
+
else if (!attestationEnabled(invocation.command)) {
|
|
459
|
+
tally.unflagged += 1;
|
|
460
|
+
failures.push(`${invocation.file}: a publish invocation does not enable ${ATTESTATION_FLAG}, so it would`
|
|
461
|
+
+ ` publish an unattested artifact: ${renderCommand(invocation.command)}`);
|
|
462
|
+
}
|
|
463
|
+
counted.set(invocation.file, tally);
|
|
464
|
+
}
|
|
465
|
+
if (invocations.length === 0) {
|
|
466
|
+
failures.push("no npm publish invocation was found in any tracked file - the scan is looking in the wrong place");
|
|
467
|
+
}
|
|
468
|
+
const notes = [];
|
|
469
|
+
for (const [file, tally] of counted) {
|
|
470
|
+
if (tally.unflagged > 0)
|
|
471
|
+
continue;
|
|
472
|
+
notes.push(`ok - ${file}: ${tally.total} publish invocation(s), each carrying ${ATTESTATION_FLAG}`);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
failures,
|
|
476
|
+
notes,
|
|
477
|
+
recognition: invocations.length === 0
|
|
478
|
+
? { kind: "none" }
|
|
479
|
+
: { kind: "recognized", count: invocations.length },
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Decide whether a tracked path can run a command.
|
|
484
|
+
*
|
|
485
|
+
* The previous enumeration named two paths -- `.github/workflows` and
|
|
486
|
+
* `package.json` -- which meant a publish added to any tracked script was never
|
|
487
|
+
* audited, and because the workflow's own attested publish satisfied the
|
|
488
|
+
* non-vacuity check the gate still reported that every invocation was attested.
|
|
489
|
+
* Auditing every shape that can execute closes that, and a shebang is honoured
|
|
490
|
+
* so an extensionless tracked script is not a blind spot either.
|
|
491
|
+
*
|
|
492
|
+
* Build output is excluded. `dist/` is generated from sources this scan already
|
|
493
|
+
* reads, it is regenerated and compared byte-for-byte on the release path, and
|
|
494
|
+
* including it would audit a bundled copy of a command rather than the command.
|
|
495
|
+
*
|
|
496
|
+
* @param path - Repository-relative path.
|
|
497
|
+
* @param firstLine - The file's first line, for shebang detection.
|
|
498
|
+
* @returns True when the file should be scanned.
|
|
499
|
+
*/
|
|
500
|
+
export function isExecutableSource(path, firstLine) {
|
|
501
|
+
if (GENERATED_PREFIXES.some((prefix) => path.startsWith(prefix)))
|
|
502
|
+
return false;
|
|
503
|
+
if (firstLine.startsWith("#!"))
|
|
504
|
+
return true;
|
|
505
|
+
return EXECUTABLE_PATHS.some((pattern) => pattern.test(path));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Read the first two bytes of a file, or nothing when it cannot be read.
|
|
509
|
+
*
|
|
510
|
+
* Only a shebang is being looked for, so the whole file is never loaded --
|
|
511
|
+
* `git ls-files` can name a large tracked asset, and this runs once per
|
|
512
|
+
* candidate. A tracked path that cannot be opened at all (a dangling symlink,
|
|
513
|
+
* a file removed from the working tree but still in the index) is not a
|
|
514
|
+
* publish path and must not take the gate down; it reads as empty.
|
|
515
|
+
*
|
|
516
|
+
* The handle is closed by an inner `finally` rather than one wrapping the
|
|
517
|
+
* catch, so there is no unreachable fall-through for the coverage gate to
|
|
518
|
+
* report as an untested branch.
|
|
519
|
+
*
|
|
520
|
+
* @param file - Absolute path to read.
|
|
521
|
+
* @returns The first two bytes as text, or an empty string.
|
|
522
|
+
*/
|
|
523
|
+
function firstBytes(file) {
|
|
524
|
+
try {
|
|
525
|
+
const handle = openSync(file, "r");
|
|
526
|
+
try {
|
|
527
|
+
const buffer = Buffer.alloc(2);
|
|
528
|
+
readSync(handle, buffer, 0, 2, 0);
|
|
529
|
+
return buffer.toString("utf8");
|
|
530
|
+
}
|
|
531
|
+
finally {
|
|
532
|
+
closeSync(handle);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return "";
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* List the tracked files that can run a publish.
|
|
541
|
+
*
|
|
542
|
+
* Git is asked rather than the filesystem walked, so an untracked scratch copy
|
|
543
|
+
* of a workflow cannot satisfy or fail the gate. `-z` is used because a tracked
|
|
544
|
+
* path may legally contain a newline, and splitting such a listing on newlines
|
|
545
|
+
* invents two paths that do not exist and drops the one that does.
|
|
546
|
+
*
|
|
547
|
+
* @param root - Repository root.
|
|
548
|
+
* @returns Repository-relative paths of every tracked file that can execute.
|
|
549
|
+
*/
|
|
550
|
+
export function trackedPublishSources(root) {
|
|
551
|
+
const listed = execFileSync("git", ["ls-files", "-z"], {
|
|
552
|
+
cwd: root,
|
|
553
|
+
encoding: "utf8",
|
|
554
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
555
|
+
});
|
|
556
|
+
return listed
|
|
557
|
+
.split("\0")
|
|
558
|
+
.filter((path) => path.length > 0)
|
|
559
|
+
.filter((path) => isExecutableSource(path, firstBytes(resolve(root, path))));
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Read the tracked sources and audit them.
|
|
563
|
+
*
|
|
564
|
+
* @param root - Repository root to verify.
|
|
565
|
+
* @returns Failures and notes for the whole repository.
|
|
566
|
+
*/
|
|
567
|
+
export function verify(root) {
|
|
568
|
+
const sources = trackedPublishSources(root).map((file) => ({
|
|
569
|
+
file,
|
|
570
|
+
text: readFileSync(resolve(root, file), "utf8"),
|
|
571
|
+
}));
|
|
572
|
+
return auditPublishAttestation(sources);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Print a result and set a failing exit code when it failed.
|
|
576
|
+
*
|
|
577
|
+
* @param result - The audit outcome.
|
|
578
|
+
* @param write - Sink for the report lines.
|
|
579
|
+
* @param exit - Called with the process exit code when there were failures.
|
|
580
|
+
*/
|
|
581
|
+
export function report(result, write, exit) {
|
|
582
|
+
for (const note of result.notes)
|
|
583
|
+
write(note);
|
|
584
|
+
for (const failure of result.failures)
|
|
585
|
+
write(`FAIL - ${failure}`);
|
|
586
|
+
if (result.failures.length > 0) {
|
|
587
|
+
write(`verify-release-publish-attestation: ${result.failures.length} failure(s).`);
|
|
588
|
+
exit(1);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
write("verify-release-publish-attestation: every publish invocation is attested.");
|
|
592
|
+
}
|
|
593
|
+
//# sourceMappingURL=attestation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attestation.js","sourceRoot":"","sources":["../attestation.ts"],"names":[],"mappings":"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"}
|