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.
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.
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.
.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.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..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..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.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:
proximo.auth, which answers
401 without credentials. proximo neither requires nor warns about that
pairing: sharing without it is the model working.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.
These are requirements on whatever transport the team runs, stated as properties. Appendix — one transport that satisfies the requirements maps them onto one product.
<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.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.
Constraint 7, and the proportionate posture in Scope and assumptions.
<machine>.<suffix> and has MaxPathLen 0. Both exclude every IP range
(0.0.0.0/0, ::/0), every email address and every URI: a name type absent
from the permitted subtrees is unrestricted.proximo config csr; the CSR reaches a
custodian over any channel, since it is public; the custodian decrypts the root
into a temporary directory on an ordinary laptop, signs with the script checked
into this repository (below), and removes the plaintext; the intermediate, also public,
returns over any channel; the machine runs proximo config intermediate. The
script takes the Peer suffix and the machine label as arguments and names
neither. A reinstalled machine repeats the ceremony with no special case, under
the same label; its old intermediate is not revoked and lapses on its own.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
peer-intermediate fails 30 days
ahead.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.