Skip to content

feat(tower): Add TOWER_HOST env var to bind to different interfaces#721

Open
otherview wants to merge 2 commits intocluesmith:mainfrom
otherview:feat/TOWER_HOST_env_var_binding
Open

feat(tower): Add TOWER_HOST env var to bind to different interfaces#721
otherview wants to merge 2 commits intocluesmith:mainfrom
otherview:feat/TOWER_HOST_env_var_binding

Conversation

@otherview
Copy link
Copy Markdown

Summary

Add TOWER_HOST environment variable to configure the Tower server bind address.

Tower currently hardcodes 127.0.0.1. With TOWER_HOST=0.0.0.0, Tower binds to all network interfaces, enabling access from other machines on the network (e.g., Docker, VMs, LAN).

Changes

server-utils.ts — New validateHost() function that accepts 127.0.0.1, 0.0.0.0, localhost, valid IPv4, and bracketed IPv6 literals. Rejects hostnames and malformed values with a clear error message.

tower-server.ts — Reads TOWER_HOST env var (defaults to 127.0.0.1), validates it via validateHost(), and passes the result to server.listen(). When bound to 0.0.0.0, log output still shows localhost for local UX.

tower-host.test.ts — Integration tests covering default behavior, 0.0.0.0 binding, and invalid host rejection.

arch.md / agent-farm.md — Documentation updates for the new env var.

Usage

TOWER_HOST=0.0.0.0 afx tower start

@waleedkadous
Copy link
Copy Markdown
Contributor

Thanks for the PR and the clean implementation — tests, docs, and validation are all in good shape.

That said, I want to share why Tower binds to 127.0.0.1 today: it's a deliberate design choice, not an oversight. Tower has no password or authentication, and the surface area is enormous — the WebSocket terminal endpoints let any connected client spawn a shell and do basically anything the user running Tower can do. Localhost-only binding is the entire security model right now. Punching a hole in it via env var, even opt-in, makes it very easy for someone to expose remote shells on their LAN without realizing the implications.

For remote access, we have cloud.codevos.ai, which gives you secure remote Tower access from anywhere (not just your local network) with proper auth in front of it. That's the recommended path for the use cases this PR seems aimed at.

Could you tell me more about your specific use case? If it's Docker/VM/LAN, cloud.codevos.ai likely solves it better. If there's a scenario it doesn't cover, I'd love to hear it — that would help us figure out the right shape for a future change (which would probably need to come bundled with an auth story rather than a bare env var).

@otherview
Copy link
Copy Markdown
Author

Hi @waleedkadous -thanks for the thoughtful feedback.
I agree with the security rationale for localhost-by-default.

My blocker is slightly different from “remote Tower access”: I need Tower to orchestrate agents running inside a local sandboxed Docker environment, where the relevant filesystem/services/network only exist in that containerized runtime. ( I don't install / run ai tooling directly on host as a practice 😅 )

Cloud access is useful, but it doesn’t replace local sandbox execution for this workflow.

I believe the ask is not really about “expose Tower broadly,” but “support explicit local-container bridging safely.”

I also agree a bare host-bind env var is too easy to misuse. I’m happy to rework this in a safer shape, for example:

  • allow non-local bind only behind an explicit unsafe opt-in (flag/env);
  • emit high-visibility startup warnings when enabled;
  • optionally require a minimal auth gate (token/header) for non-local bind.

On that note, I'd be happy to also contribute with my “secure local sandbox” profile to reduce risk for this use case, a Docker/Makefile flow where:

  • all Codev services run in containers;
  • mounts are minimal/allowlisted (project + explicit state dirs only);
  • no host SSH keys or broad home mounts by default;
  • network exposure is explicit/opt-in (Tower port published only when requested);
  • required tools are preinstalled in-image.

This preserves localhost-only as the secure default in core, while giving users a safer documented path for local containerized execution + host browser access.

But likely that would fit in a different PR, let me know what you think !

@waleedkadous
Copy link
Copy Markdown
Contributor

Thanks @otherview — the Docker sandbox use case is legitimate, and "AI tooling never on the host" is a really cool way to use Codev.

A few thoughts on shape:

Naming — frame it as "bridge mode" not "host override"

Rather than a generic TOWER_HOST env var (which reads as "expose Tower however you want"), can we make the intent explicit in the names? Something like:

  • BRIDGE_MODE=1 — the explicit opt-in flag. Without this, no non-localhost bind is possible regardless of other vars.
  • BRIDGE_TOWER_HOST=0.0.0.0 — only consulted when BRIDGE_MODE=1 is set.

This makes the use case self-documenting: anyone reading the env or the code immediately sees "ah, this is for container-bridging," not "this is a generic way to expose Tower." It also makes the docs much clearer — we can write a "Bridge Mode" section rather than burying caveats in a TOWER_HOST description.

Plus the loud startup warning when bridge mode is enabled, and tightening the IPv6 validation (current regex accepts [anything]).

On the Docker sandbox profile

Yes please — separate PR, very welcome. The "secure-by-default container profile with allowlisted mounts and explicit port publishing" is exactly the kind of thing that makes bridge mode safe to use, and it deserves its own focused review.

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.

2 participants