Last reviewed: October 30, 2026 (Asia/Shanghai).

Direct answer

Treat a file upload as an untrusted byte stream that must pass several independent gates before a coding-agent patch can use it. Authenticate and authorize the uploader, reject requests above a transport and application size limit, allow only the file classes the feature needs, inspect bytes rather than trusting the client filename or Content-Type, write to a private staging location with an application-generated name, and promote the object only after validation completes. A rejected upload must leave no executable, public, or partially processed result behind.

The dangerous patch often looks like a convenience feature: accept file.originalname, trust file.mimetype, write into a web-visible directory, and hand the path directly to a parser or agent tool. A browser can send a misleading media type, a filename can contain path or device syntax, and a valid-looking file can be much larger after decompression or parsing. OWASP’s File Upload Cheat Sheet recommends allowlists, generated names, size limits, authorization, storage outside the webroot, and content inspection where appropriate.

Make the request boundary explicit. HTTP 413 means the request content is larger than a server limit; MDN’s 413 reference notes that a server may close the connection or send Retry-After. That transport response does not prove that an accepted request is a valid PDF, image, source bundle, or model input. Application validation still needs its own result and cleanup contract.

Who this is for

Use this workflow when an agent changes multipart handlers, upload middleware, object-storage adapters, document ingestion, avatar or attachment routes, archive intake, or a worker that reads files created by a browser or API client. It applies whether the upload is temporary, private, or later made public. It is especially important when the worker can execute tools, parse complex formats, or access a repository.

This guide addresses the upload boundary. Pair it with the site’s archive-extraction path-traversal review when an accepted upload is an archive, and with the temporary-file race review when the patch creates or reopens staging paths.

Key takeaways

  • Authenticate the upload and authorize the specific operation before allocating an expensive processing job.
  • Enforce a maximum request size at the proxy and application, then enforce per-file, total-request, entry-count, and post-decompression budgets where relevant.
  • Use an allowlist for accepted media or document classes. Treat client-provided names and media types as hints, never proof.
  • Inspect a bounded prefix or complete byte stream with a format-aware detector; do not make extension checks the security decision.
  • Generate the stored name and keep the object outside the webroot or behind an authorized retrieval handler.
  • Stage privately, validate completely, and promote once. Do not let a parser, watcher, or agent tool observe partial output.
  • Make rejection side-effect free and record stable reason codes without logging contents, credentials, or full filenames.
  • Test malformed, oversized, misleading, duplicate, and parser-hostile fixtures through the real proxy and worker path.

Sources checked

  • OWASP File Upload Cheat Sheet was fetched on October 29, 2026 UTC. It supports the file-type and signature allowlist, generated-name, size, authorization, storage, and content-inspection guidance.
  • MDN 413 Content Too Large was fetched on October 29, 2026 UTC. It defines the transport response for requests over a server limit and the optional Retry-After behavior.
  • Python tempfile documentation was fetched on October 29, 2026 UTC. It documents secure random temporary names, TemporaryDirectory, mkstemp(), mkdtemp(), and cleanup responsibilities.
  • RFC 9110 HTTP Semantics was fetched on October 29, 2026 UTC. It is the current IETF reference for HTTP request and response semantics, including the 413 status.

These sources describe protocol and library behavior. The admission policy, budgets, and test matrix below are recommendations for a reviewed application; they do not claim that any particular repository or deployment has passed them.

Contract details to verify

Write the upload contract before reviewing the agent’s implementation. Keep one row per route or worker entry point.

Contract fieldDecision to recordEvidence to retain
CallerWhich identity may upload and for whose objectAuth middleware and authorization assertion
Accepted classExact formats and versions the feature supportsAllowlist, parser version, and valid fixture
Request budgetMax request bytes, file bytes, count, and processing timeProxy and app settings plus boundary tests
FilenameWhether the user name is displayed or discardedGenerated storage key and display-name encoding rule
Content checkSignature, parser, scan, or sandbox neededDetector result and version, without raw contents
StorageStaging root, owner, permissions, and public-read policyStorage class and access test
PromotionWhat makes an object ready for a worker or readerValidation record tied to object digest
FailureStatus, reason code, cleanup, and retry behaviorIntegration result and sanitized event

Set budgets at every layer

Put a hard request limit at the edge and a matching or lower limit in the application. A proxy limit catches oversized bodies before the upload handler spends memory or disk, while the application limit protects routes reached through another path. Return a deliberate 413 or documented application error; do not let a parser discover the limit after allocating an unbounded buffer. A Retry-After header is appropriate only when the limit is temporary and the client can make a meaningful later request. Do not tell a client to retry an intrinsically invalid file.

Set separate limits for one file, the complete multipart request, the number of files, decompressed bytes, parser recursion or page count, and wall-clock processing. A small compressed archive can expand dramatically, and a document can cause expensive work after upload. Count bytes while streaming into staging and abort before the next write would exceed the budget. Record a size bucket rather than the exact sensitive content.

Validate meaning, not labels

Use an allowlist such as application/pdf with a known parser version or a small set of image formats. The client Content-Type and extension can be useful early hints, but OWASP says the header is user supplied and can be spoofed. Inspect a bounded signature and parse the format without executing embedded content. Reject ambiguous or truncated files, and define what to do with polyglot content, macros, active markup, external references, and embedded archives.

Do not let a filename select a path. Generate a random identifier or storage key, retain a separately escaped display name if the UI needs it, and reject control characters or an overlong display value. Store bytes outside the webroot, or expose them through a handler that authorizes an opaque identifier. A URL that happens to be hard to guess is not an authorization policy.

Stage before an agent or parser sees the file

Create a private task directory with a secure temporary primitive. Python’s TemporaryDirectory and mkstemp() documentation describe secure random creation; the lower-level calls require the application to perform cleanup. Keep the returned handle or controlled path under the same task owner, and never reopen a user-selected path. The temporary-file race review covers the create-and-open boundary in detail.

Write only after authorization and budget checks. Validate the staged bytes, then persist a record containing an opaque upload ID, digest, class, byte count, policy revision, and validation result. Give the downstream agent or parser that record, not the original filename or an arbitrary path. Promotion should be one controlled visibility change after all checks. If a scanner or parser is required, run it with least privilege and no deployment credentials.

Build a harmless adversarial test matrix

Run these cases in a disposable environment with an outside sentinel and an empty staging root. Exercise both a direct application request and the production proxy or gateway.

FixtureRequired observation
Valid small file in each approved classStored under an application-generated key; expected parser result; no public exposure before promotion
Missing or unauthorized callerRejected before processing; no object or job is created
Request just below and just above the edge limitBelow reaches application policy; above gets the documented 413 path
File just above the per-file limitStream stops before the budget is exceeded; staging is removed or quarantined
Misleading extension and Content-TypeDetector and parser decide; spoofed labels do not admit the file
Path-like, control-character, and overlong filenameDisplay name is rejected or safely encoded; storage path is unaffected
Duplicate upload or retryIdempotency policy gives one object or a deliberate new version; no duplicate job
Truncated, malformed, or parser-hostile bytesControlled validation failure; no agent or parser side effect
Archive or compressed inputExpansion and entry budgets apply; use the site’s archive traversal tests
Scanner timeout or failureObject remains non-live; cleanup and retry state are explicit
Worker crash after stagingRestart reconciliation distinguishes active, failed, and unknown uploads before deletion

The positive control should prove that the harness can upload and process a benign fixture. For each negative case, assert the sentinel is unchanged, no executable or public object exists, no downstream job was enqueued, and the sanitized event contains only the policy revision, fixture ID, size bucket, reason code, and cleanup result.

Happy-path operator workflow

  1. Inventory every upload route, proxy limit, middleware, object-store write, parser, scanner, and worker handoff changed by the agent.
  2. Record the caller authorization rule and the exact accepted formats before reading helper code.
  3. Configure edge and application byte limits, then add file, count, expansion, and processing budgets.
  4. Stream into a private staging object with an application-generated name. Keep the object outside the webroot and the task identity outside user input.
  5. Check the actual bytes and parse with a bounded, least-privilege worker. Reject unsupported active content or external references according to the contract.
  6. Persist the upload ID, digest, type decision, size bucket, policy revision, and validation outcome.
  7. Promote only the exact validated object, then enqueue downstream work with the opaque upload ID.
  8. Run the positive fixture and the adversarial matrix through the real stack.
  9. Review logs for raw names, headers, bodies, and credentials before marking the patch ready.

A useful success record includes a run ID, upload ID, policy revision, detector and parser versions, size bucket, validation result, promotion result, queue result, and cleanup result. It should not include the raw file, full request body, bearer token, cookie, or untrusted filename.

Error-path operator workflow

Reject before writing when authorization, request size, or format policy fails early. Return a stable code such as upload_not_allowed, upload_too_large, upload_type_denied, or upload_validation_failed. Use 413 for an oversized request when that is the route’s HTTP contract; a 4xx validation response is clearer for a well-sized but unsupported file. Never “repair” a rejected upload by changing its extension, stripping bytes, or retrying it through a more permissive parser.

If failure occurs after bytes have been staged, close the handle, mark the object non-live, and remove or quarantine it through the task owner. Preserve the primary validation failure separately from cleanup failure. An unknown worker state must not trigger blind deletion or reprocessing: reconcile the upload ID and any external scan record first. If a scanner is unavailable, fail closed or use a documented pending state that cannot reach the agent.

When a client retries after 413, make the response explain the limit without echoing the filename or body. Use Retry-After only for a temporary capacity policy, and ensure the retry cannot create two jobs. If a parser or scanner reports a malformed file, keep the object out of public retrieval and do not send its bytes into an agent prompt or diagnostic log.

Failure modes

The extension is allowlisted and the content is trusted. Extensions and client media types are hints. Check the bytes and parse within a bounded worker.

Only the proxy has a limit. Alternate routes, internal calls, and configuration drift can bypass it. Enforce the application limit too and test both paths.

The original filename becomes the storage path. Path syntax, collisions, and special names can overwrite or expose data. Generate the storage key and keep display text separate.

The upload is written directly into a public directory. A file can be retrieved or consumed before validation. Stage outside the webroot and promote once.

The scanner is treated as a Boolean magic box. Record version, timeout, and result. A scanner failure is not a clean result.

A rejected multipart part is skipped. Partial success can leave an attacker-shaped object or an incomplete package. Fail the complete operation unless partial import is an explicit, tested feature.

A retry creates a second job. Bind retries to an upload ID and idempotency key, then reconcile an unknown outcome before starting work again.

Logs capture the whole request. Names, headers, prompts, and file bytes can contain secrets or hostile control characters. Keep a small sanitized event record.

FAQ

Is a matching Content-Type enough?

No. The client supplies it and can spoof it. Use it as an early hint, then inspect signatures and parse the accepted formats with resource limits.

Should every upload be scanned for malware?

Use an antivirus, sandbox, or content-disarm step when the file class and threat model justify it. Make its required, optional, pending, and failure states explicit. A scanner does not replace authorization, size, type, storage, or cleanup controls.

Can accepted files live in the webroot?

Avoid that for untrusted content. Store outside the webroot or serve through an authorization handler for an opaque ID, with the response policy and active-content behavior reviewed separately.

What does 413 prove?

Only that a layer rejected request content as too large under its configured limit. It does not prove that an accepted file is safe or that every application route uses the same limit.

How should an agent receive an upload?

Give it an opaque upload ID and a least-privilege read capability after validation. Do not place arbitrary user paths, raw multipart bodies, or unreviewed filenames in the agent’s tool arguments.

Reader next step

Choose one upload route changed by a coding agent. Write its caller, format, size, filename, storage, scanner, promotion, and cleanup contract before changing code. Add a benign positive fixture plus oversized, misleading-label, path-like-name, malformed, duplicate, scanner-failure, and worker-restart cases. Run them through the actual proxy and worker path, then retain only the policy revision, fixture ID, size bucket, result, and cleanup evidence. Keep the patch blocked if any request-controlled value can choose the storage path, if an accepted object becomes visible before validation, or if an unknown failure can be retried without reconciliation.