A very simple way to scan files for malware in Azure, using Microsoft Defender for Storage. Works as a synchronous HTTP API that any application can call to get a verdict before trusting a file.
All it does is use an Azure Function to accept a file upload over HTTP, place it into Azure Blob Storage where Microsoft Defender for Storage is enabled, poll the blob metadata until Defender produces a scan verdict, and return the result back to the caller. It uses a User Assigned Managed Identity for all authentication. No keys, no secrets, no nonsense.
- Synchronous scan verdicts over HTTP - upload a file, get a verdict back
- Two scanning modes: verdict only, or save the file on a safe verdict
- Configurable file extension whitelist via environment variable
- Fully uses Azure Managed Identity - no manual credential management. ever.
- Authenticated callers only - uses Microsoft Entra ID (Azure AD) via App Service Authentication (EasyAuth)
- Only uses the Python standard library, no external libraries
- Automatic cleanup of scanned blobs when not needed
- Automatic cleanup of malicious blobs on unsafe verdicts
- Very easy to audit. Why should you trust me? Trust yourself instead.
- Minimal maintenance overall
- No nonsense. If this has even a hint of nonsense anywhere, it is a bug. File an issue.
- verdict_only: Upload a file, get back
{"verdict": "safe"}or{"verdict": "unsafe"}. The blob is always deleted after scanning. - save_on_safe: Upload a file, get back a verdict. If safe, the blob is kept and a
blob_uriis returned:{"verdict": "safe", "blob_uri": "https://..."}. If unsafe, the blob is deleted and only{"verdict": "unsafe"}is returned.
The list here assumes you will be using dedicated Azure resources for file scanning. Use dedicated resources to uphold a strong security model. Don't cut corners here.
- Azure Storage Account
- Must have Microsoft Defender for Storage enabled with malware scanning
- Must have a blob container created for scanning (default name:
av-scanning) - Must have a lifecycle management policy configured to automatically delete blobs older than 30 minutes in the scanning container, as a safety net for orphaned blobs
- Must be network accessible by the Azure Function
- Must grant the Azure Function's User Assigned Managed Identity the Azure RBAC role of "Storage Blob Data Contributor"
- Azure Function
- Must have a User Assigned Managed Identity assigned to it
- Must have App Service Authentication (EasyAuth) enabled and configured with Microsoft Entra ID
- Must have the source code from this repo deployed to it
- Must have outbound network access to the Azure Storage Account
- Must run on a Flex Consumption App Service Plan, if you enjoy not burning money
- Calling Application (e.g. C# App Service)
- Must have a Managed Identity (system or user assigned)
- Must be granted permission to call the Azure Function via Entra ID app role assignment
- Must acquire a bearer token scoped to the Azure Function's application ID before calling the scan endpoint
Sample values are provided below.
- "AV_STORAGE_ACCOUNT_NAME" = "your-blob-storage-account-name"
- "AV_CONTAINER_NAME" = "av-scanning"
- "AV_MANAGED_IDENTITY_CLIENT_ID" = "00000000-0000-0000-0000-000000000000"
- "AV_ALLOWED_EXTENSIONS" = ".pdf,.docx,.xlsx,.pptx,.png,.jpg,.jpeg,.gif,.txt,.csv"
- "AV_MAX_FILE_SIZE_MB" = "512"
- "AV_SCAN_POLL_INTERVAL" = "2"
- "AV_SCAN_POLL_TIMEOUT" = "300"
POST /api/scan?filename=report.pdf&mode=verdict_only
Authorization: Bearer <entra-id-token>
Content-Type: application/octet-stream
<raw file bytes>
The filename query parameter is required. The file extension must be present in the AV_ALLOWED_EXTENSIONS whitelist. Files larger than the configured AV_MAX_FILE_SIZE_MB limit are rejected.
The mode query parameter is optional and defaults to verdict_only. Valid values are verdict_only and save_on_safe.
Safe file (verdict_only)
{"verdict": "safe"}Unsafe file (verdict_only)
{"verdict": "unsafe"}Safe file (save_on_safe)
{"verdict": "safe", "blob_uri": "https://yourstorageaccount.blob.core.windows.net/av-scanning/abc-def/report.pdf"}Unsafe file (save_on_safe)
{"verdict": "unsafe"}Scan timeout (504)
{"error": "Scan timed out. No verdict was returned by Defender."}Scan error (422)
{"error": "SAM259210: Scan failed - blob is protected by password."}File too large (413)
{"error": "File exceeds the maximum allowed size of 512 MB."}- Microsoft Defender for Storage will not scan files larger than 50 GB. The maximum file size is configurable via
AV_MAX_FILE_SIZE_MB(default: 512 MB) and is capped at 50 GB regardless of the configured value. In practice, the Azure Functions memory limit will be the real constraint for large files. - Scan duration varies based on file size, file type, and service load. There is no SLA on scan time.
- Password-protected archives, client-side encrypted blobs, and archive tier blobs cannot be scanned by Defender.
- The default monthly scanning cap is 10 TB per storage account. Once exceeded, scanning stops for the remainder of the month.
- Throughput is limited to 50 GB per minute per storage account. Sustained upload rates above this may result in unscanned blobs.
- The caller POSTs a file to the Azure Function with a filename and scanning mode
- The function validates the file extension against the configured whitelist
- The file is uploaded to Azure Blob Storage under a UUID-prefixed path
- Microsoft Defender for Storage automatically scans the blob and writes the result as a blob index tag (
Malware scanning scan result) - The function polls the blob index tags until the scan result appears or a timeout is reached
- The function returns the verdict to the caller and cleans up the blob as appropriate for the scanning mode
An OpenAPI 3.0.3 specification is provided in openapi.yaml for integration with API management tooling, client generation, or documentation.
This repository is not endorsed by my employer, organisation, clients, anyone, anything or any entity in any way, shape or form. This is released on the internet as a convenience only. No refunds, no "Defender said my file was safe but it ate my production database" support here.