AI Security

Secure Customer Document Summarization with Cloudflare Workers AI and R2

TL;DR: Encrypt documents before upload, store them in Cloudflare R2 with short‑lived signed URLs, invoke Workers AI in a sandboxed request, delete the temporary object and any logs immediately, and rotate the access token every few minutes. This keeps the raw file out of the AI model’s cache and limits exposure if a token is compromised.

What is the threat model for AI‑generated summaries of customer files?

When you feed a confidential PDF or Word document to a large language model (LLM), two primary risks arise:

For a small company, the impact is often regulatory (GDPR, HIPAA) or reputational. The goal is to make the raw document inaccessible outside a single, tightly‑controlled request.

How to encrypt documents before they reach the AI model?

Encryption must happen client‑side, before the file ever touches your cloud provider. A simple approach uses AES‑256‑GCM with a per‑file random key:

import os, json, base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def encrypt_file(path):
    key = AESGCM.generate_key(bit_length=256)
    aesgcm = AESGCM(key)
    with open(path, 'rb') as f:
        data = f.read()
    nonce = os.urandom(12)
    ct = aesgcm.encrypt(nonce, data, None)
    return {
        "key": base64.b64encode(key).decode(),
        "nonce": base64.b64encode(nonce).decode(),
        "ciphertext": base64.b64encode(ct).decode()
    }

Store the encrypted blob in R2 and keep the decryption key in a short‑lived secret manager (e.g., Cloudflare Workers KV with a TTL of 5 minutes). The key never lives longer than the summarization request.

How to use Cloudflare Workers AI with R2 for secure processing?

Cloudflare Workers AI lets you call an LLM from a serverless function. Combine it with R2 so the worker streams the encrypted file, decrypts in‑memory, and sends only the plaintext to the model. Example worker code:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)
  const objectKey = url.searchParams.get('file')
  // 1️⃣ Verify signed URL (short‑lived) – Cloudflare automatically checks expiry.
  const r2Object = await env.R2_BUCKET.get(objectKey)
  if (!r2Object) return new Response('Not found', {status: 404})

  // 2️⃣ Pull decryption metadata from KV (expires in 5 min).
  const meta = await env.KV.get(`meta:${objectKey}`, {type: 'json'})
  if (!meta) return new Response('Key expired', {status: 401})

  // 3️⃣ Decrypt in‑memory.
  const encrypted = await r2Object.arrayBuffer()
  const aes = new AESGCM(Buffer.from(meta.key, 'base64'))
  const plaintext = aes.decrypt(Buffer.from(meta.nonce, 'base64'), Buffer.from(encrypted))

  // 4️⃣ Call the LLM.
  const aiResponse = await fetch('https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/ai/run/@cf/meta/llama-2-7b-chat', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${env.AI_TOKEN}` },
    body: JSON.stringify({ prompt: `Summarize the following document:\n${new TextDecoder().decode(plaintext)}` })
  })
  const summary = await aiResponse.json()

  // 5️⃣ Clean up – delete R2 object and KV entry.
  await Promise.all([
    env.R2_BUCKET.delete(objectKey),
    env.KV.delete(`meta:${objectKey}`)
  ])

  return new Response(JSON.stringify({ summary: summary.result }), {status: 200, headers: {'Content-Type': 'application/json'}})
}

The worker never writes the plaintext to disk; it stays in the function’s memory, which is destroyed after the request completes.

How to enforce least‑privilege access and short‑lived tokens?

Follow these practices, which are echoed in the OWASP GenAI Security Project and NIST AI RMF:

  1. Scope the AI token: Create a token that can only invoke the specific model you need (e.g., @cf/meta/llama-2-7b-chat). Cloudflare lets you generate service tokens with model‑level scopes.
  2. Use signed URLs for R2 objects: Generate a URL that expires after 30 seconds. This prevents replay attacks.
  3. Rotate KV decryption keys every request: Store the key with a TTL of 5 minutes; the worker discards it after use.
  4. Apply a zero‑trust firewall: Restrict inbound traffic to the worker’s route to your internal IP range or VPN.

How to clean up temporary files and logs after summarization?

Even with in‑memory processing, Cloudflare may log request bodies for debugging. To avoid accidental retention:

Periodically review the audit log bucket and purge entries older than the retention window.

If you need a security review of your AI summarization pipeline, AISecAll can run a focused assessment and help you harden the workflow.

Need a practical AI security review?

AISecAll reviews prompts, tool permissions, document flows, and agent behavior so small teams can use AI without guessing where the risk sits.

Book a call Discuss a project