Embedded, Loopback, Self-Authenticated Venue
This is the deployment shape for a venue embedded inside a desktop or single-user application — launched as a subprocess, reachable only on localhost, and serving exactly one owner. It's the shape behind the GetMine desktop app and applies to any app that bundles a venue for one user.
The goal is a venue that is private to its host machine and its owner: no anonymous access, not reachable off-box, and no browser on some other origin able to reach in. The app authenticates itself as its owner and the venue's job is simply to verify and enforce.
The security model: no token minting
An embedded venue does not hand out per-launch tokens. Instead the embedding app holds its own Ed25519 key pair and self-signs a bearer JWT, which the venue verifies with no shared secret and no prior registration:
- The app generates a key pair once and derives its
did:keyfrom the public key. - For each session it signs a JWT whose
subclaim is thatdid:keyand whosekidheader carries the same public key. The venue's self-issued JWT verification checks that thekidandsubagree — proving the signer owns the DID — and enforces the token's expiry and audience. - The token's
audclaim must name this venue's DID (read it from/.well-known/did.json). A token minted for another venue cannot be replayed here.
Every request then authenticates as the app owner's DID; there is no shared
public identity to fall back to.
The recipe
Three configuration settings turn a default venue into this shape:
{
"bindAddress": "127.0.0.1",
"allowPrivateNetwork": false,
"auth": {
"public": { "enabled": false }
}
}
| Setting | Value | What it closes |
|---|---|---|
bindAddress | 127.0.0.1 | Binds the HTTP listener to loopback only, so the venue is unreachable from the LAN. (Omitted, a venue binds all interfaces.) |
allowPrivateNetwork | false (default) | Suppresses the Access-Control-Allow-Private-Network header, so a public web origin cannot reach the venue on localhost from the browser. |
auth.public.enabled | false | Removes the anonymous/shared public identity. Every request must carry a valid bearer token; an unauthenticated request gets 401. |
With all three set, the only way to reach the venue is a process on the same machine presenting a bearer token the venue accepts — i.e. the owner app.
bindAddress is the socket the listener binds to (restrict it to loopback).
It is distinct from hostname, the venue's advertised public host used to
derive baseUrl and the DID. An embedded venue typically sets no public
hostname, so its identity stays its did:key.
Secrets belong to the owner, not to public
An embedded venue's secrets — LLM API keys, provider credentials — are
bootstrapped under the owner's DID, not the public identity. Config
pre-populates the per-user encrypted secret stores at startup, keyed by DID:
{
"secrets": {
"did:key:z6MkOwnerAppKey...": {
"ANTHROPIC_API_KEY": "sk-ant-...",
"OPENAI_API_KEY": "sk-..."
}
}
}
Top-level keys resolve as follows: "venue" → the venue's own DID, "public" →
the public identity, and anything else verbatim as a literal DID. Because the
app authenticates as its owner DID, secret references (s/ANTHROPIC_API_KEY)
resolve under that identity at invocation time. Nothing lands under public, so
there is no shared credential surface.
Never commit production secrets to a checked-in config. Keep the embedded venue's config (with its bootstrap secrets) in a per-user, non-tracked location the app manages.
Putting it together
A complete embedded-venue config:
{
"name": "GetMine Local Venue",
"port": 8080,
"bindAddress": "127.0.0.1",
"allowPrivateNetwork": false,
"store": "/Users/alice/Library/Application Support/GetMine/venue.etch",
"seed": "…ed25519 hex seed for a stable venue identity…",
"auth": {
"public": { "enabled": false }
},
"secrets": {
"did:key:z6MkOwnerAppKey...": {
"ANTHROPIC_API_KEY": "sk-ant-..."
}
}
}
This venue:
- listens on loopback only, unreachable off-box;
- rejects browser Private-Network reach-in;
- requires a bearer token for every request — no anonymous access;
- accepts the owner app's self-signed JWT (audienced to this venue's DID);
- resolves the owner's secrets under their DID.
The app's responsibilities, in turn:
- Generate an Ed25519 key pair once and persist it.
- Derive its
did:keyand read the venue's DID from/.well-known/did.json. - Sign a bearer JWT per session —
sub= itsdid:key,kid= its public key,aud= the venue's DID, with an expiry — and send it asAuthorization: Bearer <jwt>. - Bootstrap its secrets under its own DID (via the config above, or
secret:setas the authenticated owner).
See also
- Configuration Reference — every venue config key and its default.
- Authentication — the auth primitives this recipe composes: self-issued JWTs and public access.
- Persistence — the
storeandseedsettings for a stable venue identity across restarts.