offsec-ai 2.0.1__tar.gz → 2.0.2__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.
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/CHANGELOG.md +16 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/PKG-INFO +30 -6
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/README.md +24 -3
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/DOCKER.md +109 -9
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/quickstart.md +6 -6
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/pyproject.toml +6 -3
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/__init__.py +1 -1
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/hybrid_identity_checker.py +1 -1
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/l7_detector.py +1 -1
- offsec_ai-2.0.2/tests/test_mtls.py +102 -0
- offsec_ai-2.0.2/tests/test_mtls_integration.py +82 -0
- offsec_ai-2.0.1/tests/test_dns_trace.py +0 -85
- offsec_ai-2.0.1/tests/test_mtls.py +0 -159
- offsec_ai-2.0.1/tests/test_mtls_integration.py +0 -193
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/.gitignore +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/CONTRIBUTING.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/LICENSE +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/SECURITY.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/api.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/assets/logo.svg +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/azure-ad-flow-explained.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/hybrid-identity.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/docs/owasp-scanner.md +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/examples/comprehensive_examples.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/examples/mtls_examples.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/examples/owasp_scan_examples.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/examples/usage_examples.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/__main__.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/cli.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/__init__.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/ai_owasp_scanner.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/cert_analyzer.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/llm_judge.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/mcp_attacker.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/mcp_scanner.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/mtls_checker.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/owasp_scanner.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/port_scanner.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/core/security_headers.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/__init__.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/ai_owasp_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/l7_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/mcp_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/mtls_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/owasp_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/models/scan_result.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/py.typed +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/__init__.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/ai_owasp_payloads.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/ai_owasp_remediation.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/common_ports.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/exporters.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/l7_signatures.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/mcp_cve_db.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/mcp_payloads.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/src/offsec_ai/utils/owasp_remediation.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/tests/conftest.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/tests/test_ai_owasp.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/tests/test_mcp_attacker.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/tests/test_mcp_scanner.py +0 -0
- {offsec_ai-2.0.1 → offsec_ai-2.0.2}/tests/test_port_scanner.py +0 -0
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.2] - 2026-06-29
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- README logo replaced with inline ASCII art (no external image dependency)
|
|
12
|
+
- User-Agent strings updated from `SimplePortChecker/1.0` to `offsec-ai/2.0` in `L7Detector` and `HybridIdentityChecker`
|
|
13
|
+
- `reportlab>=4.0.0` promoted to core dependency in `pyproject.toml` (was missing, caused `ModuleNotFoundError` on fresh installs)
|
|
14
|
+
- Docker image now installs `[ai]` extras (`openai`, `anthropic`) so LLM judge works at runtime via env vars
|
|
15
|
+
- Dockerfile redundant pre-installs removed; `pip install ".[ai]"` is now the single install step
|
|
16
|
+
- `docker-push` and `docker-release` Makefile targets added with auto-versioning from `pyproject.toml`
|
|
17
|
+
- CI/CD: `docker.yml` image labels updated (title, description); unused `build-args` removed
|
|
18
|
+
- CI/CD: `publish.yml` PyPI environment URL no longer pins a hardcoded version
|
|
19
|
+
- Stale files removed: `PROJECT_STRUCTURE.md`, `HYBRID_IDENTITY_QUICKREF.md`, `IMPLEMENTATION_HYBRID_IDENTITY.md`, `MANIFEST.in`, empty `scripts/` dir
|
|
20
|
+
- `tests/test_dns_trace.py` removed (was a live-network script, not a pytest test)
|
|
21
|
+
- `tests/test_mtls.py` and `tests/test_mtls_integration.py` rewritten as proper `assert`-based pytest tests
|
|
22
|
+
- `setup_dev.sh` updated: branding, venv path (`.venv`), and `[ai]` extras
|
|
23
|
+
|
|
8
24
|
## [2.0.0] - 2026-06-29
|
|
9
25
|
|
|
10
26
|
### Added — AI / LLM Security (fresh start as `offsec-ai`)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: offsec-ai
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Offensive-security toolkit: port scanning, L7/WAF detection, mTLS, certificate analysis, OWASP Top 10, AI/LLM OWASP Top 10 black-box probing, and MCP endpoint security scanning
|
|
5
5
|
Project-URL: Homepage, https://github.com/htunn/offsec-ai
|
|
6
6
|
Project-URL: Repository, https://github.com/htunn/offsec-ai
|
|
7
7
|
Project-URL: Issues, https://github.com/htunn/offsec-ai/issues
|
|
8
8
|
Project-URL: Documentation, https://github.com/htunn/offsec-ai#readme
|
|
9
|
-
Author
|
|
10
|
-
|
|
9
|
+
Author: Htunn
|
|
10
|
+
Author-email: htunnthuthu.linux@gmail.com
|
|
11
|
+
Maintainer: Htunn
|
|
12
|
+
Maintainer-email: htunnthuthu.linux@gmail.com
|
|
11
13
|
License: MIT
|
|
12
14
|
License-File: LICENSE
|
|
13
15
|
Keywords: ai,ai-security,certificate,certificate-analysis,firewall,l7-protection,llm,mcp,model-context-protocol,network,offensive-security,owasp,pentest,pki,port-scanner,red-team,security,ssl,tls,waf
|
|
@@ -37,6 +39,7 @@ Requires-Dist: httpx>=0.25.0
|
|
|
37
39
|
Requires-Dist: mcp>=1.0.0
|
|
38
40
|
Requires-Dist: pydantic>=2.0.0
|
|
39
41
|
Requires-Dist: python-nmap>=0.7.1
|
|
42
|
+
Requires-Dist: reportlab>=4.0.0
|
|
40
43
|
Requires-Dist: requests>=2.31.0
|
|
41
44
|
Requires-Dist: rich>=13.0.0
|
|
42
45
|
Provides-Extra: ai
|
|
@@ -56,9 +59,15 @@ Provides-Extra: gemini
|
|
|
56
59
|
Requires-Dist: google-generativeai>=0.7.0; extra == 'gemini'
|
|
57
60
|
Description-Content-Type: text/markdown
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
```
|
|
63
|
+
██████╗ ███████╗███████╗███████╗███████╗ ██████╗ █████╗ ██╗
|
|
64
|
+
██╔═══██╗██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝ ██╔══██╗██║
|
|
65
|
+
██║ ██║█████╗ █████╗ ███████╗█████╗ ██║ █████╗███████║██║
|
|
66
|
+
██║ ██║██╔══╝ ██╔══╝ ╚════██║██╔══╝ ██║ ╚════╝██╔══██║██║
|
|
67
|
+
╚██████╔╝██║ ██║ ███████║███████╗╚██████╗ ██║ ██║██║
|
|
68
|
+
╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝
|
|
69
|
+
Offensive-Security Toolkit · AI/LLM · MCP · Red-Team
|
|
70
|
+
```
|
|
62
71
|
|
|
63
72
|
<p align="center">
|
|
64
73
|
<a href="https://pypi.org/project/offsec-ai/"><img src="https://img.shields.io/pypi/v/offsec-ai" alt="PyPI Version"/></a>
|
|
@@ -554,8 +563,23 @@ docker run --rm htunnthuthu/offsec-ai:latest owasp-scan example.com
|
|
|
554
563
|
docker run --rm -v $(pwd):/app/output htunnthuthu/offsec-ai:latest \
|
|
555
564
|
ai-owasp-scan https://api.example.com/v1/chat/completions \
|
|
556
565
|
--output /app/output/llm-report.json
|
|
566
|
+
|
|
567
|
+
# LLM Judge — openai, anthropic, or gemini key auto-detected; no extra install needed
|
|
568
|
+
docker run --rm \
|
|
569
|
+
-e OPENAI_API_KEY=sk-... \
|
|
570
|
+
htunnthuthu/offsec-ai:latest \
|
|
571
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
572
|
+
|
|
573
|
+
# Custom OpenAI-compatible backend (Ollama, LM Studio, Azure OpenAI…)
|
|
574
|
+
docker run --rm \
|
|
575
|
+
-e OFFSEC_LLM_BASE_URL=http://host.docker.internal:11434/v1 \
|
|
576
|
+
-e OFFSEC_LLM_MODEL=llama3 \
|
|
577
|
+
htunnthuthu/offsec-ai:latest \
|
|
578
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
557
579
|
```
|
|
558
580
|
|
|
581
|
+
See [docs/DOCKER.md](docs/DOCKER.md) for the full Docker reference including CI/CD integration, Kubernetes jobs, Makefile publish targets, and troubleshooting.
|
|
582
|
+
|
|
559
583
|
---
|
|
560
584
|
|
|
561
585
|
## Configuration
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
```
|
|
2
|
+
██████╗ ███████╗███████╗███████╗███████╗ ██████╗ █████╗ ██╗
|
|
3
|
+
██╔═══██╗██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝ ██╔══██╗██║
|
|
4
|
+
██║ ██║█████╗ █████╗ ███████╗█████╗ ██║ █████╗███████║██║
|
|
5
|
+
██║ ██║██╔══╝ ██╔══╝ ╚════██║██╔══╝ ██║ ╚════╝██╔══██║██║
|
|
6
|
+
╚██████╔╝██║ ██║ ███████║███████╗╚██████╗ ██║ ██║██║
|
|
7
|
+
╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝
|
|
8
|
+
Offensive-Security Toolkit · AI/LLM · MCP · Red-Team
|
|
9
|
+
```
|
|
4
10
|
|
|
5
11
|
<p align="center">
|
|
6
12
|
<a href="https://pypi.org/project/offsec-ai/"><img src="https://img.shields.io/pypi/v/offsec-ai" alt="PyPI Version"/></a>
|
|
@@ -496,8 +502,23 @@ docker run --rm htunnthuthu/offsec-ai:latest owasp-scan example.com
|
|
|
496
502
|
docker run --rm -v $(pwd):/app/output htunnthuthu/offsec-ai:latest \
|
|
497
503
|
ai-owasp-scan https://api.example.com/v1/chat/completions \
|
|
498
504
|
--output /app/output/llm-report.json
|
|
505
|
+
|
|
506
|
+
# LLM Judge — openai, anthropic, or gemini key auto-detected; no extra install needed
|
|
507
|
+
docker run --rm \
|
|
508
|
+
-e OPENAI_API_KEY=sk-... \
|
|
509
|
+
htunnthuthu/offsec-ai:latest \
|
|
510
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
511
|
+
|
|
512
|
+
# Custom OpenAI-compatible backend (Ollama, LM Studio, Azure OpenAI…)
|
|
513
|
+
docker run --rm \
|
|
514
|
+
-e OFFSEC_LLM_BASE_URL=http://host.docker.internal:11434/v1 \
|
|
515
|
+
-e OFFSEC_LLM_MODEL=llama3 \
|
|
516
|
+
htunnthuthu/offsec-ai:latest \
|
|
517
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
499
518
|
```
|
|
500
519
|
|
|
520
|
+
See [docs/DOCKER.md](docs/DOCKER.md) for the full Docker reference including CI/CD integration, Kubernetes jobs, Makefile publish targets, and troubleshooting.
|
|
521
|
+
|
|
501
522
|
---
|
|
502
523
|
|
|
503
524
|
## Configuration
|
|
@@ -260,9 +260,73 @@ spec:
|
|
|
260
260
|
- ✅ OCSP and CRL URL extraction for revocation checking
|
|
261
261
|
- ✅ Certificate fingerprint generation (SHA-1, SHA-256)
|
|
262
262
|
|
|
263
|
-
##
|
|
263
|
+
## 🤖 AI / LLM Security Usage
|
|
264
|
+
|
|
265
|
+
The image ships with `openai` and `anthropic` pre-installed (`[ai]` extra). Pass an API key at runtime to enable the **LLM Judge** for smarter semantic vulnerability detection.
|
|
266
|
+
|
|
267
|
+
### AI OWASP Top 10 Scan (rule-based, no key required)
|
|
268
|
+
```bash
|
|
269
|
+
docker run --rm htunnthuthu/offsec-ai:latest \
|
|
270
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### AI OWASP Top 10 Scan with LLM Judge
|
|
264
274
|
|
|
265
|
-
|
|
275
|
+
```bash
|
|
276
|
+
# OpenAI judge
|
|
277
|
+
docker run --rm \
|
|
278
|
+
-e OPENAI_API_KEY=sk-... \
|
|
279
|
+
htunnthuthu/offsec-ai:latest \
|
|
280
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
281
|
+
|
|
282
|
+
# Anthropic judge
|
|
283
|
+
docker run --rm \
|
|
284
|
+
-e ANTHROPIC_API_KEY=sk-ant-... \
|
|
285
|
+
htunnthuthu/offsec-ai:latest \
|
|
286
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
287
|
+
|
|
288
|
+
# Google Gemini judge (no extra package needed)
|
|
289
|
+
docker run --rm \
|
|
290
|
+
-e GEMINI_API_KEY=AIzaSy... \
|
|
291
|
+
htunnthuthu/offsec-ai:latest \
|
|
292
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
293
|
+
|
|
294
|
+
# Custom OpenAI-compatible endpoint (Ollama, LM Studio, Azure OpenAI, etc.)
|
|
295
|
+
docker run --rm \
|
|
296
|
+
-e OFFSEC_LLM_BASE_URL=http://host.docker.internal:11434/v1 \
|
|
297
|
+
-e OFFSEC_LLM_MODEL=llama3 \
|
|
298
|
+
htunnthuthu/offsec-ai:latest \
|
|
299
|
+
ai-owasp-scan https://api.example.com/v1/chat/completions --llm-judge
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### MCP Security Scan
|
|
303
|
+
```bash
|
|
304
|
+
# Scan an MCP endpoint
|
|
305
|
+
docker run --rm htunnthuthu/offsec-ai:latest \
|
|
306
|
+
mcp-scan https://mcp.example.com/mcp
|
|
307
|
+
|
|
308
|
+
# Active MCP attack (requires authorization flag)
|
|
309
|
+
docker run --rm htunnthuthu/offsec-ai:latest \
|
|
310
|
+
mcp-attack https://mcp.example.com/mcp --i-have-authorization --mode deep
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 🔧 Configuration & Environment
|
|
316
|
+
|
|
317
|
+
### LLM / AI Judge Environment Variables
|
|
318
|
+
|
|
319
|
+
| Variable | Provider | Description |
|
|
320
|
+
|----------|----------|-------------|
|
|
321
|
+
| `OPENAI_API_KEY` | OpenAI | Enables OpenAI judge (e.g. `gpt-4o-mini`) |
|
|
322
|
+
| `ANTHROPIC_API_KEY` | Anthropic | Enables Anthropic judge (e.g. `claude-3-haiku`) |
|
|
323
|
+
| `GEMINI_API_KEY` | Google | Enables Gemini judge (e.g. `gemini-1.5-flash`) |
|
|
324
|
+
| `OFFSEC_LLM_BASE_URL` | Any OpenAI-compatible | Use a custom/local endpoint as the judge backend |
|
|
325
|
+
| `OFFSEC_LLM_MODEL` | All | Override the default model name |
|
|
326
|
+
|
|
327
|
+
Provider is **auto-detected** from whichever key is set; `OFFSEC_LLM_BASE_URL` takes precedence over `OPENAI_API_KEY`.
|
|
328
|
+
|
|
329
|
+
### General Environment Variables
|
|
266
330
|
```bash
|
|
267
331
|
# Set timeout for operations
|
|
268
332
|
docker run --rm -e TIMEOUT=30 htunnthuthu/offsec-ai:latest scan example.com
|
|
@@ -294,12 +358,12 @@ docker run --rm -v /host/certs:/app/certs \
|
|
|
294
358
|
## 🔒 Security Features
|
|
295
359
|
|
|
296
360
|
### Non-Root User
|
|
297
|
-
- ✅ Container runs as non-root user `
|
|
361
|
+
- ✅ Container runs as non-root user `appuser` (UID: 1000)
|
|
298
362
|
- ✅ No privileged access required
|
|
299
363
|
- ✅ Minimal attack surface
|
|
300
364
|
|
|
301
365
|
### Minimal Dependencies
|
|
302
|
-
- ✅ Based on
|
|
366
|
+
- ✅ Based on Debian slim for minimal footprint
|
|
303
367
|
- ✅ Only essential packages included
|
|
304
368
|
- ✅ Regular security updates via automated builds
|
|
305
369
|
|
|
@@ -311,17 +375,18 @@ docker run --rm -v /host/certs:/app/certs \
|
|
|
311
375
|
## 🏷️ Image Specifications
|
|
312
376
|
|
|
313
377
|
### Base Image
|
|
314
|
-
- **OS**:
|
|
378
|
+
- **OS**: Debian GNU/Linux 12 Bookworm slim (`python:3.12-slim-bookworm`)
|
|
315
379
|
- **Python**: 3.12+
|
|
316
380
|
- **Architecture**: Multi-arch (AMD64, ARM64)
|
|
317
|
-
- **User**: Non-root (`
|
|
381
|
+
- **User**: Non-root (`appuser:appuser`, UID/GID 1000)
|
|
318
382
|
|
|
319
383
|
### Installed Tools
|
|
320
|
-
- ✅
|
|
384
|
+
- ✅ `offsec-ai` (latest version)
|
|
321
385
|
- ✅ Python runtime and required dependencies
|
|
386
|
+
- ✅ `openai` & `anthropic` packages — bundled via `[ai]` extra; LLM judge activates automatically when you pass an API key env var
|
|
322
387
|
- ✅ SSL/TLS libraries for certificate handling
|
|
323
388
|
- ✅ DNS resolution utilities
|
|
324
|
-
- ✅
|
|
389
|
+
- ✅ `nmap` for port scanning
|
|
325
390
|
- ✅ Cryptography libraries for certificate analysis
|
|
326
391
|
|
|
327
392
|
### Performance
|
|
@@ -430,7 +495,42 @@ docker run --rm --memory=512m htunnthuthu/offsec-ai:latest scan example.com
|
|
|
430
495
|
docker run --rm --cpus=2 htunnthuthu/offsec-ai:latest full-scan example.com
|
|
431
496
|
```
|
|
432
497
|
|
|
433
|
-
##
|
|
498
|
+
## �️ Building & Publishing Locally
|
|
499
|
+
|
|
500
|
+
The `Makefile` provides convenience targets for building and pushing to Docker Hub.
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
# Build the image locally
|
|
504
|
+
make docker-build
|
|
505
|
+
|
|
506
|
+
# Build without cache
|
|
507
|
+
make docker-build-no-cache
|
|
508
|
+
|
|
509
|
+
# Build multi-arch (linux/amd64 + linux/arm64) — requires docker buildx
|
|
510
|
+
make docker-build-multi
|
|
511
|
+
|
|
512
|
+
# Push to Docker Hub (builds first, then tags + pushes :version and :latest)
|
|
513
|
+
make docker-push # uses DOCKER_USERNAME=htunn by default
|
|
514
|
+
make docker-push DOCKER_USERNAME=youruser # override username
|
|
515
|
+
|
|
516
|
+
# Full release in one command
|
|
517
|
+
make docker-release # equivalent to docker-build + docker-push
|
|
518
|
+
|
|
519
|
+
# Test the local image
|
|
520
|
+
make docker-test
|
|
521
|
+
|
|
522
|
+
# Scan image for vulnerabilities (requires trivy)
|
|
523
|
+
make docker-scan
|
|
524
|
+
|
|
525
|
+
# Clean up local Docker artifacts
|
|
526
|
+
make docker-clean
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
The `DOCKER_VERSION` is read automatically from `pyproject.toml`, so the published tags match the package version exactly.
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## �🔗 Related Links
|
|
434
534
|
|
|
435
535
|
- **GitHub Repository**: [Htunn/offsec-ai](https://github.com/Htunn/offsec-ai)
|
|
436
536
|
- **PyPI Package**: [offsec-ai](https://pypi.org/project/offsec-ai/)
|
|
@@ -28,9 +28,8 @@ offsec-ai ai-owasp-scan https://api.example.com/v1/chat/completions \
|
|
|
28
28
|
offsec-ai mcp-scan https://mcp.example.com/mcp
|
|
29
29
|
offsec-ai mcp-scan https://mcp.example.com/mcp --output report.json
|
|
30
30
|
|
|
31
|
-
# MCP attacker (requires --
|
|
32
|
-
offsec-ai mcp-attack https://mcp.example.com/mcp --
|
|
33
|
-
--auth-token "$TOKEN"
|
|
31
|
+
# MCP attacker (requires --i-have-authorization flag)
|
|
32
|
+
offsec-ai mcp-attack https://mcp.example.com/mcp --i-have-authorization
|
|
34
33
|
```
|
|
35
34
|
|
|
36
35
|
### Python API
|
|
@@ -167,6 +166,7 @@ offsec-ai scan example.com --verbose
|
|
|
167
166
|
## Examples
|
|
168
167
|
|
|
169
168
|
See the `examples/` directory for complete usage examples:
|
|
170
|
-
- `usage_examples.py`
|
|
171
|
-
- `
|
|
172
|
-
- `
|
|
169
|
+
- `usage_examples.py` — Core infrastructure scanning examples
|
|
170
|
+
- `comprehensive_examples.py` — All features with detailed output
|
|
171
|
+
- `mtls_examples.py` — mTLS testing and certificate generation
|
|
172
|
+
- `owasp_scan_examples.py` — OWASP Top 10 scanning with PDF/CSV export
|
|
@@ -4,16 +4,18 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "offsec-ai"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.2"
|
|
8
8
|
description = "Offensive-security toolkit: port scanning, L7/WAF detection, mTLS, certificate analysis, OWASP Top 10, AI/LLM OWASP Top 10 black-box probing, and MCP endpoint security scanning"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "
|
|
13
|
+
{name = "Htunn"},
|
|
14
|
+
{email = "htunnthuthu.linux@gmail.com"}
|
|
14
15
|
]
|
|
15
16
|
maintainers = [
|
|
16
|
-
{name = "
|
|
17
|
+
{name = "Htunn"},
|
|
18
|
+
{email = "htunnthuthu.linux@gmail.com"}
|
|
17
19
|
]
|
|
18
20
|
keywords = ["firewall", "port-scanner", "l7-protection", "waf", "network", "security", "ssl", "tls", "certificate", "certificate-analysis", "pki", "offensive-security", "red-team", "pentest", "ai", "llm", "mcp", "owasp", "ai-security", "model-context-protocol"]
|
|
19
21
|
classifiers = [
|
|
@@ -46,6 +48,7 @@ dependencies = [
|
|
|
46
48
|
"cryptography>=41.0.0",
|
|
47
49
|
"mcp>=1.0.0",
|
|
48
50
|
"httpx>=0.25.0",
|
|
51
|
+
"reportlab>=4.0.0",
|
|
49
52
|
]
|
|
50
53
|
|
|
51
54
|
[project.optional-dependencies]
|
|
@@ -91,7 +91,7 @@ class HybridIdentityChecker:
|
|
|
91
91
|
timeout: Request timeout in seconds
|
|
92
92
|
"""
|
|
93
93
|
self.timeout = timeout
|
|
94
|
-
self.user_agent = "
|
|
94
|
+
self.user_agent = "offsec-ai/2.0 (Hybrid Identity Scanner)"
|
|
95
95
|
|
|
96
96
|
async def check(self, fqdn: str) -> HybridIdentityResult:
|
|
97
97
|
"""
|
|
@@ -55,7 +55,7 @@ class L7Detector:
|
|
|
55
55
|
user_agent: Custom User-Agent string
|
|
56
56
|
"""
|
|
57
57
|
self.timeout = timeout
|
|
58
|
-
self.user_agent = user_agent or "
|
|
58
|
+
self.user_agent = user_agent or "offsec-ai/2.0 (Security Scanner)"
|
|
59
59
|
self.signatures = L7_SIGNATURES
|
|
60
60
|
|
|
61
61
|
async def detect(self, host: str, port: int = None, path: str = "/", trace_dns: bool = False) -> L7Result:
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Tests for mTLS models and CLI commands."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_imports():
|
|
7
|
+
"""All mTLS modules import without error."""
|
|
8
|
+
from offsec_ai.models.mtls_result import BatchMTLSResult, CertificateInfo, MTLSResult # noqa: F401
|
|
9
|
+
from offsec_ai.core.mtls_checker import MTLSChecker # noqa: F401
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_certificate_info_model():
|
|
13
|
+
"""CertificateInfo model fields are created correctly."""
|
|
14
|
+
from offsec_ai.models.mtls_result import CertificateInfo
|
|
15
|
+
|
|
16
|
+
cert = CertificateInfo(
|
|
17
|
+
subject="CN=test.example.com",
|
|
18
|
+
issuer="CN=Test CA",
|
|
19
|
+
version=3,
|
|
20
|
+
serial_number="12345",
|
|
21
|
+
not_valid_before="2024-01-01T00:00:00",
|
|
22
|
+
not_valid_after="2025-01-01T00:00:00",
|
|
23
|
+
signature_algorithm="sha256WithRSAEncryption",
|
|
24
|
+
key_algorithm="RSAPublicKey",
|
|
25
|
+
key_size=2048,
|
|
26
|
+
san_dns_names=["test.example.com", "*.example.com"],
|
|
27
|
+
san_ip_addresses=["192.168.1.1"],
|
|
28
|
+
is_ca=False,
|
|
29
|
+
is_self_signed=False,
|
|
30
|
+
fingerprint_sha256="abcd1234...",
|
|
31
|
+
)
|
|
32
|
+
assert cert.subject == "CN=test.example.com"
|
|
33
|
+
assert cert.key_size == 2048
|
|
34
|
+
assert "test.example.com" in cert.san_dns_names
|
|
35
|
+
assert cert.is_ca is False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_mtls_result_model():
|
|
39
|
+
"""MTLSResult is created, fields are accessible, and serializes to JSON."""
|
|
40
|
+
from offsec_ai.models.mtls_result import CertificateInfo, MTLSResult
|
|
41
|
+
|
|
42
|
+
cert = CertificateInfo(
|
|
43
|
+
subject="CN=test.example.com",
|
|
44
|
+
issuer="CN=Test CA",
|
|
45
|
+
version=3,
|
|
46
|
+
serial_number="12345",
|
|
47
|
+
not_valid_before="2024-01-01T00:00:00",
|
|
48
|
+
not_valid_after="2025-01-01T00:00:00",
|
|
49
|
+
signature_algorithm="sha256WithRSAEncryption",
|
|
50
|
+
key_algorithm="RSAPublicKey",
|
|
51
|
+
key_size=2048,
|
|
52
|
+
san_dns_names=["test.example.com"],
|
|
53
|
+
san_ip_addresses=[],
|
|
54
|
+
is_ca=False,
|
|
55
|
+
is_self_signed=False,
|
|
56
|
+
fingerprint_sha256="abcd1234...",
|
|
57
|
+
)
|
|
58
|
+
result = MTLSResult(
|
|
59
|
+
target="test.example.com",
|
|
60
|
+
port=443,
|
|
61
|
+
supports_mtls=True,
|
|
62
|
+
requires_client_cert=False,
|
|
63
|
+
server_cert_info=cert,
|
|
64
|
+
client_cert_requested=True,
|
|
65
|
+
handshake_successful=False,
|
|
66
|
+
error_message=None,
|
|
67
|
+
cipher_suite="TLS_AES_256_GCM_SHA384",
|
|
68
|
+
tls_version="TLSv1.3",
|
|
69
|
+
verification_mode="default",
|
|
70
|
+
ca_bundle_path="/etc/ssl/certs/ca-certificates.crt",
|
|
71
|
+
timestamp=datetime.now().isoformat(),
|
|
72
|
+
)
|
|
73
|
+
assert result.target == "test.example.com"
|
|
74
|
+
assert result.port == 443
|
|
75
|
+
assert result.supports_mtls is True
|
|
76
|
+
assert result.server_cert_info.key_size == 2048
|
|
77
|
+
|
|
78
|
+
json_str = result.model_dump_json()
|
|
79
|
+
assert "test.example.com" in json_str
|
|
80
|
+
assert "TLSv1.3" in json_str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_cli_mtls_commands_exist():
|
|
84
|
+
"""mTLS CLI commands are registered and return help text."""
|
|
85
|
+
from click.testing import CliRunner
|
|
86
|
+
|
|
87
|
+
from offsec_ai.cli import main
|
|
88
|
+
|
|
89
|
+
runner = CliRunner()
|
|
90
|
+
|
|
91
|
+
result = runner.invoke(main, ["--help"])
|
|
92
|
+
assert result.exit_code == 0
|
|
93
|
+
assert "mtls-check" in result.output
|
|
94
|
+
|
|
95
|
+
result = runner.invoke(main, ["mtls-check", "--help"])
|
|
96
|
+
assert result.exit_code == 0
|
|
97
|
+
|
|
98
|
+
result = runner.invoke(main, ["mtls-gen-cert", "--help"])
|
|
99
|
+
assert result.exit_code == 0
|
|
100
|
+
|
|
101
|
+
result = runner.invoke(main, ["mtls-validate-cert", "--help"])
|
|
102
|
+
assert result.exit_code == 0
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Integration-level tests for mTLS: models, CLI structure, and documentation presence."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_mtls_model_full_lifecycle():
|
|
8
|
+
"""MTLSResult supports full create → serialize → field-access lifecycle."""
|
|
9
|
+
from offsec_ai.models.mtls_result import CertificateInfo, MTLSResult
|
|
10
|
+
|
|
11
|
+
cert = CertificateInfo(
|
|
12
|
+
subject="CN=test.example.com,O=Test Org,C=US",
|
|
13
|
+
issuer="CN=Test CA,O=Test CA Org,C=US",
|
|
14
|
+
version=3,
|
|
15
|
+
serial_number="123456789",
|
|
16
|
+
not_valid_before="2024-01-01T00:00:00Z",
|
|
17
|
+
not_valid_after="2025-01-01T00:00:00Z",
|
|
18
|
+
signature_algorithm="sha256WithRSAEncryption",
|
|
19
|
+
key_algorithm="RSAPublicKey",
|
|
20
|
+
key_size=2048,
|
|
21
|
+
san_dns_names=["test.example.com", "www.test.example.com"],
|
|
22
|
+
san_ip_addresses=["192.168.1.100"],
|
|
23
|
+
is_ca=False,
|
|
24
|
+
is_self_signed=False,
|
|
25
|
+
fingerprint_sha256="a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
|
|
26
|
+
)
|
|
27
|
+
mtls_result = MTLSResult(
|
|
28
|
+
target="test.example.com",
|
|
29
|
+
port=443,
|
|
30
|
+
supports_mtls=True,
|
|
31
|
+
requires_client_cert=True,
|
|
32
|
+
server_cert_info=cert,
|
|
33
|
+
client_cert_requested=True,
|
|
34
|
+
handshake_successful=True,
|
|
35
|
+
error_message=None,
|
|
36
|
+
cipher_suite="TLS_AES_256_GCM_SHA384",
|
|
37
|
+
tls_version="TLSv1.3",
|
|
38
|
+
verification_mode="strict",
|
|
39
|
+
ca_bundle_path="/etc/ssl/certs/ca-certificates.crt",
|
|
40
|
+
timestamp=datetime.now().isoformat(),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert mtls_result.target == "test.example.com"
|
|
44
|
+
assert mtls_result.supports_mtls is True
|
|
45
|
+
assert mtls_result.server_cert_info.subject == "CN=test.example.com,O=Test Org,C=US"
|
|
46
|
+
|
|
47
|
+
json_str = mtls_result.model_dump_json(indent=2)
|
|
48
|
+
assert len(json_str) > 100
|
|
49
|
+
assert "TLSv1.3" in json_str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_cli_structure():
|
|
53
|
+
"""All expected mTLS commands are present in the CLI."""
|
|
54
|
+
from offsec_ai.cli import main
|
|
55
|
+
|
|
56
|
+
cli_source = main.__module__
|
|
57
|
+
import inspect
|
|
58
|
+
import offsec_ai.cli as cli_module
|
|
59
|
+
|
|
60
|
+
src = inspect.getsource(cli_module)
|
|
61
|
+
for symbol in ("mtls-check", "mtls-gen-cert", "mtls-validate-cert", "MTLSChecker"):
|
|
62
|
+
assert symbol in src, f"Expected '{symbol}' in cli.py"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_documentation_presence():
|
|
66
|
+
"""Key documentation files for mTLS exist and contain expected content."""
|
|
67
|
+
repo_root = os.path.join(os.path.dirname(__file__), "..")
|
|
68
|
+
|
|
69
|
+
readme = os.path.join(repo_root, "README.md")
|
|
70
|
+
assert os.path.isfile(readme)
|
|
71
|
+
content = open(readme).read()
|
|
72
|
+
assert "mTLS" in content
|
|
73
|
+
assert "mutual TLS" in content
|
|
74
|
+
|
|
75
|
+
api_doc = os.path.join(repo_root, "docs", "api.md")
|
|
76
|
+
assert os.path.isfile(api_doc)
|
|
77
|
+
content = open(api_doc).read()
|
|
78
|
+
assert "MTLSChecker" in content
|
|
79
|
+
assert "MTLSResult" in content
|
|
80
|
+
|
|
81
|
+
examples = os.path.join(repo_root, "examples", "mtls_examples.py")
|
|
82
|
+
assert os.path.isfile(examples)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test script for DNS trace functionality.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import asyncio
|
|
7
|
-
import dns.resolver
|
|
8
|
-
import json
|
|
9
|
-
|
|
10
|
-
from offsec_ai.core.l7_detector import L7Detector
|
|
11
|
-
|
|
12
|
-
async def main():
|
|
13
|
-
"""Run DNS trace tests."""
|
|
14
|
-
print("Testing DNS trace...")
|
|
15
|
-
|
|
16
|
-
# Domains to test
|
|
17
|
-
domains = ["sts-qa.gcc.gov.sg", "extadfs.gic.com.sg", "www.google.com"]
|
|
18
|
-
|
|
19
|
-
for domain in domains:
|
|
20
|
-
print(f"\n=== Testing {domain} ===")
|
|
21
|
-
|
|
22
|
-
# Simple DNS lookup using dns.resolver directly
|
|
23
|
-
try:
|
|
24
|
-
print("\nDirect DNS resolution:")
|
|
25
|
-
resolver = dns.resolver.Resolver()
|
|
26
|
-
|
|
27
|
-
# CNAME lookup
|
|
28
|
-
try:
|
|
29
|
-
cname_answers = resolver.resolve(domain, "CNAME")
|
|
30
|
-
for cname in cname_answers:
|
|
31
|
-
cname_str = str(cname.target).lower().rstrip('.')
|
|
32
|
-
print(f"CNAME: {domain} → {cname_str}")
|
|
33
|
-
|
|
34
|
-
# Try to resolve the CNAME target
|
|
35
|
-
try:
|
|
36
|
-
a_answers = resolver.resolve(cname_str, "A")
|
|
37
|
-
for a_record in a_answers:
|
|
38
|
-
ip_str = str(a_record)
|
|
39
|
-
print(f"IP: {cname_str} → {ip_str}")
|
|
40
|
-
except Exception as e:
|
|
41
|
-
print(f"Failed to resolve CNAME to IP: {e}")
|
|
42
|
-
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
|
43
|
-
print("No CNAME records found")
|
|
44
|
-
|
|
45
|
-
# Try direct A record lookup
|
|
46
|
-
try:
|
|
47
|
-
a_answers = resolver.resolve(domain, "A")
|
|
48
|
-
for a_record in a_answers:
|
|
49
|
-
ip_str = str(a_record)
|
|
50
|
-
print(f"Direct A record: {domain} → {ip_str}")
|
|
51
|
-
except Exception as e:
|
|
52
|
-
print(f"Failed to resolve A records: {e}")
|
|
53
|
-
except Exception as e:
|
|
54
|
-
print(f"DNS resolution error: {e}")
|
|
55
|
-
|
|
56
|
-
# Test with our L7Detector
|
|
57
|
-
print("\nUsing L7Detector.detect():")
|
|
58
|
-
detector = L7Detector(timeout=5.0)
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
# Execute the trace domain function directly
|
|
62
|
-
detections, dns_trace = await detector._trace_domain_protection(domain)
|
|
63
|
-
print(f"Trace results:\nDetections: {len(detections)}")
|
|
64
|
-
print(f"DNS trace data: {json.dumps(dns_trace, indent=2)}")
|
|
65
|
-
|
|
66
|
-
# Test the full detect method with our own trace function
|
|
67
|
-
dns_detections, dns_trace = await detector._trace_domain_protection(domain)
|
|
68
|
-
result = await detector.detect(domain)
|
|
69
|
-
|
|
70
|
-
# Manually check and update the dns_trace field
|
|
71
|
-
print(f"\nFull detection results:")
|
|
72
|
-
print(f"Is protected: {result.is_protected}")
|
|
73
|
-
if result.primary_protection:
|
|
74
|
-
print(f"Primary protection: {result.primary_protection.service.value} ({result.primary_protection.confidence:.1%})")
|
|
75
|
-
|
|
76
|
-
print(f"DNS trace in result before: {json.dumps(result.dns_trace, indent=2)}")
|
|
77
|
-
|
|
78
|
-
# Manually update the result for debugging
|
|
79
|
-
result.dns_trace = dns_trace
|
|
80
|
-
print(f"DNS trace in result after manual update: {json.dumps(result.dns_trace, indent=2)}")
|
|
81
|
-
except Exception as e:
|
|
82
|
-
print(f"L7Detector error: {e}")
|
|
83
|
-
|
|
84
|
-
if __name__ == "__main__":
|
|
85
|
-
asyncio.run(main())
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test script for mTLS functionality.
|
|
4
|
-
|
|
5
|
-
This script tests the mTLS checker without requiring all dependencies to be installed.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import sys
|
|
9
|
-
import os
|
|
10
|
-
|
|
11
|
-
# Add the src directory to Python path
|
|
12
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
|
13
|
-
|
|
14
|
-
def test_imports():
|
|
15
|
-
"""Test that all modules can be imported."""
|
|
16
|
-
print("Testing imports...")
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
from offsec_ai.models.mtls_result import MTLSResult, CertificateInfo, BatchMTLSResult
|
|
20
|
-
print("✓ mTLS models imported successfully")
|
|
21
|
-
except ImportError as e:
|
|
22
|
-
print(f"✗ Failed to import mTLS models: {e}")
|
|
23
|
-
return False
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
from offsec_ai.core.mtls_checker import MTLSChecker
|
|
27
|
-
print("✓ MTLSChecker imported successfully")
|
|
28
|
-
except ImportError as e:
|
|
29
|
-
print(f"✗ Failed to import MTLSChecker: {e}")
|
|
30
|
-
return False
|
|
31
|
-
|
|
32
|
-
return True
|
|
33
|
-
|
|
34
|
-
def test_mtls_result_model():
|
|
35
|
-
"""Test the MTLSResult model."""
|
|
36
|
-
print("\nTesting MTLSResult model...")
|
|
37
|
-
|
|
38
|
-
from offsec_ai.models.mtls_result import MTLSResult, CertificateInfo
|
|
39
|
-
from datetime import datetime
|
|
40
|
-
|
|
41
|
-
# Test CertificateInfo
|
|
42
|
-
cert_info = CertificateInfo(
|
|
43
|
-
subject="CN=test.example.com",
|
|
44
|
-
issuer="CN=Test CA",
|
|
45
|
-
version=3,
|
|
46
|
-
serial_number="12345",
|
|
47
|
-
not_valid_before="2024-01-01T00:00:00",
|
|
48
|
-
not_valid_after="2025-01-01T00:00:00",
|
|
49
|
-
signature_algorithm="sha256WithRSAEncryption",
|
|
50
|
-
key_algorithm="RSAPublicKey",
|
|
51
|
-
key_size=2048,
|
|
52
|
-
san_dns_names=["test.example.com", "*.example.com"],
|
|
53
|
-
san_ip_addresses=["192.168.1.1"],
|
|
54
|
-
is_ca=False,
|
|
55
|
-
is_self_signed=False,
|
|
56
|
-
fingerprint_sha256="abcd1234..."
|
|
57
|
-
)
|
|
58
|
-
print("✓ CertificateInfo model created successfully")
|
|
59
|
-
|
|
60
|
-
# Test MTLSResult
|
|
61
|
-
result = MTLSResult(
|
|
62
|
-
target="test.example.com",
|
|
63
|
-
port=443,
|
|
64
|
-
supports_mtls=True,
|
|
65
|
-
requires_client_cert=False,
|
|
66
|
-
server_cert_info=cert_info,
|
|
67
|
-
client_cert_requested=True,
|
|
68
|
-
handshake_successful=False,
|
|
69
|
-
error_message=None,
|
|
70
|
-
cipher_suite="TLS_AES_256_GCM_SHA384",
|
|
71
|
-
tls_version="TLSv1.3",
|
|
72
|
-
verification_mode="default",
|
|
73
|
-
ca_bundle_path="/etc/ssl/certs/ca-certificates.crt",
|
|
74
|
-
timestamp=datetime.now().isoformat()
|
|
75
|
-
)
|
|
76
|
-
print("✓ MTLSResult model created successfully")
|
|
77
|
-
|
|
78
|
-
# Test JSON serialization
|
|
79
|
-
json_data = result.model_dump_json()
|
|
80
|
-
print("✓ MTLSResult JSON serialization works")
|
|
81
|
-
|
|
82
|
-
return True
|
|
83
|
-
|
|
84
|
-
def test_cli_help():
|
|
85
|
-
"""Test the CLI help for mTLS commands."""
|
|
86
|
-
print("\nTesting CLI help...")
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
from offsec_ai.cli import main
|
|
90
|
-
import click.testing
|
|
91
|
-
|
|
92
|
-
runner = click.testing.CliRunner()
|
|
93
|
-
|
|
94
|
-
# Test main help
|
|
95
|
-
result = runner.invoke(main, ['--help'])
|
|
96
|
-
if 'mtls-check' in result.output:
|
|
97
|
-
print("✓ mtls-check command found in CLI help")
|
|
98
|
-
else:
|
|
99
|
-
print("✗ mtls-check command not found in CLI help")
|
|
100
|
-
return False
|
|
101
|
-
|
|
102
|
-
# Test mtls-check help
|
|
103
|
-
result = runner.invoke(main, ['mtls-check', '--help'])
|
|
104
|
-
if result.exit_code == 0:
|
|
105
|
-
print("✓ mtls-check help works")
|
|
106
|
-
else:
|
|
107
|
-
print(f"✗ mtls-check help failed: {result.output}")
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
# Test mtls-gen-cert help
|
|
111
|
-
result = runner.invoke(main, ['mtls-gen-cert', '--help'])
|
|
112
|
-
if result.exit_code == 0:
|
|
113
|
-
print("✓ mtls-gen-cert help works")
|
|
114
|
-
else:
|
|
115
|
-
print(f"✗ mtls-gen-cert help failed: {result.output}")
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
return True
|
|
119
|
-
|
|
120
|
-
except ImportError as e:
|
|
121
|
-
print(f"✗ Failed to import CLI: {e}")
|
|
122
|
-
return False
|
|
123
|
-
|
|
124
|
-
def main():
|
|
125
|
-
"""Run all tests."""
|
|
126
|
-
print("Simple Port Checker - mTLS Functionality Test")
|
|
127
|
-
print("=" * 50)
|
|
128
|
-
|
|
129
|
-
tests = [
|
|
130
|
-
test_imports,
|
|
131
|
-
test_mtls_result_model,
|
|
132
|
-
test_cli_help,
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
passed = 0
|
|
136
|
-
failed = 0
|
|
137
|
-
|
|
138
|
-
for test in tests:
|
|
139
|
-
try:
|
|
140
|
-
if test():
|
|
141
|
-
passed += 1
|
|
142
|
-
else:
|
|
143
|
-
failed += 1
|
|
144
|
-
except Exception as e:
|
|
145
|
-
print(f"✗ Test {test.__name__} failed with exception: {e}")
|
|
146
|
-
failed += 1
|
|
147
|
-
|
|
148
|
-
print("\n" + "=" * 50)
|
|
149
|
-
print(f"Tests completed: {passed} passed, {failed} failed")
|
|
150
|
-
|
|
151
|
-
if failed == 0:
|
|
152
|
-
print("🎉 All tests passed! mTLS functionality is working correctly.")
|
|
153
|
-
return 0
|
|
154
|
-
else:
|
|
155
|
-
print("❌ Some tests failed. Please check the output above.")
|
|
156
|
-
return 1
|
|
157
|
-
|
|
158
|
-
if __name__ == "__main__":
|
|
159
|
-
sys.exit(main())
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Simple test for mTLS functionality without full installation.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import sys
|
|
7
|
-
import os
|
|
8
|
-
|
|
9
|
-
# Add the src directory to Python path
|
|
10
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
|
|
11
|
-
|
|
12
|
-
def test_mtls_model():
|
|
13
|
-
"""Test the MTLSResult model directly."""
|
|
14
|
-
print("Testing mTLS models...")
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
from offsec_ai.models.mtls_result import MTLSResult, CertificateInfo
|
|
18
|
-
from datetime import datetime
|
|
19
|
-
|
|
20
|
-
# Create a test certificate info
|
|
21
|
-
cert_info = CertificateInfo(
|
|
22
|
-
subject="CN=test.example.com,O=Test Org,C=US",
|
|
23
|
-
issuer="CN=Test CA,O=Test CA Org,C=US",
|
|
24
|
-
version=3,
|
|
25
|
-
serial_number="123456789",
|
|
26
|
-
not_valid_before="2024-01-01T00:00:00Z",
|
|
27
|
-
not_valid_after="2025-01-01T00:00:00Z",
|
|
28
|
-
signature_algorithm="sha256WithRSAEncryption",
|
|
29
|
-
key_algorithm="RSAPublicKey",
|
|
30
|
-
key_size=2048,
|
|
31
|
-
san_dns_names=["test.example.com", "www.test.example.com"],
|
|
32
|
-
san_ip_addresses=["192.168.1.100"],
|
|
33
|
-
is_ca=False,
|
|
34
|
-
is_self_signed=False,
|
|
35
|
-
fingerprint_sha256="a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
|
|
36
|
-
)
|
|
37
|
-
print("✓ CertificateInfo created successfully")
|
|
38
|
-
|
|
39
|
-
# Create a test mTLS result
|
|
40
|
-
mtls_result = MTLSResult(
|
|
41
|
-
target="test.example.com",
|
|
42
|
-
port=443,
|
|
43
|
-
supports_mtls=True,
|
|
44
|
-
requires_client_cert=True,
|
|
45
|
-
server_cert_info=cert_info,
|
|
46
|
-
client_cert_requested=True,
|
|
47
|
-
handshake_successful=True,
|
|
48
|
-
error_message=None,
|
|
49
|
-
cipher_suite="TLS_AES_256_GCM_SHA384",
|
|
50
|
-
tls_version="TLSv1.3",
|
|
51
|
-
verification_mode="strict",
|
|
52
|
-
ca_bundle_path="/etc/ssl/certs/ca-certificates.crt",
|
|
53
|
-
timestamp=datetime.now().isoformat()
|
|
54
|
-
)
|
|
55
|
-
print("✓ MTLSResult created successfully")
|
|
56
|
-
|
|
57
|
-
# Test JSON serialization
|
|
58
|
-
json_str = mtls_result.model_dump_json(indent=2)
|
|
59
|
-
print("✓ JSON serialization works")
|
|
60
|
-
print(f"JSON length: {len(json_str)} characters")
|
|
61
|
-
|
|
62
|
-
# Test field access
|
|
63
|
-
print(f"✓ Target: {mtls_result.target}")
|
|
64
|
-
print(f"✓ Supports mTLS: {mtls_result.supports_mtls}")
|
|
65
|
-
print(f"✓ Server cert subject: {mtls_result.server_cert_info.subject}")
|
|
66
|
-
|
|
67
|
-
return True
|
|
68
|
-
|
|
69
|
-
except Exception as e:
|
|
70
|
-
print(f"✗ Error: {e}")
|
|
71
|
-
import traceback
|
|
72
|
-
traceback.print_exc()
|
|
73
|
-
return False
|
|
74
|
-
|
|
75
|
-
def test_cli_structure():
|
|
76
|
-
"""Test if CLI structure includes mTLS commands."""
|
|
77
|
-
print("\nTesting CLI structure...")
|
|
78
|
-
|
|
79
|
-
try:
|
|
80
|
-
# Read the CLI file to check for mTLS commands
|
|
81
|
-
cli_file = os.path.join(os.path.dirname(__file__), 'src', 'offsec_ai', 'cli.py')
|
|
82
|
-
|
|
83
|
-
with open(cli_file, 'r') as f:
|
|
84
|
-
cli_content = f.read()
|
|
85
|
-
|
|
86
|
-
# Check for mTLS command definitions
|
|
87
|
-
mtls_commands = [
|
|
88
|
-
'mtls-check',
|
|
89
|
-
'mtls-gen-cert',
|
|
90
|
-
'mtls-validate-cert',
|
|
91
|
-
'_run_mtls_check',
|
|
92
|
-
'MTLSChecker'
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
found_commands = []
|
|
96
|
-
for cmd in mtls_commands:
|
|
97
|
-
if cmd in cli_content:
|
|
98
|
-
found_commands.append(cmd)
|
|
99
|
-
print(f"✓ Found {cmd} in CLI")
|
|
100
|
-
else:
|
|
101
|
-
print(f"✗ Missing {cmd} in CLI")
|
|
102
|
-
|
|
103
|
-
if len(found_commands) == len(mtls_commands):
|
|
104
|
-
print("✓ All mTLS commands found in CLI")
|
|
105
|
-
return True
|
|
106
|
-
else:
|
|
107
|
-
print(f"✗ Only {len(found_commands)}/{len(mtls_commands)} commands found")
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
except Exception as e:
|
|
111
|
-
print(f"✗ Error checking CLI: {e}")
|
|
112
|
-
return False
|
|
113
|
-
|
|
114
|
-
def test_documentation():
|
|
115
|
-
"""Test if documentation includes mTLS information."""
|
|
116
|
-
print("\nTesting documentation...")
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
# Check README
|
|
120
|
-
readme_file = os.path.join(os.path.dirname(__file__), 'README.md')
|
|
121
|
-
with open(readme_file, 'r') as f:
|
|
122
|
-
readme_content = f.read()
|
|
123
|
-
|
|
124
|
-
if 'mTLS' in readme_content and 'mutual TLS' in readme_content:
|
|
125
|
-
print("✓ README includes mTLS documentation")
|
|
126
|
-
else:
|
|
127
|
-
print("✗ README missing mTLS documentation")
|
|
128
|
-
return False
|
|
129
|
-
|
|
130
|
-
# Check API docs
|
|
131
|
-
api_docs_file = os.path.join(os.path.dirname(__file__), 'docs', 'api.md')
|
|
132
|
-
with open(api_docs_file, 'r') as f:
|
|
133
|
-
api_content = f.read()
|
|
134
|
-
|
|
135
|
-
if 'MTLSChecker' in api_content and 'MTLSResult' in api_content:
|
|
136
|
-
print("✓ API docs include mTLS documentation")
|
|
137
|
-
else:
|
|
138
|
-
print("✗ API docs missing mTLS documentation")
|
|
139
|
-
return False
|
|
140
|
-
|
|
141
|
-
# Check examples
|
|
142
|
-
examples_file = os.path.join(os.path.dirname(__file__), 'examples', 'mtls_examples.py')
|
|
143
|
-
if os.path.exists(examples_file):
|
|
144
|
-
print("✓ mTLS examples file exists")
|
|
145
|
-
else:
|
|
146
|
-
print("✗ mTLS examples file missing")
|
|
147
|
-
return False
|
|
148
|
-
|
|
149
|
-
return True
|
|
150
|
-
|
|
151
|
-
except Exception as e:
|
|
152
|
-
print(f"✗ Error checking documentation: {e}")
|
|
153
|
-
return False
|
|
154
|
-
|
|
155
|
-
def main():
|
|
156
|
-
"""Run all tests."""
|
|
157
|
-
print("Simple Port Checker - mTLS Integration Test")
|
|
158
|
-
print("=" * 50)
|
|
159
|
-
|
|
160
|
-
tests = [
|
|
161
|
-
test_mtls_model,
|
|
162
|
-
test_cli_structure,
|
|
163
|
-
test_documentation,
|
|
164
|
-
]
|
|
165
|
-
|
|
166
|
-
results = []
|
|
167
|
-
for test in tests:
|
|
168
|
-
try:
|
|
169
|
-
result = test()
|
|
170
|
-
results.append(result)
|
|
171
|
-
except Exception as e:
|
|
172
|
-
print(f"✗ Test {test.__name__} failed with exception: {e}")
|
|
173
|
-
results.append(False)
|
|
174
|
-
|
|
175
|
-
passed = sum(results)
|
|
176
|
-
total = len(results)
|
|
177
|
-
|
|
178
|
-
print("\n" + "=" * 50)
|
|
179
|
-
print(f"Tests completed: {passed}/{total} passed")
|
|
180
|
-
|
|
181
|
-
if passed == total:
|
|
182
|
-
print("🎉 All tests passed! mTLS functionality has been successfully integrated.")
|
|
183
|
-
print("\nNext steps:")
|
|
184
|
-
print("1. Install dependencies: pip install cryptography certifi")
|
|
185
|
-
print("2. Test CLI: offsec-ai mtls-check --help")
|
|
186
|
-
print("3. Try example: python examples/mtls_examples.py")
|
|
187
|
-
return 0
|
|
188
|
-
else:
|
|
189
|
-
print("❌ Some tests failed. Please check the output above.")
|
|
190
|
-
return 1
|
|
191
|
-
|
|
192
|
-
if __name__ == "__main__":
|
|
193
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|