Stormspotter: Mapping Azure Attack Paths with Graph Database Intelligence
Hook
Traditional Azure security audits produce spreadsheets of permissions—but attackers think in graphs. Stormspotter reveals why that matters by showing you the chain of three overlooked roles that gives a low-privilege developer account full subscription control.
Context
Azure's Role-Based Access Control (RBAC) system creates a web of permissions that's nearly impossible to reason about manually. A service principal might have Reader access on a resource group, but that resource group contains a Key Vault with secrets that unlock a storage account with a connection string to a SQL database with admin credentials. Traditional security tools give you lists: who has what role, which resources exist, what permissions are assigned. But they don't answer the critical question for red teams: what can I actually reach from this compromised account?
Stormspotter emerged from Azure's own red team operations to solve this exact problem. Built by offensive security practitioners who needed to map multi-subscription environments during engagements, it takes a fundamentally different approach: modeling Azure as a directed graph where resources, identities, and permissions become nodes connected by relationships. This isn't just visualization for its own sake—graph databases enable queries like 'show me every path from this compromised service principal to production databases' or 'which identities can pivot into subscription Owner through role assignment chains.' It's the difference between having a phonebook and having a map of who-knows-who in a social network.
Technical Insight
Stormspotter's architecture splits the problem into three distinct layers, each solving a specific challenge in Azure attack surface mapping. The collector (Stormcollector) runs on any machine with Azure credentials and performs comprehensive enumeration using the Azure Python SDK. Unlike tools that simply dump API responses to JSON, it builds a relational model in SQLite—a crucial design choice that enables the second stage to construct graph relationships without holding massive datasets in memory.
The collection process handles Azure's authentication complexity gracefully. You can authenticate via Azure CLI (using your existing az login session) or provide service principal credentials directly:
# Using Azure CLI authentication (default)
python stormcollector.pyz
# Using service principal with explicit credentials
python stormcollector.pyz --cli false \
--tenant <tenant-id> \
--client <app-id> \
--secret <client-secret>
# Targeting specific Azure clouds
python stormcollector.pyz --cloud azurechinacloud
The collector enumerates subscriptions, resource groups, virtual machines, databases, storage accounts, Key Vaults, and critically—all RBAC role assignments and Azure AD relationships. It uses the --backfill flag to perform a second correlation pass, linking Azure AD objects to their RBAC permissions across subscriptions. This two-phase approach prevents timeout issues in large tenants where querying AAD and ARM simultaneously would fail.
The backend component transforms this relational data into a Neo4j graph database, where the real analytical power emerges. Each Azure resource becomes a node with typed properties, and permissions become directed edges. A role assignment like 'User X has Contributor on Resource Group Y' creates an edge labeled CONTRIBUTOR pointing from the user node to the resource group node. Nested resources create CONTAINS edges, so you can traverse from a subscription through resource groups to individual VMs.
This graph structure enables Cypher queries that would be nightmarishly complex in SQL:
// Find all paths from a specific user to VMs they can control
MATCH path = (u:User {email: 'compromised@example.com'})-[*1..5]->(vm:VirtualMachine)
WHERE ANY(r IN relationships(path) WHERE type(r) IN ['OWNER', 'CONTRIBUTOR', 'VIRTUALMACHINECONTRIBUTOR'])
RETURN path
// Identify service principals with cross-subscription privilege escalation paths
MATCH (sp:ServicePrincipal)-[:HASROLE]->(role)-[:ALLOWS]->(action)
WHERE action.name = 'Microsoft.Authorization/roleAssignments/write'
MATCH (sp)-[*1..3]->(sub:Subscription)
RETURN sp.displayName, count(DISTINCT sub) as reachable_subscriptions
ORDER BY reachable_subscriptions DESC
The frontend Vue application provides an interactive interface for exploring these graphs visually. You can click through nodes to expand their relationships, filter by resource type or permission level, and export specific attack paths for reporting. The graph layout uses force-directed positioning, so densely connected nodes (like high-privilege service accounts) naturally cluster at the center while isolated resources appear at the periphery.
One clever implementation detail: Stormspotter packages the collector as a PYZ (Python zip) file using Shiv, creating a self-contained executable with all dependencies bundled. This solves a common problem in red team operations—you can't always pip install packages on the machine with Azure access (it might be an air-gapped jumpbox or a compromised system where you have limited privileges). The PYZ runs with just a Python interpreter, no virtualenv or package installation required.
The FastAPI backend exposes REST endpoints that the frontend consumes, but you can also query them directly during engagements:
# Upload collected data
curl -X POST http://localhost:9090/api/upload \
-F 'file=@stormcollector.ss'
# Query for specific node types
curl http://localhost:9090/api/nodes?type=ServicePrincipal
# Get relationship counts for attack surface sizing
curl http://localhost:9090/api/stats
This API-first design means you can script custom analyses or integrate Stormspotter data into other offensive security workflows without relying on the GUI.
Gotcha
Stormspotter's beta status shows in frustrating ways during actual engagements. Many Azure resource types are only partially supported—the collector successfully enumerates them and stores basic properties like name and ID, but the frontend doesn't render meaningful visualizations or expose their specific permissions. You'll see nodes for App Service Plans or Logic Apps, but without the detailed property inspection and relationship mapping that makes the tool valuable for VMs and storage accounts. This creates gaps in attack path analysis where critical pivots might exist but remain invisible.
The deployment model creates operational friction for team-based engagements. You must run the collector on a machine with Azure credentials (typically your attack workstation or a compromised asset), upload the resulting SQLite file to the backend, and access the frontend—but the frontend's upload functionality only works with Docker containers running on localhost. If you deploy the backend and Neo4j on a shared team server for collaborative analysis, you can't use the web interface to upload data. Instead, you need direct filesystem access to the backend container or must use curl to POST files to the API. For a tool designed for red team operations where multiple operators need to analyze the same environment, this localhost assumption is a significant limitation.
The setup overhead is non-trivial even with Docker Compose. You need Neo4j configured and accessible, environment variables set correctly for the backend to connect, and the frontend proxy configured to reach the backend API. First-time setup typically takes 30-60 minutes of troubleshooting connection issues and volume mount permissions. During time-sensitive engagements, this delays getting actionable intelligence from your enumeration data.
Verdict
Use if: You're conducting Azure penetration tests or red team operations in complex multi-subscription environments where understanding privilege escalation chains and lateral movement paths provides competitive advantage. Stormspotter excels when you need to answer questions like 'what's the shortest path from this compromised developer account to production databases' or 'which service principals can assign themselves additional roles.' It's particularly valuable when working with organizations that have organic (messy) Azure deployments with inheritance, nested resource groups, and overlapping role assignments that create unexpected permission chains. The graph visualization helps communicate risk to clients more effectively than spreadsheet reports. Skip if: You need production-ready tooling with comprehensive Azure resource coverage, are working in simple single-subscription environments where manual RBAC review suffices, or require real-time monitoring rather than point-in-time assessment. Also skip if your engagement timeline can't accommodate 1-2 hours of setup and troubleshooting, or if you need actively maintained software with recent updates—Stormspotter's beta status and limited recent development suggest it may not keep pace with new Azure services and permission models. Consider BloodHound/AzureHound instead if you need mature, actively developed attack path analysis, or ROADtools if your focus is purely Azure AD without the broader resource relationship mapping.