diff --git a/internal/modules/telescope_exposure_test.go b/internal/modules/telescope_exposure_test.go
new file mode 100644
index 0000000..1da1abc
--- /dev/null
+++ b/internal/modules/telescope_exposure_test.go
@@ -0,0 +1,53 @@
+package modules_test
+
+import "testing"
+
+// laravel-telescope-exposure fires only when the dashboard actually renders: a
+// 200 carrying both the Telescope title and the Vue mount id. A 403-gated
+// install, a soft-404 that reflects the word, and an unrelated "telescope" page
+// must all stay silent.
+func TestLaravelTelescopeExposureModule(t *testing.T) {
+ const telescope = "../../modules/recon/laravel-telescope-exposure.yaml"
+
+ dashboard := `
Telescope - Acme` +
+ ``
+
+ t.Run("an exposed dashboard is flagged and the app name extracted", func(t *testing.T) {
+ res := runDebugModule(t, telescope, 200, dashboard)
+ if len(res.Findings) == 0 {
+ t.Fatal("expected a telescope finding")
+ }
+ if v := debugExtract(res, "app_name"); v != "Acme" {
+ t.Errorf("app_name=%q, want Acme", v)
+ }
+ })
+
+ t.Run("a dashboard without a configured app name still fires", func(t *testing.T) {
+ body := `Telescope` +
+ ``
+ if res := runDebugModule(t, telescope, 200, body); len(res.Findings) == 0 {
+ t.Fatal("expected a telescope finding without an app name")
+ }
+ })
+
+ t.Run("a gated install returning 403 is silent", func(t *testing.T) {
+ if res := runDebugModule(t, telescope, 403, dashboard); len(res.Findings) > 0 {
+ t.Errorf("403-gated telescope should not match, got %d findings", len(res.Findings))
+ }
+ })
+
+ t.Run("an unrelated telescope page without the vue mount is silent", func(t *testing.T) {
+ body := `Telescope Reviews 2026` +
+ ``
+ if res := runDebugModule(t, telescope, 200, body); len(res.Findings) > 0 {
+ t.Errorf("astronomy page should not match, got %d findings", len(res.Findings))
+ }
+ })
+
+ t.Run("a generic soft-404 shell is silent", func(t *testing.T) {
+ body := `404 Not Foundnot found`
+ if res := runDebugModule(t, telescope, 200, body); len(res.Findings) > 0 {
+ t.Errorf("soft-404 should not match, got %d findings", len(res.Findings))
+ }
+ })
+}
diff --git a/modules/recon/laravel-telescope-exposure.yaml b/modules/recon/laravel-telescope-exposure.yaml
new file mode 100644
index 0000000..0bd7770
--- /dev/null
+++ b/modules/recon/laravel-telescope-exposure.yaml
@@ -0,0 +1,37 @@
+# Laravel Telescope Dashboard Exposure Detection Module
+
+id: laravel-telescope-exposure
+info:
+ name: Laravel Telescope Dashboard Exposure
+ author: sif
+ severity: high
+ description: Detects an exposed Laravel Telescope debug dashboard that reveals requests, queries, exceptions, and dumped variables
+ tags: [laravel, telescope, debug, exposure, recon]
+
+type: http
+
+http:
+ method: GET
+ paths:
+ - "{{BaseURL}}/telescope"
+ - "{{BaseURL}}/telescope/requests"
+
+ matchers:
+ - type: status
+ status:
+ - 200
+
+ - type: word
+ part: body
+ words:
+ - 'Telescope'
+ - 'id="telescope" v-cloak'
+ condition: and
+
+ extractors:
+ - type: regex
+ name: app_name
+ part: body
+ regex:
+ - 'Telescope - ([^<]+)'
+ group: 1