Files
sif/internal/scan/js/endpoints_test.go
T
vmfunc b4e78114d7 feat(js): extract secrets and endpoints from scanned javascript
the -js pipeline already pulls every <script> into a buffer but only
mined supabase jwts from it. reuse that buffer to run a credential
regex bank (aws/github/slack/stripe/google keys, pem blocks, plus
entropy-gated generic apikey/secret/token assignments) and a
linkfinder-style endpoint extractor that resolves relatives to
absolute urls. both dedupe across scripts and surface through the
existing js logger and result struct, no new flag.
2026-06-09 18:11:38 -07:00

107 lines
3.7 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package js
import (
"slices"
"testing"
)
func TestExtractEndpoints(t *testing.T) {
const base = "https://example.com/static/app.js"
tests := []struct {
name string
content string
wantSome []string // each must appear in the result
wantAbsent []string // none of these may appear
}{
{
name: "root-relative api path resolves to absolute",
content: `fetch("/api/users")`,
wantSome: []string{"https://example.com/api/users"},
},
{
name: "absolute url passes through untouched",
content: `const u = "https://api.example.org/v1/login";`,
wantSome: []string{"https://api.example.org/v1/login"},
},
{
name: "dotted-relative path resolves against base dir",
content: `import("./chunks/main.js")`,
wantSome: []string{"https://example.com/static/chunks/main.js"},
},
{
name: "query string is preserved",
content: `axios.get("/api/search?q=test")`,
wantSome: []string{"https://example.com/api/search?q=test"},
},
{
name: "mime types are filtered out",
content: `headers["Content-Type"] = "application/json"; var t = "text/html";`,
wantAbsent: []string{"application/json", "text/html"},
},
{
name: "single words without a slash are ignored",
content: `var x = "hello"; var y = "world";`,
wantAbsent: []string{"hello", "world"},
},
{
name: "multiple endpoints deduped",
content: `fetch("/api/users"); fetch("/api/users"); fetch("/api/posts");`,
wantSome: []string{
"https://example.com/api/users",
"https://example.com/api/posts",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ExtractEndpoints(tt.content, base)
for _, want := range tt.wantSome {
if !slices.Contains(got, want) {
t.Errorf("expected %q in %v", want, got)
}
}
for _, absent := range tt.wantAbsent {
if slices.Contains(got, absent) {
t.Errorf("did not expect %q in %v", absent, got)
}
}
})
}
}
func TestExtractEndpointsDedupes(t *testing.T) {
got := ExtractEndpoints(`fetch("/api/x"); fetch("/api/x");`, "https://example.com/app.js")
count := 0
for i := 0; i < len(got); i++ {
if got[i] == "https://example.com/api/x" {
count++
}
}
if count != 1 {
t.Fatalf("expected /api/x once, got %d times in %v", count, got)
}
}
func TestExtractEndpointsBadBaseKeepsRelatives(t *testing.T) {
// a base url that won't parse must not drop findings; relatives stay as-is.
got := ExtractEndpoints(`fetch("/api/users")`, "::not a url::")
if !slices.Contains(got, "/api/users") {
t.Errorf("expected relative /api/users preserved, got %v", got)
}
}