How to fix the Azure and Bicep setup when Defender for Storage is enabled, but on-upload malware scanning still refuses to turn on.
#


Enabling Microsoft Defender for Storage with Bicep sounds straightforward. In practice, you can end up in a frustrating half-working state: the Defender resource is created, you are Owner on the subscription, and on-upload malware scanning still stays disabled.

One of those Azure moments: everything looks right, you are Owner on the subscription, the Bicep deployment says the Defender resource was created, and the portal still shows this gem:

1
Could not enable on-upload malware scanning

Even better, the storage account is already StorageV2, soft delete is enabled, and you are now deep enough into the rabbit hole that every new portal blade feels slightly personal.

We ran into exactly that while enabling Microsoft Defender for Storage with Bicep. The good news: it does work. The annoying news: there are a couple of hidden prerequisites and one very misleading failure mode.

If you found this post because you searched for one of these errors, you are in the right place:

  • Could not enable on-upload malware scanning
  • Plan enablement partially succeeded. Could not enable on-upload malware scanning: Failed to update malware scanning.
  • The attempt to configure storage notifications for the provided storage account failed
  • Failed to retrieve credentials ... listAccountSas ... InvalidAuthenticationToken
  • at least one of the claims 'puid' or 'altsecid' or 'oid' should be present

TL;DR
#

If you just want the shortest possible answer, this is what solved it for us:

  1. Register Microsoft.EventGrid on the subscription
  2. Set dataScannerResourceId explicitly in the Bicep resource
  3. Re-run the same deployment once after provider registration

If step 2 is missing, the Defender resource may be created while malwareScanning.onUpload still stays disabled. If step 1 was done only moments ago, the first retry can still fail with Storage Notification or InvalidAuthenticationToken before the next run succeeds.

🧩 The Setup
#

The goal was straightforward:

  • Enable Defender for Storage on a StorageV2 account via Bicep
  • Turn on on-upload malware scanning
  • Write scan results to blob index tags / Log Analytics
  • Use built-in remediation with BlobSoftDelete

In our case the Bicep module looked roughly like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var defenderDataScannerResourceId = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Security/datascanners/StorageDataScanner'

var defenderMalwareScanning = {
  onUpload: {
    isEnabled: true
    capGBPerMonth: 100
    filters: {}
  }
  blobScanResultsOptions: 'BlobIndexTags'
  automatedResponse: 'BlobSoftDelete'
}

resource defenderForStorageSetting 'Microsoft.Security/defenderForStorageSettings@2025-09-01-preview' = {
  scope: storageAccount
  name: 'current'
  properties: any({
    isEnabled: true
    dataScannerResourceId: defenderDataScannerResourceId
    malwareScanning: defenderMalwareScanning
    overrideSubscriptionLevelSettings: true
    sensitiveDataDiscovery: {
      isEnabled: false
    }
  })
}

That any(...) is not there because I enjoy chaos. It is there because the API accepted a property the published Bicep type definition did not properly expose in our case.

😵 The Misleading Part: Owner Was Not Enough
#

At first glance, this smelled like a permissions problem. After all, the error included things like Unauthorized, InvalidAuthenticationToken, and listAccountSas.

Naturally, the first reaction is:

“But I am Owner on the subscription. What else do you want from me, Azure?”

Turns out: quite a bit.

Being Owner was not the actual missing piece in our case.

The real blocker was that Azure still needed infrastructure behind the scenes for storage notifications, and that depends on the Microsoft.EventGrid resource provider being registered in the subscription.

So yes, RBAC and resource provider registration are two different levers. Azure was perfectly happy to let us be Owner while still refusing to make progress because the required provider was not available. Classic.

🔌 The Hidden Dependency: Microsoft.EventGrid
#

Even though our Bicep did not directly declare an Event Grid resource, Defender for Storage on-upload malware scanning still depended on it.

Why? Because Azure configures storage notifications behind the scenes for on-upload scanning.

This was the missing step:

1
az provider register -n Microsoft.EventGrid

And if you want to verify it:

1
az provider show -n Microsoft.EventGrid --query registrationState -o tsv

If the provider is not registered, you can end up with errors like:

1
Could not enable on-upload malware scanning: The attempt to configure storage notifications for the provided storage account failed.

That is the part I wish someone had highlighted in giant flashing letters somewhere in the docs.

🕵️ Another Gotcha: dataScannerResourceId
#

There was one more surprise.

After working with support, it turned out that for our scenario the API only reliably persisted the malware scanning values when we explicitly set:

1
dataScannerResourceId: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Security/datascanners/StorageDataScanner'

Without that, Defender itself could be enabled, but the malwareScanning.onUpload block was not applied the way we expected.

So if your deployment says the resource was created, but the portal still shows something like this:

1
2
3
4
{
  "isEnabled": false,
  "capGBPerMonth": -1
}

then this property is worth checking.

⚠️ The Weird 401 After EventGrid Registration
#

Here comes the part that made this feel extra fun.

After registering Microsoft.EventGrid, we expected the next deployment to just work.

It did not.

Instead, we got this:

1
2
3
4
5
6
7
8
9
Could not enable on-upload malware scanning: The attempt to configure storage notifications for the provided storage account sti2ernl3tq3ppo failed.

Failed to retrieve credentials for request=RequestUri=https://management.azure.com/.../listAccountSas

StatusCode=401
InvalidAuthenticationToken
The received access token is not valid: at least one of the claims 'puid' or 'altsecid' or 'oid' should be present.

ErrorCode: Storage Notification

At that point the natural instinct is to start tearing apart your Bicep, your identities, your storage config, your life choices, and possibly your coffee machine.

In our case, that would have been the wrong move.

The actual fix was simply:

  1. Register Microsoft.EventGrid
  2. Re-run the same deployment

That second run worked.

So at least in our case, this looked very much like a race condition or delayed backend propagation immediately after provider activation.

🧪 Things We Checked That Were Not the Root Cause
#

To save you a few hours of random portal clicking:

  • The storage account was already StorageV2
  • Soft delete was enabled manually and it still did not fix the problem
  • The subscription role was already Owner

Soft delete is relevant for BlobSoftDelete remediation, but it was not the missing prerequisite that made on-upload malware scanning start working.

✅ What Finally Worked
#

For us, the practical checklist was:

  1. Use Microsoft.Security/defenderForStorageSettings@2025-09-01-preview
  2. Set the malwareScanning block explicitly in Bicep
  3. Set dataScannerResourceId explicitly
  4. Make sure the subscription has Microsoft.EventGrid registered
  5. If the first deployment after provider registration fails with Storage Notification / InvalidAuthenticationToken, run the same deployment again

🔗 Related Azure Posts#

If you are currently wiring up more of your Azure platform around the same solution, these might also be useful:

📌 Final Takeaway
#

If you are trying to enable Defender for Storage on-upload malware scanning with Bicep and it keeps failing, the short version is:

  • Owner on the subscription does not automatically mean all hidden platform prerequisites are in place
  • Microsoft.EventGrid may be required even if your Bicep never mentions Event Grid directly
  • dataScannerResourceId can be the difference between “resource created” and “settings actually applied”
  • A 401 right after Event Grid registration may just be a transient first-run failure, not proof that your entire setup is broken

In other words: if Azure tells you that malware scanning could not be enabled, it might not be your Bicep that is wrong. Sometimes Azure just wants one more provider registration, one more backend sync, and one more deployment run for emotional support.

Happy deploying, and may your onUpload.isEnabled finally stay true.