Files
sides/AUDIT_REPORT.md
root 9122deaacd fix: seeder idempotent with firstOrCreate
Use firstOrCreate instead of create so db:seed can run safely
on container restart without duplicate key violation.
2026-05-21 02:31:47 +08:00

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 query
  • rainfall.timestamp — used for date filtering and max() queries
  • waterlevel.stationid — used in every water level query
  • waterlevel.datetime — used for date filtering
  • siren.stationid — used in every siren query
  • siren.active_time — used for max() and ordering
  • notification.stationid — used in every notification query
  • notification.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.php line 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.php lines 250-263 (alternative graph query)
  • WaterLevelController.php date conditions
  • sidesdecode.py file 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 directly
  • laravel/pail — installed but no log streaming is used
  • nunomaduro/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

  1. Immediate (before any further changes): Fix SQL injection, rotate credentials, remove .env from git
  2. Short-term: Fix water level stationtype bug, add API auth, add database indexes, fix FCM topic routing
  3. Medium-term: Create Eloquent models, add foreign keys, implement caching, add input validation
  4. Long-term: Full test coverage, clean up naming conventions, choose single CSS framework