6.9 Security model

6.9.1 Transport

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.

6.9.2 Authentication

A single bearer token, OWNSONA_API_TOKEN, set in application.ini. Two presentation paths, in order:

  1. Preferred: 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.
  2. Fallback: ?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.

6.9.3 Secret rejection

SecretScanner runs on every write (remember, remember_batch, update_memory) and rejects text that matches the shape of:

Best-effort; the calling client shouldn’t be forwarding secrets in the first place. The check returns SECRET_REJECTED when it fires.

6.9.4 Database privileges

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.

6.9.5 User isolation

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.