…p+CAS, tenant enforcement
Audit findings 1-5 confirmed real; root causes were (a) identity self-reported by
the client, (b) every failure path defaulting open.
- ERP /ai/whoami (saas-8s+ @c8e0057 follow-up) resolves token → real user/tenant/type;
AuthzService.resolveIdentity is now the single identity entry point. Client-supplied
userid/brandsid/usertype removed from all request bodies and from chat.html.
- dev-login is a real switch (erp.dev-login.enabled, default false) and its ERP creds
no longer have built-in admin/666666 defaults; blank token in production → 401,
never a silent fall back to the dev (sysadmin) account.
- conversations/op/form endpoints require login; conversation ids are namespaced by
user id and ownership-checked (403 otherwise); /op/pending no longer returns sPayload.
- op confirm/cancel check the proposer, and confirm claims the draft via CAS so
concurrent/repeat confirms cannot execute twice; bill numbers are regenerated at
execution time instead of replaying the propose-time snapshot.
- FK options take the tenant from the introspected identity and return empty rather
than dropping the sBrandsId predicate.
- secrets moved to env vars (DB_URL/DB_USERNAME/DB_PASSWORD/REDIS_*/LLM_*/ERP_BASEURL);
allowMultiQueries=false. NOTE: the previously committed credentials must be rotated.
- ids interpolated into ERP URLs are validated (safeId) to stop query/path injection.
- update path rejects system columns, resolves FK names, and coerces by column type;
numeric coercion now rejects unparseable input instead of writing 0/truncating, and
proposal summaries show the value that will actually be written.
- ResponseStatusException keeps its 401/403 status instead of being wrapped as 200.
- anti-fabrication guard stays on when the intent gate itself fails.