heygen-com/hyperframes · error
[s3Transport] expected lowercase SHA-256 digest, got ${JSON.
Error message
[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)} What it means
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.
Source
Thrown at packages/aws-lambda/src/s3Transport.ts:210
} finally {
// A failed conditional request may reject before consuming the stream.
// Explicit teardown avoids retaining the source descriptor on a warm
// Lambda planner.
body.destroy();
}
}
export async function sha256File(path: string): Promise<string> {
const hash = createHash("sha256");
for await (const chunk of createReadStream(path)) {
hash.update(chunk as Buffer);
}
return hash.digest("hex");
}
function assertSha256(value: string): void {
if (!/^[a-f0-9]{64}$/.test(value)) {
throw new Error(
`[s3Transport] expected lowercase SHA-256 digest, got ${JSON.stringify(value)}`,
);
}
}
type ContentAddressedObjectState = "missing" | "matching" | "conflict";
async function inspectContentAddressedObject(
client: S3Client,
bucket: string,
key: string,
expectedSize: number,
expectedSha256: string,
): Promise<ContentAddressedObjectState> {
try {
const existing = await client.send(
new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: "ENABLED" }),
);View on GitHub (pinned to c2996c8626)
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.
Example fix
// before
const sha = checksumBase64; // S3 returns base64
await uploadContentAddressedFileToS3(client, path, uri, sha);
// after
const sha = Buffer.from(checksumBase64, 'base64').toString('hex');
await uploadContentAddressedFileToS3(client, path, uri, sha); Defensive patterns
Strategy: validation
Validate before calling
function normalizeSha256(value: string): string {
let v = value;
if (v.startsWith('sha256:')) v = v.slice('sha256:'.length);
// base64 -> hex if it looks like base64 (44 chars, ends with =)
if (/^[A-Za-z0-9+/]{43}=$/.test(v)) v = Buffer.from(v, 'base64').toString('hex');
return v.toLowerCase();
}
function assertValidSha256(value: string): void {
if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`bad sha256: ${value}`);
} Type guard
const isLowerHexSha256 = (v: unknown): v is string =>
typeof v === 'string' && /^[a-f0-9]{64}$/.test(v); Try / catch
try {
await uploadContentAddressedFileToS3(client, path, uri, normalizeSha256(sha));
} catch (err) {
if (err instanceof Error && err.message.includes('expected lowercase SHA-256')) {
throw new Error(`digest normalization failed for input: ${sha}`);
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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').
Common situations: 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.
Related errors
- PLAN_V2_INTEGRITY_UNRECOVERABLE
- [handler] chunk URI at index ${i} is empty
- [s3Transport] expected s3:// URI, got: ${JSON.stringify(uri)
- [s3Transport] missing key in s3 URI: ${JSON.stringify(uri)}
- [s3Transport] empty bucket or key in s3 URI: ${JSON.stringif
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/00957d228ee67fc2.
Report an issue: GitHub.