Use firstOrCreate instead of create so db:seed can run safely on container restart without duplicate key violation.
313 lines
11 KiB
Markdown
313 lines
11 KiB
Markdown
# 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
|