proximo

Sharing a route with colleagues

← back to docs index

A developer labels a route, and a small group of colleagues reach it from their own machines — in a browser, with a real login, cookies and WebSockets — at a stable name that says whose machine it is on. The transport between the machines is a mesh the team operates. proximo never installs it, never configures it and never talks to it.

Scope and assumptions

This is development tooling for a small, trusted group. The colleagues already work together, every one of them may share their own routes with the others, and what a colleague reaches is somebody's work in progress on their own machine. The feature exists to simulate a production environment, not to be one. People outside the group are not served: a public link for a client or an external tester is a different product with a different threat model.

The security posture is proportionate to that. A ceremony nobody performs, a custody rule nobody follows, or a hardware dependency standing between a machine and its enrolment is a failure of the design, not caution. The one place this does not relax is a colleague's trust store, which serves their real browsing (constraint 7).

A relayed path is a normal operating condition. Colleagues sit behind home routers, phone hotspots and carrier-grade NAT, and the design is judged on the relayed path (constraint 10).

Out of scope: developing against a colleague's app as a backend of your own stack, reaching machines in the mesh that do not run proximo, operating the mesh control plane, and rewriting response bodies.

Constraints

Each is a property proximo keeps, stated without the mechanism that currently keeps it; the guides cite them by number where they bound a behaviour.

  1. A colleague reaches a route by a peer-qualified name, never by the local one. .test cannot be delegated, proximo's DNS server answers every name under the TLD with 127.0.0.1, and two machines running a project with the same name is the ordinary case. The local name and the peer name are two names for one service.
  2. Sharing is opt-in per route, by a label, in the grammar proximo already uses for its labels. Nothing is shared because the mesh happens to be up.
  3. Sharing is durable, not a session. The label is the switch. No link expires, and nothing materialises during an incident.
  4. proximo never rewrites a response body, nor a Set-Cookie, nor the Host header. An app that builds URLs from a configured base, or pins a cookie to a Domain, is documented as unshareable as it stands, not patched.
  5. No real domain name is ever written into this repository. The Peer suffix, the machine label and the address are configuration supplied by whoever installs proximo, with no default. Documentation uses placeholders.
  6. Nothing the mesh configures may become a machine's default resolver. proximo's DNS server answers .test on the loopback, so a resolver that captures unmatched queries takes down every local route on that machine. The test is the same whatever the transport: .test still resolves locally while the mesh is up.
  7. What a colleague installs into their trust store can sign only beneath the Peer suffix, in every name type. An anchor without that limit exposes a colleague's whole browsing, not the shared app. Proportionality governs how the signing key is kept, never what it may sign.
  8. No wildcard certificate may cover the Peer suffix or the label immediately beneath it. A wildcard sitting immediately above a Public Suffix List boundary is refused by Chrome once that boundary is listed, and the suffix's parent may be listed in the future. Peer leaves carry exact names.
  9. No part of this capability may put the local .test path at risk. A change made for sharing is measured against local breakage first. Every new path is inert when unconfigured — a machine that has not opted in executes no new behaviour — and .test behaviour is asserted under both states, configured and not, rather than trusted. This orders the risk; it does not veto the work.
  10. A relayed path is normal, and no per-network remedy is required. No port forward, UPnP, advertised external address or static endpoint is a prerequisite or a documented fix. Every requirement here, the interactive one included, is judged on the relayed path. A direct path is an improvement the transport may deliver, never something the design relies on.

Who can reach a shared route

The mesh grants access to a machine, never to a route: every shared route on a machine answers on one address and one port, and the route is chosen by a Host header inside a TLS session the mesh never opens. A mesh can enforce application-level access only for the protocols it terminates. So:

What a refused colleague sees: a timeout from a machine that has not admitted them, a 401 from proximo.auth, and the same nameless certificate for an unshared name and an invented one.

What the mesh must provide

These are requirements on whatever transport the team runs, stated as properties. Appendix — one transport that satisfies the requirements maps them onto one product.

  1. A layer-3 mesh. Each machine holds an address the others can reach, and reaches theirs, over relays when no direct path exists (constraint 10).
  2. The subtree <machine>.<suffix>, at any depth, is routed to that machine's configured address on port 5354 for every resolving peer — and the mesh never becomes a machine's primary resolver (constraint 6). It forwards; it hosts no records about the machines.
  3. Between the machines that take part, it admits exactly TCP 443 and port 5354 over UDP and TCP to the publishing peers.
  4. Membership follows the identity provider. Disabling a person removes their machines from the mesh within the login expiry the mesh is configured with.

Taking part

Every colleague who opens shared routes sets the Peer suffix and the team root, and trusts it. A machine that shares its own routes also gets a machine label, an address and an intermediate. The mesh routes its subtree to it only once its peer DNS service answers: created first, that routing would exist, carry the right flags and resolve nothing. The steps, in order and with the commands, are in Setting up sharing for a team.

The team root and the intermediates

Constraint 7, and the proportionate posture in Scope and assumptions.

The custodian's script is tools/team-ca/team-ca.sh, which needs only Docker. It mints the team root once, and signs each machine's CSR into its intermediate, in exactly the shape config team-root and config intermediate accept:

tools/team-ca/team-ca.sh root <suffix>        # team-root.crt + team-root.key; encrypt the key
tools/team-ca/team-ca.sh sign <suffix> <machine> team-root.crt team-root.key machine.csr > intermediate.crt

Limits for whoever runs the team

Appendix — one transport that satisfies the requirements

NetBird, self-hosted, verified end to end between two machines, one of them relayed. It is one transport that satisfies the requirements, not a requirement. Every value is a placeholder.

Requirement NetBird
1. Layer-3 mesh the NetBird client on each machine; its relay carries peers no direct path reaches
2. Subtree routing one nameserver group per publishing peer: domain <machine>.<suffix>, nameserver <address> with port = 5354, primary = false, search_domains_enabled = false, distributed to the group of resolving peers. Write all three explicitly and confirm them in the plan: an omitted optional field can carry a default that silently substitutes port 53 or turns the group primary. Leave the account's DNS domain empty. NetBird's compiled-in search domain still appears in the search list; it appends only to unqualified names and cannot capture a .test query
3. Access one group holding the machines that take part, one group-to-group policy from it to itself, bidirectional, TCP 443 and TCP+UDP 5354, nothing else. Declaring a user's groups replaces membership not declared; verify membership per peer afterwards
4. Membership developers' machines enrol through SSO with interactive login, under a tenant identity; peer_login_expiration is the login expiry above; setup keys only for headless machines; the local break-glass account holds no peers

Enumerating a nameserver group is not a test that it resolves: test with dscacheutil -q host -a name <name> on macOS (never dig, which bypasses the scoped resolvers) and always beside a control name that should not resolve.