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.
Critical because accepted phishing URLs appear on your platform under your domain's authority, directly exposing visitors to credential theft and malware.
Validate URL structure with the native URL constructor, then check the hostname against a blocklist. Reject client-submitted strings that fail either check:
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.
ID: directory-submissions-moderation.submission-form.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:
// 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
}