fix(security) : harden auth, session, document access and health endpoint

- Remove orphaned PUBLIC_ACCESS rule for deleted /api/test route
- Remove JWT login firewall (app is session-based only)
- Set APP_SECRET placeholder (real value must be in .env.local)
- Remove JWT env vars from .env
- Add session regeneration on login (prevent session fixation)
- Remove Document.path from API serialization groups (prevent path leak)
- Restrict health check details to ROLE_ADMIN (anonymes get status only)
- Add path traversal guard in DocumentStorageService
- Convert CreateProfileCommand password to interactive hidden prompt
- Restrict Profile Get endpoint to ROLE_ADMIN
- Change api firewall to stateless: false (matches session-based auth)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 13:42:09 +01:00
parent 0709d01240
commit b342d0e50a
9 changed files with 148 additions and 102 deletions

View File

@@ -17,14 +17,7 @@ class HealthCheckController extends AbstractController
#[Route('/api/health', name: 'api_health', methods: ['GET'])]
public function __invoke(Connection $connection): JsonResponse
{
$version = '0.0.0';
$versionFile = $this->getParameter('kernel.project_dir').'/VERSION';
if (file_exists($versionFile)) {
$version = trim(file_get_contents($versionFile));
}
$dbOk = false;
$dbLatency = null;
$dbOk = false;
try {
$start = hrtime(true);
@@ -32,22 +25,33 @@ class HealthCheckController extends AbstractController
$dbLatency = round((hrtime(true) - $start) / 1e6, 1);
$dbOk = true;
} catch (Throwable) {
$dbLatency = null;
}
$healthy = $dbOk;
$data = ['status' => $healthy ? 'ok' : 'degraded'];
return $this->json([
'status' => $healthy ? 'ok' : 'degraded',
'version' => $version,
'timestamp' => new DateTimeImmutable()->format(DateTimeInterface::ATOM),
'php' => PHP_VERSION,
'checks' => [
'database' => [
'status' => $dbOk ? 'ok' : 'down',
'latency_ms' => $dbLatency,
if ($this->isGranted('ROLE_ADMIN')) {
$version = '0.0.0';
$versionFile = $this->getParameter('kernel.project_dir').'/VERSION';
if (file_exists($versionFile)) {
$version = trim(file_get_contents($versionFile));
}
$data += [
'version' => $version,
'timestamp' => new DateTimeImmutable()->format(DateTimeInterface::ATOM),
'php' => PHP_VERSION,
'checks' => [
'database' => [
'status' => $dbOk ? 'ok' : 'down',
'latency_ms' => $dbLatency,
],
],
],
'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 1),
], $healthy ? 200 : 503);
'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 1),
];
}
return $this->json($data, $healthy ? 200 : 503);
}
}

View File

@@ -84,6 +84,7 @@ final class SessionProfileController
return new JsonResponse(['message' => 'Mot de passe incorrect.'], JsonResponse::HTTP_UNAUTHORIZED);
}
$session->migrate(true);
$session->set('profileId', $profile->getId());
$session->set('profileRoles', $profile->getRoles());

View File

@@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class TestController extends AbstractController
{
#[Route('/api/test', name: 'api_test', methods: ['GET', 'POST'])]
public function test(): JsonResponse
{
return $this->json(['status' => 'ok', 'message' => 'Test endpoint works!']);
}
}