All files / packages/design-system/scripts/scs artifact-provenance.js

90.21% Statements 166/184
85.36% Branches 140/164
96.29% Functions 26/27
89.2% Lines 124/139

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318          4x   52x     29x           1x 1x                                       41x 41x       41x 41x 41x     41x             17x 17x       17x       104x 104x 104x 104x     104x       104x 104x 104x 104x       207x 207x 173x 173x 167x         289x 289x 289x   27x         357x 357x 7x         33x 33x 33x 33x 33x   33x 33x 33x 33x 33x 33x 33x   28x 28x                           3x 3x 3x 1x       2x 1x     1x         1x 1x 1x           1x           1x       2x               9x 9x   9x 9x 9x 9x     9x 9x     9x       1x   8x 1x     7x 7x   9x 9x 9x 9x 2x   5x 1x 1x       4x         4x 4x 3x   1x       2x                               7x 7x     7x 7x       9x 9x 9x 9x 9x 1x   8x     8x             7x           7x                   7x     7x 7x 7x 7x     7x 1x   6x 31x 2x   30x   1x     5x 34x 34x 34x 34x 34x 1x     2x    
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
 
const SHA_PATTERN = /^[0-9a-f]{40}$/i;
 
const sha256 = (contents) => crypto.createHash('sha256').update(contents).digest('hex');
 
function assertExpectedSourceSha(expectedSourceSha) {
  Iif (!SHA_PATTERN.test(expectedSourceSha || '')) {
    throw new Error('SCS expected source SHA must be a full 40-character Git commit SHA.');
  }
}
 
export function assertReleaseArtifactVersion({ label, selectedVersion, artifactVersion }) {
  Eif (!selectedVersion || selectedVersion !== artifactVersion) {
    throw new Error(
      `Selected ${label} version ${selectedVersion || '(missing)'} does not match built artifact version ${artifactVersion || '(missing)'}.`,
    );
  }
}
 
function getDefaultGitState(packageRoot) {
  const sourceSha = execFileSync('git', ['rev-parse', 'HEAD'], {
    cwd: packageRoot,
    encoding: 'utf8',
  }).trim();
  const status = execFileSync('git', ['status', '--porcelain'], {
    cwd: packageRoot,
    encoding: 'utf8',
  });
 
  return { sourceSha, isClean: !status.trim() };
}
 
function resolveReceiptFile({ receipt, receiptRoot, sourcePath }) {
  const relativePath = receipt?.path;
  Iif (!relativePath || path.isAbsolute(relativePath)) {
    throw new Error(`Invalid provenance path for ${sourcePath}`);
  }
 
  const receiptPath = path.resolve(receiptRoot, relativePath);
  const rootPrefix = `${path.resolve(receiptRoot)}${path.sep}`;
  Iif (!receiptPath.startsWith(rootPrefix)) {
    throw new Error(`Unsafe provenance path for ${sourcePath}`);
  }
  Iif (receiptPath !== path.resolve(sourcePath)) {
    throw new Error(`Receipt does not describe selected artifact ${sourcePath}`);
  }
}
 
function assertRegularFile(filePath, label) {
  let stats;
  try {
    stats = fs.lstatSync(filePath);
  } catch {
    throw new Error(`Missing ${label}: ${filePath}`);
  }
  Iif (!stats.isFile()) throw new Error(`${label} must be a regular file: ${filePath}`);
}
 
function assertSafeDestinationPath({ destinationRoot, destinationPath, trustedRoot = destinationRoot }) {
  const root = path.resolve(destinationRoot);
  const destination = path.resolve(destinationPath);
  const trusted = path.resolve(trustedRoot);
  Iif (root !== trusted && !root.startsWith(`${trusted}${path.sep}`)) {
    throw new Error(`Unsafe SCS destination root: ${destinationRoot}`);
  }
  Iif (destination === root || !destination.startsWith(`${root}${path.sep}`)) {
    throw new Error(`Unsafe SCS destination path: ${destinationPath}`);
  }
 
  assertRegularFileOrDirectory(trusted, 'SCS trusted root', true);
  assertSafeDirectoryPath(trusted, root, 'SCS destination root');
  assertSafeDirectoryPath(root, path.dirname(destination), 'SCS destination path');
  if (pathExists(destination)) assertRegularFileOrDirectory(destination, 'SCS destination path', false);
}
 
function assertSafeDirectoryPath(startPath, endPath, label) {
  let currentPath = startPath;
  for (const segment of path.relative(startPath, endPath).split(path.sep).filter(Boolean)) {
    currentPath = path.join(currentPath, segment);
    if (!pathExists(currentPath)) continue;
    assertRegularFileOrDirectory(currentPath, label, true);
  }
}
 
function pathExists(filePath) {
  try {
    fs.lstatSync(filePath);
    return true;
  } catch {
    return false;
  }
}
 
function assertRegularFileOrDirectory(filePath, label, expectDirectory) {
  const stats = fs.lstatSync(filePath);
  if (stats.isSymbolicLink() || (expectDirectory ? !stats.isDirectory() : !stats.isFile())) {
    throw new Error(`${label} is not a safe ${expectDirectory ? 'directory' : 'file'}: ${filePath}`);
  }
}
 
export function writeProtectedFile({ destinationRoot, destinationPath, trustedRoot, contents }) {
  assertSafeDestinationPath({ destinationRoot, destinationPath, trustedRoot });
  fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
  assertSafeDestinationPath({ destinationRoot, destinationPath, trustedRoot });
  const temporaryPath = path.join(path.dirname(destinationPath), `.${path.basename(destinationPath)}.${crypto.randomUUID()}`);
  const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW;
  let descriptor;
  try {
    descriptor = fs.openSync(temporaryPath, flags, 0o644);
    fs.writeFileSync(descriptor, contents);
    const openDescriptor = descriptor;
    descriptor = null;
    fs.closeSync(openDescriptor);
    fs.renameSync(temporaryPath, destinationPath);
  } finally {
    if (descriptor !== null && descriptor !== undefined) fs.closeSync(descriptor);
    fs.rmSync(temporaryPath, { force: true });
  }
}
 
export function writeReleaseArtifactReceipt({
  expectedSourceSha,
  packageName,
  packageVersion,
  packageRoot,
  receiptPath,
  files,
  trustedRoot = packageRoot,
  getGitState = getDefaultGitState,
}) {
  assertExpectedSourceSha(expectedSourceSha);
  const { sourceSha, isClean } = getGitState(packageRoot);
  if (sourceSha !== expectedSourceSha) {
    throw new Error(
      `SCS expected source SHA ${expectedSourceSha} does not match checked-out source SHA ${sourceSha}.`,
    );
  }
  if (!isClean) {
    throw new Error('SCS release artifacts must be built from a clean source checkout.');
  }
 
  const receipt = {
    packageName,
    packageVersion,
    sourceSha,
    files: files.map((file) => {
      assertRegularFile(file, 'release artifact');
      const contents = fs.readFileSync(file);
      return {
        path: path.relative(packageRoot, file),
        sha256: sha256(contents),
      };
    }),
  };
  writeProtectedFile({
    destinationRoot: packageRoot,
    destinationPath: receiptPath,
    trustedRoot,
    contents: `${JSON.stringify(receipt, null, 2)}\n`,
  });
  return receipt;
}
 
export function writeReleaseArtifactReceiptIfRequested(options) {
  Eif (!process.env.SCS_EXPECTED_SOURCE_SHA) return null;
  return writeReleaseArtifactReceipt({
    ...options,
    expectedSourceSha: process.env.SCS_EXPECTED_SOURCE_SHA,
  });
}
 
export function loadVerifiedReleaseArtifacts({ expectedSourceSha, artifacts }) {
  assertExpectedSourceSha(expectedSourceSha);
  const receipts = new Map();
 
  return artifacts.map((artifact) => {
    let receipt = receipts.get(artifact.receiptPath);
    Eif (!receipt) {
      Iif (!fs.existsSync(artifact.receiptPath)) {
        throw new Error(`Missing provenance receipt for ${artifact.label}: ${artifact.receiptPath}`);
      }
      receipt = JSON.parse(fs.readFileSync(artifact.receiptPath, 'utf8'));
      receipts.set(artifact.receiptPath, receipt);
    }
 
    if (
      receipt.packageName !== artifact.packageName ||
      (Object.hasOwn(artifact, 'packageVersion') && receipt.packageVersion !== artifact.packageVersion)
    ) {
      throw new Error(`Receipt package identity does not match ${artifact.label}`);
    }
    if (receipt.sourceSha !== expectedSourceSha) {
      throw new Error(`Receipt source SHA does not match expected source SHA for ${artifact.label}`);
    }
 
    const receiptFile = receipt.files?.find(
      (file) => path.resolve(artifact.receiptRoot, file.path) === path.resolve(artifact.sourcePath),
    );
    resolveReceiptFile({ receipt: receiptFile, receiptRoot: artifact.receiptRoot, sourcePath: artifact.sourcePath });
    assertRegularFile(artifact.sourcePath, `release artifact ${artifact.label}`);
    const contents = fs.readFileSync(artifact.sourcePath);
    if (sha256(contents) !== receiptFile.sha256) {
      throw new Error(`Artifact ${artifact.label} does not match receipt SHA-256.`);
    }
    for (const requiredContent of artifact.requiredContent || []) {
      Eif (!contents.includes(requiredContent)) {
        throw new Error(`Artifact ${artifact.label} does not contain required generated content.`);
      }
    }
 
    return { ...artifact, contents };
  });
}
 
export function copyVerifiedReleaseArtifacts({ expectedSourceSha, destinationRoot, trustedRoot, artifacts }) {
  const verifiedArtifacts = loadVerifiedReleaseArtifacts({ expectedSourceSha, artifacts });
  for (const artifact of verifiedArtifacts) {
    writeProtectedFile({ destinationRoot, trustedRoot, ...artifact });
  }
  return verifiedArtifacts;
}
 
export function writeScsPublicationMap({ destinationRoot, publicationMapPath, publicationMap, trustedRoot }) {
  writeProtectedFile({
    destinationRoot,
    destinationPath: publicationMapPath,
    trustedRoot,
    contents: publicationMap,
  });
}
 
export function writeScsAssemblyManifest({
  manifestPath,
  destinationRoot,
  sourceSha,
  artifacts,
  publicationMap,
  trustedRoot,
}) {
  Eif (sourceSha !== null) assertExpectedSourceSha(sourceSha);
  const previousManifest = pathExists(manifestPath)
    ? (assertSafeDestinationPath({ destinationRoot, destinationPath: manifestPath, trustedRoot }), JSON.parse(fs.readFileSync(manifestPath, 'utf8')))
    : { sourceSha: null, files: [] };
  const previousFiles = new Map(previousManifest.files?.map((file) => [file.path, file]));
  const manifest = {
    sourceSha,
    ...(publicationMap ? { filesJsonSha256: sha256(publicationMap) } : {}),
    files: artifacts.map(({ destinationPath, contents, sourceSha: artifactSourceSha }) => {
      const artifactPath = path.relative(destinationRoot, destinationPath);
      assertRegularFile(destinationPath, `assembled SCS artifact ${artifactPath}`);
      const hash = sha256(contents);
      const previousFile = previousFiles.get(artifactPath);
      if (!artifactSourceSha && previousFile && previousFile.sha256 !== hash) {
        throw new Error(`Retained SCS artifact changed without verified provenance: ${artifactPath}`);
      }
      Iif (!artifactSourceSha && !previousFile) {
        throw new Error(`New SCS artifact lacks verified provenance: ${artifactPath}`);
      }
      return {
        path: artifactPath,
        sha256: hash,
        sourceSha: artifactSourceSha || previousFile?.sourceSha || null,
      };
    }),
  };
  writeProtectedFile({
    destinationRoot,
    destinationPath: manifestPath,
    trustedRoot,
    contents: `${JSON.stringify(manifest, null, 2)}\n`,
  });
  return manifest;
}
 
export function verifyScsAssemblyManifest({
  manifestPath,
  destinationRoot,
  expectedPaths = null,
  publicationMap = null,
  trustedRoot,
}) {
  Iif (!pathExists(manifestPath)) {
    throw new Error(`Missing SCS assembly provenance manifest: ${manifestPath}`);
  }
  assertSafeDestinationPath({ destinationRoot, destinationPath: manifestPath, trustedRoot });
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
  if (manifest.sourceSha !== null) assertExpectedSourceSha(manifest.sourceSha);
  Iif (!Array.isArray(manifest.files) || !manifest.files.length) {
    throw new Error('SCS assembly provenance manifest has no files.');
  }
  if (publicationMap && manifest.filesJsonSha256 !== sha256(publicationMap)) {
    throw new Error('SCS assembly provenance does not match scs/files.json.');
  }
  if (expectedPaths) {
    const manifestPaths = new Set(manifest.files.map((file) => file.path));
    if (
      manifestPaths.size !== expectedPaths.length ||
      expectedPaths.some((filePath) => !manifestPaths.has(filePath))
    ) {
      throw new Error('SCS assembly provenance does not cover the SCS publication map.');
    }
  }
  for (const file of manifest.files) {
    if (file.sourceSha !== null) assertExpectedSourceSha(file.sourceSha);
    const destinationPath = path.resolve(destinationRoot, file.path);
    resolveReceiptFile({ receipt: file, receiptRoot: destinationRoot, sourcePath: destinationPath });
    assertSafeDestinationPath({ destinationRoot, destinationPath, trustedRoot });
    if (sha256(fs.readFileSync(destinationPath)) !== file.sha256) {
      throw new Error(`Assembled SCS artifact ${file.path} does not match assembly provenance SHA-256.`);
    }
  }
  return manifest;
}