import { createHash } from 'node:crypto'; import { lstat, readFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { isDeepStrictEqual } from 'node:util'; const repositoryRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..' ); const argumentsList = process.argv.slice(2); const runtimeOnly = argumentsList.includes('--runtime-only'); if ( argumentsList.some((argument) => argument !== '--runtime-only') || (runtimeOnly && argumentsList.length !== 1) ) { throw new Error( 'Usage: node scripts/verify-ffmpeg-assets.mjs [--runtime-only]' ); } const lock = JSON.parse( await readFile( path.join(repositoryRoot, 'scripts', 'reviewed-ffmpeg-core-lock.json'), 'utf8' ) ); if ( lock.schemaVersion !== 1 || typeof lock.buildId !== 'string' || !/^[a-zA-Z0-9._-]+$/.test(lock.buildId) || lock.coreVersion !== '0.12.10' || lock.profile !== 'reviewed-stack5m' || lock.sourceArchive?.path !== 'release/ffmpeg-core-0.12.10-reviewed-stack5m.1-corresponding-source.tar.xz' || !Number.isSafeInteger(lock.sourceArchive?.bytes) || lock.sourceArchive.bytes < 1 || lock.sourceArchive.bytes > 512 * 1024 * 1024 || !/^[a-f0-9]{64}$/.test(lock.sourceArchive?.sha256 ?? '') || lock.sourceLock?.path !== 'scripts/ffmpeg-source-lock.json' || !/^[a-f0-9]{64}$/.test(lock.sourceLock?.sha256 ?? '') || lock.buildDriver?.path !== 'scripts/build-reviewed-ffmpeg-core.sh' || !/^[a-f0-9]{64}$/.test(lock.buildDriver?.sha256 ?? '') || lock.artifact?.path !== `vendor/ffmpeg-core-${lock.buildId}.zip` || !Number.isSafeInteger(lock.artifact?.bytes) || lock.artifact.bytes < 1 || lock.artifact.bytes > 64 * 1024 * 1024 || !/^[a-f0-9]{64}$/.test(lock.artifact?.sha256 ?? '') || !Array.isArray(lock.assets) || lock.assets.length !== 5 ) { throw new Error('Invalid reviewed FFmpeg core lock.'); } const outputRoot = path.join( repositoryRoot, 'public', 'vendor', 'ffmpeg', lock.buildId ); const manifestPath = path.join(outputRoot, 'version.json'); const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex'); const safeAssetPath = (value) => { if ( typeof value !== 'string' || value.length === 0 || value.includes('\\') || value.includes('\0') || value.startsWith('/') || path.posix.normalize(value) !== value || value.split('/').some((part) => !part || part === '.' || part === '..') ) { throw new Error(`Unsafe FFmpeg asset path: ${String(value)}`); } return value; }; const verifyLockedFile = async (record, label) => { const relative = safeAssetPath(record.path); const absolute = path.join(repositoryRoot, ...relative.split('/')); const details = await lstat(absolute).catch((error) => { throw new Error(`${label} is missing: ${relative}`, { cause: error }); }); if (details.isSymbolicLink() || !details.isFile()) { throw new Error(`${label} must be a regular file: ${relative}`); } const bytes = await readFile(absolute); if ( (record.bytes !== undefined && bytes.byteLength !== record.bytes) || sha256(bytes) !== record.sha256 ) { throw new Error(`${label} does not match its locked identity: ${relative}`); } }; if ( manifest.schemaVersion !== 2 || manifest.buildId !== lock.buildId || manifest.profile !== lock.profile || manifest.coreVersion !== lock.coreVersion || manifest.ffmpegVersion !== lock.ffmpegVersion || !isDeepStrictEqual(manifest.sourceArchive, lock.sourceArchive) || !isDeepStrictEqual(manifest.sourceLock, lock.sourceLock) || !isDeepStrictEqual(manifest.buildDriver, lock.buildDriver) || !isDeepStrictEqual(manifest.toolchain, lock.toolchain) || !isDeepStrictEqual(manifest.patch, lock.patch) || !isDeepStrictEqual(manifest.opusRegression, lock.opusRegression) || !Array.isArray(manifest.assets) || manifest.assets.length !== lock.assets.length ) { throw new Error(`Invalid reviewed FFmpeg asset manifest: ${manifestPath}`); } if (!runtimeOnly) { await verifyLockedFile(lock.sourceArchive, 'FFmpeg Corresponding Source'); } await verifyLockedFile(lock.sourceLock, 'FFmpeg source lock'); await verifyLockedFile(lock.buildDriver, 'Reviewed-core build driver'); await verifyLockedFile(lock.artifact, 'Reviewed-core deterministic artifact'); const lockedAssets = new Map(lock.assets.map((asset) => [asset.path, asset])); const seen = new Set(); for (const asset of manifest.assets) { safeAssetPath(asset.path); const locked = lockedAssets.get(asset.path); if ( !locked || seen.has(asset.path) || asset.bytes !== locked.bytes || asset.sha256 !== locked.sha256 ) { throw new Error(`Unexpected reviewed FFmpeg asset: ${asset.path}`); } seen.add(asset.path); const bytes = await readFile(path.join(outputRoot, ...asset.path.split('/'))); if (bytes.byteLength !== locked.bytes || sha256(bytes) !== locked.sha256) { throw new Error(`FFmpeg asset verification failed: ${asset.path}`); } } if (seen.size !== lockedAssets.size) { throw new Error('One or more reviewed FFmpeg assets are missing.'); } console.log( `Verified ${seen.size} FFmpeg ${lock.buildId} assets in ${path.relative(repositoryRoot, outputRoot)}${runtimeOnly ? ' (runtime inputs only)' : ' with Corresponding Source'}` );