How is the connection between server and client secured?

Two independent layers:

In short: TLS encrypts and authenticates the server; the connect token authenticates the client. (This is server-auth TLS + token auth, not mutual-TLS.)

Is an HTTP frontend end-to-end encrypted from the visitor to my local app?

No — like ngrok, for HTTP frontends TLS terminates at the server (it must, to answer ACME challenges and route by Host). The path is: visitor →(public TLS)→ server →(tunnel TLS)→ client →(plaintext, on your machine)→ local app. The server can see request content; the only unencrypted hop is client-to-localhost on your own machine. Run the server on infrastructure you trust. (TCP frontends are different — see below.)

I tunnel a TCP service with its own TLS (e.g. Postgres SSL). Is that end-to-end encrypted?

Yes. A TCP frontend is a raw passthrough — the server never terminates or inspects the connection, it just forwards bytes. So the application's own TLS handshake (Postgres SSL, an MQTT/AMQP TLS, etc.) happens directly between your client and your actual service; seikan only ever sees opaque ciphertext (double-encrypted on the server↔client hop, since the tunnel itself is TLS):

psql ──TLS (Postgres SSL, end-to-end)───────────────────────────► postgres
       carried as opaque bytes: public TCP port → server →(tunnel TLS)→ client → localhost:5432

For real end-to-end safety, connect with sslmode=verify-full so the client validates Postgres's own certificate (the cert presented is Postgres's, passed through untouched), and make sure the cert's SAN matches how you connect.

How private is a peer tunnel? Does anything ever touch the public internet?

No — that's the entire point of seikan serve --peer / seikan forward. A peer frontend has no domain and binds no public port; the server never accepts a public connection for it. Both sides only ever dial out to the server's :443, exactly like every other client, and the server relays bytes between their two sessions internally. Nothing about the backend service is reachable — or even discoverable — from the public internet at any point.

The two capabilities are split into independent one-time secrets: a provider token (register the backend) and a consumer token (relay a connection in). They're validated separately and are not interchangeable — holding the consumer token never lets you register a fake backend and intercept or inject traffic meant for real consumers. This matters most for the case that motivates the feature: sharing access to something like a database without also handing out the ability to impersonate it.

How are secrets stored?

API keys (sk_…) and connect tokens (sk_t_…) are 256-bit random and stored only as SHA-256 hashes in the server's bbolt database; raw values are shown once at creation. Validation is constant-time. Repeated bad credentials trigger a temporary per-IP lockout (brute-force protection).

How is the admin API protected?

It's served only on the reserved admin host over HTTPS, and requires either an administrator session (username + password) or a client key as Authorization: Bearer <sk_…>. Passwords are stored as bcrypt hashes, never recoverable. Failed attempts of either kind are rate-limited/locked out per IP.

What is the setup key, and how long does it live?

A brand-new server has no account to authenticate against, so something has to prove that whoever is filling in the setup form is the person who runs the machine. That's the setup key: the server prints it at first start and keeps it in <state-dir>/setup-key (mode 0600) — or uses SEIKAN_SETUP_KEY if you supply one, which is the usual container path. seikan-server setup-key prints it again.

It authorizes exactly one thing — creating the first administrator — and stops working the moment one exists: POST /api/setup answers 409 from then on, and the generated file is deleted. A key that leaks after setup is worthless. If you lose the admin password, the recovery path is seikan-server user passwd <username> on the host, not a new setup key.

Is seikan init without a key safe? Anyone can call that endpoint.

They can, and it gets them a row in a table. POST /api/register creates a request, not a credential: nothing is issued until an administrator approves it in the admin UI, and even then the client key is minted only when the requesting client comes back to collect it — so an approval nobody picks up leaves nothing behind, and no raw secret is ever written to disk.

Two secrets keep an approval attached to the right machine. The poll token, handed only to the requesting client, is what collects the key; knowing a request's id gets you nothing without it. The confirmation code is the other direction: it is printed on the requesting machine and shown in the admin UI, so the administrator can see that the row they are approving is the machine in front of them and not another request that arrived at the same moment. That check is the one thing the flow asks a human to actually do.

Requests expire after 15 minutes and are capped — 3 live per source address, 32 in total — so the endpoint can't be used to grow the database. Denying one tells the client and closes it out. If you'd rather not have the endpoint at all, issue client keys yourself and pass them with --api-key; the approval path is opt-in per client, not a mode the server is in.

Can I give other people scoped access (multi-tenancy)?

Yes, with client keys. An administrator issues them in the admin UI (Client keys page) or via POST /api/apikeys; each is a scoped tenant credential.

A tenant sets their client up either by requesting approval (seikan init --server admin.example.com, which mints them their own key when you approve it) or with a key you issue and hand over (--api-key sk_…); their add/list/remove are automatically scoped to their own frontends. Per-frontend connect tokens (sk_t_…) and the data plane are unchanged — ownership is purely an admin-API concern.

Can one tunnel serve several domains, or a wildcard?

Yes. An HTTP frontend can carry multiple domains and wildcards: seikan serve app.example.com,www.example.com 3000, or seikan serve '*.example.com' 3000. A wildcard matches a single label (a.example.com, not a.b.example.com). Certificates for wildcard frontends are issued per concrete subdomain on demand (TLS-ALPN-01/HTTP-01) the first time each subdomain is hit — so every subdomain you actually use must have DNS pointing at the server. There's no single *.example.com certificate (that would need DNS-01).

Is the metrics / pprof endpoint exposed?

No — it binds to 127.0.0.1:9090 by default (not public). Scrape it locally or over an SSH tunnel, or set SEIKAN_METRICS_ADDR="" to disable it.

What happens if two clients connect with the same token / domain?

It's a feature, not a conflict — behavior depends on the frontend type:

What should I avoid in production?

Don't use --insecure / SEIKAN_INSECURE (it disables server-cert verification). Keep ACME staging off once issuance works. Treat client keys and connect tokens like passwords; rotate by issuing a new key and revoking the old. Give each client its own key so revoking one doesn't disturb the others.