|
@@ -54,6 +54,43 @@ final class JobsAdminControllerTest extends AppTestCase
|
|
|
self::assertSame(404, $resp->getStatusCode());
|
|
self::assertSame(404, $resp->getStatusCode());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return iterable<string, array{string}>
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function malformedJobNames(): iterable
|
|
|
|
|
+ {
|
|
|
|
|
+ // SEC_REVIEW F44: any name outside `^[a-z0-9_-]+$` must 404
|
|
|
|
|
+ // BEFORE flowing into `registry->has()` or the `job.triggered`
|
|
|
|
|
+ // audit row. The Slim default segment regex `[^/]+` only
|
|
|
|
|
+ // protects against `/`; everything else (uppercase, dots,
|
|
|
|
|
+ // spaces, control chars, brackets) needs the controller gate.
|
|
|
|
|
+ yield 'uppercase' => ['Recompute-Scores'];
|
|
|
|
|
+ yield 'dotted' => ['recompute.scores'];
|
|
|
|
|
+ yield 'space' => ['recompute scores'];
|
|
|
|
|
+ yield 'newline injection' => ["recompute\nfaked"];
|
|
|
|
|
+ yield 'cr injection' => ["recompute\rfaked"];
|
|
|
|
|
+ yield 'bracket' => ['recompute[scores]'];
|
|
|
|
|
+ yield 'percent' => ['recompute%20scores'];
|
|
|
|
|
+ yield 'empty after url decode' => ['..'];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[\PHPUnit\Framework\Attributes\DataProvider('malformedJobNames')]
|
|
|
|
|
+ public function testTriggerRejectsMalformedJobName(string $name): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $token = $this->createToken(TokenKind::Admin, Role::Admin);
|
|
|
|
|
+ $resp = $this->request(
|
|
|
|
|
+ 'POST',
|
|
|
|
|
+ '/api/v1/admin/jobs/trigger/' . rawurlencode($name),
|
|
|
|
|
+ ['Authorization' => 'Bearer ' . $token],
|
|
|
|
|
+ );
|
|
|
|
|
+ self::assertSame(404, $resp->getStatusCode(), "expected 404 for malformed name '{$name}'");
|
|
|
|
|
+ // Critically: no audit row is emitted for the malformed name.
|
|
|
|
|
+ $audit = $this->db->fetchOne(
|
|
|
|
|
+ "SELECT COUNT(*) FROM audit_log WHERE action = 'job.triggered'"
|
|
|
|
|
+ );
|
|
|
|
|
+ self::assertSame(0, (int) $audit, "no audit row for malformed name '{$name}'");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function testTriggerRecomputeRunsAndAuditsAsManual(): void
|
|
public function testTriggerRecomputeRunsAndAuditsAsManual(): void
|
|
|
{
|
|
{
|
|
|
$token = $this->createToken(TokenKind::Admin, Role::Admin);
|
|
$token = $this->createToken(TokenKind::Admin, Role::Admin);
|