dutchcoders/transfer.sh · error

Could not decrypt file

Error message

Could not decrypt file

What it means

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'.

Source

Thrown at server/handlers.go:1259

		if strings.TrimSpace(contentType) == "" {
			contentType = "text/plain; charset=utf-8"
		}
	} else {
		disposition = "attachment"
	}

	remainingDownloads, remainingDays := metadata.remainingLimitHeaderValues()

	w.Header().Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, filename))
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Cache-Control", "no-store")
	w.Header().Set("X-Remaining-Downloads", remainingDownloads)
	w.Header().Set("X-Remaining-Days", remainingDays)

	password := r.Header.Get("X-Decrypt-Password")
	reader, err = attachDecryptionReader(reader, password)
	if err != nil {
		http.Error(w, "Could not decrypt file", http.StatusInternalServerError)
		return
	}

	if metadata.Encrypted && len(password) > 0 {
		contentType = metadata.DecryptedContentType
		contentLength = uint64(metadata.ContentLength)
	}

	w.Header().Set("Content-Type", contentType)
	w.Header().Set("Content-Length", strconv.FormatUint(contentLength, 10))
	w.Header().Set("Vary", "Range, Referer, X-Decrypt-Password")

	if rng != nil && rng.ContentRange() != "" {
		w.WriteHeader(http.StatusPartialContent)
	}

	if disposition == "inline" && canContainsXSS(contentType) {
		reader = io.NopCloser(bluemonday.UGCPolicy().SanitizeReader(reader))

View on GitHub (pinned to c37bfd9579)

Solutions

  1. Verify the file was actually uploaded with encryption (metadata.Encrypted true) before sending X-Decrypt-Password
  2. Re-upload the file with the same password to regenerate a valid encrypted blob
  3. Check the storage backend for corruption/truncation of the stored file or its metadata
  4. Remove the X-Decrypt-Password header if the file does not need decryption
  5. Upgrade/align client and server versions so the encryption format matches

Example fix

// before
curl -H 'X-Decrypt-Password: secret' https://linx/files/doc.pdf
// after
# only send the header for files uploaded with encryption
curl https://linx/files/doc.pdf  # unencrypted upload — no header
Defensive patterns

Strategy: validation

Validate before calling

// client side: only send the password header for encrypted uploads
const headers = meta.encrypted ? {'X-Decrypt-Password': password} : {}
fetch(url, {headers})

Type guard

function isEncrypted(meta) { return typeof meta === 'object' && meta !== null && meta.encrypted === true && typeof meta.contentLength === 'number'; }

Try / catch

try {
  const res = await fetch(url, {headers});
  if (!res.ok) throw new Error(await res.text());
  const blob = await res.blob();
} catch (err) {
  if (String(err).includes('Could not decrypt file')) {
    // re-fetch without password or re-upload encrypted
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dutchcoders/transfer.sh@c37bfd9579 (2026-09-05). Data as JSON: /api/errors/0422c6e3fcd943b5. Report an issue: GitHub.