18 KiB
DWH REST Per-Installation Authentication — Design
Date: 2026-08-20
Status: Approved by the owner
Purpose
Replace the single static X-API-Key currently protecting the PSD /dwh/ route with a reusable,
server-side authentication component that assigns one independently revocable credential to each
ThothII installation.
The design must preserve the existing portable connector contract. A ThothII installation on
macOS, Windows, or Linux continues to send X-API-Key over HTTPS when its selected transport is
rest_api. Installations using postgres_direct or ssh_tunnel are unaffected.
The component belongs in the ThothII source repository so that it can be reused on another DWH
server, but it is optional server infrastructure. It is not part of the portable application
runtime and is not started or stopped by tht.
Confirmed context
- The exposed credential must be considered compromised and must eventually be revoked.
- The current PSD route accepts one shared static
X-API-Keythrough Nginxauth_request. - The Mac installation must continue to access PSD through REST.
- Future remote ThothII installations will also use REST and cannot depend on an SSH tunnel.
- The new ThothII installation on the PSD server will use
postgres_directwith a dedicated, demonstrably read-only DWH role. - The current self-issued TLS certificate remains in use. Clients that do not already trust its
issuer require a separately delivered
TLS_CA_FILEand must verify the approved fingerprint. - There is no separate test environment. This is accepted and is not itself a blocker.
- Existing ThothII sessions, derived indexes, and model caches are test data. They are not migrated or protected by session-specific backup work.
- The legacy stack remains unchanged until the deployment program reaches its explicit stop gate. This is an authorization boundary, not a session-preservation requirement.
Scope
This design includes:
- the reusable
dwh-authservice and its administrative command; - a protected file-based credential registry;
- Nginx
auth_requestintegration; - one credential identity per ThothII installation;
- creation, delivery, rotation, optional expiry, and revocation;
- a dual-key transition from the exposed shared credential;
- generic and PSD-specific operator documentation;
- automated tests and production-safe acceptance checks.
This design does not:
- change the ThothII REST header or connector protocol;
- add DWH credential administration to the portable
thtCLI; - place server credentials in a workspace repository;
- replace PostgreSQL grants, RLS, or the PostgREST read-only role boundary;
- preserve or migrate legacy ThothII sessions, Qdrant indexes, or Ollama caches;
- change the current certificate or resolve the separate
.itversus.compublic-origin issue; - authorize any current server, Nginx, database, or service mutation.
Alternatives considered
Extend the portable tht CLI
This would provide a single operator command surface, but it would distribute server-only DWH administration code to every macOS and Windows installation. The DWH route also has a lifecycle independent from the local ThothII application. This option is rejected.
Maintain a manual Nginx key map
This is initially small, but it encourages plaintext credentials in Nginx configuration and makes atomic rotation, redacted inventory, and reliable revocation harder. This option is rejected.
Use SQLite
SQLite is not a declared project dependency and is unnecessary for the expected registry size and write rate. Introducing a database would add packaging, migration, backup, and corruption-recovery work without improving the required contract. This option is rejected.
Selected approach
Add a standalone, standard-library Go component with a protected file registry. On PSD it runs as
an independent systemd service and communicates with host Nginx through a Unix socket.
Component and lifecycle boundary
The repository will contain a separate component, expected under tools/dwh-auth/, plus generic
deployment templates and documentation. Its build and tests are isolated from the portable
application entrypoints.
On PSD:
dwh-authruns under a dedicated, unprivileged system account;systemdowns its lifecycle;- the service is not included in the canonical ThothII Compose stack;
- the registry resides outside both the Git checkout and ThothII application data;
- the service receives read-only access to active credential records;
- Nginx is the only runtime caller and reaches it through a permission-restricted Unix socket;
- stopping, updating, or replacing ThothII does not interrupt
/dwh/authentication.
The same source may be deployed on another Linux DWH server. Platform-specific service packaging
beyond the approved PSD systemd deployment is not required by this implementation.
Credential model
One credential identifies one ThothII installation, not one human user. An installation may have more than one temporarily active generation during rotation.
The external key contains a version marker, a public key ID, and a random secret:
thtdwh_v1.<public-key-id>.<random-secret>
Requirements:
- the public key ID is generated by the tool and contains only a strict safe alphabet;
- the secret is generated with the operating system cryptographic random source;
- the secret has at least 256 bits of entropy and uses an unambiguous transport-safe encoding;
- the complete header has a fixed maximum length;
- installation IDs are operator-provided, non-secret, unique labels and must not contain personal or clinical information;
- descriptions are optional non-secret operator metadata.
Because the secret is a uniformly random 256-bit value rather than a human password, the registry stores a SHA-256 digest and verifies it with a constant-time comparison. No password hashing or external cryptographic runtime dependency is needed.
Protected file registry
The registry has versioned active and revoked records:
<registry-root>/
active/<public-key-id>.json
revoked/<public-key-id>.json
An active record contains:
- schema version;
- public key ID;
- installation ID and optional description;
- encoded SHA-256 digest;
- creation timestamp;
- optional expiry timestamp, absent by default.
A revoked record additionally contains the revocation timestamp and a non-secret reason. The original key never appears in either directory.
Multiple key IDs may refer to the same installation ID during a rotation. Creation uses an
exclusive temporary file, file flush, directory-safe atomic rename, restrictive ownership and
mode checks, and refusal of symlinks or unsafe paths. Revocation atomically moves a record from
active to revoked on the same filesystem. A malformed or unsafe record never authenticates.
The runtime service does not update registry files. Successful and failed use is recorded only in the sanitized service journal, avoiding per-request writes and keeping the service's registry access read-only.
Administrative interface
The standalone binary provides a server-only administrative surface equivalent to:
dwh-auth key create --installation-id ID --description TEXT --output ABSOLUTE_FILE
dwh-auth key import --installation-id ID --from-file ABSOLUTE_FILE
dwh-auth key list [--json]
dwh-auth key revoke --key-id ID --reason TEXT
dwh-auth key status --key-id ID [--json]
dwh-auth check
The exact syntax will be frozen in the implementation plan and tests. The interface must obey these invariants:
- secrets are never accepted as command-line values;
createwrites the generated credential once to a new absolute file with restrictive access;importreads an existing protected file without displaying its content;- normal stdout contains only non-secret identifiers, abbreviated fingerprints, status, and paths;
- JSON output is pristine and contains no credential value or digest;
- an existing output file is never overwritten;
- listing and status commands never expose digests;
- errors and logs pass explicit secret-leak regression tests.
Provisioning and delivery
The operator creates one key for each installation and transfers it through an approved protected channel, such as an organizational password manager, a secret manager or MDM, or authenticated file transfer when the recipient has suitable access. Email, ordinary chat, and ticket bodies are not approved delivery channels.
On the recipient installation:
- the normal interactive path saves the value through authenticated Workspace management, where ThothII keeps it in the installation-local encrypted workspace vault;
- a supported headless deployment may place it in a protected file referenced by
API_KEY_FILE; - the credential never enters the shared workspace repository or ordinary environment values;
- the private CA is delivered separately and configured with
TLS_CA_FILEwhen the certificate chain is not already trusted; - the operator verifies the documented certificate fingerprint before trusting the CA file.
After configuration, the recipient runs the existing workspace connection test against the
harmless /rpc/ping diagnostic. Only after the server and recipient both confirm the public key
ID may the one-time delivery copy be removed according to the organization's secret-handling
procedure.
Expiry and rotation
Keys have no automatic expiry by default. This avoids unplanned outages for remote installations that may not be continuously administered. An optional expiry timestamp is supported for sites whose policy requires it; an expired key is rejected without fallback. Administrative status output warns about approaching expiries without exposing secrets.
Normal zero-downtime rotation is:
- create a second key generation for the installation;
- deliver and configure it on the client;
- verify
/rpc/pingand the sanitized server key ID; - revoke the earlier key;
- prove that the revoked key receives
401; - remove temporary delivery material after recipient confirmation.
Request flow and Nginx contract
remote ThothII
-> HTTPS /dwh/ with X-API-Key
-> Nginx auth_request subrequest
-> dwh-auth over a Unix socket
-> valid: Nginx proxies the original request to internal PostgREST
-> invalid or revoked: Nginx returns 401
-> authenticator fault: Nginx returns 503
dwh-auth authenticates only. It does not proxy the original request, read a clinical response,
or connect to PostgreSQL. PostgREST remains inaccessible as a public direct backend.
Nginx integration must:
- forward only the bounded credential header to the internal verification endpoint;
- suppress the original request body in the auth subrequest;
- discard client-supplied identity or audit headers;
- accept only the authenticator's success result;
- return the same generic
401for missing, malformed, unknown, expired, and revoked keys; - map authenticator or registry faults to
503, never to permissive access; - apply bounded failure-rate protection;
- log only timestamp, result, request metadata already approved for the route, and the verified public key ID;
- preserve the existing PostgREST path and response contract for successful requests.
The authenticator returns a verified public key ID only after successful authentication. Nginx may use that value in its sanitized access log but does not need to forward it to PostgREST.
Fail-closed behavior
The following conditions deny access:
- header absent, duplicated, oversized, malformed, or encoded incorrectly;
- unknown public key ID;
- digest mismatch;
- revoked or expired record;
- unsafe permissions, symlink, malformed JSON, schema mismatch, or inconsistent record identity;
- unreadable registry;
- service startup or runtime failure.
Credential failures return a generic 401. Infrastructure failures return 503 after the Nginx
mapping is applied. Neither response reveals whether an installation ID exists. Health reporting
is local-only and does not weaken the authentication decision.
PSD dual-key transition
The exposed shared credential is represented temporarily as legacy-shared. It is imported only
from an approved protected source and is never placed in a command argument, terminal output,
document, or evidence file. Because the existing value does not use the new versioned key
format, it is stored as the only permitted legacy_raw record. The service compares the digest of
the complete opaque legacy header only for that reserved record. After legacy-shared is revoked,
no unversioned credential is accepted.
The production route does not switch to the new authenticator until all of the following hold:
- protected backup and exact rollback instructions exist for the affected Nginx and service configuration;
- the new service passes local synthetic checks while disconnected from the public route;
legacy-sharedpasses/rpc/pingthrough the candidate authentication path;- a new per-installation Mac key passes the same diagnostic;
- a random invalid key is rejected;
nginx -tpasses and the authorized operator approves reload.
After the Mac uses its new credential successfully for the agreed observation window,
legacy-shared is revoked. Acceptance requires a positive test with the new key and a negative
test proving 401 with the legacy key. The new secret must not appear in service, Nginx, shell, or
application logs.
Rollback during the dual-key window restores only the reviewed authentication route and service configuration needed to keep REST clients working. It does not preserve or restore legacy ThothII sessions, indexes, or model caches.
PostgreSQL authorization boundary
Successful API-key authentication is not proof of read-only database authorization. The existing
PostgREST role, exposed schemas, function privileges, default privileges, and direct network
reachability remain subject to the catalog-only survey activity. Project execution cannot treat
dwh-auth as a substitute for a demonstrably read-only dwh_reader boundary.
Verification strategy
Automated tests use synthetic credentials and no clinical data. They cover:
- key generation entropy and syntax;
- create, import, list, status, optional expiry, rotation, and revocation;
- constant-time digest verification;
- missing, duplicate, malformed, oversized, unknown, expired, and revoked credentials;
- corrupt records, unsafe modes, symlinks, path traversal, and partial files;
- atomic creation and revocation under concurrent reads;
- clean failure when the registry or socket is unavailable;
- absence of credential values and digests in human output, JSON, errors, and logs;
- Nginx
auth_requestintegration against a synthetic upstream and/rpc/pingfixture; 401for credential failures and503for infrastructure failures;- unchanged successful request path and response body;
- independent service lifecycle from the ThothII stack.
Portability regression checks prove that:
- the existing REST connector still sends the same
X-API-Keyheader; postgres_directandssh_tunnelconfiguration remain unchanged;- canonical local and server ThothII Compose rendering does not acquire
dwh-auth; - macOS and Windows installation contracts do not require the new binary or files;
- existing
tht, backend, frontend, and harness gates remain green in proportion to the touched source boundaries.
There is no separate PSD test environment. Local synthetic and integration tests precede a
bounded, reversible production rollout using only /rpc/ping. The absence of a test environment
does not trigger preservation work for legacy test sessions.
Documentation deliverables
Implementation is incomplete until the following clear, secret-free documents exist and pass their documentation checks:
- a generic server installation and administration guide for
dwh-auth; - a generic enrollment guide for issuing a key to a macOS, Windows, Linux, or headless ThothII installation;
- exact GUI and
API_KEY_FILEconfiguration paths; - a TLS guide covering
TLS_CA_FILE, trusted delivery, fingerprint verification, renewal, and coordinated client updates; - a PSD runbook for backup, installation, legacy import, dual-key activation, Mac update, observation, revocation, negative testing, and rollback;
- a troubleshooting table for
401,503, TLS failures, revoked keys, and unreadable registry records; - sanitized evidence templates that never request secret values or digests;
- links from the existing local, server, workspace, and final unified user manuals.
Examples use synthetic hostnames, IDs, fingerprints, and credentials. No real key, certificate body, password, connection string, or clinical result is committed.
Acceptance criteria
The design is implemented only when:
- each REST installation can be identified and revoked independently;
- no plaintext server credential is stored in the registry;
- the portable ThothII connector contract is unchanged;
- the PSD ThothII server continues to select
postgres_direct; - stopping ThothII does not stop
dwh-author/dwh/authentication; - the authenticator fails closed and its logs are sanitized;
- the dual-key production procedure proves both positive and negative outcomes;
- the exposed legacy credential is demonstrably rejected;
- the current TLS limitations are documented precisely rather than silently bypassed;
- database read-only authorization is independently verified;
- all documentation deliverables are reviewed and reproducible by an operator who did not write the implementation.
Authorization gates
Approval of this design authorizes documentation and planning only. It does not authorize:
- reading or importing the legacy credential;
- creating real keys;
- installing a binary or
systemdunit; - writing the registry directory;
- modifying or reloading Nginx;
- querying or changing PostgreSQL;
- changing, stopping, or removing the legacy ThothII stack.
Those actions require the executable plan, the remaining survey gates, explicit mutation authorization, and retained sanitized evidence.