Scan Salesforce Attachments for Malware with Apex
Use AttachmentScanner 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);
}
}
}