The question this answers
This resource has a public address — is that the design working, or is it the finding?
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.
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.
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.
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 resource | Port class | What is behind it | Verdict |
|---|---|---|---|
| Load balancer / CDN / API gateway | 80, 443 | An application that authenticates its own users | Design. This is what these components are for. |
| Object storage serving a website | 443 GET | Assets intended for anonymous download | Design — provided the bucket holds only those assets. |
| Object storage holding customer or financial data | 443 GET | Records identifying people or money | Finding, high. Same configuration as the row above, opposite impact. |
| Managed database | 5432, 3306, 27017, 1433 | All application data | Finding, critical, unless client certificates plus a narrow source allow-list are enforced and documented. |
| Cache / in-memory store | 6379, 11211 | Session tokens and cached records | Finding, critical. Frequently unauthenticated by default and a known mass-compromise target. |
| Message broker | 5672, 9092 | The event backbone | Finding, high. Public brokers leak business events and accept injected ones. |
| Orchestrator or control-plane API | 6443, 2379 | The ability to schedule workloads | Finding, critical. Public control-plane access is remote code execution on the cluster. |
| Remote administration | 22, 3389, 8080 consoles | A shell or an admin UI | Finding, high. Use private connectivity or an identity-aware proxy; a source allow-list is a mitigation, not a fix. |
How the public database actually happens
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.
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"
}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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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.
- • 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/0on 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.
- • 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.
- • 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
What people believe, and what is true
Any public endpoint is a vulnerability.
Load balancers, CDN edges, API gateways and static sites are public by design. Reflexive flagging produces noise that gets the whole check ignored.
A non-standard port or an unguessable hostname is protection.
The entire address space is scanned continuously and certificate transparency publishes hostnames. Obscurity delays discovery by minutes.
An IP allow-list makes a public database fine.
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.
It is only staging.
Staging routinely holds a copy of production data and receives a fraction of the review. Attackers do not filter by environment tag.