Bug #17
openPingCRM — IVR Legacy Security Hardening, Consolidation & Modernization (27 tasks, 5 phases)
0%
Description
PingCRM — IVR Legacy Security Hardening, Consolidation & Modernization¶
Labels: ai-created, security, modernization, php, laravel, react, owasp
User Story¶
As a PingCRM platform owner,
I want the IVR legacy layer secured against OWASP critical vulnerabilities, consolidated to eliminate 660 duplicated method bodies, and migrated to database-portable SQL with an upgraded Inertia.js bridge,
so that the application is safe to operate in a multi-tenant production environment, maintainable by the engineering team, and portable to MySQL/PostgreSQL without further rework.
Background¶
Static analysis of shende-shweta/pingcrm identified 6 critical security defects confined to the IVR legacy layer, along with extreme structural duplication (12 GodService classes × 55 methods = 660 identical bodies; 84 generated controllers × 55 endpoints = 4,620 endpoints), and several portability and quality issues.
The IVR legacy layer was code-generated and never hardened for production:
- 90+ CRUD routes are publicly accessible with no authentication middleware
- Raw user input is concatenated directly into SQL strings
- A hard-coded API key (
LEGACY_IVR_KEY_2042) appears in 12 source files - PHP
extract()is called on raw request payloads in 660 methods - Mass assignment inserts
(array) $payloaddirectly into the database -
sleep(1)in every GodService method blocks PHP-FPM workers under any load - Hard-coded
$tenantId = 1breaks multi-tenant isolation across all 84 controllers
Scope¶
Phase 1 — Critical Security Hardening (T-01 → T-07)¶
Fix all 6 OWASP critical defects and restore tenant isolation. All changes are reversible and require no schema migration.
| Task | Fix |
|---|---|
| T-01 | Add ->middleware(['web', 'auth']) to ivr-legacy route group — closes unauthenticated exposure of 90+ endpoints |
| T-02 | Change Route::match(['get','post']) → Route::post() for destructive routes — closes CSRF bypass |
| T-03 | Parameterize raw SQL in 24 vulnerable Index/Store controllers — closes SQL injection |
| T-04 | Move LEGACY_IVR_KEY_2042 to config/ivr.php + .env — closes hard-coded secret |
| T-05 | Replace extract($payload) with Arr::only() field allowlists — closes variable injection |
| T-06 | Replace insertGetId((array) $payload) with explicit field allowlists — closes mass assignment |
| T-07 | Replace hard-coded $tenantId = 1 with Auth::user()->account_id in all 84 controllers |
Phase 2 — Consolidation & Performance (T-08 → T-13)¶
Eliminate 660 identical GodService method bodies and 4,620 controller endpoint duplicates. Remove sleep(1) from the request thread.
-
AbstractGodServicebase class: 12 GodService classes extend one configurable implementation -
BaseLegacyController: 84 generated controllers replaced by a single parametric controller -
IvrSyncJob(Laravel Queue): removessleep(1)from PHP-FPM worker threads - Remove static
$sharedRuntimeCache— eliminates cross-request data leakage - Structured
Log::error()in all catch blocks
Phase 3 — Quality & Portability (T-14 → T-18)¶
- DB-portable
hourExpression()helper replaces SQLite-specificstrftime()— enables MySQL/PostgreSQL migration - Inertia.js server upgraded to
^2.0(resolves server v1 / client v2 mismatch) -
moduleSlugallowlist validation inLoadsIvrModuleData - Dual
tenant_id/account_idcolumn alignment to prevent cross-tenant data leakage
Phase 4 — CRM Validation Gaps (T-19 → T-21)¶
- Scoped email uniqueness (per-account, not global)
-
password min:8enforcement - ISO 3166 country enum validation
Phase 5 — Testing & Quality Gate (T-22 → T-27)¶
- PHPUnit unit tests for all security fixes (T-22–T-23)
- PHPUnit feature/integration tests (T-24–T-25)
- Security regression suite (T-26)
- Final gate: PHPStan / Vitest /
php artisan route:listverification (T-27)
Acceptance Criteria¶
- All 90+ IVR legacy routes require authentication — unauthenticated requests return HTTP 401/403
- Zero raw SQL string concatenation remains in IVR controllers — all queries use parameterized bindings
-
LEGACY_IVR_KEY_2042does not appear in any committed source file -
extract()is not called on any user-controlled input in GodService classes - No
insertGetId((array) $payload)calls remain — all DB inserts use explicit allowlists - All 84 legacy controllers derive tenant scope from
Auth::user()->account_id -
sleep(1)removed from GodService methods; async work dispatched viaIvrSyncJob -
AbstractGodServicereplaces 12 GodService classes;BaseLegacyControllerreplaces 84 controllers - All SQL uses
hourExpression()helper — no SQLite-specificstrftime()calls remain - Inertia.js server version is
^2.0; server/client version mismatch resolved - All PHPUnit and Vitest test suites pass; PHPStan reports zero critical issues
Technical Notes¶
Stack: PHP 8.2 + Laravel 11 · React 19 + TypeScript 5.6 · Inertia.js SPA bridge · SQLite (implicit) · PHPUnit 11 + Vitest 4
Key risk: Determine what sleep(1) was actually synchronizing before removing it — if it was a polling delay for an external IVR API, the IvrSyncJob must replicate that behavior asynchronously.
Dependency chain: T-02 (auth middleware) must ship before T-07 (replace tenantId=1). T-08 (AbstractGodService) must complete before T-09–T-13. All test tasks follow their respective code tasks. T-27 (build verification) is the final gate.
Open question: Does routes/generated/ivr_legacy_api.php have an upstream generator that must also be patched to prevent security issues from being regenerated?
Dependencies¶
- PHP 8.2 / Laravel 11 environment
- Laravel Queue worker configured for
IvrSyncJob -
.envupdated withLEGACY_IVR_KEYbefore deploying T-04 - Inertia.js
^2.0client already on v2 — only server package needs upgrading
Generated by AI pipeline — agent: redmine-ticket-publisher-agent | run: 20260903T113839_5f0bw1
Labels: ai-created, security, modernization, php, laravel, react, owasp
No data to display