From dcb48be419dd75339944855aee9b0bb5cafac184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 25 Aug 2026 15:55:30 +0000 Subject: [PATCH] the agent calls the plugin, and private is a network you name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit two conclusions from working through it. the agent never touches NPM and never holds a credential. it calls this plugin, which owns keeping up with NPM's API — that is what having a plugin is for. the reads stay a thin proxy; the expose route deliberately is not, because that is where "reachable only from the private network" gets turned into a DNS record, a certificate and a proxy host. writing that down once here is the safety argument: the alternative puts it in the agent's memory, and you find out it forgot when a hostname is reachable that should not be. and "tailnet" was too specific. officer can set up tailscale and knows its range, so it can prefill — but a company with a VPN already in place has their own, and must be able to say so. what gets stored is a named network and its address ranges; NPM access lists take ranges, so it costs nothing. the certificate half is unchanged, because what forces the DNS proof is that the name is not publicly reachable, whoever runs the network. also recorded: NPM's DNS provider list is fixed and unverified here, and so is whether a godaddy account still gets DNS API access. Co-Authored-By: Claude Opus 5 Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043 --- README.md | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 049ceb3..3cc10e1 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,36 @@ So **TLS on a VPN-only hostname is impossible without the credential.** That is design, it is the shape of the problem. The alternative is not "the same thing without a key", it is a publicly exposed address with DNS you manage yourself. +## How the agent asks, and what it is never told + +The agent calls **this plugin**, and never Nginx Proxy Manager. Keeping up with NPM's API — its version, +its shape, the day it changes — is this plugin's job, and the whole point of having one. + +Most of what it exposes is a thin proxy over NPM's reads, and should stay thin. **The expose route is +the one place it deliberately is not.** The agent sends what was asked for — a hostname, what it should +point at, and which network may reach it — and the plugin turns that into the three separate things it +actually means: a DNS record, a certificate, a proxy host. + +That split is the whole safety argument. "Reachable only from the private network actually means these +three things" gets written down once, here, where it can be read and tested. The alternative is teaching +the agent NPM's own request format, which puts that knowledge in the agent's memory instead — and you +would discover it had forgotten only when a hostname is reachable that should not be. + +The agent is never given a credential. Not NPM's, not the DNS provider's. It says the sentence; the +plugin holds the keys and does the work. + +## "Private" is a network the user names, not a tailnet + +Officer can set up a Tailscale network for you, and it knows that network's address range — so it can +fill this in without asking. **That is a convenience, not the model.** Somewhere with a VPN already in +place has their own ranges and nothing to do with Tailscale, and they must be able to say so. + +So what gets stored is a **named network and the addresses it covers**, and the request is *"expose this +to "*. NPM's access lists take address ranges, so this costs nothing to support properly. + +The certificate half is unchanged by any of it. What forces the DNS proof is that the name cannot be +reached from the public internet — true of anybody's private network, whoever runs it. + ## Read-only is a state, not the design The first round creates nothing, edits nothing and deletes nothing, and the original README argued that @@ -152,10 +182,13 @@ Verified against the live instance: a wrong credential comes back `likelyAuth = renders it yet, so the connection has to be inserted by hand for now. - **`db:push` has not been run**, so `proxy_manager_connection` does not exist in Postgres yet. - **Redirections, streams, 404 hosts and access lists** have routes and no panels. -- **All of the above.** No domain registry, no DNS provider credentials, no agent, no write path. The - vision in "What this is for" is entirely unbuilt — the sentence a user should be able to say is the - thing to design next, and where domains and provider keys live is an open boundary question rather - than a settled one. +- **All of the above.** No domain registry, no DNS provider credentials, no named networks, no agent, + no expose route. Everything from "What this is for" through "Private is a network the user names" is + agreed and entirely unbuilt. +- **Two things to check before building the DNS half.** NPM can only run the DNS proof for providers it + ships support for — a fixed list, not "any provider" — and if yours is missing, the proof has to be + done elsewhere and a finished certificate handed over, which is a much larger job. Neither the list + nor whether a given GoDaddy account still has DNS API access has been verified here. - **Certificate expiry notifies nobody.** `daysUntilExpiry` is computed in `api/router.ts` and thrown away when the panel closes. NPM renews at 30 days; inside 14 means renewal has actually failed and the domain goes dark at zero. Nothing on the machine will say so.