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.
This commit is contained in:
Tigah
2026-06-22 16:47:34 -07:00
committed by GitHub
parent 21c1d1c8a5
commit 291846dde5
2 changed files with 111 additions and 5 deletions
+107
View File
@@ -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(`<!DOCTYPE html><html><body>Welcome</body></html>`))
}))
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(`
<!DOCTYPE html>
<html>
<head>
<title>Adonis Cosmetics</title>
<link rel="stylesheet" href="/assets/adonis-theme.css">
</head>
<body class="adonis-store">
<h1>Adonis Cosmetics</h1>
<a href="/adonis/collections">Shop the adonis collection</a>
</body>
</html>
`))
}))
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(`
<!DOCTYPE html>
<html>
<head><title>Phoenix App</title></head>
<body>
<div data-phx-main data-phx-session="abc" data-phx-static="def" id="phx-F1a2B3">
<span>Content</span>
</div>
</body>
</html>
`))
}))
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(`
<!DOCTYPE html>
<html>
<head><title>Phoenix AZ Roofing</title></head>
<body class="phx-page">
<nav class="phx-nav"><a href="/">Phoenix Home</a></nav>
<section class="phx-hero">Serving Phoenix, Arizona since 1998.</section>
</body>
</html>
`))
}))
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)
@@ -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},
}
}