# 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):**
```php
$stationCondition = " AND s.stationid = '{$stationFilter}'";
```

**`WaterLevelController::index()` (lines 30, 37):**
```php
$stationCondition = " WHERE s.stationid = '{$stationFilter}' ";
$dateCondition = " AND w.datetime = '{$sqlDate}' ";
```

**`RainfallController::index()` (lines 55-106):**
```php
$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):**
```python
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`:**
```php
'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**:
```php
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:
```php
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):**
```php
$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:

```php
// 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:

```php
->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:

```php
$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:**
```python
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 |

---

## 6. Recommended Priority

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
