Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion internal/scan/frameworks/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
<!DOCTYPE html>
<html data-astro-transition="forward">
<head>
<meta name="generator" content="Astro v5.16.6">
<link rel="stylesheet" href="/_astro/index.abc123.css">
</head>
<body>
<astro-island data-astro-cid-xyz789 data-astro-source-file="src/components/Counter.astro">
<div>Content</div>
</astro-island>
<nav>
<a href="/about" data-astro-history="push">About</a>
<a href="/external" data-astro-reload>External</a>
</nav>
<script src="/_astro/hoisted.def456.js"></script>
</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 != "Astro" {
t.Errorf("expected framework 'Astro', got '%s'", result.Name)
}
}

func TestExtractVersion_Astro(t *testing.T) {
tests := []struct {
body string
expected string
}{
{`<meta name="generator" content="Astro v4.2.0">`, "4.2.0"},
{`<meta name="generator" content="Astro 3.5.1">`, "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",
Expand Down Expand Up @@ -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)
Expand Down
30 changes: 30 additions & 0 deletions internal/scan/frameworks/detectors/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func init() {
fw.Register(&sveltekitDetector{})
fw.Register(&gatsbyDetector{})
fw.Register(&remixDetector{})
fw.Register(&astroDetector{})
}

// nextjsDetector detects Next.js framework.
Expand Down Expand Up @@ -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: `<meta name="generator" content="Astro`, Weight: 0.6},
{Pattern: "astro-island", Weight: 0.5},
{Pattern: "data-astro-cid-", Weight: 0.4},
{Pattern: "/_astro/", Weight: 0.4},
{Pattern: "data-astro-transition", Weight: 0.3},
{Pattern: "data-astro-reload", Weight: 0.3},
{Pattern: "data-astro-history", Weight: 0.3},
}
}

func (d *astroDetector) Detect(body string, headers http.Header) (float32, string) {
base := fw.NewBaseDetector(d.Name(), d.Signatures())
score := base.MatchSignatures(body, headers)
confidence := sigmoidConfidence(score)

var version string
if confidence > 0.5 {
version = fw.ExtractVersionOptimized(body, d.Name()).Version
}
return confidence, version
}
5 changes: 5 additions & 0 deletions internal/scan/frameworks/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func init() {
"Ghost": {
{`Ghost[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Astro": {
{`<meta name="generator" content="Astro v?(\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
{`Astro[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"astro":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
}

// Compile all patterns
Expand Down