osintkit 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osintkit/__init__.py +3 -0
- osintkit/cli.py +673 -0
- osintkit/collectors/__init__.py +24 -0
- osintkit/collectors/accounts.py +83 -0
- osintkit/collectors/base.py +138 -0
- osintkit/collectors/breaches.py +163 -0
- osintkit/collectors/ct.py +165 -0
- osintkit/collectors/dns.py +130 -0
- osintkit/collectors/github.py +190 -0
- osintkit/collectors/pastes.py +237 -0
- osintkit/collectors/ports.py +91 -0
- osintkit/collectors/subdomains.py +102 -0
- osintkit/collectors/whois_lookup.py +72 -0
- osintkit/config.py +151 -0
- osintkit/diff.py +96 -0
- osintkit/doctor.py +119 -0
- osintkit/errors.py +51 -0
- osintkit/http.py +272 -0
- osintkit/llm.py +91 -0
- osintkit/logging_store.py +206 -0
- osintkit/models.py +228 -0
- osintkit/normalise.py +131 -0
- osintkit/pipeline.py +237 -0
- osintkit/py.typed +0 -0
- osintkit/render.py +235 -0
- osintkit/retention.py +81 -0
- osintkit/synthesis.py +176 -0
- osintkit/targets.py +65 -0
- osintkit-0.1.1.dist-info/METADATA +176 -0
- osintkit-0.1.1.dist-info/RECORD +33 -0
- osintkit-0.1.1.dist-info/WHEEL +4 -0
- osintkit-0.1.1.dist-info/entry_points.txt +2 -0
- osintkit-0.1.1.dist-info/licenses/LICENSE +21 -0
osintkit/synthesis.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Prompt construction, model call, strict validation, and the single repair retry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .errors import SourceUnavailable, SynthesisError
|
|
10
|
+
from .llm import LLMClient
|
|
11
|
+
from .models import Analysis, PromptBundle
|
|
12
|
+
|
|
13
|
+
SYSTEM_PROMPT = """\
|
|
14
|
+
You are a defensive OSINT analyst. You receive raw results gathered from public sources \
|
|
15
|
+
about a single target. Work only from the supplied data plus well-known public facts about \
|
|
16
|
+
the platforms named in it. Never invent a breach, an account, or a hostname.
|
|
17
|
+
|
|
18
|
+
Produce a compact, source-grounded briefing for someone doing a personal privacy check or \
|
|
19
|
+
authorised research.
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
- Cite the supplied source name in every breach entry. Mark dates as found; use null when unknown.
|
|
23
|
+
- If a source reports more breaches than it listed, say so and keep the 15 most material, ordering
|
|
24
|
+
credential-bearing and recent incidents first.
|
|
25
|
+
- Distinguish confirmed findings from plausible inference in the summary and notes.
|
|
26
|
+
- Risk level reflects exposure only: high means credential-bearing breaches or a wide,
|
|
27
|
+
live attack surface.
|
|
28
|
+
- Recommendations must be concrete and actionable, not generic advice.
|
|
29
|
+
- If a source failed or was skipped, note the gap in infrastructure_notes.
|
|
30
|
+
|
|
31
|
+
Return ONLY a JSON object, no prose and no code fences, with exactly these keys:
|
|
32
|
+
{
|
|
33
|
+
"summary": "3-6 sentence overview of the public footprint",
|
|
34
|
+
"breaches": [{"name": "str", "date": "YYYY-MM-DD or null", "data_types": ["str"], "source": "str"}],
|
|
35
|
+
"associated_accounts": [
|
|
36
|
+
{"platform": "str", "url": "str or null", "handle": "str or null", "note": "str or null"}
|
|
37
|
+
],
|
|
38
|
+
"infrastructure_notes": ["str"],
|
|
39
|
+
"subdomains": ["str"],
|
|
40
|
+
"risk_level": "low" | "medium" | "high",
|
|
41
|
+
"recommendations": ["str"]
|
|
42
|
+
}
|
|
43
|
+
Use empty arrays when a section has no findings.\
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
USER_TEMPLATE = """\
|
|
47
|
+
Target: {target}
|
|
48
|
+
Target type: {target_type}
|
|
49
|
+
|
|
50
|
+
Raw public-source data:
|
|
51
|
+
```json
|
|
52
|
+
{payload}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Write the briefing now as strict JSON.\
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
REPAIR_PROMPT = """\
|
|
59
|
+
That response was not valid against the required schema. Error: {error}
|
|
60
|
+
|
|
61
|
+
Return the corrected JSON object only. All seven keys are required, arrays instead of prose \
|
|
62
|
+
for every list field, and risk_level must be exactly one of "low", "medium", or "high".\
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def build_messages(bundle: PromptBundle) -> list[dict[str, str]]:
|
|
67
|
+
return [
|
|
68
|
+
{"role": "system", "content": SYSTEM_PROMPT},
|
|
69
|
+
{
|
|
70
|
+
"role": "user",
|
|
71
|
+
"content": USER_TEMPLATE.format(
|
|
72
|
+
target=bundle.target,
|
|
73
|
+
target_type=bundle.target_type,
|
|
74
|
+
payload=bundle.to_prompt_json(),
|
|
75
|
+
),
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def render_prompt(bundle: PromptBundle) -> str:
|
|
81
|
+
parts = []
|
|
82
|
+
for message in build_messages(bundle):
|
|
83
|
+
parts.append(f"### {message['role']}\n\n{message['content']}")
|
|
84
|
+
return "\n\n".join(parts)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def extract_json(text: str) -> dict[str, Any]:
|
|
88
|
+
cleaned = text.strip()
|
|
89
|
+
if cleaned.startswith("```"):
|
|
90
|
+
cleaned = cleaned.split("\n", 1)[-1]
|
|
91
|
+
if cleaned.rstrip().endswith("```"):
|
|
92
|
+
cleaned = cleaned.rstrip()[:-3]
|
|
93
|
+
start = cleaned.find("{")
|
|
94
|
+
end = cleaned.rfind("}")
|
|
95
|
+
if start == -1 or end == -1 or end <= start:
|
|
96
|
+
raise SynthesisError("no JSON object in model output")
|
|
97
|
+
blob = cleaned[start : end + 1]
|
|
98
|
+
try:
|
|
99
|
+
parsed = json.loads(blob)
|
|
100
|
+
except json.JSONDecodeError as exc:
|
|
101
|
+
raise SynthesisError(f"invalid JSON: {exc.msg} at offset {exc.pos}") from exc
|
|
102
|
+
if not isinstance(parsed, dict):
|
|
103
|
+
raise SynthesisError("model output was JSON but not an object")
|
|
104
|
+
return parsed
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def synthesize(
|
|
108
|
+
client: LLMClient,
|
|
109
|
+
bundle: PromptBundle,
|
|
110
|
+
*,
|
|
111
|
+
logger: Any | None = None,
|
|
112
|
+
max_tokens: int = 2400,
|
|
113
|
+
) -> Analysis:
|
|
114
|
+
messages = build_messages(bundle)
|
|
115
|
+
|
|
116
|
+
settings = getattr(client, "settings", None)
|
|
117
|
+
|
|
118
|
+
async def call(current: list[dict[str, str]], attempt: int) -> str:
|
|
119
|
+
if logger is not None:
|
|
120
|
+
logger.request(
|
|
121
|
+
base_url=getattr(settings, "base_url", "unknown"),
|
|
122
|
+
model=getattr(settings, "model", "unknown"),
|
|
123
|
+
attempt=attempt,
|
|
124
|
+
dry_run=False,
|
|
125
|
+
messages=current,
|
|
126
|
+
max_tokens=max_tokens,
|
|
127
|
+
)
|
|
128
|
+
started = time.perf_counter()
|
|
129
|
+
try:
|
|
130
|
+
try:
|
|
131
|
+
content = await client.chat(
|
|
132
|
+
current,
|
|
133
|
+
max_tokens=max_tokens,
|
|
134
|
+
response_format={"type": "json_object"},
|
|
135
|
+
)
|
|
136
|
+
except SourceUnavailable as exc:
|
|
137
|
+
if "HTTP 400" not in str(exc):
|
|
138
|
+
raise
|
|
139
|
+
content = await client.chat(current, max_tokens=max_tokens)
|
|
140
|
+
except Exception as exc:
|
|
141
|
+
if logger is not None:
|
|
142
|
+
logger.response(
|
|
143
|
+
attempt=attempt,
|
|
144
|
+
ok=False,
|
|
145
|
+
error=str(exc),
|
|
146
|
+
elapsed_ms=int((time.perf_counter() - started) * 1000),
|
|
147
|
+
content=None,
|
|
148
|
+
)
|
|
149
|
+
raise
|
|
150
|
+
if logger is not None:
|
|
151
|
+
logger.response(
|
|
152
|
+
attempt=attempt,
|
|
153
|
+
ok=True,
|
|
154
|
+
error=None,
|
|
155
|
+
elapsed_ms=int((time.perf_counter() - started) * 1000),
|
|
156
|
+
content=content,
|
|
157
|
+
)
|
|
158
|
+
return content
|
|
159
|
+
|
|
160
|
+
raw = ""
|
|
161
|
+
try:
|
|
162
|
+
raw = await call(messages, 1)
|
|
163
|
+
return Analysis.model_validate(extract_json(raw))
|
|
164
|
+
except (SynthesisError, ValueError) as first_error:
|
|
165
|
+
repair = [
|
|
166
|
+
*messages,
|
|
167
|
+
{"role": "assistant", "content": raw or "(empty)"},
|
|
168
|
+
{"role": "user", "content": REPAIR_PROMPT.format(error=str(first_error)[:400])},
|
|
169
|
+
]
|
|
170
|
+
raw_retry = await call(repair, 2)
|
|
171
|
+
try:
|
|
172
|
+
return Analysis.model_validate(extract_json(raw_retry))
|
|
173
|
+
except (SynthesisError, ValueError) as second_error:
|
|
174
|
+
raise SynthesisError(
|
|
175
|
+
f"model output still invalid after one repair attempt: {second_error}"
|
|
176
|
+
) from second_error
|
osintkit/targets.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Target typing and normalisation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from .errors import TargetParseError
|
|
8
|
+
|
|
9
|
+
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$")
|
|
10
|
+
DOMAIN_RE = re.compile(r"^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,24}$")
|
|
11
|
+
USERNAME_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,38})$")
|
|
12
|
+
COMPANY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9 .,&'()+-]*$")
|
|
13
|
+
COMPANY_HINTS = re.compile(r"\b(inc|llc|ltd|gmbh|corp|corporation|limited|plc|ag|sa|bv|oy|pty)\b", re.I)
|
|
14
|
+
|
|
15
|
+
VALID_TYPES = ("email", "username", "domain", "company")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalise(target: str) -> str:
|
|
19
|
+
target = target.strip()
|
|
20
|
+
if target.lower().startswith("http://") or target.lower().startswith("https://"):
|
|
21
|
+
target = target.split("://", 1)[1]
|
|
22
|
+
target = target.strip().strip("<>\"'")
|
|
23
|
+
if "@" not in target:
|
|
24
|
+
target = target.split("/", 1)[0]
|
|
25
|
+
if target.lower().startswith("www."):
|
|
26
|
+
target = target[4:]
|
|
27
|
+
return target.rstrip("/")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def detect_type(target: str) -> str:
|
|
31
|
+
if EMAIL_RE.match(target):
|
|
32
|
+
return "email"
|
|
33
|
+
if DOMAIN_RE.match(target):
|
|
34
|
+
return "domain"
|
|
35
|
+
if " " in target or COMPANY_HINTS.search(target):
|
|
36
|
+
return "company"
|
|
37
|
+
if USERNAME_RE.match(target):
|
|
38
|
+
return "username"
|
|
39
|
+
raise TargetParseError(f"could not tell what {target!r} is; pass --type explicitly")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def resolve(target: str, declared: str | None = None) -> tuple[str, str]:
|
|
43
|
+
clean = normalise(target)
|
|
44
|
+
if not clean:
|
|
45
|
+
raise TargetParseError("empty target")
|
|
46
|
+
kind = (declared or "").lower() or None
|
|
47
|
+
if kind and kind not in VALID_TYPES:
|
|
48
|
+
raise TargetParseError(f"unknown type {kind!r}; expected one of {', '.join(VALID_TYPES)}")
|
|
49
|
+
if kind:
|
|
50
|
+
_check_shape(clean, kind)
|
|
51
|
+
return clean, kind
|
|
52
|
+
kind = detect_type(clean)
|
|
53
|
+
_check_shape(clean, kind)
|
|
54
|
+
return clean, kind
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _check_shape(target: str, kind: str) -> None:
|
|
58
|
+
if kind == "email" and not EMAIL_RE.match(target):
|
|
59
|
+
raise TargetParseError(f"{target!r} is not a valid email")
|
|
60
|
+
if kind == "domain" and not DOMAIN_RE.match(target):
|
|
61
|
+
raise TargetParseError(f"{target!r} is not a valid domain")
|
|
62
|
+
if kind == "username" and not USERNAME_RE.match(target):
|
|
63
|
+
raise TargetParseError(f"{target!r} is not a valid username")
|
|
64
|
+
if kind == "company" and not COMPANY_RE.match(target):
|
|
65
|
+
raise TargetParseError(f"{target!r} does not look like a company name")
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: osintkit
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Public-data OSINT aggregator for emails, usernames, domains, and companies
|
|
5
|
+
Project-URL: Homepage, https://github.com/sjdsjadjajdjsa/osintkit
|
|
6
|
+
Project-URL: Issues, https://github.com/sjdsjadjajdjsa/osintkit/issues
|
|
7
|
+
Author: osintkit contributors
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,osint,privacy,recon,security
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: dnspython>=2.6
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.7
|
|
23
|
+
Requires-Dist: python-whois>=0.9
|
|
24
|
+
Requires-Dist: rich>=13.7
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# osintkit
|
|
34
|
+
|
|
35
|
+
[](https://github.com/sjdsjadjajdjsa/osintkit/actions/workflows/ci.yml)
|
|
36
|
+
[](pyproject.toml)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://github.com/astral-sh/ruff)
|
|
39
|
+
|
|
40
|
+
Public-data OSINT for one email, username, domain, or company: gathered from free sources,
|
|
41
|
+
briefed by an LLM acting as a defensive analyst, rendered as a colour-coded report.
|
|
42
|
+
|
|
43
|
+
`osintkit` uses only public sources: certificate transparency, public DNS, WHOIS, public GitHub
|
|
44
|
+
search, free breach indexes, paste archives, and public profile pages. It never logs in on your
|
|
45
|
+
behalf and never touches a paid data broker.
|
|
46
|
+
|
|
47
|
+
<img src="docs/demo.svg" alt="osintkit report showing risk, breaches, and recommendations" width="820">
|
|
48
|
+
|
|
49
|
+
A full trimmed report, including breach and account sections, is in
|
|
50
|
+
[docs/sample-output.md](docs/sample-output.md).
|
|
51
|
+
|
|
52
|
+
## Why
|
|
53
|
+
|
|
54
|
+
Checking what the public record says about you is tedious: a dozen sites, a dozen formats, no
|
|
55
|
+
synthesis. `osintkit` runs those checks concurrently, keeps the raw evidence, and turns it into
|
|
56
|
+
a briefing with a risk level and concrete hardening advice. Everything is local, every source
|
|
57
|
+
is named, and every gap is reported instead of hidden.
|
|
58
|
+
|
|
59
|
+
Intended for personal privacy checks, authorised pre-engagement recon, and quick due diligence
|
|
60
|
+
on a domain or company. Findings are leads to verify, not facts.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv tool install git+https://github.com/sjdsjadjajdjsa/osintkit
|
|
66
|
+
# or
|
|
67
|
+
pipx install git+https://github.com/sjdsjadjajdjsa/osintkit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
[Releases](https://github.com/sjdsjadjajdjsa/osintkit/releases) carry a signed-checksum wheel;
|
|
71
|
+
`pip install osintkit-0.1.0-py3-none-any.whl` works from one. From a clone,
|
|
72
|
+
`pip install -e .` also works. Python 3.12+ only.
|
|
73
|
+
|
|
74
|
+
## Quick start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
export API_KEY=... # model key, any OpenAI-compatible endpoint
|
|
78
|
+
osintkit doctor --deep # prove the key and endpoint work
|
|
79
|
+
osintkit lookup alice@example.com
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
No key? `osintkit lookup example.com --raw` still collects everything; `--dry-run` shows the
|
|
83
|
+
exact synthesis prompt.
|
|
84
|
+
|
|
85
|
+
Any OpenAI-compatible endpoint works. `--provider venice|openrouter|openai` picks a preset, and
|
|
86
|
+
a recognisable key prefix picks one for you; `--base-url` and `--model` always win.
|
|
87
|
+
|
|
88
|
+
Optional keys: `HIBP_API_KEY` turns on Have I Been Pwned, `GITHUB_TOKEN` lifts GitHub from 60 to
|
|
89
|
+
5000 requests/hour. Everything else needs no credentials.
|
|
90
|
+
|
|
91
|
+
## Commands
|
|
92
|
+
|
|
93
|
+
| Command | Purpose |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `lookup` | One target, one report (`--email`, `--username`, `--domain`, `--company`) |
|
|
96
|
+
| `batch` | A CSV of targets, run concurrently, reported in order |
|
|
97
|
+
| `diff` | What changed between two archived runs |
|
|
98
|
+
| `history` | Past runs, replayable by id |
|
|
99
|
+
| `doctor` | Key, endpoint, and directory checks (`--deep` verifies auth) |
|
|
100
|
+
| `collectors` | Every collector and what it touches |
|
|
101
|
+
| `cache` / `prune` | Inspect, clear, or age out local state |
|
|
102
|
+
| `schema` | The JSON contract the model must satisfy |
|
|
103
|
+
|
|
104
|
+
Output: default colour report, `--json`, `--markdown` (they combine), `--raw` for collected
|
|
105
|
+
data, `--dry-run` for the prompt. Exit codes: `0` ok, `1` synthesis or partial failure, `2` bad
|
|
106
|
+
input.
|
|
107
|
+
|
|
108
|
+
## How it works
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
target -> type detection -> collectors (parallel, cached, retried)
|
|
112
|
+
|
|
|
113
|
+
v
|
|
114
|
+
merge + risk signals (deterministic)
|
|
115
|
+
|
|
|
116
|
+
v
|
|
117
|
+
LLM briefing -> pydantic validation -> one repair retry
|
|
118
|
+
|
|
|
119
|
+
v
|
|
120
|
+
colour report . JSON . Markdown . archive
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Collectors degrade to a logged gap, never a dead run; a report is produced even when sources or
|
|
124
|
+
the model fail.
|
|
125
|
+
|
|
126
|
+
- **Merged breaches.** Three indexes listing one incident collapse into one row with every
|
|
127
|
+
source and the earliest date.
|
|
128
|
+
- **Risk floor.** Breach count and credential types set a floor the model may exceed but never
|
|
129
|
+
undercut.
|
|
130
|
+
- **Cached everything.** 24h default, shorter for volatile sources; repeat prompts cost nothing.
|
|
131
|
+
- **Validated output.** Strict JSON schema, one repair round-trip, then a clean failure.
|
|
132
|
+
|
|
133
|
+
## Collectors
|
|
134
|
+
|
|
135
|
+
Two dozen sources across four target types, all free and login-free: HIBP (key), XposedOrNot,
|
|
136
|
+
LeakCheck, Gravatar, crt.sh (subdomains, certificates, organisation search), public DNS
|
|
137
|
+
(A/AAAA/MX/TXT/NS/CNAME plus SPF, DMARC, DKIM, wildcard), passive subdomains, WHOIS, TCP
|
|
138
|
+
connect probes, GitHub (profile, commits, user search, org repos), psbdmp with a Gist fallback,
|
|
139
|
+
grep.app, Hacker News, Stack Exchange, and username presence. The full table is in
|
|
140
|
+
[docs/REFERENCE.md](docs/REFERENCE.md#collectors) or via `osintkit collectors`.
|
|
141
|
+
|
|
142
|
+
## Safety and legality
|
|
143
|
+
|
|
144
|
+
- Public data only. No logins, no scraping behind auth, no paid brokers.
|
|
145
|
+
- Targets are typed and normalised before any request.
|
|
146
|
+
- Requests retry on `429`/`5xx` with backoff and honour GitHub quota headers.
|
|
147
|
+
- Results are evidence, not ground truth; the report says which sources were unavailable.
|
|
148
|
+
- Cache, logs, and reports stay under `~/.osintkit` (override with `OSINTKIT_HOME`). Retention
|
|
149
|
+
is 30 days by default.
|
|
150
|
+
|
|
151
|
+
## Limitations
|
|
152
|
+
|
|
153
|
+
WHOIS is often redacted. GitHub search is shallow. Free breach indexes lag. Port probing only
|
|
154
|
+
sees what your network can reach. Model summaries can overstate weak signals. Read the coverage
|
|
155
|
+
panel: an empty section may mean "nothing found" or "source skipped".
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
git clone https://github.com/sjdsjadjajdjsa/osintkit
|
|
161
|
+
cd osintkit
|
|
162
|
+
uv sync --extra dev
|
|
163
|
+
pytest
|
|
164
|
+
uvx ruff check src tests
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Tests never touch the network: HTTP is mocked with `respx`, DNS and WHOIS are monkeypatched, and
|
|
168
|
+
a recorded model reply covers the full-stack path. CI runs on 3.12 and 3.13. See
|
|
169
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the collector contract and
|
|
170
|
+
[docs/architecture.md](docs/architecture.md) for the module map.
|
|
171
|
+
|
|
172
|
+
## Licence
|
|
173
|
+
|
|
174
|
+
MIT. Security reports: [SECURITY.md](SECURITY.md).
|
|
175
|
+
|
|
176
|
+
`osintkit` is independent tooling. It is not affiliated with any source it queries.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
osintkit/__init__.py,sha256=nzOmu0EQ5zD3bXHXAG09l7UM6UN-hO_5H9kFS-vByGg,71
|
|
2
|
+
osintkit/cli.py,sha256=IOn7YGApgP6Dt1oO-U8Jc_m9Hmm99dgkDAoxlZw3ZkM,25214
|
|
3
|
+
osintkit/config.py,sha256=mYIazpKR4R1A75q4QiEWB00D4tjAQ3qWtpQxf94XvhM,4314
|
|
4
|
+
osintkit/diff.py,sha256=fY4Y-bEmqugUkZsul23zVwC3YBRchHqwphl0NuvpCRI,3536
|
|
5
|
+
osintkit/doctor.py,sha256=Fvyrg6Ke_5W6XYi8FAFwI9L1ReihE4dSPYtL409X52Y,4176
|
|
6
|
+
osintkit/errors.py,sha256=LpWYG6-BJeSOC7TZhJz_FxOMw_Nqwag3Odpm6nojeUw,1145
|
|
7
|
+
osintkit/http.py,sha256=wt0sWXpEpREJZrFaR--7OuEsyaCbFdENTw3fOmbm2AM,9461
|
|
8
|
+
osintkit/llm.py,sha256=jG_ubL_3_X324V46-Cu_0tZmSN2kAhuMFN92IdpoLdE,3237
|
|
9
|
+
osintkit/logging_store.py,sha256=erYmgS-TAqP7uXBhB2KCmMUWHLDR-7OcSOLXJrWGGWU,6226
|
|
10
|
+
osintkit/models.py,sha256=nRyg4rfRoU81koug53z5NuY-oX9uSlmgqAJejlP1JkI,7034
|
|
11
|
+
osintkit/normalise.py,sha256=eR_cSFHG4W_6zg5YqlbGizQF_ycrhBLd02lvFBPjjQM,4508
|
|
12
|
+
osintkit/pipeline.py,sha256=cKeI2vDf8F2GjOwbwFIReAZDATug_9iwThLODhnjv-Y,8072
|
|
13
|
+
osintkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
osintkit/render.py,sha256=hJB87iwVjy-AU_p8rRTtHpkGoOjrb_RpVqf0hGasy1U,8459
|
|
15
|
+
osintkit/retention.py,sha256=asCc5vNXxdyYLZ3V1LnlFzJxrOwDJYRdXkcFg4BkgLA,2332
|
|
16
|
+
osintkit/synthesis.py,sha256=ZXGLV7RYjC2W6oNimC2TOLeVdLueXrycNcTOi3eGL8E,6101
|
|
17
|
+
osintkit/targets.py,sha256=U7v5gobZmrWCF0nfKqBJkLO5U2o02_hckEcCZnP79wM,2447
|
|
18
|
+
osintkit/collectors/__init__.py,sha256=H1dbtafNPTa9YV0ox8VUZma8PLRM6vk8JOtH3XmYRuM,482
|
|
19
|
+
osintkit/collectors/accounts.py,sha256=gzLB5VBkHs7xReBpyfye12q1OBYxYAgr7gfFk33RRTU,2911
|
|
20
|
+
osintkit/collectors/base.py,sha256=aDJtrfpuxdjMtS1nsPho1lXa_IX0B5IUzftJvPpwJqY,3892
|
|
21
|
+
osintkit/collectors/breaches.py,sha256=HSwecmDdxuDkFVX33IUz6qTxvyetNVFZUCuGp452Vdk,5308
|
|
22
|
+
osintkit/collectors/ct.py,sha256=HJBe9z0qTeqASLyMUT4WbuOsPwShzufuuoYoR9585f8,5232
|
|
23
|
+
osintkit/collectors/dns.py,sha256=dceEKn01iUU6cd3FsJFBypb1uRYXmgw9Iihcid_d-BU,4519
|
|
24
|
+
osintkit/collectors/github.py,sha256=BGusnCVp9WnABst_apqv5uAs9aeVc15WESE4nO8Nz-8,6468
|
|
25
|
+
osintkit/collectors/pastes.py,sha256=0Zk3Qi3LG-iMTS2mSdStRuHSw7xN5sM0WzbZ0pexN7M,8810
|
|
26
|
+
osintkit/collectors/ports.py,sha256=pXsRPcF5Z-0tnM7wP_0hGpJdwRIAf0P7IekfflNHK_k,2700
|
|
27
|
+
osintkit/collectors/subdomains.py,sha256=i-osO4MyFAPcIrZ0blD3f8jIqauvPLaZ8QC36iaE_oo,3002
|
|
28
|
+
osintkit/collectors/whois_lookup.py,sha256=tkXalwMdEh4DQnVrECnF77ZrvBd5kFOh6xr-tVr2GMM,1859
|
|
29
|
+
osintkit-0.1.1.dist-info/METADATA,sha256=nFwcyLijcELGMC00PFTE8znwFRwZa-QJhFxya6bYx-4,7482
|
|
30
|
+
osintkit-0.1.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
31
|
+
osintkit-0.1.1.dist-info/entry_points.txt,sha256=_aW-Q6e3ikA8kWDSUI9EtCTwzECK00H5S8tWtLVkB4Q,46
|
|
32
|
+
osintkit-0.1.1.dist-info/licenses/LICENSE,sha256=RqsRrtEfRQOE5mURJPG7DJB1IUTa7XODM8FlMS7gUEM,1078
|
|
33
|
+
osintkit-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 osintkit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|