From 58e5b3f6483e8f49d26fb70ff4ddf822be167219 Mon Sep 17 00:00:00 2001 From: test Date: Sun, 30 Aug 2026 12:16:22 +0200 Subject: [PATCH] Expose safe production build metadata --- app/Http/Controllers/BuildInfoController.php | 43 ++++++++++++++++++ routes/web.php | 3 ++ tests/Feature/BuildInfoTest.php | 47 ++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 app/Http/Controllers/BuildInfoController.php create mode 100644 tests/Feature/BuildInfoTest.php diff --git a/app/Http/Controllers/BuildInfoController.php b/app/Http/Controllers/BuildInfoController.php new file mode 100644 index 00000000..f65d4d28 --- /dev/null +++ b/app/Http/Controllers/BuildInfoController.php @@ -0,0 +1,43 @@ +json(['error' => 'Build info unavailable'], 404) + ->header('Cache-Control', 'no-store, max-age=0'); + } + + $decoded = json_decode((string) file_get_contents($path), true); + + if (! is_array($decoded)) { + return response()->json(['error' => 'Build info unavailable'], 404) + ->header('Cache-Control', 'no-store, max-age=0'); + } + + $commit = trim((string) ($decoded['git_sha'] ?? '')); + $release = trim((string) ($decoded['release_id'] ?? '')); + $builtAt = trim((string) ($decoded['deployed_at_utc'] ?? '')); + + if ($commit === '' || $release === '' || $builtAt === '') { + return response()->json(['error' => 'Build info unavailable'], 404) + ->header('Cache-Control', 'no-store, max-age=0'); + } + + return response()->json([ + 'environment' => app()->environment(), + 'commit' => $commit, + 'built_at' => $builtAt, + 'release' => $release, + ])->header('Cache-Control', 'no-store, max-age=0'); + } +} diff --git a/routes/web.php b/routes/web.php index 5e27f5b3..4d5899d4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,7 @@ name('build-info'); Route::get('/', [HomeController::class, 'index'])->name('index'); Route::get('/home', [HomeController::class, 'index']); // Legacy route compatibility: permanently redirect old lost-password URL to the diff --git a/tests/Feature/BuildInfoTest.php b/tests/Feature/BuildInfoTest.php new file mode 100644 index 00000000..00533680 --- /dev/null +++ b/tests/Feature/BuildInfoTest.php @@ -0,0 +1,47 @@ +getJson('/build-info.json'); + + if (File::exists(base_path('build-info.json'))) { + $response->assertOk(); + expect(array_keys($response->json()))->toBe(['environment', 'commit', 'built_at', 'release']); + } else { + $response->assertNotFound() + ->assertJson(['error' => 'Build info unavailable']); + } +}); + +it('exposes only the public release provenance fields', function (): void { + $path = base_path('build-info.json'); + $hadOriginal = File::exists($path); + $original = $hadOriginal ? File::get($path) : null; + + File::put($path, json_encode([ + 'git_sha' => 'abc1234', + 'release_id' => '20260830-b37-abc1234', + 'deployed_at_utc' => '2026-08-30T09:30:00Z', + 'git_branch' => 'develop', + 'git_dirty' => 0, + 'remote_folder' => '/opt/www/virtual/SkinbaseNova', + ], JSON_THROW_ON_ERROR)); + + try { + $this->getJson('/build-info.json') + ->assertOk() + ->assertExactJson([ + 'environment' => app()->environment(), + 'commit' => 'abc1234', + 'built_at' => '2026-08-30T09:30:00Z', + 'release' => '20260830-b37-abc1234', + ]); + } finally { + if ($hadOriginal) { + File::put($path, $original); + } else { + File::delete($path); + } + } +});