UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

108 lines (107 loc) 6.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProvenanceError = void 0; exports.ensurePackageHasProvenance = ensurePackageHasProvenance; exports.getNxPackageGroup = getNxPackageGroup; const module_1 = require("module"); const fileutils_1 = require("./fileutils"); const package_manager_1 = require("./package-manager"); /* * Verifies that the given npm package has provenance attestations * generated by the GitHub Actions workflow at .github/workflows/publish.yml * in the nrwl/nx repository. * * Will throw if the package does not have valid provenance. */ async function ensurePackageHasProvenance(packageName, packageVersion) { // this is used for locally released versions without provenance // do not set this for other reasons or you might be exposed to security risks if (process.env.NX_SKIP_PROVENANCE_CHECK) { return; } try { const result = await (0, package_manager_1.packageRegistryView)(packageName, packageVersion, [ '--json', '--silent', ]); const parsed = JSON.parse(result); // `npm view <pkg>@<spec> --json` returns a bare object on npm <= 11 but an // array on npm 12 and pnpm, even for a single resolved version. A version // range matches several versions and the registry lists all of them // (including deprecated ones the installer skips), so we cannot tell which // one will actually be installed; refuse rather than verify the wrong // artifact. if (Array.isArray(parsed) && parsed.length > 1) { throw new ProvenanceError(packageName, packageVersion, 'Provenance can only be verified for a single version, but this version resolved to multiple candidates. Specify an exact version.'); } const npmViewResult = Array.isArray(parsed) ? parsed[0] : parsed; const attURL = npmViewResult?.dist?.attestations?.url; if (!attURL) throw new ProvenanceError(packageName, packageVersion, 'No attestation URL found'); const response = await fetch(attURL); if (!response.ok) { throw new ProvenanceError(packageName, packageVersion, `HTTP ${response.status}: ${response.statusText}`); } const attestations = (await response.json()); const provenanceAttestation = attestations?.attestations?.find((a) => a.predicateType === 'https://slsa.dev/provenance/v1'); const dsseEnvelopePayload = JSON.parse(Buffer.from(provenanceAttestation.bundle.dsseEnvelope.payload, 'base64').toString()); const workflowParameters = dsseEnvelopePayload?.predicate?.buildDefinition?.externalParameters ?.workflow; // verify that provenance was actually generated from the right publishing workflow if (!workflowParameters) { throw new ProvenanceError(packageName, packageVersion, 'Missing workflow parameters in attestation'); } if (workflowParameters.repository !== 'https://github.com/nrwl/nx') { throw new ProvenanceError(packageName, packageVersion, 'Repository does not match nrwl/nx'); } if (workflowParameters.path !== '.github/workflows/publish.yml') { throw new ProvenanceError(packageName, packageVersion, 'Publishing workflow does not match .github/workflows/publish.yml'); } if (workflowParameters.ref !== `refs/tags/${npmViewResult.version}`) { throw new ProvenanceError(packageName, packageVersion, `Version ref does not match refs/tags/${npmViewResult.version}`); } // verify that provenance was generated from the exact same artifact as the one we are installing const distSha = Buffer.from(npmViewResult.dist.integrity.replace('sha512-', ''), 'base64').toString('hex'); const attestationSha = dsseEnvelopePayload.subject[0].digest.sha512; if (distSha !== attestationSha) { throw new ProvenanceError(packageName, packageVersion, 'Integrity hash does not match attestation hash'); } return; } catch (error) { if (error instanceof ProvenanceError) { throw error; } throw new ProvenanceError(packageName, packageVersion, error.message || error); } } class ProvenanceError extends Error { constructor(packageName, packageVersion, error) { let customRegistry = undefined; try { const registry = (0, package_manager_1.getWorkspaceRegistryUrlForDisplay)(packageName); // Only consider it custom if it's not the default npm registry if (registry && !registry.includes('registry.npmjs.org')) { customRegistry = registry; } } catch { // If we can't determine the registry, proceed with default error message } const registryNote = customRegistry ? `This might be due to a custom registry configuration (${customRegistry}). Please check whether provenance is correctly configured for your registry.` : `This could indicate a security risk. Please double check https://www.npmjs.com/package/${packageName} to see if the package is published correctly or file an issue at https://github.com/nrwl/nx/issues.`; super(`An error occurred while checking the provenance of ${packageName}@${packageVersion}. ${registryNote} To disable this check at your own risk, you can set the NX_SKIP_PROVENANCE_CHECK environment variable to true. \n Error: ${error ?? ''}`); } } exports.ProvenanceError = ProvenanceError; function getNxPackageGroup() { const packageJsonPath = (0, module_1.createRequire)(__filename).resolve('nx/package.json'); const packageJson = (0, fileutils_1.readJsonFile)(packageJsonPath); if (!packageJson['nx-migrations']?.packageGroup) { return ['nx']; } const packages = packageJson['nx-migrations'].packageGroup.filter((dep) => typeof dep === 'string' && dep.startsWith('@nx/')); packages.push('nx'); return packages; }