From 2ddda5da894089ae404ab1bcf74e6d1fce21a144 Mon Sep 17 00:00:00 2001 From: FurkanL0 Date: Fri, 27 Feb 2026 17:06:17 +0300 Subject: [PATCH 1/2] Create DESCRIPTION.md --- skills/domain/DESCRIPTION.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 skills/domain/DESCRIPTION.md diff --git a/skills/domain/DESCRIPTION.md b/skills/domain/DESCRIPTION.md new file mode 100644 index 000000000..ae139e683 --- /dev/null +++ b/skills/domain/DESCRIPTION.md @@ -0,0 +1,24 @@ +--- +name: domain-intel +description: Passive domain reconnaissance using Python stdlib. Use this skill for subdomain discovery, SSL certificate inspection, WHOIS lookups, DNS records, domain availability checks, and bulk multi-domain analysis. No API keys required. Triggers on requests like "find subdomains", "check ssl cert", "whois lookup", "is this domain available", "bulk check these domains". +license: MIT +--- + +Passive domain intelligence using only Python stdlib and public data sources. +Zero dependencies. Zero API keys. Works out of the box. + +## Capabilities + +- Subdomain discovery via crt.sh certificate transparency logs +- Live SSL/TLS certificate inspection (expiry, cipher, SANs, TLS version) +- WHOIS lookup — supports 100+ TLDs via direct TCP queries +- DNS records: A, AAAA, MX, NS, TXT, CNAME +- Domain availability check (DNS + WHOIS + SSL signals) +- Bulk multi-domain analysis in parallel (up to 20 domains) + +## Data Sources + +- crt.sh — Certificate Transparency logs +- WHOIS servers — Direct TCP to 100+ authoritative TLD servers +- Google DNS-over-HTTPS — MX/NS/TXT/CNAME resolution +- System DNS — A/AAAA records From f9e05218caf6ce0b754a6e2e8ef73e6f34073e99 Mon Sep 17 00:00:00 2001 From: FurkanL0 Date: Fri, 27 Feb 2026 17:07:13 +0300 Subject: [PATCH 2/2] Create SKILL.md --- skills/domain/domain-intel/SKILL.md | 392 ++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 skills/domain/domain-intel/SKILL.md diff --git a/skills/domain/domain-intel/SKILL.md b/skills/domain/domain-intel/SKILL.md new file mode 100644 index 000000000..b2a897989 --- /dev/null +++ b/skills/domain/domain-intel/SKILL.md @@ -0,0 +1,392 @@ +--- +name: domain-intel +description: Passive domain reconnaissance using Python stdlib. Use this skill for subdomain discovery, SSL certificate inspection, WHOIS lookups, DNS records, domain availability checks, and bulk multi-domain analysis. No API keys required. Triggers on requests like "find subdomains", "check ssl cert", "whois lookup", "is this domain available", "bulk check these domains". +--- + +# Domain Intelligence — Passive OSINT + +Passive domain reconnaissance using only Python stdlib and public data sources. +**Zero dependencies. Zero API keys. Works out of the box.** + +## Data Sources + +- **crt.sh** — Certificate Transparency logs (subdomain discovery) +- **WHOIS servers** — Direct TCP queries to 100+ authoritative TLD servers +- **Google DNS-over-HTTPS** — MX/NS/TXT/CNAME resolution +- **System DNS** — A/AAAA record resolution + +--- + +## Usage + +When the user asks about a domain, use the `terminal` tool to run the appropriate Python snippet below. +All functions print structured JSON. Parse and summarize results for the user. + +--- + +## 1. Subdomain Discovery (crt.sh) + +```python +import json, urllib.request, urllib.parse +from datetime import datetime, timezone + +def subdomains(domain, include_expired=False, limit=200): + url = f"https://crt.sh/?q=%25.{urllib.parse.quote(domain)}&output=json" + req = urllib.request.Request(url, headers={"User-Agent": "domain-intel-skill/1.0", "Accept": "application/json"}) + with urllib.request.urlopen(req, timeout=15) as r: + entries = json.loads(r.read().decode()) + + seen, results = set(), [] + for e in entries: + not_after = e.get("not_after", "") + if not include_expired and not_after: + try: + dt = datetime.strptime(not_after[:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc) + if dt <= datetime.now(timezone.utc): + continue + except ValueError: + pass + for name in e.get("name_value", "").splitlines(): + name = name.strip().lower() + if name and name not in seen: + seen.add(name) + results.append({"subdomain": name, "issuer": e.get("issuer_name",""), "not_after": not_after}) + + results.sort(key=lambda r: (r["subdomain"].startswith("*"), r["subdomain"])) + results = results[:limit] + print(json.dumps({"domain": domain, "count": len(results), "subdomains": results}, indent=2)) + +subdomains("DOMAIN_HERE") +``` + +**Example:** Replace `DOMAIN_HERE` with `example.com` + +--- + +## 2. SSL Certificate Inspection + +```python +import json, ssl, socket +from datetime import datetime, timezone + +def check_ssl(host, port=443, timeout=10): + def flat(rdns): + r = {} + for rdn in rdns: + for item in rdn: + if isinstance(item, (list,tuple)) and len(item)==2: + r[item[0]] = item[1] + return r + + def extract_uris(entries): + return [e[-1] if isinstance(e,(list,tuple)) else str(e) for e in entries] + + def parse_date(s): + for fmt in ("%b %d %H:%M:%S %Y %Z", "%b %d %H:%M:%S %Y %Z"): + try: return datetime.strptime(s, fmt).replace(tzinfo=timezone.utc) + except ValueError: pass + return None + + warning = None + try: + ctx = ssl.create_default_context() + with socket.create_connection((host, port), timeout=timeout) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as s: + cert, cipher, proto = s.getpeercert(), s.cipher(), s.version() + except ssl.SSLCertVerificationError as e: + warning = str(e) + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with socket.create_connection((host, port), timeout=timeout) as sock: + with ctx.wrap_socket(sock, server_hostname=host) as s: + cert, cipher, proto = s.getpeercert(), s.cipher(), s.version() + + not_after = parse_date(cert.get("notAfter","")) + not_before = parse_date(cert.get("notBefore","")) + now = datetime.now(timezone.utc) + days = (not_after - now).days if not_after else None + is_expired = days is not None and days < 0 + + if is_expired: status = f"EXPIRED ({abs(days)} days ago)" + elif days is not None and days <= 14: status = f"CRITICAL — {days} day(s) left" + elif days is not None and days <= 30: status = f"WARNING — {days} day(s) left" + else: status = f"OK — {days} day(s) remaining" if days is not None else "unknown" + + print(json.dumps({ + "host": host, "port": port, + "subject": flat(cert.get("subject",[])), + "issuer": flat(cert.get("issuer",[])), + "subject_alt_names": [f"{t}:{v}" for t,v in cert.get("subjectAltName",[])], + "not_before": not_before.isoformat() if not_before else "", + "not_after": not_after.isoformat() if not_after else "", + "days_remaining": days, "is_expired": is_expired, "expiry_status": status, + "tls_version": proto, "cipher_suite": cipher[0] if cipher else None, + "serial_number": cert.get("serialNumber",""), + "ocsp_urls": extract_uris(cert.get("OCSP",[])), + "ca_issuers": extract_uris(cert.get("caIssuers",[])), + "verification_warning": warning, + }, indent=2)) + +check_ssl("DOMAIN_HERE") +``` + +--- + +## 3. WHOIS Lookup (100+ TLDs) + +```python +import json, socket, re +from datetime import datetime, timezone + +WHOIS_SERVERS = { + "com":"whois.verisign-grs.com","net":"whois.verisign-grs.com","org":"whois.pir.org", + "io":"whois.nic.io","co":"whois.nic.co","ai":"whois.nic.ai","dev":"whois.nic.google", + "app":"whois.nic.google","tech":"whois.nic.tech","shop":"whois.nic.shop", + "store":"whois.nic.store","online":"whois.nic.online","site":"whois.nic.site", + "cloud":"whois.nic.cloud","digital":"whois.nic.digital","media":"whois.nic.media", + "blog":"whois.nic.blog","info":"whois.afilias.net","biz":"whois.biz", + "me":"whois.nic.me","tv":"whois.nic.tv","cc":"whois.nic.cc","ws":"whois.website.ws", + "uk":"whois.nic.uk","co.uk":"whois.nic.uk","de":"whois.denic.de","nl":"whois.domain-registry.nl", + "fr":"whois.nic.fr","it":"whois.nic.it","es":"whois.nic.es","pl":"whois.dns.pl", + "ru":"whois.tcinet.ru","se":"whois.iis.se","no":"whois.norid.no","fi":"whois.fi", + "ch":"whois.nic.ch","at":"whois.nic.at","be":"whois.dns.be","cz":"whois.nic.cz", + "br":"whois.registro.br","ca":"whois.cira.ca","mx":"whois.mx","au":"whois.auda.org.au", + "jp":"whois.jprs.jp","cn":"whois.cnnic.cn","in":"whois.inregistry.net","kr":"whois.kr", + "sg":"whois.sgnic.sg","hk":"whois.hkirc.hk","tr":"whois.nic.tr","ae":"whois.aeda.net.ae", + "za":"whois.registry.net.za","ng":"whois.nic.net.ng","ly":"whois.nic.ly", + "space":"whois.nic.space","zone":"whois.nic.zone","ninja":"whois.nic.ninja", + "guru":"whois.nic.guru","rocks":"whois.nic.rocks","social":"whois.nic.social", + "network":"whois.nic.network","global":"whois.nic.global","design":"whois.nic.design", + "studio":"whois.nic.studio","agency":"whois.nic.agency","finance":"whois.nic.finance", + "legal":"whois.nic.legal","health":"whois.nic.health","green":"whois.nic.green", + "city":"whois.nic.city","land":"whois.nic.land","live":"whois.nic.live", + "game":"whois.nic.game","games":"whois.nic.games","pw":"whois.nic.pw", + "mn":"whois.nic.mn","sh":"whois.nic.sh","gg":"whois.gg","im":"whois.nic.im", +} + +def whois_query(domain, server, port=43): + with socket.create_connection((server, port), timeout=10) as s: + s.sendall((domain+"\r\n").encode()) + chunks = [] + while True: + c = s.recv(4096) + if not c: break + chunks.append(c) + return b"".join(chunks).decode("utf-8", errors="replace") + +def parse_iso(s): + if not s: return None + for fmt in ("%Y-%m-%dT%H:%M:%S","%Y-%m-%dT%H:%M:%SZ","%Y-%m-%d %H:%M:%S","%Y-%m-%d"): + try: return datetime.strptime(s[:19],fmt).replace(tzinfo=timezone.utc) + except ValueError: pass + return None + +def whois(domain): + parts = domain.split(".") + server = WHOIS_SERVERS.get(".".join(parts[-2:])) or WHOIS_SERVERS.get(parts[-1]) + if not server: + print(json.dumps({"error": f"No WHOIS server for .{parts[-1]}"})) + return + try: + raw = whois_query(domain, server) + except Exception as e: + print(json.dumps({"error": str(e)})) + return + + patterns = { + "registrar": r"(?:Registrar|registrar):\s*(.+)", + "creation_date": r"(?:Creation Date|Created|created):\s*(.+)", + "expiration_date": r"(?:Registry Expiry Date|Expiration Date|Expiry Date):\s*(.+)", + "updated_date": r"(?:Updated Date|Last Modified):\s*(.+)", + "name_servers": r"(?:Name Server|nserver):\s*(.+)", + "status": r"(?:Domain Status|status):\s*(.+)", + "dnssec": r"DNSSEC:\s*(.+)", + } + result = {"domain": domain, "whois_server": server} + for key, pat in patterns.items(): + matches = re.findall(pat, raw, re.IGNORECASE) + if matches: + if key in ("name_servers","status"): + result[key] = list(dict.fromkeys(m.strip().lower() for m in matches)) + else: + result[key] = matches[0].strip() + for field in ("creation_date","expiration_date","updated_date"): + if field in result: + dt = parse_iso(result[field][:19]) + if dt: + result[field] = dt.isoformat() + if field == "expiration_date": + days = (dt - datetime.now(timezone.utc)).days + result["expiration_days_remaining"] = days + result["is_expired"] = days < 0 + print(json.dumps(result, indent=2)) + +whois("DOMAIN_HERE") +``` + +--- + +## 4. DNS Records + +```python +import json, socket, urllib.request, urllib.parse + +def dns(domain, types=None): + if not types: types = ["A","AAAA","MX","NS","TXT","CNAME"] + records = {} + + for qtype in types: + if qtype == "A": + try: records["A"] = list(dict.fromkeys(i[4][0] for i in socket.getaddrinfo(domain,None,socket.AF_INET))) + except: records["A"] = [] + elif qtype == "AAAA": + try: records["AAAA"] = list(dict.fromkeys(i[4][0] for i in socket.getaddrinfo(domain,None,socket.AF_INET6))) + except: records["AAAA"] = [] + else: + url = f"https://dns.google/resolve?name={urllib.parse.quote(domain)}&type={qtype}" + try: + req = urllib.request.Request(url, headers={"User-Agent":"domain-intel-skill/1.0"}) + with urllib.request.urlopen(req, timeout=10) as r: + data = json.loads(r.read()) + records[qtype] = [a.get("data","").strip().rstrip(".") for a in data.get("Answer",[]) if a.get("data")] + except: + records[qtype] = [] + + print(json.dumps({"domain": domain, "records": records}, indent=2)) + +dns("DOMAIN_HERE") +``` + +--- + +## 5. Domain Availability Check + +```python +import json, socket, ssl + +def available(domain): + import urllib.request, urllib.parse, re + from datetime import datetime, timezone + + signals = {} + + # DNS check + try: a = [i[4][0] for i in socket.getaddrinfo(domain,None,socket.AF_INET)] + except: a = [] + try: ns_url = f"https://dns.google/resolve?name={urllib.parse.quote(domain)}&type=NS" + req = urllib.request.Request(ns_url, headers={"User-Agent":"domain-intel-skill/1.0"}) + with urllib.request.urlopen(req, timeout=10) as r: + ns = [x.get("data","") for x in json.loads(r.read()).get("Answer",[])] + except: ns = [] + signals["dns_a"] = a + signals["dns_ns"] = ns + dns_exists = bool(a or ns) + + # SSL check + ssl_up = False + try: + ctx = ssl.create_default_context() + ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE + with socket.create_connection((domain,443),timeout=3) as s: + with ctx.wrap_socket(s, server_hostname=domain): ssl_up = True + except: pass + signals["ssl_reachable"] = ssl_up + + # WHOIS check (simple) + WHOIS = {"com":"whois.verisign-grs.com","net":"whois.verisign-grs.com","org":"whois.pir.org", + "io":"whois.nic.io","co":"whois.nic.co","ai":"whois.nic.ai","dev":"whois.nic.google", + "me":"whois.nic.me","app":"whois.nic.google","tech":"whois.nic.tech"} + tld = domain.rsplit(".",1)[-1] + whois_avail = None + whois_note = "" + server = WHOIS.get(tld) + if server: + try: + with socket.create_connection((server,43),timeout=10) as s: + s.sendall((domain+"\r\n").encode()) + raw = b"" + while True: + c = s.recv(4096) + if not c: break + raw += c + raw = raw.decode("utf-8",errors="replace").lower() + if any(p in raw for p in ["no match","not found","no data found","status: free"]): + whois_avail = True; whois_note = "WHOIS: not found" + elif "registrar:" in raw or "creation date:" in raw: + whois_avail = False; whois_note = "WHOIS: registered" + else: whois_note = "WHOIS: inconclusive" + except Exception as e: whois_note = f"WHOIS error: {e}" + signals["whois_available"] = whois_avail + signals["whois_note"] = whois_note + + if not dns_exists and whois_avail is True: verdict,conf = "LIKELY AVAILABLE","high" + elif dns_exists or whois_avail is False or ssl_up: verdict,conf = "REGISTERED / IN USE","high" + elif not dns_exists and whois_avail is None: verdict,conf = "POSSIBLY AVAILABLE","medium" + else: verdict,conf = "UNCERTAIN","low" + + print(json.dumps({"domain":domain,"verdict":verdict,"confidence":conf,"signals":signals},indent=2)) + +available("DOMAIN_HERE") +``` + +--- + +## 6. Bulk Analysis (Multiple Domains in Parallel) + +```python +import json +from concurrent.futures import ThreadPoolExecutor, as_completed + +# Paste any of the functions above (check_ssl, whois, dns, available, subdomains) +# then use this runner: + +def bulk_check(domains, checks=None, max_workers=5): + if not checks: checks = ["ssl", "whois", "dns", "available"] + + def run_one(domain): + result = {"domain": domain} + # Import/define individual functions above, then: + if "ssl" in checks: + try: result["ssl"] = json.loads(check_ssl_json(domain)) + except Exception as e: result["ssl"] = {"error": str(e)} + if "whois" in checks: + try: result["whois"] = json.loads(whois_json(domain)) + except Exception as e: result["whois"] = {"error": str(e)} + if "dns" in checks: + try: result["dns"] = json.loads(dns_json(domain)) + except Exception as e: result["dns"] = {"error": str(e)} + if "available" in checks: + try: result["available"] = json.loads(available_json(domain)) + except Exception as e: result["available"] = {"error": str(e)} + return result + + results = [] + with ThreadPoolExecutor(max_workers=min(max_workers,10)) as ex: + futures = {ex.submit(run_one, d): d for d in domains[:20]} + for f in as_completed(futures): + results.append(f.result()) + + print(json.dumps({"total": len(results), "checks": checks, "results": results}, indent=2)) +``` + +--- + +## Quick Reference + +| Task | What to run | +|------|-------------| +| Find subdomains | Snippet 1 — replace `DOMAIN_HERE` | +| Check SSL cert | Snippet 2 — replace `DOMAIN_HERE` | +| WHOIS lookup | Snippet 3 — replace `DOMAIN_HERE` | +| DNS records | Snippet 4 — replace `DOMAIN_HERE` | +| Is domain available? | Snippet 5 — replace `DOMAIN_HERE` | +| Bulk check 20 domains | Snippet 6 | + +## Notes + +- All requests are **passive** — no active scanning, no packets sent to target hosts (except SSL check which makes a TCP connection) +- `subdomains` only queries crt.sh — the target domain is never contacted +- WHOIS queries go to registrar servers, not the target +- Results are structured JSON — summarize key findings for the user +- For expired cert warnings or WHOIS redaction, mention these to the user as notable findings