Skip to content

feat: bind to 127.0.0.1 by default instead of 0.0.0.0#2812

Open
bfirsh wants to merge 6 commits into
mainfrom
fix/bind-localhost-by-default
Open

feat: bind to 127.0.0.1 by default instead of 0.0.0.0#2812
bfirsh wants to merge 6 commits into
mainfrom
fix/bind-localhost-by-default

Conversation

@bfirsh

@bfirsh bfirsh commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Default port binding changed from 0.0.0.0 to 127.0.0.1 across all commands, preventing prediction endpoints from being accidentally exposed to the entire network during development.
  • Added --host flag to cog serve and host:port syntax to cog run -p so users can opt into binding to all interfaces when needed.

Changes

File Change
pkg/docker/command/command.go Added HostIP field to Port struct
pkg/docker/docker.go Uses port.HostIP (default 127.0.0.1) instead of hardcoded ""
pkg/docker/run.go GetHostPortForContainer now accepts a hostIP parameter
pkg/docker/run_test.go Updated tests + added new test cases for default and all-interfaces binding
pkg/cli/serve.go Added --host flag (default 127.0.0.1)
pkg/cli/run.go -p flag supports host:port syntax (e.g. -p 0.0.0.0:8888), defaults to 127.0.0.1
pkg/predict/predictor.go cog predict/cog train bind to 127.0.0.1

User-facing behavior

Command Before After
cog serve Binds to 0.0.0.0 Binds to 127.0.0.1. Use --host 0.0.0.0 to expose.
cog run -p 8888 Binds to 0.0.0.0:8888 Binds to 127.0.0.1:8888. Use -p 0.0.0.0:8888 to expose.
cog predict / cog train Binds to 0.0.0.0 Binds to 127.0.0.1

Test plan

  • All 853 Go unit tests pass
  • 0 lint issues

Port bindings now default to localhost-only, preventing prediction
endpoints from being exposed to the entire network during development.

- Add HostIP field to command.Port struct (defaults to 127.0.0.1)
- Add --host flag to cog serve (default 127.0.0.1, use 0.0.0.0 to expose)
- Support host:port syntax in cog run -p (e.g. -p 0.0.0.0:8888)
- Bind cog predict/train to 127.0.0.1
- Update GetHostPortForContainer to match configured host IP
@bfirsh bfirsh requested a review from a team as a code owner March 5, 2026 02:15
@michaeldwan michaeldwan added this to the 0.18.0 milestone Mar 5, 2026
@ask-bonk

ask-bonk Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@anish-sahoo Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Changes default port binding from 0.0.0.0 to 127.0.0.1 across Docker container operations, with a new --host flag for cog serve and HostIP support in the port abstraction.

Issues found:

  1. cog exec -p silently breaks backward compatibility. pkg/cli/exec.go creates command.Port without setting HostIP. Since docker.go now defaults empty HostIP to 127.0.0.1, cog exec -p 8888 now binds to localhost instead of all interfaces, with no escape hatch for users who need network exposure. Consider adding host:port parsing to the -p flag here, or an explicit --host flag for cog exec.

  2. Misleading URL when explicitly binding to all interfaces. pkg/cli/serve.go substitutes localhost when --host 0.0.0.0 is used, which hides the fact that the server is exposed to the entire network. The displayed URL should reflect the actual binding or include a warning.

@ask-bonk

ask-bonk Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

I'm Bonk, and I've done a quick review of your PR.

Summary: Changes default Docker port binding from 0.0.0.0 to 127.0.0.1, adds --host flag to cog serve, and threads HostIP through the port abstraction.

Issues (highest severity first):

  1. cog exec -p backward compatibility breakpkg/cli/exec.go:123 creates command.Port without HostIP, so it silently falls through to the new 127.0.0.1 default. Users who previously relied on cog exec -p 8888 being reachable from the network now have no escape hatch. Consider adding host:port syntax support or a --host flag to cog exec.

  2. Misleading serve URL when binding to all interfacespkg/cli/serve.go:173-176 displays http://localhost:port even when --host 0.0.0.0 is explicitly passed, which masks the security implication that the server is exposed to the entire network.

I posted the full review as a top-level PR comment because the batch review API returned a 422 (likely stale line numbers). You can view it here: #2812 (comment)

github run

@michaeldwan michaeldwan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Real security improvement — localhost-by-default is the right call, applied centrally at the bind site, with docs regenerated. One blocker and a few should-fix items below.

Blocker

cog exec -p lost its host:port override in the merge. Commit a4aec985 added host:port parsing (-p 0.0.0.0:8000) and a HostIP to pkg/cli/run.go. Main renamed cog runcog exec (ba48ff8e); during the merge (88bde6bd) git couldn't carry those changes into the renamed exec.go, so the current loop is the old strconv.Atoi(portString) with no HostIP and no host:port syntax. Because docker.go now defaults empty HostIP to 127.0.0.1, cog exec -p 8888 jupyter notebook silently became localhost-only with no escape hatch — contradicting the commit message's stated intent ("Support host:port syntax in cog run -p"). exec.go isn't in this diff, so this can't be an inline comment — please restore the host:port parsing from the original run.go diff and update the --publish help text.

Should-fix (inline below)

  • docker.go — the core HostIP propagation change has no unit test.
  • run.go — exact-string HostIP match is brittle; fall back to the single binding when only one exists.
  • command.go — extract a shared DefaultHostIP constant ("127.0.0.1" is duplicated in 5 sites).
  • serve.go — remote-Docker (DOCKER_HOST=tcp://…) users get silent breakage; no warning.

Nits (inline below)

  • serve.go:20 — package var named host is generic; serveHost/bindHost is clearer.
  • serve.go:174 — IPv6 --host values produce malformed display URLs (missing brackets); displayHost logic is also untested.
  • run_test.go — duplicated mock scaffolding, not table-driven; missing edge cases (custom IP, IPv6, inspect returning empty HostIP).

cog predict/cog train hardcoding 127.0.0.1 is intended (local-only) and correct. pkg/registry_testhelpers using 0.0.0.0 directly via nat.PortBinding is correctly unaffected.

Comment thread pkg/cli/serve.go Outdated
)

var (
host = "127.0.0.1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit — generic var name. Package-level host collides with a common local (docker.go:47 uses host, err := determineDockerHost()). serveHost or bindHost would be clearer and lower the shadowing risk. Consistent with the existing port/uploadURL convention, so low priority.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — renamed the package-level var to serveHost (flag and all references updated).

Comment thread pkg/cli/serve.go Outdated
}

runOptions.Ports = append(runOptions.Ports, command.Port{HostPort: port, ContainerPort: 5000})
runOptions.Ports = append(runOptions.Ports, command.Port{HostPort: port, ContainerPort: 5000, HostIP: host})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should-fix — remote-Docker regression. When DOCKER_HOST=tcp://remote:2375, this binding is applied on the remote daemon, so 127.0.0.1 there is unreachable from the cog client. --host 0.0.0.0 is the escape hatch but nothing warns the user. (cog predict was already broken for remote Docker — it connects to localhost — so this is a new issue only for serve.) Consider detecting a non-local DOCKER_HOST and emitting a console warning pointing at --host 0.0.0.0, or at least noting it in the --host help text.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — added docker.IsRemoteDockerHost() and cog serve now emits a console.Warnf when the daemon is reached over a non-local DOCKER_HOST (anything but unix/npipe), pointing out the bind lands on the remote host. Left cog predict's localhost assumption alone as pre-existing/out of scope.

Comment thread pkg/cli/serve.go Outdated
runOptions.Ports = append(runOptions.Ports, command.Port{HostPort: port, ContainerPort: 5000, HostIP: host})

displayHost := host
if displayHost == "0.0.0.0" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit — IPv6 display URL. displayHost only special-cases 0.0.0.0. An IPv6 value like ::1 interpolates to http://::1:8393 (missing brackets — invalid URI). Consider bracketing IPv6 literals or validating --host.

Also: this displayHost mapping and the --host defaulting are untested — extracting func displayHostFor(host string) string and table-testing it would cover both.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — extracted displayHostForServe and formatServeURL, which build the URL with net.JoinHostPort so IPv6 literals get bracketed (e.g. http://[fe80::1]:8393). Both are table-tested (TestDisplayHostForServe, TestFormatServeURL). As a bonus, --host 0.0.0.0 now prints the navigable http://localhost:PORT alongside the 0.0.0.0 URL.

Comment thread pkg/docker/command/command.go Outdated
type Port struct {
HostPort int
ContainerPort int
HostIP string // Host IP to bind to. Defaults to "127.0.0.1" if empty.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should-fix — extract a shared constant. "127.0.0.1" is duplicated as the default in 5 sites: this comment, serve.go:20, docker.go:457, run.go:41, and predictor.go:101. A single const DefaultHostIP = "127.0.0.1" here, referenced everywhere, removes the magic-string duplication and the drift risk.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — added const DefaultHostIP = "127.0.0.1" in command.go and referenced it from serve.go, docker.go, run.go, and predictor.go, removing the duplicated magic string.

Comment thread pkg/docker/docker.go Outdated
hostCfg.PortBindings[containerPort] = []nat.PortBinding{
{
HostIP: "", // use empty string to bind to all interfaces
HostIP: hostIP,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should-fix — untested core change. The empty→127.0.0.1 default and this HostIP propagation are the central behavior change of the PR, but containerRun has no unit test. Suggest extracting the PortBindings construction (the loop over options.Ports) into a pure helper like portBindingsFromPorts([]command.Port) nat.PortMap and table-testing it: empty HostIP → 127.0.0.1, explicit 0.0.0.0, explicit 127.0.0.1, custom IP, multiple ports.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — extracted portBindingsFromRunOptions (and exposedPortsFromRunOptions) out of containerRun and table-tested both in pkg/docker/ports_test.go: empty HostIP → 127.0.0.1, explicit 0.0.0.0, custom IP, IPv6, and multiple ports.

Comment thread pkg/docker/run.go
for _, portBinding := range inspect.NetworkSettings.Ports[targetPort] {
// TODO[md]: this should not be hardcoded since docker may be bound to a different address
if portBinding.HostIP != "0.0.0.0" {
if portBinding.HostIP != hostIP {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should-fix — brittle exact-string match. portBinding.HostIP != hostIP is stricter than the old != "0.0.0.0" filter (which matched in either direction). Docker runtimes (Docker Desktop VM proxy, rootless, future versions) may normalize the reported IP, causing a silent does not have a port bound to 127.0.0.1 failure on a container that did bind that port. Suggestion: when exactly one PortBinding exists for the target port, return it regardless of HostIP; only filter by hostIP when multiple are present.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08GetHostPortForContainer now falls back to the sole binding for the target port when only one exists, so a normalized/wildcard IP no longer trips a false does not have a port bound error. Scoped the fallback to empty/0.0.0.0 HostIP so a genuinely mismatched specific IP still errors; covered by new table rows.

Comment thread pkg/docker/run_test.go Outdated
},
}, nil)

hostPort, err := GetHostPortForContainer(t.Context(), testClient, "container123", 5678, "127.0.0.1")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit — table-driven + missing edge cases. Each of the 8 subtests repeats the full InspectResponse boilerplate; a single table with hostIP arg, mocked Ports, and expected (hostPort, errContains) would halve the file (AGENTS.md prefers table-driven). Missing cases: a custom (non-127/0.0.0.0) hostIP, IPv6, and Docker inspect returning an empty HostIP (would currently error — worth a row to document that).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f0f1a08 — rewrote TestGetHostPortForContainer as a single table with shared InspectResponse builders, and added the missing rows: custom non-loopback IP, IPv6 (::1), and empty-HostIP (now documents the single-binding fallback behavior).

- Restore host:port parsing for `cog exec -p` lost in the cog run→exec
  rename; support plain ports, IPv4, bare and bracketed IPv6, with port
  range and empty-host validation
- Extract shared command.DefaultHostIP constant (was duplicated)
- Narrow GetHostPortForContainer fallback to single wildcard/empty bindings
- Warn when serving via a remote Docker daemon; show a navigable localhost
  URL alongside 0.0.0.0; bracket IPv6 display hosts
- Rename generic serve `host` var to `serveHost`
- Add unit tests for port binding helpers, host parsing, remote-host
  detection, and serve URL formatting; make run_test table-driven
- Regenerate docs/cli.md and docs/llms.txt
@ask-bonk

ask-bonk Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

@ask-bonk

ask-bonk Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

LGTM

github run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants