Tomcat terminates HTTPS on :443 from a PKCS12 keystore
(typically generated from a Let’s Encrypt certificate). No reverse
proxy is required. PostgreSQL is bound to localhost:5432 and
not exposed publicly.
OAuth 2.1 (auth code + PKCE). The OwnSona server runs both halves of the OAuth machinery in a single WAR:
org.kissweb.oauth in Kiss core):
validates the Authorization: Bearer JWT on every
/mcp request. Checks the JWS signature against the JWKS,
the iss and aud claims, the exp/nbf
times (with configurable clock skew), and any configured required
scopes. Failure writes a 401 with RFC 6750 / RFC 9728
WWW-Authenticate challenge naming the resource-metadata
document.
org.kissweb.oauth.as in Kiss
core, plus app-specific extensions in ai.ownsona.oauth):
issues access and refresh tokens. Exposes /oauth/authorize,
/oauth/token, /oauth/register (RFC 7591 dynamic
client registration), /oauth/jwks, and the RFC 8414 metadata
endpoint at /.well-known/oauth-authorization-server. The
AS’s persistent state (signing key, registered clients, refresh
tokens) lives in an INI file whose location is set by
OAuthAsIniFile; production installs set this to an absolute
path outside the deployed webapp so WAR redeploys can’t touch it.
The two app-specific pieces register at startup via
AsExtensions:
OwnsonaUserAuthenticator: checks
OWNSONA_LOGIN_USERNAME / OWNSONA_LOGIN_PASSWORD from
application.ini in constant time
(MessageDigest.isEqual). On success returns an
AuthenticatedUser whose subject is OWNSONA_USER_ID.
OwnsonaConsentProvider: supplies the display name
("OwnSona") and a uniform scope description (the AS asks
for no required scopes, so any scope a client lists gets the same
brief blurb).
There is no static-token shortcut and no ?token= URL
fallback — every request must go through the OAuth validation
path. See Connecting Claude and Connecting OpenAI for
the client-side end of the flow.
SecretScanner runs on every write (remember,
remember_batch, update_memory) and rejects text that
matches the shape of:
sk-, sk-ant-, sk-proj-).
ghp_, ghs_, fine-grained
github_pat_).
AKIA, ASIA).
xoxb-, xoxp-, ...).
AIza).
-----BEGIN PRIVATE KEY----- and
variants).
Best-effort; the calling client shouldn’t be forwarding secrets in
the first place. The check returns SECRET_REJECTED when it
fires.
The ownsona PostgreSQL role has
SELECT/INSERT/UPDATE/DELETE/TRUNCATE on memories, plus
CREATE ON SCHEMA public and ownership of memories (and
its sequence) so the auto-migrator can ALTER TABLE at runtime.
It is not a superuser.
Schema-level: every row carries user_id and every query filters
on it. Operationally: single-user mode pins every write to the
OWNSONA_USER_ID value (default "default"). Multi-user
is deliberately out of scope.