Cloud Security

Public Exposure, Read With Context

A scanner that flags every public endpoint is useless. The skill is judging which exposure is the design and which is the finding: a load balancer on 443 is the front door, a database on 5432 is an incident waiting, and a public bucket is either a website or a data breach depending entirely on what is in it.

▶ Run the lab

The question this answers

Infrastructure question

This resource has a public address — is that the design working, or is it the finding?

Application requirement

The product must be reachable by customers on the internet. Some components therefore have to be public. Every other component must be reachable only by the things that legitimately need it, and somebody has to be able to tell those two categories apart quickly and repeatedly.

What it provides

A defensible verdict per exposed resource: the reason it is public, the port and protocol it exposes, what authenticates the caller, what data is behind it, and what an attacker gets if the authentication fails.

Application RequirementInfrastructure RequirementComputeNetworkStorageIdentityDeploymentScalingReliabilityObservabilitySecurityCostTrade-offs

Three public things, three different verdicts

The topology below has three resources with public addresses. A naive scanner reports three findings. A competent reviewer reports one — and the one it reports is not the one with the most traffic.

The load balancer on 443 is public because that is its entire job: it is the front door, it terminates TLS, it fronts an application that authenticates its users, and it is designed and patched to face hostile traffic. Flagging it produces noise. The static-site bucket is public because a website is public; the objects in it are marketing assets that are meant to be downloaded by strangers. Also not a finding. The database on 5432 with a public address and a security group allowing 0.0.0.0/0 is the finding, and it is a serious one: it is a data store, it authenticates with a password that is probably in a config file somewhere, it speaks a protocol whose clients are trivially scriptable, and internet-wide scanning finds an open database port in minutes, not months.

What separates them is not "public versus private". It is four questions: what kind of resource is this, what protocol and port does it speak, what authenticates the caller, and what is behind it if authentication fails. A managed database exposed publicly but requiring client certificates and IP allow-listing is a much weaker finding than a public admin console with a shared password. Context is not a softening of the rule; context *is* the rule.

Three public resources. One is a finding.ILLUSTRATIVE
Internetpublic
Load balancer :443public— Design. Front door, TLS terminated, fronts an authenticating application, built to face hostile traffic.
Object storage — marketing sitepublic— Design. The objects are public assets; the bucket serves a website. Nothing behind it is confidential.
Object storage — customer invoicespublic
⚠ FINDING. Same resource type as the site bucket, opposite verdict, because the data is customer financial records. The exposure is identical; the impact is not.
PostgreSQL :5432, public address, ingress 0.0.0.0/0public
⚠ FINDING. A data store speaking a scriptable protocol, authenticated by a password, holding everything. Internet-wide scanners find open 5432 in minutes.
Admin console :8080public
⚠ FINDING. A management interface has no reason to be internet-reachable. Move it behind private connectivity or an identity-aware proxy.
API containersprivate— Correct: reachable only from the load balancer's security group.
InternetLoad balancer :443· HTTPS — intendedcrosses boundary
InternetObject storage — marketing site· HTTPS GET — intendedcrosses boundary
InternetObject storage — customer invoices· HTTPS GET — unintendedcrosses boundary
InternetPostgreSQL :5432, public address, ingress 0.0.0.0/0· TCP 5432 — unintendedcrosses boundary
InternetAdmin console :8080· HTTP 8080 — unintendedcrosses boundary
Load balancer :443API containers· HTTP

The scanner rules, stated so you can argue with them

A useful exposure check states its reasoning. The matrix below is the rule set: resource kind, plus port class, plus what is behind it, produces a verdict and a reason. Publishing the rules matters — an engineer who disagrees with a finding can point at the rule instead of quietly adding an exception, and a rule that gets argued with repeatedly is a rule that needs fixing.

Two structural points make the rules work. First, the *port class* carries most of the signal: HTTP/HTTPS fronting an application is a normal public surface; database ports, message-broker ports, cache ports, orchestrator API ports and remote-administration ports are not. A cache exposed publicly is almost always an accident, and an unauthenticated one is a well-documented mass-compromise vector. Second, *data classification* is what separates two identical configurations into opposite verdicts, which is why the invoice bucket and the marketing bucket differ. If nobody has classified the data, the exposure check cannot produce a verdict and you have found a different problem.

The verdicts that are not "finding" still deserve a recorded reason. "Public because it is the front door" is an accepted, reviewed exposure. Writing that down converts silence into a decision, and next quarter's reviewer can tell the difference between something considered and something forgotten.

Public resourcePort classWhat is behind itVerdict
Load balancer / CDN / API gateway80, 443An application that authenticates its own usersDesign. This is what these components are for.
Object storage serving a website443 GETAssets intended for anonymous downloadDesign — provided the bucket holds only those assets.
Object storage holding customer or financial data443 GETRecords identifying people or moneyFinding, high. Same configuration as the row above, opposite impact.
Managed database5432, 3306, 27017, 1433All application dataFinding, critical, unless client certificates plus a narrow source allow-list are enforced and documented.
Cache / in-memory store6379, 11211Session tokens and cached recordsFinding, critical. Frequently unauthenticated by default and a known mass-compromise target.
Message broker5672, 9092The event backboneFinding, high. Public brokers leak business events and accept injected ones.
Orchestrator or control-plane API6443, 2379The ability to schedule workloadsFinding, critical. Public control-plane access is remote code execution on the cluster.
Remote administration22, 3389, 8080 consolesA shell or an admin UIFinding, high. Use private connectivity or an identity-aware proxy; a source allow-list is a mitigation, not a fix.
Exposure rules with their reasoning, so a finding can be argued with

How the public database actually happens

provider-specific· Terraform-flavoured pseudo-HCL; resource and argument names differ per provider. The two mechanisms — no public address, source-group instead of CIDR — exist everywhere.

Nobody writes a public database on purpose. It happens through a sequence that looks reasonable at every step: the database is created with a public endpoint because the default wizard offers one and the developer needs to connect from a laptop; the security group gets 0.0.0.0/0 at 18:30 on a Thursday because a contractor's address keeps changing; the temporary rule is never removed because nothing broke. Six months later it is in production and the audit trail recorded every step perfectly, unread.

The two definitions below are the same database. The difference is not sophistication — it is the same number of lines. What the second one costs is a real connectivity story for developers: a bastion, a VPN, or private connectivity from the office network. That cost is the honest reason the first version exists, and pretending otherwise is why security advice gets ignored.

The structural fix is stronger than the review fix. An organization-level policy that denies creating a publicly-addressable database, or that denies 0.0.0.0/0 ingress on data ports, removes the failure mode instead of detecting it. Detection tells you it happened; prevention means it cannot. Where prevention is available, prefer it — and where it is not, alert on the control-plane event as it happens, per Audit Trails.

How it looks when the laptop needed to connect
resource "db_instance" "main" {
  engine            = "postgres"
  publicly_accessible = true          # created this way by the default wizard
  vpc_security_group_ids = [sg_db.id]
}

resource "security_group_rule" "db_in" {
  security_group_id = sg_db.id
  type        = "ingress"
  from_port   = 5432
  to_port     = 5432
  cidr_blocks = ["0.0.0.0/0"]         # "temporary, contractor IP keeps changing"
}
Same database, reachable only by the application
resource "db_instance" "main" {
  engine              = "postgres"
  publicly_accessible = false         # no public address at all
  db_subnet_group     = subnet_group_private.id
  vpc_security_group_ids = [sg_db.id]
}

resource "security_group_rule" "db_in" {
  security_group_id        = sg_db.id
  type                     = "ingress"
  from_port                = 5432
  to_port                  = 5432
  source_security_group_id = sg_app.id   # the app tier, not an address range
}
# developers reach it through private connectivity or a bastion,
# which is the real cost of this change and should be budgeted for.

Referencing the application's security group instead of an address range means the rule stays correct as instances are replaced and cannot be widened by a hurried edit to a CIDR. Removing the public address makes the exposure structurally impossible rather than merely disallowed. Neither change is harder to write; the cost is providing developers another way in.

Key points

  • Public is a property, not a verdict. The question is always what kind of resource, on what port, behind what authentication, holding what data.
  • A public load balancer on 443 is the design. A public database on 5432 is the finding. A public bucket is a website or a breach depending on its contents.
  • Port class carries most of the signal: data ports, cache ports, broker ports, control-plane ports and remote-administration ports have no legitimate public role in normal designs.
  • Data classification is what makes two identical configurations produce opposite verdicts — without it, an exposure check cannot conclude anything.
  • Publish the rules. A finding that can be argued with gets fixed; an unexplained severity score gets an exception.
  • Prevention beats detection: an organization policy that refuses to create a public data endpoint removes the failure mode entirely.

The loop, answered

Every field is required, which is why no lesson here can recommend something without saying what it costs and what simpler thing to consider first.

How it works
  • Enumerate every resource with a routable public address or a public-access setting, including managed services whose endpoint is public by default.
  • For each, resolve the effective reachability: the network rules, not the intent. A private subnet with a permissive rule and a route to an internet gateway is public in practice.
  • Classify the port: application HTTP, data, cache, broker, control plane, or administration.
  • Establish what authenticates the caller and how strong it is: TLS with client certificates, an identity-aware proxy, a password, or nothing.
  • Establish what is behind it: public assets, tenant data, financial records, credentials, or the ability to schedule workloads.
  • Produce a verdict with a one-sentence reason, and record the accepted exposures alongside the findings.
What you still own
  • Re-run the check on every topology change, and continuously through provider posture tooling — exposure is created by small, well-intentioned edits.
  • Alert in real time on control-plane events that create public exposure on data ports. This is the highest-value single alert most teams can configure.
  • Maintain the accepted-exposure list with a reason and an owner for each entry, so an intentional public endpoint is distinguishable from a forgotten one.
  • Give developers a supported private path — bastion, VPN or private connectivity — because the public endpoint exists to solve a real access problem and will come back until that problem is solved.
  • Classify data. Half the verdicts in this lesson are impossible without knowing what is in the bucket.
How it fails
  • Alert fatigue from flagging every public endpoint, ending with the whole check disabled and the one real finding lost.
  • A temporary allow-all rule that outlives the reason for it, which is the single most common path to a public database.
  • Effective exposure differing from intended exposure: a resource believed private that is reachable through a peered network, a misconfigured route or a proxy nobody remembered.
  • A public storage bucket whose contents change over time — created for assets, later used for exports, and now serving customer data under the original permissive policy.
  • Relying on obscurity: a non-standard port or an unguessable hostname. Internet-wide scanning covers the whole address space continuously, and certificate transparency logs publish your hostnames.
How it scales
  • Exposed surface grows with every environment, every account and every managed service adopted — and grows fastest in the non-production environments nobody reviews.
  • Manual review stops being reliable at a few dozen resources; the enumeration must be automated even though the verdict stays human.
  • Multi-account structures cap the damage: a public resource in a sandbox account with no production data is a much smaller problem than the same mistake beside the production database.
  • The dimension that runs out first is the reviewer's credibility — once findings are seen as noise, the check stops functioning regardless of its coverage.
Security
  • Public exposure is the outermost trust boundary and the one most cheaply enumerated by an attacker; assume every public endpoint is being probed continuously.
  • A private address is not authentication. Anything reachable from inside the network still needs to authenticate its callers — see Infrastructure Trust Boundaries.
  • Non-production environments hold production-shaped data more often than anyone admits, and receive a fraction of the scrutiny.
  • Server-side request forgery turns a public application endpoint into a way to reach private resources, which is why the private tier still needs authentication and why metadata endpoints need protecting.
Cost shape
  • Removing public endpoints usually adds cost: private connectivity, a bastion host, a VPN, or per-endpoint charges for private links.
  • Public data-transfer paths are also billed as internet egress, so the insecure option is frequently the more expensive one too — see Egress: Moving Data Costs Money, Not Just Storing It.
  • Posture-management tooling is priced per resource monitored, and its main value is the enumeration, not the verdict.
  • The counterfactual dominates: a breached data store costs notification, regulatory exposure, forensics and customer trust, none of which appear on an infrastructure bill.
What to watch
  • A continuously refreshed inventory of publicly reachable resources with effective rather than intended reachability.
  • Control-plane alerts on rules that widen ingress to 0.0.0.0/0 on non-application ports.
  • Connection attempts from unexpected sources on data ports — the volume tells you how quickly the internet found you, which is usually within minutes.
  • Object-storage public-access settings tracked as a property, per bucket, over time.
  • The signal that lies: "no attacks in the logs". Scanning is constant; the absence of recorded attempts almost always means the logs are not enabled on that path.
Simpler alternatives
  • Do not make it public in the first place. Private-by-default with an explicit, reviewed exception list is simpler than any detection you can build afterwards.
  • An identity-aware proxy in front of administrative interfaces removes the exposure and the VPN, and is usually less work than either.
  • A short-lived bastion session or a session-manager style connection, rather than a permanently exposed SSH port with an allow-list.
  • Provider posture management instead of a home-grown scanner: the enumeration is commodity, and your effort belongs in the data classification that makes verdicts possible.
  • For a small system, one alert on public-exposure changes plus a monthly manual inventory genuinely covers most of the risk. Do that before buying anything.
What adopting this costs
  • Private-by-default buys a much smaller attack surface; it costs developers a connectivity path that has to be built, funded and supported.
  • Automated checks scale the enumeration and cannot judge impact, so the verdict stays human and the process stays partly manual by design.
  • Publishing the rule set invites argument, which is the point, and does slow down the closing of findings.

Public exposure scanner: the port is not the verdict

Public exposure scanner: the port is not the verdict
Eight resources, scanned. Half of the public ones are the design and half are the incident — and which is which depends on what the resource holds and who it is for, not on whether it has a public address.
rules that fired
R1 public + management or administrative plane → finding, whatever the data class. Control planes are reached through a private path or an identity-aware proxy, never from the open internet.
R2 public + audience is the internet + data is public → by design. This is what the resource is for.
R3 public + audience is the internet + non-public data + authenticated → by design. An authenticated API over TLS is the normal shape of a product.
R4 public + audience is the internet + non-public data + no authentication → finding. Anonymous read of customer data is the finding, not the public IP.
R5 public + audience is narrower than the internet → finding. The exposure exceeds the intended audience; that gap is the whole vulnerability.
R6 not public → no exposure finding. Private is not the same as authenticated: rule R6 says nothing about who inside the network may read it.
context for “object storage — customer invoices”
what does it hold?
who is it for?
public
7
by design
3
findings
4
rule
R5
“object storage — customer invoices” is a finding under R5: Intended audience is company staff, actual audience is everyone. The exposure is wider than the purpose. Change the context on the right and watch the verdict move — the same public bucket is a static website or a customer-data breach depending only on what somebody put in it, and the same open port is a product or an incident depending on who it was meant for. That is why exposure findings need an owner who knows the intent, and why "no public IPs" is a policy that both over- and under-protects.
ILLUSTRATIVErule-based, and the rules are printed below on purpose

What people believe, and what is true

Claim

Any public endpoint is a vulnerability.

Reality

Load balancers, CDN edges, API gateways and static sites are public by design. Reflexive flagging produces noise that gets the whole check ignored.

Claim

A non-standard port or an unguessable hostname is protection.

Reality

The entire address space is scanned continuously and certificate transparency publishes hostnames. Obscurity delays discovery by minutes.

Claim

An IP allow-list makes a public database fine.

Reality

It is a real mitigation and not a fix: addresses change, get spoofed in some paths, and the rule tends to widen under time pressure. Removing the public address is structural.

Claim

It is only staging.

Reality

Staging routinely holds a copy of production data and receives a fraction of the review. Attackers do not filter by environment tag.

Apply it