{"record":{"id":"77d9d8d4c66ecfaf","repo":"ruvnet/ruflo","slug":"footer-hash-must-be-sha256-size-bytes-got-fo","errorCode":null,"errorMessage":"Footer hash must be ${SHA256_SIZE} bytes, got ${footerHash.length}","messagePattern":"Footer hash must be (.+?) bytes, got (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/appliance/rvfa-signing.ts","lineNumber":327,"sourceCode":"    };\n\n    // Embed signature in header and rebuild\n    header.signature = metadata;\n    const rebuilt = rebuildRvfa(buf, header, sectionData, footer);\n    await writeFile(rvfaPath, rebuilt);\n\n    return metadata;\n  }\n\n  /**\n   * Sign a section footer hash (detached signature).\n   *\n   * @param footerHash  The 32-byte SHA256 footer hash from an RVFA file.\n   * @returns Hex-encoded Ed25519 signature.\n   */\n  async signSections(footerHash: Buffer): Promise<string> {\n    if (footerHash.length !== SHA256_SIZE) {\n      throw new Error(\n        `Footer hash must be ${SHA256_SIZE} bytes, got ${footerHash.length}`,\n      );\n    }\n    const sig = sign(null, footerHash, this.keyObj);\n    return sig.toString('hex');\n  }\n\n  /**\n   * Sign an RVFP patch file (detached signature).\n   *\n   * @param patchData  The raw patch binary data.\n   * @returns Hex-encoded Ed25519 signature.\n   */\n  async signPatch(patchData: Buffer): Promise<string> {\n    const digest = createHash('sha256').update(patchData).digest();\n    const sig = sign(null, digest, this.keyObj);\n    return sig.toString('hex');\n  }","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/appliance/rvfa-signing.ts#L309-L345","documentation":"Thrown by signSections when footerHash is not exactly 32 bytes (SHA256_SIZE). The signer signs a fixed-size digest, so a wrong-size input is a caller programmer error, not a data problem.","triggerScenarios":"Calling signer.signSections(buf) where buf is a hex string mistaken for bytes, a 64-byte SHA512 digest, the raw footer bytes, or section data instead of the digest.","commonSituations":"Passing a hex-encoded string (64 ASCII bytes) instead of decoding it; passing a SHA512 digest; passing the section payload rather than its hash.","solutions":["Pass the 32-byte footer hash from parseRvfaBinary, or compute crypto.createHash('sha256').update(data).digest().","If you hold a hex string, decode it first: Buffer.from(hex, 'hex').","Confirm the digest algorithm is SHA256 (32 bytes), not SHA512 or SHA1."],"exampleFix":"// before\nconst sig = await signer.signSections(Buffer.from(footerHex));\n\n// after\nconst sig = await signer.signSections(Buffer.from(footerHex, 'hex'));","handlingStrategy":"validation","validationCode":"if (!Buffer.isBuffer(footerHash) || footerHash.length !== 32) {\n  throw new Error('footerHash must be a 32-byte SHA256 Buffer');\n}\nconst sig = await signer.signSections(footerHash);","typeGuard":"function isSha256Digest(b: unknown): b is Buffer {\n  return Buffer.isBuffer(b) && b.length === 32;\n}","tryCatchPattern":null,"preventionTips":["Keep digest bytes as Buffers end-to-end; avoid hex round-trips at API boundaries.","Name parameters *_digest to make the 32-byte expectation explicit.","Unit-test the signer with a known 32-byte fixture."],"tags":["rvfa-signing","ed25519","validation","programmer-error"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}