How to Scan Files in DigitalOcean Spaces for Malware
DigitalOcean Spaces gives you a lot out of the box — S3-compatible object storage, a built-in CDN with 274+ edge locations, and straightforward pricing starting at $5/month. It's a great fit for applications that handle user uploads.
One thing Spaces doesn't include is malware scanning. If your application accepts files from users, you'll need to add that yourself — but the S3 compatibility makes it easy. Spaces works with the same AWS SDKs that DigitalOcean recommends, so presigned URLs and the scanning integration work out of the box. You can scan any file in Spaces with a single API call, and set up async scanning with callbacks for production.
Scanning with a Presigned URL
Generate a presigned URL for the object in Spaces and pass it to AttachmentScanner. The scanning engines fetch the file directly — your application never has to download it.
Spaces is S3-compatible, so you use the same @aws-sdk/client-s3 package
that DigitalOcean recommends — just point it at your
Spaces region:
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
// Point the S3 client at your Spaces region
const s3 = new S3Client({
region: "nyc3",
endpoint: "https://nyc3.digitaloceanspaces.com",
credentials: {
accessKeyId: DO_SPACES_KEY,
secretAccessKey: DO_SPACES_SECRET,
},
});
// Generate a presigned URL (valid for 15 minutes)
const presignedUrl = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: "my-space", Key: "uploads/document.pdf" }),
{ expiresIn: 900 }
);
// Scan the file
const response = await fetch(`https://${API_URL}/v1.0/scans`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: presignedUrl }),
});
const result = await response.json();
if (result.status === "found") {
console.log("Malware detected:", result.matches);
// Delete or quarantine the object
}
The S3 compatibility means the same SDK, the same presigned URL flow, and the same scanning integration works across Spaces and S3. If you're using both, the only difference is the endpoint.
Going Async: The Recommended Approach
The example above blocks while the scan runs. For production, use async scanning with callbacks so your application isn't waiting on scan results.
- File arrives in Spaces (via your app or direct upload)
- Your application generates a presigned URL and kicks off an async scan
- AttachmentScanner fetches the file and scans it
- When the scan completes, AttachmentScanner POSTs the result to your callback URL
- Your callback handler deletes the file, tags it, or moves it to a clean bucket
// Kick off an async scan with a callback
async function scanSpacesObject(bucket: string, key: string) {
const presignedUrl = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: bucket, Key: key }),
{ expiresIn: 900 }
);
const response = await fetch(`https://${API_URL}/v1.0/scans`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: presignedUrl,
callback: "https://your-app.com/webhooks/scan-complete",
async: true,
}),
});
return response.json();
}
// Callback handler
async function handleScanCallback(req, res) {
const result = req.body;
if (result.status === "ok") {
// File is clean — mark it as available
await markAsScanned(result.url);
} else if (result.status === "found") {
await deleteFromSpaces(result.url);
await notifyAdmin(result);
} else if (result.status === "warning") {
await flagForReview(result.url, result.matches);
}
return res.status(200).end();
}
For a full walkthrough of sync vs async scanning, staging areas, and
handling the warning status, see our
complete guide to scanning user uploads.
Where to Trigger Scans
Spaces keeps things simple — there's no event notification system to configure. Instead, you trigger scans directly from your application code, which gives you full control over when and how scanning happens. The two common patterns:
In your upload handler — right after your app writes the file to Spaces, generate a presigned URL and kick off a scan. This is the most common pattern and works well when all uploads go through your application.
On a schedule — if files arrive in your Space through other channels (CLI uploads, third-party integrations), you can run a periodic job that lists recent objects and scans any that haven't been tagged as scanned yet.
Either way, the scanning itself is the same — presigned URL, API call, callback.
If you're using the Spaces CDN, this matters even more — once a file is cached at the edge, it's being served globally. Scanning before a file becomes CDN-accessible ensures you're not distributing malicious content through your own CDN.
Testing Your Setup
Verify your integration with the EICAR test file. Upload it to your
Space, run it through your scanning flow, and confirm you get
"status": "found" back.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.attachmentscanner.com/eicar.com"}' \
-XPOST https://YOUR_API_URL/v1.0/scans
Then test through your actual Spaces flow — upload EICAR to your bucket and confirm the scan triggers and the result is handled correctly.
One API, Any Cloud
If you're using Spaces alongside other storage providers — S3, Azure Blob, Google Cloud Storage — AttachmentScanner gives you a single scanning API across all of them. Same endpoint, same response format, same callbacks. Adding scanning to a new storage backend is just another presigned URL.
Getting Started
- Sign up for an account — you'll get an API token and endpoint URL
- Test with the EICAR test file to confirm detection works
- Set up async scanning with callbacks for production
- Integrate into your upload flow
AttachmentScanner Team
Other Articles
Scan AWS S3 Files for Malware
AttachmentScanner Team
Scanning User Uploads for Malware
AttachmentScanner Team
Pass a Pen Test File Upload Check
AttachmentScanner Team