From bca4831df1d73b70928efa8f18ffbb430735f4cc Mon Sep 17 00:00:00 2001 From: Tigah <88289044+TBX3D@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:47:43 -0700 Subject: [PATCH] fix(frameworks): stop ghost detector matching the generic ghost-button (#163) the ghost detector keyed on the bare "ghost-" body substring, which fires on any page using the common "ghost-button" or skeleton-loader CSS class (e.g. a WordPress theme), scoring 0.65 and reporting it as Ghost CMS. key on the canonical ` + + Content + + `)) + })) + defer server.Close() + + result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result == nil { + t.Fatal("expected result, got nil") + } + if result.Name != "Ghost" { + t.Errorf("expected framework 'Ghost', got '%s'", result.Name) + } +} + +// ghost-button is a common generic CSS class and must not read as Ghost CMS. +func TestDetectFramework_GhostButtonNoMatch(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + + Sign up + + + `)) + })) + defer server.Close() + + result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result != nil && result.Name == "Ghost" { + t.Errorf("expected no Ghost detection for a ghost-button page, got confidence %.2f", result.Confidence) + } +} + +// the /ghost/api/ path is the only Ghost marker left for pages without the +// generator meta, so guard that it still detects on its own. +func TestDetectFramework_GhostAPIPath(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + + + + + `)) + })) + defer server.Close() + + result, err := frameworks.DetectFramework(server.URL, 5*time.Second, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result == nil { + t.Fatal("expected result, got nil") + } + if result.Name != "Ghost" { + t.Errorf("expected framework 'Ghost', got '%s'", result.Name) + } +} + func TestExtractVersion_Astro(t *testing.T) { tests := []struct { body string diff --git a/internal/scan/frameworks/detectors/cms.go b/internal/scan/frameworks/detectors/cms.go index dc5217d..bce9b5b 100644 --- a/internal/scan/frameworks/detectors/cms.go +++ b/internal/scan/frameworks/detectors/cms.go @@ -173,7 +173,7 @@ func (d *ghostDetector) Name() string { return "Ghost" } func (d *ghostDetector) Signatures() []fw.Signature { return []fw.Signature{ - {Pattern: "ghost-", Weight: 0.4}, + {Pattern: `