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.
Files changed (36) hide show
  1. otacon-1.0.0/LICENSE +21 -0
  2. otacon-1.0.0/PKG-INFO +667 -0
  3. otacon-1.0.0/README.md +616 -0
  4. otacon-1.0.0/pyproject.toml +125 -0
  5. otacon-1.0.0/setup.cfg +4 -0
  6. otacon-1.0.0/src/otacon/__init__.py +11 -0
  7. otacon-1.0.0/src/otacon/__main__.py +3 -0
  8. otacon-1.0.0/src/otacon/_asyncutils.py +35 -0
  9. otacon-1.0.0/src/otacon/_scanner.py +101 -0
  10. otacon-1.0.0/src/otacon/_validate.py +146 -0
  11. otacon-1.0.0/src/otacon/cli.py +492 -0
  12. otacon-1.0.0/src/otacon/html_report.py +373 -0
  13. otacon-1.0.0/src/otacon/interactive.py +521 -0
  14. otacon-1.0.0/src/otacon/models.py +111 -0
  15. otacon-1.0.0/src/otacon/permutations.py +556 -0
  16. otacon-1.0.0/src/otacon/py.typed +0 -0
  17. otacon-1.0.0/src/otacon/reporters.py +480 -0
  18. otacon-1.0.0/src/otacon/resolver.py +484 -0
  19. otacon-1.0.0/src/otacon/scoring.py +299 -0
  20. otacon-1.0.0/src/otacon/theme.py +92 -0
  21. otacon-1.0.0/src/otacon/whois.py +68 -0
  22. otacon-1.0.0/src/otacon.egg-info/PKG-INFO +667 -0
  23. otacon-1.0.0/src/otacon.egg-info/SOURCES.txt +34 -0
  24. otacon-1.0.0/src/otacon.egg-info/dependency_links.txt +1 -0
  25. otacon-1.0.0/src/otacon.egg-info/entry_points.txt +2 -0
  26. otacon-1.0.0/src/otacon.egg-info/requires.txt +23 -0
  27. otacon-1.0.0/src/otacon.egg-info/top_level.txt +1 -0
  28. otacon-1.0.0/tests/test_cli.py +831 -0
  29. otacon-1.0.0/tests/test_html_report.py +364 -0
  30. otacon-1.0.0/tests/test_interactive.py +881 -0
  31. otacon-1.0.0/tests/test_permutations.py +390 -0
  32. otacon-1.0.0/tests/test_reporters.py +850 -0
  33. otacon-1.0.0/tests/test_resolver.py +830 -0
  34. otacon-1.0.0/tests/test_scoring.py +517 -0
  35. otacon-1.0.0/tests/test_validate.py +215 -0
  36. 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
+ | &nbsp;&nbsp;subdomain spoof | +22 |
357
+ | &nbsp;&nbsp;combosquat · www-merge | +20 / +20 |
358
+ | &nbsp;&nbsp;typo | +18 |
359
+ | &nbsp;&nbsp;soundsquat | +16 |
360
+ | &nbsp;&nbsp;bitsquat · vowel-swap | +15 / +14 |
361
+ | &nbsp;&nbsp;hyphenation · plural · TLD-swap | +12 / +10 / +10 |
362
+ | **Domain age** — &lt;7 days | +20 |
363
+ | &nbsp;&nbsp;&lt;30 days · &lt;90 days | +12 / +5 |
364
+ | **SSL** certificate active | +15 |
365
+ | **HTTP** 2xx live · 3xx redirect | +15 / +10 |
366
+ | &nbsp;&nbsp;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.