AI Security
Protecting Customer Documents in an AI Summarization Workflow with Cloudflare Workers AI
TL;DR: Store raw documents in Cloudflare R2 with bucket‑level encryption, use short‑lived signed URLs for the Workers AI request, enforce strict IAM policies, log every access, and delete the file immediately after the summary is generated.
Why a Dedicated Threat Model Matters
Even though Cloudflare Workers AI abstracts the model behind an HTTP endpoint, the data you send travels through the same edge network that serves your public site. A mis‑configured bucket or an overly permissive API token can expose confidential contracts, medical records, or financial statements to anyone who discovers the URL.
Step 1 – Store Documents in an Encrypted R2 Bucket
Create a dedicated R2 bucket for raw files. Enable bucket‑level encryption (AES‑256‑GCM) and disable public read access. Use a separate bucket for generated summaries so that the two data sets never share the same access policy.
Step 2 – Generate Short‑Lived Signed URLs
When a user uploads a document, generate a signed URL that expires after a few minutes (e.g., 300 seconds). The signed URL is the only value you pass to the Workers AI POST /v1/ai/summarize endpoint. This limits the window an attacker has to replay the request.
const url = await R2.getSignedUrl(bucket, key, { expiresIn: 300 })
await fetch('https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/run', {
method: 'POST',
headers: { 'Authorization': `Bearer ${AI_TOKEN}` },
body: JSON.stringify({
model: 'claude-3-5-sonnet',
prompt: `Summarize the document at ${url}`
})
})
Step 3 – Apply Least‑Privilege IAM Policies
Follow the principle of least privilege when creating API tokens:
- Separate tokens for
R2:Read(upload bucket) andR2:Write(summary bucket). - Do not grant
Account:EditorWorkers:Editto the token used by the summarization script. - Scope the token to the specific account and bucket IDs.
Reference the official token‑scoping guide: Cloudflare API Tokens.
Step 4 – Secure the Prompt Itself
Never embed the raw document content in the prompt. Always reference the signed URL and add a clear instruction to the model not to retain the content. Example prompt:
Summarize the PDF located at {signed_url}. Do not store any part of the document after generating the summary.
This mitigates prompt‑injection attempts that try to trick the model into echoing the file.
Step 5 – Log Every Access Event
Use Cloudflare Logs (or a downstream logging service) to record:
- Upload event – user ID, bucket, object key, timestamp.
- Signed‑URL generation – token ID, expiry, IP address.
- AI request – model used, request ID, response status.
- Deletion event – object key, who performed the delete.
Store logs in an immutable bucket or a SIEM that complies with your audit requirements.
Step 6 – Delete the Source File Immediately After Summarization
Once the summary is stored, issue a DELETE request to the raw‑file bucket. Verify the delete succeeded before returning the summary to the user. This reduces the attack surface for any later breach.
Step 7 – Periodic Review and Rotation
Every 30 days rotate the API tokens used for the summarization flow. Re‑run a script that checks for any tokens with broader scopes than required and revoke them.
Putting It All Together – A Minimal Worker Script
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const { userId, fileKey } = await request.json()
const signedUrl = await R2.getSignedUrl('raw-docs', fileKey, { expiresIn: 300 })
const aiResp = await fetch('https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/ai/run', {
method: 'POST',
headers: { 'Authorization': `Bearer ${AI_TOKEN}` },
body: JSON.stringify({
model: 'claude-3-5-sonnet',
prompt: `Summarize the document at ${signedUrl}. Do not retain any content.`
})
})
const summary = await aiResp.json()
await R2.put('summaries', `${fileKey}.txt`, summary.output)
await R2.delete('raw-docs', fileKey) // immediate cleanup
await logEvent(userId, fileKey, 'summarized')
return new Response(JSON.stringify({ summary: summary.output }), { status: 200 })
}
Key Takeaways
- Never store raw files in a publicly readable bucket.
- Use short‑lived signed URLs to limit exposure.
- Apply least‑privilege IAM tokens for each step.
- Log every operation and rotate credentials regularly.
- Delete source documents as soon as the summary is safely stored.
Following these steps lets a solo founder or a five‑person startup leverage Cloudflare Workers AI without turning customer documents into a liability.
FAQ
- Can I keep the original document for future reference? Only if you store it in a separate, highly‑restricted bucket with its own access controls and retain it for a defined retention period. Delete it as soon as the business need ends.
- What if the AI model returns part of the original text? Include a post‑processing step that scans the output for phrases longer than 5 words that appear verbatim in the source. If found, redact or re‑run the request with a stricter prompt.
- Do I need to encrypt data in transit? Cloudflare’s edge network uses TLS 1.3 by default, so traffic between your client, R2, and Workers AI is already encrypted.
- How do I prove compliance to auditors? Export the immutable logs, show token scopes, and provide the bucket policy JSON. The OWASP GenAI Security Project recommends this documentation approach.
- Is this approach compatible with other AI providers? Yes. The core concepts—encrypted storage, signed URLs, least‑privilege tokens, and immediate deletion—apply to any hosted model that accepts a file URL.
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.