{"record":{"id":"f9b968d29348d949","repo":"hashicorp/terraform","slug":"invalid-md5-f9b968","errorCode":null,"errorMessage":"invalid md5","messagePattern":"invalid md5","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":621,"sourceCode":"\t\tTableName:            aws.String(c.ddbTable),\n\t\tConsistentRead:       aws.Bool(true),\n\t}\n\n\tresp, err := c.dynClient.GetItem(ctx, getParams)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to retrieve item from DynamoDB table %q: %w\", c.ddbTable, err)\n\t}\n\n\tvar val string\n\tif v, ok := resp.Item[\"Digest\"]; ok {\n\t\tif v, ok := v.(*dynamodbtypes.AttributeValueMemberS); ok {\n\t\t\tval = v.Value\n\t\t}\n\t}\n\n\tsum, err := hex.DecodeString(val)\n\tif err != nil || len(sum) != md5.Size {\n\t\treturn nil, errors.New(\"invalid md5\")\n\t}\n\n\treturn sum, nil\n}\n\n// store the hash of the state so that clients can check for stale state files.\nfunc (c *RemoteClient) putMD5(ctx context.Context, sum []byte) error {\n\tif c.ddbTable == \"\" {\n\t\treturn nil\n\t}\n\n\tif len(sum) != md5.Size {\n\t\treturn errors.New(\"invalid payload md5\")\n\t}\n\n\tputParams := &dynamodb.PutItemInput{\n\t\tItem: map[string]dynamodbtypes.AttributeValue{\n\t\t\t\"LockID\": &dynamodbtypes.AttributeValueMemberS{","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L603-L639","documentation":"Returned by RemoteClient.getMD5 (internal/backend/remote-state/s3/client.go:621) when the 'Digest' value read from the DynamoDB locking table fails to hex-decode or its decoded length is not md5.Size (16 bytes). The Digest attribute stores the MD5 of the last-written state for consistency checks; a malformed digest means integrity verification is impossible. Get() logs the error and, if the digest is non-empty and mismatches beyond the retry window, surfaces a badChecksumError.","triggerScenarios":"The DynamoDB item at LockID = <bucket>/<key>-md5 has a 'Digest' attribute that is not a 32-char hex string; external/manual edits to the table; a corrupt or partial write left a bad digest; the Digest column was overwritten with wrong data.","commonSituations":"Manual edits to the DynamoDB locking table; an aborted/crashed Put that wrote the S3 object but a malformed Digest; switching DynamoDB schema/format between versions; another tool writing to the same table with a different encoding.","solutions":["Inspect the DynamoDB item and correct or delete the malformed Digest attribute (a 32-char lowercase hex MD5).","Re-push a known-good state with 'terraform state push' to rewrite the digest.","Confirm no external process is mutating the locking table."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Verify the digest is well-formed before relying on it.\nif d, err := hex.DecodeString(storedDigest); err != nil || len(d) != md5.Size {\n    log.Println(\"[WARN] DynamoDB Digest is malformed; state integrity check skipped\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := client.getMD5(ctx); err != nil {\n    if strings.Contains(err.Error(), \"invalid md5\") {\n        log.Println(\"[ERROR] DynamoDB Digest corrupt; consider 'terraform state push' to repair\")\n    }\n}","preventionTips":["Do not manually edit the DynamoDB Digest attribute.","After aborted applies, verify the Digest matches the S3 state via 'terraform state pull/push'.","Restrict write access to the DynamoDB locking table to terraform only."],"tags":["backend","s3","aws","dynamodb","md5","data-integrity"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}