diff --git a/internal/scan/frameworks/detect_test.go b/internal/scan/frameworks/detect_test.go index 905e347..162dd56 100644 --- a/internal/scan/frameworks/detect_test.go +++ b/internal/scan/frameworks/detect_test.go @@ -424,6 +424,65 @@ func TestDetectFramework_Joomla(t *testing.T) { } } +func TestDetectFramework_Astro(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + + + + + + + + +
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 != "Astro" { + t.Errorf("expected framework 'Astro', got '%s'", result.Name) + } +} + +func TestExtractVersion_Astro(t *testing.T) { + tests := []struct { + body string + expected string + }{ + {``, "4.2.0"}, + {``, "3.5.1"}, + {"Astro 4.0.0", "4.0.0"}, + {"Astro/3.2.1", "3.2.1"}, + {`"astro": "^4.1.0"`, "4.1.0"}, + {`"astro": "~3.0.5"`, "3.0.5"}, + {"no version", "unknown"}, + } + + for _, tt := range tests { + result := frameworks.ExtractVersionOptimized(tt.body, "Astro").Version + if result != tt.expected { + t.Errorf("ExtractVersionOptimized(%q, 'Astro') = %q, want %q", tt.body, result, tt.expected) + } + } +} + func TestCVEEntry_Fields(t *testing.T) { entry := frameworks.CVEEntry{ CVE: "CVE-2021-3129", @@ -452,7 +511,7 @@ func TestDetectorRegistry(t *testing.T) { } // Check that some expected detectors are registered - expectedDetectors := []string{"Laravel", "Django", "React", "Vue.js", "Angular", "Next.js", "WordPress"} + expectedDetectors := []string{"Laravel", "Django", "React", "Vue.js", "Angular", "Next.js", "WordPress", "Astro"} for _, name := range expectedDetectors { if _, ok := frameworks.GetDetector(name); !ok { t.Errorf("expected detector %q to be registered", name) diff --git a/internal/scan/frameworks/detectors/meta.go b/internal/scan/frameworks/detectors/meta.go index 8ba5b02..4e9f39f 100644 --- a/internal/scan/frameworks/detectors/meta.go +++ b/internal/scan/frameworks/detectors/meta.go @@ -32,6 +32,7 @@ func init() { fw.Register(&sveltekitDetector{}) fw.Register(&gatsbyDetector{}) fw.Register(&remixDetector{}) + fw.Register(&astroDetector{}) } // nextjsDetector detects Next.js framework. @@ -159,3 +160,32 @@ func (d *remixDetector) Detect(body string, headers http.Header) (float32, strin } return confidence, version } + +// astroDetector detects Astro framework. +type astroDetector struct{} + +func (d *astroDetector) Name() string { return "Astro" } + +func (d *astroDetector) Signatures() []fw.Signature { + return []fw.Signature{ + {Pattern: ` 0.5 { + version = fw.ExtractVersionOptimized(body, d.Name()).Version + } + return confidence, version +} diff --git a/internal/scan/frameworks/version.go b/internal/scan/frameworks/version.go index 8f4553a..efd126e 100644 --- a/internal/scan/frameworks/version.go +++ b/internal/scan/frameworks/version.go @@ -149,6 +149,11 @@ func init() { "Ghost": { {`Ghost[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"}, }, + "Astro": { + {`