Methodology Deliverables Insights Notes Readiness Scope brief

Notes / 03

2026-09-03

Why authenticated downloads lose their file extensions

We shipped a vault download that returned the right bytes and a correct Content-Disposition filename. Users still saved Portal_UI_CodePipeline_+_Amplify_release with no extension. The object on disk was a Terraform file. The UI title was a human sentence.

Thesis: Cross-origin fetch cannot read Content-Disposition (or a custom X-Filename header) unless the API’s CORS configuration exposes them. If JavaScript cannot see the header, your Save-As fallback will sanitize the artifact title — and titles rarely include .tf or .md.

The failure mode

Authenticated downloads often look like this:

  1. SPA calls GET /artifacts/{id}/download with a Bearer token.
  2. API returns binary bytes plus Content-Disposition: attachment; filename="thing.tf".
  3. Client builds a Blob and calls a.download = filename.

Step 3 only works if step 2’s headers are visible to script. Under the Fetch CORS rules, most response headers are opaque unless listed in Access-Control-Expose-Headers. Lambda can emit the right headers and still lose: when API Gateway HTTP API manages CORS, its expose list wins.

Fix the gateway, not only the Lambda

On API Gateway HTTP APIs, set CORS expose_headers to include at least:

Confirm with a real browser session, not only curl. Curl always sees headers; JavaScript does not.

Defense in depth: basename on the list DTO

Even with expose-headers fixed, keep a safe download basename on the list payload — the leaf name only ( codepipeline_portal.tf), not the full object key. The UI can prefer:

  1. Header / X-Filename
  2. DTO filename
  3. Sanitized title (last resort)

That avoids re-introducing path disclosure while surviving the next CORS regression. Do not put storage prefixes or assessor notes on the public list DTO.

Titles vs files

Humans want titles like “CI pipeline and release evidence.” Filesystems want codepipeline_portal.tf. Keep both: display the title, download the basename. When they collide in the UI fallback, you get underscores and no extension — exactly the bug users report as “weird filename.”

Checklist

Next: prefix keys, identical 404s, and per-tenant KMS.

Engineering commentary only — not audit, legal, or certification advice.