Guide 112 worked guide

How to assess file upload risk beyond extension checks

Assess file uploads beyond extensions by tracing content handling, ownership, replacement, derivatives, and the authorization required when results become available.

Assess upload risk across its whole lifecycle: who may submit it, what bytes are accepted, how a parser processes them, where the original and derived files are stored, and how another person receives them. A .png extension is a claim made by the uploader. It does not establish that the bytes are an image, that an image parser is safe, or that the server will never treat the file as executable content. Start the review with the upload's purpose and follow the file until deletion. If the system only stores an opaque object for a private owner, its risks differ from a public avatar service that transforms and serves the file to every browser.

OWASP's File Upload Cheat Sheet recommends a set of controls rather than a single extension check: allowed types, validation, generated filenames, storage location, limits, authorization, and safe processing. CWE-434 describes unrestricted upload of a dangerous file type. Neither source implies that every uploaded file is dangerous; they tell you which boundaries must be checked for a specific workflow.

Work through one avatar upload

Illustrative application and all names below are invented. In Alder Desk, a signed-in user uploads an avatar. The API accepts a multipart file named portrait.png, checks that the filename ends in .png, and saves it under a public web directory. A later page uses the saved path as an image URL. The scanner reports “unrestricted file upload.” That label is plausible, but the developer needs to know what the server actually does with the bytes and how the web server serves them.

Write the intended behavior first: one authenticated user can replace their own avatar with a small raster image; the application stores a normalized image under an opaque name and serves only that derivative as image content. The review should not quietly expand that purpose to arbitrary documents, archives, scripts, or files for other users.

Use this illustrative defensive flow, not drop-in framework code:

Authenticate user and authorize avatar update for this account.
Apply the request-integrity/CSRF defense if browser cookies authorize it.
Enforce request and per-file byte limits before and while reading.
Accept only the declared image types the product supports.
Decode with a maintained image library under resource limits.
Reject if decoding, dimensions, or policy checks fail.
Re-encode pixels to a server-chosen format; discard the original.
Store the derivative under a random server-generated object key.
Serve through an authorized media path with a fixed response type.
Record the owner, object key, size, processing result, and expiry policy.

This flow changes the authority of the file. The user controls bytes, but not the storage path or response headers. The decoder and encoder are still part of the attack surface; their version, limits, and isolation deserve review. Re-encoding is useful for an avatar because the product wants pixels, not the original file. It would be inappropriate if the product's promise is to preserve an exact legal document. Choose controls around the actual business requirement.

Inspect five boundaries, not one filename

Boundary Evidence to inspect Failure that matters
Submission Authenticated route, account/object authorization, rate and size limits Another user can replace an avatar or overwhelm storage/processing.
Type decision Extension, declared MIME, file signature, parser outcome A user-controlled label is treated as proof of content.
Processing Decoder or document/AV service, time and memory limits, error handling Untrusted bytes are parsed with excessive authority or without resource bounds.
Storage Generated key, private bucket or directory, permissions, retention Raw input lands in an executable or publicly listable location.
Retrieval Access check, content type, disposition, cache, redirects, CDN behavior A file is served to an unintended reader or interpreted in an unintended context.

Do not treat MIME or a file signature as a final verdict. A MIME header comes from the client. Signature checks may identify one format while leaving embedded or malformed content, dangerous metadata, or parser-specific behavior unresolved. OWASP recommends combining controls and considering image rewriting or content disarm where suitable, rather than relying on one marker. Read the error and fallback paths too: a failed transform that publishes the raw upload may erase the main defense.

For Alder Desk, the public directory is the key observation. Ask whether the web server executes any file type in that directory, whether it sets a server-controlled content type, whether uploaded paths are reachable without authorization, and whether old originals remain after a new avatar is produced. The answer may be “public read is intentional, execution is disabled, and only normalized output is published.” If so, document those facts. If the source merely writes a filename but the serving configuration is unavailable, do not claim code execution from the extension check alone.

Make publication a state transition

Alder's revised design keeps the old avatar visible while a replacement is processed. Here is the invented state record:

user U7: current_avatar=D4, requested_generation=12
upload U12: raw_object=Q12/version1, owner=U7, state=pending
attempt A1: reads Q12/version1; output private until accepted

The raw object is not a public avatar. pending must be enforced by storage and retrieval permissions, not only by the UI hiding a button. A direct storage URL must not bypass that distinction. Only a successfully normalized derivative is eligible for publication.

Now two uploads overlap. Generation 12 is slow; generation 13 finishes first and becomes the current avatar. When generation 12 eventually finishes, the publisher compares the attempt's generation with the user's requested generation. It must not replace generation 13 with an older image. A conditional update, transaction or equivalent authoritative mechanism must enforce this comparison; checking before a long upload is insufficient.

The same rule makes retries understandable. Attempt A2 for generation 12 may produce another private derivative, but neither attempt has authority to publish after generation 13 supersedes it. Extra private objects go to cleanup without becoming downloadable. The reader should be able to identify the exact operation that updates current_avatar, not merely a callback named on_scan_complete.

Resolve failure without exposing the original

For this synthetic avatar feature, the chosen outcomes are:

Event Visible avatar Upload state and next action
Valid image decoded/re-encoded within limits New derivative, if generation still current Accepted; raw object scheduled for cleanup
Supported extension, invalid image bytes Previous D4 Rejected; no raw fallback
Parser timeout or memory limit Previous D4 Failed/limited; bounded retry policy, not acceptance
Late success for superseded generation Latest accepted generation Superseded; private derivative cleaned up
Cleanup temporarily fails Current accepted derivative only Cleanup pending, with access still denied to raw objects

A failure handler that serves the original to avoid a broken image defeats the whole transformation boundary. A cleanup failure should create operational work, not grant access. Deletion from primary storage also does not establish disappearance from caches, retained versions or backups.

Resource limits need to describe decoded work. For illustration, a 4,000 × 3,000 image has 12 million pixels; one four-byte-per-pixel buffer alone is about 48 million bytes, before decoder overhead and additional copies. This arithmetic is not a measured memory estimate for a library. It explains why a small compressed-byte limit cannot substitute for dimensions, total pixels and an external processing limit. Animated formats may add frame-related work; reject them unless the product deliberately supports and bounds it.

Verify the retrieval contract, not only the upload response

A useful release artifact names the derivative's server-controlled type, authorized audience, cache policy and storage route. Alder chooses normalized PNG derivatives, owner/account authorization for this private product, and opaque object keys. The original filename remains display metadata and never controls the storage key or response headers. Apply the private audience policy at the CDN too: an opaque URL is not authorization, and a shared public cache must not return an authorized user's private derivative to another viewer.

For future authorized validation, follow one synthetic upload ID through raw object, attempt, derivative and current-avatar pointer. Confirm a valid replacement works, a malformed image leaves D4 intact, and the two-generation race preserves the newer request. Inspect both the application delivery path and any CDN/storage route. These are expected outcomes only; no upload or parser execution was performed for this article. OWASP's upload guidance supports layered type, storage, authorization and resource controls; the generation policy is this example's application design.

The completed review can now distinguish “extension-only acceptance is weak” from the stronger defect “failed processing publishes original bytes.” It also recognizes an effective design when quarantine, derivative publication and retrieval are all enforced.

Distinguish replacement from ownership transfer

Alder's user U7 may replace their own avatar, but the upload's object identifier should not let them select another user's publication pointer. Trace authorization at submission and again at the operation that assigns the accepted derivative. A correctly normalized image can still be attached to the wrong account or profile if the worker trusts a caller-supplied owner.

The synthetic job record binds U12 to U7 and the requesting account. A worker should resolve that persisted relationship rather than accepting a new owner field from a completion message. If U7 loses the relevant account membership while processing runs, apply the product's declared rule for publication and later retrieval. The image transformation itself does not decide current authority.

This is especially useful when processing is delegated to another service. A callback saying “upload complete” is a processing result, not permission to publish any object named in the callback. The application needs an authenticated, attempt-bound result and a current publication decision for the corresponding upload. The exact callback mechanism depends on the processing service and must be reviewed in that implementation.

Inventory originals, derivatives, and pointers separately

The avatar's lifecycle has at least three objects: raw Q12, the produced derivative, and the profile pointer that makes a derivative current. Each has a different purpose and access rule. Deleting the old profile pointer does not prove the raw object is gone. Successfully deleting Q12 does not prove an older derivative is no longer served from a separate path.

For this private product, record which identities may read each object and what marks it eligible for delivery. A failed attempt's output remains private even if its name is known. A superseded derivative may need cleanup, but cleanup delay must not silently make it the current avatar. Retention and authorization are related operational concerns with different evidence.

A useful acceptance packet follows one harmless upload through all three objects. It also includes a rejected image and the overlapping-generation case. Inspect the final pointer and actual retrieval path, not only the upload endpoint's response. If the application cannot map the deployed processing service or CDN policy, report that gap instead of promoting the source design to a production guarantee.

The result should explain which defect is being repaired. Extension-only acceptance, excessive parser authority, raw-file fallback, stale-generation publication, and wrong-owner delivery are different failure modes. Keeping them distinct helps assign the correct owner and avoids calling a renamed file or a successful image decode a complete upload-security fix.

Next action

For one upload route, draw submission → processing → storage → retrieval and write down which component owns each arrow. If the file is an archive, use the archive extraction review for the extra path and expansion risks. For a broader guide to turning an alert into a testable claim, see the scanner-alert review.