Use firstOrCreate instead of create so db:seed can run safely on container restart without duplicate key violation.
11 KiB
AUDIT REPORT — SIDES (tckdev)
Date: 2026-05-20 Auditor: Automated Code Review Scope: Full brownfield codebase audit
Executive Summary
SIDES is a flood early warning system built on Laravel 12 + PostgreSQL + Docker. The application is functional for its purpose but has significant security vulnerabilities, code quality issues, and architectural concerns that should be addressed before any further development or production deployment.
Risk Level: HIGH
1. CRITICAL — Security Vulnerabilities
1.1 SQL Injection (HIGH)
Multiple controllers build raw SQL queries with string interpolation instead of parameterized queries.
RainfallController::index() (line 46):
$stationCondition = " AND s.stationid = '{$stationFilter}'";
WaterLevelController::index() (lines 30, 37):
$stationCondition = " WHERE s.stationid = '{$stationFilter}' ";
$dateCondition = " AND w.datetime = '{$sqlDate}' ";
RainfallController::index() (lines 55-106):
$rainfallData = collect(DB::select("
SELECT ... CAST('$displayDate' AS timestamp) ...
... CAST('$dateFilter' as date) - INTERVAL '6 days' ...
$stationCondition
"));
User input ($stationFilter, $dateFilter, $displayDate, $sqlDate) is interpolated directly into SQL strings. An attacker could inject SQL through these parameters.
Impact: Full database compromise (read, modify, delete data)
Files affected: RainfallController.php, WaterLevelController.php
Fix: Use parameterized queries with ? placeholders or named bindings (:param) consistently.
1.2 Hardcoded Credentials in Source Code (HIGH)
autoscript/sidesdecode.py (lines 22-31):
ftp_server = "myvscada.com"
ftp_username = "tck"
ftp_password = "tck6789"
pg_host = "192.168.0.211"
pg_database = "sides_db"
pg_user = "tck"
pg_password = "projectdev##1"
src/.env (committed to git):
MAIL_PASSWORD="ipmu zifw bpmf fsyp"
POSTGRES_PASSWORD="projectdev##1"
PGADMIN_PASSWORD="projectdev##1"
src/storage/app/firebase/sides-b4abb-3604a7cf7584.json — Firebase service account key is committed to the repository.
Impact: Anyone with repository access has all credentials
Fix: Move to environment variables, add .env to .gitignore, rotate all exposed credentials
1.3 Default Admin Credentials (HIGH)
DatabaseSeeder.php and migration 2025_12_11_124201:
'name' => 'admin',
'password' => Hash::make('password123'),
Impact: Anyone who discovers this gets admin access Fix: Remove hardcoded credentials, require admin setup on first run, use strong passwords
1.4 API Endpoints Unauthenticated (MEDIUM)
All API endpoints except /api/login have no authentication:
Route::get('/station/current', [StationController::class, 'getCurrentData']);
Route::get('/station/rainfall', [StationController::class, 'getRainfallData']);
// ... all publicly accessible
/api/alert is publicly accessible — anyone can trigger push notifications:
Route::post('/alert',[AlertController::class,'send']);
Impact: Data exfiltration, unauthorized push notification spam Fix: Add API authentication (sanctum/passport tokens) to all endpoints
1.5 API Login Returns Password Hash (LOW)
Api\AuthController::login() (line 23):
$user = DB::select("SELECT id, name, email, access_level, password FROM users ...");
While the full $user object isn't returned, the query fetches the password hash unnecessarily.
Fix: Remove password from the SELECT query.
2. HIGH — Code Quality Issues
2.1 No Eloquent ORM Usage
The entire application uses DB::table() and DB::select() (query builder / raw SQL) instead of Eloquent models. Only the User model exists.
Impact:
- No type safety, no attribute casting, no relationship definitions
- Difficulty maintaining and extending the codebase
- No model-level validation or events
Fix: Create Eloquent models for Station, Rainfall, WaterLevel, Siren, Notification with proper relationships.
2.2 No Database Foreign Keys
All table relationships (stationid references) are maintained at the application level with no database constraints:
// Migration: no foreign key
$table->string('stationid');
// vs what it should be:
$table->foreign('stationid')->references('stationid')->on('station');
Impact: Data integrity cannot be guaranteed at the database level. Orphaned records possible.
2.3 Missing Database Indexes
No indexes exist on frequently queried columns:
rainfall.stationid— used in every rainfall queryrainfall.timestamp— used for date filtering and max() querieswaterlevel.stationid— used in every water level querywaterlevel.datetime— used for date filteringsiren.stationid— used in every siren querysiren.active_time— used for max() and orderingnotification.stationid— used in every notification querynotification.timestamp— used for date filtering
Impact: Query performance degrades significantly as data grows
Fix: Add composite indexes on (stationid, timestamp) for data tables
2.4 Duplicate Admin User Creation
Admin user is created in both the DatabaseSeeder AND a migration:
DatabaseSeeder.phpline 22:User::create(['name' => 'Admin', ...])- Migration
2025_12_11_124201:DB::table('users')->insert(['name' => 'admin', ...])
Impact: Running migrate:fresh --seed will create two admin users with different names ("Admin" and "admin")
Fix: Remove one — keep only the seeder.
2.5 Inconsistent Naming Conventions
| Issue | Example |
|---|---|
| Case inconsistency | cctvController (lowercase), SirenHistory (PascalCase) |
| Mixed route naming | sirenhistory vs historicalrf vs historicalwl |
| Mixed table naming | waterlevel.datetime vs rainfall.timestamp (same concept, different column names) |
| Variable typos | $dateTImeFilter (capital I), $dateFilter |
2.6 No Input Validation on Some Endpoints
MapController::getStations() and MapController::getCurrentData() accept no parameters but other controllers accept GET/POST parameters with inconsistent validation.
2.7 Commented-Out Code Blocks
Multiple large blocks of commented-out code throughout:
RainfallController.phplines 250-263 (alternative graph query)WaterLevelController.phpdate conditionssidesdecode.pyfile management functions
3. MEDIUM — Architectural Concerns
3.1 N+1 Query Pattern in MapController
MapController::getCurrentData() uses subqueries in JOINs for latest data:
->whereRaw('r.timestamp = (SELECT MAX(timestamp) FROM rainfall WHERE stationid = s.stationid)')
This executes a subquery for every station for every table join (3 subqueries per station). Should use a CTE or window function.
3.2 No Caching
Real-time data queries (dashboard, map, station listings) run complex SQL on every page load with no caching layer despite data only updating when the Python script runs.
3.3 No Form Request Classes
Validation is done inline in controllers despite Laravel's Form Request feature. Only LoginRequest and ProfileUpdateRequest exist from Breeze scaffolding.
3.4 No API Token Authentication
The API login endpoint validates credentials but returns no token. This makes the API unusable for stateless/mobile clients that need persistent authentication.
3.5 FCM Topic Fixed
AlertController::send() always sends to FCM_TOPIC_RAINFALL_WARNING regardless of alert type:
$topic = env('FCM_TOPIC_RAINFALL_WARNING');
Water level and siren alerts go to the rainfall topic, not their respective topics.
3.6 Water Level Alert Bug
sidesdecode.py line 378:
send_alert_to_laravel(station_id, level, 1) # stationtype=1 (rainfall) instead of 2 (water level)
Water level threshold alerts are sent with stationtype=1 instead of stationtype=2, causing them to be classified as rainfall alerts.
3.7 is_blocked Not Enforced
The is_blocked column exists on users but no middleware checks it. Blocked users can still log in and use the system.
3.8 No Rate Limiting
No rate limiting on login, API endpoints, or form submissions.
4. LOW — Minor Issues
4.1 APP_DEBUG=true in .env
Debug mode should be false in any non-local environment.
4.2 database.db and database.sqlite Committed
SQLite database files are in the repository root and src/database/.
4.3 Unused Packages
laravel/sail— installed but Docker Compose is used directlylaravel/pail— installed but no log streaming is usednunomaduro/collision— dev dependency, standard
4.4 .env Committed
Both .env files (root and src/) are committed to the repository. They should be in .gitignore.
4.5 .editorconfig Duplication
.editorconfig exists in both root and src/ directories.
4.6 No .gitignore for Autoscript Logs
autoscript/sidesdecode.log and sidesdecode_error.log are tracked in git.
4.7 Mixed CSS Frameworks
Both Tailwind CSS and Bootstrap 5 are loaded on pages, creating potential style conflicts and unnecessary bundle size.
4.8 No Tests for Domain Logic
Only Breeze default tests exist (AuthenticationTest, EmailVerificationTest, etc.). No tests for domain controllers, data pipeline, or export logic.
5. Summary Table
| # | Issue | Severity | File(s) | Effort |
|---|---|---|---|---|
| 1.1 | SQL Injection | CRITICAL | RainfallController, WaterLevelController | Medium |
| 1.2 | Hardcoded credentials | CRITICAL | sidesdecode.py, .env | Low |
| 1.3 | Default admin password | CRITICAL | DatabaseSeeder, migration | Low |
| 1.4 | Unauthenticated API | HIGH | routes/api.php | Medium |
| 1.5 | Password hash in query | LOW | AuthController | Low |
| 2.1 | No Eloquent models | HIGH | All controllers | High |
| 2.2 | No foreign keys | HIGH | All migrations | Medium |
| 2.3 | Missing indexes | HIGH | All data tables | Medium |
| 2.4 | Duplicate admin creation | MEDIUM | Seeder + migration | Low |
| 2.5 | Inconsistent naming | LOW | Throughout | Medium |
| 3.1 | N+1 query pattern | MEDIUM | MapController | Medium |
| 3.2 | No caching | MEDIUM | Throughout | Medium |
| 3.5 | FCM topic hardcoded | MEDIUM | AlertController | Low |
| 3.6 | Wrong stationtype for WL | HIGH | sidesdecode.py:378 | Low |
| 3.7 | Blocked users not enforced | MEDIUM | Auth flow | Low |
| 3.8 | No rate limiting | MEDIUM | Throughout | Low |
| 4.1 | Debug mode on | LOW | .env | Low |
| 4.2 | DB files committed | LOW | Root | Low |
| 4.4 | .env committed | LOW | Root | Low |
| 4.7 | Mixed CSS frameworks | LOW | Blade templates | Medium |
| 4.8 | No domain tests | LOW | tests/ | High |
6. Recommended Priority
- Immediate (before any further changes): Fix SQL injection, rotate credentials, remove
.envfrom git - Short-term: Fix water level stationtype bug, add API auth, add database indexes, fix FCM topic routing
- Medium-term: Create Eloquent models, add foreign keys, implement caching, add input validation
- Long-term: Full test coverage, clean up naming conventions, choose single CSS framework