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": {
+ {`