Security Hardening: Phase 1-3 complete
- C1: Hash refresh tokens before DB storage (sha256) - C2: Remove JWT_SECRET fallback, fail hard if missing - H1: Enforce HTTP methods per route (405 on mismatch) - H2: CORS with origin whitelist from CORS_ORIGIN env var - H3: Redact sensitive fields (tokens, passwords) from logs - M1: Build HmacMiddleware with replay attack prevention - M2: Fix rate limiter race condition with flock LOCK_EX - M3: Guard dd() — suppressed in production - M4: Remove .env from git tracking, strengthen .gitignore - I1: Add HSTS header (max-age=31536000)
This commit is contained in:
@@ -10,11 +10,11 @@ define('ROOT_PATH', dirname(__DIR__, 2));
|
||||
define('APP_PATH', ROOT_PATH . '/app');
|
||||
define('STORAGE_PATH', ROOT_PATH . '/storage');
|
||||
|
||||
// 2. Load Environment Loader & Helpers FIRST
|
||||
// 2. Load Environment & Helpers FIRST
|
||||
require_once APP_PATH . '/bootstrap/env.php';
|
||||
require_once APP_PATH . '/helpers/helpers.php';
|
||||
|
||||
// 3. Error Reporting (Secure for production - Now we can use env())
|
||||
// 3. Error Reporting (Secure for production)
|
||||
if (env('APP_DEBUG', 'false') === 'true') {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
@@ -23,36 +23,58 @@ if (env('APP_DEBUG', 'false') === 'true') {
|
||||
ini_set('display_errors', '0');
|
||||
}
|
||||
|
||||
// 4. Security Headers
|
||||
// 4. H2 Fix: CORS — Whitelist only known origins
|
||||
$allowedOrigins = array_filter(array_map('trim', explode(',', env('CORS_ORIGIN', 'https://musadaq.intaleqapp.com'))));
|
||||
$requestOrigin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
|
||||
if (in_array($requestOrigin, $allowedOrigins, true)) {
|
||||
header("Access-Control-Allow-Origin: {$requestOrigin}");
|
||||
} else {
|
||||
// Fallback to first allowed origin (for non-browser API clients)
|
||||
header("Access-Control-Allow-Origin: " . ($allowedOrigins[0] ?? ''));
|
||||
}
|
||||
|
||||
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-HMAC-Signature, X-Timestamp");
|
||||
header("Access-Control-Allow-Credentials: true");
|
||||
header("Vary: Origin");
|
||||
|
||||
// Handle CORS preflight
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. Security Headers
|
||||
header("X-Content-Type-Options: nosniff");
|
||||
header("X-Frame-Options: DENY");
|
||||
header("X-XSS-Protection: 1; mode=block");
|
||||
header("Referrer-Policy: strict-origin-when-cross-origin");
|
||||
header("Strict-Transport-Security: max-age=31536000; includeSubDomains"); // I1 Fix: HSTS
|
||||
|
||||
// 5. Intelligent Autoloader (Case-Insensitive for directories)
|
||||
// 6. Intelligent Autoloader (Case-Insensitive for directories)
|
||||
spl_autoload_register(function ($class) {
|
||||
$prefix = 'App\\';
|
||||
$prefix = 'App\\';
|
||||
$base_dir = APP_PATH . '/';
|
||||
|
||||
|
||||
$len = strlen($prefix);
|
||||
if (strncmp($prefix, $class, $len) !== 0) return;
|
||||
|
||||
|
||||
$relative_class = substr($class, $len);
|
||||
|
||||
// Normalize path to lowercase for directories, keep filename case
|
||||
$parts = explode('\\', $relative_class);
|
||||
|
||||
$parts = explode('\\', $relative_class);
|
||||
$filename = array_pop($parts) . '.php';
|
||||
$dir = strtolower(implode('/', $parts));
|
||||
|
||||
$dir = strtolower(implode('/', $parts));
|
||||
|
||||
$file = $base_dir . ($dir ? $dir . '/' : '') . $filename;
|
||||
|
||||
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
}
|
||||
});
|
||||
|
||||
// 6. Response Utility
|
||||
// 7. Response Utility
|
||||
require_once APP_PATH . '/bootstrap/response.php';
|
||||
|
||||
// 7. Global Auth Helper
|
||||
// 8. Global Auth Helper
|
||||
require_once APP_PATH . '/bootstrap/auth.php';
|
||||
|
||||
@@ -1,51 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Standardized JSON Responses with Multi-Level Logging
|
||||
* Standardized JSON Responses with Secure Logging
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function json_response(bool $success, $data = null, ?string $message = null, int $code = 200) {
|
||||
// 1. Prepare Log Entry
|
||||
|
||||
// H3 Fix: Redact sensitive fields before logging
|
||||
$safeData = $data;
|
||||
if (is_array($safeData)) {
|
||||
$sensitiveKeys = ['access_token', 'refresh_token', 'password', 'password_hash', 'refresh_token_hash', 'token'];
|
||||
array_walk_recursive($safeData, function (&$value, $key) use ($sensitiveKeys) {
|
||||
if (in_array(strtolower((string)$key), $sensitiveKeys, true)) {
|
||||
$value = '[REDACTED]';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Log (safe — no secrets)
|
||||
$logEntry = sprintf(
|
||||
"API %s %s | Code: %d | Success: %s | Message: %s | Data: %s",
|
||||
"API %s %s | %d | %s | %s",
|
||||
$_SERVER['REQUEST_METHOD'] ?? 'CLI',
|
||||
$_SERVER['REQUEST_URI'] ?? '',
|
||||
$code,
|
||||
$success ? 'YES' : 'NO',
|
||||
$message ?? 'N/A',
|
||||
json_encode($data, JSON_UNESCAPED_UNICODE)
|
||||
$success ? 'OK' : 'FAIL',
|
||||
$message ?? 'N/A'
|
||||
);
|
||||
|
||||
// 2. Log to Standard PHP Error Log (Visible in CloudPanel Logs)
|
||||
|
||||
error_log($logEntry);
|
||||
|
||||
// 3. Try to log to custom app.log in storage folder
|
||||
$logDir = STORAGE_PATH . '/logs';
|
||||
// Try custom log file
|
||||
$logDir = STORAGE_PATH . '/logs';
|
||||
$logFile = $logDir . '/app.log';
|
||||
|
||||
|
||||
try {
|
||||
if (!is_dir($logDir)) {
|
||||
@mkdir($logDir, 0775, true);
|
||||
}
|
||||
if (is_writable($logDir) || is_writable($logFile)) {
|
||||
@file_put_contents($logFile, "[" . date('Y-m-d H:i:s') . "] " . $logEntry . "\n", FILE_APPEND);
|
||||
@file_put_contents(
|
||||
$logFile,
|
||||
"[" . date('Y-m-d H:i:s') . "] " . $logEntry . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Fallback if filesystem is locked
|
||||
// Fallback silently
|
||||
}
|
||||
|
||||
// 4. HTTP Response
|
||||
// HTTP Response
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
http_response_code($code);
|
||||
|
||||
|
||||
echo json_encode([
|
||||
'success' => $success,
|
||||
'data' => $data,
|
||||
'data' => $data, // Return real data to client
|
||||
'message' => $message,
|
||||
'timestamp' => date('c')
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user