{"record":{"id":"908301e7cd1b6825","repo":"sipeed/picoclaw","slug":"decrypt-matrix-media-w","errorCode":null,"errorMessage":"decrypt matrix media: %w","messagePattern":"decrypt matrix media: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/matrix/matrix.go","lineNumber":1026,"sourceCode":"\tif ctx != nil {\n\t\tdlCtx = ctx\n\t}\n\treqCtx, cancel := context.WithTimeout(dlCtx, 20*time.Second)\n\tdefer cancel()\n\n\tresp, err := c.client.Download(reqCtx, parsed)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer resp.Body.Close()\n\n\treader := resp.Body\n\treaderClose := func() error { return nil }\n\n\t// Encrypted attachments put URL in msgEvt.File and require client-side decryption.\n\tif msgEvt != nil && msgEvt.File != nil && msgEvt.URL == \"\" {\n\t\tif err = msgEvt.File.PrepareForDecryption(); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"decrypt matrix media: %w\", err)\n\t\t}\n\t\tdecryptReader := msgEvt.File.DecryptStream(resp.Body)\n\t\treader = decryptReader\n\t\treaderClose = decryptReader.Close\n\t}\n\n\tlabel := matrixMediaLabel(msgEvt, mediaKind)\n\text := matrixMediaExt(label, matrixContentType(msgEvt), mediaKind)\n\tmediaDir, err := matrixMediaTempDir()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create matrix media directory: %w\", err)\n\t}\n\ttmp, err := os.CreateTemp(mediaDir, \"matrix-media-*\"+ext)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttmpPath := tmp.Name()\n\tcleanup := true","sourceCodeStart":1008,"sourceCodeEnd":1044,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/matrix/matrix.go#L1008-L1044","documentation":"Thrown by MatrixChannel.downloadMedia when an inbound attachment is encrypted (msgEvt.File set, msgEvt.URL empty) and msgEvt.File.PrepareForDecryption fails (pkg/channels/matrix/matrix.go:1026). PrepareForDecryption parses the EncryptedFile JWK — key, iv, v, hashes.sha256 — and rejects malformed metadata: wrong key/IV lengths, missing hashes, or an unparseable JWK. The HTTP download already succeeded at this point; only client-side AES-CTR/Olm-style attachment decryption setup failed.","triggerScenarios":"An m.room.encrypted media event whose file object lacks hashes or has a truncated key/iv; bridges or custom clients writing non-spec encrypted attachment metadata; event content mangled by a relay; a msgEvt.File built without URL inside, causing downstream decrypt errors after PrepareForDecryption.","commonSituations":"Receiving encrypted media from non-Element clients with divergent attachment encryption; bridge software that forwards file metadata incompletely; adversarial/malformed events; older spec drafts (v field missing) from ancient clients.","solutions":["Inspect the event's file JSON: verify v=='v1', url is mxc, key.kty/alg/op/extractable shape, iv base64 16 bytes, hashes.sha256 present","If metadata is malformed, skip the attachment and notify — retrying cannot fix remote content","Confirm senders use spec-compliant attachment encryption (Matrix v1.1+ 'm.megolm-backup' era EncryptedFile shape)","Check the bot can decrypt this room's events at all (crypto helper initialized, room keys received) — if the Olm layer is broken, plain files still work while encrypted ones fail elsewhere"],"exampleFix":"// before\n if err = msgEvt.File.PrepareForDecryption(); err != nil {\n \treturn \"\", fmt.Errorf(\"decrypt matrix media: %w\", err)\n }\n\n// after: skip malformed encrypted attachments without failing the handler\n if err = msgEvt.File.PrepareForDecryption(); err != nil {\n \tlogger.WarnCF(\"matrix\", \"undecryptable attachment\", map[string]any{\"error\": err.Error()})\n \treturn \"\", &SkipAttachmentError{Cause: err} // caller notifies user, continues processing text","handlingStrategy":"fallback","validationCode":"// structural pre-check of the encrypted-file JWK before attempting decryption\nfunc encryptedFileWellFormed(f *event.EncryptedFile) bool {\n\treturn f != nil && f.Key != nil && len(f.Key.K) > 0 &&\n\t\tlen(f.IV) > 0 && f.Hashes != nil && f.Hashes.SHA256 != \"\"\n}","typeGuard":null,"tryCatchPattern":"path, err := c.downloadMedia(ctx, msgEvt, mediaKind)\nif err != nil {\n\tvar decErr *EncryptedMediaError // wrap downloadMedia's result in a typed error at your boundary\n\tif errors.As(err, &decErr) {\n\t\tnotifyUser(\"attachment could not be decrypted; asking sender to re-send\") // degrade, keep flow alive\n\t\treturn nil, nil\n\t}\n\treturn nil, err\n}","preventionTips":["Skip-and-notify on undecryptable attachments instead of failing the whole inbound message","Verify crypto helper init succeeded at startup — a broken Olm session shifts failures here","Track decrypt-failure rates per sender/room to spot broken bridges early","Never retry decryption of malformed metadata: the remote content will not change"],"tags":["go","matrix","crypto","media","e2ee","inbound","decryption"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}