Skip to content

Commit 77e3316

Browse files
committed
fix(auth): prevent DNS prefix attacks and enforce single-slash rule
Add proper boundary validation to prevent domains like micro.com from claiming permissions for com.microsoft/* through prefix overlap attacks. Changes: - Add delimiter checking after domain prefix (must be . or /) - Enforce single-slash rule for server names per PR #476 - Add comprehensive test cases for prefix attack scenarios - Update existing tests to align with new validation rules The validation now ensures name patterns have proper delimiters and follow the established server naming conventions.
1 parent 8791079 commit 77e3316

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

internal/api/handlers/v0/auth/dns.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,30 @@ func (h *DNSAuthHandler) buildPermissions(domain string, namePattern string) []a
233233

234234
// For specific name patterns, grant permission only for the specified pattern
235235
// This allows DNS controllers to scope permissions to specific prefixes
236+
// The name pattern MUST be scoped to the domain it is on.
237+
// We need to ensure proper delimiter checking to prevent prefix attacks
238+
// e.g., micro.com should not be able to claim com.microsoft/*
236239
if !strings.HasPrefix(namePattern, reverseDomain) {
237-
// The name pattern MUST be scoped to the domain it is on.
238-
// If it's not, we return no permissions.
240+
return []auth.Permission{}
241+
}
242+
243+
// Check that after the reverse domain, there's either:
244+
// - nothing (exact match)
245+
// - a '.' (subdomain like com.example.api)
246+
// - a '/' (path like com.example/foo)
247+
if len(namePattern) > len(reverseDomain) {
248+
delimiter := namePattern[len(reverseDomain)]
249+
if delimiter != '.' && delimiter != '/' {
250+
// Invalid pattern - doesn't have proper delimiter after domain
251+
return []auth.Permission{}
252+
}
253+
}
254+
255+
// Validate server name format: should have exactly one slash
256+
// This aligns with PR #476 requirements
257+
slashCount := strings.Count(namePattern, "/")
258+
if slashCount > 1 {
259+
// Invalid pattern - multiple slashes not allowed in server names
239260
return []auth.Permission{}
240261
}
241262

internal/api/handlers/v0/auth/dns_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,23 @@ func TestDNSAuthHandler_NamePatternScoping(t *testing.T) {
300300
expectError: false,
301301
},
302302
{
303-
name: "complex pattern with multiple path segments",
303+
name: "pattern with multiple slashes should be rejected",
304304
domain: "example.com",
305305
txtRecords: []string{
306306
fmt.Sprintf("v=MCPv1; k=ed25519; p=%s; n=com.example/services/auth/*", publicKeyB64_1),
307307
},
308308
usePrivateKey: privateKey1,
309-
expectedPatterns: []string{"com.example/services/auth/*"},
309+
expectError: true,
310+
errorContains: "no valid permissions found",
311+
},
312+
{
313+
name: "valid single slash pattern",
314+
domain: "example.com",
315+
txtRecords: []string{
316+
fmt.Sprintf("v=MCPv1; k=ed25519; p=%s; n=com.example/my-server", publicKeyB64_1),
317+
},
318+
usePrivateKey: privateKey1,
319+
expectedPatterns: []string{"com.example/my-server"},
310320
expectedNumPerms: 1,
311321
expectError: false,
312322
},
@@ -332,6 +342,26 @@ func TestDNSAuthHandler_NamePatternScoping(t *testing.T) {
332342
expectError: true,
333343
errorContains: "no valid permissions found",
334344
},
345+
{
346+
name: "malicious prefix overlap - micro.com trying to claim microsoft",
347+
domain: "micro.com",
348+
txtRecords: []string{
349+
fmt.Sprintf("v=MCPv1; k=ed25519; p=%s; n=com.microsoft/*", publicKeyB64_1),
350+
},
351+
usePrivateKey: privateKey1,
352+
expectError: true,
353+
errorContains: "no valid permissions found",
354+
},
355+
{
356+
name: "malicious prefix overlap - example.com trying to claim example.org",
357+
domain: "example.com",
358+
txtRecords: []string{
359+
fmt.Sprintf("v=MCPv1; k=ed25519; p=%s; n=com.examplecorp/*", publicKeyB64_1),
360+
},
361+
usePrivateKey: privateKey1,
362+
expectError: true,
363+
errorContains: "no valid permissions found",
364+
},
335365
{
336366
name: "name pattern is a subdomain",
337367
domain: "example.com",

0 commit comments

Comments
 (0)