diff --git a/src/domain-patterns.test.ts b/src/domain-patterns.test.ts
index e1a61c82d..fc395eec1 100644
--- a/src/domain-patterns.test.ts
+++ b/src/domain-patterns.test.ts
@@ -94,33 +94,36 @@ describe('isWildcardPattern', () => {
   });
 });
 
+// Pattern constant for the safer domain character class (matches the implementation)
+const DOMAIN_CHAR_PATTERN = '[a-zA-Z0-9.-]*';
+
 describe('wildcardToRegex', () => {
   describe('basic conversions', () => {
     it('should convert leading wildcard pattern', () => {
-      expect(wildcardToRegex('*.github.com')).toBe('^.*\\.github\\.com$');
+      expect(wildcardToRegex('*.github.com')).toBe(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
     });
 
     it('should convert middle wildcard pattern', () => {
-      expect(wildcardToRegex('api-*.example.com')).toBe('^api-.*\\.example\\.com$');
+      expect(wildcardToRegex('api-*.example.com')).toBe(`^api-${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
     });
 
     it('should convert trailing wildcard pattern', () => {
-      expect(wildcardToRegex('api.*')).toBe('^api\\..*$');
+      expect(wildcardToRegex('api.*')).toBe(`^api\\.${DOMAIN_CHAR_PATTERN}$`);
     });
 
     it('should handle multiple wildcards', () => {
-      expect(wildcardToRegex('*-api-*.example.com')).toBe('^.*-api-.*\\.example\\.com$');
+      expect(wildcardToRegex('*-api-*.example.com')).toBe(`^${DOMAIN_CHAR_PATTERN}-api-${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
     });
   });
 
   describe('escaping', () => {
     it('should escape dots in domain', () => {
-      expect(wildcardToRegex('*.co.uk')).toBe('^.*\\.co\\.uk$');
+      expect(wildcardToRegex('*.co.uk')).toBe(`^${DOMAIN_CHAR_PATTERN}\\.co\\.uk$`);
     });
 
     it('should escape regex metacharacters', () => {
       // These are unlikely in domains but should be handled safely
-      expect(wildcardToRegex('api+*.example.com')).toBe('^api\\+.*\\.example\\.com$');
+      expect(wildcardToRegex('api+*.example.com')).toBe(`^api\\+${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
       expect(wildcardToRegex('api[1].example.com')).toBe('^api\\[1\\]\\.example\\.com$');
     });
   });
@@ -271,7 +274,7 @@ describe('parseDomainList', () => {
 
   it('should convert patterns to regex', () => {
     const result = parseDomainList(['*.github.com']);
-    expect(result.patterns[0].regex).toBe('^.*\\.github\\.com$');
+    expect(result.patterns[0].regex).toBe(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
   });
 
   it('should handle all plain domains', () => {
@@ -328,8 +331,8 @@ describe('parseDomainList', () => {
     it('should handle protocol-prefixed wildcard patterns', () => {
       const result = parseDomainList(['http://*.example.com', 'https://*.secure.com']);
       expect(result.patterns).toEqual([
-        { original: '*.example.com', regex: '^.*\\.example\\.com$', protocol: 'http' },
-        { original: '*.secure.com', regex: '^.*\\.secure\\.com$', protocol: 'https' },
+        { original: '*.example.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.example\\.com$`, protocol: 'http' },
+        { original: '*.secure.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.secure\\.com$`, protocol: 'https' },
       ]);
     });
 
@@ -345,20 +348,20 @@ describe('parseDomainList', () => {
 
 describe('isDomainMatchedByPattern', () => {
   it('should match domain against leading wildcard', () => {
-    const patterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'both' as const }];
+    const patterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'both' as const }];
     expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, patterns)).toBe(true);
     expect(isDomainMatchedByPattern({ domain: 'raw.github.com', protocol: 'both' }, patterns)).toBe(true);
   });
 
   it('should not match domain that does not fit pattern', () => {
-    const patterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'both' as const }];
+    const patterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'both' as const }];
     expect(isDomainMatchedByPattern({ domain: 'github.com', protocol: 'both' }, patterns)).toBe(false);
     expect(isDomainMatchedByPattern({ domain: 'gitlab.com', protocol: 'both' }, patterns)).toBe(false);
     expect(isDomainMatchedByPattern({ domain: 'notgithub.com', protocol: 'both' }, patterns)).toBe(false);
   });
 
   it('should match against middle wildcard', () => {
-    const patterns = [{ original: 'api-*.example.com', regex: '^api-.*\\.example\\.com$', protocol: 'both' as const }];
+    const patterns = [{ original: 'api-*.example.com', regex: `^api-${DOMAIN_CHAR_PATTERN}\\.example\\.com$`, protocol: 'both' as const }];
     expect(isDomainMatchedByPattern({ domain: 'api-v1.example.com', protocol: 'both' }, patterns)).toBe(true);
     expect(isDomainMatchedByPattern({ domain: 'api-test.example.com', protocol: 'both' }, patterns)).toBe(true);
     expect(isDomainMatchedByPattern({ domain: 'api.example.com', protocol: 'both' }, patterns)).toBe(false);
@@ -366,8 +369,8 @@ describe('isDomainMatchedByPattern', () => {
 
   it('should match against any pattern in list', () => {
     const patterns = [
-      { original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'both' as const },
-      { original: '*.gitlab.com', regex: '^.*\\.gitlab\\.com$', protocol: 'both' as const },
+      { original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'both' as const },
+      { original: '*.gitlab.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.gitlab\\.com$`, protocol: 'both' as const },
     ];
     expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, patterns)).toBe(true);
     expect(isDomainMatchedByPattern({ domain: 'api.gitlab.com', protocol: 'both' }, patterns)).toBe(true);
@@ -375,7 +378,7 @@ describe('isDomainMatchedByPattern', () => {
   });
 
   it('should be case-insensitive', () => {
-    const patterns = [{ original: '*.GitHub.com', regex: '^.*\\.GitHub\\.com$', protocol: 'both' as const }];
+    const patterns = [{ original: '*.GitHub.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.GitHub\\.com$`, protocol: 'both' as const }];
     expect(isDomainMatchedByPattern({ domain: 'API.GITHUB.COM', protocol: 'both' }, patterns)).toBe(true);
     expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, patterns)).toBe(true);
   });
@@ -384,32 +387,38 @@ describe('isDomainMatchedByPattern', () => {
     expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, [])).toBe(false);
   });
 
+  it('should return false for excessively long domains (ReDoS protection)', () => {
+    const patterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'both' as const }];
+    const longDomain = 'a'.repeat(600) + '.github.com';
+    expect(isDomainMatchedByPattern({ domain: longDomain, protocol: 'both' }, patterns)).toBe(false);
+  });
+
   describe('protocol compatibility', () => {
     it('should match when pattern has "both" protocol', () => {
-      const patterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'both' as const }];
+      const patterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'both' as const }];
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'http' }, patterns)).toBe(true);
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'https' }, patterns)).toBe(true);
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, patterns)).toBe(true);
     });
 
     it('should not fully cover "both" domain with single protocol pattern', () => {
-      const httpPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'http' as const }];
-      const httpsPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'https' as const }];
+      const httpPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'http' as const }];
+      const httpsPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'https' as const }];
       // A domain that needs "both" cannot be fully covered by a single-protocol pattern
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, httpPatterns)).toBe(false);
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'both' }, httpsPatterns)).toBe(false);
     });
 
     it('should match when protocols match exactly', () => {
-      const httpPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'http' as const }];
-      const httpsPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'https' as const }];
+      const httpPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'http' as const }];
+      const httpsPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'https' as const }];
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'http' }, httpPatterns)).toBe(true);
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'https' }, httpsPatterns)).toBe(true);
     });
 
     it('should not match when protocols do not match', () => {
-      const httpPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'http' as const }];
-      const httpsPatterns = [{ original: '*.github.com', regex: '^.*\\.github\\.com$', protocol: 'https' as const }];
+      const httpPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'http' as const }];
+      const httpsPatterns = [{ original: '*.github.com', regex: `^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`, protocol: 'https' as const }];
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'https' }, httpPatterns)).toBe(false);
       expect(isDomainMatchedByPattern({ domain: 'api.github.com', protocol: 'http' }, httpsPatterns)).toBe(false);
     });
diff --git a/src/domain-patterns.ts b/src/domain-patterns.ts
index d29c30160..277ddb304 100644
--- a/src/domain-patterns.ts
+++ b/src/domain-patterns.ts
@@ -69,6 +69,13 @@ export function isWildcardPattern(domain: string): boolean {
   return domain.includes('*');
 }
 
+/**
+ * Regex pattern for matching valid domain name characters.
+ * Uses character class instead of .* to prevent catastrophic backtracking (ReDoS).
+ * Per RFC 1035, valid domain characters are: letters, digits, hyphens, and dots.
+ */
+const DOMAIN_CHAR_PATTERN = '[a-zA-Z0-9.-]*';
+
 /**
  * Convert a wildcard pattern to a Squid-compatible regex pattern
  *
@@ -77,7 +84,7 @@ export function isWildcardPattern(domain: string): boolean {
  * @throws Error if pattern is invalid
  *
  * Conversion rules:
- * - `*` becomes `.*` (match any characters)
+ * - `*` becomes `[a-zA-Z0-9.-]*` (match valid domain characters, safe from ReDoS)
  * - `.` becomes `\.` (literal dot)
  * - Other regex metacharacters are escaped
  * - Result is anchored with `^` and `$`
@@ -92,7 +99,8 @@ export function wildcardToRegex(pattern: string): string {
 
     switch (char) {
       case '*':
-        regex += '.*';
+        // Use character class instead of .* to prevent catastrophic backtracking
+        regex += DOMAIN_CHAR_PATTERN;
         break;
       case '.':
         regex += '\\.';
@@ -257,6 +265,9 @@ export function parseDomainList(domains: string[]): ParsedDomainList {
  * - Pattern 'http' only covers domain with 'http' protocol
  * - Pattern 'https' only covers domain with 'https' protocol
  *
+ * Security: Input length is validated before regex matching to prevent
+ * potential ReDoS attacks with extremely long inputs.
+ *
  * @param domainEntry - Plain domain entry with protocol to check
  * @param patterns - Array of wildcard patterns with their regex and protocol
  * @returns true if the domain is fully covered by a pattern
@@ -265,6 +276,13 @@ export function isDomainMatchedByPattern(
   domainEntry: PlainDomainEntry,
   patterns: DomainPattern[]
 ): boolean {
+  // Defense in depth: Limit domain length to prevent potential ReDoS
+  // RFC 1035 limits domain names to 253 characters, add buffer for edge cases
+  const MAX_DOMAIN_LENGTH = 512;
+  if (domainEntry.domain.length > MAX_DOMAIN_LENGTH) {
+    return false;
+  }
+
   for (const pattern of patterns) {
     try {
       // Use case-insensitive matching (DNS is case-insensitive)
diff --git a/src/squid-config.test.ts b/src/squid-config.test.ts
index 0c8cfab46..3bcf9cfeb 100644
--- a/src/squid-config.test.ts
+++ b/src/squid-config.test.ts
@@ -1,6 +1,9 @@
 import { generateSquidConfig } from './squid-config';
 import { SquidConfig } from './types';
 
+// Pattern constant for the safer domain character class (matches the implementation)
+const DOMAIN_CHAR_PATTERN = '[a-zA-Z0-9.-]*';
+
 describe('generateSquidConfig', () => {
   const defaultPort = 3128;
 
@@ -610,7 +613,7 @@ describe('generateSquidConfig', () => {
       };
       const result = generateSquidConfig(config);
       expect(result).toContain('acl allowed_domains_regex dstdom_regex -i');
-      expect(result).toContain('^.*\\.github\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
     });
 
     it('should use separate ACLs for plain and pattern domains', () => {
@@ -621,7 +624,7 @@ describe('generateSquidConfig', () => {
       const result = generateSquidConfig(config);
       expect(result).toContain('acl allowed_domains dstdomain .example.com');
       expect(result).toContain('acl allowed_domains_regex dstdom_regex -i');
-      expect(result).toContain('^.*\\.github\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
     });
 
     it('should combine ACLs in http_access rule when both present', () => {
@@ -664,7 +667,7 @@ describe('generateSquidConfig', () => {
       const result = generateSquidConfig(config);
       // api.github.com should be removed since *.github.com covers it
       expect(result).not.toContain('acl allowed_domains dstdomain .api.github.com');
-      expect(result).toContain('^.*\\.github\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
     });
 
     it('should handle middle wildcard patterns', () => {
@@ -673,7 +676,7 @@ describe('generateSquidConfig', () => {
         port: defaultPort,
       };
       const result = generateSquidConfig(config);
-      expect(result).toContain('^api-.*\\.example\\.com$');
+      expect(result).toContain(`^api-${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
     });
 
     it('should handle multiple wildcard patterns', () => {
@@ -682,9 +685,9 @@ describe('generateSquidConfig', () => {
         port: defaultPort,
       };
       const result = generateSquidConfig(config);
-      expect(result).toContain('^.*\\.github\\.com$');
-      expect(result).toContain('^.*\\.gitlab\\.com$');
-      expect(result).toContain('^api-.*\\.example\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.github\\.com$`);
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.gitlab\\.com$`);
+      expect(result).toContain(`^api-${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
       // Should only have regex ACLs
       expect(result).not.toContain('acl allowed_domains dstdomain');
     });
@@ -745,7 +748,7 @@ describe('generateSquidConfig', () => {
       };
       const result = generateSquidConfig(config);
       expect(result).toContain('acl allowed_http_only_regex dstdom_regex -i');
-      expect(result).toContain('^.*\\.example\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.example\\.com$`);
       expect(result).toContain('http_access allow !CONNECT allowed_http_only_regex');
     });
 
@@ -756,7 +759,7 @@ describe('generateSquidConfig', () => {
       };
       const result = generateSquidConfig(config);
       expect(result).toContain('acl allowed_https_only_regex dstdom_regex -i');
-      expect(result).toContain('^.*\\.secure\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.secure\\.com$`);
       expect(result).toContain('http_access allow CONNECT allowed_https_only_regex');
     });
 
@@ -767,11 +770,11 @@ describe('generateSquidConfig', () => {
       };
       const result = generateSquidConfig(config);
       // HTTP-only pattern
-      expect(result).toContain('acl allowed_http_only_regex dstdom_regex -i ^.*\\.api\\.com$');
+      expect(result).toContain(`acl allowed_http_only_regex dstdom_regex -i ^${DOMAIN_CHAR_PATTERN}\\.api\\.com$`);
       // HTTPS-only pattern
-      expect(result).toContain('acl allowed_https_only_regex dstdom_regex -i ^.*\\.secure\\.com$');
+      expect(result).toContain(`acl allowed_https_only_regex dstdom_regex -i ^${DOMAIN_CHAR_PATTERN}\\.secure\\.com$`);
       // Both protocols pattern
-      expect(result).toContain('acl allowed_domains_regex dstdom_regex -i ^.*\\.both\\.com$');
+      expect(result).toContain(`acl allowed_domains_regex dstdom_regex -i ^${DOMAIN_CHAR_PATTERN}\\.both\\.com$`);
     });
   });
 
@@ -858,7 +861,7 @@ describe('generateSquidConfig', () => {
       };
       const result = generateSquidConfig(config);
       expect(result).toContain('acl blocked_domains_regex dstdom_regex -i');
-      expect(result).toContain('^.*\\.internal\\.example\\.com$');
+      expect(result).toContain(`^${DOMAIN_CHAR_PATTERN}\\.internal\\.example\\.com$`);
       expect(result).toContain('http_access deny blocked_domains_regex');
     });
 
@@ -1052,6 +1055,7 @@ describe('generateSquidConfig', () => {
     });
 
     it('should include URL pattern ACLs when provided', () => {
+      // URL patterns passed here are the output of parseUrlPatterns which now uses [^\s]*
       const config: SquidConfig = {
         domains: ['github.com'],
         port: defaultPort,
@@ -1061,11 +1065,11 @@ describe('generateSquidConfig', () => {
           keyPath: '/tmp/test/ssl/ca-key.pem',
         },
         sslDbPath: '/tmp/test/ssl_db',
-        urlPatterns: ['^https://github\\.com/githubnext/.*'],
+        urlPatterns: ['^https://github\\.com/githubnext/[^\\s]*'],
       };
       const result = generateSquidConfig(config);
       expect(result).toContain('acl allowed_url_0 url_regex');
-      expect(result).toContain('^https://github\\.com/githubnext/.*');
+      expect(result).toContain('^https://github\\.com/githubnext/[^\\s]*');
     });
 
     it('should not include SSL Bump section when disabled', () => {
diff --git a/src/ssl-bump.test.ts b/src/ssl-bump.test.ts
index 441dd36eb..0bf2aea04 100644
--- a/src/ssl-bump.test.ts
+++ b/src/ssl-bump.test.ts
@@ -1,5 +1,8 @@
 import { parseUrlPatterns } from './ssl-bump';
 
+// Pattern constant for the safer URL character class (matches the implementation)
+const URL_CHAR_PATTERN = '[^\\s]*';
+
 describe('SSL Bump', () => {
   describe('parseUrlPatterns', () => {
     it('should escape regex special characters except wildcards', () => {
@@ -7,14 +10,14 @@ describe('SSL Bump', () => {
       expect(patterns).toEqual(['^https://github\\.com/user$']);
     });
 
-    it('should convert * wildcard to .* regex', () => {
+    it('should convert * wildcard to safe regex pattern', () => {
       const patterns = parseUrlPatterns(['https://github.com/githubnext/*']);
-      expect(patterns).toEqual(['^https://github\\.com/githubnext/.*']);
+      expect(patterns).toEqual([`^https://github\\.com/githubnext/${URL_CHAR_PATTERN}`]);
     });
 
     it('should handle multiple wildcards', () => {
       const patterns = parseUrlPatterns(['https://api-*.example.com/*']);
-      expect(patterns).toEqual(['^https://api-.*\\.example\\.com/.*']);
+      expect(patterns).toEqual([`^https://api-${URL_CHAR_PATTERN}\\.example\\.com/${URL_CHAR_PATTERN}`]);
     });
 
     it('should remove trailing slash for consistency', () => {
@@ -43,8 +46,8 @@ describe('SSL Bump', () => {
         'https://api.example.com/v1/*',
       ]);
       expect(patterns).toHaveLength(2);
-      expect(patterns[0]).toBe('^https://github\\.com/githubnext/.*');
-      expect(patterns[1]).toBe('^https://api\\.example\\.com/v1/.*');
+      expect(patterns[0]).toBe(`^https://github\\.com/githubnext/${URL_CHAR_PATTERN}`);
+      expect(patterns[1]).toBe(`^https://api\\.example\\.com/v1/${URL_CHAR_PATTERN}`);
     });
 
     it('should handle empty array', () => {
@@ -60,8 +63,8 @@ describe('SSL Bump', () => {
 
     it('should not add end anchor for wildcard patterns', () => {
       const patterns = parseUrlPatterns(['https://github.com/*']);
-      // Should only have start anchor for patterns ending with .*
-      expect(patterns[0]).toBe('^https://github\\.com/.*');
+      // Should only have start anchor for patterns ending with the URL char pattern
+      expect(patterns[0]).toBe(`^https://github\\.com/${URL_CHAR_PATTERN}`);
       expect(patterns[0]).not.toContain('$');
     });
   });
diff --git a/src/ssl-bump.ts b/src/ssl-bump.ts
index d0c0a8dde..c06ed52b3 100644
--- a/src/ssl-bump.ts
+++ b/src/ssl-bump.ts
@@ -167,13 +167,20 @@ export async function isOpenSslAvailable(): Promise<boolean> {
   }
 }
 
+/**
+ * Regex pattern for matching URL path characters.
+ * Uses character class instead of .* to prevent catastrophic backtracking (ReDoS).
+ * Matches any non-whitespace character, which is appropriate for URL paths.
+ */
+const URL_CHAR_PATTERN = '[^\\s]*';
+
 /**
  * Parses URL patterns for SSL Bump ACL rules
  *
  * Converts user-friendly URL patterns into Squid url_regex ACL patterns.
  *
  * Examples:
- * - `https://github.com/githubnext/*` → `^https://github\.com/githubnext/.*`
+ * - `https://github.com/githubnext/*` → `^https://github\.com/githubnext/[^\s]*`
  * - `https://api.example.com/v1/users` → `^https://api\.example\.com/v1/users$`
  *
  * @param patterns - Array of URL patterns (can include wildcards)
@@ -184,22 +191,22 @@ export function parseUrlPatterns(patterns: string[]): string[] {
     // Remove trailing slash for consistency
     let p = pattern.replace(/\/$/, '');
 
-    // Preserve .* patterns by using a placeholder before escaping
+    // Preserve existing .* patterns by using a placeholder before escaping
     const WILDCARD_PLACEHOLDER = '\x00WILDCARD\x00';
     p = p.replace(/\.\*/g, WILDCARD_PLACEHOLDER);
 
     // Escape regex special characters except *
     p = p.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
 
-    // Convert * wildcards to .* regex
-    p = p.replace(/\*/g, '.*');
+    // Convert * wildcards to safe pattern (prevents ReDoS)
+    p = p.replace(/\*/g, URL_CHAR_PATTERN);
 
-    // Restore .* patterns from placeholder
-    p = p.replace(new RegExp(WILDCARD_PLACEHOLDER, 'g'), '.*');
+    // Restore preserved patterns from placeholder
+    p = p.replace(new RegExp(WILDCARD_PLACEHOLDER, 'g'), URL_CHAR_PATTERN);
 
     // Anchor the pattern
-    // If pattern ends with .* (from wildcard), don't add end anchor
-    if (p.endsWith('.*')) {
+    // If pattern ends with the URL char pattern (from wildcard), don't add end anchor
+    if (p.endsWith(URL_CHAR_PATTERN)) {
       return `^${p}`;
     }
     // For exact matches, add end anchor
