@mawesome/npm-vm 0.0.0-alpha.0 → 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 +50 -1
- package/dist/cli.js +4157 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-B071ZJDk.d.cts +164 -0
- package/dist/index-B071ZJDk.d.ts +164 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal.cjs +2709 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +139 -0
- package/dist/internal.d.ts +139 -0
- package/dist/internal.js +2706 -0
- package/dist/internal.js.map +1 -0
- package/dist/provenance--sOYCeF2.js +322 -0
- package/dist/provenance--sOYCeF2.js.map +1 -0
- package/dist/provenance-DDegWCyS.js +2 -0
- package/dist/runtime/atomic-C3shKdH9.cjs +88 -0
- package/dist/runtime/bootstrap.cjs +271 -0
- package/dist/runtime/commonjs-AANzyd21.cjs +7393 -0
- package/dist/runtime/dispatch-B9OSfqsn.cjs +25156 -0
- package/dist/runtime/dist-CXwBT6rE.cjs +30766 -0
- package/dist/runtime/home-BaoZZSBy.cjs +91 -0
- package/dist/runtime/install-C_M5WK98.cjs +13598 -0
- package/dist/runtime/journal-_bs-eJRJ.cjs +282 -0
- package/dist/runtime/launchers/cmd.tmpl +2 -0
- package/dist/runtime/launchers/ps1.tmpl +5 -0
- package/dist/runtime/launchers/sh.tmpl +3 -0
- package/dist/runtime/lease-HBD4CmBx.cjs +224 -0
- package/dist/runtime/lock-DBeYN-Zg.cjs +141 -0
- package/dist/runtime/messages-vUDkl1-0.cjs +174 -0
- package/dist/runtime/p-map-b8pXdaFZ.cjs +105 -0
- package/dist/runtime/proc-hB8N9WjN.cjs +127 -0
- package/dist/runtime/provenance-B4bajGXQ.cjs +2 -0
- package/dist/runtime/rolldown-runtime-BGT596CS.cjs +64 -0
- package/dist/runtime/runtime.cjs +143 -0
- package/dist/runtime/src-SpeGbMJx.cjs +314 -0
- package/dist/src-Bc9nN1Kl.cjs +2125 -0
- package/dist/src-Bc9nN1Kl.cjs.map +1 -0
- package/dist/src-NhNG2mnQ.js +1798 -0
- package/dist/src-NhNG2mnQ.js.map +1 -0
- package/package.json +87 -17
- package/index.js +0 -3
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { Agent, ProxyAgent, request } from "undici";
|
|
5
|
+
//#region src/errors.ts
|
|
6
|
+
/** Exit codes of `npm-vm` itself and of the shim before npm starts (spec section 10). */
|
|
7
|
+
const EXIT = {
|
|
8
|
+
ok: 0,
|
|
9
|
+
problems: 1,
|
|
10
|
+
partial: 2,
|
|
11
|
+
resolution: 3,
|
|
12
|
+
transport: 4,
|
|
13
|
+
verification: 5,
|
|
14
|
+
abi: 6,
|
|
15
|
+
usage: 64
|
|
16
|
+
};
|
|
17
|
+
/** A failure with a message id from spec section 12 and the exit code it maps to. */
|
|
18
|
+
var NpmVmError = class extends Error {
|
|
19
|
+
name = "NpmVmError";
|
|
20
|
+
id;
|
|
21
|
+
exitCode;
|
|
22
|
+
constructor(id, exitCode, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.id = id;
|
|
25
|
+
this.exitCode = exitCode;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function isNpmVmError(error) {
|
|
29
|
+
return error instanceof NpmVmError;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/messages.ts
|
|
33
|
+
/** Message builders for spec section 12; every message names the cause and one fix. */
|
|
34
|
+
/** Every URL in `text` with its user info replaced, so a registry token never reaches a terminal (spec SEC-10). */
|
|
35
|
+
function redactUrls(text) {
|
|
36
|
+
return text.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^\s/]*)@/gi, "$1***@");
|
|
37
|
+
}
|
|
38
|
+
function describeSpecs(specs) {
|
|
39
|
+
return specs.map((spec) => spec.hash ? `${spec.exact}+${spec.hash.algorithm}.${spec.hash.hex.slice(0, 8)}…` : spec.range).join(" or ");
|
|
40
|
+
}
|
|
41
|
+
function engineMessage(version, enginesNode, nodeVersion) {
|
|
42
|
+
return [`npm ${version} needs Node ${enginesNode}, but the active Node is ${nodeVersion}.`, `Switch Node with \`nvm install <version>\` or \`fnm use <version>\`, or relax the npm range in package.json.`].join("\n");
|
|
43
|
+
}
|
|
44
|
+
function noMatchMessage(specs, file, reason) {
|
|
45
|
+
return [`No npm version satisfies ${describeSpecs(specs)} (${file}): ${reason}.`, `Run \`npm-vm which\` to see the resolution.`].join("\n");
|
|
46
|
+
}
|
|
47
|
+
function transportMessage(ownVersion, detail) {
|
|
48
|
+
return [`Cannot download npm: ${ownVersion === null ? "no npm beside the active Node" : `Node's own npm ${ownVersion}`} (${detail}); npm 8 or newer is needed as the transport.`, `Reinstall Node or run \`npm-vm install <version>\` from a Node whose npm works.`].join("\n");
|
|
49
|
+
}
|
|
50
|
+
function keyMessage(keyid) {
|
|
51
|
+
return [`The registry signed npm with key ${keyid}, which npm-vm does not know.`, `Run \`npm-vm self-update\` to pick up new registry keys.`].join("\n");
|
|
52
|
+
}
|
|
53
|
+
function unsignedMessage(registry, version) {
|
|
54
|
+
return [`${redactUrls(registry)} serves npm ${version} without a registry signature.`, `Pin a tarball hash in devEngines.packageManager (\`${version}+sha512.<hex>\`) to allow unsigned registries.`].join("\n");
|
|
55
|
+
}
|
|
56
|
+
function integrityMessage(expected, actual, detail) {
|
|
57
|
+
return [`npm tarball verification failed: ${detail}.`, `Expected ${expected}, got ${actual}. Clear the entry with \`npm-vm cache prune\` and retry.`].join("\n");
|
|
58
|
+
}
|
|
59
|
+
function abiMessage(detail) {
|
|
60
|
+
return [`npm-vm launcher mismatch: ${detail}.`, `Run \`npx --prefix ~ -y @mawesome/npm-vm setup\` to reinstall the launchers.`].join("\n");
|
|
61
|
+
}
|
|
62
|
+
/** M-LOCKED: names the file, the holder, and the one recovery that is safe for where the holder runs. */
|
|
63
|
+
function lockedMessage(path, holder, live, foreign) {
|
|
64
|
+
if (holder === null) return `${path} is locked by an unreadable owner. If no npm-vm command is running, delete that file and retry.`;
|
|
65
|
+
const who = `PID ${holder.pid} on ${holder.host} since ${holder.createdAt || "an unknown time"}`;
|
|
66
|
+
if (foreign) return [`${path} is locked by ${who}, which cannot be verified from this machine.`, `Confirm on ${holder.host} that no npm-vm command runs, then delete ${path} and retry.`].join("\n");
|
|
67
|
+
if (live) return `${path} is locked by ${who}, which is still running. Wait for it to finish and retry.`;
|
|
68
|
+
return `${path} is locked by ${who}, which is no longer running. Delete ${path} and retry.`;
|
|
69
|
+
}
|
|
70
|
+
function provenanceMessage(name, version, detail) {
|
|
71
|
+
return [`${name}@${version} failed provenance verification: ${detail}.`, "The installed version is unchanged. Retry later, or install the release by hand after checking it at https://www.npmjs.com/package/@mawesome/npm-vm."].join("\n");
|
|
72
|
+
}
|
|
73
|
+
function dirPermsMessage(dir, problem, fix) {
|
|
74
|
+
return [`${dir} ${problem}.`, `Run \`${fix}\` and retry, or point NPM_VM_HOME at a private directory.`].join("\n");
|
|
75
|
+
}
|
|
76
|
+
function changedMessage(file, what) {
|
|
77
|
+
return `${file} changed since npm-vm wrote it; left untouched. Remove the ${what} by hand if you still want it gone.`;
|
|
78
|
+
}
|
|
79
|
+
function restartMessage() {
|
|
80
|
+
return "Restart open terminals and IDEs so they pick up the new PATH.";
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/install/provenance.ts
|
|
84
|
+
/** What the attestation must say about this repository's release workflow (spec SUP-2). */
|
|
85
|
+
const PROVENANCE_ISSUER = "https://token.actions.githubusercontent.com";
|
|
86
|
+
const PROVENANCE_IDENTITY = "https://github.com/mawesomedev/mawesome/.github/workflows/release.yml@refs/heads/main";
|
|
87
|
+
const SLSA_PREDICATE = "https://slsa.dev/provenance/v1";
|
|
88
|
+
function failure(name, version, detail) {
|
|
89
|
+
return new NpmVmError("M-PROVENANCE", EXIT.verification, provenanceMessage(name, version, detail));
|
|
90
|
+
}
|
|
91
|
+
/** Resolves the attestation path against the configured registry: never another origin (spec SUP-2). */
|
|
92
|
+
function attestationUrl(input, registry) {
|
|
93
|
+
const base = new URL(registry);
|
|
94
|
+
const url = new URL(base);
|
|
95
|
+
if (input.attestationsUrl !== null) {
|
|
96
|
+
const given = new URL(input.attestationsUrl, base);
|
|
97
|
+
url.pathname = given.pathname;
|
|
98
|
+
url.search = given.search;
|
|
99
|
+
} else {
|
|
100
|
+
url.pathname = `${base.pathname.replace(/\/$/, "")}/-/npm/v1/attestations/${encodeURIComponent(input.name)}@${input.version}`;
|
|
101
|
+
url.search = "";
|
|
102
|
+
}
|
|
103
|
+
url.hash = "";
|
|
104
|
+
url.username = "";
|
|
105
|
+
url.password = "";
|
|
106
|
+
if (url.origin !== base.origin) throw new Error(`attestation URL ${redactUrls(url.href)} leaves the registry origin ${base.origin}`);
|
|
107
|
+
return url;
|
|
108
|
+
}
|
|
109
|
+
/** TLS settings npm itself would use: `ca` and `cafile`, `strict-ssl`, and the registry's client certificate (spec DL-2). */
|
|
110
|
+
function tlsOptions(config) {
|
|
111
|
+
const ca = [...config.ca ?? []];
|
|
112
|
+
if (config.cafile !== null) ca.push(readFileSync(config.cafile, "utf8"));
|
|
113
|
+
const { certfile, keyfile } = config.credentials;
|
|
114
|
+
return {
|
|
115
|
+
rejectUnauthorized: config.strictSsl,
|
|
116
|
+
...ca.length === 0 ? {} : { ca },
|
|
117
|
+
...certfile !== void 0 && keyfile !== void 0 ? {
|
|
118
|
+
cert: readFileSync(certfile, "utf8"),
|
|
119
|
+
key: readFileSync(keyfile, "utf8")
|
|
120
|
+
} : {}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** `noproxy` the way @npmcli/agent reads it: comma or space separated, `*` for all, `host[:port]`, leading dots and `*.`. */
|
|
124
|
+
function noProxyMatches(noproxy, url) {
|
|
125
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
126
|
+
return (noproxy ?? "").split(/[\s,]+/).map((item) => item.trim().toLowerCase()).filter((item) => item !== "").some((item) => {
|
|
127
|
+
if (item === "*") return true;
|
|
128
|
+
const [host, itemPort] = item.split(":");
|
|
129
|
+
if (itemPort !== void 0 && itemPort !== port) return false;
|
|
130
|
+
const bare = host.replace(/^\*?\./, "");
|
|
131
|
+
return url.hostname === bare || url.hostname.endsWith(`.${bare}`);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function dispatcherFor(config, url) {
|
|
135
|
+
const proxy = url.protocol === "https:" ? config.httpsProxy ?? config.proxy : config.proxy;
|
|
136
|
+
const bypass = noProxyMatches(config.noproxy, url);
|
|
137
|
+
const tls = tlsOptions(config);
|
|
138
|
+
if (proxy !== null && !bypass) return new ProxyAgent({
|
|
139
|
+
uri: proxy,
|
|
140
|
+
requestTls: tls
|
|
141
|
+
});
|
|
142
|
+
return new Agent({ connect: tls });
|
|
143
|
+
}
|
|
144
|
+
function authHeader(config) {
|
|
145
|
+
const { token, username, password, auth } = config.credentials;
|
|
146
|
+
if (token !== void 0) return { authorization: `Bearer ${token}` };
|
|
147
|
+
if (auth !== void 0) return { authorization: `Basic ${auth}` };
|
|
148
|
+
if (username !== void 0 && password !== void 0) return { authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}` };
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
/** Fetches the attestations with the user's registry credentials, sent to that origin only (spec DL-2). */
|
|
152
|
+
const fetchAttestations = async (url, config) => {
|
|
153
|
+
const dispatcher = dispatcherFor(config, url);
|
|
154
|
+
try {
|
|
155
|
+
const response = await request(url, {
|
|
156
|
+
dispatcher,
|
|
157
|
+
headers: {
|
|
158
|
+
accept: "application/json",
|
|
159
|
+
...authHeader(config)
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (response.statusCode !== 200) {
|
|
163
|
+
await response.body.dump();
|
|
164
|
+
throw new Error(`registry answered ${response.statusCode} for ${url.pathname}`);
|
|
165
|
+
}
|
|
166
|
+
const body = await response.body.json();
|
|
167
|
+
return (Array.isArray(body.attestations) ? body.attestations : []).filter((item) => typeof item === "object" && item !== null && typeof item.predicateType === "string" && "bundle" in item);
|
|
168
|
+
} finally {
|
|
169
|
+
await dispatcher.close();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
/** sigstore's verifier with this repository's identity pinned; the TUF cache lives under the state dir. */
|
|
173
|
+
/** sigstore matches the identity as an unanchored regular expression, so the exact SAN is escaped and anchored. */
|
|
174
|
+
function identityPattern(identity = PROVENANCE_IDENTITY) {
|
|
175
|
+
return `^${identity.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
|
|
176
|
+
}
|
|
177
|
+
/** The in-process verifier, run by the `verify-attestation` child command. */
|
|
178
|
+
async function verifyBundleHere(bundle, tufCachePath) {
|
|
179
|
+
await (await import("sigstore")).verify(bundle, {
|
|
180
|
+
certificateIssuer: PROVENANCE_ISSUER,
|
|
181
|
+
certificateIdentityURI: identityPattern(),
|
|
182
|
+
tufCachePath
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** How to reach this program again from a child process: the entry argv plus the `cli` selector under the launcher. */
|
|
186
|
+
function selfCommand() {
|
|
187
|
+
const entry = process.argv[1] ?? "";
|
|
188
|
+
return entry.endsWith("bootstrap.cjs") ? [entry, "cli"] : [entry];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Runs the verifier in a child Node (spec DL-2): sigstore reaches TUF through make-fetch-happen, which reads
|
|
192
|
+
* proxies from the env, and Node reads NODE_EXTRA_CA_CERTS only at start. The child's env carries nothing else.
|
|
193
|
+
*/
|
|
194
|
+
const verifyBundle = async (bundle, options) => {
|
|
195
|
+
const dir = mkdtempSync(join(options.tufCachePath, "bundle-"));
|
|
196
|
+
const file = join(dir, "bundle.json");
|
|
197
|
+
writeFileSync(file, JSON.stringify(bundle), { mode: 384 });
|
|
198
|
+
const { args, env } = verifierLaunch(file, options, dir);
|
|
199
|
+
try {
|
|
200
|
+
await new Promise((resolve, reject) => {
|
|
201
|
+
execFile(process.execPath, args, {
|
|
202
|
+
env,
|
|
203
|
+
encoding: "utf8",
|
|
204
|
+
timeout: 3e5,
|
|
205
|
+
windowsHide: true
|
|
206
|
+
}, (error, stdout, stderr) => {
|
|
207
|
+
if (error === null) {
|
|
208
|
+
resolve();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
reject(new Error(`${stderr.trim() || stdout.trim() || error.message}`.replace(/^npm-vm: /, "")));
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
} finally {
|
|
215
|
+
rmSync(dir, {
|
|
216
|
+
recursive: true,
|
|
217
|
+
force: true
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
/** The verifier child's argv and env for one bundle file; `dir` is private to this run and holds any CA bundle. */
|
|
222
|
+
function verifierLaunch(file, options, dir) {
|
|
223
|
+
const [entry, ...prefix] = selfCommand();
|
|
224
|
+
return {
|
|
225
|
+
args: [
|
|
226
|
+
entry,
|
|
227
|
+
...prefix,
|
|
228
|
+
"verify-attestation",
|
|
229
|
+
"--bundle",
|
|
230
|
+
file,
|
|
231
|
+
"--tuf-cache",
|
|
232
|
+
options.tufCachePath
|
|
233
|
+
],
|
|
234
|
+
env: childEnv(options.config, options.env, dir)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const PROXY_KEYS = /^(https?_proxy|all_proxy|no_proxy|proxy|node_extra_ca_certs|node_options)$/i;
|
|
238
|
+
/** The child's env: the parent's minus every proxy and Node override, plus only what the npm config says. */
|
|
239
|
+
function childEnv(config, env, bundleDir = null) {
|
|
240
|
+
const child = {};
|
|
241
|
+
for (const [key, value] of Object.entries(env)) if (!PROXY_KEYS.test(key) && !/^npm_config_/i.test(key)) child[key] = value;
|
|
242
|
+
const httpsProxy = config.httpsProxy ?? config.proxy;
|
|
243
|
+
if (httpsProxy !== null) child["https_proxy"] = httpsProxy;
|
|
244
|
+
if (config.proxy !== null) child["http_proxy"] = config.proxy;
|
|
245
|
+
if (config.noproxy !== null) child["no_proxy"] = config.noproxy;
|
|
246
|
+
if (config.ca !== null && config.ca.length > 0 && bundleDir !== null) {
|
|
247
|
+
const bundle = join(bundleDir, "ca-bundle.pem");
|
|
248
|
+
const parts = [...config.ca];
|
|
249
|
+
if (config.cafile !== null) parts.push(readFileSync(config.cafile, "utf8"));
|
|
250
|
+
writeFileSync(bundle, `${parts.map((part) => part.trim()).join("\n")}\n`, { mode: 384 });
|
|
251
|
+
child["NODE_EXTRA_CA_CERTS"] = bundle;
|
|
252
|
+
} else if (config.cafile !== null) child["NODE_EXTRA_CA_CERTS"] = config.cafile;
|
|
253
|
+
return child;
|
|
254
|
+
}
|
|
255
|
+
function isSubject(value) {
|
|
256
|
+
if (typeof value !== "object" || value === null) return false;
|
|
257
|
+
const { name, digest } = value;
|
|
258
|
+
return typeof name === "string" && typeof digest === "object" && digest !== null && !Array.isArray(digest);
|
|
259
|
+
}
|
|
260
|
+
function statementOf(bundle) {
|
|
261
|
+
const payload = bundle.dsseEnvelope?.payload;
|
|
262
|
+
if (typeof payload !== "string") return null;
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
265
|
+
if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.subject)) return null;
|
|
266
|
+
return {
|
|
267
|
+
predicateType: typeof parsed.predicateType === "string" ? parsed.predicateType : "",
|
|
268
|
+
subject: parsed.subject.filter(isSubject).map((subject) => ({
|
|
269
|
+
name: subject.name,
|
|
270
|
+
digest: Object.fromEntries(Object.entries(subject.digest).filter(([, value]) => typeof value === "string"))
|
|
271
|
+
}))
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Verifies the SLSA provenance of a release (spec SUP-2): fetched from the registry origin, signed by this
|
|
279
|
+
* repository's release workflow on main, and naming exactly this package version and tarball digest.
|
|
280
|
+
*/
|
|
281
|
+
async function verifyProvenance(input, deps = {}) {
|
|
282
|
+
const { name, version } = input;
|
|
283
|
+
let url;
|
|
284
|
+
try {
|
|
285
|
+
url = attestationUrl(input, input.config.registry);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
throw failure(name, version, `bad attestation URL: ${redactUrls(error.message)}`);
|
|
288
|
+
}
|
|
289
|
+
let attestations;
|
|
290
|
+
try {
|
|
291
|
+
attestations = await (deps.fetch ?? fetchAttestations)(url, input.config);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
throw failure(name, version, `cannot fetch attestations: ${redactUrls(error.message)}`);
|
|
294
|
+
}
|
|
295
|
+
const provenance = attestations.find((item) => item.predicateType === SLSA_PREDICATE);
|
|
296
|
+
if (provenance === void 0) throw failure(name, version, "the registry holds no SLSA provenance attestation for it");
|
|
297
|
+
const statement = statementOf(provenance.bundle);
|
|
298
|
+
if (statement === null) throw failure(name, version, "the attestation bundle carries no readable in-toto statement");
|
|
299
|
+
if (statement.predicateType !== "https://slsa.dev/provenance/v1") throw failure(name, version, `the signed statement is ${statement.predicateType}, not SLSA provenance`);
|
|
300
|
+
const expectedSubject = `pkg:npm/${name.replace(/^@/, "%40")}@${version}`;
|
|
301
|
+
const subject = statement.subject.find((item) => item.name === expectedSubject);
|
|
302
|
+
if (subject === void 0) throw failure(name, version, `the attestation names ${statement.subject.map((item) => item.name).join(", ") || "nothing"}, not ${expectedSubject}`);
|
|
303
|
+
if (subject.digest["sha512"] !== input.sha512hex) throw failure(name, version, "the attested tarball digest differs from the downloaded tarball");
|
|
304
|
+
const tufCachePath = join(input.stateDir, "sigstore");
|
|
305
|
+
mkdirSync(tufCachePath, {
|
|
306
|
+
recursive: true,
|
|
307
|
+
mode: 448
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
await (deps.verify ?? verifyBundle)(provenance.bundle, {
|
|
311
|
+
tufCachePath,
|
|
312
|
+
config: input.config,
|
|
313
|
+
env: deps.env ?? process.env
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
throw failure(name, version, `signature verification failed: ${redactUrls(error.message)}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
export { redactUrls as C, EXIT as D, unsignedMessage as E, NpmVmError as O, noMatchMessage as S, transportMessage as T, dirPermsMessage as _, childEnv as a, keyMessage as b, noProxyMatches as c, verifierLaunch as d, verifyBundle as f, changedMessage as g, abiMessage as h, attestationUrl as i, isNpmVmError as k, selfCommand as l, verifyProvenance as m, PROVENANCE_ISSUER as n, fetchAttestations as o, verifyBundleHere as p, SLSA_PREDICATE as r, identityPattern as s, PROVENANCE_IDENTITY as t, tlsOptions as u, engineMessage as v, restartMessage as w, lockedMessage as x, integrityMessage as y };
|
|
321
|
+
|
|
322
|
+
//# sourceMappingURL=provenance--sOYCeF2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance--sOYCeF2.js","names":[],"sources":["../src/errors.ts","../src/messages.ts","../src/install/provenance.ts"],"sourcesContent":["/** Exit codes of `npm-vm` itself and of the shim before npm starts (spec section 10). */\nexport const EXIT = {\n\tok: 0,\n\tproblems: 1,\n\tpartial: 2,\n\tresolution: 3,\n\ttransport: 4,\n\tverification: 5,\n\tabi: 6,\n\tusage: 64,\n} as const;\n\nexport type ExitCode = (typeof EXIT)[keyof typeof EXIT];\n\n/** A failure with a message id from spec section 12 and the exit code it maps to. */\nexport class NpmVmError extends Error {\n\toverride name = 'NpmVmError';\n\treadonly id: string;\n\treadonly exitCode: ExitCode;\n\n\tconstructor(id: string, exitCode: ExitCode, message: string) {\n\t\tsuper(message);\n\t\tthis.id = id;\n\t\tthis.exitCode = exitCode;\n\t}\n}\n\nexport function isNpmVmError(error: unknown): error is NpmVmError {\n\treturn error instanceof NpmVmError;\n}\n","import type { NpmSpec } from './spec.ts';\n\n/** Message builders for spec section 12; every message names the cause and one fix. */\n\n/** Every URL in `text` with its user info replaced, so a registry token never reaches a terminal (spec SEC-10). */\nexport function redactUrls(text: string): string {\n\treturn text.replace(/\\b([a-z][a-z0-9+.-]*:\\/\\/)([^\\s/]*)@/gi, '$1***@');\n}\n\nexport function describeSpecs(specs: readonly NpmSpec[]): string {\n\treturn specs\n\t\t.map((spec) =>\n\t\t\tspec.hash ? `${spec.exact}+${spec.hash.algorithm}.${spec.hash.hex.slice(0, 8)}…` : spec.range,\n\t\t)\n\t\t.join(' or ');\n}\n\nexport function engineMessage(version: string, enginesNode: string, nodeVersion: string): string {\n\treturn [\n\t\t`npm ${version} needs Node ${enginesNode}, but the active Node is ${nodeVersion}.`,\n\t\t`Switch Node with \\`nvm install <version>\\` or \\`fnm use <version>\\`, or relax the npm range in package.json.`,\n\t].join('\\n');\n}\n\nexport function noMatchMessage(specs: readonly NpmSpec[], file: string, reason: string): string {\n\treturn [\n\t\t`No npm version satisfies ${describeSpecs(specs)} (${file}): ${reason}.`,\n\t\t`Run \\`npm-vm which\\` to see the resolution.`,\n\t].join('\\n');\n}\n\nexport function transportMessage(ownVersion: string | null, detail: string): string {\n\tconst found =\n\t\townVersion === null ? 'no npm beside the active Node' : `Node's own npm ${ownVersion}`;\n\treturn [\n\t\t`Cannot download npm: ${found} (${detail}); npm 8 or newer is needed as the transport.`,\n\t\t`Reinstall Node or run \\`npm-vm install <version>\\` from a Node whose npm works.`,\n\t].join('\\n');\n}\n\nexport function keyMessage(keyid: string): string {\n\treturn [\n\t\t`The registry signed npm with key ${keyid}, which npm-vm does not know.`,\n\t\t`Run \\`npm-vm self-update\\` to pick up new registry keys.`,\n\t].join('\\n');\n}\n\nexport function unsignedMessage(registry: string, version: string): string {\n\treturn [\n\t\t`${redactUrls(registry)} serves npm ${version} without a registry signature.`,\n\t\t`Pin a tarball hash in devEngines.packageManager (\\`${version}+sha512.<hex>\\`) to allow unsigned registries.`,\n\t].join('\\n');\n}\n\nexport function integrityMessage(expected: string, actual: string, detail: string): string {\n\treturn [\n\t\t`npm tarball verification failed: ${detail}.`,\n\t\t`Expected ${expected}, got ${actual}. Clear the entry with \\`npm-vm cache prune\\` and retry.`,\n\t].join('\\n');\n}\n\nexport function oldNodeMessage(nodeVersion: string, floor: string, requirement: string): string {\n\treturn `npm-vm: Node ${nodeVersion} is below ${floor}; running Node's own npm and ignoring the project requirement ${requirement}. Upgrade Node to honour it.`;\n}\n\nexport function abiMessage(detail: string): string {\n\treturn [\n\t\t`npm-vm launcher mismatch: ${detail}.`,\n\t\t`Run \\`npx --prefix ~ -y @mawesome/npm-vm setup\\` to reinstall the launchers.`,\n\t].join('\\n');\n}\n\nexport interface LockHolder {\n\tpid: number;\n\thost: string;\n\tcreatedAt: string;\n}\n\n/** M-LOCKED: names the file, the holder, and the one recovery that is safe for where the holder runs. */\nexport function lockedMessage(\n\tpath: string,\n\tholder: LockHolder | null,\n\tlive: boolean,\n\tforeign: boolean,\n): string {\n\tif (holder === null) {\n\t\treturn `${path} is locked by an unreadable owner. If no npm-vm command is running, delete that file and retry.`;\n\t}\n\tconst who = `PID ${holder.pid} on ${holder.host} since ${holder.createdAt || 'an unknown time'}`;\n\tif (foreign) {\n\t\treturn [\n\t\t\t`${path} is locked by ${who}, which cannot be verified from this machine.`,\n\t\t\t`Confirm on ${holder.host} that no npm-vm command runs, then delete ${path} and retry.`,\n\t\t].join('\\n');\n\t}\n\tif (live) {\n\t\treturn `${path} is locked by ${who}, which is still running. Wait for it to finish and retry.`;\n\t}\n\treturn `${path} is locked by ${who}, which is no longer running. Delete ${path} and retry.`;\n}\n\nexport function provenanceMessage(name: string, version: string, detail: string): string {\n\treturn [\n\t\t`${name}@${version} failed provenance verification: ${detail}.`,\n\t\t'The installed version is unchanged. Retry later, or install the release by hand after checking it at https://www.npmjs.com/package/@mawesome/npm-vm.',\n\t].join('\\n');\n}\n\nexport function dirPermsMessage(dir: string, problem: string, fix: string): string {\n\treturn [\n\t\t`${dir} ${problem}.`,\n\t\t`Run \\`${fix}\\` and retry, or point NPM_VM_HOME at a private directory.`,\n\t].join('\\n');\n}\n\nexport function changedMessage(file: string, what: string): string {\n\treturn `${file} changed since npm-vm wrote it; left untouched. Remove the ${what} by hand if you still want it gone.`;\n}\n\nexport function restartMessage(): string {\n\treturn 'Restart open terminals and IDEs so they pick up the new PATH.';\n}\n","import { execFile } from 'node:child_process';\nimport { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { Agent, ProxyAgent, request, type Dispatcher } from 'undici';\nimport { EXIT, NpmVmError } from '../errors.ts';\nimport { provenanceMessage, redactUrls } from '../messages.ts';\nimport type { NpmConfigView } from './config.ts';\n\n/** What the attestation must say about this repository's release workflow (spec SUP-2). */\nexport const PROVENANCE_ISSUER = 'https://token.actions.githubusercontent.com';\nexport const PROVENANCE_IDENTITY =\n\t'https://github.com/mawesomedev/mawesome/.github/workflows/release.yml@refs/heads/main';\nexport const SLSA_PREDICATE = 'https://slsa.dev/provenance/v1';\n\nexport interface ProvenanceInput {\n\tname: string;\n\tversion: string;\n\t/** Hex sha512 of the tarball being installed. */\n\tsha512hex: string;\n\t/** `dist.attestations.url` from the packument; only its path is used (SUP-2). */\n\tattestationsUrl: string | null;\n\tconfig: NpmConfigView;\n\tstateDir: string;\n}\n\nexport interface Attestation {\n\tpredicateType: string;\n\tbundle: unknown;\n}\n\nexport type AttestationFetcher = (url: URL, config: NpmConfigView) => Promise<Attestation[]>;\nexport type BundleVerifier = (\n\tbundle: unknown,\n\toptions: { tufCachePath: string; config: NpmConfigView; env: NodeJS.ProcessEnv },\n) => Promise<void>;\n\nfunction failure(name: string, version: string, detail: string): NpmVmError {\n\treturn new NpmVmError(\n\t\t'M-PROVENANCE',\n\t\tEXIT.verification,\n\t\tprovenanceMessage(name, version, detail),\n\t);\n}\n\n/** Resolves the attestation path against the configured registry: never another origin (spec SUP-2). */\nexport function attestationUrl(\n\tinput: Pick<ProvenanceInput, 'name' | 'version' | 'attestationsUrl'>,\n\tregistry: string,\n): URL {\n\tconst base = new URL(registry);\n\tconst url = new URL(base);\n\tif (input.attestationsUrl !== null) {\n\t\t// Assigned, not re-parsed: a pathname starting with `//host` would otherwise become another origin.\n\t\tconst given = new URL(input.attestationsUrl, base);\n\t\turl.pathname = given.pathname;\n\t\turl.search = given.search;\n\t} else {\n\t\turl.pathname = `${base.pathname.replace(/\\/$/, '')}/-/npm/v1/attestations/${encodeURIComponent(input.name)}@${input.version}`;\n\t\turl.search = '';\n\t}\n\turl.hash = '';\n\t// Credentials travel in the Authorization header, never inline in the URL.\n\turl.username = '';\n\turl.password = '';\n\tif (url.origin !== base.origin) {\n\t\tthrow new Error(\n\t\t\t`attestation URL ${redactUrls(url.href)} leaves the registry origin ${base.origin}`,\n\t\t);\n\t}\n\treturn url;\n}\n\n/** TLS settings npm itself would use: `ca` and `cafile`, `strict-ssl`, and the registry's client certificate (spec DL-2). */\nexport function tlsOptions(config: NpmConfigView): {\n\trejectUnauthorized: boolean;\n\tca?: string[];\n\tcert?: string;\n\tkey?: string;\n} {\n\tconst ca = [...(config.ca ?? [])];\n\tif (config.cafile !== null) {\n\t\tca.push(readFileSync(config.cafile, 'utf8'));\n\t}\n\tconst { certfile, keyfile } = config.credentials;\n\treturn {\n\t\trejectUnauthorized: config.strictSsl,\n\t\t...(ca.length === 0 ? {} : { ca }),\n\t\t...(certfile !== undefined && keyfile !== undefined\n\t\t\t? { cert: readFileSync(certfile, 'utf8'), key: readFileSync(keyfile, 'utf8') }\n\t\t\t: {}),\n\t};\n}\n\n/** `noproxy` the way @npmcli/agent reads it: comma or space separated, `*` for all, `host[:port]`, leading dots and `*.`. */\nexport function noProxyMatches(noproxy: string | null, url: URL): boolean {\n\tconst port = url.port || (url.protocol === 'https:' ? '443' : '80');\n\treturn (noproxy ?? '')\n\t\t.split(/[\\s,]+/)\n\t\t.map((item) => item.trim().toLowerCase())\n\t\t.filter((item) => item !== '')\n\t\t.some((item) => {\n\t\t\tif (item === '*') {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tconst [host, itemPort] = item.split(':') as [string, string | undefined];\n\t\t\tif (itemPort !== undefined && itemPort !== port) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst bare = host.replace(/^\\*?\\./, '');\n\t\t\treturn url.hostname === bare || url.hostname.endsWith(`.${bare}`);\n\t\t});\n}\n\nfunction dispatcherFor(config: NpmConfigView, url: URL): Dispatcher {\n\tconst proxy = url.protocol === 'https:' ? (config.httpsProxy ?? config.proxy) : config.proxy;\n\tconst bypass = noProxyMatches(config.noproxy, url);\n\tconst tls = tlsOptions(config);\n\tif (proxy !== null && !bypass) {\n\t\treturn new ProxyAgent({ uri: proxy, requestTls: tls });\n\t}\n\treturn new Agent({ connect: tls });\n}\n\nfunction authHeader(config: NpmConfigView): Record<string, string> {\n\tconst { token, username, password, auth } = config.credentials;\n\tif (token !== undefined) {\n\t\treturn { authorization: `Bearer ${token}` };\n\t}\n\tif (auth !== undefined) {\n\t\treturn { authorization: `Basic ${auth}` };\n\t}\n\tif (username !== undefined && password !== undefined) {\n\t\treturn { authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}` };\n\t}\n\treturn {};\n}\n\n/** Fetches the attestations with the user's registry credentials, sent to that origin only (spec DL-2). */\nexport const fetchAttestations: AttestationFetcher = async (url, config) => {\n\tconst dispatcher = dispatcherFor(config, url);\n\ttry {\n\t\tconst response = await request(url, {\n\t\t\tdispatcher,\n\t\t\theaders: { accept: 'application/json', ...authHeader(config) },\n\t\t});\n\t\tif (response.statusCode !== 200) {\n\t\t\tawait response.body.dump();\n\t\t\tthrow new Error(`registry answered ${response.statusCode} for ${url.pathname}`);\n\t\t}\n\t\tconst body = (await response.body.json()) as { attestations?: unknown };\n\t\tconst list = Array.isArray(body.attestations) ? body.attestations : [];\n\t\treturn list.filter(\n\t\t\t(item): item is Attestation =>\n\t\t\t\ttypeof item === 'object' &&\n\t\t\t\titem !== null &&\n\t\t\t\ttypeof (item as Attestation).predicateType === 'string' &&\n\t\t\t\t'bundle' in item,\n\t\t);\n\t} finally {\n\t\tawait dispatcher.close();\n\t}\n};\n\n/** sigstore's verifier with this repository's identity pinned; the TUF cache lives under the state dir. */\n/** sigstore matches the identity as an unanchored regular expression, so the exact SAN is escaped and anchored. */\nexport function identityPattern(identity: string = PROVENANCE_IDENTITY): string {\n\treturn `^${identity.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}$`;\n}\n\n/** The in-process verifier, run by the `verify-attestation` child command. */\nexport async function verifyBundleHere(bundle: unknown, tufCachePath: string): Promise<void> {\n\tconst sigstore = await import('sigstore');\n\tawait sigstore.verify(bundle as Parameters<typeof sigstore.verify>[0], {\n\t\tcertificateIssuer: PROVENANCE_ISSUER,\n\t\tcertificateIdentityURI: identityPattern(),\n\t\ttufCachePath,\n\t});\n}\n\n/** How to reach this program again from a child process: the entry argv plus the `cli` selector under the launcher. */\nexport function selfCommand(): string[] {\n\tconst entry = process.argv[1] ?? '';\n\treturn entry.endsWith('bootstrap.cjs') ? [entry, 'cli'] : [entry];\n}\n\n/**\n * Runs the verifier in a child Node (spec DL-2): sigstore reaches TUF through make-fetch-happen, which reads\n * proxies from the env, and Node reads NODE_EXTRA_CA_CERTS only at start. The child's env carries nothing else.\n */\nexport const verifyBundle: BundleVerifier = async (bundle, options) => {\n\tconst dir = mkdtempSync(join(options.tufCachePath, 'bundle-'));\n\tconst file = join(dir, 'bundle.json');\n\twriteFileSync(file, JSON.stringify(bundle), { mode: 0o600 });\n\tconst { args, env } = verifierLaunch(file, options, dir);\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\texecFile(\n\t\t\t\tprocess.execPath,\n\t\t\t\targs,\n\t\t\t\t{ env, encoding: 'utf8', timeout: 300_000, windowsHide: true },\n\t\t\t\t(error, stdout, stderr) => {\n\t\t\t\t\tif (error === null) {\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`${stderr.trim() || stdout.trim() || error.message}`.replace(/^npm-vm: /, ''),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t);\n\t\t});\n\t} finally {\n\t\trmSync(dir, { recursive: true, force: true });\n\t}\n};\n\n/** The verifier child's argv and env for one bundle file; `dir` is private to this run and holds any CA bundle. */\nexport function verifierLaunch(\n\tfile: string,\n\toptions: Parameters<BundleVerifier>[1],\n\tdir: string,\n): { args: string[]; env: NodeJS.ProcessEnv } {\n\tconst [entry, ...prefix] = selfCommand();\n\treturn {\n\t\targs: [\n\t\t\tentry as string,\n\t\t\t...prefix,\n\t\t\t'verify-attestation',\n\t\t\t'--bundle',\n\t\t\tfile,\n\t\t\t'--tuf-cache',\n\t\t\toptions.tufCachePath,\n\t\t],\n\t\tenv: childEnv(options.config, options.env, dir),\n\t};\n}\n\nconst PROXY_KEYS = /^(https?_proxy|all_proxy|no_proxy|proxy|node_extra_ca_certs|node_options)$/i;\n\n/** The child's env: the parent's minus every proxy and Node override, plus only what the npm config says. */\nexport function childEnv(\n\tconfig: NpmConfigView,\n\tenv: NodeJS.ProcessEnv,\n\tbundleDir: string | null = null,\n): NodeJS.ProcessEnv {\n\tconst child: NodeJS.ProcessEnv = {};\n\tfor (const [key, value] of Object.entries(env)) {\n\t\tif (!PROXY_KEYS.test(key) && !/^npm_config_/i.test(key)) {\n\t\t\tchild[key] = value;\n\t\t}\n\t}\n\tconst httpsProxy = config.httpsProxy ?? config.proxy;\n\tif (httpsProxy !== null) {\n\t\tchild['https_proxy'] = httpsProxy;\n\t}\n\tif (config.proxy !== null) {\n\t\tchild['http_proxy'] = config.proxy;\n\t}\n\tif (config.noproxy !== null) {\n\t\tchild['no_proxy'] = config.noproxy;\n\t}\n\t// Inline `ca` has no file for NODE_EXTRA_CA_CERTS to point at; the child gets a private bundle of both.\n\tif (config.ca !== null && config.ca.length > 0 && bundleDir !== null) {\n\t\tconst bundle = join(bundleDir, 'ca-bundle.pem');\n\t\tconst parts = [...config.ca];\n\t\tif (config.cafile !== null) {\n\t\t\tparts.push(readFileSync(config.cafile, 'utf8'));\n\t\t}\n\t\twriteFileSync(bundle, `${parts.map((part) => part.trim()).join('\\n')}\\n`, { mode: 0o600 });\n\t\tchild['NODE_EXTRA_CA_CERTS'] = bundle;\n\t} else if (config.cafile !== null) {\n\t\tchild['NODE_EXTRA_CA_CERTS'] = config.cafile;\n\t}\n\treturn child;\n}\n\ninterface Statement {\n\tsubject: Array<{ name: string; digest: Record<string, string> }>;\n\tpredicateType: string;\n}\n\nfunction isSubject(value: unknown): value is Statement['subject'][number] {\n\tif (typeof value !== 'object' || value === null) {\n\t\treturn false;\n\t}\n\tconst { name, digest } = value as { name?: unknown; digest?: unknown };\n\treturn (\n\t\ttypeof name === 'string' &&\n\t\ttypeof digest === 'object' &&\n\t\tdigest !== null &&\n\t\t!Array.isArray(digest)\n\t);\n}\n\nfunction statementOf(bundle: unknown): Statement | null {\n\tconst payload = (bundle as { dsseEnvelope?: { payload?: unknown } }).dsseEnvelope?.payload;\n\tif (typeof payload !== 'string') {\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(\n\t\t\tBuffer.from(payload, 'base64').toString('utf8'),\n\t\t) as Partial<Statement>;\n\t\tif (typeof parsed !== 'object' || parsed === null || !Array.isArray(parsed.subject)) {\n\t\t\treturn null;\n\t\t}\n\t\treturn {\n\t\t\tpredicateType: typeof parsed.predicateType === 'string' ? parsed.predicateType : '',\n\t\t\tsubject: parsed.subject.filter(isSubject).map((subject) => ({\n\t\t\t\tname: subject.name,\n\t\t\t\tdigest: Object.fromEntries(\n\t\t\t\t\tObject.entries(subject.digest).filter(([, value]) => typeof value === 'string'),\n\t\t\t\t) as Record<string, string>,\n\t\t\t})),\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Verifies the SLSA provenance of a release (spec SUP-2): fetched from the registry origin, signed by this\n * repository's release workflow on main, and naming exactly this package version and tarball digest.\n */\nexport async function verifyProvenance(\n\tinput: ProvenanceInput,\n\tdeps: { fetch?: AttestationFetcher; verify?: BundleVerifier; env?: NodeJS.ProcessEnv } = {},\n): Promise<void> {\n\tconst { name, version } = input;\n\tlet url: URL;\n\ttry {\n\t\turl = attestationUrl(input, input.config.registry);\n\t} catch (error) {\n\t\tthrow failure(name, version, `bad attestation URL: ${redactUrls((error as Error).message)}`);\n\t}\n\tlet attestations: Attestation[];\n\ttry {\n\t\tattestations = await (deps.fetch ?? fetchAttestations)(url, input.config);\n\t} catch (error) {\n\t\tthrow failure(\n\t\t\tname,\n\t\t\tversion,\n\t\t\t`cannot fetch attestations: ${redactUrls((error as Error).message)}`,\n\t\t);\n\t}\n\tconst provenance = attestations.find((item) => item.predicateType === SLSA_PREDICATE);\n\tif (provenance === undefined) {\n\t\tthrow failure(name, version, 'the registry holds no SLSA provenance attestation for it');\n\t}\n\tconst statement = statementOf(provenance.bundle);\n\tif (statement === null) {\n\t\tthrow failure(name, version, 'the attestation bundle carries no readable in-toto statement');\n\t}\n\t// The registry's label is unsigned; the statement inside the envelope is what the signature covers.\n\tif (statement.predicateType !== SLSA_PREDICATE) {\n\t\tthrow failure(\n\t\t\tname,\n\t\t\tversion,\n\t\t\t`the signed statement is ${statement.predicateType}, not SLSA provenance`,\n\t\t);\n\t}\n\t// npm's provenance encodes the scope marker only: `pkg:npm/%40scope/name@version`.\n\tconst expectedSubject = `pkg:npm/${name.replace(/^@/, '%40')}@${version}`;\n\tconst subject = statement.subject.find((item) => item.name === expectedSubject);\n\tif (subject === undefined) {\n\t\tthrow failure(\n\t\t\tname,\n\t\t\tversion,\n\t\t\t`the attestation names ${statement.subject.map((item) => item.name).join(', ') || 'nothing'}, not ${expectedSubject}`,\n\t\t);\n\t}\n\tif (subject.digest['sha512'] !== input.sha512hex) {\n\t\tthrow failure(name, version, 'the attested tarball digest differs from the downloaded tarball');\n\t}\n\tconst tufCachePath = join(input.stateDir, 'sigstore');\n\tmkdirSync(tufCachePath, { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait (deps.verify ?? verifyBundle)(provenance.bundle, {\n\t\t\ttufCachePath,\n\t\t\tconfig: input.config,\n\t\t\tenv: deps.env ?? process.env,\n\t\t});\n\t} catch (error) {\n\t\tthrow failure(\n\t\t\tname,\n\t\t\tversion,\n\t\t\t`signature verification failed: ${redactUrls((error as Error).message)}`,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const require_lease = require("./lease-HBD4CmBx.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
//#region src/state/atomic.ts
|
|
5
|
+
const RETRY_CODES = /* @__PURE__ */ new Set([
|
|
6
|
+
"EBUSY",
|
|
7
|
+
"EPERM",
|
|
8
|
+
"EACCES"
|
|
9
|
+
]);
|
|
10
|
+
const RETRY_LIMIT_MS = 5e3;
|
|
11
|
+
function sleepSync(ms) {
|
|
12
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs a file operation, retrying Windows sharing violations with backoff for up to 5 s (spec MUT-3).
|
|
16
|
+
* Elsewhere the same codes are real permission errors and surface at once.
|
|
17
|
+
*/
|
|
18
|
+
function withSharingRetry(operation, platform = process.platform, limitMs = RETRY_LIMIT_MS) {
|
|
19
|
+
const started = Date.now();
|
|
20
|
+
let delay = 20;
|
|
21
|
+
for (;;) try {
|
|
22
|
+
return operation();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const code = error.code ?? "";
|
|
25
|
+
if (platform !== "win32" || !RETRY_CODES.has(code) || Date.now() - started + delay > limitMs) throw error;
|
|
26
|
+
sleepSync(delay);
|
|
27
|
+
delay = Math.min(delay * 2, 500);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Writes `content` to `path` through a temp file in the same dir, fsynced and renamed into place. */
|
|
31
|
+
function atomicWrite(path, content, mode = 420) {
|
|
32
|
+
const dir = (0, node_path.dirname)(path);
|
|
33
|
+
(0, node_fs.mkdirSync)(dir, { recursive: true });
|
|
34
|
+
const temp = (0, node_path.join)(dir, `.${require_lease.newToken()}.tmp`);
|
|
35
|
+
try {
|
|
36
|
+
const fd = (0, node_fs.openSync)(temp, "wx", mode);
|
|
37
|
+
try {
|
|
38
|
+
const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
39
|
+
for (let offset = 0; offset < buffer.length;) {
|
|
40
|
+
const written = (0, node_fs.writeSync)(fd, buffer, offset, buffer.length - offset);
|
|
41
|
+
if (written <= 0) throw new Error(`short write to ${temp}: ${offset} of ${buffer.length} bytes`);
|
|
42
|
+
offset += written;
|
|
43
|
+
}
|
|
44
|
+
(0, node_fs.fsyncSync)(fd);
|
|
45
|
+
} finally {
|
|
46
|
+
(0, node_fs.closeSync)(fd);
|
|
47
|
+
}
|
|
48
|
+
withSharingRetry(() => (0, node_fs.renameSync)(temp, path));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
(0, node_fs.rmSync)(temp, { force: true });
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Renames with the Windows retry; a cross-device rename is not retried, it is a layout error. */
|
|
55
|
+
function atomicRename(from, to) {
|
|
56
|
+
withSharingRetry(() => (0, node_fs.renameSync)(from, to));
|
|
57
|
+
}
|
|
58
|
+
function removeWithRetry(path) {
|
|
59
|
+
withSharingRetry(() => (0, node_fs.rmSync)(path, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
Object.defineProperty(exports, "atomicRename", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
get: function() {
|
|
68
|
+
return atomicRename;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
Object.defineProperty(exports, "atomicWrite", {
|
|
72
|
+
enumerable: true,
|
|
73
|
+
get: function() {
|
|
74
|
+
return atomicWrite;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
Object.defineProperty(exports, "removeWithRetry", {
|
|
78
|
+
enumerable: true,
|
|
79
|
+
get: function() {
|
|
80
|
+
return removeWithRetry;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
Object.defineProperty(exports, "withSharingRetry", {
|
|
84
|
+
enumerable: true,
|
|
85
|
+
get: function() {
|
|
86
|
+
return withSharingRetry;
|
|
87
|
+
}
|
|
88
|
+
});
|