'First.Name+tag@gmail.com'], [], [], ['REMOTE_ADDR' => '192.0.2.10']); $second = Request::create('/login', 'POST', ['email' => 'firstname@gmail.com'], [], [], ['REMOTE_ADDR' => '198.51.100.10']); $firstLimits = $limiter($first); $secondLimits = $limiter($second); expect($firstLimits)->toHaveCount(2) ->and($firstLimits[0]->key)->not->toBe($secondLimits[0]->key) ->and($firstLimits[1]->key)->toBe($secondLimits[1]->key); }); it('restricts user broadcast channels to their own ID', function () { $callback = Broadcast::getChannels()['user.{userId}']; $user = User::factory()->create(); expect($callback($user, (int) $user->id))->toBeTrue() ->and($callback($user, (int) $user->id + 1))->toBeFalse(); }); it('does not require email verification for protected web routes', function () { InstanceSettings::forceCreate(['id' => 0]); $user = User::factory()->create(['email_verified_at' => null]); Team::query()->update(['show_boarding' => false]); Cache::flush(); $this->actingAs($user)->get('/analytics')->assertOk(); }); it('does not apply the REST API allowlist to MCP and MCP switch routes', function () { $mcp = Route::getRoutes()->match(Request::create('/mcp', 'POST')); $enable = Route::getRoutes()->match(Request::create('/api/v1/mcp/enable', 'POST')); $disable = Route::getRoutes()->match(Request::create('/api/v1/mcp/disable', 'POST')); expect($mcp)->not->toBeNull() ->and($mcp->gatherMiddleware())->not->toContain(ApiAllowed::class) ->and($enable->gatherMiddleware())->not->toContain(ApiAllowed::class) ->and($disable->gatherMiddleware())->not->toContain(ApiAllowed::class); }); it('throttles only failed authentication on manual webhook routes', function (string $provider) { $route = Route::getRoutes()->match(Request::create("/webhooks/source/{$provider}/events/manual", 'POST')); $request = Request::create("/webhooks/source/{$provider}/events/manual", 'POST', server: ['REMOTE_ADDR' => '192.0.2.44']); $helper = new class { use MatchesManualWebhookApplications; public function key(Request $request, string $provider): string { return $this->manualWebhookFailureRateLimitKey($request, $provider, 'test-org/test-repo', 'main'); } public function reply(array $payloads, string $failureKey): int { return $this->manualWebhookResponse(collect($payloads), $failureKey, $this->manualWebhookTokenAttempt('wrong-token'))->getStatusCode(); } }; $failureKey = $helper->key($request, $provider); expect($route->gatherMiddleware())->not->toContain('throttle:60,1'); expect($failureKey)->toStartWith("manual-webhook-failures:{$provider}:192.0.2.44:"); $helper->reply([['status' => 'success', 'message' => 'queued']], $failureKey); expect(RateLimiter::attempts($failureKey))->toBe(0); $helper->reply([['status' => 'failed', 'message' => 'Invalid signature.']], $failureKey); expect(RateLimiter::attempts($failureKey))->toBe(1); })->with(['github', 'gitlab', 'bitbucket', 'gitea']); it('does not reveal how many applications share a manual webhook repository', function () { $helper = new class { use MatchesManualWebhookApplications; public function reply(array $payloads): string { return $this->manualWebhookResponse(collect($payloads), 'manual-webhook-failures:test', $this->manualWebhookTokenAttempt('wrong-token'))->getContent(); } }; $failure = ['status' => 'failed', 'message' => 'Invalid signature.']; expect($helper->reply([$failure, $failure]))->toBe($helper->reply([$failure])); expect($helper->reply([$failure, ['status' => 'success', 'message' => 'queued']])) ->not->toContain('Invalid signature.'); }); it('never exposes a shown-once database variable even with sensitive read access', function () { $variable = new EnvironmentVariable; $variable->forceFill([ 'key' => 'SECRET', 'value' => 'secret-value', 'is_shown_once' => true, ]); request()->attributes->set('can_read_sensitive', true); $method = new ReflectionMethod(DatabasesController::class, 'removeSensitiveEnvData'); $result = $method->invoke(new DatabasesController, $variable); expect($result)->not->toHaveKey('value') ->not->toHaveKey('real_value'); }); it('does not include nested service and server details in a service application response', function () { $application = new ServiceApplication; $application->forceFill(['name' => 'app']); $application->setRelation('service', (new Service)->forceFill(['name' => 'service'])); $method = new ReflectionMethod(ServiceApplicationsController::class, 'removeSensitiveData'); $result = $method->invoke(new ServiceApplicationsController, $application); expect($result)->not->toHaveKey('service'); });