Port Expose Guard

Reads docker run, compose ports or a Kubernetes Service back as who can reach each port, so a database isn't published to the internet by accident.

StopsHelps prevent a database port being opened to the internet

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/port-expose-guard.json

The files land in your project. They're yours to edit.

Usage

port-expose-guard-usage.tsx
import { PortExposeGuard } from "@/components/ui/port-expose-guard"

<PortExposeGuard
  label="docker-compose.yml ports"
  defaultValue={COMPOSE}
  onValueChange={() => setApplied(false)}
  onApply={() => new Promise<void>((resolve) => setTimeout(() => { setApplied(true); resolve() }, 600))}
/>

What it catches

  • Database or admin ports bound to every interface
  • Docker's iptables rules bypassing ufw
  • Host networking, -P, NodePort or public LoadBalancer for internal services
  • Container port the image doesn't listen on (5432:5433)
  • Unquoted 22:22 that YAML 1.1 reads as a number
  • On Docker older than 28, 127.0.0.1 binds reachable from the LAN

Props

PortExposeGuard props
PropTypeDefault
label

Visible label.

string—
value

Controlled config text.

string—
defaultValue

Initial text when uncontrolled.

string""
onValueChange

Called with the text on every edit and one-tap fix.

(text: string) => void—
listening

Ports your own images listen on, keyed by image or Service name, for the mismatch check.

CheckPortsOptions["listening"]—
sensitivePorts

Extra sensitive ports, e.g. `{ 9000: "the MinIO API" }`.

CheckPortsOptions["sensitivePorts"]—
dockerVersion

Docker Engine version on the host, e.g. "27.5.1". Below 28 (or unknown) a note says 127.0.0.1 binds are reachable from the LAN.

string—
delay

Pause in ms before the readback refreshes while typing.

number500
onApply

Called on Apply. With findings left, the first click jumps to them and the second applies. May return a Promise.

(text: string) => void | Promise<void>—
messages

Any visible string; `source` and `reach` may be partial.

Partial<Omit<PortExposeMessages, "source" | "reach">> & { source?: Partial<PortExposeMessages["source"]>; reach?: Partial<PortExposeMessa…—
className

Class for the outer wrapper.

string—

* required. Also accepts the native props of <textarea> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible label; textarea is described by the readback
  • Announcements go to a polite live region
  • Reach is shown as text with icons aria-hidden; focus moves to the first fix

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Enforce network policy and firewalls at the host or cloud level; never rely on a config review alone.

  • Reads the config you paste; it can't see cloud security groups or host firewalls
  • ${VAR} binds without defaults can't be judged until run time
  • Sensitive-port list is a default; pass your own

Rules

Rules last verified:

Informational. Confirmed: before 28.0.0, hosts on the same L2 segment can reach ports published to localhost; Docker's rules divert traffic before ufw. The YAML 1.1 sexagesimal reading was not re-checked. Copied components don't update themselves, so re-check after rule changes (see updating a component).

What it stops

Helps prevent a database port being opened to the internet

Also known as

docker run -p · docker compose · ports · 0.0.0.0 · kubernetes service · loadbalancer · nodeport · firewall

Field type

Dependencies

Pairs well with