Lucene search
+L

Identity Exposure Graph

🗓️ 02 May 2026 21:27:51Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 3 Views

Identity Exposure Graph tool links identity artifacts to score exploitability.

Refs
Code
from kittysploit import *
import json
import re


class Module(Auxiliary):
    __info__ = {
        "name": "Identity Exposure Graph",
        "author": ["KittySploit Team"],
        "description": "Link human and machine identity artifacts and score real exploitability.",
        "tags": ["osint", "identity", "graph", "correlation"],
    }

    query = OptString("", "Identity seed (email, username, or full name)", required=True)
    identity_file = OptString("", "JSON output from identity_handle_hunter", required=False)
    breach_file = OptString("", "JSON output from breach_exposure_score", required=False)
    js_file = OptString("", "JSON output from js_endpoint_extractor", required=False)
    domain_surface_file = OptString("", "JSON output from domain_surface_mapper", required=False)
    bucket_file = OptString("", "JSON output from public_bucket_hunter", required=False)
    output_file = OptString("", "Optional JSON output file", required=False)

    def _load_json(self, path):
        if not path:
            return {}
        try:
            with open(str(path), "r") as fp:
                data = json.load(fp)
                return data if isinstance(data, dict) else {}
        except Exception:
            return {}

    def _extract_seed_parts(self, query):
        q = str(query).strip().lower()
        parts = {"raw": q, "emails": [], "handles": [], "domains": []}
        if "@" in q:
            parts["emails"].append(q)
            local, dom = q.split("@", 1)
            if local:
                parts["handles"].append(local)
            if "." in dom:
                parts["domains"].append(dom)
        else:
            cleaned = re.sub(r"[^a-z0-9._\- ]", " ", q)
            atoms = [x for x in cleaned.split() if x]
            if atoms:
                parts["handles"].extend(atoms[:5])
            for a in atoms:
                if "." in a and len(a) > 3:
                    parts["domains"].append(a)
        return parts

    def _build_entities(self, seed, identity, breach, js, surface, bucket):
        entities = {
            "person": [],
            "handle": [],
            "email": [],
            "domain": [],
            "repo_or_profile": [],
            "bucket": [],
            "asn_or_ip": [],
            "signal": [],
        }
        links = []

        root = seed["raw"] or "identity"
        entities["person"].append(root)
        for h in seed["handles"]:
            entities["handle"].append(h)
            links.append((root, h, "handle"))
        for e in seed["emails"]:
            entities["email"].append(e)
            links.append((root, e, "email"))
        for d in seed["domains"]:
            entities["domain"].append(d)
            links.append((root, d, "domain"))

        for f in identity.get("findings", [])[:50]:
            profile = f.get("url")
            handle = f.get("handle")
            if handle:
                entities["handle"].append(handle)
                links.append((root, handle, "observed"))
            if profile:
                entities["repo_or_profile"].append(profile)
                links.append((handle or root, profile, f.get("platform", "profile")))

        for f in bucket.get("findings", [])[:30]:
            b = f"{f.get('provider')}:{f.get('bucket')}"
            entities["bucket"].append(b)
            dom = bucket.get("target") or (seed["domains"][0] if seed["domains"] else root)
            links.append((dom, b, "bucket"))

        for d in (surface.get("subdomains", [])[:40] or []):
            entities["domain"].append(d)
            links.append((surface.get("target", root), d, "subdomain"))
        for ip in (surface.get("dns", {}).get("A", [])[:20] or []):
            entities["asn_or_ip"].append(ip)
            links.append((surface.get("target", root), ip, "A"))

        for r in breach.get("reasons", [])[:10]:
            entities["signal"].append(r)
            links.append((root, r, "risk_signal"))

        jsf = js.get("findings", {}) or {}
        for dom in (jsf.get("external_domains", [])[:30] or []):
            entities["domain"].append(dom)
            links.append((js.get("target", root), dom, "external"))
        for k in (jsf.get("key_hints", [])[:15] or []):
            sig = f"key_hint:{k.get('name', 'secret')}"
            entities["signal"].append(sig)
            links.append((js.get("target", root), sig, "secret_hint"))

        # Deduplicate while preserving order.
        for k in list(entities.keys()):
            seen = set()
            uniq = []
            for v in entities[k]:
                s = str(v).strip()
                if not s or s in seen:
                    continue
                seen.add(s)
                uniq.append(s)
            entities[k] = uniq
        return entities, links

    def _score_exploitability(self, entities, breach):
        score = 0
        reasons = []
        c_handles = len(entities.get("handle", []))
        c_profiles = len(entities.get("repo_or_profile", []))
        c_domains = len(entities.get("domain", []))
        c_buckets = len(entities.get("bucket", []))
        c_signals = len(entities.get("signal", []))

        if c_handles >= 5:
            score += 2
            reasons.append("large_handle_footprint")
        elif c_handles >= 2:
            score += 1
            reasons.append("multiple_handles")

        if c_profiles >= 3:
            score += 2
            reasons.append("multiple_public_profiles")

        if c_domains >= 8:
            score += 2
            reasons.append("wide_domain_graph")

        if c_buckets > 0:
            score += 2
            reasons.append("cloud_storage_exposure")

        if c_signals > 0:
            score += 2
            reasons.append("secret_or_breach_signals")

        score += min(2, int(breach.get("risk_score", 0) / 4))
        level = "LOW" if score <= 3 else ("MEDIUM" if score <= 6 else "HIGH")
        return score, level, reasons

    def run(self):
        query = str(self.query).strip()
        if not query:
            print_error("query is required")
            return {"error": "query is required"}

        identity = self._load_json(self.identity_file)
        breach = self._load_json(self.breach_file)
        js = self._load_json(self.js_file)
        surface = self._load_json(self.domain_surface_file)
        bucket = self._load_json(self.bucket_file)

        seed = self._extract_seed_parts(query)
        entities, links = self._build_entities(seed, identity, breach, js, surface, bucket)
        score, level, reasons = self._score_exploitability(entities, breach)

        result = {
            "query": query,
            "entities": entities,
            "links": [{"from": a, "to": b, "type": t} for (a, b, t) in links[:400]],
            "entity_counts": {k: len(v) for k, v in entities.items()},
            "exploitability_score": score,
            "exploitability_level": level,
            "reasons": reasons,
        }

        print_success(
            f"Identity graph ready: entities={sum(result['entity_counts'].values())} "
            f"links={len(result['links'])} exploitability={level}({score})"
        )
        if reasons:
            print_info(f"Signals: {', '.join(reasons)}")

        if self.output_file:
            try:
                with open(str(self.output_file), "w") as fp:
                    json.dump(result, fp, indent=2)
                print_success(f"Results saved to {self.output_file}")
            except Exception as e:
                print_error(f"Failed to save output: {e}")
        return result

    def get_graph_nodes(self, data):
        if not isinstance(data, dict) or "error" in data:
            return [], []

        nodes = []
        edges = []
        entities = data.get("entities", {})
        summary_id = "idexp_summary"
        nodes.append({
            "id": summary_id,
            "label": f"Identity exposure ({data.get('exploitability_level', 'LOW')}:{data.get('exploitability_score', 0)})",
            "group": "risk",
            "icon": "📌",
            "custom_info": (
                f"Exploitability: {data.get('exploitability_level', 'LOW')} ({data.get('exploitability_score', 0)})\n"
                f"Reasons: {', '.join(data.get('reasons', []) or ['none'])}\n"
                f"Entity counts: {json.dumps(data.get('entity_counts', {}), ensure_ascii=True)}"
            ),
        })
        group_icon = {
            "person": ("generic", "🧑"),
            "handle": ("hostname", "👤"),
            "email": ("hostname", "📧"),
            "domain": ("domain", "🌐"),
            "repo_or_profile": ("generic", "🧪"),
            "bucket": ("hostname", "🪣"),
            "asn_or_ip": ("ip", "🖥️"),
            "signal": ("risk", "🚨"),
        }
        node_ids = {}
        idx = 0
        for k, vals in entities.items():
            group, icon = group_icon.get(k, ("generic", "•"))
            for v in vals[:50]:
                nid = f"idexp_{idx}"
                idx += 1
                node_ids[v] = nid
                nodes.append({
                    "id": nid,
                    "label": str(v)[:80],
                    "group": group,
                    "icon": icon,
                    "custom_info": f"Entity type: {k}\nValue: {str(v)}",
                })
                if k in ("person", "handle", "email", "signal"):
                    edges.append({
                        "from": summary_id,
                        "to": nid,
                        "label": "entity",
                        "custom_info": f"Category: {k}",
                    })

        for l in data.get("links", [])[:300]:
            a = l.get("from")
            b = l.get("to")
            if a in node_ids and b in node_ids:
                relation = l.get("type", "link")
                edges.append({
                    "from": node_ids[a],
                    "to": node_ids[b],
                    "label": relation,
                    "custom_info": f"Relation: {relation}\nFrom: {a}\nTo: {b}",
                })
        return nodes, edges

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 May 2026 21:27Current
5.8Medium risk
Vulners AI Score5.8
3