osintengine 1.0.2__py3-none-any.whl

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 (103) hide show
  1. osintengine-1.0.2.dist-info/METADATA +311 -0
  2. osintengine-1.0.2.dist-info/RECORD +103 -0
  3. osintengine-1.0.2.dist-info/WHEEL +5 -0
  4. osintengine-1.0.2.dist-info/entry_points.txt +2 -0
  5. osintengine-1.0.2.dist-info/licenses/LICENSE +202 -0
  6. osintengine-1.0.2.dist-info/top_level.txt +2 -0
  7. src/watson/__init__.py +10 -0
  8. src/watson/agent/__init__.py +96 -0
  9. src/watson/agents/__init__.py +101 -0
  10. src/watson/agents/protocol.py +196 -0
  11. src/watson/auth/__init__.py +68 -0
  12. src/watson/auth/store.py +145 -0
  13. src/watson/conversation.py +68 -0
  14. src/watson/core/__init__.py +0 -0
  15. src/watson/core/models.py +96 -0
  16. src/watson/ethics.py +205 -0
  17. src/watson/exports.py +182 -0
  18. src/watson/graph/__init__.py +69 -0
  19. src/watson/graph/entities.py +207 -0
  20. src/watson/graph/graph.py +489 -0
  21. src/watson/graph/osint_framework.py +266 -0
  22. src/watson/graph/relationships.py +116 -0
  23. src/watson/graph/transforms.py +787 -0
  24. src/watson/infra/__init__.py +43 -0
  25. src/watson/infra/cache.py +171 -0
  26. src/watson/infra/ratelimit.py +125 -0
  27. src/watson/infra/resilience.py +239 -0
  28. src/watson/infra/retry.py +186 -0
  29. src/watson/metrics.py +128 -0
  30. src/watson/opsec/__init__.py +290 -0
  31. src/watson/orchestration/__init__.py +23 -0
  32. src/watson/orchestration/engine.py +5587 -0
  33. src/watson/orchestration/executor.py +96 -0
  34. src/watson/orchestration/intent.py +139 -0
  35. src/watson/orchestration/intent_classifier.py +45 -0
  36. src/watson/orchestration/llm_config.py +160 -0
  37. src/watson/orchestration/resolution.py +555 -0
  38. src/watson/orchestration/scraper.py +94 -0
  39. src/watson/orchestration/synthesis.py +726 -0
  40. src/watson/orchestration/target_profile.py +748 -0
  41. src/watson/persistence/__init__.py +18 -0
  42. src/watson/persistence/models.py +161 -0
  43. src/watson/persistence/store.py +230 -0
  44. src/watson/pipeline/__init__.py +16 -0
  45. src/watson/pipeline/pre_synthesis.py +621 -0
  46. src/watson/search.py +103 -0
  47. src/watson/serializers/stix.py +451 -0
  48. src/watson/tools/__init__.py +31 -0
  49. src/watson/tools/base.py +97 -0
  50. src/watson/tools/blockchain.py +359 -0
  51. src/watson/tools/captcha.py +276 -0
  52. src/watson/tools/conflict.py +107 -0
  53. src/watson/tools/corporate.py +287 -0
  54. src/watson/tools/darkweb.py +144 -0
  55. src/watson/tools/geolocation.py +347 -0
  56. src/watson/tools/image_video.py +108 -0
  57. src/watson/tools/marinetraffic.py +142 -0
  58. src/watson/tools/people.py +476 -0
  59. src/watson/tools/registry.py +56 -0
  60. src/watson/tools/satellite.py +118 -0
  61. src/watson/tools/scraper.py +745 -0
  62. src/watson/tools/shodan.py +129 -0
  63. src/watson/tools/social_media.py +160 -0
  64. src/watson/tools/websites.py +291 -0
  65. src/watson/tools/wikidata.py +398 -0
  66. src/watson/utils/__init__.py +1 -0
  67. src/watson/utils/helpers.py +49 -0
  68. src/watson/utils/http.py +119 -0
  69. src/watson/verification/__init__.py +245 -0
  70. watson/__init__.py +6 -0
  71. watson/agents/__init__.py +20 -0
  72. watson/agents/base.py +103 -0
  73. watson/agents/direct.py +225 -0
  74. watson/agents/hermes.py +275 -0
  75. watson/agents/hermes_mcp.py +292 -0
  76. watson/api_keys.py +176 -0
  77. watson/browser_scraper.py +481 -0
  78. watson/cli.py +836 -0
  79. watson/crossref.py +175 -0
  80. watson/ethics.py +20 -0
  81. watson/graph.py +406 -0
  82. watson/mcp_server.py +601 -0
  83. watson/memory.py +552 -0
  84. watson/neo4j_graph.py +124 -0
  85. watson/opsec/__init__.py +307 -0
  86. watson/reporter.py +537 -0
  87. watson/scheduler.py +486 -0
  88. watson/serializers/stix.py +451 -0
  89. watson/terminal.py +410 -0
  90. watson/toolkit.py +252 -0
  91. watson/toolkit_api.py +347 -0
  92. watson/toolkit_automation.py +95 -0
  93. watson/toolkit_registry.py +345 -0
  94. watson/verification/__init__.py +251 -0
  95. watson/web/__init__.py +1 -0
  96. watson/web/app.py +1650 -0
  97. watson/web/middleware.py +301 -0
  98. watson/web/static/assets/index-B7hPOc0z.js +278 -0
  99. watson/web/static/assets/index-CJ6FP8Mp.css +1 -0
  100. watson/web/static/assets/watson-logo-Dk9tawHb.png +0 -0
  101. watson/web/static/index.html +14 -0
  102. watson/web/templates/chat.html +2 -0
  103. watson/web/templates/investigation-map.html +429 -0
@@ -0,0 +1,207 @@
1
+ """Typed OSINT entities for the Watson entity graph.
2
+
3
+ Each entity type corresponds to a real-world OSINT artifact that can be
4
+ discovered, enriched, and pivoted from.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ from dataclasses import dataclass, field
11
+ from datetime import datetime, timezone
12
+ from enum import Enum
13
+ from typing import Any, ClassVar, Optional
14
+
15
+
16
+ class EntityType(str, Enum):
17
+ """Core entity types — maps to Maltego's entity palette."""
18
+ DOMAIN = "domain"
19
+ IP_ADDRESS = "ip_address"
20
+ EMAIL = "email"
21
+ PERSON = "person"
22
+ ORGANIZATION = "organization"
23
+ WEBSITE = "website"
24
+ LOCATION = "location"
25
+ DOCUMENT = "document"
26
+ UNKNOWN = "unknown"
27
+
28
+
29
+ @dataclass
30
+ class Entity:
31
+ """Base entity — all entities have a unique ID derived from their value + type."""
32
+
33
+ entity_type: EntityType = EntityType.UNKNOWN
34
+ value: str = "" # Canonical string representation (lowercased, normalized)
35
+ display_name: str = "" # Human-readable label
36
+ confidence: float = field(default=0.8, metadata={"ge": 0.0, "le": 1.0})
37
+ source: str = "" # Transform name or tool that discovered this
38
+ discovered_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
39
+ properties: dict[str, Any] = field(default_factory=dict)
40
+
41
+ # Subclasses override this to set their entity_type
42
+ _entity_type: ClassVar[EntityType] = EntityType.UNKNOWN
43
+
44
+ def __post_init__(self):
45
+ if not self.display_name:
46
+ self.display_name = self.value
47
+ # Set entity_type from class variable if not explicitly passed
48
+ if self.entity_type == EntityType.UNKNOWN and self._entity_type != EntityType.UNKNOWN:
49
+ self.entity_type = self._entity_type
50
+
51
+ @property
52
+ def id(self) -> str:
53
+ """Deterministic entity ID — same value+type always produces same ID."""
54
+ raw = f"{self.entity_type.value}:{self.value}"
55
+ return hashlib.sha256(raw.encode()).hexdigest()[:16]
56
+
57
+ @property
58
+ def label(self) -> str:
59
+ """Short label for graph display."""
60
+ return f"{self.entity_type.value}:{self.display_name[:40]}"
61
+
62
+ def __hash__(self) -> int:
63
+ return hash(self.id)
64
+
65
+ def __eq__(self, other: object) -> bool:
66
+ if not isinstance(other, Entity):
67
+ return False
68
+ return self.id == other.id
69
+
70
+ def to_dict(self) -> dict:
71
+ return {
72
+ "id": self.id,
73
+ "entity_type": self.entity_type.value,
74
+ "value": self.value,
75
+ "display_name": self.display_name,
76
+ "confidence": self.confidence,
77
+ "source": self.source,
78
+ "properties": self.properties,
79
+ }
80
+
81
+ def __repr__(self) -> str:
82
+ return f"{self.__class__.__name__}(value={self.value!r}, conf={self.confidence:.2f})"
83
+
84
+
85
+ # ── Concrete entity types ─────────────────────────────────────────
86
+
87
+
88
+ @dataclass(eq=False)
89
+ class Domain(Entity):
90
+ """A DNS domain name."""
91
+ _entity_type: ClassVar[EntityType] = EntityType.DOMAIN
92
+ tld: str = ""
93
+ registrar: str = ""
94
+
95
+ def __post_init__(self):
96
+ super().__post_init__()
97
+ self.value = self.value.lower().rstrip(".")
98
+ if not self.tld and "." in self.value:
99
+ self.tld = self.value.rsplit(".", 1)[-1]
100
+
101
+
102
+ @dataclass(eq=False)
103
+ class IPAddress(Entity):
104
+ """An IPv4 or IPv6 address."""
105
+ _entity_type: ClassVar[EntityType] = EntityType.IP_ADDRESS
106
+ is_ipv6: bool = False
107
+ asn: str = ""
108
+ isp: str = ""
109
+
110
+ def __post_init__(self):
111
+ super().__post_init__()
112
+ self.is_ipv6 = ":" in self.value
113
+
114
+
115
+ @dataclass(eq=False)
116
+ class Email(Entity):
117
+ """An email address."""
118
+ _entity_type: ClassVar[EntityType] = EntityType.EMAIL
119
+ local_part: str = ""
120
+ domain: str = ""
121
+
122
+ def __post_init__(self):
123
+ super().__post_init__()
124
+ self.value = self.value.lower()
125
+ if "@" in self.value and not self.local_part:
126
+ self.local_part, self.domain = self.value.split("@", 1)
127
+
128
+
129
+ @dataclass(eq=False)
130
+ class Person(Entity):
131
+ """A person (name or identity)."""
132
+ _entity_type: ClassVar[EntityType] = EntityType.PERSON
133
+ given_name: str = ""
134
+ surname: str = ""
135
+
136
+ def __post_init__(self):
137
+ super().__post_init__()
138
+ if not self.given_name and not self.surname:
139
+ parts = self.value.split()
140
+ if len(parts) >= 2:
141
+ self.given_name = parts[-1] # Western order assumption
142
+ self.surname = parts[0]
143
+ elif len(parts) == 1:
144
+ self.surname = parts[0]
145
+
146
+
147
+ @dataclass(eq=False)
148
+ class Organization(Entity):
149
+ """A company, NGO, government body, etc."""
150
+ _entity_type: ClassVar[EntityType] = EntityType.ORGANIZATION
151
+ industry: str = ""
152
+ country: str = ""
153
+
154
+
155
+ @dataclass(eq=False)
156
+ class Website(Entity):
157
+ """A specific web page or site URL."""
158
+ _entity_type: ClassVar[EntityType] = EntityType.WEBSITE
159
+ url: str = ""
160
+
161
+ def __post_init__(self):
162
+ super().__post_init__()
163
+ if not self.url:
164
+ self.url = self.value
165
+ self.value = self.url.lower().rstrip("/")
166
+
167
+
168
+ @dataclass(eq=False)
169
+ class Location(Entity):
170
+ """A physical location (city, country, coordinates)."""
171
+ _entity_type: ClassVar[EntityType] = EntityType.LOCATION
172
+ latitude: float = 0.0
173
+ longitude: float = 0.0
174
+ country_code: str = ""
175
+
176
+
177
+ @dataclass(eq=False)
178
+ class Document(Entity):
179
+ """A document, file, or extracted text snippet."""
180
+ _entity_type: ClassVar[EntityType] = EntityType.DOCUMENT
181
+ snippet: str = ""
182
+ source_url: str = ""
183
+
184
+
185
+ # ── Entity factory ────────────────────────────────────────────────
186
+
187
+
188
+ def make_entity(
189
+ entity_type: EntityType,
190
+ value: str,
191
+ **kwargs,
192
+ ) -> Entity:
193
+ """Create an entity of the appropriate type."""
194
+ _type_map: dict[EntityType, type[Entity]] = {
195
+ EntityType.DOMAIN: Domain,
196
+ EntityType.IP_ADDRESS: IPAddress,
197
+ EntityType.EMAIL: Email,
198
+ EntityType.PERSON: Person,
199
+ EntityType.ORGANIZATION: Organization,
200
+ EntityType.WEBSITE: Website,
201
+ EntityType.LOCATION: Location,
202
+ EntityType.DOCUMENT: Document,
203
+ }
204
+ cls = _type_map.get(entity_type, Entity)
205
+ if cls is Entity:
206
+ kwargs["entity_type"] = entity_type
207
+ return cls(value=value, **kwargs)