tracevector 2.5.0__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 (37) hide show
  1. tracevector-2.5.0/PKG-INFO +10 -0
  2. tracevector-2.5.0/README.md +33 -0
  3. tracevector-2.5.0/pyproject.toml +17 -0
  4. tracevector-2.5.0/setup.cfg +4 -0
  5. tracevector-2.5.0/setup.py +14 -0
  6. tracevector-2.5.0/tracevector.egg-info/PKG-INFO +10 -0
  7. tracevector-2.5.0/tracevector.egg-info/SOURCES.txt +35 -0
  8. tracevector-2.5.0/tracevector.egg-info/dependency_links.txt +1 -0
  9. tracevector-2.5.0/tracevector.egg-info/entry_points.txt +2 -0
  10. tracevector-2.5.0/tracevector.egg-info/requires.txt +3 -0
  11. tracevector-2.5.0/tracevector.egg-info/top_level.txt +1 -0
  12. tracevector-2.5.0/tvx/__init__.py +1 -0
  13. tracevector-2.5.0/tvx/banner.py +13 -0
  14. tracevector-2.5.0/tvx/colors.py +8 -0
  15. tracevector-2.5.0/tvx/config.py +27 -0
  16. tracevector-2.5.0/tvx/doctor.py +39 -0
  17. tracevector-2.5.0/tvx/email.py +9 -0
  18. tracevector-2.5.0/tvx/ip.py +8 -0
  19. tracevector-2.5.0/tvx/main.py +170 -0
  20. tracevector-2.5.0/tvx/phone.py +16 -0
  21. tracevector-2.5.0/tvx/plugin_loader.py +31 -0
  22. tracevector-2.5.0/tvx/plugin_security.py +8 -0
  23. tracevector-2.5.0/tvx/plugins/__init__.py +0 -0
  24. tracevector-2.5.0/tvx/plugins/email_basic.py +18 -0
  25. tracevector-2.5.0/tvx/plugins/email_osint.py +61 -0
  26. tracevector-2.5.0/tvx/plugins/ip_basic.py +20 -0
  27. tracevector-2.5.0/tvx/plugins/ip_osint.py +59 -0
  28. tracevector-2.5.0/tvx/plugins/phone_basic.py +13 -0
  29. tracevector-2.5.0/tvx/reporting.py +67 -0
  30. tracevector-2.5.0/tvx/risk.py +10 -0
  31. tracevector-2.5.0/tvx/scoring.py +38 -0
  32. tracevector-2.5.0/tvx/storage/__init__.py +0 -0
  33. tracevector-2.5.0/tvx/storage/base.py +20 -0
  34. tracevector-2.5.0/tvx/storage/manager.py +96 -0
  35. tracevector-2.5.0/tvx/storage/sqlite.py +68 -0
  36. tracevector-2.5.0/tvx/utils.py +0 -0
  37. tracevector-2.5.0/tvx/version.py +2 -0
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracevector
3
+ Version: 2.5.0
4
+ Summary: TRACEVECTOR OSINT CLI Investigator
5
+ Project-URL: Homepage, https://github.com/oedxdigitals/tracevector
6
+ Project-URL: Source, https://github.com/oedxdigitals/tracevector
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: phonenumbers
9
+ Requires-Dist: dnspython
10
+ Requires-Dist: ipwhois
@@ -0,0 +1,33 @@
1
+ # TRACEVECTOR (tvx)
2
+
3
+ TRACEVECTOR is a professional, terminal-first OSINT investigation CLI designed for investigators, analysts, and security professionals.
4
+
5
+ It runs as a **single portable binary**, supports a **plugin-based architecture**, and performs ethical open-source intelligence lookups on targets such as phone numbers, IP addresses, and email addresses.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - ๐Ÿ” OSINT investigations from the terminal
12
+ - ๐Ÿงฉ Plugin-based architecture
13
+ - ๐Ÿ“ฆ Single-file portable binary (PyInstaller)
14
+ - ๐Ÿ•ต๏ธ Phone number metadata investigation
15
+ - ๐Ÿงช Built-in self diagnostics (`tvx doctor`)
16
+ - ๐Ÿ“„ JSON output support
17
+ - โš™๏ธ Works in minimal / container / Android-like environments
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ### Option 1: Use prebuilt binary
24
+ ```bash
25
+ chmod +x tvx
26
+ ./tvx --version
27
+ ### Option 2: Use pip
28
+ '''bash
29
+ pip instal tracevector
30
+
31
+ ### v2.5.0 Help Text
32
+ TRACEVECTOR performs lawful, metadata-based OSINT only.
33
+ Private communications are never accessed.
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "tracevector"
3
+ version = "2.5.0"
4
+ description = "TRACEVECTOR OSINT CLI Investigator"
5
+ requires-python = ">=3.9"
6
+ dependencies = [
7
+ "phonenumbers",
8
+ "dnspython",
9
+ "ipwhois"
10
+ ]
11
+
12
+ [project.urls]
13
+ Homepage = "https://github.com/oedxdigitals/tracevector"
14
+ Source = "https://github.com/oedxdigitals/tracevector"
15
+
16
+ [project.scripts]
17
+ tvx = "tvx.main:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,14 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="tracevector",
5
+ version="2.5.0",
6
+ packages=find_packages(),
7
+ include_package_data=True,
8
+ install_requires=[],
9
+ entry_points={
10
+ "console_scripts": [
11
+ "tvx=tvx.main:main"
12
+ ]
13
+ },
14
+ )
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracevector
3
+ Version: 2.5.0
4
+ Summary: TRACEVECTOR OSINT CLI Investigator
5
+ Project-URL: Homepage, https://github.com/oedxdigitals/tracevector
6
+ Project-URL: Source, https://github.com/oedxdigitals/tracevector
7
+ Requires-Python: >=3.9
8
+ Requires-Dist: phonenumbers
9
+ Requires-Dist: dnspython
10
+ Requires-Dist: ipwhois
@@ -0,0 +1,35 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ tracevector.egg-info/PKG-INFO
5
+ tracevector.egg-info/SOURCES.txt
6
+ tracevector.egg-info/dependency_links.txt
7
+ tracevector.egg-info/entry_points.txt
8
+ tracevector.egg-info/requires.txt
9
+ tracevector.egg-info/top_level.txt
10
+ tvx/__init__.py
11
+ tvx/banner.py
12
+ tvx/colors.py
13
+ tvx/config.py
14
+ tvx/doctor.py
15
+ tvx/email.py
16
+ tvx/ip.py
17
+ tvx/main.py
18
+ tvx/phone.py
19
+ tvx/plugin_loader.py
20
+ tvx/plugin_security.py
21
+ tvx/reporting.py
22
+ tvx/risk.py
23
+ tvx/scoring.py
24
+ tvx/utils.py
25
+ tvx/version.py
26
+ tvx/plugins/__init__.py
27
+ tvx/plugins/email_basic.py
28
+ tvx/plugins/email_osint.py
29
+ tvx/plugins/ip_basic.py
30
+ tvx/plugins/ip_osint.py
31
+ tvx/plugins/phone_basic.py
32
+ tvx/storage/__init__.py
33
+ tvx/storage/base.py
34
+ tvx/storage/manager.py
35
+ tvx/storage/sqlite.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tvx = tvx.main:main
@@ -0,0 +1,3 @@
1
+ phonenumbers
2
+ dnspython
3
+ ipwhois
@@ -0,0 +1 @@
1
+ __version__ = "2.5.0"
@@ -0,0 +1,13 @@
1
+ def show_banner():
2
+ banner = r"""
3
+ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
4
+ โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
5
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
6
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•
7
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
8
+ โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•
9
+
10
+ TRACEVECTOR OSINT CLI
11
+ Digital Footprint Investigator
12
+ """
13
+ print(banner)
@@ -0,0 +1,8 @@
1
+ import os
2
+
3
+ COLOR_ENABLED = os.environ.get("TVX_COLOR", "1") == "1"
4
+
5
+ def c(text, code):
6
+ if not COLOR_ENABLED:
7
+ return text
8
+ return f"\033[{code}m{text}\033[0m"
@@ -0,0 +1,27 @@
1
+ import json
2
+ import os
3
+
4
+ CONFIG_PATH = os.path.expanduser("~/.tracevector_config.json")
5
+
6
+
7
+ def _load():
8
+ if not os.path.exists(CONFIG_PATH):
9
+ return {}
10
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
11
+ return json.load(f)
12
+
13
+
14
+ def _save(data: dict):
15
+ with open(CONFIG_PATH, "w", encoding="utf-8") as f:
16
+ json.dump(data, f, indent=2)
17
+
18
+
19
+ def set_key(key: str, value: str):
20
+ config = _load()
21
+ config[key] = value
22
+ _save(config)
23
+
24
+
25
+ def get_key(key: str, default=None):
26
+ config = _load()
27
+ return config.get(key, default)
@@ -0,0 +1,39 @@
1
+ import sys
2
+ import platform
3
+ from importlib import util
4
+
5
+ from tvx import __version__
6
+ from tvx import plugin_loader
7
+
8
+
9
+ def run_doctor():
10
+ print("TRACEVECTOR Doctor\n")
11
+
12
+ # Version
13
+ print(f"[+] Version: {__version__}")
14
+
15
+ # Frozen binary check
16
+ frozen = getattr(sys, "frozen", False)
17
+ print(f"[+] Frozen binary: {frozen}")
18
+
19
+ # Python executable
20
+ print(f"[+] Python: {sys.executable}")
21
+
22
+ # Platform
23
+ print(f"[+] Platform: {platform.system()} {platform.release()}")
24
+
25
+ # Plugin diagnostics
26
+ plugins = []
27
+ try:
28
+ plugins = plugin_loader.list_plugins()
29
+ except Exception as e:
30
+ print(f"[!] Plugin loader error: {e}")
31
+
32
+ print(f"[+] Plugins detected: {len(plugins)}")
33
+
34
+ for p in plugins:
35
+ name = p.get("command", "unknown")
36
+ desc = p.get("name", "unnamed plugin")
37
+ print(f" - [{name}] {desc}")
38
+
39
+ print("\n[โœ“] Environment looks healthy")
@@ -0,0 +1,9 @@
1
+ import re
2
+ class EmailPlugin:
3
+ name = "email"
4
+
5
+ def run(self, target):
6
+ return {
7
+ "target": target,
8
+ "disposable_email": False
9
+ }
@@ -0,0 +1,8 @@
1
+ import requests
2
+
3
+ def investigate_ip(ip):
4
+ url = f"http://ip-api.com/json/{ip}"
5
+ data = requests.get(url).json()
6
+
7
+ for k, v in data.items():
8
+ print(f"[+] {k}: {v}")
@@ -0,0 +1,170 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+ from datetime import datetime
5
+
6
+ from tvx.plugin_loader import load_plugins
7
+ from tvx.scoring import calculate_risk
8
+ from tvx.reporting import generate_html
9
+ from tvx.config import set_key
10
+ from tvx.storage.manager import get_storage
11
+
12
+
13
+ # =====================================
14
+ # Banner
15
+ # =====================================
16
+ def print_banner():
17
+ print(r"""
18
+ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
19
+ โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
20
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
21
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ•
22
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
23
+ โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•
24
+
25
+ TRACEVECTOR FRAUD INTELLIGENCE
26
+ """)
27
+
28
+
29
+ # =====================================
30
+ # Main
31
+ # =====================================
32
+ def main():
33
+ parser = argparse.ArgumentParser(
34
+ prog="tvx",
35
+ description="TraceVector Fraud Intelligence Toolkit"
36
+ )
37
+
38
+ parser.add_argument("command", help="Core command or plugin")
39
+ parser.add_argument("args", nargs="*", help="Command arguments")
40
+ parser.add_argument("--json", action="store_true", help="Output JSON")
41
+ parser.add_argument("--report", help="Generate HTML report")
42
+
43
+ parsed = parser.parse_args()
44
+
45
+ print_banner()
46
+
47
+ command = parsed.command
48
+ arguments = parsed.args
49
+
50
+ # =========================
51
+ # CORE: CONFIG
52
+ # =========================
53
+ if command == "config":
54
+ if not arguments or "=" not in arguments[0]:
55
+ print("Usage: tvx config key=value")
56
+ sys.exit(1)
57
+
58
+ key, value = arguments[0].split("=", 1)
59
+ set_key(key.strip(), value.strip())
60
+ print(f"[โœ“] Config saved: {key}")
61
+ sys.exit(0)
62
+
63
+ # =========================
64
+ # CORE: CASE
65
+ # =========================
66
+ if command == "case":
67
+ if not arguments:
68
+ print("Usage: tvx case <create|add|show>")
69
+ sys.exit(1)
70
+
71
+ action = arguments[0]
72
+ storage = get_storage()
73
+
74
+ # CREATE
75
+ if action == "create":
76
+ if len(arguments) < 2:
77
+ print("Usage: tvx case create CASE_ID")
78
+ sys.exit(1)
79
+
80
+ case_id = arguments[1]
81
+ storage.create_case(case_id)
82
+ print(f"[โœ“] Case created: {case_id}")
83
+ sys.exit(0)
84
+
85
+ # ADD
86
+ if action == "add":
87
+ if len(arguments) < 4:
88
+ print("Usage: tvx case add CASE_ID plugin target")
89
+ sys.exit(1)
90
+
91
+ case_id = arguments[1]
92
+ plugin_name = arguments[2]
93
+ target = arguments[3]
94
+
95
+ plugins = load_plugins()
96
+
97
+ if plugin_name not in plugins:
98
+ print("[!] Unknown plugin")
99
+ sys.exit(1)
100
+
101
+ plugin = plugins[plugin_name]
102
+ result = plugin.run(target)
103
+ risk = calculate_risk(result)
104
+
105
+ storage.add_evidence(case_id, plugin_name, target, result, risk)
106
+
107
+ print(f"[โœ“] Evidence added to case {case_id}")
108
+ print(f"Risk Level: {risk['level']} (Score: {risk['score']})")
109
+ sys.exit(0)
110
+
111
+ # SHOW
112
+ if action == "show":
113
+ if len(arguments) < 2:
114
+ print("Usage: tvx case show CASE_ID")
115
+ sys.exit(1)
116
+
117
+ case_id = arguments[1]
118
+ case_data = storage.get_case(case_id)
119
+
120
+ if not case_data["case"]:
121
+ print("[!] Case not found.")
122
+ sys.exit(1)
123
+
124
+ print(json.dumps(case_data, indent=2))
125
+ sys.exit(0)
126
+
127
+ print("Unknown case action.")
128
+ sys.exit(1)
129
+
130
+ # =========================
131
+ # PLUGIN EXECUTION
132
+ # =========================
133
+ plugins = load_plugins()
134
+
135
+ if command not in plugins:
136
+ print(f"[!] Unknown command: {command}")
137
+ print("Available plugins:", ", ".join(plugins.keys()))
138
+ sys.exit(1)
139
+
140
+ if not arguments:
141
+ print("[!] Target required.")
142
+ sys.exit(1)
143
+
144
+ target = arguments[0]
145
+ plugin = plugins[command]
146
+
147
+ try:
148
+ result = plugin.run(target)
149
+ except Exception as e:
150
+ print(f"[!] Plugin execution failed: {e}")
151
+ sys.exit(1)
152
+
153
+ risk = calculate_risk(result)
154
+
155
+ output = {
156
+ "scan": result,
157
+ "risk": risk
158
+ }
159
+
160
+ if parsed.json:
161
+ print(json.dumps(output, indent=2))
162
+ else:
163
+ print("\n=== Scan Result ===")
164
+ print(json.dumps(result, indent=2))
165
+ print("\n=== Risk Assessment ===")
166
+ print(json.dumps(risk, indent=2))
167
+
168
+ if parsed.report:
169
+ file_path = generate_html(output, risk, parsed.report)
170
+ print(f"\n[โœ“] HTML report generated: {file_path}")
@@ -0,0 +1,16 @@
1
+ try:
2
+ import phonenumbers
3
+ from phonenumbers import geocoder, carrier
4
+ except ImportError:
5
+ print("[!] Missing dependency: phonenumbers")
6
+ print("Run: pip install phonenumbers")
7
+ exit(1)
8
+
9
+ def investigate_phone(number):
10
+ try:
11
+ parsed = phonenumbers.parse(number)
12
+ print("[+] Valid Number:", phonenumbers.is_valid_number(parsed))
13
+ print("[+] Country:", geocoder.description_for_number(parsed, "en"))
14
+ print("[+] Carrier:", carrier.name_for_number(parsed, "en"))
15
+ except Exception as e:
16
+ print("[!] Error:", e)
@@ -0,0 +1,31 @@
1
+ import importlib
2
+ import inspect
3
+
4
+ PLUGIN_MODULES = [
5
+ "tvx.email",
6
+ "tvx.phone",
7
+ "tvx.ip",
8
+ ]
9
+
10
+
11
+ def load_plugins():
12
+ plugins = {}
13
+
14
+ for module_path in PLUGIN_MODULES:
15
+ try:
16
+ module = importlib.import_module(module_path)
17
+
18
+ # Look for a class with a run() method
19
+ for name, obj in inspect.getmembers(module):
20
+ if inspect.isclass(obj) and hasattr(obj, "run"):
21
+ instance = obj()
22
+
23
+ # Use class attribute if available
24
+ plugin_name = getattr(instance, "name", module_path.split(".")[-1])
25
+
26
+ plugins[plugin_name] = instance
27
+
28
+ except Exception:
29
+ continue
30
+
31
+ return plugins
@@ -0,0 +1,8 @@
1
+ import hashlib
2
+
3
+ def sha256_file(path):
4
+ h = hashlib.sha256()
5
+ with open(path, "rb") as f:
6
+ for chunk in iter(lambda: f.read(4096), b""):
7
+ h.update(chunk)
8
+ return h.hexdigest()
File without changes
@@ -0,0 +1,18 @@
1
+ COMMAND = "email"
2
+
3
+ class Plugin:
4
+ name = "Basic Email Metadata"
5
+
6
+ def run(self, args):
7
+ target = args[0] if args else None
8
+
9
+ print("[PLUGIN] Basic Email Metadata")
10
+ print(f"input: {target}")
11
+
12
+ if not target or "@" not in target:
13
+ print("error: invalid email")
14
+ return
15
+
16
+ domain = target.split("@")[-1]
17
+ print(f"domain: {domain}")
18
+ print("status: plugin_loaded_successfully")
@@ -0,0 +1,61 @@
1
+ import dns.resolver
2
+ import re
3
+ import hashlib
4
+
5
+ COMMAND = "email"
6
+
7
+ class Plugin:
8
+ name = "Email OSINT Analysis"
9
+
10
+ def run(self, args):
11
+ email = args[0]
12
+ domain = email.split("@")[-1]
13
+
14
+ result = {
15
+ "target": email,
16
+ "metadata": {},
17
+ "risk": {"score": 0, "level": "low"},
18
+ "sources": [],
19
+ "notes": []
20
+ }
21
+
22
+ # Basic validation
23
+ if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
24
+ result["notes"].append("Invalid email format")
25
+ result["risk"]["score"] += 50
26
+ return result
27
+
28
+ # MX Records
29
+ try:
30
+ mx = dns.resolver.resolve(domain, "MX")
31
+ result["metadata"]["mx_records"] = [r.exchange.to_text() for r in mx]
32
+ except Exception:
33
+ result["metadata"]["mx_records"] = []
34
+ result["risk"]["score"] += 30
35
+ result["notes"].append("No MX records")
36
+
37
+ # Provider detection
38
+ if "google.com" in str(result["metadata"].get("mx_records", "")):
39
+ result["metadata"]["provider"] = "Google"
40
+ else:
41
+ result["metadata"]["provider"] = "Unknown"
42
+
43
+ # Disposable check (basic)
44
+ disposable_domains = {"mailinator.com", "tempmail.com"}
45
+ if domain in disposable_domains:
46
+ result["risk"]["score"] += 40
47
+ result["notes"].append("Disposable email domain")
48
+
49
+ # Risk level
50
+ result["risk"]["level"] = score_level(result["risk"]["score"])
51
+ result["sources"].append("DNS")
52
+
53
+ return result
54
+
55
+
56
+ def score_level(score):
57
+ if score >= 70:
58
+ return "high"
59
+ if score >= 40:
60
+ return "medium"
61
+ return "low"
@@ -0,0 +1,20 @@
1
+ import ipaddress
2
+
3
+ COMMAND = "ip"
4
+
5
+ class Plugin:
6
+ name = "Basic IP Metadata"
7
+
8
+ def run(self, args):
9
+ target = args[0] if args else None
10
+
11
+ print("[PLUGIN] Basic IP Metadata")
12
+ print(f"input: {target}")
13
+
14
+ try:
15
+ ip = ipaddress.ip_address(target)
16
+ print(f"version: IPv{ip.version}")
17
+ print("is_private:", ip.is_private)
18
+ print("status: plugin_loaded_successfully")
19
+ except ValueError:
20
+ print("error: invalid IP address")
@@ -0,0 +1,59 @@
1
+ import ipaddress
2
+ from ipwhois import IPWhois
3
+
4
+ COMMAND = "ip"
5
+
6
+ class Plugin:
7
+ name = "IP OSINT Analysis"
8
+
9
+ def run(self, args):
10
+ ip = args[0]
11
+
12
+ result = {
13
+ "target": ip,
14
+ "metadata": {},
15
+ "risk": {"score": 0, "level": "low"},
16
+ "sources": [],
17
+ "notes": []
18
+ }
19
+
20
+ try:
21
+ ip_obj = ipaddress.ip_address(ip)
22
+ except ValueError:
23
+ result["notes"].append("Invalid IP address")
24
+ result["risk"]["score"] = 100
25
+ result["risk"]["level"] = "high"
26
+ return result
27
+
28
+ # Private IP check
29
+ if ip_obj.is_private:
30
+ result["notes"].append("Private IP address")
31
+ result["risk"]["score"] += 10
32
+
33
+ # WHOIS
34
+ try:
35
+ whois = IPWhois(ip).lookup_rdap()
36
+ result["metadata"]["asn"] = whois.get("asn")
37
+ result["metadata"]["org"] = whois.get("network", {}).get("name")
38
+ except Exception:
39
+ result["notes"].append("WHOIS lookup failed")
40
+ result["risk"]["score"] += 20
41
+
42
+ # Datacenter heuristic
43
+ org = str(result["metadata"].get("org", "")).lower()
44
+ if any(k in org for k in ["google", "amazon", "cloud", "hosting"]):
45
+ result["notes"].append("Datacenter / hosting IP")
46
+ result["risk"]["score"] += 20
47
+
48
+ result["risk"]["level"] = score_level(result["risk"]["score"])
49
+ result["sources"].append("RDAP")
50
+
51
+ return result
52
+
53
+
54
+ def score_level(score):
55
+ if score >= 70:
56
+ return "high"
57
+ if score >= 40:
58
+ return "medium"
59
+ return "low"
@@ -0,0 +1,13 @@
1
+ COMMAND = "phone"
2
+ NAME = "Basic Phone Metadata"
3
+
4
+ def run(args):
5
+ phone = args[0] if args else "N/A"
6
+
7
+ print("=" * 50)
8
+ print("[PLUGIN] Basic Phone Metadata")
9
+ print("-" * 50)
10
+ print(f"input: {phone}")
11
+ print("country: Unknown")
12
+ print("carrier: Unknown")
13
+ print("risk_score: 20/100")
@@ -0,0 +1,67 @@
1
+ import os
2
+ from datetime import datetime
3
+
4
+
5
+ def generate_html(scan_data: dict, risk_data: dict, filename: str) -> str:
6
+ """
7
+ Generate simple HTML fraud investigation report.
8
+ """
9
+
10
+ if not filename.endswith(".html"):
11
+ filename += ".html"
12
+
13
+ html_content = f"""
14
+ <html>
15
+ <head>
16
+ <title>TraceVector Fraud Report</title>
17
+ <style>
18
+ body {{
19
+ font-family: Arial, sans-serif;
20
+ margin: 40px;
21
+ background-color: #f4f4f4;
22
+ }}
23
+ h1 {{
24
+ color: #222;
25
+ }}
26
+ .box {{
27
+ background: white;
28
+ padding: 20px;
29
+ margin-bottom: 20px;
30
+ border-radius: 8px;
31
+ box-shadow: 0 0 10px rgba(0,0,0,0.05);
32
+ }}
33
+ .high {{ color: red; }}
34
+ .medium {{ color: orange; }}
35
+ .low {{ color: green; }}
36
+ </style>
37
+ </head>
38
+ <body>
39
+ <h1>TraceVector Fraud Investigation Report</h1>
40
+ <p><strong>Generated:</strong> {datetime.utcnow().isoformat()} UTC</p>
41
+
42
+ <div class="box">
43
+ <h2>Scan Result</h2>
44
+ <pre>{scan_data}</pre>
45
+ </div>
46
+
47
+ <div class="box">
48
+ <h2>Risk Assessment</h2>
49
+ <p><strong>Score:</strong> {risk_data.get("score")}</p>
50
+ <p><strong>Level:</strong>
51
+ <span class="{risk_data.get("level", "").lower()}">
52
+ {risk_data.get("level")}
53
+ </span>
54
+ </p>
55
+ <p><strong>Flags:</strong></p>
56
+ <ul>
57
+ {''.join(f'<li>{flag}</li>' for flag in risk_data.get("flags", []))}
58
+ </ul>
59
+ </div>
60
+ </body>
61
+ </html>
62
+ """
63
+
64
+ with open(filename, "w", encoding="utf-8") as f:
65
+ f.write(html_content)
66
+
67
+ return os.path.abspath(filename)
@@ -0,0 +1,10 @@
1
+ def normalize(score):
2
+ return min(100, max(0, score))
3
+
4
+ def level(score):
5
+ score = normalize(score)
6
+ if score >= 70:
7
+ return "high"
8
+ if score >= 40:
9
+ return "medium"
10
+ return "low"
@@ -0,0 +1,38 @@
1
+ def calculate_risk(result: dict) -> dict:
2
+ """
3
+ Simple fraud risk scoring engine.
4
+ """
5
+
6
+ score = 0
7
+ flags = []
8
+
9
+ # Example signals
10
+ if result.get("disposable_email"):
11
+ score += 40
12
+ flags.append("Disposable email provider")
13
+
14
+ if result.get("tor_exit"):
15
+ score += 50
16
+ flags.append("Tor exit node detected")
17
+
18
+ if result.get("vpn"):
19
+ score += 30
20
+ flags.append("VPN usage suspected")
21
+
22
+ if result.get("blacklisted"):
23
+ score += 60
24
+ flags.append("Found in blacklist")
25
+
26
+ # Risk level
27
+ if score >= 80:
28
+ level = "HIGH"
29
+ elif score >= 40:
30
+ level = "MEDIUM"
31
+ else:
32
+ level = "LOW"
33
+
34
+ return {
35
+ "score": score,
36
+ "level": level,
37
+ "flags": flags
38
+ }
File without changes
@@ -0,0 +1,20 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseStorage(ABC):
5
+
6
+ @abstractmethod
7
+ def init(self):
8
+ pass
9
+
10
+ @abstractmethod
11
+ def create_case(self, case_id: str):
12
+ pass
13
+
14
+ @abstractmethod
15
+ def add_scan(self, case_id: str, scan_data: dict):
16
+ pass
17
+
18
+ @abstractmethod
19
+ def get_case(self, case_id: str):
20
+ pass
@@ -0,0 +1,96 @@
1
+ import sqlite3
2
+ import json
3
+ import os
4
+ from datetime import datetime
5
+
6
+ DB_PATH = os.path.expanduser("~/.tracevector_cases.db")
7
+
8
+
9
+ def _connect():
10
+ conn = sqlite3.connect(DB_PATH)
11
+ conn.row_factory = sqlite3.Row
12
+ return conn
13
+
14
+
15
+ def _init():
16
+ conn = _connect()
17
+ cur = conn.cursor()
18
+
19
+ cur.execute("""
20
+ CREATE TABLE IF NOT EXISTS cases (
21
+ id TEXT PRIMARY KEY,
22
+ created_at TEXT
23
+ )
24
+ """)
25
+
26
+ cur.execute("""
27
+ CREATE TABLE IF NOT EXISTS evidence (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ case_id TEXT,
30
+ plugin TEXT,
31
+ target TEXT,
32
+ result TEXT,
33
+ risk TEXT,
34
+ created_at TEXT
35
+ )
36
+ """)
37
+
38
+ conn.commit()
39
+ conn.close()
40
+
41
+
42
+ class Storage:
43
+
44
+ def __init__(self):
45
+ _init()
46
+
47
+ def create_case(self, case_id):
48
+ conn = _connect()
49
+ cur = conn.cursor()
50
+ cur.execute(
51
+ "INSERT INTO cases (id, created_at) VALUES (?, ?)",
52
+ (case_id, datetime.utcnow().isoformat())
53
+ )
54
+ conn.commit()
55
+ conn.close()
56
+
57
+ def add_evidence(self, case_id, plugin, target, result, risk):
58
+ conn = _connect()
59
+ cur = conn.cursor()
60
+ cur.execute(
61
+ """
62
+ INSERT INTO evidence (case_id, plugin, target, result, risk, created_at)
63
+ VALUES (?, ?, ?, ?, ?, ?)
64
+ """,
65
+ (
66
+ case_id,
67
+ plugin,
68
+ target,
69
+ json.dumps(result),
70
+ json.dumps(risk),
71
+ datetime.utcnow().isoformat()
72
+ )
73
+ )
74
+ conn.commit()
75
+ conn.close()
76
+
77
+ def get_case(self, case_id):
78
+ conn = _connect()
79
+ cur = conn.cursor()
80
+
81
+ cur.execute("SELECT * FROM cases WHERE id=?", (case_id,))
82
+ case = cur.fetchone()
83
+
84
+ cur.execute("SELECT * FROM evidence WHERE case_id=?", (case_id,))
85
+ evidence = cur.fetchall()
86
+
87
+ conn.close()
88
+
89
+ return {
90
+ "case": dict(case) if case else None,
91
+ "evidence": [dict(e) for e in evidence]
92
+ }
93
+
94
+
95
+ def get_storage():
96
+ return Storage()
@@ -0,0 +1,68 @@
1
+ import sqlite3
2
+ import json
3
+ from datetime import datetime
4
+
5
+ from tvx.storage.base import BaseStorage
6
+
7
+
8
+ class SQLiteStorage(BaseStorage):
9
+
10
+ def __init__(self, db_path="tracevector.db"):
11
+ self.db_path = db_path
12
+ self.conn = sqlite3.connect(self.db_path)
13
+ self.conn.row_factory = sqlite3.Row
14
+
15
+ def init(self):
16
+ cursor = self.conn.cursor()
17
+
18
+ cursor.execute("""
19
+ CREATE TABLE IF NOT EXISTS cases (
20
+ id TEXT PRIMARY KEY,
21
+ created_at TEXT
22
+ )
23
+ """)
24
+
25
+ cursor.execute("""
26
+ CREATE TABLE IF NOT EXISTS scans (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ case_id TEXT,
29
+ timestamp TEXT,
30
+ scan_json TEXT,
31
+ FOREIGN KEY(case_id) REFERENCES cases(id)
32
+ )
33
+ """)
34
+
35
+ self.conn.commit()
36
+
37
+ def create_case(self, case_id: str):
38
+ cursor = self.conn.cursor()
39
+ cursor.execute(
40
+ "INSERT INTO cases (id, created_at) VALUES (?, ?)",
41
+ (case_id, datetime.utcnow().isoformat())
42
+ )
43
+ self.conn.commit()
44
+
45
+ def add_scan(self, case_id: str, scan_data: dict):
46
+ cursor = self.conn.cursor()
47
+ cursor.execute(
48
+ "INSERT INTO scans (case_id, timestamp, scan_json) VALUES (?, ?, ?)",
49
+ (
50
+ case_id,
51
+ datetime.utcnow().isoformat(),
52
+ json.dumps(scan_data)
53
+ )
54
+ )
55
+ self.conn.commit()
56
+
57
+ def get_case(self, case_id: str):
58
+ cursor = self.conn.cursor()
59
+ cursor.execute("SELECT * FROM cases WHERE id = ?", (case_id,))
60
+ case = cursor.fetchone()
61
+
62
+ cursor.execute("SELECT * FROM scans WHERE case_id = ?", (case_id,))
63
+ scans = cursor.fetchall()
64
+
65
+ return {
66
+ "case": dict(case) if case else None,
67
+ "scans": [dict(s) for s in scans]
68
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ VERSION = "1.0.0"
2
+ BUILD = "stable"