Security: admin radio routes now require auth+admin.security, CORS default no longer wildcard, README security section

This commit is contained in:
root
2026-06-04 20:46:07 +02:00
parent 8a324b3082
commit 4d8d22f40a
3 changed files with 50 additions and 3 deletions
+47
View File
@@ -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 ## Support
- **Discord:** [Join our server](https://discord.gg/pP6HyZedAj) - **Discord:** [Join our server](https://discord.gg/pP6HyZedAj)
+2 -2
View File
@@ -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_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_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' => [], 'exposed_headers' => [],
+1 -1
View File
@@ -6,7 +6,7 @@ use App\Http\Controllers\Api\FurniEditorController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
// Admin radio setup // 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::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', [RadioSetupController::class, 'setup'])->name('admin.radio.setup.post');
Route::post('/radio/setup/do', [RadioSetupController::class, 'doSetup'])->name('admin.radio.setup.do'); Route::post('/radio/setup/do', [RadioSetupController::class, 'doSetup'])->name('admin.radio.setup.do');