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.
A single bearer token, OWNSONA_API_TOKEN, set in
application.ini. Two presentation paths, in order:
Authorization: Bearer <token> HTTP header.
Used by curl, the CLI, the smoke test, the OpenAI Responses API, and
any client that can set custom headers.
?token=<token> URL query parameter. Used
by clients (e.g. ChatGPT’s connector UI) that can’t supply custom
headers. The access log’s pattern is deliberately set to
"%m %U %H" so query strings are not logged — the token
doesn’t end up in tomcat/logs/ or in nightly backups.
Both paths compare in constant time via MessageDigest.isEqual.
Any failure returns HTTP 401 with WWW-Authenticate: Bearer.
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.