osintkit 0.1.3 → 0.1.5
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.
- package/LICENSE +0 -0
- package/README.md +28 -24
- package/bin/osintkit.js +32 -20
- package/osintkit/__init__.py +1 -1
- package/osintkit/__pycache__/__init__.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/cli.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/config.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/profiles.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/risk.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/scanner.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/setup.cpython-311.pyc +0 -0
- package/osintkit/cli.py +59 -38
- package/osintkit/config.py +13 -0
- package/osintkit/modules/__init__.py +10 -0
- package/osintkit/modules/__pycache__/__init__.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/breach.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/brokers.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/certs.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/dark_web.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/gravatar.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/harvester.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/hibp.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/hibp_kanon.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/holehe.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/libphonenumber_info.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/paste.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/phone.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/sherlock.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/social.cpython-311.pyc +0 -0
- package/osintkit/modules/__pycache__/wayback.cpython-311.pyc +0 -0
- package/osintkit/modules/gravatar.py +0 -0
- package/osintkit/modules/libphonenumber_info.py +0 -0
- package/osintkit/modules/sherlock.py +0 -0
- package/osintkit/modules/stage2/__init__.py +0 -0
- package/osintkit/modules/stage2/__pycache__/__init__.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/__pycache__/github_api.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/__pycache__/hunter.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/__pycache__/leakcheck.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/__pycache__/numverify.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/__pycache__/securitytrails.cpython-311.pyc +0 -0
- package/osintkit/modules/stage2/github_api.py +12 -9
- package/osintkit/modules/stage2/hunter.py +7 -5
- package/osintkit/modules/stage2/leakcheck.py +7 -5
- package/osintkit/modules/stage2/numverify.py +7 -5
- package/osintkit/modules/stage2/securitytrails.py +7 -5
- package/osintkit/output/__pycache__/__init__.cpython-311.pyc +0 -0
- package/osintkit/output/__pycache__/html_writer.cpython-311.pyc +0 -0
- package/osintkit/output/__pycache__/json_writer.cpython-311.pyc +0 -0
- package/osintkit/output/__pycache__/md_writer.cpython-311.pyc +0 -0
- package/osintkit/output/md_writer.py +0 -0
- package/osintkit/output/templates/report.html +313 -50
- package/osintkit/scanner.py +25 -2
- package/osintkit/setup.py +21 -10
- package/package.json +10 -2
- package/postinstall.js +69 -50
- package/pyproject.toml +1 -1
- package/osintkit/__pycache__/__main__.cpython-311.pyc +0 -0
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@ osintkit open # View profile details + open latest report
|
|
|
33
33
|
| `osintkit refresh [id]` | Re-run scan for a profile |
|
|
34
34
|
| `osintkit open [id]` | Show profile details and open latest report |
|
|
35
35
|
| `osintkit export [id]` | Export as JSON or Markdown |
|
|
36
|
-
| `osintkit setup` | Configure API keys |
|
|
36
|
+
| `osintkit setup` | Configure API keys interactively (preserves existing keys) |
|
|
37
|
+
| `osintkit config set-key <key> <value>` | Update a single API key without touching others |
|
|
38
|
+
| `osintkit config show` | Show which API keys are set (values hidden) |
|
|
37
39
|
| `osintkit delete [id]` | Delete a profile |
|
|
38
40
|
| `osintkit version` | Show version |
|
|
39
41
|
|
|
@@ -56,19 +58,19 @@ osintkit open # View profile details + open latest report
|
|
|
56
58
|
| Data brokers | name/email | Google CSE broker scan |
|
|
57
59
|
| Dark web | email | Ahmia / public index |
|
|
58
60
|
| Breach lookup | email | BreachDirectory |
|
|
61
|
+
| GitHub | username | Public profile (always runs, no key needed) |
|
|
59
62
|
|
|
60
|
-
### Stage 2 — Optional
|
|
63
|
+
### Stage 2 — Optional API keys, unlocks extra data sources
|
|
61
64
|
|
|
62
|
-
| Service | Input |
|
|
63
|
-
|
|
64
|
-
| HaveIBeenPwned | email |
|
|
65
|
+
| Service | Input | Tier |
|
|
66
|
+
|---------|-------|------|
|
|
67
|
+
| HaveIBeenPwned | email | Paid ($3.50/month) |
|
|
65
68
|
| LeakCheck | email/phone/user | Free tier |
|
|
66
|
-
| NumVerify | phone | 100/month |
|
|
67
|
-
| Hunter.io |
|
|
68
|
-
|
|
|
69
|
-
| SecurityTrails | domain | Free tier |
|
|
69
|
+
| NumVerify | phone | 100/month free |
|
|
70
|
+
| Hunter.io | email | 25/month free |
|
|
71
|
+
| SecurityTrails | domain | Paid |
|
|
70
72
|
|
|
71
|
-
Stage 2
|
|
73
|
+
Stage 2 modules only run when a key is configured. If rate-limited, the scan continues gracefully with Stage 1 results — rate-limited modules are shown as yellow, not red.
|
|
72
74
|
|
|
73
75
|
## Output
|
|
74
76
|
|
|
@@ -81,23 +83,25 @@ Risk score is 0–100 based on breach exposure, social footprint, data broker li
|
|
|
81
83
|
|
|
82
84
|
## API Keys (All Optional)
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
Keys are stored in `~/.osintkit/config.yaml` (permissions: 600).
|
|
85
87
|
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
+
```bash
|
|
89
|
+
osintkit config set-key hunter YOUR_KEY # add or update one key
|
|
90
|
+
osintkit config show # see which keys are set
|
|
91
|
+
osintkit setup # interactive wizard (preserves existing keys)
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
| Service |
|
|
91
|
-
|
|
92
|
-
| HaveIBeenPwned | haveibeenpwned.com/API/Key |
|
|
93
|
-
| LeakCheck | leakcheck.io |
|
|
94
|
-
| NumVerify | numverify.com |
|
|
95
|
-
| Hunter.io | hunter.io |
|
|
96
|
-
| GitHub | github.com/settings/tokens |
|
|
97
|
-
| Intelbase | intelbase.is |
|
|
98
|
-
| BreachDirectory | rapidapi.com |
|
|
99
|
-
| Google CSE | developers.google.com/custom-search |
|
|
100
|
-
| SecurityTrails | securitytrails.com |
|
|
94
|
+
| Service | Where to get it | Free? |
|
|
95
|
+
|---------|----------------|-------|
|
|
96
|
+
| HaveIBeenPwned | haveibeenpwned.com/API/Key | Paid ($3.50/mo) |
|
|
97
|
+
| LeakCheck | leakcheck.io | Free tier |
|
|
98
|
+
| NumVerify | numverify.com | 100 req/month free |
|
|
99
|
+
| Hunter.io | hunter.io | 25 req/month free |
|
|
100
|
+
| GitHub | github.com/settings/tokens | Free (raises rate limit) |
|
|
101
|
+
| Intelbase | intelbase.is | 100 req/month free |
|
|
102
|
+
| BreachDirectory | rapidapi.com (search "BreachDirectory") | 50 req/day free |
|
|
103
|
+
| Google CSE | developers.google.com/custom-search | 100 req/day free |
|
|
104
|
+
| SecurityTrails | securitytrails.com | Paid |
|
|
101
105
|
|
|
102
106
|
## Run from Source
|
|
103
107
|
|
package/bin/osintkit.js
CHANGED
|
@@ -1,42 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { spawn } = require('child_process');
|
|
2
|
+
const { spawn, spawnSync } = require('child_process');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
|
|
6
|
-
// The npm package root is one level up from bin/
|
|
7
6
|
const packageDir = path.dirname(path.dirname(__filename));
|
|
7
|
+
const isWin = process.platform === 'win32';
|
|
8
|
+
|
|
9
|
+
const venvPython = isWin
|
|
10
|
+
? path.join(packageDir, '.venv', 'Scripts', 'python.exe')
|
|
11
|
+
: path.join(packageDir, '.venv', 'bin', 'python3');
|
|
12
|
+
|
|
13
|
+
// If venv doesn't exist yet, run postinstall first (self-healing first run)
|
|
14
|
+
if (!fs.existsSync(venvPython)) {
|
|
15
|
+
const postinstall = path.join(packageDir, 'postinstall.js');
|
|
16
|
+
if (fs.existsSync(postinstall)) {
|
|
17
|
+
console.log('osintkit: first run — setting up Python environment...');
|
|
18
|
+
const r = spawnSync(process.execPath, [postinstall], {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
cwd: packageDir,
|
|
21
|
+
});
|
|
22
|
+
if (!fs.existsSync(venvPython)) {
|
|
23
|
+
console.error('\nosintkit: setup failed. Run manually:');
|
|
24
|
+
console.error(` node ${postinstall}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
8
29
|
|
|
9
|
-
// Prefer venv Python (installed by postinstall), fall back to system Python.
|
|
10
|
-
// Checks both Unix (.venv/bin/) and Windows (.venv/Scripts/) paths.
|
|
11
30
|
function findPython() {
|
|
31
|
+
// Prefer venv Python (Unix + Windows)
|
|
12
32
|
const venvCandidates = [
|
|
13
|
-
path.join(packageDir, '.venv', 'bin', 'python3'),
|
|
14
|
-
path.join(packageDir, '.venv', 'bin', 'python'),
|
|
15
|
-
path.join(packageDir, '.venv', 'Scripts', 'python.exe'),
|
|
16
|
-
path.join(packageDir, 'venv', 'bin', 'python3'),
|
|
17
|
-
path.join(packageDir, 'venv', '
|
|
18
|
-
path.join(packageDir, 'venv', 'Scripts', 'python.exe'), // Windows alt name
|
|
33
|
+
path.join(packageDir, '.venv', 'bin', 'python3'),
|
|
34
|
+
path.join(packageDir, '.venv', 'bin', 'python'),
|
|
35
|
+
path.join(packageDir, '.venv', 'Scripts', 'python.exe'),
|
|
36
|
+
path.join(packageDir, 'venv', 'bin', 'python3'),
|
|
37
|
+
path.join(packageDir, 'venv', 'Scripts', 'python.exe'),
|
|
19
38
|
];
|
|
20
39
|
for (const p of venvCandidates) {
|
|
21
40
|
if (fs.existsSync(p)) return p;
|
|
22
41
|
}
|
|
23
|
-
|
|
24
42
|
// Fall back to system Python
|
|
25
|
-
const systemCandidates =
|
|
26
|
-
? ['python', 'python3']
|
|
27
|
-
: ['python3.11', 'python3', 'python'];
|
|
43
|
+
const systemCandidates = isWin ? ['python', 'python3'] : ['python3.11', 'python3', 'python'];
|
|
28
44
|
for (const bin of systemCandidates) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return bin;
|
|
32
|
-
} catch (_) {}
|
|
45
|
+
const r = spawnSync(bin, ['--version'], { stdio: 'pipe' });
|
|
46
|
+
if (r.status === 0) return bin;
|
|
33
47
|
}
|
|
34
48
|
return 'python3';
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
const pythonBin = findPython();
|
|
38
|
-
|
|
39
|
-
// Always set PYTHONPATH so the package is importable regardless of install method
|
|
40
52
|
const env = {
|
|
41
53
|
...process.env,
|
|
42
54
|
PYTHONPATH: process.env.PYTHONPATH
|
package/osintkit/__init__.py
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/osintkit/cli.py
CHANGED
|
@@ -17,8 +17,9 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
|
17
17
|
|
|
18
18
|
from osintkit import __version__
|
|
19
19
|
from osintkit.scanner import Scanner
|
|
20
|
-
from osintkit.config import load_config, Config, APIKeys
|
|
20
|
+
from osintkit.config import load_config, save_config, Config, APIKeys
|
|
21
21
|
from osintkit.profiles import Profile, ProfileStore, ScanHistory
|
|
22
|
+
from osintkit.setup import update_api_key
|
|
22
23
|
|
|
23
24
|
app = typer.Typer(help="OSINT CLI for personal digital footprint analysis", invoke_without_command=True)
|
|
24
25
|
console = Console()
|
|
@@ -291,20 +292,15 @@ def run_scan_for_profile(profile: Profile) -> dict:
|
|
|
291
292
|
|
|
292
293
|
@app.command()
|
|
293
294
|
def setup():
|
|
294
|
-
"""Configure API keys."""
|
|
295
|
+
"""Configure API keys. Existing keys are preserved unless a new value is entered."""
|
|
295
296
|
config_path = Path.home() / ".osintkit" / "config.yaml"
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
console.print(f"\n[yellow]Config already exists: {config_path}[/yellow]")
|
|
299
|
-
if not Confirm.ask("Overwrite?"):
|
|
300
|
-
return
|
|
301
|
-
|
|
297
|
+
existing = load_config(config_path)
|
|
298
|
+
|
|
302
299
|
console.print("\n[bold cyan]═══ API Key Setup ═══[/bold cyan]\n")
|
|
303
|
-
console.print("Enter
|
|
304
|
-
|
|
305
|
-
keys = {}
|
|
300
|
+
console.print("Press [bold]Enter[/bold] to keep an existing key. Type a new value to update it.\n")
|
|
301
|
+
|
|
306
302
|
api_key_list = [
|
|
307
|
-
("hibp", "HaveIBeenPwned
|
|
303
|
+
("hibp", "HaveIBeenPwned", "https://haveibeenpwned.com/API/Key"),
|
|
308
304
|
("breachdirectory", "BreachDirectory", "https://rapidapi.com/"),
|
|
309
305
|
("leakcheck", "LeakCheck", "https://leakcheck.io/"),
|
|
310
306
|
("intelbase", "Intelbase", "https://intelbase.is/"),
|
|
@@ -316,37 +312,62 @@ def setup():
|
|
|
316
312
|
("github", "GitHub Personal Access Token", "https://github.com/settings/tokens"),
|
|
317
313
|
("securitytrails", "SecurityTrails", "https://securitytrails.com"),
|
|
318
314
|
]
|
|
319
|
-
|
|
315
|
+
|
|
316
|
+
keys_dict = existing.api_keys.model_dump()
|
|
317
|
+
|
|
320
318
|
for key_name, label, url in api_key_list:
|
|
319
|
+
current = keys_dict.get(key_name, "")
|
|
320
|
+
status = "[green][set][/green]" if current else "[dim][not set][/dim]"
|
|
321
321
|
hint = f" ({url})" if url else ""
|
|
322
|
-
value = Prompt.ask(f"{label}{hint}", default="")
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
output_dir
|
|
328
|
-
timeout_seconds
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
breachdirectory: "{keys.get('breachdirectory', '')}"
|
|
333
|
-
leakcheck: "{keys.get('leakcheck', '')}"
|
|
334
|
-
intelbase: "{keys.get('intelbase', '')}"
|
|
335
|
-
google_cse_key: "{keys.get('google_cse_key', '')}"
|
|
336
|
-
google_cse_cx: "{keys.get('google_cse_cx', '')}"
|
|
337
|
-
numverify: "{keys.get('numverify', '')}"
|
|
338
|
-
emailrep: "{keys.get('emailrep', '')}"
|
|
339
|
-
resend: ""
|
|
340
|
-
hunter: "{keys.get('hunter', '')}"
|
|
341
|
-
github: "{keys.get('github', '')}"
|
|
342
|
-
securitytrails: "{keys.get('securitytrails', '')}"
|
|
343
|
-
epieos: ""
|
|
344
|
-
"""
|
|
345
|
-
config_path.write_text(config_content)
|
|
346
|
-
config_path.chmod(0o600) # API keys must not be world-readable
|
|
322
|
+
value = Prompt.ask(f" {status} {label}{hint}", default="")
|
|
323
|
+
if value.strip():
|
|
324
|
+
keys_dict[key_name] = value.strip()
|
|
325
|
+
|
|
326
|
+
updated = Config(
|
|
327
|
+
output_dir=existing.output_dir,
|
|
328
|
+
timeout_seconds=existing.timeout_seconds,
|
|
329
|
+
api_keys=APIKeys(**keys_dict),
|
|
330
|
+
)
|
|
331
|
+
save_config(updated, config_path)
|
|
347
332
|
console.print(f"\n[green]✓[/green] Config saved: {config_path}")
|
|
348
333
|
|
|
349
334
|
|
|
335
|
+
config_app = typer.Typer(help="Manage osintkit configuration.")
|
|
336
|
+
app.add_typer(config_app, name="config")
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@config_app.command("set-key")
|
|
340
|
+
def config_set_key(
|
|
341
|
+
key: str = typer.Argument(..., help="API key name (e.g. github, hunter)"),
|
|
342
|
+
value: str = typer.Argument(..., help="The API key value"),
|
|
343
|
+
):
|
|
344
|
+
"""Update a single API key without touching others."""
|
|
345
|
+
valid_keys = set(APIKeys.model_fields.keys())
|
|
346
|
+
if key not in valid_keys:
|
|
347
|
+
console.print(f"[red]Unknown key '{key}'.[/red]")
|
|
348
|
+
console.print(f"Valid keys: {', '.join(sorted(valid_keys))}")
|
|
349
|
+
raise typer.Exit(1)
|
|
350
|
+
update_api_key(key, value)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@config_app.command("show")
|
|
354
|
+
def config_show():
|
|
355
|
+
"""Show which API keys are set (values are not shown)."""
|
|
356
|
+
config_path = Path.home() / ".osintkit" / "config.yaml"
|
|
357
|
+
cfg = load_config(config_path)
|
|
358
|
+
keys_dict = cfg.api_keys.model_dump()
|
|
359
|
+
|
|
360
|
+
table = Table(title="API Keys", show_header=True)
|
|
361
|
+
table.add_column("Key", style="cyan")
|
|
362
|
+
table.add_column("Status")
|
|
363
|
+
|
|
364
|
+
for key_name in sorted(keys_dict.keys()):
|
|
365
|
+
status = "[green]set[/green]" if keys_dict[key_name] else "[dim]not set[/dim]"
|
|
366
|
+
table.add_row(key_name, status)
|
|
367
|
+
|
|
368
|
+
console.print(table)
|
|
369
|
+
|
|
370
|
+
|
|
350
371
|
@app.command()
|
|
351
372
|
def new():
|
|
352
373
|
"""Create a new person profile."""
|
package/osintkit/config.py
CHANGED
|
@@ -32,6 +32,19 @@ class Config(BaseModel):
|
|
|
32
32
|
api_keys: APIKeys = Field(default_factory=APIKeys)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def save_config(config: Config, config_path: Path) -> None:
|
|
36
|
+
"""Save configuration to YAML file."""
|
|
37
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
data = {
|
|
39
|
+
"output_dir": config.output_dir,
|
|
40
|
+
"timeout_seconds": config.timeout_seconds,
|
|
41
|
+
"api_keys": config.api_keys.model_dump(),
|
|
42
|
+
}
|
|
43
|
+
with open(config_path, "w") as f:
|
|
44
|
+
yaml.dump(data, f, default_flow_style=False)
|
|
45
|
+
config_path.chmod(0o600)
|
|
46
|
+
|
|
47
|
+
|
|
35
48
|
def load_config(config_path: Path) -> Config:
|
|
36
49
|
"""Load configuration from YAML file.
|
|
37
50
|
|
|
@@ -3,4 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
class ModuleError(Exception):
|
|
5
5
|
"""Base exception for module failures."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RateLimitError(ModuleError):
|
|
10
|
+
"""Raised when an API rate limit (429) is hit."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InvalidKeyError(ModuleError):
|
|
15
|
+
"""Raised when an API key is invalid or unauthorized (401/403)."""
|
|
6
16
|
pass
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,6 +4,8 @@ from typing import Dict, List
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
|
+
from osintkit.modules import RateLimitError, InvalidKeyError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
9
11
|
"""Look up a GitHub user profile via the GitHub REST API.
|
|
@@ -23,19 +25,20 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
23
25
|
return []
|
|
24
26
|
|
|
25
27
|
try:
|
|
28
|
+
headers = {"Accept": "application/vnd.github.v3+json"}
|
|
29
|
+
if api_key:
|
|
30
|
+
headers["Authorization"] = f"token {api_key}"
|
|
31
|
+
|
|
26
32
|
async with httpx.AsyncClient(timeout=httpx.Timeout(10.0, connect=5.0)) as client:
|
|
27
33
|
response = await client.get(
|
|
28
34
|
f"https://api.github.com/users/{username}",
|
|
29
|
-
headers=
|
|
30
|
-
"Authorization": f"token {api_key}",
|
|
31
|
-
"Accept": "application/vnd.github.v3+json",
|
|
32
|
-
},
|
|
35
|
+
headers=headers,
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
if response.status_code == 429:
|
|
36
|
-
raise
|
|
39
|
+
raise RateLimitError("GitHub API rate limit reached")
|
|
37
40
|
if response.status_code in (401, 403):
|
|
38
|
-
raise
|
|
41
|
+
raise InvalidKeyError("GitHub token invalid or unauthorized")
|
|
39
42
|
if response.status_code == 404:
|
|
40
43
|
return []
|
|
41
44
|
|
|
@@ -59,7 +62,7 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
59
62
|
"url": data.get("html_url"),
|
|
60
63
|
}]
|
|
61
64
|
|
|
62
|
-
except
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
except (RateLimitError, InvalidKeyError):
|
|
66
|
+
raise
|
|
67
|
+
except Exception:
|
|
65
68
|
return []
|
|
@@ -4,6 +4,8 @@ from typing import Dict, List
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
|
+
from osintkit.modules import RateLimitError, InvalidKeyError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
9
11
|
"""Verify an email address via Hunter.io email-verifier endpoint.
|
|
@@ -30,9 +32,9 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
if response.status_code == 429:
|
|
33
|
-
raise
|
|
35
|
+
raise RateLimitError("Hunter.io rate limit reached")
|
|
34
36
|
if response.status_code in (401, 403):
|
|
35
|
-
raise
|
|
37
|
+
raise InvalidKeyError("Hunter.io API key invalid or unauthorized")
|
|
36
38
|
|
|
37
39
|
data = response.json()
|
|
38
40
|
result = data.get("data", {})
|
|
@@ -58,7 +60,7 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
58
60
|
"url": None,
|
|
59
61
|
}]
|
|
60
62
|
|
|
61
|
-
except
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
except (RateLimitError, InvalidKeyError):
|
|
64
|
+
raise
|
|
65
|
+
except Exception:
|
|
64
66
|
return []
|
|
@@ -4,6 +4,8 @@ from typing import Dict, List
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
|
+
from osintkit.modules import RateLimitError, InvalidKeyError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
9
11
|
"""Query LeakCheck.io for email breach records.
|
|
@@ -30,9 +32,9 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
if response.status_code == 429:
|
|
33
|
-
raise
|
|
35
|
+
raise RateLimitError("LeakCheck rate limit reached")
|
|
34
36
|
if response.status_code in (401, 403):
|
|
35
|
-
raise
|
|
37
|
+
raise InvalidKeyError("LeakCheck API key invalid or unauthorized")
|
|
36
38
|
|
|
37
39
|
data = response.json()
|
|
38
40
|
if not data.get("success") or not data.get("result"):
|
|
@@ -52,7 +54,7 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
52
54
|
})
|
|
53
55
|
return findings
|
|
54
56
|
|
|
55
|
-
except
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
except (RateLimitError, InvalidKeyError):
|
|
58
|
+
raise
|
|
59
|
+
except Exception:
|
|
58
60
|
return []
|
|
@@ -4,6 +4,8 @@ from typing import Dict, List
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
|
+
from osintkit.modules import RateLimitError, InvalidKeyError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
9
11
|
"""Validate a phone number via the NumVerify/apilayer API.
|
|
@@ -30,9 +32,9 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
if response.status_code == 429:
|
|
33
|
-
raise
|
|
35
|
+
raise RateLimitError("NumVerify rate limit reached")
|
|
34
36
|
if response.status_code in (401, 403):
|
|
35
|
-
raise
|
|
37
|
+
raise InvalidKeyError("NumVerify API key invalid or unauthorized")
|
|
36
38
|
|
|
37
39
|
data = response.json()
|
|
38
40
|
if not data.get("valid") and data.get("error"):
|
|
@@ -56,7 +58,7 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
56
58
|
"url": None,
|
|
57
59
|
}]
|
|
58
60
|
|
|
59
|
-
except
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
except (RateLimitError, InvalidKeyError):
|
|
62
|
+
raise
|
|
63
|
+
except Exception:
|
|
62
64
|
return []
|
|
@@ -4,6 +4,8 @@ from typing import Dict, List
|
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
|
+
from osintkit.modules import RateLimitError, InvalidKeyError
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
9
11
|
"""Enumerate subdomains for the target's email domain via SecurityTrails API.
|
|
@@ -35,9 +37,9 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
if response.status_code == 429:
|
|
38
|
-
raise
|
|
40
|
+
raise RateLimitError("SecurityTrails rate limit reached")
|
|
39
41
|
if response.status_code in (401, 403):
|
|
40
|
-
raise
|
|
42
|
+
raise InvalidKeyError("SecurityTrails API key invalid or unauthorized")
|
|
41
43
|
|
|
42
44
|
data = response.json()
|
|
43
45
|
subdomains = data.get("subdomains", [])
|
|
@@ -59,7 +61,7 @@ async def run(inputs: dict, api_key: str) -> List[Dict]:
|
|
|
59
61
|
})
|
|
60
62
|
return findings
|
|
61
63
|
|
|
62
|
-
except
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
except (RateLimitError, InvalidKeyError):
|
|
65
|
+
raise
|
|
66
|
+
except Exception:
|
|
65
67
|
return []
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|