Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This Symfony bundle **tracks route performance metrics** (timing, database query
| SQL injection | Use Doctrine parameterized queries; validate sort/filter parameters. |
| SSRF via webhook URLs | Only allow webhook URLs from trusted configuration (env), not from end-user POST bodies. |
| Information leakage in exports | Restrict export actions to trusted roles; avoid exporting secrets from request attributes. |
| DoS via large exports or queries | Use pagination, limits, and infrastructure timeouts. |
| DoS via large exports or queries | Access-record exports capped by `export.max_rows` (default **5000**, max **50000** for BC); use pagination and infrastructure timeouts. |

## Admin UI guard (REQ-UI-002)

Expand Down Expand Up @@ -88,6 +88,6 @@ Before tagging a release, confirm:
| **Logging** | No secrets in application logs from bundle code paths. |
| **Cryptography** | HTTPS/TLS for outbound calls is the deployer’s responsibility; document. |
| **Permissions / exposure** | Dashboard and export routes require `access_roles` or custom checker; `allow_unauthenticated` is `false` by default. |
| **Limits / DoS** | Export size and query limits documented where applicable. |
| **Limits / DoS** | `export.max_rows` defaults to 5000 (configurable up to 50000). |

Record confirmation in the release PR or tag notes.
4 changes: 4 additions & 0 deletions src/Controller/PerformanceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ public function __construct(
#[Autowire('%nowo_performance.access_records_retention_days%')]
private readonly ?int $accessRecordsRetentionDays = null,
private readonly ?PerformanceAccessCheckerInterface $accessChecker = null,
#[Autowire('%nowo_performance.export.max_rows%')]
private readonly int $exportMaxRows = 5000,
) {
}

Expand Down Expand Up @@ -1120,6 +1122,7 @@ public function exportRecordsCsv(Request $request): StreamedResponse
$maxMemoryUsage,
$referer,
$user,
$this->exportMaxRows,
);
} catch (Throwable) {
$result = ['records' => [], 'total' => 0];
Expand Down Expand Up @@ -1244,6 +1247,7 @@ public function exportRecordsJson(Request $request): Response
$maxMemoryUsage,
$referer,
$user,
$this->exportMaxRows,
);
} catch (Throwable) {
$result = ['records' => [], 'total' => 0];
Expand Down
12 changes: 12 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,18 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->arrayNode('export')
->info('Export limits for access-records CSV/JSON downloads')
->addDefaultsIfNotSet()
->children()
->integerNode('max_rows')
->info('Maximum access-record rows returned by export endpoints. Default 5000; set up to 50000 to restore previous behavior.')
->defaultValue(5000)
->min(1)
->max(50000)
->end()
->end()
->end()
->arrayNode('notifications')
->info('Performance alert notifications configuration')
->addDefaultsIfNotSet()
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/PerformanceExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter($securityPath . '.access_checker', $securityConfig['access_checker'] ?? null);
$container->setParameter($securityPath . '.allow_unauthenticated', $securityConfig['allow_unauthenticated'] ?? false);

$exportConfig = $config['export'] ?? [];
$container->setParameter(Configuration::ALIAS . '.export.max_rows', $exportConfig['max_rows'] ?? 5000);

$this->registerAccessChecker($container, $securityConfig);

// Notifications configuration
Expand Down
4 changes: 2 additions & 2 deletions src/Repository/RouteDataRecordRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ public function getPaginatedRecords(
* @param int|null $maxMemoryUsage Optional max memory (bytes)
* @param string|null $referer Optional referer filter (partial match)
* @param string|null $user Optional user filter (partial match)
* @param int $limit Maximum records to return (default 50_000)
* @param int $limit Maximum records to return (default 5_000; configurable via nowo_performance.export.max_rows)
*
* @return array{records: RouteDataRecord[], total: int}
*/
Expand All @@ -918,7 +918,7 @@ public function getRecordsForExport(
?int $maxMemoryUsage = null,
?string $referer = null,
?string $user = null,
int $limit = 50_000,
int $limit = 5_000,
): array {
$qb = $this->createQueryBuilder('r')
->join('r.routeData', 'rd')
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function testDefaultConfiguration(): void
$this->assertSame(['ROLE_ADMIN'], $config['security']['access_roles']);
$this->assertNull($config['security']['access_checker']);
$this->assertFalse($config['security']['allow_unauthenticated']);
$this->assertSame(5000, $config['export']['max_rows']);
}

public function testCustomConfiguration(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function testLoadDefaultConfiguration(): void
$this->assertSame('bootstrap', $this->container->getParameter('nowo_performance.dashboard.template'));
$this->assertSame(['ROLE_ADMIN'], $this->container->getParameter('nowo_performance.security.access_roles'));
$this->assertFalse($this->container->getParameter('nowo_performance.security.allow_unauthenticated'));
$this->assertSame(5000, $this->container->getParameter('nowo_performance.export.max_rows'));
$this->assertTrue($this->container->hasAlias(PerformanceAccessCheckerInterface::class));
$this->assertTrue($this->container->hasDefinition('nowo_performance.access_checker.default'));
$this->assertSame(
Expand Down
Loading