otacon 1.0.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.
- otacon-1.0.0/LICENSE +21 -0
- otacon-1.0.0/PKG-INFO +667 -0
- otacon-1.0.0/README.md +616 -0
- otacon-1.0.0/pyproject.toml +125 -0
- otacon-1.0.0/setup.cfg +4 -0
- otacon-1.0.0/src/otacon/__init__.py +11 -0
- otacon-1.0.0/src/otacon/__main__.py +3 -0
- otacon-1.0.0/src/otacon/_asyncutils.py +35 -0
- otacon-1.0.0/src/otacon/_scanner.py +101 -0
- otacon-1.0.0/src/otacon/_validate.py +146 -0
- otacon-1.0.0/src/otacon/cli.py +492 -0
- otacon-1.0.0/src/otacon/html_report.py +373 -0
- otacon-1.0.0/src/otacon/interactive.py +521 -0
- otacon-1.0.0/src/otacon/models.py +111 -0
- otacon-1.0.0/src/otacon/permutations.py +556 -0
- otacon-1.0.0/src/otacon/py.typed +0 -0
- otacon-1.0.0/src/otacon/reporters.py +480 -0
- otacon-1.0.0/src/otacon/resolver.py +484 -0
- otacon-1.0.0/src/otacon/scoring.py +299 -0
- otacon-1.0.0/src/otacon/theme.py +92 -0
- otacon-1.0.0/src/otacon/whois.py +68 -0
- otacon-1.0.0/src/otacon.egg-info/PKG-INFO +667 -0
- otacon-1.0.0/src/otacon.egg-info/SOURCES.txt +34 -0
- otacon-1.0.0/src/otacon.egg-info/dependency_links.txt +1 -0
- otacon-1.0.0/src/otacon.egg-info/entry_points.txt +2 -0
- otacon-1.0.0/src/otacon.egg-info/requires.txt +23 -0
- otacon-1.0.0/src/otacon.egg-info/top_level.txt +1 -0
- otacon-1.0.0/tests/test_cli.py +831 -0
- otacon-1.0.0/tests/test_html_report.py +364 -0
- otacon-1.0.0/tests/test_interactive.py +881 -0
- otacon-1.0.0/tests/test_permutations.py +390 -0
- otacon-1.0.0/tests/test_reporters.py +850 -0
- otacon-1.0.0/tests/test_resolver.py +830 -0
- otacon-1.0.0/tests/test_scoring.py +517 -0
- otacon-1.0.0/tests/test_validate.py +215 -0
- otacon-1.0.0/tests/test_whois.py +186 -0
otacon-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gabriel
|
|
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.
|
otacon-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: otacon
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Domain impersonation detector — typosquatting, homoglyph & combosquat detection.
|
|
5
|
+
Author: Gabriel
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/notimeftnoir/otacon
|
|
8
|
+
Project-URL: Repository, https://github.com/notimeftnoir/otacon
|
|
9
|
+
Project-URL: Issues, https://github.com/notimeftnoir/otacon/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/notimeftnoir/otacon/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: security,typosquatting,osint,phishing,domain-monitoring,pentest,blue-team,threat-intelligence
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Internet :: Name Service (DNS)
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: typer<1,>=0.12.0
|
|
29
|
+
Requires-Dist: rich<16,>=13.7.0
|
|
30
|
+
Requires-Dist: httpx<1,>=0.27.0
|
|
31
|
+
Requires-Dist: httpcore<2,>=1.0.0
|
|
32
|
+
Requires-Dist: cryptography<51,>=42.0.0
|
|
33
|
+
Requires-Dist: aiodns<5,>=3.1.0
|
|
34
|
+
Requires-Dist: pydantic<3,>=2.6.0
|
|
35
|
+
Requires-Dist: questionary<3,>=2.0.0
|
|
36
|
+
Requires-Dist: prompt_toolkit<4,>=3.0.0
|
|
37
|
+
Requires-Dist: asyncwhois<2,>=1.1.0
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=9.0.3; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: respx>=0.20.0; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
44
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
45
|
+
Requires-Dist: pip-audit>=2.7.0; extra == "dev"
|
|
46
|
+
Requires-Dist: interrogate>=1.7.0; extra == "dev"
|
|
47
|
+
Requires-Dist: bandit>=1.9.4; extra == "dev"
|
|
48
|
+
Requires-Dist: types-setuptools; extra == "dev"
|
|
49
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<img src="assets/brand/otacon-header-v2.svg" alt="Otacon — domain impersonation detection" width="760">
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<a href="https://github.com/notimeftnoir/otacon/actions/workflows/ci.yml"><img src="https://github.com/notimeftnoir/otacon/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
58
|
+
<a href="https://github.com/notimeftnoir/otacon/actions/workflows/codeql.yml"><img src="https://github.com/notimeftnoir/otacon/actions/workflows/codeql.yml/badge.svg" alt="CodeQL"></a>
|
|
59
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/notimeftnoir/otacon"><img src="https://api.scorecard.dev/projects/github.com/notimeftnoir/otacon/badge" alt="OpenSSF Scorecard"></a>
|
|
60
|
+
<img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python 3.10+">
|
|
61
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="MIT">
|
|
62
|
+
<img src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey" alt="Platforms">
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
> **Otacon** finds domains impersonating yours — **typosquats, homoglyph fakes, combosquats, IDN/punycode tricks** and more. It generates hundreds of variants, checks which are actually registered, and scores each by real-world phishing risk. One command, ~10 seconds, no paid APIs.
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
⚠ 3 registered · crit: 1 · mx: 1 · fresh <7d: 1
|
|
69
|
+
|
|
70
|
+
Otacon · target: github.com
|
|
71
|
+
┌──────────────────────────────────────┬────────────────┬────────┬───────┬───────┬───────┬─────────┐
|
|
72
|
+
│ Domain │ Risk │ Age │ DNS │ MX │ SSL │ HTTP │
|
|
73
|
+
├──────────────────────────────────────┼────────────────┼────────┼───────┼───────┼───────┼─────────┤
|
|
74
|
+
│ githubupdate.com │ ███████░ 92 │ 3d │ ✓ │ ✓ │ ✓ │ 200 │
|
|
75
|
+
│ combosquat │ │ │ │ │ │ │
|
|
76
|
+
│ "GitHub - Security Update Required" │ │ │ │ │ │ │
|
|
77
|
+
│ bithub.com │ █████░░░ 68 │ 2y │ ✓ │ — │ ✓ │ 301 │
|
|
78
|
+
│ typo │ │ │ │ │ │ │
|
|
79
|
+
│ githuub.com │ ████░░░░ 48 │ 8mo │ ✓ │ — │ — │ 404 │
|
|
80
|
+
│ typo │ │ │ │ │ │ │
|
|
81
|
+
└──────────────────────────────────────┴────────────────┴────────┴───────┴───────┴───────┴─────────┘
|
|
82
|
+
Permutations: 143 · registered: 3 · med: 1 · high: 1 · crit: 1
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Table of contents
|
|
88
|
+
|
|
89
|
+
- [Why Otacon?](#why-otacon)
|
|
90
|
+
- [Quick start](#quick-start)
|
|
91
|
+
- [Install](#install)
|
|
92
|
+
- [Usage](#usage)
|
|
93
|
+
- [Modes](#modes)
|
|
94
|
+
- [Output formats](#output-formats)
|
|
95
|
+
- [How scoring works](#how-scoring-works)
|
|
96
|
+
- [Detection techniques](#detection-techniques)
|
|
97
|
+
- [CI/CD integration](#cicd-integration)
|
|
98
|
+
- [Interactive mode](#interactive-mode)
|
|
99
|
+
- [Whitelist / defensive flag](#whitelist--defensive-flag)
|
|
100
|
+
- [Comparison with dnstwist](#comparison-with-dnstwist)
|
|
101
|
+
- [FAQ](#faq)
|
|
102
|
+
- [Troubleshooting](#troubleshooting)
|
|
103
|
+
- [Contributing & development](#contributing--development)
|
|
104
|
+
- [License & ethics](#license--ethics)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Why Otacon?
|
|
109
|
+
|
|
110
|
+
Phishing campaigns almost always start with a **lookalike domain**. Attackers register `github-update.com`, `paypa1.com`, `goog1e.com` weeks (or days) before the actual attack. By the time anyone notices, credentials are already gone.
|
|
111
|
+
|
|
112
|
+
Otacon is built for the people who need to find those domains **before the attack lands**:
|
|
113
|
+
|
|
114
|
+
| Role | Use case |
|
|
115
|
+
|---|---|
|
|
116
|
+
| **Pentester / red team** | Reconnaissance — find existing lookalikes against the client's brand to include in scope or use in social-engineering tests |
|
|
117
|
+
| **Blue team / SOC** | Scheduled audits of your own domain (e.g. a daily CI cron job) to catch a new fake fast, especially one with MX or fresh registration |
|
|
118
|
+
| **Brand protection** | Audit hundreds of variants in one shot, export as JSON/HTML for the legal team or DMCA filings |
|
|
119
|
+
| **CI/CD gate** | Block deploys when a critical impersonation is live (`--fail-on critical`) |
|
|
120
|
+
|
|
121
|
+
Otacon is **fully passive**: DNS queries, a TLS handshake, one HTTP GET per variant. No exploit attempts, no auth, no scraping at scale.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pipx install git+https://github.com/notimeftnoir/otacon.git
|
|
129
|
+
otacon scan example.com
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
That's it. You get a colored terminal table, ranked by risk, in about 10 seconds.
|
|
133
|
+
|
|
134
|
+
Need a wordlist without network checks? `otacon generate example.com -o wordlist.txt`
|
|
135
|
+
Want guided mode? Just run `otacon` with no arguments.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Install
|
|
140
|
+
|
|
141
|
+
**Recommended — isolated global install via [pipx](https://pipx.pypa.io):**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
pipx install git+https://github.com/notimeftnoir/otacon.git
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Alternative — standard pip into any active virtual environment:**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install git+https://github.com/notimeftnoir/otacon.git
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
> **macOS / Kali / Debian:** the `aiodns` dependency needs the `c-ares` system library.
|
|
154
|
+
> `brew install c-ares` (macOS) · `sudo apt install libc-ares-dev` (Debian/Kali/Ubuntu)
|
|
155
|
+
>
|
|
156
|
+
> **Windows:** no extra deps — wheels are prebuilt. Otacon auto-switches to `SelectorEventLoop` to avoid known Proactor issues.
|
|
157
|
+
|
|
158
|
+
<details>
|
|
159
|
+
<summary><b>Development install (from source, with tests)</b></summary>
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
git clone https://github.com/notimeftnoir/otacon.git
|
|
163
|
+
cd otacon
|
|
164
|
+
python3 -m venv .venv
|
|
165
|
+
source .venv/bin/activate # Linux / macOS
|
|
166
|
+
.venv\Scripts\activate # Windows (PowerShell)
|
|
167
|
+
pip install -e ".[dev]"
|
|
168
|
+
pytest && ruff check .
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Usage
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
otacon # interactive mode (guided prompts)
|
|
179
|
+
otacon scan example.com # one-shot scan, all signals
|
|
180
|
+
otacon scan example.com --no-http # DNS only — faster, fewer signals
|
|
181
|
+
otacon scan example.com --all # include unregistered variants
|
|
182
|
+
otacon scan example.com --concurrency 100 # crank concurrency (default 50)
|
|
183
|
+
otacon scan example.com --exclude "alias.com,brand.io"
|
|
184
|
+
otacon scan example.com --exclude-file allowed.txt
|
|
185
|
+
otacon scan example.com --json r.json --html r.html --markdown r.md --csv r.csv
|
|
186
|
+
otacon scan example.com --fail-on high # CI gate — exit 2 on high/critical
|
|
187
|
+
otacon scan example.com --weights-file weights.json # custom scoring weights
|
|
188
|
+
|
|
189
|
+
# Scan multiple domains (sequential)
|
|
190
|
+
otacon scan github.com google.com example.com
|
|
191
|
+
|
|
192
|
+
# Scan from a file (one domain per line, # = comment)
|
|
193
|
+
otacon scan --domains-file brands.txt
|
|
194
|
+
|
|
195
|
+
# Combine: extra domains alongside a file
|
|
196
|
+
otacon scan mycompany.com --domains-file more_brands.txt
|
|
197
|
+
|
|
198
|
+
# Aggregated HTML report for all domains
|
|
199
|
+
otacon scan github.com google.com --html report.html
|
|
200
|
+
|
|
201
|
+
# Aggregated JSON export
|
|
202
|
+
otacon scan --domains-file brands.txt --json results.json
|
|
203
|
+
|
|
204
|
+
# Fail CI if any domain has a critical hit
|
|
205
|
+
otacon scan --domains-file brands.txt --fail-on critical
|
|
206
|
+
|
|
207
|
+
otacon generate example.com -o variants.txt # wordlist only, no network
|
|
208
|
+
otacon --debug scan example.com # global flags precede the subcommand
|
|
209
|
+
otacon --version # print version and exit
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### CLI flags reference
|
|
213
|
+
|
|
214
|
+
> **Placement matters:** `--debug`/`--quiet`/`--version` belong to `otacon` itself and must come
|
|
215
|
+
> **before** the subcommand (`otacon --debug scan example.com`). Every other flag below belongs to
|
|
216
|
+
> `scan` and comes **after** it (`otacon scan example.com --fail-on high`).
|
|
217
|
+
|
|
218
|
+
**Global options** (before the subcommand):
|
|
219
|
+
|
|
220
|
+
| Flag | Default | Description |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `--debug`, `-v`, `--verbose` | off | Log DNS/WHOIS/HTTP graceful-degradation events to stderr. |
|
|
223
|
+
| `--quiet`, `-q` | off | Disable UI/banner/progress; print JSON to stdout. |
|
|
224
|
+
| `-V`, `--version` | — | Print version and exit. |
|
|
225
|
+
|
|
226
|
+
**`scan` options** (after `otacon scan`):
|
|
227
|
+
|
|
228
|
+
| Flag | Default | Description |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| `--no-http` | off | Skip HTTP & TLS probing. DNS+MX+WHOIS only. Disables the ⚑ defensive flag. |
|
|
231
|
+
| `--all` | off | Show unregistered variants in the output. |
|
|
232
|
+
| `-c`, `--concurrency` | `50` | Max concurrent DNS/HTTP checks. Raise carefully — your resolver may rate-limit. |
|
|
233
|
+
| `-x`, `--exclude` | — | Comma-separated whitelist: `--exclude "alias.com,brand.io"` |
|
|
234
|
+
| `--exclude-file` | — | Path to a file with one domain per line. `#` starts a comment. |
|
|
235
|
+
| `--domains-file`, `-D` | — | File with one domain per line (`#` = comment) — scan many targets in one run. |
|
|
236
|
+
| `--json` | — | Write the full report (every variant, every signal, every reason) to a file. |
|
|
237
|
+
| `--markdown` / `--md` | — | Write a Markdown table ready to paste into a ticket. |
|
|
238
|
+
| `--html` | — | Write a self-contained dark-theme HTML report. |
|
|
239
|
+
| `--csv` | — | Write a CSV report of registered domains (spreadsheet-safe, CWE-1236 hardened). |
|
|
240
|
+
| `--fail-on` | — | `low` / `medium` / `high` / `critical` — exit `2` when any registered variant reaches this level. |
|
|
241
|
+
| `-w`, `--weights-file` | — | Path to a JSON file overriding default scoring weights (see below). |
|
|
242
|
+
|
|
243
|
+
<details>
|
|
244
|
+
<summary><b>Custom scoring weights (<code>--weights-file</code>)</b></summary>
|
|
245
|
+
|
|
246
|
+
Override any subset of the default point values without touching `scoring.py`. Unspecified keys keep their default:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"points_mx": 30,
|
|
251
|
+
"points_ssl_fresh": 15,
|
|
252
|
+
"kind_base": { "homoglyph": 30 }
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
otacon scan example.com --weights-file weights.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
</details>
|
|
261
|
+
|
|
262
|
+
**`generate` options** (after `otacon generate`):
|
|
263
|
+
|
|
264
|
+
| Flag | Default | Description |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `-n`, `--limit` | `0` (all) | Print only the first N variants. The file written by `--output` always contains every variant. |
|
|
267
|
+
| `-o`, `--output` | — | Write the variants, one per line, to a file — a wordlist for `subfinder`, `nuclei`, `ffuf`, etc. |
|
|
268
|
+
| `-x`, `--exclude` | — | Comma-separated whitelist, same syntax as `scan`. |
|
|
269
|
+
| `--exclude-file` | — | Path to a whitelist file, one domain per line. |
|
|
270
|
+
|
|
271
|
+
### Exit codes (for CI gating)
|
|
272
|
+
|
|
273
|
+
| Code | Meaning |
|
|
274
|
+
|---|---|
|
|
275
|
+
| `0` | Clean — nothing at/above the `--fail-on` threshold |
|
|
276
|
+
| `1` | Runtime error (bad input, empty domain, file I/O error) |
|
|
277
|
+
| `2` | Threshold breached — at least one registered variant met `--fail-on` |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Modes
|
|
282
|
+
|
|
283
|
+
Otacon has two subcommands plus an interactive guided mode:
|
|
284
|
+
|
|
285
|
+
| Mode | Network? | Use when |
|
|
286
|
+
|---|---|---|
|
|
287
|
+
| `scan` | yes | One-shot audit. Most common. |
|
|
288
|
+
| `generate` | **no** | Offline wordlist generation. Useful for feeding into external tooling (`subfinder`, `nuclei`, etc.) or sanity-checking what Otacon *would* check. |
|
|
289
|
+
| *(no subcommand)* | yes | **Interactive mode** — guided prompts, then a post-scan action loop (open in browser, WHOIS, rescan, allow-list). |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Output formats
|
|
294
|
+
|
|
295
|
+
One scan, five ways to consume it. Pick the one that fits your workflow:
|
|
296
|
+
|
|
297
|
+
| Format | Flag | Best for |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| **Rich terminal table** | *(default)* | Interactive triage — colors, risk bars, live streaming as hits arrive |
|
|
300
|
+
| **JSON** | `--json r.json` | Pipelines, SIEM ingestion, custom analytics. Includes `risk_reasons` for every variant. |
|
|
301
|
+
| **Markdown** | `--md r.md` | Paste straight into Jira / GitHub issues / Slack |
|
|
302
|
+
| **HTML** | `--html r.html` | Hand to legal / compliance / management. Self-contained dark-theme file, no external dependencies. |
|
|
303
|
+
| **CSV** | `--csv r.csv` | Spreadsheets — registered domains only, formula-injection-safe cells |
|
|
304
|
+
|
|
305
|
+
You can pass all export flags at once — every format is rendered from the same in-memory report, so they always agree.
|
|
306
|
+
|
|
307
|
+
### JSON structure (excerpt)
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"target": "github.com",
|
|
312
|
+
"started_at": "2026-06-08T14:23:01+00:00",
|
|
313
|
+
"total_permutations": 143,
|
|
314
|
+
"results": [
|
|
315
|
+
{
|
|
316
|
+
"domain": "githubupdate.com",
|
|
317
|
+
"kind": "combosquat",
|
|
318
|
+
"resolves": true,
|
|
319
|
+
"has_mx": true,
|
|
320
|
+
"has_ssl": true,
|
|
321
|
+
"http_status": 200,
|
|
322
|
+
"page_title": "GitHub - Security Update Required",
|
|
323
|
+
"created_at": "2026-06-05T09:12:00+00:00",
|
|
324
|
+
"age_days": 3,
|
|
325
|
+
"risk_score": 92,
|
|
326
|
+
"risk_level": "critical",
|
|
327
|
+
"risk_reasons": [
|
|
328
|
+
"technique: combosquat (+20)",
|
|
329
|
+
"resolves to an IP (+10)",
|
|
330
|
+
"has an MX record — ready for email phishing (+25)",
|
|
331
|
+
"active SSL certificate (+15)",
|
|
332
|
+
"responds HTTP 200 — active site (+15)",
|
|
333
|
+
"registered 3 days ago (+20)"
|
|
334
|
+
],
|
|
335
|
+
"is_likely_defensive": false
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Every score is fully decomposed — you can always answer *"why did this get 92?"*.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## How scoring works
|
|
346
|
+
|
|
347
|
+
Every score is the sum of **explicit, explainable signals** — no ML, no black box. Every reason is exposed in the JSON export (`risk_reasons`) and in the interactive detail view.
|
|
348
|
+
|
|
349
|
+
<details open>
|
|
350
|
+
<summary><b>Signal point values</b></summary>
|
|
351
|
+
|
|
352
|
+
| Signal | Points |
|
|
353
|
+
|---|---|
|
|
354
|
+
| **MX record** — ready for email phishing | +25 |
|
|
355
|
+
| **Technique** — homoglyph / IDN | +25 |
|
|
356
|
+
| subdomain spoof | +22 |
|
|
357
|
+
| combosquat · www-merge | +20 / +20 |
|
|
358
|
+
| typo | +18 |
|
|
359
|
+
| soundsquat | +16 |
|
|
360
|
+
| bitsquat · vowel-swap | +15 / +14 |
|
|
361
|
+
| hyphenation · plural · TLD-swap | +12 / +10 / +10 |
|
|
362
|
+
| **Domain age** — <7 days | +20 |
|
|
363
|
+
| <30 days · <90 days | +12 / +5 |
|
|
364
|
+
| **SSL** certificate active | +15 |
|
|
365
|
+
| **HTTP** 2xx live · 3xx redirect | +15 / +10 |
|
|
366
|
+
| 4xx · 5xx | +5 / +3 |
|
|
367
|
+
| **Resolves** to an IP | +10 |
|
|
368
|
+
| **Redirects** elsewhere (non-2xx, non-3xx) | +5 |
|
|
369
|
+
|
|
370
|
+
Score is capped at 100. Unregistered domains always score 0.
|
|
371
|
+
|
|
372
|
+
</details>
|
|
373
|
+
|
|
374
|
+
### Risk levels
|
|
375
|
+
|
|
376
|
+
| Level | Score | Meaning |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| 🔴 **critical** | 80–100 | Active infrastructure + email-ready — treat as a live threat. Investigate immediately. |
|
|
379
|
+
| 🟠 **high** | 60–79 | Registered with serious signals (MX or live site). Add to monitoring; consider takedown. |
|
|
380
|
+
| 🟡 **medium** | 35–59 | Registered, some signals — worth watching. |
|
|
381
|
+
| 🔵 **low** | 15–34 | Registered, minimal signals. Could be parked / unused. |
|
|
382
|
+
| 🟢 **safe** | 0–14 | Unregistered or negligible. |
|
|
383
|
+
|
|
384
|
+
> 📐 Full architecture, pipeline, and design rationale → [`docs/DESIGN.md`](docs/DESIGN.md)
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Detection techniques
|
|
389
|
+
|
|
390
|
+
Otacon implements **12 permutation techniques** modeled on real-world attacks. The
|
|
391
|
+
homoglyph table is cross-checked against Unicode's own
|
|
392
|
+
[confusables.txt](https://www.unicode.org/Public/security/latest/confusables.txt)
|
|
393
|
+
so every look-alike character is a documented substitution, not a guess — it
|
|
394
|
+
covers all 26 letters, not just the handful that are easy to eyeball.
|
|
395
|
+
|
|
396
|
+
| Technique | Example (`example.com`) | Real attack vector |
|
|
397
|
+
|---|---|---|
|
|
398
|
+
| **Homoglyph** | `examp1e.com`, `ex4mple.com` | Visual identity — humans can't tell the difference |
|
|
399
|
+
| **IDN / Punycode** | `xn--exampe-7db.com` *(`l` → ł)* | ACE-encoded unicode that browsers may render natively |
|
|
400
|
+
| **Typo** | `exmple.com`, `exsmple.com`, `exampel.com` | Fat-finger typing on QWERTY keyboards |
|
|
401
|
+
| **Combosquat** | `example-login.com`, `secureexample.com` | Adds "trust" keyword — common in phishing email links |
|
|
402
|
+
| **TLD swap** | `example.io`, `example.top`, `example.icu` | Same name, different (often cheap/abused) TLD |
|
|
403
|
+
| **Subdomain spoof** | `example.com.login.net` | Original domain as a label; URL-bar trickery |
|
|
404
|
+
| **Bitsquat** | `axample.com` (`e`→`a` is one bit flip) | DRAM/DNS memory errors flip a single bit |
|
|
405
|
+
| **Hyphenation** | `ex-ample.com` | Insert/remove a hyphen |
|
|
406
|
+
| **Soundsquat** | `eksample.com` | Phonetic substitution (ph/f, c/k, s/z, x/ks) |
|
|
407
|
+
| **Vowel swap** | `exomple.com`, `exumple.com` | Replace one vowel with another |
|
|
408
|
+
| **Plural** | `shops.com` ← `shop.com` | Singular ↔ plural variation |
|
|
409
|
+
| **WWW-merge** | `wwwexample.com` | Dot dropped between "www" and the domain — easy to misread |
|
|
410
|
+
|
|
411
|
+
Every example above is real output, not an illustration. Two details worth
|
|
412
|
+
knowing before you read a report:
|
|
413
|
+
|
|
414
|
+
- **Unicode look-alikes are emitted as punycode.** Swapping the `l` in
|
|
415
|
+
`example.com` for a Polish `ł` is reported as `xn--exampe-7db.com`, because
|
|
416
|
+
that is the name DNS actually resolves and the form you will see in logs.
|
|
417
|
+
Those land under **IDN**, which leaves the **Homoglyph** rows for the ASCII
|
|
418
|
+
confusables (`1`/`l`, `4`/`a`, `rn`/`m`).
|
|
419
|
+
- **Each variant is reported once, under the first technique that produced it.**
|
|
420
|
+
Techniques overlap, and the priority order is the one in this table. That is
|
|
421
|
+
why `examples.com` is labelled a typo rather than a plural for a target like
|
|
422
|
+
`example.com`: `s` sits next to `e` on QWERTY, so the typo generator reaches
|
|
423
|
+
it first. On `shop.com`, where no adjacent key produces it, `shops.com`
|
|
424
|
+
comes through as a plural.
|
|
425
|
+
|
|
426
|
+
The generator deduplicates results and **never includes the original domain** in the output.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## CI/CD integration
|
|
431
|
+
|
|
432
|
+
Add a brand-protection gate to your release pipeline. If a critical impersonation goes live, the pipeline fails.
|
|
433
|
+
|
|
434
|
+
### GitHub Actions
|
|
435
|
+
|
|
436
|
+
```yaml
|
|
437
|
+
name: brand-protection
|
|
438
|
+
on:
|
|
439
|
+
schedule:
|
|
440
|
+
- cron: "0 6 * * *" # daily at 06:00 UTC
|
|
441
|
+
workflow_dispatch:
|
|
442
|
+
|
|
443
|
+
jobs:
|
|
444
|
+
scan:
|
|
445
|
+
runs-on: ubuntu-latest
|
|
446
|
+
steps:
|
|
447
|
+
- uses: actions/setup-python@v5
|
|
448
|
+
with:
|
|
449
|
+
python-version: "3.12"
|
|
450
|
+
- run: sudo apt-get install -y libc-ares-dev
|
|
451
|
+
- run: pip install git+https://github.com/notimeftnoir/otacon.git
|
|
452
|
+
- run: otacon scan example.com --json report.json --fail-on critical
|
|
453
|
+
- if: failure()
|
|
454
|
+
uses: actions/upload-artifact@v4
|
|
455
|
+
with:
|
|
456
|
+
name: otacon-report
|
|
457
|
+
path: report.json
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### GitLab CI
|
|
461
|
+
|
|
462
|
+
```yaml
|
|
463
|
+
brand-protection:
|
|
464
|
+
image: python:3.12
|
|
465
|
+
before_script:
|
|
466
|
+
- apt-get update && apt-get install -y libc-ares-dev
|
|
467
|
+
- pip install git+https://github.com/notimeftnoir/otacon.git
|
|
468
|
+
script:
|
|
469
|
+
- otacon scan $CI_PROJECT_NAME.com --html report.html --fail-on high
|
|
470
|
+
artifacts:
|
|
471
|
+
when: always
|
|
472
|
+
paths: [report.html]
|
|
473
|
+
only: { refs: [schedules] }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Interactive mode
|
|
479
|
+
|
|
480
|
+
Run `otacon` with no subcommand for a guided experience. After the scan, you can act on each registered domain individually:
|
|
481
|
+
|
|
482
|
+
```text
|
|
483
|
+
Action for githubupdate.com:
|
|
484
|
+
[*] [o]pen — open in browser
|
|
485
|
+
[w]hois — show registration info
|
|
486
|
+
[e]xport — save result as JSON
|
|
487
|
+
[a]llow — skip in this session
|
|
488
|
+
[r]escan — re-check this domain now
|
|
489
|
+
[b]ack — pick a different domain
|
|
490
|
+
[q]uit — exit actions
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Designed for triaging a fresh scan without leaving the terminal: open the suspicious site, check WHOIS, decide to allow-list or escalate.
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Whitelist / defensive flag
|
|
498
|
+
|
|
499
|
+
Brands often own their own lookalikes defensively (e.g., `google.com` owns `gooogle.com` and redirects it). Otacon flags these with ⚑ when the redirect points back to the original:
|
|
500
|
+
|
|
501
|
+
```text
|
|
502
|
+
microsft.com ⚑ → microsoft.com crit(85) — but defensive
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
After the scan, Otacon offers to write all ⚑-flagged domains to `whitelist.txt`. Future runs in the same directory pick this file up automatically; you can also point at a custom file with `--exclude-file path/to/list.txt`.
|
|
506
|
+
|
|
507
|
+
Whitelist file format:
|
|
508
|
+
|
|
509
|
+
```text
|
|
510
|
+
# defensive registrations, owned by us
|
|
511
|
+
gooogle.com
|
|
512
|
+
goggle.com
|
|
513
|
+
g00gle.com
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Whitelisted domains are skipped before any network call — saving both time and quota.
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Comparison with dnstwist
|
|
521
|
+
|
|
522
|
+
[`dnstwist`](https://github.com/elceef/dnstwist) is the OG tool in this space. Otacon and dnstwist solve overlapping problems with different priorities.
|
|
523
|
+
|
|
524
|
+
| | **Otacon** | **dnstwist** |
|
|
525
|
+
|---|---|---|
|
|
526
|
+
| Risk **score** with explained signals | ✓ 0–100, every point sourced | ✗ raw signals only |
|
|
527
|
+
| Defensive-registration flag | ✓ ⚑ on redirect-to-original | ✗ |
|
|
528
|
+
| CI/CD exit code gating | ✓ `--fail-on` | ✗ |
|
|
529
|
+
| Self-contained HTML report | ✓ dark theme, no JS | partial (`--format html`) |
|
|
530
|
+
| Interactive post-scan triage | ✓ open/whois/rescan/allow | ✗ |
|
|
531
|
+
| Permutation techniques | 12 | 13+ |
|
|
532
|
+
| Visual screenshots of pages | ✗ | ✓ |
|
|
533
|
+
| Fuzzy/phonetic dictionary attacks | ✓ soundsquat | ✓ |
|
|
534
|
+
| GeoIP / Whois enrichment | WHOIS only | both |
|
|
535
|
+
|
|
536
|
+
**Use dnstwist if** you want screenshots, fuzzy hashing, deeper enrichment.
|
|
537
|
+
**Use Otacon if** you want an opinionated risk score, defensive-flag detection, and a CI-friendly exit code.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## FAQ
|
|
542
|
+
|
|
543
|
+
<details>
|
|
544
|
+
<summary><b>Is this legal? Does it touch the target domains?</b></summary>
|
|
545
|
+
|
|
546
|
+
Otacon performs only **passive recon**: standard DNS queries, a TLS handshake on :443 (no data exchange), and a single HTTP GET. No login attempts, no scanning, no scraping at scale. This is the same level of activity as visiting the page in a browser.
|
|
547
|
+
|
|
548
|
+
That said, use it on **your own domains** or **within authorized engagements**. See `LICENSE` and `SECURITY.md`.
|
|
549
|
+
|
|
550
|
+
</details>
|
|
551
|
+
|
|
552
|
+
<details>
|
|
553
|
+
<summary><b>Why no machine learning?</b></summary>
|
|
554
|
+
|
|
555
|
+
Because pentesters and SOC analysts need to *defend* their findings. "Our model gave it 87" is not a defensible answer. `risk_reasons` is. Every score in Otacon comes with the exact list of signals that produced it — auditable in five seconds, tunable in a one-line edit to `scoring.py`.
|
|
556
|
+
|
|
557
|
+
</details>
|
|
558
|
+
|
|
559
|
+
<details>
|
|
560
|
+
<summary><b>How fast is it?</b></summary>
|
|
561
|
+
|
|
562
|
+
A full scan with HTTP probing on a 150-permutation domain typically completes in **8–15 seconds** on a residential connection. DNS-only mode (`--no-http`) is roughly **3× faster**.
|
|
563
|
+
|
|
564
|
+
Bottlenecks are usually:
|
|
565
|
+
1. WHOIS query rate-limiting (we cap at 4 concurrent)
|
|
566
|
+
2. DNS resolver latency (default `--concurrency 50` is conservative; raise it on a server with a fast resolver)
|
|
567
|
+
3. The `c-ares` library — make sure it's installed natively, not falling back to Python's stdlib resolver
|
|
568
|
+
|
|
569
|
+
</details>
|
|
570
|
+
|
|
571
|
+
<details>
|
|
572
|
+
<summary><b>Will it find IDN homoglyph attacks (xn-- domains)?</b></summary>
|
|
573
|
+
|
|
574
|
+
Yes. The `IDN` technique generates punycode-encoded variants for each Unicode homoglyph. They show up as `xn--...` in the table. Score base is +25, same as homoglyph — these are the most dangerous because they render as the original glyph in most browsers.
|
|
575
|
+
|
|
576
|
+
</details>
|
|
577
|
+
|
|
578
|
+
<details>
|
|
579
|
+
<summary><b>Does it work on internationalized domains (non-ASCII targets)?</b></summary>
|
|
580
|
+
|
|
581
|
+
Partially. Otacon accepts unicode input but the permutation engine is tuned for ASCII labels. IDN/punycode encoding works for *output* (generated homoglyphs of an ASCII original). True i18n of the engine is on the roadmap.
|
|
582
|
+
|
|
583
|
+
</details>
|
|
584
|
+
|
|
585
|
+
<details>
|
|
586
|
+
<summary><b>Why does my scan show 0 results when I know there are fakes?</b></summary>
|
|
587
|
+
|
|
588
|
+
Most likely causes, in order:
|
|
589
|
+
|
|
590
|
+
1. **DNS-only mode missed them** — with `--no-http`, you only see variants that resolve. Try a full scan.
|
|
591
|
+
2. **They're behind Cloudflare / a CDN** — they resolve but the SSL/HTTP probe times out. Increasing `--concurrency` doesn't help; raise the per-request timeout in `resolver.py` if it's a recurring issue.
|
|
592
|
+
3. **Your DNS resolver is rate-limiting** — try with a different resolver or lower `--concurrency`.
|
|
593
|
+
4. **The fakes are on a TLD not in our default list** — open an issue with the TLD.
|
|
594
|
+
|
|
595
|
+
</details>
|
|
596
|
+
|
|
597
|
+
<details>
|
|
598
|
+
<summary><b>Where is the WHOIS data coming from?</b></summary>
|
|
599
|
+
|
|
600
|
+
We use [`asyncwhois`](https://pypi.org/project/asyncwhois/), which talks directly to TLD WHOIS servers (no third-party API, no quota). Some TLDs (e.g., `.ai`, `.io`) sometimes return rate-limited or stripped responses — in that case `age_days` will be `null` and the age-based scoring contribution is just skipped (graceful degradation).
|
|
601
|
+
|
|
602
|
+
</details>
|
|
603
|
+
|
|
604
|
+
<details>
|
|
605
|
+
<summary><b>How do I add my own permutation technique?</b></summary>
|
|
606
|
+
|
|
607
|
+
1. Add a value to the `PermutationType` enum in `models.py`
|
|
608
|
+
2. Add a `_my_technique(label: str) -> set[str]` function in `permutations.py`
|
|
609
|
+
3. Add it to the pipeline list in `generate()` — order matters for dedup priority
|
|
610
|
+
4. Add a base score in `scoring._KIND_BASE`
|
|
611
|
+
5. Open a PR with tests in `tests/test_permutations.py`
|
|
612
|
+
|
|
613
|
+
</details>
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## Troubleshooting
|
|
618
|
+
|
|
619
|
+
| Symptom | Likely cause | Fix |
|
|
620
|
+
|---|---|---|
|
|
621
|
+
| `ModuleNotFoundError: aiodns` on install | `c-ares` system library missing | `apt install libc-ares-dev` / `brew install c-ares`, then reinstall |
|
|
622
|
+
| Scan hangs at "Checking variants" | DNS resolver unreachable or rate-limiting | Lower `--concurrency`, switch resolver (`1.1.1.1`, `8.8.8.8`) |
|
|
623
|
+
| WHOIS always `—` for `.ai` / `.io` / `.pl` | Registry WHOIS rate-limit | Re-run later; this is normal |
|
|
624
|
+
| Windows: `ConnectionResetError [WinError 10054]` | Should be auto-fixed in 1.0+ | Ensure you're on the latest version; this used the Proactor loop, we've switched to Selector on Windows |
|
|
625
|
+
| `Unverified HTTPS request` warnings | Suppressed intentionally — we probe bad certs on purpose | n/a, hidden by default |
|
|
626
|
+
|
|
627
|
+
If something still doesn't work, please [open an issue](https://github.com/notimeftnoir/otacon/issues) with:
|
|
628
|
+
|
|
629
|
+
- Otacon version (`otacon --version`)
|
|
630
|
+
- Python version, OS, and architecture
|
|
631
|
+
- The full command and (if safe to share) the target
|
|
632
|
+
- The error message or unexpected behavior
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Contributing & development
|
|
637
|
+
|
|
638
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the dev setup, lint, and test commands.
|
|
639
|
+
|
|
640
|
+
Quick summary:
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
git clone https://github.com/notimeftnoir/otacon.git
|
|
644
|
+
cd otacon
|
|
645
|
+
python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate
|
|
646
|
+
pip install -e ".[dev]"
|
|
647
|
+
pytest && ruff check .
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
Architecture deep-dive: [`docs/DESIGN.md`](docs/DESIGN.md).
|
|
651
|
+
|
|
652
|
+
Security disclosure policy: [`SECURITY.md`](SECURITY.md).
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## License & ethics
|
|
657
|
+
|
|
658
|
+
**MIT License.** Use it freely.
|
|
659
|
+
|
|
660
|
+
Otacon is **passive only** — DNS queries, a TLS handshake, a single HTTP GET per variant. No exploit attempts, no brute-forcing, no auth.
|
|
661
|
+
|
|
662
|
+
**Use only on:**
|
|
663
|
+
- Domains you own
|
|
664
|
+
- Domains within an authorized security testing engagement (with written scope)
|
|
665
|
+
- Domains you have explicit permission to monitor
|
|
666
|
+
|
|
667
|
+
**Do not use to:** harass, dox, or build attack tooling. If you found this useful for a defense engagement, [say hi](https://github.com/notimeftnoir/otacon/issues) — feedback shapes the roadmap.
|