{"record":{"id":"0422c6e3fcd943b5","repo":"dutchcoders/transfer.sh","slug":"could-not-decrypt-file","errorCode":null,"errorMessage":"Could not decrypt file","messagePattern":"Could not decrypt file","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"server/handlers.go","lineNumber":1259,"sourceCode":"\t\tif strings.TrimSpace(contentType) == \"\" {\n\t\t\tcontentType = \"text/plain; charset=utf-8\"\n\t\t}\n\t} else {\n\t\tdisposition = \"attachment\"\n\t}\n\n\tremainingDownloads, remainingDays := metadata.remainingLimitHeaderValues()\n\n\tw.Header().Set(\"Content-Disposition\", fmt.Sprintf(`%s; filename=\"%s\"`, disposition, filename))\n\tw.Header().Set(\"Connection\", \"keep-alive\")\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.Header().Set(\"X-Remaining-Downloads\", remainingDownloads)\n\tw.Header().Set(\"X-Remaining-Days\", remainingDays)\n\n\tpassword := r.Header.Get(\"X-Decrypt-Password\")\n\treader, err = attachDecryptionReader(reader, password)\n\tif err != nil {\n\t\thttp.Error(w, \"Could not decrypt file\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif metadata.Encrypted && len(password) > 0 {\n\t\tcontentType = metadata.DecryptedContentType\n\t\tcontentLength = uint64(metadata.ContentLength)\n\t}\n\n\tw.Header().Set(\"Content-Type\", contentType)\n\tw.Header().Set(\"Content-Length\", strconv.FormatUint(contentLength, 10))\n\tw.Header().Set(\"Vary\", \"Range, Referer, X-Decrypt-Password\")\n\n\tif rng != nil && rng.ContentRange() != \"\" {\n\t\tw.WriteHeader(http.StatusPartialContent)\n\t}\n\n\tif disposition == \"inline\" && canContainsXSS(contentType) {\n\t\treader = io.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))","sourceCodeStart":1241,"sourceCodeEnd":1277,"githubUrl":"https://github.com/dutchcoders/transfer.sh/blob/c37bfd95797fd6da8a6da53fc13d191994b3f687/server/handlers.go#L1241-L1277","documentation":"In linx-server's getHandler, the file being downloaded is stored encrypted at rest. When the request carries an X-Decrypt-Password header, attachDecryptionReader wraps the storage reader in a decryption stream; if that setup fails (e.g. the password header is present but decryption cannot be initialized), the server aborts with HTTP 500 'Could not decrypt file'.","triggerScenarios":"GET (or HEAD) request for a stored file where X-Decrypt-Password is set but the decryption reader cannot be attached — typically because the stored blob is not encrypted, the key/nonce metadata is missing or corrupt, or the crypto reader construction fails.","commonSituations":"Client sends a decrypt password to a file uploaded without encryption; storage backend data corrupted or truncated so metadata is invalid; a client automating downloads passes the header unconditionally; mismatched server version after an encryption format change.","solutions":["Verify the file was actually uploaded with encryption (metadata.Encrypted true) before sending X-Decrypt-Password","Re-upload the file with the same password to regenerate a valid encrypted blob","Check the storage backend for corruption/truncation of the stored file or its metadata","Remove the X-Decrypt-Password header if the file does not need decryption","Upgrade/align client and server versions so the encryption format matches"],"exampleFix":"// before\ncurl -H 'X-Decrypt-Password: secret' https://linx/files/doc.pdf\n// after\n# only send the header for files uploaded with encryption\ncurl https://linx/files/doc.pdf  # unencrypted upload — no header","handlingStrategy":"validation","validationCode":"// client side: only send the password header for encrypted uploads\nconst headers = meta.encrypted ? {'X-Decrypt-Password': password} : {}\nfetch(url, {headers})","typeGuard":"function isEncrypted(meta) { return typeof meta === 'object' && meta !== null && meta.encrypted === true && typeof meta.contentLength === 'number'; }","tryCatchPattern":"try {\n  const res = await fetch(url, {headers});\n  if (!res.ok) throw new Error(await res.text());\n  const blob = await res.blob();\n} catch (err) {\n  if (String(err).includes('Could not decrypt file')) {\n    // re-fetch without password or re-upload encrypted\n  }\n}","preventionTips":["Only attach X-Decrypt-Password when metadata confirms the file is encrypted","Re-upload files whose encryption metadata may be corrupt","Pin matching client/server versions to avoid encryption format drift","Monitor storage backend integrity for truncated blobs"],"tags":["http-500","decryption","file-download"],"backgroundTag":"decryption-failed","analyzedSha":"c37bfd95797fd6da8a6da53fc13d191994b3f687","analyzedAt":"2026-09-05T10:21:07.548Z","contentChangedAt":"2026-09-05T10:21:07.548Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}