Scan Salesforce Attachments for Malware with Apex
Use the AttachmentScanner antivirus API to scan files and URLs from Salesforce for viruses and malware using Named Credentials and Apex HTTP callouts.
Prerequisites
-
Create a Named Credential called
AttachmentScanner:- URL:
https://scans.attachmentscanner.com - Authentication: Custom Header
- Header:
Authorization=Bearer YOUR_TOKEN
- URL:
-
Add a Remote Site Setting for your scanner URL
Scan a URL
The simplest way to scan — pass a URL and get back a result:
public class AttachmentScannerService {
private static final String NAMED_CREDENTIAL = 'callout:AttachmentScanner';
public static Map<String, Object> scanUrl(String fileUrl) {
HttpRequest req = new HttpRequest();
req.setEndpoint(NAMED_CREDENTIAL + '/v1.0/scans');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, String>{ 'url' => fileUrl }));
HttpResponse res = new Http().send(req);
return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
// result.get('status') => "found" or "ok"
}
}
Scan a ContentVersion (File Upload)
Scan files uploaded to Salesforce by reading the ContentVersion blob. This must be called from a @future or Queueable context because Salesforce doesn't allow synchronous callouts from triggers.
@future(callout=true)
public static void scanContentVersion(Id contentVersionId) {
ContentVersion cv = [
SELECT VersionData, Title, FileExtension
FROM ContentVersion
WHERE Id = :contentVersionId
LIMIT 1
];
String boundary = '----FormBoundary' + String.valueOf(DateTime.now().getTime());
String filename = cv.Title + '.' + cv.FileExtension;
// Build multipart body
String body = '--' + boundary + '\r\n'
+ 'Content-Disposition: form-data; name="file"; filename="' + filename + '"\r\n'
+ 'Content-Type: application/octet-stream\r\n\r\n';
String footer = '\r\n--' + boundary + '--';
Blob bodyBlob = EncodingUtil.base64Decode(
EncodingUtil.base64Encode(Blob.valueOf(body))
+ EncodingUtil.base64Encode(cv.VersionData)
+ EncodingUtil.base64Encode(Blob.valueOf(footer))
);
HttpRequest req = new HttpRequest();
req.setEndpoint(NAMED_CREDENTIAL + '/v1.0/scans');
req.setMethod('POST');
req.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
req.setBodyAsBlob(bodyBlob);
HttpResponse res = new Http().send(req);
Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
if ((String) result.get('status') == 'found') {
// Malware detected — delete the file
ContentDocument doc = [
SELECT Id FROM ContentDocument
WHERE LatestPublishedVersionId = :contentVersionId
LIMIT 1
];
delete doc;
}
}
Async Scan with Callback
Salesforce supports inbound webhooks via Sites or Experience Cloud. Use async: true with a callback to avoid hitting Apex callout time limits on large files:
public static String scanUrlAsync(String fileUrl, String callbackUrl) {
HttpRequest req = new HttpRequest();
req.setEndpoint(NAMED_CREDENTIAL + '/v1.0/scans');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String, Object>{
'url' => fileUrl,
'async' => true,
'callback' => callbackUrl
}));
HttpResponse res = new Http().send(req);
Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (String) result.get('id'); // scan ID for polling
}
Auto-Scan Trigger
Automatically scan every file uploaded to Salesforce:
trigger ContentVersionScanTrigger on ContentVersion (after insert) {
for (ContentVersion cv : Trigger.new) {
if (cv.IsLatest) {
AttachmentScannerService.scanContentVersion(cv.Id);
}
}
}