domsniper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.env.example +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/package.json +72 -0
  5. package/src/app.tsx +2062 -0
  6. package/src/completions.ts +65 -0
  7. package/src/core/db.ts +1313 -0
  8. package/src/core/features/asn-lookup.ts +91 -0
  9. package/src/core/features/backlinks.ts +83 -0
  10. package/src/core/features/blacklist-check.ts +67 -0
  11. package/src/core/features/cert-transparency.ts +87 -0
  12. package/src/core/features/config.ts +81 -0
  13. package/src/core/features/cors-check.ts +90 -0
  14. package/src/core/features/dns-details.ts +27 -0
  15. package/src/core/features/domain-age.ts +33 -0
  16. package/src/core/features/domain-suggest.ts +87 -0
  17. package/src/core/features/drop-catch.ts +159 -0
  18. package/src/core/features/email-security.ts +112 -0
  19. package/src/core/features/expiring-feed.ts +160 -0
  20. package/src/core/features/export.ts +74 -0
  21. package/src/core/features/filter.ts +96 -0
  22. package/src/core/features/http-probe.ts +46 -0
  23. package/src/core/features/marketplace.ts +69 -0
  24. package/src/core/features/path-scanner.ts +123 -0
  25. package/src/core/features/port-scanner.ts +132 -0
  26. package/src/core/features/portfolio-bulk.ts +125 -0
  27. package/src/core/features/portfolio-monitor.ts +214 -0
  28. package/src/core/features/portfolio.ts +98 -0
  29. package/src/core/features/price-compare.ts +39 -0
  30. package/src/core/features/rdap.ts +128 -0
  31. package/src/core/features/reverse-ip.ts +73 -0
  32. package/src/core/features/s3-export.ts +99 -0
  33. package/src/core/features/scoring.ts +121 -0
  34. package/src/core/features/security-headers.ts +162 -0
  35. package/src/core/features/session.ts +74 -0
  36. package/src/core/features/snipe.ts +264 -0
  37. package/src/core/features/social-check.ts +81 -0
  38. package/src/core/features/ssl-check.ts +88 -0
  39. package/src/core/features/subdomain-discovery.ts +53 -0
  40. package/src/core/features/takeover-detect.ts +143 -0
  41. package/src/core/features/tech-stack.ts +135 -0
  42. package/src/core/features/tld-expand.ts +43 -0
  43. package/src/core/features/variations.ts +134 -0
  44. package/src/core/features/version-check.ts +58 -0
  45. package/src/core/features/waf-detect.ts +171 -0
  46. package/src/core/features/watch.ts +120 -0
  47. package/src/core/features/wayback.ts +64 -0
  48. package/src/core/features/webhooks.ts +126 -0
  49. package/src/core/features/whois-history.ts +99 -0
  50. package/src/core/features/zone-transfer.ts +75 -0
  51. package/src/core/index.ts +50 -0
  52. package/src/core/paths.ts +9 -0
  53. package/src/core/registrar.ts +413 -0
  54. package/src/core/theme.ts +140 -0
  55. package/src/core/types.ts +143 -0
  56. package/src/core/validate.ts +58 -0
  57. package/src/core/whois.ts +265 -0
  58. package/src/index.tsx +1888 -0
  59. package/src/market-client.ts +186 -0
  60. package/src/proxy/ca.ts +116 -0
  61. package/src/proxy/db.ts +175 -0
  62. package/src/proxy/server.ts +155 -0
  63. package/tsconfig.json +30 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Shared type definitions used across all modules
3
+ */
4
+
5
+ import type { DomainStatus } from "./theme.js";
6
+ import type { WhoisResult } from "./whois.js";
7
+ import type { RegistrationResult } from "./registrar.js";
8
+ import type { RdapResult } from "./features/rdap.js";
9
+ import type { SslResult } from "./features/ssl-check.js";
10
+ import type { SubdomainResult } from "./features/subdomain-discovery.js";
11
+ import type { MarketplaceListing } from "./features/marketplace.js";
12
+ import type { SocialCheckResult } from "./features/social-check.js";
13
+ import type { TechStackResult } from "./features/tech-stack.js";
14
+ import type { BlacklistResult } from "./features/blacklist-check.js";
15
+ import type { BacklinkResult } from "./features/backlinks.js";
16
+ import type { PortScanResult } from "./features/port-scanner.js";
17
+ import type { ReverseIpResult } from "./features/reverse-ip.js";
18
+ import type { AsnResult } from "./features/asn-lookup.js";
19
+ import type { EmailSecurityResult } from "./features/email-security.js";
20
+ import type { ZoneTransferResult } from "./features/zone-transfer.js";
21
+ import type { CertTransparencyResult } from "./features/cert-transparency.js";
22
+ import type { TakeoverResult } from "./features/takeover-detect.js";
23
+ import type { SecurityHeadersResult } from "./features/security-headers.js";
24
+ import type { WafResult } from "./features/waf-detect.js";
25
+ import type { PathScanResult } from "./features/path-scanner.js";
26
+ import type { CorsResult } from "./features/cors-check.js";
27
+
28
+ export type { RdapResult } from "./features/rdap.js";
29
+ export type { SslResult } from "./features/ssl-check.js";
30
+ export type { SubdomainResult } from "./features/subdomain-discovery.js";
31
+ export type { MarketplaceListing } from "./features/marketplace.js";
32
+ export type { SocialCheckResult } from "./features/social-check.js";
33
+ export type { TechStackResult } from "./features/tech-stack.js";
34
+ export type { BlacklistResult } from "./features/blacklist-check.js";
35
+ export type { BacklinkResult } from "./features/backlinks.js";
36
+ export type { PortScanResult } from "./features/port-scanner.js";
37
+ export type { ReverseIpResult } from "./features/reverse-ip.js";
38
+ export type { AsnResult } from "./features/asn-lookup.js";
39
+ export type { EmailSecurityResult } from "./features/email-security.js";
40
+ export type { ZoneTransferResult } from "./features/zone-transfer.js";
41
+ export type { CertTransparencyResult } from "./features/cert-transparency.js";
42
+ export type { TakeoverResult } from "./features/takeover-detect.js";
43
+ export type { SecurityHeadersResult } from "./features/security-headers.js";
44
+ export type { WafResult } from "./features/waf-detect.js";
45
+ export type { PathScanResult } from "./features/path-scanner.js";
46
+ export type { CorsResult } from "./features/cors-check.js";
47
+
48
+ export interface DnsDetails {
49
+ a: string[];
50
+ aaaa: string[];
51
+ mx: string[];
52
+ txt: string[];
53
+ cname: string[];
54
+ }
55
+
56
+ export interface HttpProbeResult {
57
+ status: number | null;
58
+ redirectUrl: string | null;
59
+ server: string | null;
60
+ parked: boolean;
61
+ reachable: boolean;
62
+ error: string | null;
63
+ }
64
+
65
+ export interface WaybackResult {
66
+ hasHistory: boolean;
67
+ firstArchived: string | null;
68
+ lastArchived: string | null;
69
+ snapshots: number;
70
+ }
71
+
72
+ export interface DomainEntry {
73
+ domain: string;
74
+ status: DomainStatus;
75
+ whois: WhoisResult | null;
76
+ verification: { available: boolean; confidence: string; checks: string[] } | null;
77
+ registrarCheck: { available: boolean; price?: number; currency?: string } | null;
78
+ registration: RegistrationResult | null;
79
+ error: string | null;
80
+ tagged: boolean;
81
+ dns: DnsDetails | null;
82
+ httpProbe: HttpProbeResult | null;
83
+ wayback: WaybackResult | null;
84
+ domainAge: string | null;
85
+ rdap: RdapResult | null;
86
+ ssl: SslResult | null;
87
+ subdomains: SubdomainResult[] | null;
88
+ marketplace: MarketplaceListing[] | null;
89
+ socialMedia: SocialCheckResult[] | null;
90
+ techStack: TechStackResult | null;
91
+ blacklist: BlacklistResult | null;
92
+ backlinks: BacklinkResult | null;
93
+ portScan: PortScanResult | null;
94
+ reverseIp: ReverseIpResult | null;
95
+ asn: AsnResult | null;
96
+ emailSecurity: EmailSecurityResult | null;
97
+ zoneTransfer: ZoneTransferResult | null;
98
+ certTransparency: CertTransparencyResult | null;
99
+ takeover: TakeoverResult | null;
100
+ securityHeaders: SecurityHeadersResult | null;
101
+ waf: WafResult | null;
102
+ pathScan: PathScanResult | null;
103
+ cors: CorsResult | null;
104
+ }
105
+
106
+ /**
107
+ * Create an empty DomainEntry with sensible defaults
108
+ */
109
+ export function createEmptyEntry(domain: string): DomainEntry {
110
+ return {
111
+ domain,
112
+ status: "pending",
113
+ whois: null,
114
+ verification: null,
115
+ registrarCheck: null,
116
+ registration: null,
117
+ error: null,
118
+ tagged: false,
119
+ dns: null,
120
+ httpProbe: null,
121
+ wayback: null,
122
+ domainAge: null,
123
+ rdap: null,
124
+ ssl: null,
125
+ subdomains: null,
126
+ marketplace: null,
127
+ socialMedia: null,
128
+ techStack: null,
129
+ blacklist: null,
130
+ backlinks: null,
131
+ portScan: null,
132
+ reverseIp: null,
133
+ asn: null,
134
+ emailSecurity: null,
135
+ zoneTransfer: null,
136
+ certTransparency: null,
137
+ takeover: null,
138
+ securityHeaders: null,
139
+ waf: null,
140
+ pathScan: null,
141
+ cors: null,
142
+ };
143
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Input validation utilities for domain names, session IDs, and file paths
3
+ */
4
+
5
+ import { resolve, normalize } from "path";
6
+
7
+ export const DOMAIN_RE =
8
+ /^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?)*\.[a-z]{2,}$/;
9
+
10
+ /**
11
+ * Check if a string is a valid domain name (case-insensitive)
12
+ */
13
+ export function isValidDomain(domain: string): boolean {
14
+ if (domain.length > 253) return false;
15
+ return DOMAIN_RE.test(domain.toLowerCase());
16
+ }
17
+
18
+ /**
19
+ * Assert that a string is a valid domain name, throwing if not
20
+ */
21
+ export function assertValidDomain(domain: string): void {
22
+ if (!isValidDomain(domain)) {
23
+ throw new Error(`Invalid domain: ${domain}`);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Filter and normalize a list of domains to only valid ones (lowercased)
29
+ */
30
+ export function sanitizeDomainList(domains: string[]): string[] {
31
+ return domains
32
+ .map((d) => d.toLowerCase())
33
+ .filter(isValidDomain);
34
+ }
35
+
36
+ /**
37
+ * Check if a string is a valid session ID (alphanumeric + hyphens, max 100 chars)
38
+ */
39
+ export function isValidSessionId(id: string): boolean {
40
+ if (id.length === 0 || id.length > 100) return false;
41
+ return /^[a-z0-9\-]+$/.test(id);
42
+ }
43
+
44
+ /**
45
+ * Resolve and normalize a file path, ensuring it falls within one of the allowed root directories.
46
+ * Throws if the resolved path is outside all allowed roots.
47
+ */
48
+ export function safePath(input: string, allowedRoots: string[]): string {
49
+ const resolved = normalize(resolve(input));
50
+ const isAllowed = allowedRoots.some((root) => {
51
+ const normalizedRoot = normalize(resolve(root));
52
+ return resolved.startsWith(normalizedRoot + "/") || resolved === normalizedRoot;
53
+ });
54
+ if (!isAllowed) {
55
+ throw new Error(`Path "${input}" is outside allowed roots`);
56
+ }
57
+ return resolved;
58
+ }
@@ -0,0 +1,265 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import { assertValidDomain, isValidDomain } from "./validate.js";
4
+
5
+ const execFileAsync = promisify(execFile);
6
+
7
+ export interface WhoisResult {
8
+ domain: string;
9
+ available: boolean;
10
+ expired: boolean;
11
+ expiryDate: string | null;
12
+ registrar: string | null;
13
+ createdDate: string | null;
14
+ updatedDate: string | null;
15
+ status: string[];
16
+ nameServers: string[];
17
+ rawText: string;
18
+ error: string | null;
19
+ }
20
+
21
+ /**
22
+ * Parse a WHOIS response to extract domain info
23
+ */
24
+ function parseWhoisResponse(domain: string, raw: string): WhoisResult {
25
+ const lines = raw.split("\n").map((l) => l.trim());
26
+
27
+ const result: WhoisResult = {
28
+ domain,
29
+ available: false,
30
+ expired: false,
31
+ expiryDate: null,
32
+ registrar: null,
33
+ createdDate: null,
34
+ updatedDate: null,
35
+ status: [],
36
+ nameServers: [],
37
+ rawText: raw,
38
+ error: null,
39
+ };
40
+
41
+ // Check for "not found" / available indicators
42
+ const notFoundPatterns = [
43
+ "no match for",
44
+ "not found",
45
+ "no entries found",
46
+ "no data found",
47
+ "domain not found",
48
+ "no object found",
49
+ "nothing found",
50
+ "status: free",
51
+ "status: available",
52
+ "is available for registration",
53
+ ];
54
+
55
+ const lowerRaw = raw.toLowerCase();
56
+ for (const pattern of notFoundPatterns) {
57
+ if (lowerRaw.includes(pattern)) {
58
+ result.available = true;
59
+ return result;
60
+ }
61
+ }
62
+
63
+ // Extract fields
64
+ for (const line of lines) {
65
+ const lower = line.toLowerCase();
66
+
67
+ // Expiry date
68
+ if (
69
+ !result.expiryDate &&
70
+ (lower.startsWith("registry expiry date:") ||
71
+ lower.startsWith("registrar registration expiration date:") ||
72
+ lower.startsWith("expiration date:") ||
73
+ lower.startsWith("expires:") ||
74
+ lower.startsWith("expiry date:") ||
75
+ lower.startsWith("paid-till:") ||
76
+ lower.startsWith("expire:"))
77
+ ) {
78
+ result.expiryDate = line.split(":").slice(1).join(":").trim();
79
+ }
80
+
81
+ // Registrar
82
+ if (!result.registrar && lower.startsWith("registrar:")) {
83
+ result.registrar = line.split(":").slice(1).join(":").trim();
84
+ }
85
+
86
+ // Created date
87
+ if (
88
+ !result.createdDate &&
89
+ (lower.startsWith("creation date:") ||
90
+ lower.startsWith("created:") ||
91
+ lower.startsWith("created date:") ||
92
+ lower.startsWith("registration date:"))
93
+ ) {
94
+ result.createdDate = line.split(":").slice(1).join(":").trim();
95
+ }
96
+
97
+ // Updated date
98
+ if (
99
+ !result.updatedDate &&
100
+ (lower.startsWith("updated date:") || lower.startsWith("last updated:"))
101
+ ) {
102
+ result.updatedDate = line.split(":").slice(1).join(":").trim();
103
+ }
104
+
105
+ // Status
106
+ if (
107
+ lower.startsWith("domain status:") ||
108
+ lower.startsWith("status:")
109
+ ) {
110
+ const status = line.split(":").slice(1).join(":").trim();
111
+ if (status) result.status.push(status);
112
+ }
113
+
114
+ // Name servers
115
+ if (lower.startsWith("name server:") || lower.startsWith("nserver:")) {
116
+ const ns = line.split(":").slice(1).join(":").trim();
117
+ if (ns) result.nameServers.push(ns);
118
+ }
119
+ }
120
+
121
+ // Check if domain is expired
122
+ if (result.expiryDate) {
123
+ try {
124
+ const expiry = new Date(result.expiryDate);
125
+ const now = new Date();
126
+ if (expiry < now) {
127
+ result.expired = true;
128
+ }
129
+ } catch {
130
+ // Could not parse date
131
+ }
132
+ }
133
+
134
+ // Check status for expiration indicators
135
+ const expiredStatuses = [
136
+ "redemptionperiod",
137
+ "pendingdelete",
138
+ "expired",
139
+ "autorenewperiod",
140
+ ];
141
+ for (const s of result.status) {
142
+ const lowerStatus = s.toLowerCase();
143
+ for (const es of expiredStatuses) {
144
+ if (lowerStatus.includes(es)) {
145
+ result.expired = true;
146
+ break;
147
+ }
148
+ }
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Perform a WHOIS lookup for a domain
156
+ */
157
+ export async function whoisLookup(domain: string): Promise<WhoisResult> {
158
+ assertValidDomain(domain);
159
+ try {
160
+ const { stdout, stderr } = await execFileAsync("whois", [domain], {
161
+ timeout: 15000,
162
+ });
163
+
164
+ const raw = stdout || stderr || "";
165
+ return parseWhoisResponse(domain, raw);
166
+ } catch (err: any) {
167
+ // whois command may return non-zero but still have useful output
168
+ if (err.stdout) {
169
+ return parseWhoisResponse(domain, err.stdout);
170
+ }
171
+ return {
172
+ domain,
173
+ available: false,
174
+ expired: false,
175
+ expiryDate: null,
176
+ registrar: null,
177
+ createdDate: null,
178
+ updatedDate: null,
179
+ status: [],
180
+ nameServers: [],
181
+ rawText: "",
182
+ error: err.message || "WHOIS lookup failed",
183
+ };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Verify domain availability using multiple methods to avoid false positives
189
+ */
190
+ export async function verifyAvailability(
191
+ domain: string
192
+ ): Promise<{ available: boolean; confidence: "high" | "medium" | "low"; checks: string[] }> {
193
+ assertValidDomain(domain);
194
+ const checks: string[] = [];
195
+ let availableCount = 0;
196
+ let totalChecks = 0;
197
+
198
+ // Check 1: WHOIS lookup
199
+ const whois = await whoisLookup(domain);
200
+ totalChecks++;
201
+ if (whois.available) {
202
+ availableCount++;
203
+ checks.push("✓ WHOIS: Domain not found in registry");
204
+ } else if (whois.expired) {
205
+ availableCount += 0.5;
206
+ checks.push("⚠ WHOIS: Domain expired (may be in grace period)");
207
+ } else {
208
+ checks.push("✗ WHOIS: Domain is registered");
209
+ }
210
+
211
+ // Check 2: DNS resolution check
212
+ try {
213
+ const { stdout } = await execFileAsync("dig", ["+short", domain, "A"], {
214
+ timeout: 10000,
215
+ });
216
+ totalChecks++;
217
+ if (!stdout.trim()) {
218
+ availableCount++;
219
+ checks.push("✓ DNS: No A records found");
220
+ } else {
221
+ checks.push(`✗ DNS: Resolves to ${stdout.trim().split("\n")[0]}`);
222
+ }
223
+ } catch {
224
+ checks.push("⚠ DNS: Check failed");
225
+ }
226
+
227
+ // Check 3: NS record check
228
+ try {
229
+ const { stdout } = await execFileAsync("dig", ["+short", domain, "NS"], {
230
+ timeout: 10000,
231
+ });
232
+ totalChecks++;
233
+ if (!stdout.trim()) {
234
+ availableCount++;
235
+ checks.push("✓ NS: No nameservers found");
236
+ } else {
237
+ checks.push(`✗ NS: Has nameservers (${stdout.trim().split("\n")[0]})`);
238
+ }
239
+ } catch {
240
+ checks.push("⚠ NS: Check failed");
241
+ }
242
+
243
+ const ratio = totalChecks > 0 ? availableCount / totalChecks : 0;
244
+ let confidence: "high" | "medium" | "low" = "low";
245
+ if (ratio >= 0.8) confidence = "high";
246
+ else if (ratio >= 0.5) confidence = "medium";
247
+
248
+ return {
249
+ available: ratio >= 0.5,
250
+ confidence,
251
+ checks,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Parse a domain list from file content (one domain per line)
257
+ */
258
+ export function parseDomainList(content: string): string[] {
259
+ return content
260
+ .split("\n")
261
+ .map((line) => line.trim())
262
+ .filter((line) => line && !line.startsWith("#") && !line.startsWith("//"))
263
+ .map((line) => line.toLowerCase())
264
+ .filter(isValidDomain);
265
+ }