Cloud Scanning
Cloud scanning checks your running AWS account, not your code. It reads the control plane through AWS’s own APIs and reports what is actually configured right now: public S3 buckets, security groups open to the internet, unencrypted volumes, a root account without MFA, CloudTrail switched off — and Route 53 records still pointing at IP addresses you no longer own.
It is powered by Prowler. You can connect an account and let Vygl scan it on a schedule, or run the scan yourself from the CLI — see How Vygl reaches your account. Either way, findings land in the same queue as your SAST, SCA, secrets and IaC results, with the same triage, deduplication and AI verification.
Cloud scanning vs. IaC scanning
Section titled “Cloud scanning vs. IaC scanning”They answer different questions, and you want both.
| IaC scanning (Checkov) | Cloud scanning (Prowler) | |
|---|---|---|
| Reads | Terraform / CloudFormation / Kubernetes files in your repo | Your live AWS account |
| Question | “Will this template create something insecure?” | “Is what’s running right now insecure?” |
| Catches | A bucket declared public before anyone applies it | Drift, console changes, and anything created by a deletion |
| Runs against | Your source tree | A provider string (aws) — no repo needed |
That last row is the one people underestimate. A resource released back to AWS leaves nothing behind in source or in a template — the template that created it may be flawless. Only live state shows it.
Subdomain takeover detection
Section titled “Subdomain takeover detection”This is the reason cloud scanning exists.
You point app.example.com at an Elastic IP. Months later the instance is retired and the EIP is released back to AWS’s pool. Nobody deletes the DNS record. Anyone who allocates Elastic IPs in a loop until they land on that address is now serving content on your subdomain — with your cookies, your CORS origin, and a certificate they can issue themselves.
Vygl flags those records, with the hosted zone, the record name, and the address it currently resolves to.
The check evaluates records that contain a literal IP address. Route 53 alias records — the ones the console shows as Type A, Alias Yes — hold an AWS resource name rather than an address, so this particular failure mode cannot occur on them: there is no address to release back to a pool. They are not evaluated, and on an estate that uses aliases throughout you should expect this check to report nothing. See the caution below for what that leaves uncovered.
The finding’s identity is the record, not the address:
- Repoint the record at another address you do not own → same finding, so your triage notes and AI verdict survive. It is the same unfixed problem.
- Repoint it at an address you do own, or delete the record → the finding stops appearing and auto-closes.
What gets checked
Section titled “What gets checked”Vygl ships a curated profile called Cloud Essentials — 85 Prowler checks, not all 655. Running everything on a first scan produces four figures of mostly best-practice noise and nobody comes back to the queue. The profile is what a security engineer would actually act on this week:
| Family | Examples |
|---|---|
| Dangling DNS | Route 53 records pointing at unowned IPs |
| Public exposure | Public S3 buckets and objects, public EBS snapshots and AMIs, public RDS / Redshift / DocumentDB, public ECR / EFS / KMS / Lambda / EKS / OpenSearch / SQS / SNS |
| Network | Security groups open to 0.0.0.0/0 (all ports, SSH, RDP, database ports), internet-facing instances with instance profiles, IMDSv2, VPC flow logs |
| Identity & access | Root MFA, administrative policies, privilege-escalation-capable inline policies, password policy, key rotation, Access Analyzer |
| Encryption | EBS / RDS / EFS / S3 / DocumentDB at rest, ElastiCache in transit, KMS key rotation, weak TLS ciphers on load balancers |
| Logging & audit | CloudTrail multi-region + log-file validation + KMS, Config recorder, GuardDuty, ELB and API Gateway logging |
Individual checks can be disabled, or have their severity overridden, from Rules — the same per-rule controls the other engines use.
Running a scan
Section titled “Running a scan”Cloud scanning runs from the CLI or your CI pipeline, wherever AWS credentials already exist:
vygl scan --scan-types cloudThere is no source directory to point at — the target is the account your credentials resolve to.
In CI, prefer OIDC over long-lived keys:
# GitHub Actionspermissions: id-token: write contents: readsteps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/VyglCloudPostureReadOnly aws-region: eu-central-1 - run: vygl scan --scan-types cloudThe role needs AWS’s managed SecurityAudit and ViewOnlyAccess policies. Nothing more — the scan never writes.
A full-account scan legitimately takes 20–60 minutes.
Configuration
Section titled “Configuration”Add a scan.cloud block to .vygl.yml:
scan: cloud: provider: aws # only "aws" today regions: # omit to scan every enabled region - eu-central-1 - us-east-1 profile: vygl-audit # AWS named profile, optional role_arn: arn:aws:iam::123456789012:role/VyglCloudPostureReadOnly checks: # narrow the profile, optional - route53_dangling_ip_subdomain_takeover engine_timeout_seconds: 3600See Configuration for the full schema.
How Vygl reaches your account
Section titled “How Vygl reaches your account”There are two paths, and they produce identical findings.
Connect an account. You deploy a small CloudFormation stack that creates a read-only role trusting Vygl, conditioned on an ExternalId Vygl generated for you. Vygl scans the account daily. The stack grants SecurityAudit and ViewOnlyAccess plus a narrowed set of extra reads; nothing it grants can write, and deleting the stack revokes access immediately.
Run it yourself. vygl scan -t cloud runs Prowler on your machine or CI runner against the ambient credential chain — environment variables, ~/.aws/config, an instance role, or CI OIDC. Only findings metadata is uploaded. This is the only path for air-gapped and self-hosted installs, and for anyone who will not grant a cross-account role.
Vygl stores no cloud credential
Section titled “Vygl stores no cloud credential”On the CLI path nothing leaves your runner but findings. On the connected path Vygl holds two things: a role ARN, which is an identifier rather than a secret, and an ExternalId that Vygl generated and which is worthless to anyone who is not already the trusted principal. The actual credentials are a one-hour STS session that exists for the length of a single scan and is never written down.
What that does not mean is that Vygl is outside your trust boundary on the connected path. Vygl becomes a trusted principal in your account, and that is a real obligation on our side. It is why the ExternalId is per-tenant, unguessable, and never customer-supplied: without it, anyone who learned your role’s ARN could ask Vygl to scan your account on their behalf.
Credentials are never passed to Prowler as command-line arguments on either path — that would expose them to every other process on the host. On the connected path the scanner process is handed the session through its environment with every other AWS credential source stripped out and instance-metadata access switched off, so a scan can never silently fall back to Vygl’s own credentials and audit the wrong account.
When a scan fails instead of returning “clean”
Section titled “When a scan fails instead of returning “clean””Prowler’s dangling-DNS check needs AWS’s published list of IP ranges to decide whether an address belongs to AWS. Upstream, if that list cannot be fetched, the check quietly passes everything.
Vygl refuses to inherit that. If the IP-range data is unreachable and your scan requested the dangling-DNS check, the scan fails rather than reporting a clean account. A false all-clear would be worse than no scan: a clean full scan auto-closes findings, so it would close a live subdomain takeover somebody had already triaged.
The same principle applies throughout — a missing prowler binary, a timeout, an AccessDenied, or unreadable output all fail the scan. None of them is recorded as zero findings.
Findings
Section titled “Findings”Cloud findings have no file and no line number; there is no code involved. The location is the resource:
scan_type=cloudmetadata.account_id,metadata.region,metadata.servicemetadata.resource_uid,metadata.resource_name,metadata.resource_typemetadata.check_id— the Prowler check that firedmetadata.remediation,metadata.remediation_urlmetadata.compliance— mapped framework controls (CIS, MITRE ATT&CK, …)
Filter for them anywhere with scan_type=cloud, including GET /api/v1/findings — see the REST API reference.

