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.
@@ -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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"}