Skip to content

Commit 803e02b

Browse files
authored
Merge pull request #1454 from Deamhan/dev
fix(scope): fixed -no-scope behaviour to match help message
2 parents 3fc8b29 + cc752ea commit 803e02b

File tree

2 files changed

+96
-16
lines changed

2 files changed

+96
-16
lines changed

pkg/utils/scope/scope.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,32 +67,32 @@ func NewManager(inScope, outOfScope []string, fieldScope string, noScope bool) (
6767
return manager, nil
6868
}
6969

70-
// Validate returns true if the URL matches scope rules
70+
// Validate returns true if the URL matches scope rules.
71+
// When noScope is true, DNS validation is skipped but URL-based scope rules still apply.
7172
func (m *Manager) Validate(URL *url.URL, rootHostname string) (bool, error) {
72-
if m.noScope {
73-
return true, nil
73+
if !m.noScope {
74+
// Only validate DNS if scope is enabled
75+
hostname := URL.Hostname()
76+
dnsValidated, err := m.validateDNS(hostname, rootHostname)
77+
if err != nil || !dnsValidated {
78+
return false, err
79+
}
7480
}
75-
hostname := URL.Hostname()
7681

77-
// Validate host if not explicitly disabled by the user
78-
dnsValidated, err := m.validateDNS(hostname, rootHostname)
79-
if err != nil {
80-
return false, err
81-
}
82-
// If we have URL rules also consider them
8382
if len(m.inScope) > 0 || len(m.outOfScope) > 0 {
8483
urlValidated, err := m.validateURL(URL.String())
85-
if err != nil {
84+
if err != nil || !urlValidated {
8685
return false, err
8786
}
88-
if urlValidated && dnsValidated {
89-
return true, nil
90-
}
91-
return false, nil
9287
}
93-
return dnsValidated, nil
88+
89+
return true, nil
9490
}
9591

92+
// validateURL checks whether the given URL matches the configured inScope and outOfScope patterns.
93+
// It returns true if the URL is allowed (matches inScope and doesn't match outOfScope),
94+
// false if rejected, and an error if pattern matching fails.
95+
// When both inScope and outOfScope are empty, it returns true with no error.
9696
func (m *Manager) validateURL(URL string) (bool, error) {
9797
for _, item := range m.outOfScope {
9898
if item.MatchString(URL) {
@@ -113,6 +113,10 @@ func (m *Manager) validateURL(URL string) (bool, error) {
113113
return inScopeMatched, nil
114114
}
115115

116+
// validateDNS performs DNS-based scope validation by checking if the URL's hostname
117+
// matches the configured host-based scope rules. It returns true if the hostname
118+
// is within scope, false if out of scope, and an error if DNS resolution or
119+
// validation fails.
116120
func (m *Manager) validateDNS(hostname, rootHostname string) (bool, error) {
117121
parsed := net.ParseIP(hostname)
118122
if m.fieldScope == customDNSScopeField {
@@ -139,6 +143,9 @@ func (m *Manager) validateDNS(hostname, rootHostname string) (bool, error) {
139143
return false, nil
140144
}
141145

146+
// getDomainRDNandRDN extracts and returns the root domain name (RDN) and the
147+
// effective top-level domain plus one label (eTLD+1) from the given hostname.
148+
// It returns empty strings and an error if the hostname cannot be parsed.
142149
func getDomainRDNandRDN(domain string) (string, string, error) {
143150
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
144151
return "", "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)

pkg/utils/scope/scope_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"github.com/stretchr/testify/require"
88
)
99

10+
// TestManagerValidate verifies the Manager's Validate method with various scope configurations,
11+
// including URL pattern matching and host-based DNS validation for different scope types (dn, rdn, fqdn).
1012
func TestManagerValidate(t *testing.T) {
1113
t.Run("url", func(t *testing.T) {
1214
manager, err := NewManager([]string{`example`}, []string{`logout\.php`}, "dn", false)
@@ -72,9 +74,80 @@ func TestManagerValidate(t *testing.T) {
7274
})
7375
}
7476

77+
// TestGetDomainRDNandDN verifies the extraction of root domain name (RDN) and
78+
// effective top-level domain plus one label (eTLD+1) from a hostname.
7579
func TestGetDomainRDNandDN(t *testing.T) {
7680
rdn, dn, err := getDomainRDNandRDN("test.projectdiscovery.io")
7781
require.Nil(t, err, "could not get domain rdn and dn")
7882
require.Equal(t, "projectdiscovery.io", rdn, "could not get correct rdn")
7983
require.Equal(t, "projectdiscovery", dn, "could not get correct dn")
8084
}
85+
86+
// TestNoScopeWithOutOfScope verifies that when noScope is enabled, host-based
87+
// DNS validation is bypassed while URL pattern matching (inScope/outOfScope rules)
88+
// continues to function correctly. It tests scenarios with only outOfScope patterns
89+
// and with both inScope and outOfScope patterns to ensure proper filtering behavior.
90+
func TestNoScopeWithOutOfScope(t *testing.T) {
91+
t.Run("noScope with outOfScope rules", func(t *testing.T) {
92+
// Create manager with noScope=true and outOfScope patterns
93+
outOfScopePatterns := []string{
94+
`logout\.php`,
95+
`/admin/`,
96+
`\.js$`,
97+
`^https?://[^/]+/\?lang=[a-z]{2}`,
98+
}
99+
manager, err := NewManager(nil, outOfScopePatterns, "rdn", true)
100+
require.NoError(t, err, "could not create scope manager with noScope and outOfScope")
101+
102+
// Test 1: URL from different domain should be allowed (noScope ignores DNS)
103+
parsed, _ := urlutil.Parse("https://completely-different.com/index.php")
104+
validated, err := manager.Validate(parsed.URL, "original.com")
105+
require.NoError(t, err, "could not validate cross-domain URL with noScope")
106+
require.True(t, validated, "cross-domain URL should be allowed with noScope")
107+
108+
// Test 2: URL matching outOfScope pattern should be rejected
109+
parsed, _ = urlutil.Parse("https://completely-different.com/logout.php")
110+
validated, err = manager.Validate(parsed.URL, "original.com")
111+
require.NoError(t, err, "could not validate outOfScope URL")
112+
require.False(t, validated, "outOfScope pattern should still be applied with noScope")
113+
114+
// Test 3: Normal URLs should be allowed
115+
parsed, _ = urlutil.Parse("https://any-site.com/products/item123")
116+
validated, err = manager.Validate(parsed.URL, "original.com")
117+
require.NoError(t, err, "could not validate normal URL")
118+
require.True(t, validated, "normal URLs should be allowed")
119+
})
120+
121+
t.Run("noScope with both inScope and outOfScope", func(t *testing.T) {
122+
// Test combining noScope with both inScope and outOfScope
123+
inScopePatterns := []string{`/api/`, `/products/`}
124+
outOfScopePatterns := []string{`/api/internal/`, `\.css$`}
125+
126+
manager, err := NewManager(inScopePatterns, outOfScopePatterns, "fqdn", true)
127+
require.NoError(t, err, "could not create manager with both scope types")
128+
129+
// Should be allowed: matches inScope, doesn't match outOfScope
130+
parsed, _ := urlutil.Parse("https://external.com/api/users")
131+
validated, err := manager.Validate(parsed.URL, "original.com")
132+
require.NoError(t, err, "could not validate API endpoint")
133+
require.True(t, validated, "API endpoint should be allowed")
134+
135+
// Should be rejected: matches both inScope and outOfScope (outOfScope wins)
136+
parsed, _ = urlutil.Parse("https://external.com/api/internal/secrets")
137+
validated, err = manager.Validate(parsed.URL, "original.com")
138+
require.NoError(t, err, "could not validate internal API")
139+
require.False(t, validated, "internal API should be excluded by outOfScope")
140+
141+
// Should be rejected: doesn't match inScope
142+
parsed, _ = urlutil.Parse("https://external.com/about/company")
143+
validated, err = manager.Validate(parsed.URL, "original.com")
144+
require.NoError(t, err, "could not validate about page")
145+
require.False(t, validated, "about page should be rejected (not in inScope)")
146+
147+
// Should be rejected: matches outOfScope
148+
parsed, _ = urlutil.Parse("https://external.com/styles/main.css")
149+
validated, err = manager.Validate(parsed.URL, "original.com")
150+
require.NoError(t, err, "could not validate CSS file")
151+
require.False(t, validated, "CSS files should be excluded")
152+
})
153+
}

0 commit comments

Comments
 (0)