Merge pull request #56 from 0x4bs3nt/feat/astro-framework-detection

feat(frameworks): add Astro framework detection
This commit is contained in:
Celeste Hickenlooper
2026-01-06 12:10:34 -08:00
committed by GitHub
3 changed files with 95 additions and 1 deletions

View File

@@ -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",
@@ -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)

View File

@@ -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: `<meta name="generator" content="Astro`, Weight: 0.5},
{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
}

View File

@@ -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