detecti-cli 2.0.11__tar.gz → 2.0.12__tar.gz

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 (81) hide show
  1. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/PKG-INFO +1 -1
  2. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/cli.py +0 -12
  3. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/config.py +8 -0
  4. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/engine.py +23 -1
  5. detecti_cli-2.0.12/detecti/modules/securitytrails.py +125 -0
  6. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/utils/http.py +4 -1
  7. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/pyproject.toml +1 -2
  8. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/.env.example +0 -0
  9. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/.github/workflows/publish.yml +0 -0
  10. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/.gitignore +0 -0
  11. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/README.md +0 -0
  12. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/__init__.py +0 -0
  13. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/__main__.py +0 -0
  14. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/__init__.py +0 -0
  15. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/banner +0 -0
  16. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/__init__.py +0 -0
  17. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/database/__init__.py +0 -0
  18. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/database/config_db.py +0 -0
  19. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/database/schema.py +0 -0
  20. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/database/storage.py +0 -0
  21. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/core/models.py +0 -0
  22. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/data/config.sqlite +0 -0
  23. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/data/dbs/.gitkeep +0 -0
  24. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/data/dbs/example.com.sqlite +0 -0
  25. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/__init__.py +0 -0
  26. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/base.py +0 -0
  27. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/censys.py +0 -0
  28. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/crtsh.py +0 -0
  29. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/exploitdb.py +0 -0
  30. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/masscan.py +0 -0
  31. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/nuclei.py +0 -0
  32. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/nvd.py +0 -0
  33. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/reverse_whois.py +0 -0
  34. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/modules/shodan.py +0 -0
  35. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/reporters/__init__.py +0 -0
  36. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/reporters/csv_reporter.py +0 -0
  37. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/reporters/html_reporter.py +0 -0
  38. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/reporters/json_reporter.py +0 -0
  39. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/reporters/markdown_reporter.py +0 -0
  40. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/utils/__init__.py +0 -0
  41. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/utils/logger.py +0 -0
  42. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/utils/setup.py +0 -0
  43. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/utils/updater.py +0 -0
  44. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/__init__.py +0 -0
  45. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/api/__init__.py +0 -0
  46. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/api/auth.py +0 -0
  47. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/api/graph_builder.py +0 -0
  48. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/api/routes.py +0 -0
  49. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/process_manager.py +0 -0
  50. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/server.py +0 -0
  51. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/android-chrome-192x192.png +0 -0
  52. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/android-chrome-512x512.png +0 -0
  53. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/apple-touch-icon.png +0 -0
  54. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/css/__init__.py +0 -0
  55. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/css/dashboard.css +0 -0
  56. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/favicon-16x16.png +0 -0
  57. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/favicon-32x32.png +0 -0
  58. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/favicon.ico +0 -0
  59. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/img/DetecTI_Security_Logo.png +0 -0
  60. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/img/detecti-ico.png +0 -0
  61. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/index.html +0 -0
  62. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/__init__.py +0 -0
  63. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/api.js +0 -0
  64. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/cytoscape-cose-bilkent.js +0 -0
  65. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/cytoscape-dagre.js +0 -0
  66. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/cytoscape.min.js +0 -0
  67. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/dagre.min.js +0 -0
  68. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/graph.js +0 -0
  69. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/js/lucide.min.js +0 -0
  70. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/login.html +0 -0
  71. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/detecti/web/static/site.webmanifest +0 -0
  72. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/requirements.txt +0 -0
  73. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/__init__.py +0 -0
  74. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/conftest.py +0 -0
  75. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_engine_cli.py +0 -0
  76. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_http.py +0 -0
  77. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_masscan.py +0 -0
  78. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_models.py +0 -0
  79. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_modules.py +0 -0
  80. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_nuclei.py +0 -0
  81. {detecti_cli-2.0.11 → detecti_cli-2.0.12}/tests/test_reporters.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: detecti-cli
3
- Version: 2.0.11
3
+ Version: 2.0.12
4
4
  Summary: Modern Attack Surface Management & Threat Intelligence Engine
5
5
  Author-email: "Lucas S. (Ls4ss)" <lucas@lucassouza.io>
6
6
  License: MIT
@@ -419,18 +419,6 @@ def config_check_command(
419
419
 
420
420
 
421
421
 
422
- print_section_header("API Credentials Status")
423
- engine = ThreatTrackEngine()
424
- api_statuses = asyncio.run(engine.verify_environment_apis())
425
- for mod_key, info in api_statuses.items():
426
- name = info.get("name", mod_key.title())
427
- status = info.get("status", "Unknown")
428
- status_styled = (
429
- f"[bold green]{status}[/bold green]"
430
- if info.get("valid")
431
- else (f"[bold red]{status}[/bold red]" if info.get("configured") else f"[dim]{status}[/dim]")
432
- )
433
- console.print(f" • [cyan]{name}:[/cyan] {status_styled}")
434
422
 
435
423
 
436
424
  @hound_app.command("start")
@@ -99,6 +99,11 @@ class Settings(BaseSettings):
99
99
  validation_alias="WHOISFREAKS_API_KEY",
100
100
  description="WhoisFreaks API key for reverse WHOIS",
101
101
  )
102
+ securitytrails_api_key: Optional[str] = Field(
103
+ default=None,
104
+ validation_alias="SECURITYTRAILS_API_KEY",
105
+ description="SecurityTrails API key",
106
+ )
102
107
  github_token: Optional[str] = Field(
103
108
  default=None,
104
109
  validation_alias="GITHUB_TOKEN",
@@ -166,6 +171,9 @@ class Settings(BaseSettings):
166
171
  raw_whois = self.whoisfreaks_api_key or os.getenv("WHOISFREAKS_API_KEY")
167
172
  self.whoisfreaks_api_key = sanitize_api_key(raw_whois)
168
173
 
174
+ raw_sectrails = self.securitytrails_api_key or os.getenv("SECURITYTRAILS_API_KEY")
175
+ self.securitytrails_api_key = sanitize_api_key(raw_sectrails)
176
+
169
177
  raw_github = self.github_token or os.getenv("GITHUB_TOKEN")
170
178
  self.github_token = sanitize_api_key(raw_github)
171
179
 
@@ -30,6 +30,7 @@ from detecti.modules.crtsh import CrtshModule
30
30
  from detecti.modules.exploitdb import ExploitDBModule
31
31
  from detecti.modules.nvd import NVDModule
32
32
  from detecti.modules.reverse_whois import ReverseWhoisModule
33
+ from detecti.modules.securitytrails import SecurityTrailsModule
33
34
  from detecti.modules.shodan import ShodanModule
34
35
  from detecti.utils.http import AsyncHTTPClient, http_client
35
36
 
@@ -60,6 +61,7 @@ class ThreatTrackEngine:
60
61
  "censys": CensysModule,
61
62
  "crtsh": CrtshModule,
62
63
  "reverse_whois": ReverseWhoisModule,
64
+ "securitytrails": SecurityTrailsModule,
63
65
  "nvd": NVDModule,
64
66
  "exploitdb": ExploitDBModule,
65
67
  }
@@ -293,7 +295,27 @@ class ThreatTrackEngine:
293
295
  "tier": "Free OSINT",
294
296
  }
295
297
 
296
- # 5. ExploitDB / GitHub Token
298
+ # 5. SecurityTrails
299
+ if "securitytrails" in active_mod_names:
300
+ st_mod = self.modules["securitytrails"]
301
+ if st_mod.is_configured():
302
+ status_report["securitytrails"] = {
303
+ "name": "SecurityTrails",
304
+ "configured": True,
305
+ "valid": True,
306
+ "status": "Active (API Key)",
307
+ "tier": "Standard API",
308
+ }
309
+ else:
310
+ status_report["securitytrails"] = {
311
+ "name": "SecurityTrails",
312
+ "configured": False,
313
+ "valid": False,
314
+ "status": "Not Configured (Bypassed)",
315
+ "tier": "None",
316
+ }
317
+
318
+ # 6. ExploitDB / GitHub Token
297
319
  if "exploitdb" in active_mod_names:
298
320
  from config import is_placeholder_key
299
321
  has_gh = bool(settings.github_token and not is_placeholder_key(settings.github_token))
@@ -0,0 +1,125 @@
1
+ """SecurityTrails passive collection module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import ipaddress
7
+ import logging
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from detecti.config import settings
11
+ from detecti.core.models import Finding, FindingType, HostInfoData
12
+ from detecti.modules.base import BaseModule
13
+
14
+ logger = logging.getLogger("detecti.securitytrails")
15
+
16
+
17
+ class SecurityTrailsModule(BaseModule):
18
+ """SecurityTrails module for subdomains and reverse IP."""
19
+
20
+ name: str = "securitytrails"
21
+ description: str = "SecurityTrails API for DNS and Reverse IP"
22
+ category: str = "recon"
23
+
24
+ def is_configured(self) -> bool:
25
+ """Check if valid SecurityTrails API key is set."""
26
+ from detecti.config import is_placeholder_key
27
+ return bool(settings.securitytrails_api_key and not is_placeholder_key(settings.securitytrails_api_key))
28
+
29
+ async def run(
30
+ self,
31
+ target: str,
32
+ context: Optional[Dict[str, Any]] = None,
33
+ ) -> List[Finding]:
34
+ """Execute SecurityTrails queries based on target input type."""
35
+ if not self.is_configured():
36
+ logger.warning("SecurityTrails API key is not configured. Skipping module.")
37
+ return []
38
+
39
+ target = target.strip()
40
+ findings: List[Finding] = []
41
+ headers = {
42
+ "APIKEY": settings.securitytrails_api_key,
43
+ "Accept": "application/json"
44
+ }
45
+
46
+ # Check if target is an IP or a domain
47
+ try:
48
+ ip_obj = ipaddress.ip_address(target)
49
+ is_ip = True
50
+ except ValueError:
51
+ is_ip = False
52
+
53
+ if is_ip:
54
+ self.notify(f"Querying SecurityTrails Reverse IP for {target}...")
55
+ # Query reverse DNS using DSL or IP endpoint
56
+ url = "https://api.securitytrails.com/v1/dns/search"
57
+ payload = {
58
+ "filter": {
59
+ "ipv4": target
60
+ }
61
+ }
62
+ try:
63
+ resp = await self.http_client.post(
64
+ url=url,
65
+ headers=headers,
66
+ json=payload,
67
+ timeout=10.0,
68
+ max_retries=2,
69
+ raise_for_status=True,
70
+ )
71
+ data = resp.json()
72
+ records = data.get("records", [])
73
+ self.notify(f"Found {len(records)} domains resolving to {target} via SecurityTrails.")
74
+ for rec in records:
75
+ hostname = rec.get("hostname")
76
+ if hostname:
77
+ findings.append(
78
+ Finding(
79
+ type=FindingType.HOST_INFO,
80
+ target=target,
81
+ source="SecurityTrails",
82
+ data=HostInfoData(
83
+ ip=target,
84
+ associated_fqdns=[hostname],
85
+ ),
86
+ severity="info",
87
+ description=f"SecurityTrails mapped {hostname} to IP {target}"
88
+ )
89
+ )
90
+ except Exception as e:
91
+ logger.error(f"SecurityTrails IP search error for {target}: {e}")
92
+ self.notify(f"Error querying SecurityTrails for IP: {e}")
93
+ else:
94
+ self.notify(f"Querying SecurityTrails subdomains for {target}...")
95
+ url = f"https://api.securitytrails.com/v1/domain/{target}/subdomains"
96
+ try:
97
+ resp = await self.http_client.get(
98
+ url=url,
99
+ headers=headers,
100
+ timeout=10.0,
101
+ max_retries=2,
102
+ raise_for_status=True,
103
+ )
104
+ data = resp.json()
105
+ subdomains = data.get("subdomains", [])
106
+ self.notify(f"Found {len(subdomains)} subdomains for {target} via SecurityTrails.")
107
+ for sub in subdomains:
108
+ fqdn = f"{sub}.{target}"
109
+ findings.append(
110
+ Finding(
111
+ type=FindingType.HOST_INFO,
112
+ target=target,
113
+ source="SecurityTrails",
114
+ data=HostInfoData(
115
+ associated_fqdns=[fqdn]
116
+ ),
117
+ severity="info",
118
+ description=f"SecurityTrails discovered subdomain {fqdn}"
119
+ )
120
+ )
121
+ except Exception as e:
122
+ logger.error(f"SecurityTrails domain search error for {target}: {e}")
123
+ self.notify(f"Error querying SecurityTrails for domain: {e}")
124
+
125
+ return findings
@@ -38,12 +38,15 @@ class AsyncHTTPClient:
38
38
 
39
39
  self._headers = default_headers
40
40
  self._client: Optional[httpx.AsyncClient] = None
41
- self._semaphore = asyncio.Semaphore(self.concurrency_limit)
41
+ self._semaphore: Optional[asyncio.Semaphore] = None
42
42
  self._domain_locks: Dict[str, asyncio.Lock] = {}
43
43
  self._domain_last_request: Dict[str, float] = {}
44
44
 
45
45
  async def get_client(self) -> httpx.AsyncClient:
46
46
  """Get or initialize the underlying httpx.AsyncClient."""
47
+ if self._semaphore is None:
48
+ self._semaphore = asyncio.Semaphore(self.concurrency_limit)
49
+
47
50
  if self._client is None or self._client.is_closed:
48
51
  self._client = httpx.AsyncClient(
49
52
  timeout=httpx.Timeout(self.timeout, connect=10.0),
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "detecti-cli"
7
- version = "2.0.11"
7
+ version = "2.0.12"
8
8
  description = "Modern Attack Surface Management & Threat Intelligence Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -69,7 +69,6 @@ dev = [
69
69
  ]
70
70
 
71
71
  [project.scripts]
72
- detecti = "detecti.cli:app"
73
72
  detecti-cli = "detecti.cli:app"
74
73
 
75
74
  [tool.pytest.ini_options]
File without changes
File without changes
File without changes
File without changes
File without changes