mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-30 23:40:13 -07:00
fix(frameworks): stop false positives and version mis-extraction (#247)
a detector accuracy audit surfaced two classes of bug in the framework detectors. bare-brand header false positives: header-only signatures matched a brand name as a substring across every header name and value, so a detector fired on any response that merely referenced the brand (a vendor cdn named in a link or csp value, a cookie sharing the prefix). add an optional Header field to Signature that scopes a header-only match to one named header's value, and apply it (or a structural anchor) per detector: - express: "Express" scoped to x-powered-by, was firing on an express_checkout cookie. - flask: "Werkzeug" scoped to the server header. - symfony: dropped the bare "symfony" word (symfony sets no such header, it fired on symfony.com links); the x-debug-token header is the marker. - shopify: key on the x-shopify response headers instead of the bare "Shopify" word, which fired on a cdn.shopify.com link. - remix: dropped the bare "remix"/"_remix" substrings that fired on a track_remix.mp3 asset; window.__remixContext is the definitive marker. - spring boot: anchor the whitelabel title in its h1 tag context so a tutorial discussing the error does not fire. the gin and fastapi detectors are removed: gin keyed on the "gin-gonic" import-path string (appears in tutorials, never in a real gin response) and fastapi on bare words matching the projects' doc domains. neither framework advertises itself in a response header or a non-prose body marker, so there is no clean passive signal to anchor on. version mis-extraction: drop the low-confidence ".*?" version fallbacks (rails, django, laravel, spring), whose unbounded gap grabbed the first version-shaped number after the framework word and reported an unrelated asset's cache-buster when no real version was present. let isValidVersionString accept a single integer so a bare major such as drupal's "Drupal 10" is no longer rejected as "unknown". each false positive and version bug is covered by a regression test.
This commit is contained in:
@@ -770,39 +770,6 @@ func TestDetectFramework_Htmx(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectFramework_GinFalsePositive(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
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 && result.Name == "Gin" {
|
||||
t.Errorf("false positive: detected Gin (confidence %.2f) on a CORS header", result.Confidence)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectFramework_Gin(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(`404 page not found - powered by gin-gonic`))
|
||||
}))
|
||||
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 != "Gin" {
|
||||
t.Errorf("expected framework 'Gin', got '%v'", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectFramework_MeteorFalsePositive(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
Reference in New Issue
Block a user