Changelog¶
Notable changes per release. Patch releases are folded into the major entry they belong to.
0.7.0¶
Released 2026-10-25
Azure and GCP are mapped as deeply as AWS, scanner and collector failures raise exceptions you can import and catch, report.html opens on an animated isometric map of the estate, and the docs site moves to MkDocs Material.
Added¶
- Azure depth. Besides the Resource Graph breadth query, cloudg reads the
policyresources,securityresources,recoveryservicesresources,networkresources,maintenanceresources,insightsresourcesandkubernetesconfigurationresourcestables, management-group and tenant-scope role assignments, ARM child resources (APIM, Service Bus, Event Hubs, DNS record sets, Front Door, Virtual WAN, Data Factory and Synapse pipelines, AI Search, Azure OpenAI deployments and more), diagnostic settings, Entra ID through Microsoft Graph (users, groups, app registrations, service principals, app-role assignments, OAuth2 grants, directory roles, PIM eligibility) and the objects inside AKS clusters. The ARM type catalog grows from 125 to 278 types.azure.map_entra,azure.child_resources,azure.diagnostic_settingsandazure.data_planeturn the extra sources off. - GCP depth. 350 Cloud Asset Inventory types now have extractors (123 before), and API sources fill what the inventory does not return: Cloud DNS records, Security Command Center, Cloud Identity groups, IAM deny policies and principal access boundaries, PAM, audit configs, Cloud Monitoring, OS Config, Artifact Analysis, Backup and DR, Apigee, Cloud Scheduler and others.
gcp.deep_sourcespicks which ones run. - Azure and GCP record coverage per task family, as AWS does, show "not enabled" placeholders for Defender plans, Sentinel and Security Command Center services, and follow
--services/inventory.exclude_services. cloudg.exceptions:CloudGErrorand its subclasses (ScannerError,ScannerTimeoutError,ScannerNotInstalledError,ScannerExecutionError,ScannerOutputError,ReportParseError,ScannerFailed,CollectionError,CredentialError,ProviderSDKMissingError,ConfigurationError), also exported fromcloudg. Every scanner wrapper takesstrict=Trueto raise on the first failure, records each failure in.failures, and hasraise_for_errors().parse_report(path, strict=True)raisesReportParseErrorfor an unreadable report.- report.html: an estate map in 3D (provider plates, account and region platforms, network slabs, subnet tiles, a control deck for identity and governance), with every relation drawn as a trace line on a lane of its own, asset selection, tracing from the internet, turning, zoom and pan. The D3 tree and force views stay one toggle away. The page also gains a posture overview, a top-risks list, an Assets tab, CSV export, light and dark themes and print styles. The Anatomy iso-kit and the Hairline kernel (both MIT) ship in
cloudg/templates/vendor/and are always embedded. - The docs site is built with MkDocs Material, with API pages generated from the docstrings, tabbed examples, an Examples section, an Exceptions page, a Credits page and a sample report at
/sample-report.html.
Changed¶
- Credential, SDK and collection failures raise
CredentialError,ProviderSDKMissingErrorandCollectionError. They keep their old bases (RuntimeError,ImportError), so existingexceptclauses still match.ResilienceErrorandLiveOperationRejectednow derive fromCloudGError. - The PyPI project page is a short overview (
docs/PYPI.md) that links into the docs site.docs/DOCUMENTATION.mdstays the full reference that the MCP server serves.
0.6.1¶
Released 2026-10-10
A security fix for report.html, a round of fixes found while documenting every command and API, and the new docs site. Upgrade if you open reports built from scanner output you did not produce yourself. Several commands now behave the way their help text always said, so check the Changed list if you script cloudg report, cloudg scan or the exit codes.
Security¶
- report.html no longer runs markup or script that arrives in scanner output. Finding titles, descriptions, evidence, resource IDs, asset names and compliance framework names are escaped wherever the page writes them, the page header is autoescaped, and data embedded in the page's script can no longer close the script element.
- A package can no longer take over a built-in scanner (such as
prowler) by registering acloudg.scannersentry point with the same name. Built-in names always win and the duplicate is ignored with a warning.
Fixed¶
- report.html works offline. Chart.js 4.4.0 and D3 7.9.0 ship with cloudg and are embedded in the page when
report.inline_jsis true (the default). Withinline_js: falsethe page loads the same pinned builds from cdn.jsdelivr.net with Subresource Integrity. Clicking a row in a filtered findings table now opens that finding. - Config keys that were accepted but ignored now take effect:
graph.persist_graphml,graph.export_cytoscape,graph.max_nodes_warn,rag.chunk_strategy,rag.max_chunk_tokens,report.formats,report.inline_js,report.output_dir(as the default of theCloudGEnginemethods),rulesets.load_externalandontology.include_raw_metadata. A misspelt key in any section is logged asUnknown config key <section>.<key> is ignored, not only underratelimit.rulesets.rules_dir: nullloads the packaged rulesets instead of failing validation. cloudg deps --jsonwrites only JSON to stdout. Whenever--jsonis used, and forcloudg mcp, the banner and logs go to stderr.cloudg report -ikeeps the scan ID, timestamps, compliance results and edges of a findings.json, rebuilds edges for older files that lack them, and accepts raw-findings.json.scanners.timeout_secondslimits each scanner process incloudg run,cloudg scanandCloudGEngine.scan(). Before, the limits were hard-coded at 3600 and 1800 seconds. A scanner that times out is reported as failed and its partial findings are kept.cloudg scanreads-c config.yaml, no longer scans the current directory unless asked, runs the IAM linter when given--assets, and exits 1 when no requested scanner could run.cloudg collectuses the credentials and regions in config.yaml.cloudg map --sweepfollowsinventory.tagging_sweepunless the flag is given.- Prowler and ScoutSuite get the credentials cloudg was given: session token, the assumed
role_arn, web identity andaws.profilefrom the config, and real regions.AWS_DEFAULT_REGION=ALLis no longer set. Region discovery for--regions alluses the same credentials, and falls back to the 17 regions enabled by default in every account when discovery fails. cloudg runexits 3 when collection failed for every target, including a run with no credentials at all where every service fails on its own, and lists the targets that failed. Partial collection still exits 0, with a warning.--accountswithout--role-name(oraws.role_name) is an error instead of mapping the caller's own account once per listed ID.aws.role_arnis assumed first and the member role from there, instead of replacing the member role. An--outhat matches nothing is an error that lists the organization's OUs.- Scanner plugins registered under
cloudg.scannersrun fromscanners.enabledand--scanners, and unknown scanner names produce a warning. The IAM linter followsscanners.enabledand--scannerstoo. CloudGEngine.run_pipeline()passesaws.profileto the scanners,PipelineResult.errorslists collection, scanner and analysis failures as well,on_findingfires as each scanner finishes and receives copies normalisation does not change, and the*_syncwrappers raise a clear error inside a running event loop.CloudGEngine.scan()falls back to a Trivy filesystem scan when no images are set, like the CLI.- Prowler compliance control IDs name the check (
CIS/iam_root_hardware_mfa_enabled) instead of including the account ID, so one check across several accounts is one control. When findings from two scanners are merged, both scanners' check-ID mappings are kept. - Compliance results are no longer all FAIL. A control fails when a finding maps to it and passes when only Prowler passing checks (ASFF
PASSED, OCSFPASS) map to it; controls nothing assessed are left out.FindingsNormaliser.normalise()takespassed_checks=, and the Prowler parser returns them on the list it gives back. - Prowler OCSF output (
*.ocsf.json, the Prowler 4 default) is parsed: status, severity, resource, check ID, compliance and muted findings. Before, every record became an "Unknown Prowler Finding", passing checks included. The MCP loaders accept OCSF instead of refusing it. ScoutSuite documentation URLs no longer show up as compliance frameworks. build_asset_map()andbuild_compliance_map()match findings to assets the way the ontology and RAG exports do, so a finding no longer attaches to every asset that shares its name.CloudGEngine.map_inventory(findings=...)applies the overlay withoutoutput_dirtoo (result.asset_map,result.compliance_map).- GraphML export (
cloudg run,InventoryResult.export(), the MCP export) no longer fails on assets without an ARN.RelationshipLinker.link()can be called more than once.inventory-map.jsonsaves the collection coverage records andInventoryResult.load()restores them. - Each ontology format gets its own file (
.ttl,.jsonld,.rdf,.nt). N-Triples no longer overwrite the Turtle file, and spellings such asjsonldno longer land inontology.ttl.AnalysisResult.rag_chunk_countis filled in. CloudOntology.query_asset_neighbourhood()works withhopsabove 1 and with IDs containing:or/, andquery()returns None for unbound variables.NACL_RULEedges get the same ontology relations asSECURITY_GROUP_RULEedges.ReachabilityAnalyzer.compute_blast_radius()stops at the internet placeholder instead of following an egress rule out to everything the internet reaches.- findings.json metadata timestamps are ISO 8601, and an unset
completed_atisnullrather than"None".
Changed¶
cloudg reportrefuses to write findings.json over its own input; pass-oto write elsewhere, or--overwrite. findings.json now has anedgeslist.cloudg scan --scannersdefaults toscanners.enabledinstead ofprowler,checkov, andcloudg scan -paccepts only aws, azure and gcp.- The Terraform recreation goes to
<output>/terraform, following-o, unlessterraform.output_diris set to another path. The old default./reports/terraformcounts as unset. cloudg runwritestopology-cytoscape.jsononly withgraph.export_cytoscape: true, and skipstopology.graphmlwithgraph.persist_graphml: false.run_pipeline()writes the formats inreport.formatsand now includestopology.svg.- RAG chunks are sized by
rag.max_chunk_tokens(about 4 characters per token) instead of fixed caps of 30 relations, 10 findings and 50 triples. Long lists end with an "... and N more" line, and community chunks are bounded too. - report.html is about 485 KB larger with the libraries embedded.
query_asset_neighbourhood()rows for more than one hop are{neighbour, neighbourName, hops}. D3 graph JSON has"arn": ""instead ofnullfor assets without an ARN.cloudg.mcp.state.check_prowler_inputwas removed.ProwlerScannertakes its newer settings (aws_session_token,aws_regions,timeout_seconds,env) and the access keys as keyword arguments; calls that pass them by keyword work as before.aws_regionis now the fifth positional argument.- Parts of three large modules moved into their own: the engine result types and collection checks to
cloudg.api_results, the rate-limit models tocloudg.config_ratelimit, and the RAG chunk text builders tocloudg.graph.rag_text. Everything public is still importable fromcloudg.api,cloudg.configandcloudg.graph.rag_export.
Documentation¶
- The handbook at morpheuslord.github.io/cloudg is now a site of separate pages instead of one long
docs/index.html. Guides, the CLI, the Python API, the MCP server, the reference documents and this changelog each have their own tab and sidebar.site/build.pybuilds it: the CLI options tables come from the click definitions, the API pages from the docstrings, the config pages fromconfig.yamland theCloudGConfigmodels, and the longdocs/*.mdreferences are split into one page per section. Writing rules for new pages are insite/AUTHORING.md. - New guides cover installation, the pipeline phase by phase, authentication for each provider, AWS Organizations, scanners and ingest input formats, compliance mapping, reports, the graph, ontology and RAG exports, rate limits, Docker, CI and Python recipes. Code samples are complete scripts, and processes are drawn as diagrams.
- Search (Ctrl K or /), a dark theme, linked code tabs and copy buttons work without a server. Links to the old
index.html#anchorsections redirect to the new pages.
0.6.0¶
Released 2026-10-09
Two additions and a round of graph fixes. cloudg is now a Model Context Protocol server, so AI agents can query an inventory map, its graph, findings, compliance and ontology under an access and privacy policy. Every cloud API call now goes through a shared rate limiter that slows down when a provider throttles, so an organization-wide map (or an agent asking for one too often) no longer fails or hammers the APIs.
The release also changes what the graph analysis reports for the same input. Reachability follows network-flow edges only, matches ports as numbers and gives findings stable ids; the ontology emits different triples; GraphBuilder merges parallel rule edges; the inventory mapper keeps parallel rules when it deduplicates assets; cloudg.retry.with_retry retries a different set of errors. The modules concerned are public API: cloudg.graph.ports (new), cloudg.graph.builder, cloudg.graph.reachability, cloudg.graph.ontology_rules, the ontology and RAG exporters (cloudg.graph.ontology, cloudg.graph.rag_export), cloudg.inventory.dependencies, cloudg.inventory.mapper and cloudg.retry. Embedders that store reachability findings, count graph edges or query the ontology should read Changed and Fixed below.
Added¶
- The MCP layer,
cloudg.mcp, built around one class,CloudGMCPLayer. Its default catalog has 74 tools in ten categories (graph 17, inventory 11, findings 10, workspace 8, ontology 6, compliance 5, live 5, privacy 5, meta 4, export 3), 13 resources, 11 resource templates and 11 prompts (security_posture_review,attack_surface_report,blast_radius_assessment,compliance_gap_analysis,incident_triageand six more). The CLI also listscloudg://metrics. Tools return structured content, resource links and progress notifications; lists are paginated and arguments complete. A list cursor belongs to the query and dataset version that produced it: once the dataset is reloaded or replaced, the old cursor is refused with an invalid-arguments error instead of returning the wrong page. - Graph analysis in the MCP catalog uses the same rules as the library. Attack paths and
find_pathsfollow the reachability network-flow hops plus explicit identity pivots (role assumption, IAM trust, grants);internet_exposureand the edge search read Azure*ports andInternet/Any/*sources, GCP all-port rules and AWS ICMP rules, and skip egress rules;find_paths(source="internet")works on an Azure-only dataset. Findings and asset references resolve throughAssetIndex, so an ambiguous name is never matched to the first asset that has it, andreachability_findingsreturns the analyser's deterministic ids. - Tool errors do not repeat the caller's value or list close matches in their message. Both are in the error data instead (
data.value,data.suggestions), where the privacy policy can transform them. - A dependency-free native server (
cloudg.mcp.native): stdio, streamable HTTP and the deprecated SSE endpoints, bearer tokens mapped to roles,OriginandHostchecks against loopback by default, CORS,/healthz. It serves both protocol eras on the same object: the handshake versions 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25, and the stateless 2026-07-28 (server/discover,subscriptions/listen).HTTPConfigcaps sessions per principal (max_sessions_per_principal, 100; at the cap the principal's least recently used idle session is closed) and open connections (max_connections, 512), and gives a client 30 seconds to send its headers (header_timeout) and 120 seconds for the body (body_timeout). - Flavors and adapters:
--flavor native,sdk(the official SDK's low-level server),fastmcporauto.register_into(server)mounts the whole catalog into an mcp 2.xMCPServer, an mcp 1.xFastMCP, a low-levelServeror afastmcp.FastMCP(2.x, 3.x, 4.x) next to the host's own tools;build_server()andexport_definitions()cover the other cases. Verified with mcp 1.30.0 and 2.3.0 and fastmcp 2.14.7, 3.4.8 and 4.0.11. On the SDK and fastmcp HTTP transports a session id is bound to the principal that opened it. - Options objects for embedders:
LayerOptions(CloudGMCPLayer(options=...)) andServeOptions(serve_async(options=...)). The existing keyword arguments still work; an unknown keyword raisesTypeError. - A tool handler can take a single pydantic arguments model,
def tool(ctx, args: Model), instead of one parameter per argument. The input schema and the validation are the same as for the flat form. - Content
_metakeyscloudg/dataandcloudg/render: a resource or prompt that renders structured data as text passes the data itself, so the privacy transforms see it with its keys before it is rendered. - Policies: seven built-in profiles (
open,standard(default),strict,read_only,airgapped,audit,soc-analyst) plus policy files that extend each other, with per-role rules, sensitivity ceilings, capability denials and per-principal rate limits. Hidden primitives look the same as nonexistent ones. The textual graph and ontology exports (cloudg://graph/{format},cloudg://ontology/{format},cloudg://ontology/turtle),sparql_queryandsubgraph_exportare RESTRICTED, because the privacy layer cannot see inside one opaque string; a profile with aconfidentialceiling hides them (strict, andsoc-analystfor every role exceptlead). - Transforms, composed per principal into input and output pipelines:
redact,sanitize(fences attacker-controllable text),project,annotate,substitute,alias,regex_replace,rename_keys,template,guard_secrets(refuses secrets in arguments) anddepseudonymize, with built-in detectors for secrets and tokens, private keys, cloud credential ids, ARNs and resource ids, account ids, IP addresses, hostnames and e-mail addresses. TokenVault: format-preserving pseudonyms derived with HMAC-SHA256, stable across restarts withCLOUDG_MCP_VAULT_KEY, and saved when the server shuts down when the policy setsvault.path. A pseudonym an agent passes back as an argument is reversed before the tool runs;reveal_tokenreverses pseudonyms and the aliases introduced bysubstituteandaliastransforms.- Middleware:
AuditLogMiddleware(JSONL, argument values hashed),MetricsMiddleware(cloudg://metrics),CachingMiddleware,ConcurrencyLimitMiddleware,RetryMiddleware, and your ownasync (info, call_next)callables. - CLI:
cloudg mcp serve | tools | resources | prompts | call | read | config, also installed as thecloudg-mcpconsole script and runnable aspython -m cloudg.mcp.cloudg mcp configprints client configuration for Claude Desktop, Claude Code, Cursor and VS Code. Undercloudg mcp, the banner and UI output go to stderr so stdout stays a clean protocol channel. - The MCP workspace reads only files under its allowed roots. With none configured (
allowed_rootsorCLOUDG_MCP_ALLOWED_ROOTS) they are the working directory and the report directory, and either one is left out when it is/or the home directory. A load error never quotes the file's contents, and the search for report files in a directory is bounded. SPARQL queries stop at the row limit or after 60 seconds, and a slow analysis build on one dataset no longer blocks the others or the workspace status. - Live tools (
map_inventory,collect_assets,run_scanners,run_pipeline) check the configured credentials before any cloud call (preflight, on by default, at most 25 seconds) and run under a live operation guard: identical concurrent calls share one run, a scope collected in the last 120 seconds is refused withretry_after_secondsand a loaded live dataset that covers the same providers, and live operations are capped at one per scope and two overall. A live operation that runs longer thanratelimit.live_operation_timeout_seconds(default 3600) is cancelled, raisesLiveOperationTimeoutand frees its scopes.force=trueskips the cooldown for theadminandoperatorroles. Each live tool reports the throttling of its own run.run_scannersrefuses container image names that start with-. The newrate_limit_statustool andcloudg://ratelimitresource show the guard and throttling state without calling a cloud API. cloudg.resilience: provider-aware error classification (classify,classify_strictfor codes, classes and statuses without message text,is_provider_answer,retry_after,describe_error), adaptive token buckets per provider, account, region and service (per action for EC2) that halve on throttling and recover additively, bulkheads, circuit breakers at the granularity of the rate bucket (per action for EC2, per service elsewhere),call_with_resilience/call_with_resilience_sync/@resilientwith decorrelated-jitter backoff that never undercuts Retry-After, a per-call deadline and a per-provider retry budget, per-run telemetry (stats_scope,ResilienceStats), the process-wide governor (get_governor,configure,reset_governor) andLiveOperationGuardwith itsGuardSettings. The first two retries of every call do not draw on the shared retry budget, so one busy run cannot starve every other call of its retries. A half-open breaker lets one probe through and takes the probe back after one cooldown if it never reports; a provider's non-retryable answer (anAccessDenied, a 404) counts as proof that the service is reachable. Calls refused by an open circuit, and calls given up because the retry budget ran out, do not count as breaker failures. Reconfiguring the limits keeps the breakers, budgets and held concurrency slots of providers whose settings did not change.- Provider wiring, with rate limits, concurrency caps, breakers and the shared retry budget enforced in every SDK: botocore event hooks on every AWS session cloudg creates (the collectors' aioboto3 sessions, the base and assumed-role sessions behind STS, Organizations and Control Tower discovery and the organization fan-out); an explicit azure-core
RetryPolicyand a per-attempt policy on every Azure client, which also reads thex-ms-ratelimit-remaining-subscription-readsheader (pausing the whole subscription) and Resource Graph'sx-ms-user-quota-*headers; Resource Graph paging that honours Retry-After and quota resets, without retrying a page again on top of azure-core's own 429 retries; a google-api-coreRetrycapped per page, and per-page pacing, for Cloud Asset Inventory. An instrumented AWS client that refuses a call because a circuit is open or the retry budget is spent raises an error that is also a botocoreClientErrorwith codeThrottling, so existingexcept ClientErrorhandlers catch it. A cancelled aioboto3 call frees its concurrency slot at once. ratelimitconfig section (RateLimitConfig,ProviderRateLimitConfig,ServiceRateLimitConfig): master and per-provider switches, rates, bursts, account and provider-wide limits, concurrency, adaptive floor, breaker threshold and cooldown, retries, backoff cap, deadline, retry budget, per-service and per-operation overrides, and thelive_*settings of the guard. Unknown keys are logged and ignored.config.yamlships the section with comments.InventoryResult.throttling: the run's throttling summary (totals, per-scope counters, messages, skipped scopes, tripped breakers, slowed buckets), orNonewhen no API pushed back. It is added tosummaryandinventory-map.jsononly when present, andload()reads it back.MultiAccountCollector.resilience_statsholds the last run's stats.cloudg.graph.ports: the port and edge rules that the graph builder, the reachability analysis and the ontology share.parse_port_ranges(),edge_port_ranges()andport_in_ranges()turnport_rangestrings into numeric ranges;is_internet_source()is the single check for a source that stands for the internet (0.0.0.0/0,::/0, and the Azure tagsInternet,Anyand*in any letter case).- Network-flow traversal as public API:
ReachabilityAnalyzer.flow_hops(),flow_successors()andinternet_entry_points(), andcloudg.graph.reachability.network_flow_graph(), so other code walks the network the way the exposure analysis does.finding_id()andasset_key()give the id scheme of reachability findings. cloudg.inventory.dependencies.AssetIndex, the one asset matcher: asset id, ARN or alias, unique name, unique ARN tail, plus two opt-in tiers (arn_partsfor ARN resource parts such asdb:orders,casefoldfor names in any letter case).AssetIndex.add()takes extraaliases.DependencyGraph.find, the ontology and the RAG exporter use it.RelationType.CONTAINS, a generic containment relation in theCONTAINMENTgroup, for 64 relation types in all.
Changed¶
aws.max_retriesandaws.retry_modeare honoured. Earlier releases accepted them and then hard-coded adaptive mode with 10 retries, so the defaults behave as before. botocore countsmax_retriesafter the first attempt: the default of 10 allows up to 11 attempts per call.- Every cloud API call is paced by default at or below the provider's published limits: AWS 20 rps per service and per EC2 action, Route 53 5, tagging API 5, Azure 20 rps per subscription and 3 rps per user for Resource Graph, GCP 1.5 rps for
ListAssets. Per-item fan-out is held to those rates, so maps of AMI-heavy or bucket-heavy accounts can take longer than in 0.5.x.ratelimit.enabled: falseturns the pacing off. - A service that stays throttled after its retries is recorded in coverage as FAILED or PARTIAL with an error starting
throttled:instead of failing the run. AWS collectors that swallowed per-item errors used to report SUCCESS with missing assets when they were throttled; they now report PARTIAL, telling calls that gave up after retries apart from calls an open circuit skipped. cloudg.retry.with_retrykeeps its signature and still retries everything it retried in 0.5.x. It now also retries provider throttling identified by error code, exception class or HTTP status (Azure 429s, GCPResourceExhausted), and never sleeps less than the server's Retry-After, capped at itsmax_delay. It does not retry cloudg's own resilience errors (an open circuit, a spent retry budget, a missed deadline), a plain HTTP 500 or an error whose message only mentions rate limiting.cloudg.retryre-exports the maincloudg.resiliencenames.- New optional extra
mcp(mcp>=1.30,<3).allnow includes it, and so doesdev. The core layer and its native server need no extra. - The wheel ships
docs/DOCUMENTATION.mdanddocs/INVENTORY_REFERENCE.mdascloudg/_docs/, so the MCPcloudg://docs/{topic}resources work from a plainpip install. The sdist includes everydocs/*.md. ReachabilityAnalyzer.find_internet_exposed()walks network-flow edges only, sogenerate_findings()no longer reports resources that are reachable only through an IAM grant. Before, the walk followed every edge type out of an internet-exposed node, includingGRANTS_ACCESS,ASSUMES_ROLE,IAM_TRUSTandIAM_POLICY_ATTACHMENT, and produced findings such as a CRITICAL "Internet-exposed RDS_INSTANCE" for a database whose only link to the internet was a role granted access to it. The rules, documented in the module docstring:INTERNET_EXPOSED,LOAD_BALANCER_TARGET,ROUTEandPEERINGare followed source to target.SECURITY_GROUP_RULEandNACL_RULEare followed source to target, except egress rules (direction: "egress").ATTACHED_TOis followed backwards when the target is a security group, NSG or NACL, or the edge declaresPROTECTED_BY_SG/PROTECTED_BY_NACL(traffic a group admits reaches the resources attached to it), and forwards when the source is aNETWORK_INTERFACEorELASTIC_IP. Other attachments are not followed.CONTAINSis followed only from aVPC,VNETorSUBNET. Organization, account, cluster and namespace containment is not.INVOKES,REFERENCES,USES_IMAGE,LOGS_TO,PROTECTS,MONITORS,MANAGESandGOVERNSare not followed. A Lambda function behind a public API Gateway (anINVOKESedge) is therefore no longer reported as internet-exposed by this analysis.compute_blast_radius()is unchanged and still follows every edge type.
- Internet entry points are nodes named
0.0.0.0/0or::/0and the source of any non-egress edge whose CIDR is0.0.0.0/0,::/0or an Azure service tag for the internet (Internet,Any,*). Before, every external placeholder node counted as an entry point, including private CIDRs such as10.0.0.0/8and unresolved references, and a security group with an egress rule to0.0.0.0/0became an entry point itself. - Security groups, NSGs, NACLs and target groups are no longer reported as "unexpected internet-exposed resources". The walk still passes through them and marks them
is_internet_exposed; a group's open rules are reported by the sensitive-port findings, and a target group's exposure by the findings on the targets behind it. - Reachability finding ids are deterministic: a UUID5 hash of the rule and a stable asset key (
cloudg.graph.reachability.finding_id(rule, asset_key)) instead of a randomuuid4. The key is the asset's ARN, or its asset id when it has no ARN, because collectors give assets a fresh random id on every collection. Exposure findings key on the asset, open-port findings on the keys of the rule's two endpoints and the port, so the same exposure gets the same id on every scan.generate_findings()also returns exposure findings in a stable order. - Ontology:
CONTAINSedges get a relation only when both endpoint types match it:VPC_CONTAINS_SUBNET(VPC or VNet to subnet),SUBNET_CONTAINS_INSTANCE(subnet to a resource placed in it, not a route table, NACL, gateway or other network construct),CLUSTER_CONTAINS_SERVICE(cluster or Kubernetes namespace to a workload, service, namespace, node group or add-on) andORG_CONTAINS_ACCOUNT(organization or OU to account). Every other containment (organization to OU, account to VPC, VNet to VM, resource group to resource, unresolved endpoints) uses the new genericRelationType.CONTAINS. Before, these fell back toVPC_CONTAINS_SUBNETand produced triples such assample-org VPC_CONTAINS_SUBNET Workloads. - Ontology: a relationship declared on an edge (
NetworkEdge.relationship) replaces the inferred relation onCONTAINS,ATTACHED_TOand the typed inventory edges (INVOKES,USES_IMAGE,ASSUMES_ROLE,GRANTS_ACCESS,LOGS_TO,PROTECTS,MONITORS,MANAGES,GOVERNS,REFERENCES). OnSECURITY_GROUP_RULE,IAM_TRUST,IAM_POLICY_ATTACHMENT,LOAD_BALANCER_TARGET,ROUTE,PEERINGandINTERNET_EXPOSEDedges the declared relation comes first and the inferred ones are added after it. - Ontology:
SECURITY_GROUP_RULEandNACL_RULEedges no longer yieldPROTECTED_BY_SG/PROTECTED_BY_NACL. Those edges point from the traffic source to the group, so they produced triples such as0.0.0.0/0 PROTECTED_BY_SG sg-web. The protection relations now come fromATTACHED_TOedges whose target is a security group or NSG (PROTECTED_BY_SG) or a NACL (PROTECTED_BY_NACL), so the triple readsweb-alb PROTECTED_BY_SG sg-web. OtherATTACHED_TOedges without a declared relationship stayDEPENDS_ON. - Ontology: the port relations of a security group rule are read from the parsed port ranges, as the sensitive-port findings are. A rule open on every port (
0-65535, protocolALL/-1/*, or an Azure*rule forTcp) isALL_TRAFFIC; otherwise each of ports 22, 80, 443 and 3389 inside the ranges givesONLY_SSH,ONLY_HTTP,ONLY_HTTPSorONLY_RDP(an AWS rule for1-1024gives the first three), and a rule that opens none of them isPORT_RESTRICTED. A rule from an AzureInternet,Anyor*source isINTERNET_REACHABLE, like one from0.0.0.0/0. - Ontology:
ENCRYPTED_BY_KMSnow links an encrypted asset to its key:orders-db ENCRYPTED_BY_KMS <key>. Before,infer_relations()added it to every edge whose target hadencryptionorstorage_encryptedplus akms_key_idin its metadata, soCloudOntologywrote triples with the edge's source as the subject and the encrypted asset as the object, such asec2-1 ENCRYPTED_BY_KMS rds-1orsubnet-private ENCRYPTED_BY_KMS orders-db. The relation now comes frominfer_asset_relations()for the encrypted asset itself. Itskms_key_idis matched against the collectedKMS_KEYassets by asset id, ARN, bare key id, alias name or alias ARN. The keys are indexed once per ontology build instead of being scanned for every encrypted asset. If no collected key matches, the object is the rawkms_key_idstring.infer_relations()no longer returnsENCRYPTED_BY_KMSunless the edge declares it as itsrelationship(the linker'sREFERENCESedges still do). - Ontology: containment inferred from
vpc_idmetadata points from the VPC to its members. Before,infer_asset_relations()returned(VPC_CONTAINS_SUBNET, vpc)for a subnet and(SUBNET_CONTAINS_INSTANCE, vpc)for anything else in the VPC, which gavesubnet VPC_CONTAINS_SUBNET vpcandinstance SUBNET_CONTAINS_INSTANCE vpc: the wrong direction, and a subnet relation whose object was a VPC. Now a VPC or VNet asset returns(VPC_CONTAINS_SUBNET, subnet)for each subnet and(CONTAINS, member)for every other asset with the samevpc_id, and member assets return no containment. The return shape ofinfer_asset_relations()is unchanged: the asset passed in is still the subject of every tuple. If several VPC or VNet assets share avpc_id, the first one in the list claims the members.CloudOntology.query_asset_neighbourhood()withhops > 1follows these relations forward, so a walk from a VPC now reaches its members, and a walk from an instance no longer reaches the VPC through them. - RAG export: relation-group chunks include the asset-level relations (
ENCRYPTED_BY_KMS,vpc_idcontainment, tag governance, key rotation, EC2 security group membership) next to the edge relations. The per-edgeENCRYPTED_BY_KMSentries described above are gone. GraphBuildermerges parallel rule edges (see Fixed), sograph.number_of_edges()and the edge lists into_d3_json(),to_cytoscape_json()and GraphML exports shrink by the number of merged rules, and a merged edge'sport_range,protocolanddescriptioncan hold comma- or semicolon-separated lists.
Fixed¶
ReachabilityAnalyzerdecided whether a sensitive port was open with a substring test on the edge'sport_rangestring. A rule open on0-65535, which is how the AWS collector writes protocol-1(all traffic), produced no SSH or RDP finding, and neither did an Azure NSG rule with*or a comma list such as443,3380-3390. The same test reported port 22 for2200-2300and port 3389 for33890. The string is now parsed into numeric ranges bycloudg.graph.portsand each of the 11 ports inSENSITIVE_PORTSis tested against those ranges. The module readsa-b, single ports, comma lists and*. An emptyport_rangeon aSECURITY_GROUP_RULEorNACL_RULEedge means every port when the protocol isALL/-1/*/Any, or TCP, UDP or SCTP (a GCP firewall protocol with no ports listed). On other edge types an empty string still opens nothing, so a GCPINTERNET_EXPOSEDedge for a public bucket gets no port findings. ICMP, ICMPv6, ESP, AH, GRE and IPIP rules open no ports; for ICMP the numbers AWS stores in the port fields are a type and code. Expect more open-port findings on estates with all-traffic or wide-range ingress rules from the internet, and fewer on rules whose range only looked like a sensitive port. An edge still yields at most one finding per port, however many of its ranges overlap that port. The analyzer also reads aportslist from a graph edge when one is present, as before;GraphBuilderstill does not copyNetworkEdge.portsonto the graph, since the collectors cap that list at 100 entries and GraphML export cannot store lists.- Egress rules (
direction: "egress") no longer produce open-port findings. They point from a group to0.0.0.0/0and admit nothing inbound. Before this change they were checked like ingress rules and only escaped because the default AWS egress string0-65535contains none of the sensitive port numbers as a substring; with numeric matching every security group's default egress rule would otherwise report all 11 ports. - Azure NSG rules whose source is the
Internet,Anyor*service tag now produce open-port findings, and the finding title names the tag. Before, they marked resources internet-exposed but no port finding was ever raised for them. GraphBuilderlost rules that shared a source and target. The graph is annx.DiGraph, which holds one edge per pair, so the secondNetworkEdgefor a pair overwrote the first one's attributes. A security group with ingress rules from0.0.0.0/0for port 22 and port 443 ended up with a single edge for 443, andReachabilityAnalyzernever reported SSH open to the internet. An ICMP rule from0.0.0.0/0listed after the SSH rule hid it the same way. AWS security groups, Azure NSG rules and GCP firewall rules all produce such pairs.SECURITY_GROUP_RULE,NACL_RULEandINTERNET_EXPOSEDedges on the same pair are now merged into one graph edge when their CIDR and direction match:port_rangelists the ports of every merged rule, comma-separated and without repeats: rules for 22 and 443 give"22,443".- Rules for different protocols are merged too, and
protocollists each one once ("TCP,ICMP"). ICMP, ESP, AH, GRE and IPIP rules add no ports, because the numbers AWS stores for ICMP are a type and code, and Azure writes"*"for an ICMP rule. A TCP, UDP or all-protocol filter rule with no ports (GCP writes these) adds0-65535. With mixed protocols the ports are a union: the edge shows which ports are open from the source, not which protocol each port is open for. Sensitive-port findings never looked at the protocol, so they come out the same as if each rule had its own edge. descriptionjoins the rules' descriptions with"; ".- Rules whose CIDR or direction differ, and edges of any other type, still replace the earlier edge on the pair.
- The inventory mapper's deduplication dropped parallel security group, NACL and internet-exposure rules from
inventory-map.jsonwhenever it merged a duplicate asset. Rules that differ in ports, protocol, CIDR or direction are now kept. - The ontology and RAG exporters attached findings to assets by raw
Finding.resource_idonly. Findings whoseresource_idis an ARN or a display name, which is common in scanner output, left a dangling resource node (for examplecmr:arn:aws:rds:...) with noFINDING_AFFECTSedge to the real asset, and RAG entity and community chunks reported afinding_countof 0 for it. Both exporters now resolve a finding's asset withAssetIndex: by id, then ARN (fromresource_idorresource_arn), then unique name, then unique ARN tail, falling back to the rawresource_idonly when nothing matches. Ambiguous names never match.DependencyGraph.findbehaves as before apart from building its lookup tables once instead of scanning every asset on each call, and an empty reference now returnsNone. - AWS
SECURITY_GROUP_RULEedges fromcollect_edges()now use the security group asset'sidas the security group endpoint instead of the nativesg-...ID, as Azure NSG rule edges already did. The graph exports no longer contain a disconnectedEXTERNALnode per security group, and sensitive-port findings carry the security group's asset ID and ARN. The internet-exposure walk now continues from an open security group to the resources attached to it, so an instance behind a group that admits0.0.0.0/0is reported as internet-exposed; before, the walk stopped at the native-ID placeholder. The CIDR endpoint is unchanged. - The multi-account collector called STS (
AssumeRole,GetCallerIdentity) synchronously on the event loop, which stalled every other collection while it waited. Those calls now run in a worker thread.
Security¶
- The MCP layer never echoes a real value that it restored from a pseudonym in the arguments. It records each restored value with its pseudonym, and error messages and error data, results, resource links, progress messages and log notifications are pseudonymised again with those pairs before they leave, for tools, resources, prompts and completions alike. Text payloads (JSON strings, GraphML, N-Triples) are pseudonymised consistently with structured results, using the values the vault already knows (
TokenVault.replace_known_values). - Progress messages, log notifications, resource-link names, titles and descriptions, and the URIs of embedded resources go through the caller's privacy policy.
- An unexpected exception in a resource or prompt handler reaches the client as a generic error on every flavor, never as the raw exception text.
- Percent-encoded and templated resource URIs are put in canonical form before the policy's allow and deny rules are checked, so
cloudg://graph/%64%33is treated ascloudg://graph/d3. - DNS rebinding: on a non-loopback bind without
--allowed-host, the native HTTP server no longer trusts anOriginjust because it equals theHostheader. Same-origin requests are accepted only when theHostheader is validated. cloudg mcp serverefuses an empty--auth-tokenorCLOUDG_MCP_AUTH_TOKENS.--registryaccepts only amodule:attributedotted name that resolves to aRegistry; it loads operator code and must never come from an untrusted source.- Session caps, connection caps and read timeouts on the native HTTP server, and session ids bound to the principal on the SDK and fastmcp HTTP transports (see Added).
- Policy allow and deny lists and rules also match the concrete resource URI that was requested, both as sent and percent-decoded.
preview_transformpseudonymises with a separate vault under a random key (Policy.preview_vault()), so a preview cannot confirm which real value is behind a pseudonym. Thestrictprofile hides the tool.privacy_audit_logreturns only the caller's own entries unless the caller has theadminorprivacy-adminrole, and its result says which (scope:ownorall).- Audit log lines escape CR, LF and other control characters, and a probe for an unknown resource is recorded with a keyed hash of the URI's variable part instead of the value.
- An invalid policy is reported without echoing the values in it, so a misplaced vault key does not end up in an error message.
- Vault files are written atomically with fsync, with mode 0600 in directories created with mode 0700.
- Regular expressions that could backtrack for a long time on hostile input were replaced with linear patterns or small parsers: the vault's token scanner, the detectors for GCP resource names, e-mail addresses, hostnames and AWS secret access keys, the prompt-injection pattern, the RAG member and triple parsers, fence stripping, the
explain_*metadata helpers and the literal stripping insparql_query(a 20,000-character query used to take 23 seconds there; 200,000 characters now take 0.06). - Over HTTP with OAuth, the scopes of the access token become the principal's roles as they are, so a token with the scope
adminorprivacy-admingets what the policy grants those roles, reveal included. Issue such scopes only to people who may see real identifiers, or pass aprincipal_resolverthat maps scopes onto roles.
Notes¶
- Existing imports, signatures and config files keep working, and a config without
ratelimitgets the built-in limits. The release is not purely additive, though: the reachability, ontology, graph builder, mapper andwith_retrychanges under Changed and Fixed alter results for the same input. Reachability finding ids, open-port findings, ontology triples, graph edge counts and the rules kept ininventory-map.jsonall differ from 0.5.x. - Several large modules were split into packages or sibling modules (
cloudg.mcp.stateandcloudg.mcp.policyare packages, the transforms are spread over smaller modules,cloudg.apimoved its scanner runs tocloudg.api_scanners, the linker rules moved tocloudg.inventory.linker_rules), and long functions across the CLI, the collectors, the resilience layer and the MCP server were split up. The documented import paths, public signatures and CLI options are unchanged. The GCP default skip list is nowcloudg.config.GCP_DEFAULT_SKIP_ASSET_TYPES;cloudg.collectors.gcp.DEFAULT_SKIP_ASSET_TYPESstays as an alias. - The
sanitizetransform is about three times faster on large results, and the redactor no longer scans the same value twice. - The
mcpextra allowsmcp>=1.30,<3, verified with mcp 1.30.0 and 2.3.0. Mounting into an SDKMCPServer(mcp 2.x) orFastMCP(mcp 1.x) goes through private SDK attributes that a future release can rename, so applications using that path should pin the minor version they tested, for examplemcp>=2.3,<2.4. Mounting into the low-levelServerand the native server are unaffected. - Sessions on the legacy SSE transport of the SDK and fastmcp flavors are not bound to a principal.
Documentation¶
- MCP guide: installation, client setup, CLI, transports and protocol versions, flavors, mounting, in-process use, workspace, errors, middleware, live tools, troubleshooting.
- MCP tool reference: every tool, resource, template and prompt.
- MCP privacy: profiles, policy schema, transforms, detectors, the token vault.
- MCP internals: package map, core contracts, the layer, the adapters, the native server.
- Rate limits and throttling: architecture, provider wiring, built-in limits with sources, every
ratelimitkey, a throttled run's output, the Python API, tuning, limitations. - The feature reference and the handbook have new MCP server and rate limit chapters, the
ratelimitconfiguration and the new Python API. The inventory reference, catalog and internals describe merged rule edges, the port and protocol formats of each provider and the new reachability rules.
0.5.3¶
Released 2026-10-04
A security fix release for CodeQL's "incomplete URL substring sanitization" alerts (py/incomplete-url-substring-sanitization).
Security¶
- The GCP relation extractors decided a resource's API service by testing whether a string started with a host-like literal. The affected checks were the CMEK scan skip for
cloudkms.googleapis.com/asset types, service-account detection on//iam.googleapis.com/names, and the//sqladmin.googleapis.com/and//container.googleapis.com/name normalisation. They now parse the service out of the name with anchored regular expressions, through the new helpersapi_service()andasset_type_service()incloudg.inventory.gcp_relations.names. Behaviour is unchanged; the new code was checked against the previous implementation on about 92,000 generated inputs with no differences. - Test assertions that checked a host-like literal against a list with
innow use set comparison, so the scanner no longer reads them as URL checks.
0.5.2¶
Released 2026-10-04
A documentation release: the inventory mapper's return structures, asset types and internals are now documented in full. No code changes.
Documentation¶
- Inventory reference: every return structure and exported file of the inventory mapper, field by field, with annotated examples.
- Inventory catalog: all 156 asset types with the native types mapped to each, their metadata keys, declared relations and the relationship matrix.
- Inventory internals: architecture, code map, the collector task registry, linker resolution rules, Azure and GCP extractor frameworks, dependency semantics, recipes for extending the mapper, security rules and testing.
- The feature reference and handbook describe the full
AssetTypeandEdgeTypesets and the new models' fields. - The handbook changelog lists 0.5.0 and 0.5.1 as separate releases.
0.5.1¶
Released 2026-10-04
A much deeper inventory map. cloudg map now follows workloads into containers and Kubernetes, through serverless and event wiring, across the IAM graph and the security services watching it all, and across every account of an AWS Organization / Control Tower landing zone. Edges say what they mean, and cloudg deps answers what an asset needs and what breaks with it.
Added¶
- Deep AWS service collectors in
cloudg.inventory.aws_services, composed intoAWSDeepInventoryCollectorand grouped into service families selectable with--services/--exclude-services(inventory.services,inventory.exclude_services):- containers: ECR repositories (image sample, scan findings summary, registry scan mode, repository policy grants), ECS services and task definitions (images, task and execution roles, secrets, log groups, target groups, subnets), EKS clusters, nodegroups, Fargate profiles, addons, access entries and pod identity associations;
- Kubernetes: namespaces, Deployments, StatefulSets, DaemonSets, CronJobs, Services, Ingresses and ServiceAccounts read from inside EKS clusters with a read-only EKS token (
cloudg.inventory.kubernetes, no new dependency;--kubernetes/--no-kubernetes); - serverless and integration: Lambda event source mappings, resource-policy invokers, function URLs, container images, layers, DLQs and EFS mounts; API Gateway REST and HTTP APIs; SQS; SNS subscriptions; EventBridge buses, rules and targets; Step Functions; Kinesis;
- data, DNS and deployment: deep S3 (real region, notifications, replication, access logging, policy grants), ALB/NLB listeners, certificates and target groups, classic ELB, Auto Scaling groups, launch templates, deep RDS and Aurora, EFS, ElastiCache, OpenSearch, Redshift, Route 53 records, CloudFormation stacks and the resources they manage, log groups, ACM in-use-by, VPC endpoints, flow logs, transit gateway attachments;
- security services and scanners: GuardDuty, Security Hub, Inspector (per-resource coverage), Macie, AWS Config, IAM Access Analyzer, Detective, WAFv2, Network Firewall, Shield Advanced, CloudTrail. Services that are not enabled appear as
enabled: falseplaceholders; - identity: the full IAM graph from
GetAccountAuthorizationDetails: trust policies (cross-account, OIDC / IRSA / GitHub), managed policy attachments, group membership, instance profiles, admin detection, and principal → resource grants.
- AWS Organizations and Control Tower:
cloudg map --org(aws.organizationconfig) discovers the OU tree, every member account, SCPs/RCPs and other org policies, delegated administrators and, when present, the Control Tower landing zone (version, drift, governed regions, log archive / audit / config / backup accounts), enabled controls and baselines, then maps every selected account by assuming--org-role(defaultAWSControlTowerExecution). Filters:--ou,--exclude-account; governed regions replace--regions all.cloudg.inventory.discover_organization()/OrganizationTopologyare public. - Typed relationships: new
EdgeTypevaluesINVOKES,USES_IMAGE,ASSUMES_ROLE,GRANTS_ACCESS,LOGS_TO,PROTECTS,MONITORS,MANAGES,GOVERNS;NetworkEdge.relationship(ontology relation name) andNetworkEdge.properties. Collectors declare relationships inmetadata["relations"], which the linker resolves. - Dependency analysis:
cloudg.inventory.DependencyGraph(depends_on,dependents,tree,shared_dependencies,blast_radius), cross-account edges and security coverage (gaps, workloads without vulnerability scanning, internet-facing endpoints without WAF);InventoryResult.dependency_graph(),analysis(),load();inventory-dependencies.jsonandinventory-organization.jsonexports; thecloudg depscommand. - 47 new
AssetTypevalues covering containers, Kubernetes, messaging, data, DNS, IaC, security services and organization structure, mapped to new OWL classes; richer Azure ARM and GCP Cloud Asset Inventory type maps. cloudg mapflags:--accounts,--role-name,--org,--org-role,--ou,--exclude-account,--ct-home-region,--services,--exclude-services,--kubernetes/--no-kubernetes.inventoryconfig:services,exclude_services,kubernetes,kubernetes_timeout,iam_resource_edges,max_images_per_repository,stack_resources,account_hierarchy.- Coverage expansion after a full audit of every AWS, Azure and GCP service against what cloudg enumerated:
- AWS breadth: a Cloud Control API sweep lists every resource type with a list handler, tagged or not (the tagging API never returns resources that were never tagged), skipping types a dedicated collector covers.
--cloud-control/--no-cloud-control,inventory.cloud_control*. - AWS depth, 133 collectors in all (
cloudg.inventory.aws_services.{governance,network_ext,application,data_ml}): IAM Identity Center (permission sets, assignments, users, groups, provisioned roles), RAM shares, Service Catalog / Account Factory, StackSets, SAML providers, access key metadata, Roles Anywhere, Cognito, deep KMS / Secrets Manager / DynamoDB, snapshot and AMI sharing, S3 access points and account Block Public Access; VPN, customer and virtual private gateways, transit gateway route tables and peering, PrivateLink services, prefix lists, egress-only gateways, API Gateway authorizers / VPC links / custom domains, deep CloudFront, Route 53 Resolver, Direct Connect, Global Accelerator, listener rules, VPC Lattice, Cloud WAN; CodePipeline / CodeBuild / CodeDeploy / CodeConnections, SSM, CloudWatch alarms, AWS Backup, ECS capacity providers and container instances, Cloud Map, Lambda aliases, Scheduler, Pipes, API destinations, archives, Firehose, log subscriptions and destinations, OAM, Batch, App Runner, Elastic Beanstalk; MSK, Amazon MQ, SageMaker, Bedrock, OpenSearch Serverless, Redshift Serverless, RDS proxies and global clusters, MemoryDB, DAX, Glue, Lake Formation, EMR, Athena, Transfer Family, DataSync, FSx, ECR Public. - Azure: collection through Azure Resource Graph with full properties (
azure-mgmt-resourcegraphadded to theazureextra; SDK fallback without it), typed relationships for managed identities, role assignments, private endpoints, AKS, App Service, Container Apps, networking, Key Vault, storage, SQL and Defender plans; every Enabled subscription; management groups, Azure Landing Zone archetypes and Azure Policy (cloudg.inventory.azure_hierarchy,azure.all_subscriptions,azure.map_management_groups). - GCP: collection through Cloud Asset Inventory
list_assetswith full resource JSON, org-wide whengcp.organization_idis set; typed relationships across compute, firewalls, load balancing, GKE / Workload Identity, Cloud Run, Functions, Pub/Sub, Eventarc, logging sinks, CMEK and Shared VPC (cloudg.inventory.gcp_relations); IAM bindings to service accounts and principal nodes; organization → folder → project hierarchy, organization policies and VPC Service Controls (cloudg.inventory.gcp_hierarchy); newgcpoptionscollection_scope,skip_asset_types,iam_policies,map_hierarchy,org_policies,vpc_service_controls. - 62 further
AssetTypevalues (identity federation, provisioning, hybrid networking, operations, data processing, ML), 156 in all.
- AWS breadth: a Cloud Control API sweep lists every resource type with a list handler, tagged or not (the tagging API never returns resources that were never tagged), skipping types a dedicated collector covers.
- Reference catalogs: type maps and skip lists moved out of code into YAML under
cloudg/inventory/catalogs/(aws_cloudcontrol,aws_arn_types,azure_arm_types,gcp_asset_types), validated at load and extensible through$CLOUDG_CATALOG_DIRoverlays.
Changed¶
- The relationship linker resolves identifiers with account/region scope, normalises image tags, Lambda qualifiers, S3 object ARNs,
:*log-group ARNs, assumed-role sessions and DNS names, createsCLOUD_ACCOUNTplaceholders (external: true) for references into unmapped accounts, and recordsunresolvedreferences. - Execution roles of Lambda functions and instance profiles are now
ASSUMES_ROLEedges (wereREFERENCES); security-group attachments carryrelationship: PROTECTED_BY_SG. - Every account gets a
CLOUD_ACCOUNTnode containing its top-level resources (inventory.account_hierarchy); the summary adds accounts, relationship counts, cross-account edges, external accounts, security service gaps and unresolved references. AsyncAWSCollector._service_tasks()returns collector callables instead of coroutine objects, so subclasses can prune tasks without leaving unawaited coroutines.MultiAccountCollectorpassesis_primary_regionto collectors that support region scoping, and collects the caller's own account with the base credentials instead of assuming the member role there.- GraphML / D3 exports carry
account_idon nodes andrelationship/descriptionon edges; the ontology uses each edge's declared relationship. AWSDeepInventoryCollectorandGCPCollectortake their options as keyword arguments validated byDeepInventoryOptions/GCPCollectorOptions: an unknown option name raisesTypeErrorinstead of being ignored.tagging_sweepstays the fourth positional parameter ofAWSDeepInventoryCollector.- Large inventory modules were split into packages and focused modules (
aws_services/*,azure_graph/,gcp_relations/,mapper_result,linker_index,aws_deep_tasks,organization_assets); the previous import paths keep re-exporting the same names. - URL and host checks in the collectors and the linker (SQS queue URLs, ECR and ACR registry hosts, S3 endpoints) parse the URL and match the host with anchored expressions instead of substring tests.
Fixed¶
- Global services (IAM, S3, CloudFront) were collected once per region, so
--regions allproduced the same role or bucket many times under different IDs. They are now collected once per account, and the mapper deduplicates anything seen twice. - Ambiguous names (for example security groups called
default) could link to a resource in another account. - Per-service collection failures inside an AWS account/region were hidden behind an overall SUCCESS; they are now reported, and the run is marked PARTIAL.
- AWS: KMS rotation was always reported off; ACM listed only RSA-2048 certificates; SQS stopped at 1,000 queues; organization CloudTrail trails made every member account look unaudited; WAF associations missed App Runner, Verified Access and Amplify; event buses and classic load balancers were not paginated; S3 made one location call per bucket; Network Firewalls attached to transit gateways had no edge; API Gateway stages, S3 access points and WAF IP sets were classified as APIs, buckets and web ACLs.
- Azure: a single NSG rule using plural address prefixes or application security groups raised during edge collection and discarded the whole subscription; Key Vault details were always empty; only the first subscription was collected (and, with azure-mgmt-resource 26, none could be listed); collection blocked the event loop.
- GCP: a string
statecrashed resource mapping and silently truncated every project; IAM members became external placeholder nodes that marked entire projects internet-exposed; firewall edges were fabricated from network tags; subnet containment used the wrong parent; collection blocked the event loop.
0.5.0¶
Released 2026-09-30
Inventory mapping. A dedicated, scanner-independent function that maps everything deployed (or default) in a cloud estate and how it interlinks. It answers what exists and how it is wired together; the scanners keep answering what is wrong.
Added¶
cloudg mapCLI command: deep-collects the complete infrastructure across AWS, Azure and GCP without running any scanner, links it into an interconnected map, and writesinventory-map.json,inventory-map.graphmlandinventory-graph.json.--findingsmerges previously generated scanner findings intoasset-map.json(asset → risk) andcompliance-map.json(framework → affected assets).cloudg.inventorypackage, all public API:AWSDeepInventoryCollectoradds route tables, internet/NAT gateways, network interfaces, EBS volumes, Elastic IPs, NACLs, VPC peering, transit gateways and customer-managed IAM policies on top of the standard collector, plus a catch-all sweep of the Resource Groups Tagging API so every taggable resource appears on the map.AzureDeepInventoryCollectoradds NICs, public IPs, managed disks, load balancers and route tables, plus a full-subscription ARMresources.list()sweep covering every service.GCPDeepInventoryCollectorbrings a richer Cloud Asset Inventory type taxonomy and IAM policy bindings as attachment edges.RelationshipLinkerderives interconnection edges purely from asset metadata (attachment, containment, routing, peering, and a generic cross-service reference pass) and works on anylist[CloudAsset].InventoryMapper/InventoryResult: orchestration, summaries, exports, and thebuild_asset_map()/build_compliance_map()/export_merged()merge helpers.
CloudGEngine.map_inventory()/map_inventory_sync()for embedders.inventory:config section (tagging_sweep,link_references).- New schema values:
AssetType.NETWORK_INTERFACE,EdgeType.ATTACHED_TO,EdgeType.REFERENCES. MultiAccountCollectoraccepts per-provider collector class overrides.- Documentation: inventory mapping is covered in the README, the handbook (
cloudg map, the inventory mapping API) and the docs site; a new "deeper toolkit" chapter documents the standalone programmatic APIs (GraphBuilder, CloudOntology, RAGExporter, TerraformExporter, FindingsNormaliser, parsers).
Fixed (pre-release hardening, folded in before publish):
- The engine's Checkov path passed
framework=to a constructor that takesframeworks=(a list) and raised TypeError; it now forwards the configured framework list. - The GCP identifier checks in the inventory linker and mapper are anchored regexes, so
.googleapis.cominside an unrelated ARN no longer classifies an asset as GCP (CodeQL py/incomplete-url-substring-sanitization). - The Pages deploy artifact carries the run attempt in its name; re-running the workflow no longer fails on "Multiple artifacts named github-pages".
- install.sh no longer uses
A && B || Cas if-then-else anywhere; every step is an explicit if/else, with apip_toolhelper for the repeated pip installs. - Dockerfile pins parliament to 1.6.4.
- Codacy complexity findings: the long CLI commands (
run,scan,ingest), the engine phases (scan,analyze,run_pipeline), the SVG/HTML renderers, the ontology relation inference, the RAG exporters, credentials, registry discovery and the Prowler/IAM-linter scanners were split into focused helpers; behavior is unchanged and the CLIruncommand no longer takes 27 named parameters. - Codacy file-length findings: the four oversized modules were split along their natural seams, with every import path preserved. The CLI helpers moved to
cli_helpers.py/cli_run_helpers.pyand theruncommand tocli_commands.py; the AWS per-service collectors moved toaws_services.py/aws_services_extended.pymixins; the ontology relation-inference layer moved toontology_rules.py(re-exported fromontology); the SVG hierarchical layout moved tosvg_layout.py. The SVG output is byte-identical and the whole suite passes unchanged.
0.4.1¶
Released 2026-09-29
Security hardening pass over the Checkov and Codacy findings.
- The Docker image now runs as a non-root
cloudguser and carries a HEALTHCHECK. install.shno longer pipes curl output straight into bash; installer scripts are downloaded to a temp file first (Homebrew, NodeSource, Azure CLI).- Third-party GitHub Actions are pinned to full commit SHAs across all workflows.
- Plugin loading validates the
module:Classpath shape beforeimportlib.import_module. - Retry jitter uses the system RNG.
- Silent
except: passhandlers now log at debug level, so swallowed errors are traceable. - Log messages no longer put words like "credentials" next to runtime values.
- The vulnerability-report email moved out of SECURITY.md in favour of GitHub private reporting.
- A
.codacy.ymlscopes analysis to shipped code and documents suppressed false positives (pytest asserts, taxonomy names such asAssetType.SECRET).
0.4.0¶
Released 2026-09-29
Ingest mode. cloudg can now work entirely from scanner outputs you already have, with no cloud credentials and no scanner binaries on the machine.
Added¶
cloudg ingestCLI command. Takes--prowler,--scoutsuite,--checkovand--trivypaths (file or directory, each repeatable, any combination), runs the normaliser and the check-equivalence merge over them, and renders the HTML and JSON reports.parse_report(path)classmethods onProwlerScanner,ScoutSuiteScanner,CheckovScannerandTrivyScanner. Each parses the tool's native output format directly. Trivy image and filesystem reports are told apart by theArtifactTypefield.cloudg.ingestmodule withparse_report(tool, path)andingest_reports(mapping).CloudGEngine.ingest_reports(),CloudGEngine.normalise_findings()andCloudGEngine.run_from_reports()/run_from_reports_sync()for embedders.- Feature documentation:
docs/DOCUMENTATION.md, a rendered handbook atdocs/index.html, and this changelog.
Fixed¶
- The publish workflow now sets an explicit least-privilege
permissionsblock (CodeQLactions/missing-workflow-permissions).
0.3.0¶
Released 2026-09-26
The rebrand release: the project, package, CLI, Docker image, installers and CI became cloudg (cloud graphing).
- Renamed everything to cloudg; classes follow as
CloudGConfigandCloudGEngine. - Multi-cloud authentication: OIDC web identity federation, STS role assumption with external IDs and multi-account fan-out on AWS; workload identity federation, service principals, managed identity on Azure; credential files and service account impersonation on GCP.
- Expanded compliance rulesets, generated from Prowler's public compliance data: 28 frameworks, 4,166 controls, 10,236 check mappings across AWS, Azure and GCP.
- uv-based packaging and a PyPI publish workflow using trusted publishing (OIDC), plus a Docker image on GHCR bundling all four scanner binaries.
- MIT license and community standards files.
Patch releases folded in¶
- 0.3.1 (2026-09-26): the IaC scanners no longer fall back to silently scanning the current working directory; they target configured directories or the Terraform recreation of the live infrastructure, and are skipped otherwise.
- 0.3.2 (2026-09-28): finding deduplication is keyed on scanner check IDs with the
rules/check_equivalence.yamlcross-scanner map, instead of resource and title alone. The CLI moved to a dedicated Rich UI layer.
Before 0.3.0¶
The pre-rebrand history (April 2026): the async AWS collector grew ECS, DynamoDB, CloudFront, Secrets Manager and KMS support with adaptive retries; scanners moved to parallel execution under a thread pool; the Terraform export renderer and live-streaming Prowler output landed; the Click CLI took shape.