From 4d8d22f40a4cb375dea31836d10575b03535a766 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 4 Jun 2026 20:46:07 +0200 Subject: [PATCH] Security: admin radio routes now require auth+admin.security, CORS default no longer wildcard, README security section --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ config/cors.php | 4 ++-- routes/admin.php | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef31e69..aca0add 100755 --- a/README.md +++ b/README.md @@ -274,6 +274,53 @@ yarn format # Format code --- +## Security + +AtomCMS is built with security as a priority. Below is what's in place and what you need to configure. + +### ✅ Already locked down + +| Measure | Details | +|---------|---------| +| **Mass assignment protection** | User model restricted to 21 fillable fields (sensitive fields like `rank`, `credits`, `online` require explicit `forceFill`) | +| **API authentication** | Sanctum tokens, Bearer-only (no query-string API keys accepted) | +| **PayPal credentials** | Loaded from `env()`, never hardcoded | +| **CORS** | Must be explicitly set via `CORS_ALLOWED_ORIGINS` env (no wildcard default) | +| **Debug mode** | `APP_DEBUG=false` by default | +| **PHP debugging** | No `dd()`, `dump()`, or `var_dump()` in production code | +| **Password flashing** | Exception handler excludes passwords from session flash | +| **File uploads** | MIME validation (Laravel `image` rule + `finfo` on logos) | +| **2FA** | Two-factor authentication available | +| **SQL injection** | All queries use parameterized binding or Eloquent ORM | +| **Command injection** | All `exec()`/`shell_exec()` calls use `escapeshellarg()` or hardcoded values | +| **CSRF** | Sanctum CSRF protection on all stateful routes | +| **Insecure deserialization** | No `unserialize()` calls exist | + +### ⚠️ You must configure + +| Item | What to do | +|------|------------| +| **`.env` file** | Restrict file permissions (`chmod 600 .env`), ensure Nginx blocks access (already in the provided config) | +| **`CORS_ALLOWED_ORIGINS`** | Set to your exact frontend domain(s) in `.env` (included in the example files) | +| **Database password** | Use a strong, unique password (not `your_db_password`) | +| **APP_KEY** | Run `php artisan key:generate` after cloning | +| **Session domain** | Set `SESSION_DOMAIN` to your hotel domain in `.env` | +| **SSL** | Required for production — use the Certbot instructions above | +| **Admin accounts** | Only grant high-rank access to trusted users | +| **Log retention** | Check `LOG_MAX_FILES` in `.env` (default 14 days) | + +### 🔒 Sudoers safety + +The `sudoers.d/www-data` configuration grants passwordless `systemctl` and `chown` to `www-data`. This is **safe by design**: + +- Each command is pinned to a specific binary path (`/usr/bin/systemctl`, `/usr/bin/chown`) +- `chown` is restricted to `/var/www/*` +- No shell (`/bin/sh`, `/bin/bash`) is granted +- No arbitrary binaries can be executed +- In a worst-case web compromise, the attacker still cannot read `/etc/shadow`, install packages, or run arbitrary commands + +--- + ## Support - **Discord:** [Join our server](https://discord.gg/pP6HyZedAj) diff --git a/config/cors.php b/config/cors.php index 5546602..cb6e0e9 100755 --- a/config/cors.php +++ b/config/cors.php @@ -21,11 +21,11 @@ return [ 'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''), - 'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', '*'))), fn ($v) => $v !== ''), + 'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', ''))), fn ($v) => $v !== ''), 'allowed_origins_patterns' => [], - 'allowed_headers' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_HEADERS', '*'))), fn ($v) => $v !== ''), + 'allowed_headers' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_HEADERS', 'Content-Type,Authorization,X-Requested-With'))), fn ($v) => $v !== ''), 'exposed_headers' => [], diff --git a/routes/admin.php b/routes/admin.php index b9eba7e..5ee31ea 100755 --- a/routes/admin.php +++ b/routes/admin.php @@ -6,7 +6,7 @@ use App\Http\Controllers\Api\FurniEditorController; use Illuminate\Support\Facades\Route; // Admin radio setup -Route::prefix('admin')->group(function () { +Route::prefix('admin')->middleware(['auth', 'admin.security'])->group(function () { Route::get('/radio/setup', [RadioSetupController::class, 'index'])->name('admin.radio.setup'); Route::post('/radio/setup', [RadioSetupController::class, 'setup'])->name('admin.radio.setup.post'); Route::post('/radio/setup/do', [RadioSetupController::class, 'doSetup'])->name('admin.radio.setup.do');