Blog · · 12 min read

AI agent file upload security from quarantine through deletion

AI agent file upload security from quarantine through deletion

AI agent file upload security breaks down when an attachment goes directly from an HTTP request to a parser, vector store, or model. The file might exploit a parser, consume all available storage, leak across tenants, inject instructions into model context, or remain in chunks and embeddings after the original has been deleted. MIME validation alone cannot deal with all of those failures.

A safe design gives the file a product-owned lifecycle. It begins before parsing and ends when the product has deliberately retained, quarantined, or deleted the original and every derivative. The implementation below connects upload controls, isolated extraction, current tenant authorization, provenance, model trust, tool policy, recovery, and deletion checks for a multi-user product.

A file crosses two security boundaries

An upload is both a binary artifact and a message to the model.

The binary can contain malformed parser input, active content, spoofed types, dangerous names, or an archive that expands until it exhausts storage. The OWASP File Upload Cheat Sheet recommends several independent controls. Allow only necessary extensions, distrust the content type supplied by the client, check signatures, generate storage names, cap sizes, isolate storage, scan or sandbox files, and restrict who may upload or read them.

The second boundary appears after extraction. Text, metadata, images, comments, hidden layers, and retrieved chunks can tell the model to ignore product policy or misuse a tool. The OWASP LLM Prompt Injection Prevention Cheat Sheet lists documents and email attachments as indirect injection sources. It also covers instructions hidden in non-textual inputs. A parser can handle the bytes correctly while producing content that remains hostile to the agent.

A scan therefore answers one narrow question: may these bytes proceed to bounded parsing? It does not give the extracted content permission to issue commands or increase the agent's authority. Keep that distinction in the data model and in the runtime policy.

Give the original and its derivatives stable identities

Assign an internal identity as soon as admission starts. A display filename, temporary object URL, or provider file ID is not stable enough to identify the product record.

file_candidate:
  file_id: "fil_01JEXAMPLE"
  tenant_id: "tenant_2048"
  owner_subject: "user_913"
  original_name: "quarterly-report.pdf"
  storage_key: "tenant_2048/fil_01JEXAMPLE/original"
  sha256: "<content-digest>"
  observed_media_type: "application/pdf"
  declared_media_type: "application/pdf"
  bytes: 1842201
  policy_version: "file-ingestion-12"
  state: "quarantined"
  scan_results: []
  parser:
    name: "pdf-extractor"
    version: "4.3.2"
  derivatives: []
  retention_class: "customer-upload-30d"
  legal_hold: false
  created_at: "2026-08-13T09:00:00Z"

Each derivative needs its own record and a link to the exact source bytes:

parsed_derivative:
  derivative_id: "der_01JEXAMPLE"
  source_file_id: "fil_01JEXAMPLE"
  tenant_id: "tenant_2048"
  kind: "text-chunk"
  source_locator: "pages:4-5"
  content_hash: "<normalized-content-digest>"
  parser_name: "pdf-extractor"
  parser_version: "4.3.2"
  trust: "untrusted-external-content"
  index_ids: ["vec_7921"]
  provider_file_ids: []
  retention_class: "customer-upload-30d"

These records should answer which user introduced the file into which tenant, which bytes were inspected, and which parser and policy produced a derivative. They should also show where each copy can be retrieved or transferred. Most importantly, they must let an operator quarantine or delete every copy when access or policy changes.

Do not leave this relationship implicit. During an incident, filenames and free-form metadata are poor substitutes for a graph that spans object storage, extracted text, rendered images, vector indexes, model providers, caches, traces, and generated artifacts.

Authorize before accepting the body

Check the uploader's authority before reading an expensive request body or creating durable storage. Resolve the authenticated subject, tenant, target workspace, intended use, and available quota. Permission to attach a file to one support case does not imply permission to add that file to a tenant-wide knowledge base.

The admission policy should define the file types needed for that purpose, along with compressed and uncompressed size limits. Add format-specific limits for pages, sheets, slides, rows, images, archive entries, and nesting depth. Processing time and memory need separate budgets. Tenant storage quotas, allowed ingestion destinations, retention class, and residency rules belong in the same policy decision.

Compare the normalized extension, declared content type, detected signature, and the parser's own identification. The client controls the Content-Type header, and OWASP treats signature validation as one layer rather than conclusive proof. A mismatch should produce a typed rejection or manual review. It should never be a warning that later code ignores.

Generate the storage key yourself. Keep the supplied filename only for display after removing control characters, path separators, reserved dot names, and excessive length. A Dify filename-validation report describes control characters and . or .. passing into stored metadata, then reaching ZIP names, headers, or logs. That issue is one implementation report, not evidence that every platform shares the defect. It still shows why display metadata and storage identity must remain separate.

Stream the original bytes into private quarantine storage, calculate the digest during the write, and save the admission policy version. The object should have no public URL and must not be available to retrieval yet.

Scan and parse outside the application runtime

Run malware inspection and parsing in a disposable worker. The web process and the agent runtime are the wrong isolation boundaries. Give the worker no production secrets, no tenant-wide filesystem, and no outbound access by default. Mount the quarantined object read-only and place output in a fresh, scoped location.

Apply budgets before extraction finishes. A small archive can expand into a large tree, and a malformed document can consume far more CPU or memory than its size suggests. Enforce compressed size, expanded size, entry count, nesting depth, runtime, memory, and output limits while work is in progress.

function process_file(file_id, expected_policy_version):
    candidate = load_candidate(file_id)
    assert candidate.state == "quarantined"
    assert candidate.policy_version == expected_policy_version
    assert current_tenant_policy_allows(candidate)

    bytes = open_quarantine_object_read_only(candidate.storage_key)
    assert sha256(bytes) == candidate.sha256

    type_result = identify_type(bytes)
    enforce_type_and_complexity_limits(candidate, type_result)

    scan_result = malware_scan(bytes)
    if scan_result.verdict != "allow":
        transition(file_id, "rejected", scan_result.reason)
        return

    sandbox = create_parser_sandbox(
        network="deny",
        secrets="none",
        filesystem="ephemeral",
        cpu_limit="2 cores",
        memory_limit="1 GiB",
        timeout="60 seconds"
    )

    output = sandbox.parse(bytes, parser_for(type_result))
    validate_output_limits(output)
    derivatives = persist_with_provenance(candidate, output)
    transition(file_id, "parsed", derivative_ids(derivatives))

Block remote images, fonts, templates, linked spreadsheets, and URLs unless a separate policy names an allowed destination. A remote reference changes parsing into an outbound network request. That request may expose internal addresses, signed URLs, or request metadata.

Keep the parser's exit status, stderr, resource use, policy decisions, and output manifest. Raw customer text and hostile markup do not belong in ordinary logs.

Record how every derived copy was made

Providers process formats differently. OpenAI's file-input documentation says PDF inputs can contribute extracted text and page images, while non-PDF documents contribute extracted text. Spreadsheets follow a format-specific augmentation flow. The same interface accepts file IDs, base64 data, and external URLs. A product that uses these paths must track the provider transfer and the copies it creates.

Every derivative record needs the original file_id, source digest, tenant, owner scope, parser name, parser version, and parser configuration. Add a source locator such as a page, slide, sheet, cell range, or archive member. Record the transformation, whether OCR, rendering, extraction, chunking, embedding, or summarization. Store the derivative hash, location, trust label, index references, cache references, provider IDs, generated artifacts, retention class, legal hold, and deletion state.

Use structured fields. Retrieval filters and deletion workers cannot safely enforce a free-form provenance note.

Content-addressed deduplication is possible, but physical deduplication must not merge authorization or retention across tenants. Maintain separate grants and lifecycle records even when tenants happen to upload identical bytes. Remove a shared blob only after every valid grant, hold, and retention rule has released it.

Keep extracted instructions below the tool-policy boundary

The agent may analyze extracted content, but that content cannot change system policy. Label it as untrusted in the context envelope and keep its source identity attached. Delimiters can make the distinction clearer to the model, but the system cannot rely on delimiters as an access control.

Tool authority should come from the authenticated user and current product policy:

  1. Retrieve only derivatives available to the current subject.
  2. Include source IDs, locators, and trust labels in model context.
  3. Have the model return a structured proposal instead of executing unrestricted instructions.
  4. Check the proposed tool and parameters against the user's request and current permissions.
  5. Require approval for consequential actions and for risky proposals derived from untrusted content.
  6. Record which derivatives informed the proposal and final decision.

OWASP recommends least-privilege tools, parameter checks, output monitoring, and human review for high-risk actions. Enforce these outside the model. A document that tells the agent to email every customer record has no authority to make that request valid.

A LangChain request for RAG security documentation asks for one guide that covers retrieved-document manipulation, data leakage, filtering, output validation, sandboxing, monitoring, and hardened examples. The request shows how scattered the current guidance is. Prompt templates cannot solve the storage, parser, authorization, and action problems around retrieval.

Recheck access when retrieving content

Authorization can change after indexing. Users leave teams, cases are reassigned, sharing is revoked, and retention periods expire. An upload-time decision is stale evidence at retrieval time.

Filter by current tenant and resource grants before semantic ranking. Retrieving globally and removing unauthorized chunks afterward exposes them to selection and possibly to the model. Index metadata needs stable product identifiers, and the retrieval service should read the authoritative product record again when access is mutable or sensitive.

Separate indexes may be required for regulatory or operational isolation. If a shared index is acceptable, apply tenant filters on the server and test that no client can omit or replace them. Cache keys should include the tenant, authority version, file version, parser version, and agent release whenever those values affect what may be reused.

A revocation should block the next retrieval. It must not wait for the original object to expire while indexed derivatives remain available.

Make quarantine and deletion graph operations

A file can become unsafe after release. Scanner signatures change, sharing is revoked, an incident adds a digest to quarantine, or retention expires. Use explicit states for those changes.

quarantined means the object is stored but unavailable for parsing or retrieval. scanning covers bounded inspection. rejected records a block reason and disposal schedule. A parsed file has derivatives but is not automatically released. available permits authorized retrieval, while restricted limits access to incident or compliance work. During deleting, deny new access while cleanup runs. deleted means required copies were removed and checked. held prevents ordinary deletion while a valid legal hold applies.

Deletion starts with file_id and walks the entire graph: original object, rendered pages, OCR output, extracted text, chunks, embeddings, summaries, provider files, caches, generated artifacts, checkpoints, and retained trace payloads. Use each system's deletion operation, then check absence through an authoritative read. Keep receipts and unresolved exceptions, but do not preserve the deleted content inside the receipt.

If provider retention or deletion behavior cannot meet the selected policy, do not transfer that retention class to the provider. A successful provider upload says nothing about whether the product can prove deletion later.

For an incident quarantine, deny retrieval first. Preserve evidence only under an authorized hold, then identify affected runs through provenance. Any credentials or actions influenced by the file may need revocation or investigation.

Return precise failure states

Callers and operators need to know where processing stopped. Use typed outcomes:

OutcomeMeaningProduct action
rejected_typeType or signature violates policyDelete after the rejection retention period
malware_detectedScanner blocked the artifactQuarantine, alert, and prevent parsing
complexity_limitExpansion, pages, rows, or nesting exceeded a budgetReject without retrying unchanged bytes
parser_timeoutExtraction exceeded a bounded runtimeRetry only with a safer parser or stricter policy
parser_crashWorker failed before a complete manifestDiscard partial derivatives and retain diagnostics
policy_changedTenant or platform policy changed mid-runRe-evaluate before release
derivative_write_failedThe manifest is incompleteKeep the file unavailable and reconcile writes
provider_transfer_failedExternal copy status is unknown or failedDo not claim availability; reconcile by provider ID
deletion_partialAt least one derivative remainsKeep access denied and continue reconciliation

A retry reuses the same file identity and digest. It can create another processing attempt, but it must not create a second logical upload or another billable event.

Release a file only when all required derivatives and index entries belong to one completed attempt. If a worker writes chunks and crashes before the state transition, a sweeper can find those uncommitted derivatives by attempt ID and either finish or remove them.

Test the lifecycle, including the awkward cases

The happy path says little about the strength of this boundary. Run these tests against the persisted system:

  1. Give non-PDF bytes a .pdf name and confirm signature policy blocks the upload.
  2. Use path separators, control characters, and bare dot names in the filename; confirm that the internal identity remains generated and display metadata stays safe.
  3. Submit a nested archive that exceeds expansion or entry limits, then verify that processing stops before resource exhaustion.
  4. Crash the parser with a malformed document and confirm that no partial output becomes retrievable.
  5. Add a remote image or template and confirm the sandbox cannot reach an undeclared host.
  6. Put hostile instructions in text, metadata, comments, images, and hidden layers; confirm they remain untrusted and cannot override tool policy.
  7. Change client-side tenant filters and confirm server authorization prevents unauthorized candidates from entering ranking.
  8. Revoke file access after indexing and confirm retrieval stops on the next request.
  9. Change the parser version and confirm new derivatives receive distinct provenance without rewriting historical evidence.
  10. Delete a file and verify absence from object storage, extracted-text storage, vector indexes, caches, provider files, and generated artifacts.
  11. Add a legal hold and confirm ordinary retention jobs leave the file intact.
  12. Quarantine a previously used digest and enumerate the tenants, runs, proposals, and actions that consumed it.

In a thread about multimodal prompt-injection defence, the author reports that false positives and semantically novel attacks are harder than simple encoding checks. This is a practitioner report, not a general benchmark. Detection can supply risk evidence, but isolation, authority checks, approval, and provenance still have to limit damage when detection misses.

Mistakes that break the boundary

Sending fresh uploads from the request handler straight to a model provider skips quarantine, parser budgets, provenance, and dependable deletion.

The client-provided Content-Type is only one input to type detection. It cannot approve a file by itself. A malware scan is just as narrow: it cannot decide whether extracted text is safe for the agent to follow.

User filenames should remain display metadata, never object keys. Parsers should not have default network access. Chunks must retain tenant and source identities so retrieval and deletion can enforce product state.

Documents cannot grant tools or permissions. Every proposed action still needs validation against the initiating principal. Deleting only the original object is also incomplete because extracted text, embeddings, provider copies, caches, checkpoints, and generated artifacts belong to the same lifecycle.

Map one real upload before shipping

Pick one file-enabled agent flow and draw every copy it creates. Include the original, parser workspace, rendered images, extracted text, chunks, embeddings, provider files, caches, traces, and generated artifacts. Put the same stable file_id, tenant scope, processing attempt, trust label, and retention state on every edge. Before production access, prove that the system can quarantine one digest and delete all its derivatives without looking them up by filename or relying on a manual search.

References

  • OWASP File Upload Cheat Sheet supports defense-in-depth upload controls, parser and archive threats, storage separation, authorization, scanning, and size limits.
  • OWASP LLM Prompt Injection Prevention Cheat Sheet supports the indirect and multimodal document-injection threat model plus least-privilege, validation, monitoring, and approval controls.
  • OpenAI file inputs supports the documented processing differences among PDFs, documents, spreadsheets, file IDs, base64 data, and external file URLs.
  • Dify issue #37809 provides a concrete implementation report about unsafe filename metadata reaching later ZIP, header, and logging paths.
  • LangChain issue #34780 provides practitioner evidence for a missing RAG-security guide that connects injection, leakage, filtering, sandboxing, monitoring, and hardened examples.
  • Ask HN about multimodal prompt-injection defence provides author-reported problem evidence about document attacks, semantic detection, non-English inputs, and false positives.

Be first in line.

Join the waitlist and we'll email you the moment it's ready. No sales call.

Try the demo →
or talk to us