paperclipai/paperclip · critical · Error
Artifact URL does not match its content hash and trusted…
Error message
Artifact URL does not match its content hash and trusted origin.
What it means
The cloud migrator artifacts script throws this when a pin's integrity digest does not round-trip through base64 decoding, or the pin's URL does not equal the trusted artifactBase blob URL derived from the digest hex and expected extension. This ensures artifacts are only fetched from the trusted origin at the location matching their content hash, blocking substituted or relocated artifacts.
Solutions
- Regenerate the manifest so url and integrity are derived from the same content hash.
- Restore pin.url to ${artifactBase}/blobs/<sha512-hex>.<extension> for the correct extension (tgz for packages/lockfile... json for the lockfile).
- Verify artifactBase matches the environment the manifest was produced for.
- Audit how the manifest was modified — this error often indicates accidental or malicious rewriting.
Example fix
// before "integrity": "sha512-AAAA...==", "url": "https://mirror.example.com/pkg.tgz" // after "integrity": "sha512-AAAA...==", "url": "<artifactBase>/blobs/<hex-of-sha512>.tgz"
Defensive patterns
Strategy: validation
Validate before calling
const digest = Buffer.from(pin.integrity.slice(7), 'base64');
const expectedUrl = `${artifactBase}/blobs/${digest.toString('hex')}.${extension}`;
if (digest.toString('base64') !== pin.integrity.slice(7) || pin.url !== expectedUrl)
throw new Error('Refusing artifact: URL/integrity binding failed'); Type guard
const urlMatchesHash = (pin, base, ext) => pin.url === `${base}/blobs/${Buffer.from(pin.integrity.slice(7),'base64').toString('hex')}.${ext}`; Try / catch
try { assertDescriptor(pin, 'tgz'); } catch (e) {
if (e.message.includes('does not match its content hash')) {
throw new Error('Possible tampering or stale mirror; re-download manifest from trusted origin');
} else throw e;
} Prevention
- Only download artifacts from the trusted artifactBase origin; never rewrite URLs to mirrors.
- Regenerate the manifest whenever artifacts are republished or the base URL changes.
- Treat any URL/hash mismatch as a security signal and investigate provenance.
When it happens
Trigger: assertDescriptor is given a pin whose integrity is valid-format but whose bytes don't decode back to the same base64 (corrupted digest), or whose url was rewritten to a different host/path than ${artifactBase}/blobs/<hex>.<extension> (e.g. edited URL, mirror, or wrong extension).
Common situations: Redirecting artifacts to a local mirror without regenerating the manifest; copying integrity from one artifact into another row; a build pipeline changing the artifact base URL; tampered manifest.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- ACPX runtime executable digest mismatch
- ACPX private snapshot digest mismatch
- ACPX provider requires verified package snapshots
- ACPX snapshot manifest digest mismatch
- Invalid artifact integrity or size.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b570138c625dcdff.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/cloud-migrator-artifacts.mjs:29
export const artifactBase = "https://d1p6rlowie26tp.cloudfront.net/cloud-migrators/v1";
export const artifactBucket = "paperclipai-runner-e2e-history-078455283791-us-east-1";
const prefix = "cloud-migrators/v1/";
const names = ["db", "shared"];
const maximumBytes = 32 * 1024 * 1024;
const integrityFor = (bytes) => `sha512-${createHash("sha512").update(bytes).digest("base64")}`;
export function descriptor(bytes, extension) {
const hash = createHash("sha512").update(bytes).digest("hex");
return { url: `${artifactBase}/blobs/${hash}.${extension}`, integrity: integrityFor(bytes), size: bytes.length };
}
function assertDescriptor(pin, extension) {
if (!pin || typeof pin.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(pin.integrity) ||
!Number.isSafeInteger(pin.size) || pin.size <= 0 || pin.size > maximumBytes) throw new Error("Invalid artifact integrity or size.");
const digest = Buffer.from(pin.integrity.slice(7), "base64");
if (digest.toString("base64") !== pin.integrity.slice(7) || pin.url !== `${artifactBase}/blobs/${digest.toString("hex")}.${extension}`) {
throw new Error("Artifact URL does not match its content hash and trusted origin.");
}
}
export function assertManifest(manifest, sha) {
if (manifest?.version !== 1 || manifest.sourceSha !== sha || manifest.packageVersion !== versionFor(sha)) throw new Error("Artifact source identity mismatch.");
for (const name of names) assertDescriptor(manifest.packages?.[name], "tgz");
assertDescriptor(manifest.lockfile, "json");
}
export function assertLockfile(lock, manifest) {
const version = manifest.packageVersion;
if (lock?.lockfileVersion !== 3 || !lock.packages || Array.isArray(lock.packages) ||
JSON.stringify(lock.packages[""]?.dependencies) !== JSON.stringify({ "@paperclipai/db": version })) throw new Error("Invalid migrator lockfile root.");
for (const name of names) {
const pin = lock.packages[`node_modules/@paperclipai/${name}`];
const expected = manifest.packages[name];
if (pin?.version !== version || pin.integrity !== expected.integrity || pin.resolved !== expected.url || pin.link || pin.inBundle) throw new Error("Migrator lockfile package pin mismatch.");
}View on GitHub (pinned to 3f1d897a7c)