From 291846dde5b3d3253cc2ce3caec0e2aecfe9e8d4 Mon Sep 17 00:00:00 2001 From: Tigah <88289044+TBX3D@users.noreply.github.com> Date: Mon, 22 Jun 2026 16:47:34 -0700 Subject: [PATCH] fix(frameworks): make Phoenix and AdonisJS detection specific (#153) the phoenix and adonis detectors matched bare substrings ("phx-", "phoenix", "adonis") that fire on unrelated pages: a "phx-" css class on a phoenix, arizona site, or any markup containing the word "adonis". replace them with markers the frameworks actually emit. phoenix keys on the liveview container attributes data-phx-main, data-phx-session and data-phx-static; adonis on its default adonis-session cookie. this narrows detection: plain (non-liveview) phoenix and session-less adonis apis are no longer matched. the markers we now key on (liveview's container attributes, adonis's default session cookie) are ones ordinary prose cannot forge. each detector gains a true-positive test and a false-positive tripwire. --- internal/scan/frameworks/detect_test.go | 107 ++++++++++++++++++ internal/scan/frameworks/detectors/backend.go | 9 +- 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/internal/scan/frameworks/detect_test.go b/internal/scan/frameworks/detect_test.go index 19a710a..c592109 100644 --- a/internal/scan/frameworks/detect_test.go +++ b/internal/scan/frameworks/detect_test.go @@ -424,6 +424,113 @@ func TestDetectFramework_Joomla(t *testing.T) { } } +func TestDetectFramework_AdonisJS(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Set-Cookie", "adonis-session=s%3Aabc.def; Path=/; HttpOnly") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`Welcome`)) + })) + 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 != "AdonisJS" { + t.Errorf("expected framework 'AdonisJS', got '%s'", result.Name) + } +} + +// a cosmetics brand page that merely contains "adonis" in its markup (CSS +// classes, asset paths, links) must not be fingerprinted as AdonisJS, as the +// old bare "adonis" substring signature did. +func TestDetectFramework_AdonisFalsePositive(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + + Adonis Cosmetics + + + +

Adonis Cosmetics

+ Shop the adonis collection + + + `)) + })) + 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 == "AdonisJS" { + t.Errorf("false positive: plain page mentioning 'Adonis' detected as AdonisJS (%.2f)", result.Confidence) + } +} + +func TestDetectFramework_Phoenix(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + Phoenix App + +
+ 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 != "Phoenix" { + t.Errorf("expected framework 'Phoenix', got '%s'", result.Name) + } +} + +// a Phoenix, Arizona business page using "phx-" CSS class prefixes must not be +// fingerprinted as the Phoenix framework, as the old bare "phx-" signature did. +func TestDetectFramework_PhoenixFalsePositive(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + Phoenix AZ Roofing + + +
Serving Phoenix, Arizona since 1998.
+ + + `)) + })) + 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 == "Phoenix" { + t.Errorf("false positive: phx- CSS class page detected as Phoenix (%.2f)", result.Confidence) + } +} + func TestDetectFramework_Astro(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) diff --git a/internal/scan/frameworks/detectors/backend.go b/internal/scan/frameworks/detectors/backend.go index 4b324ae..d50f22d 100644 --- a/internal/scan/frameworks/detectors/backend.go +++ b/internal/scan/frameworks/detectors/backend.go @@ -375,9 +375,9 @@ func (d *phoenixDetector) Name() string { return "Phoenix" } func (d *phoenixDetector) Signatures() []fw.Signature { return []fw.Signature{ - {Pattern: "_csrf_token", Weight: 0.4, HeaderOnly: true}, - {Pattern: "phx-", Weight: 0.3}, - {Pattern: "phoenix", Weight: 0.2}, + {Pattern: "data-phx-main", Weight: 0.4}, + {Pattern: "data-phx-session", Weight: 0.3}, + {Pattern: "data-phx-static", Weight: 0.3}, } } @@ -424,8 +424,7 @@ func (d *adonisDetector) Name() string { return "AdonisJS" } func (d *adonisDetector) Signatures() []fw.Signature { return []fw.Signature{ - {Pattern: "adonis", Weight: 0.4}, - {Pattern: "_csrf", Weight: 0.2, HeaderOnly: true}, + {Pattern: "adonis-session", Weight: 0.4, HeaderOnly: true}, } }