Skip to content

Law Firm Platform -- Consolidated Review Report

Date: February 26, 2026 Synthesized from: 5 independent reviews (Backend, Security, Frontend, Database, Infrastructure) Platform: Multi-Tenant Law Firm Practice Management SaaS Codebase: /opt/development/lawfirm/development/ Overall Grade: C+ (2.1 / 4.0) -- HIGH RISK, Not Production Ready


1. Executive Summary

The law firm platform is a multi-tenant Express.js/TypeScript API with a Next.js 14 frontend, PostgreSQL schema-per-tenant isolation, and Docker-based infrastructure. The architecture is fundamentally sound -- the multi-tenant isolation model is well-designed, the layered backend pattern (route/controller/service/repository) is followed by 80% of modules, and the Docker builds follow production best practices with multi-stage builds and non-root users.

However, the platform has critical security vulnerabilities that make it unsuitable for handling attorney-client privileged data in its current state. A full SQL injection vector in the report builder, zero input validation across all 24 API modules, open user registration with arbitrary role assignment, and missing tenant-JWT cross-validation together represent a complete breach path requiring minimal attacker sophistication. These issues are compounded by zero unit tests (2 test files across 148+ API modules), zero accessibility compliance on the frontend, and no audit logging for legal compliance.

Key Statistics Across All Reviews:

Metric Value
API modules 24
Frontend pages 70 (all 'use client', zero SSR)
Route endpoints ~70+
SQL migration files 22
Input validation schemas 0
Unit/integration test files 2
E2E test files 23 (many debug-only)
aria-* attributes 0
alert()/confirm()/prompt() calls 94
TypeScript any usages 64
Rate limiting applied None
Helmet security headers applied None
Audit trail for tenant operations None
Critical findings (deduplicated) 5
High findings (deduplicated) 14
Medium findings (deduplicated) 20+

2. Critical Findings

CRIT-1. SQL Injection in Report Builder -- Full Query Compromise

Attribute Detail
Flagged by Backend (C1), Security (C1), Database (C1) -- all 3 reviewers
File /opt/development/lawfirm/development/api/src/modules/reports/report.service.ts
Lines 93-278 (buildQuery method)
CVSS Estimate 9.8 (Critical)
OWASP A03:2021 -- Injection

The buildQuery() method constructs SQL by directly interpolating user-provided field names, aggregation functions, GROUP BY/ORDER BY clauses, filter fields, filter operators, metric names, and LIMIT values into raw SQL strings with zero sanitization or whitelisting.

Vulnerable code paths (all in report.service.ts): - Line 105: groupBy fields injected into SELECT and GROUP BY - Line 116-118: metric.field and metric.aggregation injected into SELECT - Line 154: groupBy array joined directly into GROUP BY clause - Line 160: orderBy.field and orderBy.direction injected into ORDER BY - Line 165: config.limit interpolated as string (not parameterized) - Lines 187-243: dateRange.field injected into WHERE clauses - Lines 252-277: filter.field and filter.operator injected into WHERE clauses

The entry point is POST /api/reports/execute (report.controller.ts:17-40), which passes req.body directly as ReportConfig with no validation.

Impact: Any authenticated user can execute arbitrary SQL against the tenant schema, enabling full database dump, cross-schema access to other tenants, data modification/deletion, and potential PostgreSQL command execution.

Remediation: Implement strict allowlists for all field names (only known column names from matters and joined tables), validate aggregation values against a fixed set (COUNT, SUM, AVG, MIN, MAX), validate operators against a fixed set, parameterize LIMIT, and use pg format() with %I for any dynamic identifiers.


CRIT-2. Zero Input Validation Across Entire API

Attribute Detail
Flagged by Backend (C2), Security (implicit across all findings), Infrastructure (env validation)
Scope All 24 API modules, all ~70+ endpoints
OWASP A03:2021 -- Injection, A04:2021 -- Insecure Design

No validation library (Zod, Joi, yup, ajv) is installed or used anywhere. All validation is ad-hoc presence checks:

// Typical "validation" pattern across all modules:
if (!data.email || !data.password) { throw new AppError(...); }
// No type checking, format validation, length limits, or sanitization

This means: email fields accept any string, UUID route parameters are not validated, date fields accept any string, numeric fields could receive NaN-producing values, and string fields have no length limits (potential DoS via large payloads). On the frontend, react-hook-form and zod are listed as dependencies but never imported or used.

Remediation: Add Zod schema validation for every endpoint. Create shared schemas for common types (UUID, email, pagination params). Apply validation middleware at the route level. On the frontend, implement react-hook-form + Zod (already in package.json).


CRIT-3. Open Registration with Arbitrary Role Assignment

Attribute Detail
Flagged by Backend (H3), Security (C2), Frontend (C4 in priorities)
Files api/src/modules/auth/auth.routes.ts:14-16, auth.controller.ts:80-86, auth.service.ts:72, frontend/src/app/register/page.tsx
OWASP A01:2021 -- Broken Access Control

The POST /api/auth/register endpoint is public (no authentication required) and accepts a role field from the request body with no authorization check:

// auth.controller.ts:85
role: req.body.role || 'attorney',

The database defines roles as admin, attorney, paralegal, staff. Anyone who knows a tenant's subdomain can register as admin and gain full access to all tenant data. The frontend registration page (register/page.tsx) also allows self-selection of roles. Combined with the absence of RBAC enforcement on any endpoint (Security H1), this is a complete tenant takeover vector.

Remediation: Remove public registration entirely or restrict to invitation-only. Never allow role to be specified in the registration request body. Default to lowest-privilege role. Require admin approval for elevated roles.


CRIT-4. No Tenant-JWT Cross-Validation (Multi-Tenant Isolation Breach)

Attribute Detail
Flagged by Backend (H2), Security (C3)
Files api/src/middleware/auth.middleware.ts:56-66, api/src/middleware/tenantContext.middleware.ts
OWASP A01:2021 -- Broken Access Control

The JWT payload contains tenant_id, and the X-Tenant-Subdomain header resolves the tenant context. These are never cross-validated anywhere in the codebase:

// auth.middleware.ts:61-66 - sets req.user from JWT
req.user = { id, email, role, tenant_id }; // tenant_id from JWT
// tenantContext.middleware.ts - sets req.tenant from header
// NOWHERE: req.user.tenant_id === req.tenant.id check

Impact: A user authenticated in Tenant A can send X-Tenant-Subdomain: tenantB and access Tenant B's data. This is a complete multi-tenant isolation breach -- catastrophic for a platform handling attorney-client privileged data across multiple firms.

Remediation: Add middleware that enforces req.user.tenant_id === req.tenant.id on every authenticated request. This is a single check that blocks the entire cross-tenant attack vector.


CRIT-5. Security Middleware Installed but Never Applied (No Rate Limiting, No Security Headers)

Attribute Detail
Flagged by Backend (C3), Security (H3, M3), Infrastructure (Nginx headers, Nginx rate limiting)
Files api/src/index.ts:53-64, nginx/nginx.conf
OWASP A05:2021 -- Security Misconfiguration, A07:2021 -- Auth Failures

helmet and express-rate-limit are in package.json but never imported or used. The Express middleware stack is only cors, morgan, and express.json() (without body size limit). Nginx also lacks security headers (HSTS, CSP, X-Frame-Options) and rate limiting configuration.

Impact: - No rate limiting anywhere in the stack: unlimited brute force on login, credential stuffing, DoS - No security headers: clickjacking, MIME sniffing, no HTTPS enforcement - No request body size limits: memory exhaustion possible - Combined with no account lockout (Security H5), authentication is effectively unprotected

Remediation: Apply helmet() and express-rate-limit in index.ts (2 lines of code for basic protection). Add security headers and rate limiting in Nginx. Add explicit body size limits to express.json().


3. High Findings Table

ID Finding Flagged By File(s) OWASP
HIGH-1 Hardcoded JWT secret fallbacks -- All 3 JWT secrets (jwtSecret, jwtRefreshSecret, platformAdminJwtSecret) have predictable fallback values. If env vars are missing, app runs with forgeable tokens. Backend (H1), Security (H2) api/src/config/auth.config.ts:7-17 A02:2021
HIGH-2 No RBAC enforcement on any endpoint -- 4 roles defined (admin, attorney, paralegal, staff) but req.user.role is never checked. All authenticated users have identical permissions. Security (H1) All route files A01:2021
HIGH-3 Logout does not invalidate tokens -- Logout is a no-op (comment says "handled client-side"). Tokens have 24-hour expiry with no server-side revocation. Redis is deployed but unused for session management. Security (H4) api/src/modules/auth/auth.controller.ts:168-186 A07:2021
HIGH-4 No account lockout mechanism -- No failed attempt tracking, no progressive delays, no CAPTCHA. Combined with no rate limiting, allows unlimited brute force. Security (H5) api/src/modules/auth/auth.service.ts:23-62 A07:2021
HIGH-5 Platform admin runTenantMigration race condition -- Uses pool.query() for SET search_path + SQL execution, but pool may use different connections for each call. Cross-tenant migration execution possible. Backend (H4), Database (L8) api/src/modules/platform-admin/platform-admin.repository.ts:207-211 A01:2021
HIGH-6 N+1 query pattern in Kanban board -- Executes 1 query per workflow stage (N+1). For 8 stages = 9 queries, each with a correlated subquery. Backend (M7), Database (H2) api/src/modules/workflows/workflow.repository.ts:589-667 Performance
HIGH-7 Distribution repository missing schema name quoting -- Uses ${tenantSchema}.table instead of "${tenantSchema}".table, inconsistent with all other repositories. Backend (H5) api/src/modules/distribution/distribution.repository.ts:30+ A03:2021
HIGH-8 CORS allows overly broad origins in development -- Regex /.baywood(:\d+)?$/ matches any .baywood subdomain. If CORS_ORIGIN env var unset, these persist in production with credentials: true. Backend (H6), Security (I2) api/src/index.ts:54-62 A05:2021
HIGH-9 Hardcoded schema name in migration 016 -- Schema baywood hardcoded instead of iterating all tenant schemas. Only one tenant received percentage billing columns. Database (H1) sql/migrations/016-add-percentage-billing.sql:9 Data Integrity
HIGH-10 No migration tracking until migration 021 -- schema_migrations table created only in migration 021. No reliable way to know which of migrations 001-020 have been applied. Database (H5) sql/migrations/021-phase5-automation.sql:447-457 Data Integrity
HIGH-11 Inconsistent migration strategies -- 4 different approaches used across 22 migrations (direct, loop-over-tenants, hardcoded schema, function-based). New tenants may not get all tables. Database (H6) Multiple migration files Data Integrity
HIGH-12 Missing Docker resource limits -- No memory/CPU limits in docker-compose. Runaway process can consume all host resources. Infrastructure (Docker 1) docker-compose.yml A05:2021
HIGH-13 PII leakage in logs -- Error handler logs details, stack, user, tenant without redaction. Database connection errors could expose credentials. No log sanitization. Infrastructure (Logging 2) api/src/middleware/errorHandler.middleware.ts A09:2021
HIGH-14 No audit logging for tenant operations -- Platform admin has audit logging, but tenant-level operations (client access, matter updates, document access, billing) have zero audit trail. Required for legal compliance. Security (I1), Infrastructure (10.3), Backend (L3) Codebase-wide A09:2021

4. Medium Findings Table

ID Finding Flagged By File(s)
MED-1 OAuth credentials "encrypted" with Base64 -- encryptCredential() uses base64, trivially reversible. TODO comment acknowledges need for AES-256. Security (M4) platform-admin.service.ts:346-351
MED-2 Webhook SSRF risk -- fetch(webhook.url) with no URL validation. Attacker can target internal network, cloud metadata endpoints. Security (M5) webhook.service.ts:202-253
MED-3 MFA implementation incomplete -- Database fields exist (mfa_secret_encrypted, mfa_enabled) but login flow never checks. MFA cannot be enabled. Security (M7) Auth module
MED-4 Subdomain validation logic bug -- Uses && instead of ||: if (!/regex/.test(subdomain) && length < 3). Invalid subdomains with length >= 3 pass. Backend (M5) platform-admin.controller.ts:197
MED-5 Billing generateFromTimeEntries non-atomic -- First transaction commits, then create() starts new transaction. Failure in second leaves inconsistent state. Backend (M4) billing.repository.ts:163-238
MED-6 Dashboard has no service/repository layer -- 6 raw SQL queries directly in controller, executed sequentially (could be 1 CTE query). Backend (M2), Database (H3) dashboard.controller.ts:1-242
MED-7 Massive code duplication in UPDATE builders -- Each repository manually builds dynamic UPDATE with 20-50+ field checks. Matters has 200+ lines. Backend (M1) Multiple repositories
MED-8 Inconsistent response formats -- Reports, Intake, Billing, Dashboard deviate from standard successResponse()/errorResponse() format. Backend (M6), Infrastructure (5.1) Multiple controllers
MED-9 Password config booleans always true -- process.env.X === 'true' || true always evaluates to true due to JS short-circuit. Backend (M8) auth.config.ts:27-32
MED-10 Billing/Matter repositories reference non-existent columns -- Code references billed, billable, activity_code, opened_date, billing_method etc. not matching migration-defined schema. Database (M1, M2) billing.repository.ts, matter.repository.ts
MED-11 Connection pool hardcoded to 10 -- Not configurable, undersized for multi-tenant workloads. No pool monitoring. Database (H4), Infrastructure (1.2) database.ts:13
MED-12 Reporting tables in ambiguous schema -- Tables have tenant_id FK but are queried with tenant schema prefix. Unclear whether shared or isolated. Database (M5) sql/migrations/015-create-reporting-tables.sql
MED-13 Frontend: Every page is 'use client' -- Zero server-side rendering across 70 pages. No SSR, no streaming, no progressive rendering. App Router value proposition unused. Frontend (1) All page.tsx files
MED-14 Frontend: Zero accessibility compliance -- 0 aria-* attributes, 3 role attributes. No keyboard navigation on custom components. Color-only status indicators. Frontend (7) Codebase-wide
MED-15 Frontend: 94 browser dialog calls -- 63 alert(), 27 confirm(), 4 prompt(). Blocks main thread, unstyled, inaccessible. Frontend (4) Multiple pages
MED-16 Frontend: No error boundaries -- Zero error.tsx files. Unhandled error crashes entire application with no recovery. Frontend (9) App Router
MED-17 Console logging instead of structured JSON -- console.log/console.error throughout instead of Winston (which is installed). Emoji in logs breaks parsing. Infrastructure (4.1) Multiple files
MED-18 No request ID tracking -- Cannot trace requests through logs. No correlation between frontend and backend errors. Infrastructure (5.2) index.ts, error handler
MED-19 E2E tests disabled / no test infrastructure -- Playwright webServer commented out. No Jest/Vitest for unit tests. 2 API test files for 148 modules. Infrastructure (7), Frontend (13) Test configs
MED-20 JWT stored in localStorage -- Vulnerable to XSS. Both tenant and admin tokens in localStorage. Frontend (12) api-client.ts:26, admin-api.ts:31
MED-21 Hardcoded 'demo' tenant in frontend -- Public intake form and API client default to 'demo' tenant. Multi-tenancy broken for public forms. Frontend (12) api-client.ts:19, public/intake/[slug]/page.tsx:56

5. Cross-Cutting Themes

Theme 1: "Installed but Not Used" Pattern

Multiple critical tools are listed as dependencies but never imported: - Backend: helmet (security headers), express-rate-limit (rate limiting) -- in package.json, not in code - Frontend: react-hook-form, @hookform/resolvers, zod -- in package.json, never imported - Frontend: axios -- in package.json, custom fetch client used instead - Backend: winston -- in package.json, console.log used throughout - Backend: Redis -- deployed in Docker, unused for sessions/caching/rate-limiting

This pattern suggests a gap between architectural planning and implementation follow-through.

Theme 2: Zero Validation / Zero Trust at Every Layer

The absence of input validation is not an isolated issue -- it permeates every layer: - API layer: No Zod/Joi schemas, ad-hoc presence checks only (Backend C2) - Frontend forms: Manual useState with minimal checks, no field-level validation (Frontend 4) - Database layer: No CHECK constraints on many columns, tenant schema names not validated against a regex (Database C2) - Environment config: No startup validation of required env vars -- app runs silently with missing secrets (Infrastructure 3.2) - Nginx layer: No request validation, no rate limiting (Infrastructure 2.2)

Theme 3: Security Through Intention, Not Implementation

Security features are clearly planned but incompletely implemented: - MFA fields exist in the database but login never checks mfa_enabled (Security M7) - Role field exists on users but RBAC is never enforced (Security H1) - OAuth encryption has a TODO for AES-256 but uses base64 (Security M4) - Token blacklisting has a comment "In future, could add token to blacklist in Redis" (Security H4) - Password complexity config exists but booleans always evaluate to true (Backend M8)

Theme 4: Schema and Code Drift

The database migrations and application code have diverged: - Billing repository references columns not in migrations (billed vs is_billed, rate vs unit_price) (Database M1) - Matter repository references 6+ columns not defined in migration 004 (Database M2) - Migration 016 only applies to one hardcoded tenant schema (Database H1) - 4 different migration strategies used across 22 files with no unified approach (Database H6) - No migration tracking until migration 021 (Database H5)

For software handling attorney-client privileged data: - No audit trail for who accessed which client/matter records (Security I1, Infrastructure 10.3) - No data retention/deletion policy or endpoints (Infrastructure 10.2) - No encryption at rest for sensitive fields (SSN, financial data) (Security Compliance) - No PII redaction in logs -- passwords, user data potentially logged unredacted (Infrastructure 4.2) - Cross-tenant data leakage possible via CRIT-4 -- privileged communications could be exposed - No access controls beyond authentication -- all staff see all client data in a tenant


6. Prioritized Action Plan

Immediate -- Fix Today (Blocks All Deployment)

# Action Effort Addresses
1 Fix SQL injection in report builder -- Add field name allowlists, validate aggregations against fixed set, parameterize LIMIT 4-8 hours CRIT-1
2 Add tenant-JWT cross-validation -- Single middleware check: if (req.user.tenant_id !== req.tenant.id) return 403 1 hour CRIT-4
3 Restrict registration -- Remove role from request body, default to lowest privilege, require admin invitation for elevated roles 2-4 hours CRIT-3
4 Apply helmet() -- app.use(helmet()) in index.ts 15 minutes CRIT-5 (partial)
5 Apply rate limiting -- app.use(rateLimit({...})) globally, stricter on /api/auth/login and /api/auth/register 1-2 hours CRIT-5 (partial)
6 Remove JWT secret fallbacks -- Throw fatal error at startup if JWT_SECRET, JWT_REFRESH_SECRET, PLATFORM_ADMIN_JWT_SECRET are not set 30 minutes HIGH-1
7 Add Nginx security headers -- HSTS, X-Frame-Options, X-Content-Type-Options, CSP, Referrer-Policy 1-2 hours CRIT-5 (partial)

Short-Term -- This Sprint (1-2 Weeks)

# Action Effort Addresses
8 Add Zod input validation -- Start with auth, billing, reports, platform-admin modules 3-5 days CRIT-2
9 Implement RBAC middleware -- requireRole(['admin', 'attorney']) applied to all routes per permission matrix 2-3 days HIGH-2
10 Implement token blacklisting -- Use Redis for logout revocation; reduce access token lifetime to 15-30 minutes 1-2 days HIGH-3
11 Add account lockout -- Track failed attempts in Redis, lock after 10 failures, require admin unlock 1 day HIGH-4
12 Fix runTenantMigration race condition -- Use pool.connect() with dedicated client 1 hour HIGH-5
13 Fix distribution repository schema quoting -- Add double quotes to all ${tenantSchema} references 30 minutes HIGH-7
14 Implement structured logging -- Replace all console.log/console.error with Winston; add PII redaction 3-5 days HIGH-13, MED-17
15 Add Docker resource limits -- Memory and CPU limits for all services in docker-compose.yml 2 hours HIGH-12
16 Fix subdomain validation -- Change && to || in condition 15 minutes MED-4
17 Fix password config booleans -- Change === 'true' || true to !== 'false' 15 minutes MED-9
18 Add error.tsx error boundaries -- At /, /admin/, and key route segments 2 hours MED-16

Medium-Term -- This Quarter (1-3 Months)

# Action Effort Addresses
19 Implement comprehensive audit logging -- Record user, action, resource type/ID, changes, IP for all state-changing operations 1-2 weeks HIGH-14
20 Build unit/integration test suite -- Jest + supertest for API; React Testing Library for frontend; target 70% coverage 2-4 weeks MED-19
21 Reconcile schema drift -- Audit running DB vs migrations, create corrective migrations, standardize migration strategy 1 week MED-10, HIGH-9, HIGH-10, HIGH-11
22 Implement MFA -- TOTP-based MFA with speakeasy or otpauth; enforce for admin users 1 week MED-3
23 Replace N+1 Kanban query -- Single query with application-level grouping 4 hours HIGH-6
24 Consolidate dashboard to single CTE query -- Replace 6 sequential queries with 1 2-4 hours MED-6
25 Implement AES-256-GCM for OAuth credentials -- Replace base64 "encryption" 1-2 days MED-1
26 Add SSRF protections for webhooks -- Validate URLs, block private/reserved IPs, resolve DNS before fetch 1 day MED-2
27 Fix billing transaction atomicity -- Pass client to create(), run in single transaction 2-4 hours MED-5
28 Make connection pool configurable -- Env var for pool size, add pool monitoring 2 hours MED-11
29 Add request ID tracking -- UUID per request, propagate through logs and error responses 4 hours MED-18
30 Frontend: Add basic accessibility -- ARIA attributes on interactive elements, keyboard navigation, skip-to-content link 1-2 weeks MED-14
31 Frontend: Replace browser dialogs -- Swap 94 alert()/confirm()/prompt() calls with shadcn AlertDialog 1 week MED-15
32 Move auth tokens to httpOnly cookies -- Eliminate localStorage XSS vector 3-5 days MED-20

Long-Term -- Strategic (3-6 Months)

# Action Effort Addresses
33 Convert frontend to server components -- Leverage Next.js 14 App Router properly; server-side data fetching for list/detail pages 2-4 weeks MED-13
34 Add data caching layer -- SWR/React Query on frontend; Redis caching for tenant lookups, user permissions, reference data on backend 2-3 weeks Performance
35 Implement data retention/deletion -- Compliance endpoints for client data export and purge 1-2 weeks Compliance
36 Add encryption at rest -- Encrypt PII fields (SSN, financial data) in database; implement key management (AWS KMS or Docker secrets) 2-3 weeks Compliance
37 Extract shared utilities -- Common UPDATE builder, response helpers, pagination constants, shared frontend components (DataTable, StatusBadge) 1-2 weeks Code Quality
38 Implement CI/CD pipeline -- Automated tests, lint, security scanning on every commit; enforce coverage thresholds 1-2 weeks Process
39 Add row-level security -- PostgreSQL RLS as defense-in-depth for multi-tenant isolation 1 week Defense-in-Depth
40 Implement log aggregation and monitoring -- CloudWatch/Datadog/ELK; alerting on errors, job failures, security events 1-2 weeks Observability

7. What's Working Well

Despite the critical issues, the platform has a solid foundation worth preserving:

Backend Architecture: - Schema-per-tenant isolation is a strong multi-tenancy pattern, correctly implemented across 23 of 24 modules - Consistent route -> controller -> service -> repository layered pattern (~80% adherence) - Parameterized SQL queries in all modules except reports (23/24 is excellent for raw SQL) - Custom AppError class with proper HTTP status codes and centralized error handler - StageDurationChecker background job is well-implemented with per-tenant isolation and duplicate execution prevention

Database Design: - Good use of PostgreSQL features: triggers, CHECK constraints, GIN indexes, JSONB columns - Comprehensive indexing strategy with partial indexes, trigram GIN indexes for fuzzy search, and descending date indexes - Proper transaction handling in billing operations (BEGIN/COMMIT/ROLLBACK) - Schema-per-tenant provides true data isolation

Frontend: - Clean, consistent visual design using shadcn/ui with Tailwind CSS - Well-organized API service layer with typed interfaces and automatic response unwrapping - Comprehensive feature coverage across 70 pages - TypeScript strict mode enabled with additional safety flags - Zustand auth store is lightweight and clean

Infrastructure: - Docker multi-stage builds for both API and frontend (proper dependency pruning, minimal production images) - Non-root user execution in all containers - Health checks configured for API, PostgreSQL, and Redis - Proper separation of development and production Dockerfiles - Feature flags in environment configuration (ENABLE_REGISTRATION, ENABLE_TWO_FACTOR, etc.)

Code Organization: - Path aliases (@/*) for clean imports - Consistent file naming conventions across modules - Shared response format utilities exist (even if not universally applied) - Platform admin module has proper audit logging (model for tenant-level audit logging)



Appendix: Deduplication Matrix

The following table shows which findings were flagged by multiple reviewers, confirming cross-reviewer agreement on severity.

Finding Backend Security Frontend Database Infra Count
SQL injection in report builder C1 C1 -- C1 -- 3
Zero input validation C2 (implicit) D4 -- M 3
Open registration + role assignment H3 C2 C4 -- -- 3
No tenant-JWT cross-validation H2 C3 -- -- -- 2
No rate limiting C3 H3 -- -- H 3
Hardcoded JWT secret fallbacks H1 H2 -- -- H 3
N+1 Kanban query M7 -- -- H2 -- 2
Missing security headers (helmet/Nginx) C3 M3 -- -- H 3
No audit logging L3 I1 -- -- H 3
Connection pool undersized -- -- -- H4 M 2
Inconsistent response formats M6 -- -- -- L 2
Schema name quoting inconsistency H5 M1 -- C2 -- 3

Report synthesized February 26, 2026 Source: 5 independent reviews totaling ~3,720 lines of analysis Platform: Law Firm Practice Management -- Multi-Tenant SaaS Verdict: Solid foundation, critical security gaps. Fix Immediate items before any deployment beyond controlled development.