# Submitted URLs validated and checked against spam domain blocklist

- **Pattern:** `ab-001079` (`directory-submissions-moderation.submission-form.url-validation-blocklist`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001079
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001079)

## Why it matters

Accepting unvalidated URLs in a directory creates two distinct attack surfaces: phishing (a listing points visitors to a credential-harvesting domain) and open redirect abuse (your platform lends credibility to malicious links). CWE-601 (Open Redirect) and CWE-20 (Improper Input Validation) under OWASP A03 both cover this. A blocklist is not optional — URL format validation alone accepts `https://known-phishing-domain.com` without complaint.

## Severity rationale

Critical because accepted phishing URLs appear on your platform under your domain's authority, directly exposing visitors to credential theft and malware.

## Remediation

Validate URL structure with the native `URL` constructor, then check the hostname against a blocklist. Reject client-submitted strings that fail either check:

```ts
const BLOCKED_DOMAINS = new Set(['bit.ly', 'tinyurl.com', 't.co'])
// Augment from a maintained feed (e.g., PhishTank, URLhaus)

function validateUrl(raw: string): { ok: boolean; error?: string } {
  let parsed: URL
  try { parsed = new URL(raw) } catch {
    return { ok: false, error: 'Invalid URL format' }
  }
  if (!['http:', 'https:'].includes(parsed.protocol)) {
    return { ok: false, error: 'Only HTTP/HTTPS URLs accepted' }
  }
  if (BLOCKED_DOMAINS.has(parsed.hostname)) {
    return { ok: false, error: 'This domain is not permitted' }
  }
  return { ok: true }
}
```

Run this in `app/api/listings/submit/route.ts` before persisting — never defer URL checks to the moderation queue.

## Detection

- **ID:** `url-validation-blocklist`
- **Severity:** `critical`
- **What to look for:** If the form accepts URLs (website, social links, etc.), examine the validation logic. Check for: (1) URL format validation, (2) checks against known spam/phishing domains, (3) verification that the URL is actually reachable (optional but good). Look for a blocklist file or a third-party API call to check URLs.
- **Pass criteria:** Enumerate all relevant code paths. URLs are validated for format (valid URL structure). If a blocklist is used, it's checked server-side and suspicious URLs are rejected before approval. with at least 1 verified instance.
- **Fail criteria:** No URL validation, or URLs are accepted without checking against spam lists, or validation is only client-side.
- **Skip (N/A) when:** The form does not accept URLs.
- **Detail on fail:** `"Form accepts a website URL field but doesn't validate the format or check against spam lists. User can submit entries with broken URLs or phishing links."` or `"URL validation exists on the client but server accepts any string in the URL field."`
- **Remediation:** Validate URLs and check against a blocklist:

  ```ts
  // Validate URL format
  function isValidUrl(urlString: string): boolean {
    try {
      new URL(urlString)
      return true
    } catch {
      return false
    }
  }

  // Simple blocklist check
  const SPAM_DOMAINS = [
    'bit.ly',
    'tinyurl.com',
    'example-spam.com'
    // ... maintain or fetch from external service
  ]

  function isBlocklisted(urlString: string): boolean {
    const url = new URL(urlString)
    return SPAM_DOMAINS.some(domain => url.hostname.includes(domain))
  }

  // On submission
  export async function POST(req: Request) {
    const { website, ...data } = await req.json()

    if (website) {
      if (!isValidUrl(website)) {
        return Response.json(
          { error: 'Invalid website URL' },
          { status: 400 }
        )
      }

      if (isBlocklisted(website)) {
        return Response.json(
          { error: 'This website is not allowed' },
          { status: 400 }
        )
      }
    }

    // Proceed with submission
  }
  ```

---

## External references

- cwe CWE-20
- cwe CWE-601
- owasp A03

Taxons: injection-and-input-trust, content-integrity

HTML version: https://auditbuffet.com/patterns/ab-001079
