Skip to content

Instantly share code, notes, and snippets.

@shakquraa
Created February 4, 2026 20:28
Show Gist options
  • Select an option

  • Save shakquraa/7b86b0245971e0315836c8c9fdf8b971 to your computer and use it in GitHub Desktop.

Select an option

Save shakquraa/7b86b0245971e0315836c8c9fdf8b971 to your computer and use it in GitHub Desktop.
Microsoft tenant–based domain discovery for bug bounty recon.
#!/usr/bin/env python3
import argparse
import sys
import time
import random
import os
import requests
UA = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/139.0.0.0 Safari/537.36"
)
HEADERS = {"User-Agent": UA}
def log(msg, silent):
if not silent:
print(msg, file=sys.stderr)
def fetch_tenant_id(domain):
url = f"https://login.microsoftonline.com/{domain}/.well-known/openid-configuration"
try:
r = requests.get(url, headers=HEADERS, timeout=10)
if r.status_code != 200:
return None
data = r.json()
token_endpoint = data.get("token_endpoint")
if not token_endpoint:
return None
# https://login.microsoftonline.com/{tenant_id}/oauth2/...
return token_endpoint.split("/")[3]
except Exception:
return None
def query_domains(tenant_id):
# random delay to avoid hammering
time.sleep(random.uniform(1, 10))
url = "https://tenant-api.micahvandeusen.com/search"
try:
r = requests.get(url, params={"tenant_id": tenant_id}, headers=HEADERS, timeout=15)
if r.status_code != 200:
return []
data = r.json()
return data.get("domains", [])
except Exception:
return []
def process_domain(domain, silent):
log(f"[*] Processing domain: {domain}", silent)
tenant_id = fetch_tenant_id(domain)
if not tenant_id:
return []
return query_domains(tenant_id)
def read_stdin_domains():
return [line.strip() for line in sys.stdin if line.strip()]
def write_output(results, path):
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w") as f:
f.write("\n".join(results))
def main():
parser = argparse.ArgumentParser(
description="Discover Microsoft tenant-linked domains"
)
parser.add_argument(
"-d", "--domain",
help="Single domain to query (example.com)"
)
parser.add_argument(
"-o", "--output",
help="Write results to file"
)
parser.add_argument(
"-s", "--silent",
action="store_true",
help="Suppress status output"
)
args = parser.parse_args()
domains = []
if args.domain:
domains.append(args.domain)
if not sys.stdin.isatty():
domains.extend(read_stdin_domains())
if not domains:
parser.print_help()
sys.exit(0)
found = []
for d in domains:
found.extend(process_domain(d, args.silent))
if not found:
log("[-] No domains discovered.", args.silent)
sys.exit(1)
unique = sorted(set(found))
if args.output:
write_output(unique, args.output)
log(f"[+] {len(unique)} unique domains saved to {args.output}", args.silent)
else:
print("\n".join(unique))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment