Register a content type with a find callback that takes the viewer's group ids and filters by them. A find that ignores the viewer is a leak.
$this->app->make('content_types')->register('example', [
    'label' => 'Examples',
    'find' => function (string $q, int $limit, ?array $viewer = null, array $groupIds = []): array {
        $rows = $this->app->make('examples')->search($q, $limit, $groupIds);

        return array_map(static fn (array $r): array => [
            'title' => $r['title'],
            'url' => $router->url('example.show', ['slug' => $r['slug']]),
            'meta' => 'Example',
            'excerpt' => mb_substr($r['summary'], 0, 110),
        ], $rows);
    },
]);

🚨 The group ids must reach the query. Filter restricted content out before the LIKE, not after — and never after the LIMIT, or results silently shrink.

🚨 A search result is a disclosure. A title is very often the whole of the secret: "Secret Acquisition Plans" tells you what you needed without opening anything. This has already gone wrong here once, with a restricted page returning 404 when opened and handing a signed-out visitor its title through the search box.

Build the URL from the route rather than writing it out, or it drifts until every result is a 404.

Sign in to say whether this helped.