Secure AI File Uploads: A Fail-Closed Admission Boundary
A secure AI file upload requires more than checking that a filename says .pdf. Internal extraction and RAG systems often pass uploaded bytes straight to OCR, parsers, models, and indexes. A renamed executable, malformed document, decompression bomb, malware payload, or hidden instruction can then exhaust a worker, exploit a library, or become trusted model context. Put a file admission controller in front of those components. It should quarantine every upload, verify its real type, enforce resource budgets, scan it, parse it in isolation, and release only a versioned sanitized artifact. This guide defines the controller and the tests that prove it fails closed.
Why AI document ingestion needs its own boundary
A normal upload endpoint protects the web application. An AI document workflow adds several downstream interpreters. A PDF library decodes object streams. OCR software processes images. Archive handlers expand nested files. A model interprets extracted text as language, and a RAG pipeline may preserve that text for later users. Each step gives hostile input another way to consume resources or influence behavior.
The OWASP File Upload Cheat Sheet recommends allowlisted extensions, independent media-type and signature checks, application-generated filenames, size limits, safe storage, authorized uploaders, malware or sandbox scanning, and content disarm and reconstruction where appropriate. AI ingestion needs more because downstream components interpret the file again.
AI systems also have a semantic attack surface. The OWASP prompt injection guidance describes remote or indirect injection through external content. It also describes separating a component that reads untrusted content from the privileged component that can use tools. Microsoft uses the term document attack for hostile instructions embedded in third-party documents that try to alter model behavior or trigger unintended actions in its Prompt Shields documentation.
Treat upload acceptance, safe parsing, and semantic trust as separate decisions. A clean malware result does not make extracted instructions trustworthy. A valid PDF signature does not prove that parsing it is cheap. Accurate OCR does not authorize the text to enter a privileged agent's context.
Define states instead of one accepted flag
Do not represent an upload with accepted: true. Use an explicit state machine so workers, operators, and audits can distinguish what has been checked.
A useful lifecycle is:
received: the service has recorded metadata but has not trusted the body.quarantined: bytes are in private object storage and cannot be downloaded through a public path.type_verified: extension, declared media type, detected signature, and business policy agree.malware_cleared: the configured scanner completed with an allowed result.parsed_isolated: a restricted worker produced an artifact inside its limits.content_screened: extracted text and embedded content passed the workflow's semantic policy.released: a specific sanitized artifact version may enter extraction or retrieval.rejected: the controller recorded a stable rejection reason and retained or deleted evidence under policy.
Scanning services can be unavailable or return an indeterminate result. Keep the object quarantined and retry under a bounded policy or route it to an operator. A timeout must never be recorded as a clean result.
Store enough evidence to make each transition reproducible:
{
"upload_id": "upl_1042",
"tenant_id": "tenant_19",
"original_name": "supplier-pack.pdf",
"storage_name": "4d/7a/upl_1042.bin",
"declared_media_type": "application/pdf",
"detected_type": "pdf",
"content_digest": "sha256:stored-server-side",
"compressed_bytes": 1842390,
"page_limit": 300,
"expanded_byte_limit": 50000000,
"parser_profile": "pdf-restricted-v4",
"scanner_version": "managed-scan-policy-3",
"status": "quarantined",
"sanitized_artifact_id": null,
"rejection_code": null
}
Keep the raw object immutable. If content disarm, image conversion, or normalization creates a new file, give that artifact its own digest and provenance. Replacing the original in place destroys the evidence needed to investigate a parser failure or explain what entered the index.
Build the secure AI file upload sequence
The controller should make cheap, deterministic decisions before expensive or risky processing.
Authenticate and set policy before reading the body
Resolve the tenant, user, workflow, and allowed document classes from authenticated server context. Do not let a form field choose a more permissive parser profile. Apply request-size limits at the reverse proxy and application edge so rejected bodies do not occupy memory or disk first.
Issue a server-generated upload ID and storage name. Preserve the original name only as display metadata after removing control characters and enforcing a length limit. Never use it as a filesystem path, public object key, or command argument.
Verify type through independent signals
Maintain a short allowlist based on the business task. If the workflow processes invoices, it may need PDF, JPEG, PNG, and a specific spreadsheet format. Supporting every format increases parser code and attack surface without improving the job.
Check the normalized extension, declared Content-Type, and detected file signature. None is sufficient alone. Clients control names and request headers, while signatures can identify a container without proving that every object inside it is safe. Reject mismatches rather than guessing which parser the user meant.
Validate container contents too. Office formats are archives. A valid outer ZIP signature can still contain unexpected executables, paths that escape an extraction directory, too many entries, or nested archives. Reject absolute paths, parent-directory traversal, symbolic links, encrypted members that policy cannot inspect, and formats outside the inner allowlist.
Enforce expansion and parser budgets
Limit both stored bytes and work created after upload. Set caps for page count, image dimensions, archive members, nesting depth, total expanded bytes, compression ratio, parser wall time, memory, CPU, processes, and output size. A 2 MB archive that expands to 20 GB is a different input from a 2 MB PDF even though the request limits look identical.
A current Unstructured parser security fix bounds quadratic array-stream decoding in a PDF complexity check. The pull request is repository evidence about one implementation, not proof that every PDF parser has the same defect. It shows why admission must enforce resource limits even when a library already tries to classify complex files.
Abort the worker when any budget is reached. Record the limit that fired, parser version, elapsed time, and artifact digest. Do not retry the same bytes with a larger unrestricted profile automatically. A separate approved policy may allow an operator to route a legitimate large document to a purpose-built path.
Quarantine, scan, and isolate parsing
Write uploads to private storage with no execute permission and no public route. The upload service should not parse them. It should enqueue an immutable upload ID for a scanner and restricted parser workers.
Malware scanning belongs before release. Amazon GuardDuty Malware Protection for S3 is one maintained example of scanning newly uploaded objects. Represent each result as clean, malicious, unsupported, failed, or unavailable whether the scanner is managed or self-hosted. Only the clean state can advance automatically.
Run parsers in an isolated worker with an unprivileged identity, a read-only base image, no inherited application secrets, a task-specific input mount, a separate output location, and denied outbound network access unless a parser has a documented need. Destroy the worker after the job. A parser should not share a process with the API, indexer, or tool-using agent.
Patch parser, OCR, image, and archive libraries on a tracked schedule. Pin versions in the parser profile so a later incident can identify which code handled a file. Release new parser images through the same representative fixture and hostile-input suite used for application changes.
Separate safe parsing from semantic release
A successful extraction proves only that the parser returned content within its limits. The semantic policy still has to decide whether that content may influence a model.
Classify the source and destination. An uploaded contract may be allowed as evidence for a summarizer but forbidden from adding instructions to an autonomous agent. A shared-drive file may enter a search index only after uploader authorization and sensitivity metadata are attached. Extracted links, embedded files, macros, comments, hidden text, and OCR layers may need separate handling.
Screen text for document attacks as one defense layer, then keep untrusted document content clearly separated from system instructions. Do not let retrieved text choose tools, permissions, or destinations. If a privileged action is needed, trusted application policy must derive it from the authenticated user's request and validated business state.
Promotion should reference the sanitized artifact digest, parser profile, scanner result, source identity, tenant, and semantic policy version. Indexers and extraction workers must reject raw upload IDs. This one rule prevents a developer from bypassing admission later by wiring the convenient original object directly to a model.
Handle failures without releasing unchecked files
Assign every failure a terminal or recoverable disposition.
Reject type mismatches, forbidden inner files, path traversal, password-protected content that cannot be inspected, known malware, and exceeded hard limits. Return a user-facing reason that helps legitimate users without exposing scanner signatures or internal paths.
Keep scanner outages, transient storage errors, and worker capacity failures quarantined. Retry with capped attempts and an expiry. After expiry, notify the workflow owner and delete or retain the object according to evidence policy. Do not let operational pressure turn scan_unavailable into clean.
Parser crashes are data, not permission to try an unrestricted fallback. Capture the parser profile, digest, and failure class. A fallback parser may run only if it has its own isolation and limits and the format policy explicitly allows it.
If semantic screening flags a document attack, decide by workflow. A read-only analyst may review the extracted text in a safe interface. A tool-using agent should not receive the flagged content. Preserve the decision and artifact version so a later rescan cannot silently change what an earlier run used.
Verify the whole admission transaction
Test through the actual upload API, object storage, queue, scanner adapter, parser worker, artifact store, and release consumer. Unit tests around filename validation miss most of the boundary.
Your fixture set should include:
- A valid example for every allowed format
- A forbidden extension with a valid-looking media type
- A PDF name containing non-PDF bytes
- A valid container with a forbidden inner file
- Parent-directory paths and symbolic links in an archive
- Excessive member count, nesting, expanded bytes, pages, and image dimensions
- A parser input that times out, crashes, or exceeds memory
- Scanner results for malicious, unsupported, failed, unavailable, and clean states
- Hidden or visible instructions that ask a model to ignore policy or invoke a tool
- A raw upload ID sent directly to the indexer or extraction consumer
- Two tenants attempting to read each other's quarantine or released artifacts
- A parser upgrade replayed against the complete fixture set
Assert negative outcomes. A rejected object must never produce a released artifact. A quarantined object must not be fetchable through a public URL. An expired scan must not advance. A parser timeout must terminate its process. A released artifact must carry the expected source digest and policy versions. A document-attack fixture must not reach a privileged tool path.
Monitor counts and age by state, rejection codes, scanner latency, parser terminations, resource-limit hits, artifact promotions, and attempts to consume raw uploads. Alert on any release without all required evidence. That invariant is more useful than a generic upload-error rate.
Put one hostile file through staging
Choose one staging workflow that accepts documents. Add a renamed non-document, an archive that exceeds its expanded-byte budget, a parser-timeout fixture, a scanner-unavailable response, and a document containing an instruction to call a privileged tool. Prove that each file stays quarantined or is rejected, and that no raw upload reaches the model or index.
A secure AI file upload is complete only when the released artifact can be traced to an immutable upload, exact scanner and parser versions, enforced resource limits, and an explicit semantic policy. If your consumer can open the raw object without that record, close that bypass before accepting another document source.
References
- OWASP File Upload Cheat Sheet supports the upload allowlist, independent type checks, safe naming and storage, size limits, scanning, and content-disarm controls.
- OWASP LLM Prompt Injection Prevention Cheat Sheet supports the indirect-injection threat and separation of untrusted content handling from privileged tool execution.
- Microsoft Prompt Shields supports the document-attack model for hostile instructions in third-party content.
- Amazon GuardDuty Malware Protection for S3 provides a maintained implementation surface for scanning newly uploaded objects.
- Unstructured pull request 4437 provides current repository evidence for bounding resource use in a PDF parser complexity check.