Security Testing, AI & ComplianceCloud Mapping
On this page0

Unfamiliar word? See Glossary: words used in these docs. Looking for the tasks in order? Begin at Start Here.

Reference

Included in: Advanced. Which Plan Includes What

Cloud Mapping

A map of what runs in your AWS, Azure and GCP accounts, built from a read-only scan of each one.

What It Does

Cloud discovery maps the path from the internet through your VPC to compute and storage, flagging exposed resources.CLOUD DISCOVERY · POINT-IN-TIME SNAPSHOTInternetProduction VPC10.0.0.0/16EC2SSH openRDSS3 vaultPublic S3 bucketpublic access block offFINDINGS3 critical4 warningre-syncs daily by default

Zonitix scans your AWS, Azure and GCP accounts to build a point-in-time topology map that re-syncs on a schedule (daily by default) and whenever you refresh it. It visually highlights databases, file storages, and virtual machines.

Cloud Spend

For AWS, see six months of monthly spend broken down by service from Cost Explorer, with a simple run-rate forecast for the current month. For Azure, monthly spend comes from Azure Cost Management. The map does not flag unused or unattached resources.

Security Auditing

Check that public network rules match your company security policy. Exposed ports and loose firewall rules are visible on the map.

Refresh Behaviour

The map is a point-in-time snapshot served from cache, not a live view. Request a refresh explicitly to re-scan. Every connected cloud (AWS, Azure and GCP) also re-syncs on a schedule — daily by default, and the interval you configure applies per cloud. Each scan on any of the three clouds also writes a historical snapshot. The Changes tab compares the current map with the previous snapshot by default, or any two saved snapshots you pick.

A rules pass runs over the resulting graph and raises findings such as world-open security groups, exposed RDP/SSH, public or unencrypted databases, unencrypted volumes, S3 buckets without versioning, a public EKS API endpoint, KMS keys, secrets, SNS topics or SQS queues whose policy allows any principal, stale or unused access keys, and missing MFA. These are configuration checks against the data already pulled — nothing is probed or attacked.

Reading The Map

  • Findings are the per-resource rules described above. Each carries an indicative mapping to control families in CIS AWS Foundations, PCI DSS, SOC 2 and NIST 800-53 — a starting point for an auditor, not an attestation.
  • Conflicts are four network rules: a VPC main route table that routes to the internet, a resource with a public address but no internet route, a database reachable from the internet, and a route table with a blackhole route.
  • Attack Paths chain existing results into a ranked list: an internet-exposed workload, the role it runs as, and the sensitive data or admin rights that role reaches. It is a traversal of data already scanned, not a new probe.
  • Priorities is the opening view: resources ranked by risk, with a TOXIC tag where several risks stack on one resource. It has four optional AI helpers, all advisory and all run on the local model: a plain-English filter (Ask AI), Explain with AI on a priority, Summarize with AI (what to fix first) and Find toxic combos (AI). The ranked list itself does not depend on them.
  • Identity Risks joins cloud principals to your employee directory and raises four types: Departed-employee key, Orphaned access key (no matching owner), No MFA, and Stale access key (older than 90 days). The key-based types can be sent to identity remediation when the finding carries a key id; No MFA never can.

If something here is not working as you expect, start with When something goes wrong.

The plain explanation is above. This part is for IT and developers: the exact settings, permission names, API addresses and data fields behind it. Closed: select the heading above to open it. While it is closed, your browser’s find-on-page (Ctrl+F or Cmd+F) does not search this part.

Connection & Scan Specs

Cloud Mapping reads each cloud with a read-only identity. AWS is shown first: it uses a read-only IAM principal to read resource, network and identity configuration. Azure and GCP are covered in the runbook below.

Attach the AWS-managed ReadOnlyAccess policy (or SecurityAudit) to that principal. A narrow hand-written policy is not enough: the connection check requires iam:ListUsers to pass before the hook is verified, and the scan reads IAM, S3 bucket settings, Lambda, load balancers, ECS, EKS, KMS, Secrets Manager, SNS, SQS, DynamoDB, GuardDuty and Security Hub as well as EC2 and RDS. If you do scope your own policy it must still include iam:List* and iam:Get*, or the hook never verifies and no map is drawn. The cost view calls Cost Explorer (ce:GetCostAndUsage).

Graph Generation

Zonitix uses AWS SDK metadata to inspect Security Group ingress/egress rules and construct a network directional graph mapping node-to-node connectivity vectors.

Scan Scheduling

A scheduler wakes hourly and re-syncs any connected account whose cached map is older than the sync interval configured for that cloud (awsSyncInterval, azureSyncInterval or gcpSyncInterval, in minutes; each defaults to 1440, which is daily). The interval is set per cloud under Security Settings → Cloud Mapping, not by plan tier. Admins can force an on-demand sync from the mapping control panel at any time.

Connecting A Cloud Account

The steps on the Zonitix screens are in the guide Connect AWS, Azure or GCP. This section gives what has to exist in each cloud account first, what a scan reads, and the permissions involved.

Connecting a cloud account uses the same hook wizard as an identity provider. To create one you need manage_hooks or manage_integrations; to view the resulting map you need view_cloud_infrastructure.

AWS

Two ways in. You can paste an IAM access key pair, or have Zonitix assume a role in your account.

  1. Create an IAM role in your AWS account and attach ReadOnlyAccess.
  2. On its trust policy, allow Zonitix to sts:AssumeRole, conditioned on sts:ExternalId matching the External ID you set in Zonitix. That condition is what stops a third party who learns your role ARN from assuming it — do not skip it.
  3. In Zonitix, fill roleArn and externalId and set region. Contact Zonitix support before using a role, so the trust policy names the right identity (see the note below on the key fields).

The scan reads S3 buckets (with public-access-block, encryption and versioning state), IAM users and roles with their attached policies (including policies granted through groups) and MFA status, and then per active region: VPCs, subnets, route tables, EC2, RDS, Lambda, security groups, NACLs, NAT gateways, peering, load balancers with target health, EKS and ECS clusters, KMS keys, Secrets Manager secrets, SNS topics, SQS queues and DynamoDB tables. If GuardDuty or Security Hub is enabled in a region, their existing findings are pulled onto the map too (up to 200 from each per region). Cost data comes from AWS Cost Explorer — six months of monthly spend by service.

What a role connection needs. The connection check really assumes the role (passing the External ID), confirms the assumed identity, and runs the remaining checks with it — so a role hook reaches verified state and is re-synced on schedule like a key hook. Something still has to make the sts:AssumeRole call: if the hook also holds an access key pair, those keys are used as the base; if the key fields are blank, the call uses your Zonitix instance’s own AWS identity, and fails if the instance has none or the role does not trust it. Two things do not use the role at all and sign directly with the hook’s stored access keys: the Identity Risks scan and key deactivation in armed remediation (both below). A role hook with blank key fields therefore gets a map but no Identity Risks and cannot deactivate keys; with keys present, both run with the permissions of that key’s IAM identity, not the role’s.

Azure

Azure reuses the same app registration as directory sync (tenantId, clientId, clientSecret) — but resource discovery is a separate authorization system from Microsoft Graph. A Graph permission grants nothing in Azure Resource Manager and vice versa.

So in addition to the Graph permission you granted for directory sync: Subscriptions → your subscription → Access control (IAM) → Add role assignment → Reader, assigned to the app registration. Assign it at subscription scope, not resource-group scope.

Discovery pulls virtual networks, VMs with live power state, NSGs, storage accounts, SQL servers with their databases and firewall rules, NICs, public IPs, and role assignments resolved to readable role names. Cost data comes from Azure Cost Management.

One thing to set deliberately: a hook maps one subscription. Put the subscription you want in the Subscription ID field and Zonitix validates the app registration can actually reach it. Leave it blank and it maps whichever subscription ARM returns first — and tells you in the logs that it picked one of several, rather than quietly implying the map is complete. Invoice sync calls the real Azure Cost Management API and fails loudly if it can’t.

Google Cloud (GCP)

GCP discovery authenticates with a service-account key. Create a service account in the project you want to map, grant it the Viewer and Security Reviewer roles on the project, download its JSON key, and paste that key plus the Project ID into the GCP hook. As with Azure, one hook maps one project.

Discovery builds the same node-and-edge resource graph it does for AWS and Azure — your compute, network, and related resources and how they connect — cached as a point-in-time snapshot that re-syncs daily by default (on the GCP Sync Frequency you set) and whenever you refresh it. GCP mapping is gated by the same cloud-mapping entitlement as AWS and Azure, which is part of the Advanced plan.

Snapshot And Change Endpoints

MethodPathPurposeAuth
GET/api/v1/auth/aws-snapshots/:providerIdThe saved snapshots for one connected account. Azure and GCP: /azure-snapshots/:providerId, /gcp-snapshots/:providerId. One snapshot at /aws-snapshot-detail/:id (and the azure- and gcp- equivalents).view_cloud_infrastructure · license awsMappingEnabled
GET/api/v1/auth/aws-changes/:providerIdWhat changed between two snapshots (?a=&b= pick them; without them, the current map against the previous snapshot). Azure and GCP: /azure-changes/:providerId, /gcp-changes/:providerId.view_cloud_infrastructure · license awsMappingEnabled

Each of the four AI helpers on the Priorities view is refused with 503 when AI is switched off for the instance and 403 when the caller is not permitted to use AI.

If the map is empty but credentials validated: for Azure, an ARM call returning 200 with an empty subscription list means the identity holds no role assignment at all — that is a missing Reader role, not a bad secret. For AWS, confirm the assumed role actually carries read permissions in the regions you expect.

Executing A Fix — Cloud Remediation

Everything above only reads your cloud. Remediation is the separate, higher-privilege control that mutates it — deactivating an access key, revoking a security-group rule, blocking public S3. Two endpoints do this, and the single most important fact about both is that they default to dry run: as shipped they simulate the change, write an audit record, and touch nothing in your account.

Dry Run Is The Default

Both remediation engines read one environment variable, REMEDIATION_DRY_RUN, and treat anything except the exact string false as dry run — unset, empty, 0 and even true all simulate. Live cloud mutation happens only when REMEDIATION_DRY_RUN=false is set on the security-lab-service. One flag arms both engines; there is no per-request override, so an inspector can read the control’s posture from that single value.

MethodPathPurposeAuth
POST/security-lab/remediateApply a fix for a cloud config finding (S3/SG/RDS/MFA, Azure storage/SQL/VM) by running the mapped CLI commandmanage_security_findings OR manage_compliance · license awsMappingEnabled
POST/aws-resources/:providerId/identity-remediateDeactivate the IAM access keys behind an identity findingmanage_compliance OR manage_security · requireSudo · license awsMappingEnabled

These are the security-lab-service routes, where the permission, license and step-up gates are actually enforced. The api-gateway surfaces identity-remediate at /api/v1/auth/aws-identity-remediate/:providerId; the cloud-finding /security-lab/remediate route is defined in the service and is not exposed through the gateway as shipped.

Identity Remediation — Deactivate Leaked Keys (Step-Up Gated)

The stronger of the two controls. On top of manage_compliance or manage_security it requires a fresh step-up (requireSudo), so an ordinary admin session is not enough — the caller must have re-authenticated recently. A step-up counts for 15 minutes. The server reads the window from a stored setting, sudoTimeoutMinutes, and holds any value between 1 and 60 minutes, but no screen and no API route writes that setting, so every instance uses 15 minutes. The finding you post must be remediable and carry at least one key id, or the call is refused before anything is contacted.

// POST /aws-resources/:providerId/identity-remediate

// Needs a recent step-up session (see POST /auth/sudo) + manage_compliance|manage_security

Request body: { "finding": {

"principal": "svc-ci-deploy",

"remediable": true,

"keyIds": ["AKIAEXAMPLE1", "AKIAEXAMPLE2"]

} }


// Default (dry run) — nothing is deactivated:

200 { "ok": true, "dryRun": true, "planned": [...],

"message": "Dry run: would deactivate 2 access key(s) for

svc-ci-deploy. Set REMEDIATION_DRY_RUN=false to execute." }

// audit event: IDENTITY_REMEDIATION_DRYRUN


// Armed (REMEDIATION_DRY_RUN=false) — keys set Inactive via IAM UpdateAccessKey:

200 { "ok": true, "dryRun": false,

"executed": ["AKIAEXAMPLE1", "AKIAEXAMPLE2"],

"message": "Deactivated 2 access key(s) for svc-ci-deploy." }

// audit event: IDENTITY_REMEDIATION_EXECUTED


// Refusals:

400 "This finding has no automated remediation." // not remediable / no keyIds

404 "Provider not found"

400 { "error": "SUDO_REQUIRED" } // step-up missing or expired (status as the gateway returns it)

Only AWS is automated. If the provider is Azure or GCP, the endpoint does not fake a success — it returns { ok: false, automated: false, planned, message } telling you to disable the credential in that cloud’s own console, and logs IDENTITY_REMEDIATION_UNSUPPORTED. The only mutation this engine performs is deactivating AWS IAM access keys.

Cloud Config Remediation — And The Argument-Injection Guard

The /security-lab/remediate engine maps a finding code to a fixed CLI command and runs it. The body carries code (required), plus resourceId and resourceLabel, which flow into the command’s arguments. Because those two are client-supplied, the controller validates both before the command is built — and the command runs through execFile, not a shell.

// resourceId / resourceLabel are rejected BEFORE any command is assembled:

// empty / null -> allowed (that slot is simply skipped)

// length -> must be <= 512

// leading '-' -> rejected (a value posing as a CLI flag = argument injection)

// resourceId -> ^[A-Za-z0-9][A-Za-z0-9:/_.-]*$ (no spaces)

// resourceLabel -> ^[A-Za-z0-9][A-Za-z0-9 :/_.()-]*$ (spaces + parens ok)

// Any check fails -> 400 "Invalid resource identifier or label"

// execFile means shell metacharacters are never interpreted; the leading-dash

// rule is what additionally blocks argument injection and resource retargeting.


// POST /security-lab/remediate

Request body: { "finding": {

"code": "OPEN_SSH_PORT",

"resourceId": "sg-0a1b2c3d",

"resourceLabel": "web-tier"

} }


// Default (dry run):

200 { "ok": true, "message": "Remediation simulated successfully (Dry Run).",

"command": "aws ec2 revoke-security-group-ingress --group-id sg-0a1b2c3d ..." }

// audit event: SECURITY_REMEDIATION_DRY_RUN (mode: dry-run)

// Armed (REMEDIATION_DRY_RUN=false):

200 { "ok": true, "message": "Remediation executed successfully.", "output": "..." }

// audit event: SECURITY_REMEDIATION_EXECUTED (mode: execute)


// Refusals / failures:

400 "Finding details required" // missing finding or finding.code

400 "Invalid resource identifier or label" // validation above

404 "No automated remediation available for this finding" // code has no mapped fix

500 { "error": "Remediation execution failed", ... } // CLI errored -> SECURITY_REMEDIATION_FAILED

Know What This Engine Actually Is

Only a fixed set of codes has a mapped fix: PUBLIC_S3_BUCKET, OPEN_SSH_PORT, UNENCRYPTED_RDS, MFA_NOT_ENABLED (AWS) and PUBLIC_STORAGE_ACCOUNT, SQL_TDE_DISABLED, UNENCRYPTED_VM_DISK (Azure); anything else is a 404. The engine shells out to the aws/az CLI on the service host rather than the cloud SDKs, and the Azure fixes carry literal placeholders ([RESOURCE_GROUP], [SERVER_NAME], [KEYVAULT_NAME]) that must be filled before they could run. Treat it as a demonstration path, not turnkey Azure automation.

How An Inspector Verifies The Control

Dry-Run By Default

With REMEDIATION_DRY_RUN unset, post a real finding and confirm the response reports a dry run (dryRun: true / “Dry Run”), that a IDENTITY_REMEDIATION_DRYRUN or SECURITY_REMEDIATION_DRY_RUN audit row is written, and that nothing changed in the cloud account.

The Step-Up Gate

Call identity-remediate from a session that has not re-authenticated recently and confirm the reply carries "error": "SUDO_REQUIRED" and that SUDO_REQUIRED is logged. A missing or expired step-up is refused with "error": "SUDO_REQUIRED" in the response body. The status beside it is 400 on most routes, because the gateway turns a 401 from a service behind it into 400. It stays 401 on the routes the gateway passes through unchanged: removing a passkey, the diagnostics bundle, and in Security Lab starting an aggressive scan, changing a scan scope and archiving findings. Key on the error string, not the status. A step-up counts for 15 minutes. The server reads the window from a stored setting, sudoTimeoutMinutes, and holds any value between 1 and 60 minutes, but no screen and no API route writes that setting, so every instance uses 15 minutes.

The Injection Guard

Post a finding whose resourceId begins with - or contains a space or a shell metacharacter and confirm 400 "Invalid resource identifier or label" — no command is assembled and no CLI is invoked.

The Audit Trail

Every path is logged: dry runs, executions and failures all leave an event carrying the finding code, resource id and the exact command. Reconcile _EXECUTED events against real cloud changes; an execution with no matching audit row should not exist.

Related reference