This page lists the changes in CYPEX v2.0.0 that alter the default behavior of the platform without breaking existing integrations. You should be aware of these changes, but they typically do not require action.
A new row is inserted into cypex.t_config with key password_policy and
value:
| |
This is very permissive by design — it ships a working policy rather than
locking existing users out. The insert uses ON CONFLICT (key) DO NOTHING,
so:
- If a password policy is already present in
cypex.t_config, the existing policy is kept. - If no password policy is present, the v2.0.0 default is inserted.
Admins can override the policy at any time through the admin panel or by updating the row directly. See Default password policy for the verification query and override procedure.
A row is inserted into cypex.t_organization with organization_domain = 'default'. Every existing module is mapped to this organization through
cypex.t_module_organization, and every existing role is mapped through
cypex.t_role_organization.
The effect:
- The Default Organization is the implicit tenant for all v1.x data.
- Existing rows in tenant-scoped tables have
organization_id IS NULL. - RLS policies explicitly allow
organization_id IS NULLto be visible to all users mapped to the Default Organization, which is everyone.
This is why existing data remains visible after the upgrade — see RLS impact on existing data for the full mechanism.
The migration creates the function
cypex.validate_root_table_organization(), but it is not attached to any
table — no CREATE TRIGGER references it. On the cypex and cypex_log
tables, nothing in the database stamps or validates organization_id on
insert.
The four sso_gateway tables are the exception: they carry a
BEFORE INSERT OR UPDATE trigger that stamps organization_id from the
caller’s claims and rejects a cross-organization write.
New rows created through CYPEX get organization_id because the application
sets it. Direct database writes must supply the column themselves, either
explicitly or through a column default such as
DEFAULT cypex.current_organization_id().
WarningA row left with a NULL
organization_idis visible to every organization, because the baseline policies include anorganization_id IS NULLclause for legacy data. After any direct-SQL load, verify the column is populated:
1SELECT count(*) FROM cypex.t_ui WHERE organization_id IS NULL;
The cypex_admin role keeps all its INSERT/UPDATE/DELETE privileges on
tenant-scoped tables. Administrative reach is written into the policies
themselves: cypex.is_admin() returns true when the JWT carries
isSuperAdmin, so every policy admits the system administrator explicitly.
No CYPEX role is granted the PostgreSQL BYPASSRLS attribute.
This means:
- System administrators continue to see and edit every row, regardless of organization — still as a policy decision, not a bypass.
- Existing admin workflows are not affected.
A cypex_user role mapped to the Default Organization sees:
- All rows where
organization_id IS NULL(legacy data). - All rows where
organization_idmatches one of the organizations the user has access to (percypex.t_role_organization).
A cypex_user mapped to a non-default organization sees only the second
category. This is the intended multi-tenant behavior.
RLS policies read the caller’s organization from the request.jwt.claims
GUC, which PostgREST sets from the access token. Direct database
integrations (custom reports, ETL jobs, maintenance scripts) get no claims
by default, so they must set the GUC themselves:
| |
Organization IDs are bigint. Look yours up with
SELECT id FROM cypex.t_organization WHERE organization_domain = 'default';.
With no claims set, cypex.current_organization_id() returns NULL: a
cypex_user mapped to the Default Organization still sees legacy rows
through the organization_id IS NULL clause, while a user mapped to a
non-default organization sees nothing. See
RLS impact on existing data.
| Change | What you should know |
|---|---|
| Default password policy enforced | Very permissive; can be overridden. |
| Existing data assigned to Default Organization | Transparent to end users. |
organization_id is not stamped by a trigger | The application sets it; direct writes must supply it. |
cypex_admin retains full access | Admin workflows unaffected. |
cypex_user sees only its own rows | Per t_role_organization. |
Direct DB sessions need request.jwt.claims | Set the GUC before querying. |