mirror of
https://github.com/jakejarvis/rdapper.git
synced 2026-09-23 00:15:31 -04:00
- Add `isSafeWhoisReferralHost` to reject private/loopback/link-local IPs and malformed hostnames before following WHOIS referrals (SSRF guard)
- Detect WHOIS throttle replies in the referral chain: drop the registrar response, keep the registry record, and surface a warning instead of returning bad data
- Add `looksEmptyWhois` guard in the main lookup path: a "registered" record with no parseable fields now fails with `errorCode: "unparseable"` rather than resolving silently
- Map RDAP 429 responses to a structured `rate_limited` error (including `Retry-After` header) so callers can distinguish throttling from generic HTTP errors and the fallback to WHOIS is recorded in `attempts`
- Add `rate_limited` and `unparseable` to `LookupErrorCode`
- Change `collectWhoisReferralChain` to return `{ results, warnings }` instead of a bare array; warnings are merged onto the final `DomainRecord`
- Remove the `followWhoisReferrals` fallback path from `index.ts` (dead code after the chain API stabilised)
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
import type { DomainRecord } from "../types";
|
|
|
|
// Real WHOIS records are much longer; throttle notices and error pages are short.
|
|
const MAX_THROTTLE_TEXT_LENGTH = 2048;
|
|
|
|
const THROTTLE_PATTERNS: RegExp[] = [
|
|
/rate[\s-]?limit/i,
|
|
/limit\s+exceeded/i,
|
|
/quota\s+exceeded/i,
|
|
/exceeded\s+.{0,40}(quer|limit|request|connection)/i,
|
|
/too\s+many\s+(quer|request|connection)/i,
|
|
/try\s+again\s+(later|in)/i,
|
|
/access\s+(denied|limit)/i,
|
|
/\b(blocked|blacklisted|banned)\b/i,
|
|
/^\s*<(!doctype|html)\b/i,
|
|
];
|
|
|
|
/**
|
|
* Heuristic: does this WHOIS response look like a throttle notice or error page rather than
|
|
* a domain record? Only short responses qualify, so a real record that mentions "rate limit"
|
|
* in a remark is not rejected.
|
|
*/
|
|
export function detectWhoisThrottle(text: string | undefined): boolean {
|
|
if (!text) return false;
|
|
if (text.length > MAX_THROTTLE_TEXT_LENGTH) return false;
|
|
return THROTTLE_PATTERNS.some((re) => re.test(text));
|
|
}
|
|
|
|
/** True when a normalized WHOIS record carries none of the fields a real registration would. */
|
|
export function looksEmptyWhois(record: DomainRecord): boolean {
|
|
return !(
|
|
record.registrar ||
|
|
record.creationDate ||
|
|
record.updatedDate ||
|
|
record.expirationDate ||
|
|
record.nameservers?.length ||
|
|
record.statuses?.length ||
|
|
record.contacts?.length
|
|
);
|
|
}
|