HTTP Content-Disposition Filename Conformance
When to Use
- A browser, curl, framework, or download worker chooses a different filename from another client.
filenameandfilename*disagree, are duplicated, or contain malformed encoding.- A server-supplied download name must be treated safely before a file is created.
- Redirect fallback, Unicode, path, reserved-name, or extension policy needs a reproducible audit.
Do not activate for HTML <title> selection, multipart upload-part names, local file renaming with no HTTP response, or generic MIME attachment handling outside HTTP.
Prerequisites
- A redacted raw
Content-Dispositionfield value and optionalContent-Type. - Python 3.9+ for the offline analyzer.
- A project-owned media-type-to-extension policy when extension consistency matters.
Never put response bodies, credentials, production URLs, or existing destination paths in a fixture. The analyzer performs no network or filesystem writes beyond reading its input and emitting JSON.
Quick Reference
python3 scripts/analyze_content_disposition.py case.json > report.json
# 0 = accepted filename, 1 = rejected filename, 2 = invalid input, 3 = output failure
{
"content_disposition": "attachment; filename=\"resume.csv\"; filename*=UTF-8''r%C3%A9sum%C3%A9.csv",
"content_type": "text/csv",
"media_type_extensions": {"text/csv": [".csv"]}
}
Completion requires decision: "accept", an empty finding_codes array, and a reviewed safe_basename. This authorizes only the name decision—not a download or write.
Procedure
1. Preserve the decision inputs
Capture the final response's field value exactly, after the HTTP implementation has combined field lines according to its documented behavior. Also record which redirect supplied the final representation, but do not feed URLs to the analyzer or assume a final URL basename is authorized.
Keep these layers separate:
- presentation: raw header syntax and quoted-string escaping;
- extended value:
charset'language'percent-encoded-value; - selection: valid
filename*takes precedence overfilename; - consumer policy: basename, controls, device names, and extension checks;
- write boundary: fresh-directory creation and atomic publication.
2. Parse before choosing
Reject the field when disposition syntax, quoting, parameter shape, or an extended value is malformed. Parameter names are case-insensitive. Duplicate parameter names make the field invalid; never select the first or last duplicate merely because one library does.
For filename*, parse the three RFC 8187 parts before percent-decoding. Reject malformed percent triplets, invalid bytes for the declared charset, unsupported charsets, and invalid language syntax. Do not MIME-word-decode filename; RFC 6266's HTTP profile does not define encoded-word recovery as a filename selection rule.
A valid filename* is selected ahead of filename. A malformed filename* is not silently repaired or downgraded to a potentially attacker-chosen fallback. Ask the producer for one unambiguous header.
3. Treat the received name as advisory
Before any write, require a single basename. Reject:
/,\\, dot segments, leading/trailing whitespace, and trailing dots;- C0/DEL or Unicode format controls, including bidirectional overrides;
- platform special names such as
CON,NUL,COM1, andLPT1; - normalization changes that the consuming platform has not explicitly reviewed;
- an extension outside the supplied media-type policy.
The analyzer reports NFC differences rather than changing them silently. Extension policy is a project recommendation, not an RFC media-type mandate: supply it explicitly, verify payload type independently, and never infer executable safety from a filename alone.
4. Compare clients with synthetic fixtures
Run the same redacted matrix through the analyzer and each target client/framework:
| Boundary | Fixtures |
|---|---|
| Selection | only filename; only filename*; both; duplicate each |
| Encoding | UTF-8; ISO-8859-1; malformed %; invalid UTF-8; language tag |
| Syntax | token; quoted escape; empty value; unterminated quote; folded input |
| Safety | separators; ./..; bidi/control; device name; trailing dot/space |
| Policy | matching and mismatching verified media type/extension |
| Navigation | header on initial response, final response, neither, and conflicting redirects |
Record observed client behavior separately from conformance. A client saving a file is not evidence that the field was valid or the name safe.
5. Enforce the write boundary
After acceptance, create output only inside a fresh quota-limited temporary directory. Join the reviewed basename to that directory, resolve the parent, and prove it remains the intended directory. Open with exclusive creation, stream under byte/time limits, verify the expected payload digest or authenticated metadata, flush, and atomically publish only with explicit caller authorization.
On collision, write failure, changed metadata, or digest mismatch, preserve neither a partial destination nor an automatically incremented attacker-controlled name. Return to the caller for a new policy decision.
Failure Recovery
- Duplicate or malformed parameter: fix the producer; do not pick a convenient value.
- Client disagreement: retain the raw field, client/version, selected parameter, and resulting basename; compare these transitions rather than only final filenames.
- Unsafe but decodable name: reject it. If business rules permit a replacement, generate a local name independent of the supplied path and retain the original only in escaped audit metadata.
- Redirect with no valid filename: apply an explicitly documented local fallback policy; do not trust the final URL path by default.
- Output failure: treat exit
3as no report. Do not mistake buffered write failure for a completed audit.
Pitfalls and Safety
- RFC 6266 defines received filenames as advisory; precedence does not make a filename safe.
filename*uses RFC 8187 encoding, not form/query+-for-space rules.- MIME multipart
Content-Dispositionhas a different profile; use the multipart wire-conformance skill for uploads. - Unicode normalization, reserved names, and extension allowlists are consumer/platform policy. Keep them distinguishable from standards violations.
- Never fetch a researched or fixture-supplied URL, extract an archive, or overwrite an existing file as part of this audit.
Objective Verification
PYTHONDONTWRITEBYTECODE=1 python3 tests/test_analyze_content_disposition.py
The tests prove filename* precedence, UTF-8 and ISO-8859-1 decoding, duplicate and malformed-percent separation, invalid UTF-8 rejection, path/bidi/device/extension policy findings, malformed schema/JSON/non-finite rejection, unreadable input handling, and controlled stdout failure.
Evaluation Prompts
- Normal: Audit
attachment; filename="resume.csv"; filename*=UTF-8''r%C3%A9sum%C3%A9.csvunder a.csvpolicy without writing a file. - Difficult edge: Reject duplicate
filenamevalues plus a malformedfilename*containing encoded traversal, bidi, and extension-confusion evidence; identify independently provable findings without silently repairing. - Should not activate: Choose an HTML document title when there is no HTTP response header or downloaded file.
Sources
Sourced facts: syntax, duplicate invalidity, extended-value precedence, decoding, and advisory-filename requirements come from the standards. Issue records establish independent implementation and fallback failures; they are not normative.
- RFC 6266: Content-Disposition in HTTP
- RFC 8187: HTTP Header Field Parameters
- Spring Framework #31940
- Spring Framework #36805
- curl #10533
- curl #20318
Recommendations: strict fail-closed fallback, NFC review, explicit extension policy, synthetic differential fixtures, quota-limited exclusive creation, and atomic publication are this skill's operational safety guidance.