Skip to content

[Bug] Server crashes (ERR_INVALID_CHAR) when downloading attachment with non-ASCII filename #2291

Description

@likai1130

Summary

Uploading an attachment with a non-ASCII filename (e.g. Chinese characters like 截图.png) causes the server process to crash when the attachment is later downloaded. The Content-Disposition response header is set with raw non-ASCII bytes, which Node.js v24 strictly rejects with ERR_INVALID_CHAR, resulting in an unhandled exception that terminates the process.

Surface Area

  • Server: packages/server/src/api/attachments.tsencodeRfc6266Filename() + download route

Environment

  • version: 0.5.20
  • install method: Docker image (harbor.dns.guazi.com/first-tree/first-tree:0.5.20)
  • operating system: Linux (private deployment)
  • Node.js: v24.19.0

Reproduction

  1. Deploy First Tree 0.5.20 via Docker
  2. Upload any attachment whose filename contains non-ASCII characters (e.g. 截图.png)
  3. Trigger an attachment download: GET /api/v1/attachments/<uuid>
  4. Server process crashes immediately

Expected Behavior

The attachment downloads successfully. The Content-Disposition header uses RFC 6266 filename*=UTF-8''... encoding (percent-encoded) for non-ASCII filenames, or at minimum escapes all non-ASCII bytes so the header value is safe for Node.js HTTP.

Actual Behavior

The server crashes with an unhandled TypeError:

node:_http_outgoing:690
    throw new ERR_INVALID_CHAR('header content', name);

TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["content-disposition"]
    at ServerResponse.setHeader (node:_http_outgoing:690:11)
    at sendStream (/app/node_modules/.pnpm/fastify@5.8.2/node_modules/fastify/lib/reply.js:815:11)
    ...

Root Cause

encodeRfc6266Filename() in packages/server/src/api/attachments.ts only encodes CR, LF, ", and \:

function encodeRfc6266Filename(name: string): string {
  return name.replace(/[\r\n"\\]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
}

Non-ASCII characters (e.g. Chinese, Japanese, emoji) pass through unencoded. Node.js v24 enforces strict ASCII-only HTTP header values, so the raw non-ASCII bytes in Content-Disposition: inline; filename="截图.png" trigger a fatal throw.

Suggested Fix

Use filename*=UTF-8''<percent-encoded> (RFC 5987 / RFC 6266) for filenames containing non-ASCII characters:

function encodeRfc6266Filename(name: string): string {
  // If all ASCII and safe, use simple quoted form
  if (/^[\x20-\x7E]*$/.test(name) && !/[\r\n"\\]/.test(name)) {
    return `filename="${name}"`;
  }
  // RFC 5987: filename*=UTF-8''<percent-encoded>
  const encoded = encodeURIComponent(name).replace(/['()*]/g, (c) =>
    `%${c.charCodeAt(0).toString(16).toUpperCase()}`
  );
  return `filename*=UTF-8''${encoded}`;
}

Workaround

Rename files to ASCII-only names before uploading (e.g. image.png instead of 截图.png).

Additional Context

Discovered during private deployment testing. The crash is 100% reproducible with any non-ASCII filename. The process exits without a graceful shutdown, causing brief downtime until the container restarts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions