This is a backlog of deferred hardening work. Each item is a candidate to be planned and implemented later; none of them is a current defect that makes the server unusable. The items came out of a threat-model review that asked two questions: what an attacker can still read or crack from a full dump of the PostgreSQL database, and what an attacker already inside the image's trust boundary can reach.
This document only records the gaps. It does not describe anything that is already implemented, and it does not propose code changes here.
The baseline is better than a naive server's, because secrets are stored hashed or encrypted, and the key material lives outside the database. The table below is the full accounting.
| Data | Protection at rest | If the dump leaks |
|---|---|---|
| User passwords | Argon2id PHC, salted (src/db/user_repo.rs) |
Offline cracking only. |
| Client secrets | Argon2id PHC (src/db/client_repo.rs) |
Offline cracking only. |
| TOTP secrets | AES-256-GCM under HKDF-SHA256 (src/crypto/totp.rs, info totp-gcm-v2; pre-migration v1 blobs under bare SHA256(totp_key) still decrypt) |
Safe only if the key is not in the dump. |
| TOTP recovery codes | Argon2id PHC (src/db/user_repo/totp.rs) |
Offline cracking only. |
| DCR registration access tokens | Keyed HMAC hash (src/db/client_repo.rs) |
Safe; the pepper is not in the database. |
| Emails, client names, contacts, redirect URIs, scopes, entitlements | Plaintext | Full disclosure. |
jwks (client public keys) |
Plaintext | Public by design. |
tls_client_auth_subject_dn and cert thumbprints |
Plaintext | Identifier disclosure. |
is_admin, disabled, must_change_password, timestamps |
Plaintext | Metadata disclosure. |
The critical dependency is outside the database. The token pepper and the TOTP
encryption key are supplied through environment variables or mounted files
(src/config/raw.rs, src/config/build.rs), never through a table. A pure
database dump therefore does not yield them, and every hashed or encrypted
column above stays opaque. This separation is the single most important
protection the server has today, and the gaps below are mostly about not
depending on it alone.
Each item names the risk, the change to plan, and the files it would touch. The list is deliberately short and ordered by value.
email
lookup so case-insensitive uniqueness still works.src/db/user_repo.rs, src/db/client_repo.rs,
src/db/redirect_repo.rs, src/keys.rs, migrations/.src/crypto/totp.rs, src/keys.rs.The attack surface and the controls already in place are documented in SECURITY.md; this section lists only the mitigations that remain. It answers a second threat-model question: if an attacker is already inside the image's trust boundary, how could they reach the container or the host, and how should we reduce that risk? The remaining vectors are narrow.
request_urirequest_uri fetch (src/jose/jar/fetch.rs) cannot reach a publicly routable
request-object host. A deployment either relies on a feature that silently
fails, or loosens egress further than intended.request_uri is
unavailable under the shipped policy. A blanket 443 allowance is not an
option because it restores arbitrary exfiltration.deploy/k8s/networkpolicy.yaml, docs/DEPLOYMENT.md.networkpolicy.yaml allows egress to any address on ports 5432 and
6379 (link-local excluded), so those ports are an exfiltration channel to an
arbitrary internet host, not only the database and cache.ipBlock: 0.0.0.0/0 peer with their namespaceSelector/podSelector so only
the real endpoints are reachable; keep a port-scoped rule only for an external
managed database.deploy/k8s/networkpolicy.yaml, docs/DEPLOYMENT.md.Ranked by value against effort:
Recommendation: implement item 1 next. It closes the largest plaintext disclosure; item 2 then reduces the cost of a future key compromise.
request_uri objects? This
decides whether item 3 needs a scoped egress rule or only documentation.