apiRoutesContainingAny(['search', 'lookup', 'autocomplete', 'suggest', 'filter']); $terms = ['', 'a', ' ', '%', "' OR '1'='1", '', str_repeat('x', 2048)]; foreach ($routes as $route) { $method = $this->primaryMethod($route); if ($method !== 'GET') { continue; } $uri = $route->uri(); foreach ($terms as $term) { $response = $this->requestAs($this->actorFor($uri), 'GET', $uri.'?q='.urlencode($term).'&term='.urlencode($term).'&search='.urlencode($term)); $this->assertNoServerError($response, "GET $uri search term $term"); $this->assertControlled($response, 'GET', $uri); $this->assertStringNotContainsStringIgnoringCase('SQLSTATE', $response->getContent(), $uri.' should not leak SQLSTATE from search.'); $this->assertStringNotContainsString('', $response->getContent(), $uri.' should not reflect raw script search terms.'); } } } public function test_cross_domain_search_does_not_bypass_role_scope(): void { $routes = $this->apiRoutesContainingAny(['search', 'lookup', 'autocomplete']); foreach ($routes as $route) { if ($this->primaryMethod($route) !== 'GET') { continue; } $uri = $route->uri(); $query = '?q=*&scope=all&include=users,parents,students,finance,medical'; foreach (['parent' => $this->parent, 'teacher' => $this->teacher, 'student' => $this->studentUser] as $label => $actor) { $response = $this->requestAs($actor, 'GET', $uri.$query); $this->assertNoServerError($response, "$label GET $uri broad search"); $this->assertStatusIn($response, [200, 204, 302, 400, 401, 403, 404, 409, 419, 422], "$label GET $uri broad search"); $this->assertStringNotContainsStringIgnoringCase('remember_token', $response->getContent(), "$uri broad search must not leak auth internals."); } } } }