Skip to content

fix: resolve real Disk resource for AZ-CMP-002 encryption detection - #237

Open
parthrohit22 wants to merge 2 commits into
devfrom
fix/az-cmp-002-disk-encryption-detection
Open

fix: resolve real Disk resource for AZ-CMP-002 encryption detection#237
parthrohit22 wants to merge 2 commits into
devfrom
fix/az-cmp-002-disk-encryption-detection

Conversation

@parthrohit22

@parthrohit22 parthrohit22 commented Aug 7, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Fixes AZ-CMP-002, which was fail-open and could never produce a finding against real Azure data under any configuration.

Type of change

  • New scan rule
  • Remediation playbook
  • Bug fix
  • Dashboard/front-end work
  • API endpoint
  • Documentation
  • Compliance mapping

Rule details (if applicable)

  • Rule ID: AZ-CMP-002
  • Severity: HIGH (confirmed violation) / LOW (indeterminate — see "Review changes" below)
  • Category: Compute
  • Frameworks mapped: CIS 7.2 / NIST PR.DS-1 / ISO 27001 A.10.1.1 / SOC 2 CC6.7 (mapping unchanged — only detection logic was broken)

Testing

  • Tested against a real Azure free trial subscription
  • Returns correct JSON output
  • All seven CI checks pass
  • No hardcoded credentials or secrets

Real Azure test: I have an "Azure for Students" subscription authenticated via az login. I provisioned a throwaway VM (Standard_B2ats_v2, no public IP) in a disposable resource group — its default OS disk is EncryptionAtRestWithPlatformKey with no ADE, i.e. a genuinely non-compliant disk. I ran the rule against it live via AzureCliCredential:

  • Old (pre-fix) implementation against that real VM → 0 findings (confirms the fail-open bug against real data, not just theory).
  • Fixed implementation against the same real VM → 1 finding, determination: "non_compliant", correct disk name, correct framework mapping.

The resource group was deleted immediately after (az group delete --yes) — confirmed fully gone before pushing this branch, no lingering billable resources.

I also ran pytest, ruff check, ruff format --check, and bandit -r api/ scanner/ ai/ -ll locally exactly as the CI workflow configures them — 490 passed, 3 skipped. The actual GitHub Actions run on this push has now completed: every required check (lint, rule/compliance validation, secret scan, SAST, SCA, SBOM, container scan, backend tests + coverage, frontend, website, DCO sign-off, CI Summary) is green. The only non-green item is semgrep-cloud-platform/scan, an external app-based check outside ci.yml's own required job set, still pending as of this update.

Related issue

Closes #236

Checklist

  • Every commit includes a DCO Signed-off-by trailer (git commit -s; see docs/dco.md)
  • My code follows the rule template in CONTRIBUTING.md
  • I added or updated the matching CLI playbook
  • I added or updated all four compliance framework mappings
  • I have not committed any real Azure credentials
  • My branch name follows the convention: fix/description (bug fix, per CONTRIBUTING.md)

The FRAMEWORKS dict and compliance/frameworks/*.json entries for AZ-CMP-002 were already correct and are unchanged — the bug was purely that the rule's detection logic could never fire, so every one of those mappings was a guaranteed silent pass. No mapping file changes were needed.


The defect

_disk_needs_flagging() in scanner/rules/az_cmp_002.py read managed_disk.security_profile and managed_disk.encryption, then .type off whichever was non-None. The object actually passed in is azure.mgmt.compute.models.ManagedDiskParameters, which only exposes id, storage_account_type, disk_encryption_set, and security_profile — there is no encryption attribute. Its security_profile is a VMDiskSecurityProfile, which carries security_encryption_type, not type. Every branch resolved to None, so the function returned False for every disk on every VM, unconditionally.

tests/test_rules_compute.py didn't catch it because the fixtures built managed_disk=make_resource(encryption=make_resource(type=...)) — a shape the SDK never returns, since make_resource accepts arbitrary kwargs.

The fix

  • Added AzureClient.get_disk() to resolve the real Disk resource from a managed disk's id, cached per subscription, returning None on failure (matching the existing convention on get_managed_clusters/get_container_registries — callers must never treat None as compliant).
  • _classify_disk() now determines compliance from the real Disk resource: Disk.encryption.type distinguishes EncryptionAtRestWithPlatformKey (non-compliant) from EncryptionAtRestWithCustomerKey/EncryptionAtRestWithPlatformAndCustomerKeys (compliant), and Disk.encryption_settings_collection.enabled reports Azure Disk Encryption (compliant regardless of key type).
  • Disks whose Disk resource can't be read are no longer silently treated as compliant — they're logged and surfaced so this can't regress into a silent pass again.
  • Updated playbooks/cli/fix_az_cmp_002.sh with a note that it only remediates confirmed (non_compliant) findings, not indeterminate ones, since running ADE remediation against an indeterminate finding could unnecessarily touch an already-compliant CMK disk.
  • Rebuilt the AZ-CMP-002 fixtures against the real SDK attribute surface, covering compliant (customer key, platform-and-customer key, ADE enabled), non-compliant (platform key only), and indeterminate (unreadable Disk) cases. The non-compliant test is a regression test — it fails against the old implementation and passes against this fix.

Review changes (per @TFT444)

  • Indeterminate disks now report a distinct LOW-severity "unknown scan result", not a HIGH "confirmed" finding — separate INDETERMINATE_SEVERITY/INDETERMINATE_DESCRIPTION/INDETERMINATE_REMEDIATION. A confirmed non-compliant disk still keeps the finding at HIGH even if a sibling disk on the same VM is indeterminate (metadata.determination distinguishes the two; see test_cmp_002_confirmed_violation_outweighs_indeterminate_sibling_disk). This still satisfies "never silently pass on an unreadable disk," just without inflating an unknown into a confirmed HIGH violation.
  • Added regression tests built from genuine azure.mgmt.compute.models instances (ManagedDiskParameters, Disk, Encryption, EncryptionSettingsCollection) rather than the make_resource stand-in, which accepts arbitrary kwargs and is exactly what let the original bug's fixtures assert against a shape the SDK never returns. One test (test_cmp_002_managed_disk_parameters_has_no_encryption_attribute) asserts directly that ManagedDiskParameters has no encryption attribute, as a canary against this exact bug class recurring if the SDK's shape ever drifts. These follow the same @pytest.mark.skipif(not _AZURE_SDK_AVAILABLE, ...) pattern already used in tests/test_rules_network.py for AZ-NET-003.

_disk_needs_flagging() read managed_disk.security_profile.type and
managed_disk.encryption.type, neither of which exist on
azure.mgmt.compute.models.ManagedDiskParameters (id,
storage_account_type, disk_encryption_set, security_profile) or
VMDiskSecurityProfile (security_encryption_type, disk_encryption_set).
Every branch resolved to None, so the rule never flagged a disk under
any configuration.

Add AzureClient.get_disk() to resolve the underlying Disk resource
from a managed disk's id, cached per subscription, returning None
(never treated as compliant) on failure. Classify each disk from
Disk.encryption.type (platform key vs customer key vs
platform-and-customer keys) and
Disk.encryption_settings_collection.enabled (ADE). Disks that cannot
be read are reported as indeterminate in finding metadata rather than
silently passing.

Rebuild the AZ-CMP-002 test fixtures against the real SDK attribute
surface and add compliant/non-compliant/indeterminate coverage,
including a regression test for the platform-key-only case that the
old implementation always missed.

Fixes #236

Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@TFT444 TFT444 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@parthrohit22 The main fix looks good and all checks passed. Please handle unreadable disks as an unknown scan result instead of a confirmed HIGH finding, and add one test using the real Azure SDK disk model so the original mock-related bug cannot happen again.

Per review on #237: an unreadable Disk resource is not a confirmed
CIS 7.2 violation, so it must not carry the same HIGH severity and
remediation text as a genuine platform-key-only disk. Indeterminate
disks now produce a distinct LOW-severity finding with its own
description/remediation pointing at the missing
Microsoft.Compute/disks/read grant, while a confirmed non-compliant
disk still outweighs any indeterminate sibling on the same VM and
keeps HIGH severity.

Add regression tests built from genuine
azure.mgmt.compute.models.ManagedDiskParameters/Disk/Encryption
instances (not the make_resource stand-in, which accepts arbitrary
kwargs and would have silently accepted the original bug's invented
attribute shape) so the SDK-shape mismatch that caused the original
fail-open bug cannot recur unnoticed. One test asserts
ManagedDiskParameters has no encryption attribute directly, as a
canary against a future SDK shape drift.

Refs #236

Signed-off-by: PARTH J ROHIT <parthrohit60@gmail.com>
@parthrohit22

Copy link
Copy Markdown
Member Author

@TFT444 Thanks for the review — both addressed in c0252ad:

Unreadable disks as an unknown result, not a confirmed HIGH finding: Agreed, treating "we couldn't read the Disk" the same as "we confirmed platform-key-only" was overstating certainty. Indeterminate disks now produce a distinct LOW-severity finding with its own description ("could not be verified... not a confirmed violation") and remediation (grant Microsoft.Compute/disks/read and re-scan), separate from the HIGH/CIS-7.2 text used for actual violations. metadata.determination still distinguishes "non_compliant" vs "indeterminate" so nothing is silently dropped — a VM with one confirmed non-compliant disk and one indeterminate sibling still reports HIGH (the confirmed violation shouldn't be diluted by an unrelated permissions gap on another disk), covered by test_cmp_002_confirmed_violation_outweighs_indeterminate_sibling_disk.

Test using the real Azure SDK disk model: Added test_cmp_002_noncompliant_with_real_sdk_models_returns_one_finding and a compliant counterpart, both built from genuine azure.mgmt.compute.models.ManagedDiskParameters / Disk / Encryption / EncryptionSettingsCollection instances instead of the make_resource stand-in (which accepts arbitrary kwargs and is exactly what let the original bug's fixtures assert against a shape the SDK never returns). Also added test_cmp_002_managed_disk_parameters_has_no_encryption_attribute, which asserts directly against the real model as a canary — if a future SDK bump ever changed that shape, this fails loudly instead of the rule silently going fail-open again. These follow the existing @pytest.mark.skipif(not _AZURE_SDK_AVAILABLE, ...) pattern already used for AZ-NET-003 in tests/test_rules_network.py.

Also ran the fix against a real Azure subscription (not just mocks) — a throwaway VM with a genuine platform-key-only disk, old implementation returned 0 findings against it, fixed implementation correctly returned 1. Details in the PR description; test resource group has been deleted.

pytest/ruff/bandit all pass locally (490 passed, 3 skipped); waiting on the GitHub Actions run on this push to confirm in CI.

@parthrohit22 parthrohit22 self-assigned this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: AZ-CMP-002 is fail-open, never flags platform-key-only disk encryption

3 participants