Kredal Docs
How to

Verify Row-Level Security

Prove cross-tenant isolation, private storage, and role permissions before a pilot.

Kredal handles passports, Emirates IDs, and UBO documents. Before any pilot, confirm that a workspace can only ever see its own data. This runbook is the manual security check from TECH_SPEC.md §11.

1. Cross-tenant read isolation

  1. Create two accounts, A and B, each with their own workspace and one company.
  2. Copy company B's ID from B's URL (/app/companies/<id>).
  3. While signed in as A, navigate to /app/companies/<company-B-id>.
  4. Expected: a "not found" / error state, never B's data. RLS select policies are scoped to is_workspace_member(workspace_id), so the row is invisible to A.

Repeat for documents, questionnaire responses, assessments, and reports — all use the same workspace-membership check.

2. Private storage & signed URLs

  1. As A, upload a document, then open its preview. The URL is a short-lived signed URL (120s TTL) generated server-side.
  2. Copy that signed URL and open it after it expires (or strip the token). Expected: access denied. The bucket is private; there is no public path.
  3. Try to fetch B's storage path directly — the storage RLS policy checks workspace membership on the first path segment ({workspace_id}/...), so it is denied.

3. Role permissions (viewer is read-only)

  1. In a consultant workspace, add a member with the viewer role.
  2. As the viewer, confirm you can see a client's company but cannot upload documents, edit the company, add owners, or run an assessment.
  3. This is enforced by is_workspace_writer() on all insert/update policies — viewers are deliberately excluded. See Security model.

4. Unauthenticated access

  1. Sign out and navigate to /app. Expected: redirect to /login?next=/app (enforced by src/proxy.ts).

5. Audit log is append-only

Confirm audit_events has an insert policy but no update/delete policy — mutation is denied by default. Company creation, uploads, deletions, assessments, and report generation should each produce an audit row.

Automated backstop

The pure-logic layer (scoring, reports) is unit-tested with npm test. RLS itself is verified manually as above; consider adding pgTAP tests when the pilot scales.

On this page