From dd189dde2a615991ed3dd62d6879b57185b3dc77 Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 11:57:31 -0500 Subject: [PATCH 1/2] Add the shared OSV and secret scans beside the local audit --- .github/workflows/security.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 2dfc08b..f7d15e3 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -59,3 +59,16 @@ jobs: - name: Audit run: composer audit --no-interaction + + # The OSV scan and the secret scan this repository has never had. The local `audit` job above is + # deliberately left alone: it caches composer and pins its own PHP, which the shared workflow does + # not, and its `composer update` audit covers dev dependencies that the shared job's + # `--locked --no-dev` skips. The two are not duplicates; neither is a superset of the other. + shared: + name: shared scans (OSV, secrets) + uses: laranail/.github/.github/workflows/security.yml@main + with: + php-version: '8.4' + # Carries OP_SERVICE_ACCOUNT_TOKEN through for the ggshield step. The shared workflow declares it + # optional and skips with a warning when absent, so this is correct before the credential exists. + secrets: inherit From 4d0e94a3dc077193733cfb9a7754ea8363dd0e5e Mon Sep 17 00:00:00 2001 From: Imani Manyara Date: Fri, 13 Nov 2026 12:51:26 -0500 Subject: [PATCH 2/2] Give the throw-only closures the never return type Rector asks for --- tests/Feature/AdapterContractTest.php | 4 ++-- tests/Feature/BotManagementTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Feature/AdapterContractTest.php b/tests/Feature/AdapterContractTest.php index bf4a1cb..de52e0c 100644 --- a/tests/Feature/AdapterContractTest.php +++ b/tests/Feature/AdapterContractTest.php @@ -58,7 +58,7 @@ function httpProviders(): array )); it('fails closed when the provider cannot be reached', function (Provider $provider): void { - Http::fake(fn () => throw new ConnectionException('Connection timed out')); + Http::fake(fn (): never => throw new ConnectionException('Connection timed out')); $result = app(AdapterFactory::class)->make($provider)->verify('a-token', VerificationContext::none()); @@ -116,7 +116,7 @@ function httpProviders(): array })->with('http adapters'); it('never leaks the secret into the failure it returns', function (Provider $provider): void { - Http::fake(fn () => throw new ConnectionException('POST failed: secret=' . TestCredentials::secret('captcha'))); + Http::fake(fn (): never => throw new ConnectionException('POST failed: secret=' . TestCredentials::secret('captcha'))); $result = app(AdapterFactory::class)->make($provider)->verify('a-token', VerificationContext::none()); diff --git a/tests/Feature/BotManagementTest.php b/tests/Feature/BotManagementTest.php index 09807de..8360d58 100644 --- a/tests/Feature/BotManagementTest.php +++ b/tests/Feature/BotManagementTest.php @@ -56,7 +56,7 @@ function dataDomeAnswers(int $status): void }); it('fails open when the provider cannot be reached', function (): void { - Http::fake(fn () => throw new ConnectionException('timed out')); + Http::fake(fn (): never => throw new ConnectionException('timed out')); $this->get('/guarded')->assertOk(); });