Review security and exposure
An exposure review asks what can be reached from the internet, by which path, and what an attacker could reach from there. Answering it properly means walking from a service to its host, security groups and the load balancers in front, across every account, and knowing which parts of the estate were actually scanned. This page covers each operation with Vörr from Claude or ChatGPT and CodeZero in VS Code, and what a credentialed coding agent can already do without them.
When you'd do this
- A compliance or customer security review is coming and someone has to say, with evidence, which resources are internet-facing.
- A security engineer wants to know whether an internal admin or back-office service can be reached from the internet, by any path.
- A new account or acquisition has been connected and nobody knows what in it is public.
- A resource is failing or about to change and you need the list of what depends on it downstream.
- Someone says "we have no public buckets" and you need to know whether that is a verified zero or a partial scan.
- A finding needs fixing and you need to know whether to change Terraform, a service's route config or the console.
Before you start
- Vörr: your organisation is set up and every AWS, Google Cloud and Azure account in scope is connected with read-only credentials, with a completed scan. An account that is not connected is not in any answer.
- Vörr: the connector is added to Claude, ChatGPT or another MCP assistant and you have consented to connect it. Answers draw on the organisation's connected sources, and workspaces label what comes back; admins manage cloud connections, workspaces and the service map. ChatGPT needs a paid plan with Developer mode turned on.
- Vörr: workspaces and environments are mapped for the accounts you care about, so production can be separated from everything else.
- Vörr, for Terraform coverage: your Terraform state buckets are connected through the Terraform connector. Without it, Vörr cannot say whether a resource is managed as code.
- Vörr, for linking to code: your repositories are connected, with branches mapped to environments where you have them.
- CodeZero: the extension is installed in VS Code and the organisation's repositories are indexed, including Terraform, Helm and deployment-config repositories.
- Nothing here needs write access to your cloud. Vörr's stored credentials are never shown to the assistant.
If any of this isn't set up yet, start here:
Where agents go wrong on exposure
Two failures come up again and again. In the first, an engineer preparing for a compliance review asks whether the internal admin service is internet-exposed. The agent reads the instance's security group, sees no inbound rule from the internet, and answers "private". In fact the host sits behind an internet-facing load balancer whose security group allows public traffic. The agent checked one hop and stopped.
In the second, the agent reports "no public buckets". The scan behind that answer was partial: two regions returned access denied and one storage service was throttled. The empty list was real, but it was not a zero.
Both failures are about the shape of the answer, not the reasoning. Exposure is a path, and an empty result needs the coverage behind it. The operations below are built around those two facts.
How Vörr classifies exposure
Vörr gives every scanned resource an exposure class. The class is derived from entry points and the relationships between resources, not from a single setting on the resource itself.
- Public
- The resource is an internet entry point, or is reachable through one: an internet gateway, a CloudFront distribution, an Elastic IP or other public IP address, an internet-facing load balancer, a public S3 bucket, or a publicly invokable serverless service.
- Private
- The resource sits in a private network with no entry point from the internet that Vörr found, for example behind an internal load balancer or reachable only inside its VPC.
- Isolated
- The resource has no network path in or out that Vörr found.
- Unknown
- The resource is not public and not inside a VPC, but is linked to something. This is normal for many managed services; check the ones that hold data or that you expected to be private.
Exposure follows traffic relationships. A host with a closed security group is still public if a public load balancer forwards to it, and Vörr lists the load balancers in front of a service for that reason. The class comes from entry points and traffic links, not from firewall rules; to read a resource's security group rules, ask for a resource lookup.
Operations
The operations below cover a full exposure review, from listing what is public to handing an auditor the evidence. Each one names which product does each step. Most start in the assistant with Vörr and end, when something has to change, in VS Code with CodeZero.
List internet-facing resources
- Vörr: search by exposure class for public resources across every connected account and provider, and narrow by account, region, type or environment.
- Vörr: for each one, ask which entry point makes it public, for example an internet-facing load balancer, internet gateway, CloudFront distribution, public IP or public S3 bucket.
- Vörr: ask for the scan coverage of the accounts and regions searched, so the list has a boundary.
- Agent: group the result by entry point and environment, and flag anything that looks internal by name or tag but is classed public.
Using Vörr, list every public resource across all connected AWS, Google Cloud and Azure accounts, grouped by account and type, with the entry point that makes each one public. Include the scan coverage behind the list.Using Vörr, which public resources have admin, internal, backoffice or debug in their name or tags? For each, show the entry point and the load balancers in front.A good result names the account, region, type and entry point of each resource, and says which accounts and services were scanned. Check that every account in scope is on that list before treating it as complete.
Walk one service's exposure path
This is the operation that prevents the "private because its security group is closed" mistake. Ask for the path, not the resource.
- Vörr: ask for the network exposure of the service. It walks from the service to its host or network interface, to the security groups attached, and lists the load balancers in front of it.
- Vörr: for each load balancer, read its scheme (internet-facing or internal) and its security groups.
- Vörr: read the one-hop neighbours with confidence, to see which relationships were read from configuration and which were inferred.
- Agent: state the conclusion as a path, e.g. internet → public load balancer → target host → service, and name any hop the answer does not show.
Using Vörr, is the admin-portal service in prod reachable from the internet by any path? Walk service, host or network interface, security groups and every load balancer in front. Then look up the security group rules on each hop.Using Vörr, which load balancers front the billing-api ECS service, are any of them internet-facing, and what do their security groups allow inbound?A good answer shows each hop, the class of each load balancer and any gap. If a hop is unknown, the honest conclusion is "not shown to be private", and the next step is to read that configuration in the console.
Check public storage, and read empty results correctly
- Vörr: search S3 by exposure class for public buckets. For Google Cloud Storage, Vörr records public-access candidates to check.
- Vörr: ask for scan coverage per service and region for the same accounts.
- Vörr: ask which storage-related resource types are not indexed.
- Agent: restate the result with its coverage, for example "none in the regions that completed; unknown for the two that were denied".
Using Vörr, list every public S3 bucket, and any Cloud Storage public-access candidates, across all connected accounts. Then give the scan coverage for storage in every region, and say whether the result is a verified zero.You said there are no public buckets. Which regions and accounts completed, which were access denied, failed or throttled, and which storage types could you not read?Only a service that was scanned and came back empty is a real zero, and only for that service and region. What each scan-coverage status means is set out on Explore cloud infrastructure.
Triage unknown exposure and unmapped resources
- Vörr: search for resources with unknown exposure, by account and type. Most are managed services outside a VPC and are expected.
- Vörr: for the ones that hold data or that you expected to be private, read their configuration and neighbours to see what they are linked to.
- Vörr: search by mapping status for resources with no workspace or environment, since an unowned public resource is the hardest to fix.
- CodeZero or Vörr: find owners by commit history for the code or Terraform that references the resource.
- You: resolve the missing permission or mapping in Vörr's Cloud Mapping; see Explore cloud infrastructure for how mapping works.
Using Vörr, list resources with unknown exposure in the prod accounts. Which of them hold data, and what is each one linked to?Using Vörr, which public or unknown-exposure resources have no workspace or environment mapping? Suggest a likely owner from tags and neighbours, with your confidence.The useful output is a short review table: resource, exposure class, what it is linked to, suggested owner and the evidence. Treat suggested owners as a draft for a human to confirm.
Separate production from non-production
- Vörr: filter public resources by environment, using the workspace and environment mapping, and by account.
- Vörr: check for public resources whose environment is unmapped, since those cannot be placed in either list.
- Agent: report production findings first, and list non-production public resources separately; a public staging database is still a finding, but a different one.
Using Vörr, split every public resource into production, non-production and unmapped environment, per workspace. Which production ones are not behind a load balancer?Using Vörr, in the payments workspace only, what is public in production, and is anything in staging public that is private in production?Environment comes from mapping. If mapping is incomplete, say how many public resources have no environment rather than leaving them out.
Size the blast radius of a failing resource
- Vörr: ask for the downstream blast radius of the resource. It is precomputed from the scanned graph and reports true counts, not the first page of results.
- Vörr: read the one-hop neighbours with confidence to see how each path starts, for example a security-group rule, a config reference or a queue subscription.
- CodeZero: check the code side, i.e. which repositories use the same secrets or settings, and which tables the affected services read and write.
- Agent: merge both into one list, marked by source, and bound it by the scan coverage.
Using Vörr, if the reports-db instance in prod fails, what depends on it downstream? Give the true totals, the list, the confidence of each first hop, and the scan coverage that bounds the answer.Which repositories and services read the REPORTS_DB_URL or REPORTS_DB_PASSWORD env vars, in code, Helm values or Compose files, and which tables do they write?Blast radius follows relationships Vörr scanned: what depends on a resource and what a failure would reach. Network reachability is limited to the one-hop "can reach" paths. It says what is connected, not what an attacker could do with a host's permissions; Vörr does not analyse IAM policies. For general dependency mapping before maintenance, see Explore cloud infrastructure.
Find the code that creates exposure
- CodeZero: ask the agent for the HTTP routes each service repository declares, or read them per feature in Functions, and look for admin, debug, internal or health routes that a public load balancer would forward.
- CodeZero: find the settings each service expects and where they are set, and search for those that point at public endpoints or hostnames.
- CodeZero: search across all indexed repositories for hard-coded endpoints, public hostnames and IP addresses, including repositories that are not checked out.
- CodeZero: open Graph to see which services call each other over HTTP, with file:line evidence and confidence on inferred edges.
List the HTTP routes declared by the admin-portal and billing-api repositories, and flag any admin, debug, internal or metrics routes with their file and line.Search every indexed repository for hard-coded URLs or IP addresses pointing at our public load balancer hostnames, and for env vars whose .env.example value is a public endpoint.This is pattern and name matching, not data-flow analysis. A route declared in code is not necessarily reachable; join it to Vörr's exposure path for the same service before calling it a finding.
Decide where to fix it: Terraform or console
- Vörr: for each exposed resource, read whether it is Terraform-managed and from which module and state.
- Vörr: ask for the account's Terraform coverage: the connected state buckets and unmanaged counts by type.
- CodeZero: search the indexed infrastructure repositories for the module, security group or listener that defines it.
- Agent: using the per-resource answers, split the list into fix-in-code (managed) and fix-in-console or import (unmanaged). Fixing a managed resource in the console will be reverted by the next apply.
Using Vörr, for every public resource in prod, say whether it is Terraform-managed and from which module and state. List the unmanaged ones separately.Find the Terraform that defines the internal-admin-alb load balancer and its security group across our indexed repositories, and show the files and their last commits.Terraform coverage covers only the state connected through the Terraform connector. Quote unmanaged counts only for accounts whose coverage is complete, and check the list of connected state buckets before deciding to fix in the console.
Plan the remediation in CodeZero
- Vörr, in the assistant: collect the exposure facts, i.e. the resource, its exposure path, its module and state, and its dependents, and paste the summary into CodeZero.
- CodeZero: plan the fix in Planned mode across the Terraform repository and the service's Helm chart, then approve and apply it as described on Explore cloud infrastructure.
/plan Make internal-admin-alb internal-only in the infra repo: change its scheme to internal, remove the 0.0.0.0/0 ingress on its security group, and verify with terraform validate and terraform plan. Facts from Vörr: .../plan Remove the /debug and /internal/metrics routes from the public listener in the billing-api Helm chart and ingress config, keep them on the internal service, and list every environment file that sets them.Prepare an evidence pack for an audit
- Vörr: ask the assistant to summarise the public resources, their exposure paths and their environments, with the source of each finding.
- Vörr: include the per-service scan coverage, the scan time, the accounts connected and the unindexed types.
- Agent: write the summary as a document you can review: findings, coverage, open unknowns and the remediation plans already raised.
- You: check it against Vörr's Cloud Mapping and scan results in the web app before you send it.
Using Vörr, write an exposure evidence summary for the prod accounts: every public resource with its entry point and path, production versus non-production, unknown-exposure items, and the scan coverage and scan time behind each section. Cite the source of every finding.This is evidence about network exposure in your scanned accounts. It is not an attestation against a compliance framework; the controls an auditor asks about still need your own mapping and sign-off.
Without CodeZero or Vörr
Claude Code with credentialed aws, gcloud or az CLIs, or a cloud provider's MCP server, can do real exposure work. It can describe security groups, load balancers, listeners and target groups, read bucket policies and public-access settings, and reason correctly about a path once it has all the pieces. ChatGPT or Claude chat can reason over the same output if you paste it in.
The cost is in the joins. One path is several calls joined by hand, per account and per region. The agent has to remember to check the load balancers in front, not just the host's security group. Credentials sit on the laptop, often with write permissions. And an empty list, an access-denied region and an account that was never queried all look alike.
| Operation | Claude Code / ChatGPT on their own | With CodeZero and Vörr |
|---|---|---|
| List internet-facing resources | Possible per account and region with describe calls; multi-account means switching profiles and merging by hand | Vörr searches by exposure class across every connected account and provider |
| Walk an exposure path | Join instance, interface, security groups, target groups and load balancers by ID; easy to stop at the first hop | Vörr walks service → host or interface → security groups and lists load balancers in front, marking what the answer does not show |
| Public storage check | Read policies and public-access blocks bucket by bucket; a denied region can surface as an error or not at all | Vörr searches S3 by exposure and records Cloud Storage public-access candidates, with per-service coverage and the types it could not read |
| Unknown and unmapped | No notion of unknown exposure or mapping; depends on tagging conventions | Vörr searches unknown exposure and mapping status directly |
| Production vs non-production | Only as good as account naming and tags | Vörr filters by mapped workspace and environment |
| Blast radius | Built hop by hop, which is expensive on large graphs and can stop early; counts are only what was walked | Vörr gives precomputed downstream blast radius with true counts; CodeZero adds code-side readers |
| Code that creates exposure | Grep in cloned repositories only | CodeZero lists declared routes when asked or per feature in Functions, with config and search across all indexed repositories, plus Graph edges |
| Terraform vs unmanaged | terraform state list for each state it can reach, compared manually | Vörr shows Terraform management per resource and unmanaged counts by type per account |
| Remediation | Edits freely; no plan or approval guard | CodeZero plan with named repositories, approval and declared verification |
| Evidence pack | A summary of what it queried, with no record of what it did not | A summary with sources, scan time and coverage per service |
With CodeZero and Vörr
- Exposure classes from entry points (Vörr)
- Public, private, isolated or unknown for every scanned resource, derived from entry points such as internet gateways, CloudFront, public IPs, internet-facing load balancers and public S3 buckets, and from traffic relationships.
- Exposure paths (Vörr)
- Service → host or network interface → security groups, plus the load balancers in front, with gaps stated rather than guessed.
- Coverage on every answer (Vörr)
- Per-service scan status and the types Vörr could not read, so a zero is either verified or explicitly unknown.
- Ownership, environment and IaC state (Vörr)
- Workspace and environment mapping with gaps, and Terraform management per resource and per account.
- Blast radius (Vörr)
- Precomputed downstream dependents with true counts and confidence on each relationship.
- The code behind the exposure (CodeZero)
- Declared routes, config, hard-coded endpoints and Terraform modules across every indexed repository, including ones not cloned.
- A safe path to the fix (CodeZero)
- A reviewed plan that names the repositories and verification commands, with no code changes until you approve it.
The two products do not call each other. You, or the agent, carry Vörr's findings into a CodeZero plan: Vörr says what is exposed and by which path; CodeZero finds where that path is defined and changes it under a plan you approve.
Worked example: is the admin service exposed?
A security engineer is preparing for a customer security review and has to confirm that the internal admin-portal service in prod cannot be reached from the internet, and that no buckets are public. Resource names, rules and counts below are illustrative.
Using Vörr, is admin-portal in the prod AWS account reachable from the internet by any path? Walk the full exposure path and list every load balancer in front. Then list public buckets across all prod accounts with the scan coverage behind that list.What Vörr returned
Service: admin-portal (ECS service) - account prod-core, eu-west-1
Workspace: platform Environment: production
Exposure: public
Path
admin-portal task ENI eni-0a1f... no public IP
security group sg-admin-tasks inbound 8080 from sg-internal-alb only
Load balancers in front
internal-admin-alb scheme: internet-facing
security group sg-internal-alb inbound 443 from 0.0.0.0/0
Terraform: internal-admin-alb managed - module alb_admin, state s3://tf-state-prod/platform.tfstate
Public buckets (all prod accounts): none found
Scan coverage, S3
prod-core eu-west-1 completed us-east-1 completed
prod-data eu-west-1 completed eu-central-1 access denied
prod-edge us-east-1 access denied
CloudFront (prod-edge) throttledReading the result
- The task's own security group allows nothing from the internet, which is where a one-hop check would have stopped and said private.
- The load balancer in front is internet-facing, despite its name, and its security group allows 443 from anywhere. The admin service is public.
- "No public buckets" is a verified zero only for the three regions that completed. Two regions are unknown because of access denied, and CloudFront, which could front a bucket, was throttled.
Checking the code side in CodeZero
The engineer asks CodeZero for the routes admin-portal declares and where the load balancer is defined. CodeZero lists the routes and finds the Terraform module named in the state.
admin-portal routes (src/routes/admin.ts)
GET /admin/users :14
POST /admin/users/:id/role :31
GET /internal/debug/env :58
infra: modules/alb_admin/main.tf
internal = false :9
ingress cidr_blocks = ["0.0.0.0/0"] :27
last commit: 2026-03-02 "temp: open admin alb for vendor demo"Conclusion and follow-ups
- Finding: admin-portal is internet-facing through internal-admin-alb, including a debug route that prints environment variables.
- Fix in code, not the console: the load balancer is Terraform-managed, so a console change would be reverted. A CodeZero plan in the infra repository sets internal = true and narrows the ingress, with terraform validate and terraform plan as verification. A second task in the admin-portal repository removes the debug route.
- Blast radius: Vörr's downstream radius for admin-portal lists the databases and queues it reaches; these go into the review as potentially exposed until the fix is applied.
- Coverage: the account owner grants the read-only role S3 access in the two denied regions and re-runs the scan; CloudFront is re-scanned. The bucket statement waits for that.
Verify and follow up
- Coverage first: for every list and every zero, read the per-service scan status, the scan time and the unindexed types for the accounts and regions involved.
- Accounts: confirm every account in scope is connected. Anything outside them is not in the review.
- Paths: for each "private" conclusion, check that the load balancers in front were listed and their schemes read; treat an undetermined hop as not shown to be private.
- Confidence: check medium and low-confidence relationships in Vörr's Cloud Mapping, and inferred code edges with Verify in CodeZero.
- Changes: let the CodeZero plan's verification commands run, read the terraform plan output yourself, and apply through your pipeline.
- After the fix: wait for the next scan and ask Vörr for the resource's exposure again. The review is closed when the class has changed, not when the pull request merges.
- Next: look at cost on anything public and idle, and assign owners to what is still unmapped.
Limits
- Vörr is read-only towards your systems from the assistant. It cannot change security groups, load balancers, mappings or tags from chat. Opt-in fg: tag writes and consented EC2 deep inspection exist in the product but are not available to the assistant.
- Vörr is not a vulnerability scanner, a CSPM rule engine or an IAM analyser. It does not check CVEs, evaluate IAM policies or score you against a compliance framework. It tells you what is exposed and by which path.
- Exposure classes come from scanned configuration and relationships. A path through something not scanned or in an unconnected account is invisible.
- Answers reflect the latest scan. Changes made since then do not appear until the next one.
- Blast radius follows the scanned graph. It does not include dependencies that exist only in code or in systems that are not connected, and it does not model what credentials on a host allow.
- CodeZero indexes committed code on the default branch; local working trees add your current branch. Routes, references and inferred edges are name- and pattern-based, not compiler-accurate.
- Inventory, general dependency mapping, Terraform coverage and Kubernetes usage are covered in more depth on Explore cloud infrastructure.