{"record":{"id":"00957d228ee67fc2","repo":"heygen-com/hyperframes","slug":"s3transport-expected-lowercase-sha-256-digest-g","errorCode":null,"errorMessage":"[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)}","messagePattern":"\\[s3Transport\\] expected lowercase SHA-256 digest, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/aws-lambda/src/s3Transport.ts","lineNumber":210,"sourceCode":"  } finally {\n    // A failed conditional request may reject before consuming the stream.\n    // Explicit teardown avoids retaining the source descriptor on a warm\n    // Lambda planner.\n    body.destroy();\n  }\n}\n\nexport async function sha256File(path: string): Promise<string> {\n  const hash = createHash(\"sha256\");\n  for await (const chunk of createReadStream(path)) {\n    hash.update(chunk as Buffer);\n  }\n  return hash.digest(\"hex\");\n}\n\nfunction assertSha256(value: string): void {\n  if (!/^[a-f0-9]{64}$/.test(value)) {\n    throw new Error(\n      `[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)}`,\n    );\n  }\n}\n\ntype ContentAddressedObjectState = \"missing\" | \"matching\" | \"conflict\";\n\nasync function inspectContentAddressedObject(\n  client: S3Client,\n  bucket: string,\n  key: string,\n  expectedSize: number,\n  expectedSha256: string,\n): Promise<ContentAddressedObjectState> {\n  try {\n    const existing = await client.send(\n      new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: \"ENABLED\" }),\n    );","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/aws-lambda/src/s3Transport.ts#L192-L228","documentation":"Thrown by assertSha256, a private guard called at the top of both uploadContentAddressedFileToS3 and downloadS3ObjectToFileVerified, when the expectedSha256 argument does not match the strict lowercase-hex 64-character pattern /^[a-f0-9]{64}$/. The library uses the digest both as a CAS key and as an S3 ChecksumSHA256 header, so any malformed value (uppercase, base64, truncated, prefixed) would produce a corrupt content-addressed key or a signature mismatch downstream. Failing early prevents a bad object from landing in the immutable store.","triggerScenarios":"Passing a digest in uppercase hex (e.g. from a tool that uppercases), a base64-encoded checksum (S3's native ChecksumSHA256 wire format), a truncated 32-char digest, or a value with a 'sha256:' prefix. Any caller of uploadContentAddressedFileToS3 or downloadS3ObjectToFileVerified whose digest source differs from crypto.createHash('sha256').digest('hex').","commonSituations":"Interfacing with a system that emits base64 digests (AWS SDK ChecksumSHA256 is base64); copying a digest from a git log or UI that uppercases; a manifest schema that stores 'sha256:abcd…'; passing the raw hash Buffer instead of its hex string.","solutions":["Normalize the digest to lowercase hex before the call: digest.toLowerCase().","If the source is base64, convert it: Buffer.from(b64, 'base64').toString('hex').","Strip any algorithm prefix (e.g. 'sha256:') before passing.","Verify the digest is exactly 64 chars with /[a-f0-9]{64}/ before constructing the call."],"exampleFix":"// before\nconst sha = checksumBase64; // S3 returns base64\nawait uploadContentAddressedFileToS3(client, path, uri, sha);\n\n// after\nconst sha = Buffer.from(checksumBase64, 'base64').toString('hex');\nawait uploadContentAddressedFileToS3(client, path, uri, sha);","handlingStrategy":"validation","validationCode":"function normalizeSha256(value: string): string {\n  let v = value;\n  if (v.startsWith('sha256:')) v = v.slice('sha256:'.length);\n  // base64 -> hex if it looks like base64 (44 chars, ends with =)\n  if (/^[A-Za-z0-9+/]{43}=$/.test(v)) v = Buffer.from(v, 'base64').toString('hex');\n  return v.toLowerCase();\n}\nfunction assertValidSha256(value: string): void {\n  if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`bad sha256: ${value}`);\n}","typeGuard":"const isLowerHexSha256 = (v: unknown): v is string =>\n  typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);","tryCatchPattern":"try {\n  await uploadContentAddressedFileToS3(client, path, uri, normalizeSha256(sha));\n} catch (err) {\n  if (err instanceof Error && err.message.includes('expected lowercase SHA-256')) {\n    throw new Error(`digest normalization failed for input: ${sha}`);\n  }\n  throw err;\n}","preventionTips":["Always produce digests with createHash('sha256').digest('hex') so they're already lowercase hex.","When consuming digests from external sources (S3 checksums, OCI manifests), normalize base64→hex and lowercase before passing.","Strip algorithm prefixes ('sha256:') at the boundary."],"tags":["sha256","validation","plan-v2","regex","content-addressing"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}