Back to skill

Security audit

AuditClaw Azure

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Azure compliance scanner that uses read-only Azure access and stores results locally, with some operational risks users should manage.

Install only if you are comfortable granting a scanner identity read-only visibility into the target Azure subscription and storing compliance findings in the local shared GRC database. Prefer a dedicated service principal, protect and rotate AZURE_CLIENT_SECRET, scope access as narrowly as your audit needs allow, and review the known evidence-quality limitations before relying on results for formal assurance.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:90
Finding
Subscription-Wide Azure Roles Exceed the Minimum Permissions Required<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:90-98`; `scripts/azure-roles.json:17-30` **Vulnerability Type**: Excessive Azure RBAC permissions **Risk Level**: Medium ### Vulnerable Code ```markdown ### Step 1: Create Service Principal az ad sp create-for-rbac --name auditclaw-scanner --role Reader --scopes /subscriptions/<SUBSCRIPTION_ID> ### Step 2: Add Security Reader Role az role assignment create --assignee <APP_ID> --role "Security Reader" --scope /subscriptions/<SUBSCRIPTION_ID> ``` The corresponding role configuration is: ```json { "role": "Reader", "scope": "Subscription", "reason": "Read access to all resource configurations (storage, compute, network, SQL, App Service)", "built_in_id": "acdd72a7-3385-48ef-bd42-f606fba81ae7" }, { "role": "Security Reader", "scope": "Subscription", "reason": "Read access to Microsoft Defender for Cloud status and security recommendations", "built_in_id": "39bc4728-0917-49c7-9d2c-d95423bc2eb4" } ``` ### Technical Analysis The Skill performs a limited set of configuration checks against Storage, Network Security Groups, Key Vault, SQL, Compute, App Service, and Defender pricing information. However, its setup instructions grant the built-in **Reader** and **Security Reader** roles across the entire subscription. These built-in roles provide visibility beyond the exact management-plane read operations used by the twelve declared checks. In particular, the policy states that Security Reader permits access to security recommendations, while the implementation only queries Defender pricing tiers. The behavior is read-only and does not permit direct Azure resource modification, but it violates least privilege by allowing broad subscription reconnaissance if the scanner identity is compromised. ### Attack Path 1. An operator follows the documented setup and grants Reader and Security Reader at subscription scope. 2. The service-principal client secret is exposed through an unrelated ...[truncated 940 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define a custom Azure role containing only the management-plane read actions required by the implemented checks. 2. Restrict the role to the smallest feasible scope, such as selected resource groups when full-subscription coverage is not required. 3. Determine whether Defender pricing can be read through a narrowly scoped custom permission. If so, remove the built-in Security Reader assignment. 4. If Security Reader remains necessary, document the additional information it exposes and why that access is required. 5. Store the service-principal secret in an approved secret manager, rotate it regularly, and use short-lived or workload-identity credentials where possible. 6. Monitor sign-ins and management API activity associated with the scanner principal. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/checks/network.py:54
Finding
NSG Analysis Can Miss Public SSH and RDP Rules Using Plural Prefix or Port Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/checks/network.py:54-62` **Vulnerability Type**: Incomplete Azure NSG rule parsing **Risk Level**: Medium ### Vulnerable Code ```python for rule in (nsg.security_rules or []): if (rule.direction == "Inbound" and rule.access == "Allow" and rule.source_address_prefix in OPEN_SOURCES): port_range = str(rule.destination_port_range) if rule.destination_port_range else "" if _port_matches(port_range, 22): ssh_open = True if _port_matches(port_range, 3389): rdp_open = True ``` ### Technical Analysis Azure NSG rules can represent sources and destination ports through singular fields such as `source_address_prefix` and `destination_port_range`, or through plural fields such as `source_address_prefixes` and `destination_port_ranges`. The implementation only evaluates the singular fields. Consequently, an inbound Allow rule expressed through plural fields may not be recognized as exposing port 22 or 3389. The check can then produce passing evidence even though SSH or RDP is reachable from an unrestricted source. This is an evidence-integrity flaw rather than an Azure privilege escalation. It weakens the reliability of the compliance result stored in the GRC database. ### Attack Path 1. An NSG contains or is configured with an inbound Allow rule using `source_address_prefixes` rather than `source_address_prefix`. 2. The rule includes an unrestricted source and exposes SSH or RDP through `destination_port_ranges`. 3. The evidence sweep lists the NSG but examines only the singular fields. 4. The rule is not identified as unrestricted. 5. The Skill records a passing SSH or RDP finding in the compliance database. 6. An auditor or automated workflow relies on that evidence and fails to remediate the public management endpoint. ### Impact Assessment The flaw can create false-negative compliance results for NSGs across the scanned sub ...[truncated 348 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Normalize both singular and plural source fields into one collection: - `source_address_prefix` - `source_address_prefixes` 2. Normalize both singular and plural destination-port fields: - `destination_port_range` - `destination_port_ranges` 3. Evaluate every source and port combination for unrestricted access. 4. Normalize case when comparing direction, access mode, and service tags. 5. Explicitly handle wildcard values, Internet service tags, IPv4 and IPv6 unrestricted CIDRs, and valid port ranges. 6. Add unit tests covering plural prefixes, plural port ranges, wildcards, mixed singular/plural representations, IPv6 exposure, and malformed rules. 7. Fail conservatively or emit an indeterminate result when a rule cannot be parsed reliably. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/azure_evidence.py:247
Finding
Azure Connection Test Reports Unexpected Exceptions as Successful Access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/azure_evidence.py:247-257` **Vulnerability Type**: Fail-open service connectivity validation **Risk Level**: Medium ### Vulnerable Code ```python except Exception as e: error_str = str(e) if "AuthorizationFailed" in error_str or "Forbidden" in error_str: results.append({"service": service_name, "status": "denied", "detail": error_str[:200]}) failed += 1 else: results.append({"service": service_name, "status": "ok", "detail": f"accessible (no resources or {type(e).__name__})"}) passed += 1 ``` ### Technical Analysis The service probe counts every exception as success unless its message contains the exact strings `AuthorizationFailed` or `Forbidden`. Network failures, timeouts, TLS errors, SDK failures, alternative authorization errors, malformed responses, and API incompatibilities can therefore be reported as successful access. A successful probe should require successful completion of the Azure API call. Empty resource collections are valid successful responses and do not need to be inferred from an exception. The current fail-open behavior can make `--test-connection` claim that an integration is healthy even when individual Azure services were never successfully queried. ### Attack Path 1. A service probe encounters a timeout, transport failure, SDK error, or authorization error whose text does not contain either expected substring. 2. The broad exception handler catches the failure. 3. The failure enters the `else` branch and is labeled `status: "ok"`. 4. The `passed` counter is incremented. 5. The final connection-test result reports the affected service as accessible and may report `all_passed: true`. 6. An operator trusts the health result and proceeds even though evidence collection for that service may be unavailable. An attacker able to disrupt or manipulate network connectivity could exploit this fail-open behavior to conceal an unhealth ...[truncated 474 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Count a service as accessible only after its API request completes successfully. 2. Treat all caught exceptions as failures by default. 3. Classify errors through Azure SDK exception types and HTTP status codes rather than matching human-readable message strings. 4. Distinguish authorization failures, authentication failures, transport failures, timeouts, and unexpected SDK errors in the output. 5. Preserve the empty-list case as a successful response because `list(method())` naturally returns an empty list without throwing. 6. Ensure `all_passed` is false whenever any service probe throws an exception. 7. Add tests for timeouts, connection errors, alternate authorization messages, HTTP 401/403 responses, malformed SDK responses, and empty resource collections. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The manifest says the skill provides '12 read-only checks' for Azure evidence collection, which implies assessment-oriented behavior against Azure resources. However, this file also inserts evidence records and updates integration status in a local GRC database via add-evidence, INSERT, and UPDATE operations. Those write-side effects go beyond a plain read-only checking role as described.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill advertises executable behavior and relies on environment variables and shell/Python execution, but it does not declare an explicit tool scope such as allowed tools or permissions. That creates an authorization and transparency gap: a host agent or user may not understand that command execution and secret-bearing environment access are required, increasing the risk of unintended shell use or exposure of Azure credentials.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This manifest lists sensitive authentication material including AZURE_CLIENT_SECRET, but provides no warning about secure storage, exposure risk, or precautions when using these credentials. Because manifest files are in scope for SQP-2 only when they are markdown files, this finding is based on the adjacent operational guidance nature of the file combined with explicit credential disclosure expectations in the content.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"--file-content", json.dumps(result, default=str),
    ]

    proc = subprocess.run(cmd, capture_output=True, text=True)
    if proc.returncode != 0:
        return _store_evidence_direct(db_path, check_name, result)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code stores Azure compliance results as evidence, and the stored payload includes serialized check output via `store_evidence`, which can contain cloud inventory or configuration data. Although the module docstring states that results are stored, there is no confirmation prompt or nearby user-facing disclosure at the point of collection and persistence in the execution flow.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The skill states that collected compliance evidence is persisted to a shared local SQLite database, but it does not prominently warn users about local data retention, cross-skill sharing, or the sensitivity of stored cloud security metadata. Even if the data is read-only evidence, it can still reveal security posture, resource names, misconfigurations, and audit details that another local process or skill could access.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The `test_connection` routine actively connects to Azure Resource Manager and several service APIs to enumerate accessibility, which is a network operation involving account and subscription context. While the function name suggests connectivity testing, the code lacks an explicit runtime disclosure that it will contact multiple Azure services and attempt listing operations.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
for service_name, (module_path, class_name, prop_name, method_name) in service_probes.items():
        try:
            mod = importlib.import_module(module_path)
            client_class = getattr(mod, class_name)
            client = client_class(credential, subscription_id)
            prop = getattr(client, prop_name)
            method = getattr(prop, method_name)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
mod = importlib.import_module(module_path)
            client_class = getattr(mod, class_name)
            client = client_class(credential, subscription_id)
            prop = getattr(client, prop_name)
            method = getattr(prop, method_name)
            list(method())  # Force execution
            results.append({"service": service_name, "status": "ok", "detail": "accessible"})
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
client_class = getattr(mod, class_name)
            client = client_class(credential, subscription_id)
            prop = getattr(client, prop_name)
            method = getattr(prop, method_name)
            list(method())  # Force execution
            results.append({"service": service_name, "status": "ok", "detail": "accessible"})
            passed += 1
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Missing User Warnings

Low
Confidence
79% confidence
Finding
This code instantiates an Azure Compute client and lists all virtual machines in the subscription, which is a network/API operation involving access to cloud resource metadata. While the docstring describes the check's purpose, there is no user-facing warning, confirmation, logging, or explicit disclosure in this file that it will query the Azure subscription.