nx
Version:
106 lines (105 loc) • 5.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProvenanceError = void 0;
exports.ensurePackageHasProvenance = ensurePackageHasProvenance;
exports.getNxPackageGroup = getNxPackageGroup;
const child_process_1 = require("child_process");
const path_1 = require("path");
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 npmViewResult = JSON.parse(result);
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 packageManager = (0, package_manager_1.detectPackageManager)();
const commands = (0, package_manager_1.getPackageManagerCommand)(packageManager);
// Try to get registry from current package manager, fall back to npm
const registryCommand = commands.getRegistryUrl ?? 'npm config get registry';
const registry = (0, child_process_1.execSync)(registryCommand, {
timeout: 5000,
windowsHide: true,
encoding: 'utf-8',
}).trim();
// Only consider it custom if it's not the default npm registry
if (registry &&
registry !== 'undefined' &&
!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, path_1.join)(__dirname, '../../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;
}