osintkit 0.1.1 → 0.1.2
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 +84 -53
- package/bin/osintkit.js +32 -7
- package/osintkit/__init__.py +1 -1
- package/osintkit/__main__.py +0 -0
- package/osintkit/__pycache__/__init__.cpython-310.pyc +0 -0
- package/osintkit/__pycache__/__init__.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/__main__.cpython-310.pyc +0 -0
- package/osintkit/__pycache__/__main__.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/cli.cpython-310.pyc +0 -0
- package/osintkit/__pycache__/cli.cpython-311.pyc +0 -0
- package/osintkit/__pycache__/risk.cpython-310.pyc +0 -0
- package/osintkit/cli.py +64 -2
- package/osintkit/modules/hibp_kanon.py +14 -1
- 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/wayback.py +19 -1
- package/osintkit/profiles.py +15 -0
- package/osintkit/risk.py +16 -7
- package/osintkit/setup.py +3 -1
- package/package.json +5 -3
- package/postinstall.js +75 -0
- package/pyproject.toml +2 -1
- package/requirements-tools.txt +12 -0
- package/requirements.txt +1 -0
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -1,86 +1,117 @@
|
|
|
1
1
|
# osintkit
|
|
2
2
|
|
|
3
|
-
OSINT CLI
|
|
3
|
+
OSINT CLI for personal digital footprint analysis. Input an email, phone, username, or name — get a risk-scored report saved locally as JSON, HTML, and Markdown.
|
|
4
|
+
|
|
5
|
+
MIT licensed. No server. Everything stays on your machine.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
9
|
-
cd osintkit-oss
|
|
10
|
-
npm install -g .
|
|
11
|
-
|
|
12
|
-
# Or use directly without installation
|
|
13
|
-
PYTHONPATH=. python3 -m osintkit.cli <command>
|
|
10
|
+
npm install -g osintkit
|
|
14
11
|
```
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
# First-time setup (configure API keys)
|
|
20
|
-
osintkit setup
|
|
21
|
-
|
|
22
|
-
# Create a new profile
|
|
23
|
-
osintkit new
|
|
24
|
-
|
|
25
|
-
# List all profiles
|
|
26
|
-
osintkit list
|
|
13
|
+
This automatically installs all Python dependencies (core + optional OSINT tools) via the postinstall script. Just run `osintkit new` when it's done.
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
osintkit refresh <profile_id>
|
|
15
|
+
**Requirements:** Python 3.10+, Node.js 16+
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
osintkit open <profile_id>
|
|
17
|
+
## Quick Start
|
|
33
18
|
|
|
34
|
-
|
|
35
|
-
osintkit
|
|
19
|
+
```bash
|
|
20
|
+
osintkit setup # Configure API keys (optional, all have free tiers)
|
|
21
|
+
osintkit new # Create a profile and run a scan
|
|
22
|
+
osintkit list # View all profiles
|
|
23
|
+
osintkit refresh # Re-run scan on a profile
|
|
24
|
+
osintkit open # View profile details + open latest report
|
|
36
25
|
```
|
|
37
26
|
|
|
38
27
|
## Commands
|
|
39
28
|
|
|
40
29
|
| Command | Description |
|
|
41
30
|
|---------|-------------|
|
|
42
|
-
| `osintkit new` | Create new profile |
|
|
43
|
-
| `osintkit list` | List all profiles |
|
|
44
|
-
| `osintkit refresh
|
|
45
|
-
| `osintkit open
|
|
46
|
-
| `osintkit export
|
|
31
|
+
| `osintkit new` | Create a new profile and optionally run a scan |
|
|
32
|
+
| `osintkit list` | List all profiles with last risk score |
|
|
33
|
+
| `osintkit refresh [id]` | Re-run scan for a profile |
|
|
34
|
+
| `osintkit open [id]` | Show profile details and open latest report |
|
|
35
|
+
| `osintkit export [id]` | Export as JSON or Markdown |
|
|
47
36
|
| `osintkit setup` | Configure API keys |
|
|
48
|
-
| `osintkit delete
|
|
37
|
+
| `osintkit delete [id]` | Delete a profile |
|
|
49
38
|
| `osintkit version` | Show version |
|
|
50
39
|
|
|
51
|
-
##
|
|
40
|
+
## What It Checks
|
|
41
|
+
|
|
42
|
+
### Stage 1 — No API keys needed, works out of the box
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
| Module | Input | What it does |
|
|
45
|
+
|--------|-------|-------------|
|
|
46
|
+
| Maigret | username | 3000+ sites |
|
|
47
|
+
| Sherlock | username | 400+ sites |
|
|
48
|
+
| Holehe | email | 120+ platform registrations |
|
|
49
|
+
| HIBP k-anonymity | email | Password breach check (no key) |
|
|
50
|
+
| Gravatar | email | Profile existence + avatar |
|
|
51
|
+
| theHarvester | email/domain | Web presence, subdomains |
|
|
52
|
+
| crt.sh | email/domain | Certificate transparency |
|
|
53
|
+
| Wayback CDX | email | Historical web appearances |
|
|
54
|
+
| libphonenumber | phone | Carrier, region, line type (offline) |
|
|
55
|
+
| Paste search | email | Paste site appearances |
|
|
56
|
+
| Data brokers | name/email | Google CSE broker scan |
|
|
57
|
+
| Dark web | email | Ahmia / public index |
|
|
58
|
+
| Breach lookup | email | BreachDirectory |
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
### Stage 2 — Optional free API keys, runs first when configured
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
| Service | Input | Free Tier |
|
|
63
|
+
|---------|-------|-----------|
|
|
64
|
+
| HaveIBeenPwned | email | Free w/ key |
|
|
65
|
+
| LeakCheck | email/phone/user | Free tier |
|
|
66
|
+
| NumVerify | phone | 100/month |
|
|
67
|
+
| Hunter.io | name + domain | 50/month |
|
|
68
|
+
| GitHub API | username | 5000/hr w/ key |
|
|
69
|
+
| SecurityTrails | domain | Free tier |
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|---------|------------|---------|
|
|
65
|
-
| Have I Been Pwned | 10/min | Breach database |
|
|
66
|
-
| NumVerify | 100/month | Phone validation |
|
|
67
|
-
| Intelbase | 100/month | Dark web + paste |
|
|
68
|
-
| BreachDirectory | 50/day | Breach lookups |
|
|
69
|
-
| Google CSE | 100/day | Data broker detection |
|
|
71
|
+
Stage 2 always runs first when a key is configured. If rate-limited or key missing, falls back to Stage 1 automatically.
|
|
70
72
|
|
|
71
|
-
##
|
|
73
|
+
## Output
|
|
72
74
|
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
75
|
+
Each scan creates a folder at `~/osint-results/<target>_<date>/` containing:
|
|
76
|
+
- `report.html` — rendered report with risk score
|
|
77
|
+
- `findings.json` — full structured data
|
|
78
|
+
- `findings.md` — markdown summary
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
Risk score is 0–100 based on breach exposure, social footprint, data broker listings, and dark web/paste appearances.
|
|
81
|
+
|
|
82
|
+
## API Keys (All Optional)
|
|
83
|
+
|
|
84
|
+
Run `osintkit setup` to configure. All keys are optional — the tool works without any of them.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
~/.osintkit/config.yaml
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Service | Get key at |
|
|
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 |
|
|
101
|
+
|
|
102
|
+
## Run from Source
|
|
78
103
|
|
|
79
|
-
For full functionality, install:
|
|
80
104
|
```bash
|
|
81
|
-
|
|
105
|
+
git clone https://github.com/diesesschnitzel/osintkit.git
|
|
106
|
+
cd osintkit
|
|
107
|
+
pip install -r requirements.txt -r requirements-tools.txt
|
|
108
|
+
PYTHONPATH=. python3 -m osintkit.cli new
|
|
82
109
|
```
|
|
83
110
|
|
|
111
|
+
## Ethics
|
|
112
|
+
|
|
113
|
+
Only use osintkit on targets you have explicit permission to investigate. GDPR applies to EU subjects. A disclaimer is shown before every scan.
|
|
114
|
+
|
|
84
115
|
## License
|
|
85
116
|
|
|
86
|
-
MIT
|
|
117
|
+
MIT
|
package/bin/osintkit.js
CHANGED
|
@@ -3,10 +3,24 @@ const { spawn } = require('child_process');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// The npm package root is one level up from bin/
|
|
7
|
+
const packageDir = path.dirname(path.dirname(__filename));
|
|
8
|
+
|
|
9
|
+
// Prefer venv Python (installed by postinstall), fall back to system Python
|
|
7
10
|
function findPython() {
|
|
8
|
-
const
|
|
9
|
-
|
|
11
|
+
const venvCandidates = [
|
|
12
|
+
path.join(packageDir, '.venv', 'bin', 'python3'),
|
|
13
|
+
path.join(packageDir, '.venv', 'bin', 'python'),
|
|
14
|
+
path.join(packageDir, 'venv', 'bin', 'python3'),
|
|
15
|
+
path.join(packageDir, 'venv', 'bin', 'python'),
|
|
16
|
+
];
|
|
17
|
+
for (const p of venvCandidates) {
|
|
18
|
+
if (fs.existsSync(p)) return p;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fall back to system Python
|
|
22
|
+
const systemCandidates = ['python3.11', 'python3', 'python'];
|
|
23
|
+
for (const bin of systemCandidates) {
|
|
10
24
|
try {
|
|
11
25
|
require('child_process').execSync(`${bin} --version`, { stdio: 'ignore' });
|
|
12
26
|
return bin;
|
|
@@ -15,14 +29,25 @@ function findPython() {
|
|
|
15
29
|
return 'python3';
|
|
16
30
|
}
|
|
17
31
|
|
|
18
|
-
const
|
|
32
|
+
const pythonBin = findPython();
|
|
33
|
+
|
|
34
|
+
// Always set PYTHONPATH so the package is importable regardless of install method
|
|
35
|
+
const env = {
|
|
36
|
+
...process.env,
|
|
37
|
+
PYTHONPATH: process.env.PYTHONPATH
|
|
38
|
+
? `${packageDir}:${process.env.PYTHONPATH}`
|
|
39
|
+
: packageDir,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const child = spawn(pythonBin, ['-m', 'osintkit', ...process.argv.slice(2)], {
|
|
19
43
|
stdio: 'inherit',
|
|
20
|
-
|
|
44
|
+
cwd: packageDir,
|
|
45
|
+
env,
|
|
21
46
|
});
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
child.on('error', () => {
|
|
24
49
|
console.error('Error: Python 3.10+ required. Install from https://python.org');
|
|
25
50
|
process.exit(1);
|
|
26
51
|
});
|
|
27
52
|
|
|
28
|
-
|
|
53
|
+
child.on('close', (code) => process.exit(code || 0));
|
package/osintkit/__init__.py
CHANGED
package/osintkit/__main__.py
CHANGED
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/osintkit/cli.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
+
import threading
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Optional
|
|
8
9
|
from datetime import datetime
|
|
@@ -14,14 +15,67 @@ from rich.table import Table
|
|
|
14
15
|
from rich.prompt import Prompt, Confirm
|
|
15
16
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
16
17
|
|
|
18
|
+
from osintkit import __version__
|
|
17
19
|
from osintkit.scanner import Scanner
|
|
18
20
|
from osintkit.config import load_config, Config, APIKeys
|
|
19
21
|
from osintkit.profiles import Profile, ProfileStore, ScanHistory
|
|
20
22
|
|
|
21
|
-
app = typer.Typer(help="OSINT CLI for personal digital footprint analysis")
|
|
23
|
+
app = typer.Typer(help="OSINT CLI for personal digital footprint analysis", invoke_without_command=True)
|
|
22
24
|
console = Console()
|
|
23
25
|
store = ProfileStore()
|
|
24
26
|
logger = logging.getLogger(__name__)
|
|
27
|
+
_update_thread = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.callback()
|
|
31
|
+
def _startup(ctx: typer.Context):
|
|
32
|
+
"""Start background version check on every invocation."""
|
|
33
|
+
global _update_thread
|
|
34
|
+
_update_thread = _start_update_check()
|
|
35
|
+
|
|
36
|
+
# ---- Version update check ----
|
|
37
|
+
|
|
38
|
+
_update_available: Optional[str] = None # Set to newer version string if one exists
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _check_for_update_bg():
|
|
42
|
+
"""Check npm registry for a newer version in a background thread (non-blocking)."""
|
|
43
|
+
global _update_available
|
|
44
|
+
try:
|
|
45
|
+
import httpx
|
|
46
|
+
resp = httpx.get(
|
|
47
|
+
"https://registry.npmjs.org/osintkit/latest",
|
|
48
|
+
timeout=3.0,
|
|
49
|
+
headers={"Accept": "application/json"},
|
|
50
|
+
)
|
|
51
|
+
if resp.status_code == 200:
|
|
52
|
+
latest = resp.json().get("version", "")
|
|
53
|
+
if latest and latest != __version__:
|
|
54
|
+
from packaging.version import Version
|
|
55
|
+
if Version(latest) > Version(__version__):
|
|
56
|
+
_update_available = latest
|
|
57
|
+
except Exception:
|
|
58
|
+
pass # Never crash the app over a version check
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _start_update_check():
|
|
62
|
+
"""Start the background version check thread."""
|
|
63
|
+
t = threading.Thread(target=_check_for_update_bg, daemon=True)
|
|
64
|
+
t.start()
|
|
65
|
+
return t
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _print_update_notice():
|
|
69
|
+
"""Print update notice if a newer version was found."""
|
|
70
|
+
if _update_available:
|
|
71
|
+
console.print(Panel(
|
|
72
|
+
f"[bold cyan]osintkit {_update_available}[/bold cyan] is available "
|
|
73
|
+
f"[dim](you have {__version__})[/dim]\n"
|
|
74
|
+
"[dim]Run:[/dim] [bold]npm install -g osintkit[/bold]",
|
|
75
|
+
title="[bold yellow]⬆ Update available[/bold yellow]",
|
|
76
|
+
border_style="yellow",
|
|
77
|
+
padding=(0, 2),
|
|
78
|
+
))
|
|
25
79
|
|
|
26
80
|
|
|
27
81
|
def _print_ethics_banner():
|
|
@@ -123,6 +177,7 @@ api_keys:
|
|
|
123
177
|
epieos: ""
|
|
124
178
|
"""
|
|
125
179
|
config_path.write_text(config_content)
|
|
180
|
+
config_path.chmod(0o600) # API keys must not be world-readable
|
|
126
181
|
console.print(f"\n[green]✓[/green] Config saved to {config_path}")
|
|
127
182
|
return True
|
|
128
183
|
|
|
@@ -288,6 +343,7 @@ api_keys:
|
|
|
288
343
|
epieos: ""
|
|
289
344
|
"""
|
|
290
345
|
config_path.write_text(config_content)
|
|
346
|
+
config_path.chmod(0o600) # API keys must not be world-readable
|
|
291
347
|
console.print(f"\n[green]✓[/green] Config saved: {config_path}")
|
|
292
348
|
|
|
293
349
|
|
|
@@ -402,6 +458,9 @@ def list():
|
|
|
402
458
|
|
|
403
459
|
console.print(table)
|
|
404
460
|
console.print()
|
|
461
|
+
if _update_thread:
|
|
462
|
+
_update_thread.join(timeout=4)
|
|
463
|
+
_print_update_notice()
|
|
405
464
|
|
|
406
465
|
|
|
407
466
|
@app.command()
|
|
@@ -440,6 +499,7 @@ def refresh(profile_ref: str = typer.Argument(None, help="Profile ID or name")):
|
|
|
440
499
|
)
|
|
441
500
|
store.add_scan_result(profile.id, scan_record)
|
|
442
501
|
console.print(f"\n[green]✓[/green] Scan saved to profile history")
|
|
502
|
+
_print_update_notice()
|
|
443
503
|
|
|
444
504
|
|
|
445
505
|
@app.command()
|
|
@@ -605,8 +665,10 @@ def delete(profile_ref: str = typer.Argument(None, help="Profile ID or name")):
|
|
|
605
665
|
@app.command()
|
|
606
666
|
def version():
|
|
607
667
|
"""Show version."""
|
|
608
|
-
|
|
668
|
+
if _update_thread:
|
|
669
|
+
_update_thread.join(timeout=4)
|
|
609
670
|
console.print(f"osintkit v{__version__}")
|
|
671
|
+
_print_update_notice()
|
|
610
672
|
|
|
611
673
|
|
|
612
674
|
if __name__ == "__main__":
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
"""HIBP k-anonymity password exposure check using SHA1 prefix API.
|
|
1
|
+
"""HIBP k-anonymity password exposure check using SHA1 prefix API.
|
|
2
|
+
|
|
3
|
+
Mock format for tests
|
|
4
|
+
---------------------
|
|
5
|
+
The API returns plain text, one entry per line: ``HASHSUFFIX:COUNT``
|
|
6
|
+
The suffix is uppercase hex (35 chars = SHA1 40 minus 5-char prefix).
|
|
7
|
+
|
|
8
|
+
Example mock response.text for email "test@example.com":
|
|
9
|
+
sha1 = hashlib.sha1(b"test@example.com").hexdigest().upper()
|
|
10
|
+
prefix, suffix = sha1[:5], sha1[5:]
|
|
11
|
+
mock_text = f"{suffix}:42\\nDEADBEEFCAFE00000000000000000000000:1\\n"
|
|
12
|
+
|
|
13
|
+
The module checks each line for a matching suffix and reads the count.
|
|
14
|
+
"""
|
|
2
15
|
|
|
3
16
|
import hashlib
|
|
4
17
|
import logging
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
"""Wayback Machine CDX API lookup for email domain and username.
|
|
1
|
+
"""Wayback Machine CDX API lookup for email domain and username.
|
|
2
|
+
|
|
3
|
+
Mock format for tests
|
|
4
|
+
---------------------
|
|
5
|
+
The CDX API returns JSON: a list of lists where the FIRST row is a header
|
|
6
|
+
and subsequent rows are data. Each row matches the ``fl`` fields requested.
|
|
7
|
+
This module requests ``fl=original,timestamp``, so:
|
|
8
|
+
|
|
9
|
+
[
|
|
10
|
+
["original", "timestamp"], # header row
|
|
11
|
+
["http://example.com/page", "20210315120000"], # data row
|
|
12
|
+
["http://example.com/other", "20200101000000"],
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
An empty result (or only the header row) is treated as no findings::
|
|
16
|
+
|
|
17
|
+
[] # API returned nothing
|
|
18
|
+
[["original", "timestamp"]] # header only — no data
|
|
19
|
+
"""
|
|
2
20
|
|
|
3
21
|
from typing import Any, Dict, List
|
|
4
22
|
|
package/osintkit/profiles.py
CHANGED
|
@@ -54,6 +54,21 @@ class ProfileStore:
|
|
|
54
54
|
with open(self.store_path, "w") as f:
|
|
55
55
|
json.dump(profiles, f, indent=2, default=str)
|
|
56
56
|
|
|
57
|
+
def find_duplicate(self, profile: Profile) -> Optional[Profile]:
|
|
58
|
+
"""Return first existing profile that shares email, username, or phone with the given profile.
|
|
59
|
+
|
|
60
|
+
Used before create() to warn the user about potential duplicates.
|
|
61
|
+
"""
|
|
62
|
+
profiles = self._load()
|
|
63
|
+
for p in profiles.values():
|
|
64
|
+
if profile.email and p.get("email") and profile.email.lower() == p["email"].lower():
|
|
65
|
+
return Profile(**p)
|
|
66
|
+
if profile.username and p.get("username") and profile.username.lower() == p["username"].lower():
|
|
67
|
+
return Profile(**p)
|
|
68
|
+
if profile.phone and p.get("phone") and profile.phone == p["phone"]:
|
|
69
|
+
return Profile(**p)
|
|
70
|
+
return None
|
|
71
|
+
|
|
57
72
|
def create(self, profile: Profile) -> Profile:
|
|
58
73
|
"""Create a new profile."""
|
|
59
74
|
profiles = self._load()
|
package/osintkit/risk.py
CHANGED
|
@@ -32,11 +32,20 @@ def calculate_risk_score(findings: Dict[str, List]) -> int:
|
|
|
32
32
|
dark_count = len(findings.get("dark_web", [])) + len(findings.get("paste_sites", []))
|
|
33
33
|
score += min(15, dark_count * 5)
|
|
34
34
|
|
|
35
|
-
# Password
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
# Password / hash exposure (15 points max)
|
|
36
|
+
# Covers both: HIBP full API ("password_exposure") and k-anonymity check ("hibp_kanon")
|
|
37
|
+
pw_score = 0
|
|
38
|
+
for key in ("password_exposure", "hibp_kanon"):
|
|
39
|
+
pw_data = findings.get(key, [])
|
|
40
|
+
if pw_data and isinstance(pw_data[0], dict):
|
|
41
|
+
count = pw_data[0].get("data", {}).get("count", 0)
|
|
42
|
+
if count:
|
|
43
|
+
pw_score = max(pw_score, min(15, count // 1000))
|
|
44
|
+
# Even a single confirmed exposure without a count is worth some points
|
|
45
|
+
if pw_score == 0:
|
|
46
|
+
all_pw = findings.get("password_exposure", []) + findings.get("hibp_kanon", [])
|
|
47
|
+
if all_pw:
|
|
48
|
+
pw_score = 5
|
|
49
|
+
score += pw_score
|
|
50
|
+
|
|
42
51
|
return min(100, score)
|
package/osintkit/setup.py
CHANGED
|
@@ -121,11 +121,13 @@ def run_setup_wizard():
|
|
|
121
121
|
config_path = config_dir / "config.yaml"
|
|
122
122
|
with open(config_path, "w") as f:
|
|
123
123
|
yaml.dump(config, f, default_flow_style=False)
|
|
124
|
-
|
|
124
|
+
config_path.chmod(0o600) # owner read/write only — API keys must not be world-readable
|
|
125
|
+
|
|
125
126
|
# Create profiles file
|
|
126
127
|
profiles_path = config_dir / "profiles.json"
|
|
127
128
|
if not profiles_path.exists():
|
|
128
129
|
profiles_path.write_text("{}")
|
|
130
|
+
profiles_path.chmod(0o600)
|
|
129
131
|
|
|
130
132
|
console.print(f"\n[green]✓[/green] Config saved to: {config_path}")
|
|
131
133
|
console.print("[green]✓[/green] Ready to use!")
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osintkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OSINT CLI for personal digital footprint analysis",
|
|
5
5
|
"bin": {
|
|
6
6
|
"osintkit": "./bin/osintkit.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"postinstall": "
|
|
9
|
+
"postinstall": "node postinstall.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"bin/",
|
|
13
13
|
"osintkit/",
|
|
14
14
|
"pyproject.toml",
|
|
15
15
|
"requirements.txt",
|
|
16
|
-
"
|
|
16
|
+
"requirements-tools.txt",
|
|
17
|
+
"README.md",
|
|
18
|
+
"postinstall.js"
|
|
17
19
|
],
|
|
18
20
|
"keywords": ["osint", "security", "privacy", "cli", "recon"],
|
|
19
21
|
"author": "",
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* osintkit postinstall — installs all Python dependencies automatically.
|
|
4
|
+
*
|
|
5
|
+
* Runs after `npm install -g osintkit`.
|
|
6
|
+
* Installs:
|
|
7
|
+
* 1. Core Python deps (typer, rich, httpx, pydantic, etc.) from requirements.txt
|
|
8
|
+
* 2. Optional OSINT tools (maigret, holehe, sherlock) from requirements-tools.txt
|
|
9
|
+
*
|
|
10
|
+
* Uses --break-system-packages on Linux/macOS where needed (Python 3.11+).
|
|
11
|
+
* Falls back gracefully if pip isn't available.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
const packageDir = __dirname;
|
|
19
|
+
const req = path.join(packageDir, 'requirements.txt');
|
|
20
|
+
const reqTools = path.join(packageDir, 'requirements-tools.txt');
|
|
21
|
+
|
|
22
|
+
function findPip() {
|
|
23
|
+
for (const bin of ['pip3', 'pip']) {
|
|
24
|
+
try {
|
|
25
|
+
execSync(`${bin} --version`, { stdio: 'ignore' });
|
|
26
|
+
return bin;
|
|
27
|
+
} catch (_) {}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pipInstall(pip, requirementsFile, label) {
|
|
33
|
+
if (!fs.existsSync(requirementsFile)) {
|
|
34
|
+
console.log(` ⚠️ ${label}: requirements file not found, skipping`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const flags = ['-r', requirementsFile, '-q', '--disable-pip-version-check'];
|
|
39
|
+
|
|
40
|
+
// Try with --break-system-packages first (needed on modern Linux/macOS)
|
|
41
|
+
try {
|
|
42
|
+
execSync(`${pip} install ${flags.join(' ')} --break-system-packages`, {
|
|
43
|
+
stdio: 'pipe',
|
|
44
|
+
cwd: packageDir,
|
|
45
|
+
});
|
|
46
|
+
console.log(` ✅ ${label} installed`);
|
|
47
|
+
return;
|
|
48
|
+
} catch (_) {}
|
|
49
|
+
|
|
50
|
+
// Fall back without the flag (works on older systems / virtual envs)
|
|
51
|
+
try {
|
|
52
|
+
execSync(`${pip} install ${flags.join(' ')}`, {
|
|
53
|
+
stdio: 'pipe',
|
|
54
|
+
cwd: packageDir,
|
|
55
|
+
});
|
|
56
|
+
console.log(` ✅ ${label} installed`);
|
|
57
|
+
return;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.log(` ⚠️ ${label}: could not install (${err.message.split('\n')[0]})`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log('\nosintkit: installing Python dependencies...\n');
|
|
64
|
+
|
|
65
|
+
const pip = findPip();
|
|
66
|
+
if (!pip) {
|
|
67
|
+
console.log(' ⚠️ pip not found — skipping Python dependency install.');
|
|
68
|
+
console.log(' Run manually: pip3 install -r requirements.txt -r requirements-tools.txt');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pipInstall(pip, req, 'Core dependencies');
|
|
73
|
+
pipInstall(pip, reqTools, 'OSINT tools (maigret, holehe, sherlock)');
|
|
74
|
+
|
|
75
|
+
console.log('\nosintkit ready. Run: osintkit new\n');
|
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "osintkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "OSINT CLI tool for personal digital footprint analysis"
|
|
5
5
|
authors = ["Your Name <you@example.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -17,6 +17,7 @@ pyyaml = "^6.0.1"
|
|
|
17
17
|
pydantic = "^2.5.0"
|
|
18
18
|
aiofiles = "^23.2.1"
|
|
19
19
|
phonenumbers = ">=8.13"
|
|
20
|
+
packaging = ">=23.0"
|
|
20
21
|
questionary = ">=2.0"
|
|
21
22
|
|
|
22
23
|
[tool.poetry.group.dev.dependencies]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Optional subprocess-based OSINT tools used by osintkit modules.
|
|
2
|
+
# Install these to enable social profile enumeration, email account checks,
|
|
3
|
+
# and username search:
|
|
4
|
+
#
|
|
5
|
+
# pip install -r requirements-tools.txt
|
|
6
|
+
#
|
|
7
|
+
# They are not listed as poetry dependencies because they are standalone CLI
|
|
8
|
+
# tools invoked via subprocess rather than imported as Python libraries.
|
|
9
|
+
|
|
10
|
+
maigret>=0.4.4
|
|
11
|
+
holehe>=1.1.9
|
|
12
|
+
sherlock-project>=0.14.3
|