Review a file-processing job as a chain of boundaries: who may submit the file, which exact object the worker reads, what parsers can do with its bytes, what resources the job can consume, where output goes, and how every attempt ends. Moving work to a queue does not make the input safe. A worker may have broader permissions, longer time to run and less visible failures than the request that created the job. The answer changes with the file format and business purpose, but every job needs a bounded admission decision and a controlled execution environment.
OWASP's file-upload guidance covers type, name, size, storage, permissions and malicious-content risks; it warns that no single validation step is sufficient. CWE-400 describes uncontrolled resource consumption, a risk that matters especially for asynchronous processors. A review should make these controls concrete for the actual worker, not stop at “the upload endpoint checks extensions.”
An illustrative invoice-preview job
All names, object IDs and numbers are invented. No file was executed. Birch Billing lets an account user upload a PDF invoice for preview. The API stores the upload and enqueues {account, object_key}. A worker downloads the object, extracts text and thumbnails, and writes a preview that other account members can open. At revision BB-27, the API limits the compressed upload to 20 MB. The worker has a generous runtime limit, an outbound network route, broad access to the object bucket and a parser library that has not been reviewed for its current configuration.
The 20 MB limit is useful but incomplete. A small encoded file can demand much more CPU, memory or disk after parsing. The worker must also be sure that the object named by the queue message still belongs to the requesting account, and that its result cannot overwrite another account's preview. The PDF is data, not a trusted instruction source. It may contain links, scripts, malformed structures or content that the parser library handles in surprising ways; the review must establish whether the selected conversion path executes anything, follows external references or launches helpers.
Use this checklist as a handoff artifact. Fill the last column with evidence, not “yes” based on intent:
| Boundary | Review decision | Evidence to request for Birch |
|---|---|---|
| Admission | Which actor may submit, for which account and object? | API authorization path, object ownership check, queue creation record |
| Identity | Which immutable object version is processed? | Version or content digest bound to job and tenant; retry semantics |
| Type | Which formats are intentionally supported? | Parser selection, signature/content checks, rejection status |
| Name and path | Can a supplied filename influence a filesystem path or output key? | Generated name rule and confined storage path |
| Resources | What are the limits on bytes, pages, extracted text, CPU, memory, disk and elapsed time? | Configured limits and observed termination behavior |
| Parser | Which library and options process the bytes? | Version, selected commands and handling of external references |
| Privilege | Which files, secrets, network destinations and output objects can the worker access? | Deployed identity, mounts and egress policy |
| Output | Who can see previews, logs and errors? | Account binding, rendering/escaping path, source-free error format |
| Retry and cleanup | Can duplicate or cancelled jobs still process or publish? | Job fence, attempt inventory and cleanup receipt |
The object-version row is easy to miss. If the queue carries only a mutable key, a later upload or overwrite may change the bytes while the same job ID remains. The worker should bind the intended content and account at admission, then recheck them when it starts. An object-storage permission that lets one worker read every tenant's files is not made safe by trusting an account ID copied from the queue message.
Inspect the file path from input to output
Start with a sequence diagram or a one-page trace: request → stored object → queue → worker → parser → temporary files → preview → notification. Name the code owner at each arrow. Check whether the upload API and worker parse the same bytes, and whether any intermediate service transforms the file. Record unsupported formats and what happens to them. A failed parser should not silently produce a “safe preview” badge.
For the PDF example, inspect file-type checks, parser configuration and generated temporary names. A content-type header and .pdf extension are claims from the submitter; OWASP advises checking actual content and using application-generated filenames. If the parser unpacks embedded objects or invokes an external converter, include the converter in scope. Avoid general-purpose deserialization of attacker-supplied objects; OWASP's deserialization guidance explains why unsafe object reconstruction can have effects beyond reading data.
Check all output paths, not only the parser's input. A generated thumbnail or extracted text can contain attacker-controlled content. The preview service must bind the output to the right account and encode it for its display context. Error messages and logs should describe a failure without copying private file bytes or embedded credentials. If a parser follows links or writes files using names supplied inside a document or archive, review traversal and overwrite risk; CWE-22 defines path traversal as reaching outside the intended directory through pathname input.
Put a bound outside the parser
The worker should have limits it cannot raise merely by deciding to keep processing. Review memory, CPU, wall time, temporary storage, output size and recursion or page count as applicable. The queue must also bound attempts and concurrency so a flood of small jobs cannot consume all worker capacity. A per-file timeout in application code is helpful, but an external supervisor or task boundary is needed when a parser hangs or crashes before that code can clean up.
At Birch, the platform owner could choose a per-attempt isolated worker with no source-code execution, no dependency installation, no unnecessary secrets and narrowly scoped network egress. The preview parser needs access to the one admitted object and its own output namespace, not general bucket administration. If the job must use a shared bucket credential, require an independent account/object ownership check and record that the credential's scope remains a residual risk. Do not claim that a sandbox removes all parser or tenant risks; inspect actual mounts, policy and escape surface.
Cancellation and failure require the same attention as success. A cancellation flag in a database does not prove a running attempt stopped. Inventory attempts, prevent new work, observe termination, and keep cleanup failures visible. Retrying must not publish an old preview after a new version was admitted. Temporary-file removal does not prove physical erasure; classify storage retention and backup behavior separately.
Follow two attempts through one cancellation
Birch's revised job record binds the admitted object and output generation:
job J7: account=Cedar, object=O17/version3, generation=8
attempt A1: private output prefix J7/A1, lease generation 8
attempt A2: private output prefix J7/A2, lease generation 9
publication: one conditional pointer to an accepted attempt's manifest
These records are design pseudocode. A digest can add content identity evidence, but it does not authorize the object; the tenant and admitted-object binding still matter. An attempt reads the specified immutable version, not whatever later occupies O17's mutable key.
Now A1 stalls and its lease expires. The scheduler atomically advances J7's authoritative current generation from 8 to 9, then admits A2 under that generation. A1 then wakes and finishes a thumbnail. If publication only checks job_id=J7, the stale attempt can win. The publisher must atomically require the current accepted generation and non-cancelled job state when updating the visible manifest. A separate check before upload leaves another race.
A1 publishes with generation 8 → denied; private files retained for cleanup
A2 publishes with generation 9 → eligible if job still active and authorized
cancellation atomically sets cancelled and current generation 10
→ later generation-9 publication denied
Publication that committed before cancellation is a separate state: the product's cancellation/delivery policy must say whether existing previews remain visible. For a user-dependent job, stale permission review addresses ordering against grant removal. The worker generation prevents stale attempts; it does not replace current authorization.
Termination, retry and cleanup are different outcomes
Birch chooses illustrative per-attempt limits of 60 seconds elapsed work and 256 MiB temporary output, with at most two attempts. These are not measured safe settings for a PDF parser. The selected values need realistic workload measurements and an external enforcement mechanism. A worker timing out its own function does not prove its converter subprocess stopped.
The supervisor records termination, and the job controller separately records cleanup:
| Event | Job decision | Visible result |
|---|---|---|
| Unsupported type | Reject, no retry | No preview |
| Parser deterministically exceeds a resource policy | Limited/failed; no automatic identical retry | No preview |
| Transient storage read failure before parsing | One bounded retry if still authorized | No partial preview |
| Attempt lease expires | Fence old generation; reconcile process state before consuming more capacity | Old attempt cannot publish |
| Cancellation | Deny new work/publication; stop all attempts | Delivery follows explicit cancellation policy |
| Process stopped, temporary deletion fails | Cleanup pending with restricted access | Failure does not publish files |
Two attempts can briefly overlap if one is merely unresponsive. The scheduler must account for both until termination is established; changing a database state to cancelled does not release real CPU, memory or disk. The retry budget and fleet concurrency bound are therefore related to observed process state, not just queue message count.
Leave an inspectable rollout decision
The source-only review can approve the design direction while leaving deployment qualification outstanding: immutable input, narrow identity, parser configuration, externally enforced limits, generation-bound publication and visible cleanup state. It cannot call a sandbox safe because the word appears in infrastructure code.
For future authorized validation, a benign accepted PDF establishes the positive path. A controlled timeout, duplicate delivery, old-attempt completion and cancellation near publication establish specific failure outcomes. Observe supervisor termination and the publication pointer, not only HTTP status or queue acknowledgement. Use synthetic files and no repository-code execution. Nothing in this article was run.
Review admission capacity as well as one worker's limits
Birch's two-attempt limit bounds one job only if the scheduler also accounts for all active attempts. An unresponsive A1 still consumes resources until its termination is established. The controller should not free that capacity merely because it advanced the lease and started A2. The review needs the attempt inventory and supervisor state that support the fleet's actual admission decision.
The same principle applies across customers. A per-file byte limit does not explain how many simultaneous jobs one account can admit or how much queued work the service retains. Define the intended account and global concurrency rules, then inspect the component that enforces them. These are workload-management choices whose values need real operational evidence, not universal settings supplied by this article.
For the synthetic review, the next artifact is a trace connecting job admission, attempt creation, termination, and publication. It should show the stale generation denied while its private files remain restricted for cleanup. This ties resource use to durable job state without confusing cancellation, stopped processing, unpublished output, and completed deletion.
The platform owner now has a small set of decisive artifacts to supply: selected worker image/parser configuration, effective identity/mount/egress restrictions, supervisor limits, and one job-attempt/publication trace. That packet is much more useful than a broad checklist marked “isolated.” For repository readers use the symlink review; for leaked output use the scanner-output response.