mb mybillbook private beta
← All features

Compliance

The things that matter for an Indian SMB tax tool but never show up on a feature comparison sheet. Built into the DB layer, not the marketing layer.

Postgres Row-Level Security

Tenant isolation isn't enforced by the app. It's enforced by Postgres. Every business table has an RLS policy of the form:

USING (tenant_id = NULLIF(
  current_setting('app.tenant_id', true),
  ''
)::uuid)

The runtime app role mb_app does not have BYPASSRLS. If the application forgets to set app.tenant_id on a connection, the NULLIF wrapper returns NULL, the cast fails, and no rows are returned. A second tenant cannot accidentally see your data even via a bug.

Continuously verified by an RLS fuzzer (scripts/rls-fuzzer) that seeds two tenants in CI on every push, sets the GUC to tenant A, and asserts B's rows are invisible across all 15 business tables.

Append-only audit log

Every state-changing action — login, signup, invoice finalize, payment record, credit note issue, quotation conversion, supplier edit, purchase delete — writes a row to audit_log with actor user, tenant, entity, IP, user-agent, request-id, and before/after JSONB payloads.

Append-only is enforced at the DB layer, not the app:

CREATE TRIGGER audit_log_no_update_delete
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION raise_immutable();

Same trigger pattern on ledger_entries (the compensating-entries ledger for future ITC reversal trails). Tampering with history requires DB owner credentials, which the application never has.

DPDP Act 2023 readiness

India's Digital Personal Data Protection Act (2023) requires consent records, retention limits, and erasure-on-request for every data fiduciary handling personal data. mb ships the data model:

  • · legal_terms — versioned Terms of Service text. Every consent ties to a specific version.
  • · consents — one row per user × terms version × timestamp + IP, recording the explicit grant.
  • · erasure_requests — track right-to-be-forgotten requests with status (received / completed).
  • · Signup audit row includes IP + UserAgent (§8(8) non-repudiation requirement).
  • · See our DPDP disclosures for grievance officer + retention policy.

6-year GST retention via snapshots

CGST Rules require books to be preserved for 6 years from the end of the financial year. On invoice finalize and credit-note issue, mb writes a JSONB snapshot_jsonb column that freezes the complete state at the moment of issue: every line, every tax field, the customer's address as it was, the business's GSTIN as it was, the place-of-supply as it was.

Subsequent edits to the customer or business don't retroactively change a filed document. What you can show the assessing officer is exactly what was filed.

Auth: argon2id + server-side sessions + CSRF

  • · Passwords hashed with argon2id (memory-hard, GPU-resistant). Cost parameters tunable per release; existing hashes auto-rehashed on next login when params change.
  • · Server-side sessions (not JWTs). Logout actually invalidates — we delete the session row.
  • · Cookies: HttpOnly, Secure, SameSite=Lax.
  • · CSRF double-submit cookie, bound to the session, constant-time compared on every POST.
  • · Google OIDC sign-in option with full JWKS verification (no shortcut on the discovery doc).
  • · Rate limits on signup, signin, password-reset, and the public quote endpoint.