ti2dac 0.1.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 (76) hide show
  1. ti2dac-0.1.0/LICENSE +21 -0
  2. ti2dac-0.1.0/PKG-INFO +180 -0
  3. ti2dac-0.1.0/README.md +117 -0
  4. ti2dac-0.1.0/pyproject.toml +86 -0
  5. ti2dac-0.1.0/setup.cfg +4 -0
  6. ti2dac-0.1.0/src/ti2dac/__init__.py +3 -0
  7. ti2dac-0.1.0/src/ti2dac/aistatus.py +169 -0
  8. ti2dac-0.1.0/src/ti2dac/attack.py +169 -0
  9. ti2dac-0.1.0/src/ti2dac/chains.py +211 -0
  10. ti2dac-0.1.0/src/ti2dac/cli.py +951 -0
  11. ti2dac-0.1.0/src/ti2dac/clients.py +149 -0
  12. ti2dac-0.1.0/src/ti2dac/condense.py +228 -0
  13. ti2dac-0.1.0/src/ti2dac/config.py +320 -0
  14. ti2dac-0.1.0/src/ti2dac/correlate.py +97 -0
  15. ti2dac-0.1.0/src/ti2dac/data/attack_index.json +1 -0
  16. ti2dac-0.1.0/src/ti2dac/data/detections-ci.yml +42 -0
  17. ti2dac-0.1.0/src/ti2dac/data/wazuh_parents.json +133 -0
  18. ti2dac-0.1.0/src/ti2dac/duplicates.py +126 -0
  19. ti2dac-0.1.0/src/ti2dac/feedback.py +369 -0
  20. ti2dac-0.1.0/src/ti2dac/health.py +47 -0
  21. ti2dac-0.1.0/src/ti2dac/ingest.py +325 -0
  22. ti2dac-0.1.0/src/ti2dac/iocs.py +293 -0
  23. ti2dac-0.1.0/src/ti2dac/llm.py +598 -0
  24. ti2dac-0.1.0/src/ti2dac/locks.py +34 -0
  25. ti2dac-0.1.0/src/ti2dac/modelchoice.py +162 -0
  26. ti2dac-0.1.0/src/ti2dac/models.py +422 -0
  27. ti2dac-0.1.0/src/ti2dac/obs.py +131 -0
  28. ti2dac-0.1.0/src/ti2dac/pipeline.py +1025 -0
  29. ti2dac-0.1.0/src/ti2dac/prompts.py +183 -0
  30. ti2dac-0.1.0/src/ti2dac/quality.py +79 -0
  31. ti2dac-0.1.0/src/ti2dac/redact.py +279 -0
  32. ti2dac-0.1.0/src/ti2dac/regexes.py +193 -0
  33. ti2dac-0.1.0/src/ti2dac/render.py +664 -0
  34. ti2dac-0.1.0/src/ti2dac/replay.py +302 -0
  35. ti2dac-0.1.0/src/ti2dac/review.py +328 -0
  36. ti2dac-0.1.0/src/ti2dac/safety.py +213 -0
  37. ti2dac-0.1.0/src/ti2dac/sigma_tools.py +586 -0
  38. ti2dac-0.1.0/src/ti2dac/targets.py +384 -0
  39. ti2dac-0.1.0/src/ti2dac/users.py +99 -0
  40. ti2dac-0.1.0/src/ti2dac/wazuh.py +301 -0
  41. ti2dac-0.1.0/src/ti2dac/web/__init__.py +0 -0
  42. ti2dac-0.1.0/src/ti2dac/web/app.py +793 -0
  43. ti2dac-0.1.0/src/ti2dac/web/jobs.py +358 -0
  44. ti2dac-0.1.0/src/ti2dac/web/static/app.css +631 -0
  45. ti2dac-0.1.0/src/ti2dac/web/static/app.js +276 -0
  46. ti2dac-0.1.0/src/ti2dac/web/static/chevron.svg +1 -0
  47. ti2dac-0.1.0/src/ti2dac/web/static/favicon.svg +1 -0
  48. ti2dac-0.1.0/src/ti2dac/web/templates/_ui.html +107 -0
  49. ti2dac-0.1.0/src/ti2dac/web/templates/ai.html +207 -0
  50. ti2dac-0.1.0/src/ti2dac/web/templates/api.html +93 -0
  51. ti2dac-0.1.0/src/ti2dac/web/templates/base.html +74 -0
  52. ti2dac-0.1.0/src/ti2dac/web/templates/feedback.html +100 -0
  53. ti2dac-0.1.0/src/ti2dac/web/templates/index.html +196 -0
  54. ti2dac-0.1.0/src/ti2dac/web/templates/job.html +51 -0
  55. ti2dac-0.1.0/src/ti2dac/web/templates/run.html +514 -0
  56. ti2dac-0.1.0/src/ti2dac.egg-info/PKG-INFO +180 -0
  57. ti2dac-0.1.0/src/ti2dac.egg-info/SOURCES.txt +74 -0
  58. ti2dac-0.1.0/src/ti2dac.egg-info/dependency_links.txt +1 -0
  59. ti2dac-0.1.0/src/ti2dac.egg-info/entry_points.txt +2 -0
  60. ti2dac-0.1.0/src/ti2dac.egg-info/requires.txt +40 -0
  61. ti2dac-0.1.0/src/ti2dac.egg-info/top_level.txt +1 -0
  62. ti2dac-0.1.0/tests/test_ai_limits.py +377 -0
  63. ti2dac-0.1.0/tests/test_ai_review.py +282 -0
  64. ti2dac-0.1.0/tests/test_deterministic.py +200 -0
  65. ti2dac-0.1.0/tests/test_edge_cases.py +286 -0
  66. ti2dac-0.1.0/tests/test_hardening_pipeline.py +471 -0
  67. ti2dac-0.1.0/tests/test_hardening_web.py +383 -0
  68. ti2dac-0.1.0/tests/test_incident.py +185 -0
  69. ti2dac-0.1.0/tests/test_job_workers.py +117 -0
  70. ti2dac-0.1.0/tests/test_model_choice.py +131 -0
  71. ti2dac-0.1.0/tests/test_observability.py +167 -0
  72. ti2dac-0.1.0/tests/test_pipeline.py +348 -0
  73. ti2dac-0.1.0/tests/test_replay_semantics.py +67 -0
  74. ti2dac-0.1.0/tests/test_review_2026_11_07.py +292 -0
  75. ti2dac-0.1.0/tests/test_siem_coverage.py +323 -0
  76. ti2dac-0.1.0/tests/test_web_keys.py +100 -0
ti2dac-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sazidul0
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.
ti2dac-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: ti2dac
3
+ Version: 0.1.0
4
+ Summary: Threat Intelligence to Detection-as-Code: TI report -> Sigma -> multi-SIEM queries + safe simulation guides
5
+ Author: Sazidul0
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Sazidul0/TI2DaC
8
+ Project-URL: Repository, https://github.com/Sazidul0/TI2DaC
9
+ Project-URL: Issues, https://github.com/Sazidul0/TI2DaC/issues
10
+ Keywords: threat-intelligence,sigma,detection-as-code,mitre-attack,siem,splunk,elastic,microsoft-sentinel,wazuh,blue-team,detection-engineering,llm,security
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Environment :: Console
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Topic :: Security
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: anthropic>=2.3
26
+ Requires-Dist: pydantic>=2.7
27
+ Requires-Dist: pysigma<0.12,>=0.11
28
+ Requires-Dist: pyparsing<3.2,>=3.1
29
+ Requires-Dist: sigma-cli==1.0.6
30
+ Requires-Dist: pySigma-backend-splunk==1.1.3
31
+ Requires-Dist: pySigma-backend-elasticsearch==1.1.6
32
+ Requires-Dist: pySigma-backend-kusto==0.4.5
33
+ Requires-Dist: pySigma-backend-crowdstrike==2.0.1
34
+ Requires-Dist: pySigma-backend-qradar-aql==0.3.2
35
+ Requires-Dist: pySigma-backend-opensearch==1.0.4
36
+ Requires-Dist: pySigma-backend-loki==0.12.4
37
+ Requires-Dist: pySigma-pipeline-sysmon==1.0.4
38
+ Requires-Dist: pySigma-pipeline-windows==1.2.0
39
+ Requires-Dist: pySigma-backend-sqlite==0.2.0
40
+ Requires-Dist: regex==2023.12.25
41
+ Requires-Dist: dateparser==1.2.1
42
+ Requires-Dist: trafilatura>=1.8
43
+ Requires-Dist: beautifulsoup4
44
+ Requires-Dist: lxml
45
+ Requires-Dist: pypdf>=4
46
+ Requires-Dist: python-docx>=1.1
47
+ Requires-Dist: pyyaml>=6
48
+ Requires-Dist: httpx>=0.27
49
+ Requires-Dist: typer>=0.12
50
+ Requires-Dist: fastapi>=0.110
51
+ Requires-Dist: uvicorn>=0.29
52
+ Requires-Dist: jinja2>=3.1
53
+ Requires-Dist: markdown-it-py>=3
54
+ Requires-Dist: python-multipart
55
+ Provides-Extra: dev
56
+ Requires-Dist: pytest>=8; extra == "dev"
57
+ Provides-Extra: gemini
58
+ Requires-Dist: google-genai>=1.30; extra == "gemini"
59
+ Provides-Extra: evtx
60
+ Requires-Dist: python-evtx>=0.7; extra == "evtx"
61
+ Requires-Dist: defusedxml>=0.7; extra == "evtx"
62
+ Dynamic: license-file
63
+
64
+ <p align="center">
65
+ <img src="https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/ti2dac-banner.svg" alt="ti2dac: Threat Intelligence to Detection-as-Code" width="100%">
66
+ </p>
67
+
68
+ <p align="center">
69
+ <a href="https://pypi.org/project/ti2dac/"><img src="https://img.shields.io/pypi/v/ti2dac?label=PyPI&color=1f4e79" alt="PyPI version"></a>
70
+ <a href="https://pypi.org/project/ti2dac/"><img src="https://img.shields.io/pypi/pyversions/ti2dac" alt="Python versions"></a>
71
+ <a href="https://github.com/Sazidul0/TI2DaC/actions/workflows/ci.yml"><img src="https://github.com/Sazidul0/TI2DaC/actions/workflows/ci.yml/badge.svg" alt="Build status"></a>
72
+ <a href="https://github.com/Sazidul0/TI2DaC/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Sazidul0/TI2DaC" alt="License"></a>
73
+ <a href="https://github.com/Sazidul0/TI2DaC/stargazers"><img src="https://img.shields.io/github/stars/Sazidul0/TI2DaC?style=flat" alt="GitHub stars"></a>
74
+ <a href="https://github.com/Sazidul0/TI2DaC/issues"><img src="https://img.shields.io/github/issues/Sazidul0/TI2DaC" alt="GitHub issues"></a>
75
+ </p>
76
+
77
+ **Open-source Threat Intelligence to Detection-as-Code engine.** ti2dac turns threat reports and incident write-ups into evidence-checked Sigma rules, native queries for Splunk, Elastic, Sentinel, Defender, CrowdStrike, QRadar, OpenSearch, Loki and Wazuh, and safe simulation and test guides, so blue teams can go from a report to a reviewed detection package in minutes.
78
+
79
+ `sigma` · `detection-as-code` · `threat-intelligence` · `mitre-attack` · `siem` · `splunk` · `elastic` · `microsoft-sentinel` · `wazuh` · `blue-team` · `detection-engineering` · `llm`
80
+
81
+ # ti2dac: Threat Intelligence → Detection-as-Code
82
+
83
+ ti2dac turns a **threat-intelligence report or an internal incident report** into a reviewed, tested detection package:
84
+
85
+ - the attacker behaviours (TTPs) in the report, with evidence quotes checked against the source and verified MITRE ATT&CK mappings
86
+ - one vendor-neutral **Sigma** rule per behaviour (single-event or threshold/correlation), validated with pySigma
87
+ - the same rule as native queries for **Splunk, Elastic, Microsoft Sentinel, Defender XDR, CrowdStrike NG-SIEM, QRadar, OpenSearch and Grafana Loki**, with a per-SIEM field-mapping table
88
+ - a **simulation and test guide** per rule: a safe lab script and its cleanup, the matching Atomic Red Team test, expected events, and positive and false-positive test procedures
89
+ - **unit tests** for each rule, plus local replay of real lab or incident logs, so you know a rule fires *before* it reaches a SIEM
90
+ - **ADS** triage documentation, telemetry requirements, a validation matrix and an ATT&CK Navigator layer
91
+ - a **feedback and review workflow**: error taxonomy, versioned corrections, lessons fed back into the AI, four-eyes approval and an audit log
92
+
93
+ The AI does the reading and the detection writing. Everything that can be deterministic is deterministic: conversion,
94
+ validation, documentation, safety checks and redaction. This keeps token use low and the output reproducible.
95
+
96
+ ![ti2dac main flow: report, reader, gatekeeper, summariser, AI analysis, verification, AI rule drafting, inspection and repair, SIEM translation, packaging, then human testing, review and export to Git](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/ti2dac-overview.svg)
97
+
98
+ *Steps 1 to 9 run automatically. Steps 10 to 12 are done by people: ti2dac never runs the simulation scripts and never connects to your SIEM.*
99
+
100
+ ## Install
101
+
102
+ Requires Python 3.10 or newer.
103
+
104
+ ```bash
105
+ pip install ti2dac # or: pipx install ti2dac
106
+ pip install "ti2dac[gemini]" # optional: Google Gemini as the AI provider
107
+ pip install "ti2dac[evtx]" # optional: replay Windows .evtx evidence
108
+ ```
109
+
110
+ ## Quick start
111
+
112
+ From a source checkout (`git clone https://github.com/Sazidul0/TI2DaC && cd TI2DaC`):
113
+
114
+ ```bash
115
+ uv venv .venv && uv pip install --python .venv/bin/python -e ".[dev]"
116
+ cp .env.example .env # then add ANTHROPIC_API_KEY (see docs/getting-started.md)
117
+ cp ti2dac.example.yaml ti2dac.yaml # then set your domains, customer names, telemetry
118
+
119
+ ti2dac run path/to/report.html # public threat-intel report (file or URL)
120
+ ti2dac run IR-2026-0147.docx --incident --events host-sysmon.evtx # internal incident report + its evidence logs
121
+ ti2dac serve # web UI on http://127.0.0.1:8000
122
+ ```
123
+
124
+ The two-step flow lets an analyst choose behaviours before any rule-generation tokens are spent:
125
+
126
+ ```bash
127
+ ti2dac analyze report.html # 1 AI call: behaviours ranked as B1..Bn
128
+ ti2dac generate <run-id> --select B2,B5 # 1 AI call per chosen behaviour
129
+ ```
130
+
131
+ Without AI (`--no-ai` or `TI2DAC_NO_AI=1`), the deterministic parts still work: ingestion, IOC extraction,
132
+ cited ATT&CK IDs, and validation, conversion, unit tests and replay of hand-written rules.
133
+
134
+ ## Web UI
135
+
136
+ `ti2dac serve` starts a local web UI (bound to `127.0.0.1`) for analysts and reviewers, with logins and roles.
137
+
138
+ ![ti2dac Runs page: totals for reports, rules, approvals and AI usage, the New analysis form with document type choice, recent jobs and SIEM coverage](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/screenshot-runs.png)
139
+
140
+ *The Runs page: upload a report or paste a URL, choose the document type, and follow recent jobs and totals.*
141
+
142
+ ![ti2dac run page: extracted behaviours ranked by detectability, each with ATT&CK technique, verified evidence count and the rule generated from it](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/screenshot-behaviours.png)
143
+
144
+ *A run: behaviours extracted from the report, ranked, with checked evidence quotes and ATT&CK mappings. Tick any behaviour without a rule to generate more.*
145
+
146
+ ## Safety at a glance
147
+
148
+ - **Your data stays home.** The AI sees only trimmed, redacted text. Names, hosts, internal IPs, emails and secrets are replaced with placeholders first. TLP:RED documents never go to an AI.
149
+ - **Nothing is executed.** Simulation scripts are linted for destructive or credential-stealing actions, and the tool never runs them. Nothing the model writes is run.
150
+ - **Nothing is trusted unchecked.** Evidence quotes, ATT&CK IDs, Sigma syntax and rule self-tests are verified by code before you see a rule.
151
+ - **People stay in charge.** The tool drafts. A second person (the approver) must approve what the author created, and only approved rules are exported.
152
+
153
+ Generated rules are drafts until you have tested them on real events. See [known limitations](docs/known-limitations.md).
154
+
155
+ ## Documentation
156
+
157
+ | Guide | What is in it |
158
+ |---|---|
159
+ | [Getting started](docs/getting-started.md) | AI provider setup, a step-by-step walk-through, supported inputs |
160
+ | [How it works](docs/how-it-works.md) | Architecture in plain terms, the working process, how tokens are kept low |
161
+ | [Quality, safety and testing](docs/quality-and-testing.md) | Safety controls and the three levels of proving a rule works |
162
+ | [SIEM coverage](docs/siem-coverage.md) | The 14 targets, Wazuh, correlations and client profiles |
163
+ | [Review and configuration](docs/governance-and-config.md) | Feedback, four-eyes approval, Git hand-off, `ti2dac.yaml` |
164
+ | [Operations](docs/operations.md) | Monitoring, web UI security, output package layout |
165
+ | [CLI reference](docs/cli-reference.md) | Every command |
166
+ | [Known limitations](docs/known-limitations.md) | What the tool does not do yet |
167
+
168
+ ## Development
169
+
170
+ ```bash
171
+ .venv/bin/python -m pytest -q # 254 tests, no network or API key needed
172
+ ```
173
+
174
+ Dependency pins that matter: pySigma must stay `0.11.x`, because the QRadar backend breaks on 2.x and newer
175
+ SQLite-backend releases silently pull in pySigma 2. `pyparsing` must stay below 3.2, which pySigma 0.11's condition
176
+ parser needs. `pySigma-backend-sqlite` must be `0.2.0`. `regex` and `dateparser` are pinned together (see `pyproject.toml`).
177
+
178
+ ## License
179
+
180
+ MIT. See [LICENSE](LICENSE).
ti2dac-0.1.0/README.md ADDED
@@ -0,0 +1,117 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/ti2dac-banner.svg" alt="ti2dac: Threat Intelligence to Detection-as-Code" width="100%">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://pypi.org/project/ti2dac/"><img src="https://img.shields.io/pypi/v/ti2dac?label=PyPI&color=1f4e79" alt="PyPI version"></a>
7
+ <a href="https://pypi.org/project/ti2dac/"><img src="https://img.shields.io/pypi/pyversions/ti2dac" alt="Python versions"></a>
8
+ <a href="https://github.com/Sazidul0/TI2DaC/actions/workflows/ci.yml"><img src="https://github.com/Sazidul0/TI2DaC/actions/workflows/ci.yml/badge.svg" alt="Build status"></a>
9
+ <a href="https://github.com/Sazidul0/TI2DaC/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Sazidul0/TI2DaC" alt="License"></a>
10
+ <a href="https://github.com/Sazidul0/TI2DaC/stargazers"><img src="https://img.shields.io/github/stars/Sazidul0/TI2DaC?style=flat" alt="GitHub stars"></a>
11
+ <a href="https://github.com/Sazidul0/TI2DaC/issues"><img src="https://img.shields.io/github/issues/Sazidul0/TI2DaC" alt="GitHub issues"></a>
12
+ </p>
13
+
14
+ **Open-source Threat Intelligence to Detection-as-Code engine.** ti2dac turns threat reports and incident write-ups into evidence-checked Sigma rules, native queries for Splunk, Elastic, Sentinel, Defender, CrowdStrike, QRadar, OpenSearch, Loki and Wazuh, and safe simulation and test guides, so blue teams can go from a report to a reviewed detection package in minutes.
15
+
16
+ `sigma` · `detection-as-code` · `threat-intelligence` · `mitre-attack` · `siem` · `splunk` · `elastic` · `microsoft-sentinel` · `wazuh` · `blue-team` · `detection-engineering` · `llm`
17
+
18
+ # ti2dac: Threat Intelligence → Detection-as-Code
19
+
20
+ ti2dac turns a **threat-intelligence report or an internal incident report** into a reviewed, tested detection package:
21
+
22
+ - the attacker behaviours (TTPs) in the report, with evidence quotes checked against the source and verified MITRE ATT&CK mappings
23
+ - one vendor-neutral **Sigma** rule per behaviour (single-event or threshold/correlation), validated with pySigma
24
+ - the same rule as native queries for **Splunk, Elastic, Microsoft Sentinel, Defender XDR, CrowdStrike NG-SIEM, QRadar, OpenSearch and Grafana Loki**, with a per-SIEM field-mapping table
25
+ - a **simulation and test guide** per rule: a safe lab script and its cleanup, the matching Atomic Red Team test, expected events, and positive and false-positive test procedures
26
+ - **unit tests** for each rule, plus local replay of real lab or incident logs, so you know a rule fires *before* it reaches a SIEM
27
+ - **ADS** triage documentation, telemetry requirements, a validation matrix and an ATT&CK Navigator layer
28
+ - a **feedback and review workflow**: error taxonomy, versioned corrections, lessons fed back into the AI, four-eyes approval and an audit log
29
+
30
+ The AI does the reading and the detection writing. Everything that can be deterministic is deterministic: conversion,
31
+ validation, documentation, safety checks and redaction. This keeps token use low and the output reproducible.
32
+
33
+ ![ti2dac main flow: report, reader, gatekeeper, summariser, AI analysis, verification, AI rule drafting, inspection and repair, SIEM translation, packaging, then human testing, review and export to Git](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/ti2dac-overview.svg)
34
+
35
+ *Steps 1 to 9 run automatically. Steps 10 to 12 are done by people: ti2dac never runs the simulation scripts and never connects to your SIEM.*
36
+
37
+ ## Install
38
+
39
+ Requires Python 3.10 or newer.
40
+
41
+ ```bash
42
+ pip install ti2dac # or: pipx install ti2dac
43
+ pip install "ti2dac[gemini]" # optional: Google Gemini as the AI provider
44
+ pip install "ti2dac[evtx]" # optional: replay Windows .evtx evidence
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ From a source checkout (`git clone https://github.com/Sazidul0/TI2DaC && cd TI2DaC`):
50
+
51
+ ```bash
52
+ uv venv .venv && uv pip install --python .venv/bin/python -e ".[dev]"
53
+ cp .env.example .env # then add ANTHROPIC_API_KEY (see docs/getting-started.md)
54
+ cp ti2dac.example.yaml ti2dac.yaml # then set your domains, customer names, telemetry
55
+
56
+ ti2dac run path/to/report.html # public threat-intel report (file or URL)
57
+ ti2dac run IR-2026-0147.docx --incident --events host-sysmon.evtx # internal incident report + its evidence logs
58
+ ti2dac serve # web UI on http://127.0.0.1:8000
59
+ ```
60
+
61
+ The two-step flow lets an analyst choose behaviours before any rule-generation tokens are spent:
62
+
63
+ ```bash
64
+ ti2dac analyze report.html # 1 AI call: behaviours ranked as B1..Bn
65
+ ti2dac generate <run-id> --select B2,B5 # 1 AI call per chosen behaviour
66
+ ```
67
+
68
+ Without AI (`--no-ai` or `TI2DAC_NO_AI=1`), the deterministic parts still work: ingestion, IOC extraction,
69
+ cited ATT&CK IDs, and validation, conversion, unit tests and replay of hand-written rules.
70
+
71
+ ## Web UI
72
+
73
+ `ti2dac serve` starts a local web UI (bound to `127.0.0.1`) for analysts and reviewers, with logins and roles.
74
+
75
+ ![ti2dac Runs page: totals for reports, rules, approvals and AI usage, the New analysis form with document type choice, recent jobs and SIEM coverage](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/screenshot-runs.png)
76
+
77
+ *The Runs page: upload a report or paste a URL, choose the document type, and follow recent jobs and totals.*
78
+
79
+ ![ti2dac run page: extracted behaviours ranked by detectability, each with ATT&CK technique, verified evidence count and the rule generated from it](https://raw.githubusercontent.com/Sazidul0/TI2DaC/main/docs/screenshot-behaviours.png)
80
+
81
+ *A run: behaviours extracted from the report, ranked, with checked evidence quotes and ATT&CK mappings. Tick any behaviour without a rule to generate more.*
82
+
83
+ ## Safety at a glance
84
+
85
+ - **Your data stays home.** The AI sees only trimmed, redacted text. Names, hosts, internal IPs, emails and secrets are replaced with placeholders first. TLP:RED documents never go to an AI.
86
+ - **Nothing is executed.** Simulation scripts are linted for destructive or credential-stealing actions, and the tool never runs them. Nothing the model writes is run.
87
+ - **Nothing is trusted unchecked.** Evidence quotes, ATT&CK IDs, Sigma syntax and rule self-tests are verified by code before you see a rule.
88
+ - **People stay in charge.** The tool drafts. A second person (the approver) must approve what the author created, and only approved rules are exported.
89
+
90
+ Generated rules are drafts until you have tested them on real events. See [known limitations](docs/known-limitations.md).
91
+
92
+ ## Documentation
93
+
94
+ | Guide | What is in it |
95
+ |---|---|
96
+ | [Getting started](docs/getting-started.md) | AI provider setup, a step-by-step walk-through, supported inputs |
97
+ | [How it works](docs/how-it-works.md) | Architecture in plain terms, the working process, how tokens are kept low |
98
+ | [Quality, safety and testing](docs/quality-and-testing.md) | Safety controls and the three levels of proving a rule works |
99
+ | [SIEM coverage](docs/siem-coverage.md) | The 14 targets, Wazuh, correlations and client profiles |
100
+ | [Review and configuration](docs/governance-and-config.md) | Feedback, four-eyes approval, Git hand-off, `ti2dac.yaml` |
101
+ | [Operations](docs/operations.md) | Monitoring, web UI security, output package layout |
102
+ | [CLI reference](docs/cli-reference.md) | Every command |
103
+ | [Known limitations](docs/known-limitations.md) | What the tool does not do yet |
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ .venv/bin/python -m pytest -q # 254 tests, no network or API key needed
109
+ ```
110
+
111
+ Dependency pins that matter: pySigma must stay `0.11.x`, because the QRadar backend breaks on 2.x and newer
112
+ SQLite-backend releases silently pull in pySigma 2. `pyparsing` must stay below 3.2, which pySigma 0.11's condition
113
+ parser needs. `pySigma-backend-sqlite` must be `0.2.0`. `regex` and `dateparser` are pinned together (see `pyproject.toml`).
114
+
115
+ ## License
116
+
117
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,86 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ti2dac"
7
+ version = "0.1.0"
8
+ description = "Threat Intelligence to Detection-as-Code: TI report -> Sigma -> multi-SIEM queries + safe simulation guides"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Sazidul0" }]
14
+ keywords = ["threat-intelligence", "sigma", "detection-as-code", "mitre-attack", "siem", "splunk", "elastic", "microsoft-sentinel", "wazuh", "blue-team", "detection-engineering", "llm", "security"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Information Technology",
18
+ "Intended Audience :: Developers",
19
+ "Environment :: Console",
20
+ "Environment :: Web Environment",
21
+ "Topic :: Security",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Operating System :: OS Independent",
27
+ ]
28
+ dependencies = [
29
+ "anthropic>=2.3",
30
+ "pydantic>=2.7",
31
+ "pysigma>=0.11,<0.12",
32
+ "pyparsing>=3.1,<3.2", # pySigma 0.11 condition parser breaks on pyparsing 3.2+
33
+ # Exact pins: the sigma backends are tested together at these versions.
34
+ # Unpinned, pip backtracks through dozens of releases during install.
35
+ "sigma-cli==1.0.6",
36
+ "pySigma-backend-splunk==1.1.3",
37
+ "pySigma-backend-elasticsearch==1.1.6",
38
+ "pySigma-backend-kusto==0.4.5",
39
+ "pySigma-backend-crowdstrike==2.0.1",
40
+ "pySigma-backend-qradar-aql==0.3.2",
41
+ "pySigma-backend-opensearch==1.0.4",
42
+ "pySigma-backend-loki==0.12.4",
43
+ "pySigma-pipeline-sysmon==1.0.4",
44
+ "pySigma-pipeline-windows==1.2.0",
45
+ "pySigma-backend-sqlite==0.2.0", # local rule evaluation (self-tests, replay); later versions need pySigma 2
46
+ # pySigma-backend-qradar-aql needs regex<2024; dateparser>=1.4 (pulled in via trafilatura) needs regex>=2024.
47
+ # These two pins are the combination the project is tested with.
48
+ "regex==2023.12.25",
49
+ "dateparser==1.2.1",
50
+ "trafilatura>=1.8",
51
+ "beautifulsoup4",
52
+ "lxml",
53
+ "pypdf>=4",
54
+ "python-docx>=1.1", # incident reports in Word format
55
+ "pyyaml>=6",
56
+ "httpx>=0.27",
57
+ "typer>=0.12",
58
+ "fastapi>=0.110",
59
+ "uvicorn>=0.29",
60
+ "jinja2>=3.1",
61
+ "markdown-it-py>=3",
62
+ "python-multipart",
63
+ ]
64
+
65
+ [project.optional-dependencies]
66
+ dev = ["pytest>=8"]
67
+ gemini = ["google-genai>=1.30"]
68
+ evtx = ["python-evtx>=0.7", "defusedxml>=0.7"]
69
+
70
+ [project.urls]
71
+ Homepage = "https://github.com/Sazidul0/TI2DaC"
72
+ Repository = "https://github.com/Sazidul0/TI2DaC"
73
+ Issues = "https://github.com/Sazidul0/TI2DaC/issues"
74
+
75
+ [project.scripts]
76
+ ti2dac = "ti2dac.cli:app"
77
+
78
+ [tool.setuptools.packages.find]
79
+ where = ["src"]
80
+
81
+ [tool.setuptools.package-data]
82
+ ti2dac = ["data/*.json", "data/*.yaml", "data/*.yml", "web/templates/*.html", "web/static/*"]
83
+
84
+ [tool.pytest.ini_options]
85
+ testpaths = ["tests"]
86
+ filterwarnings = ["ignore::SyntaxWarning", "ignore::DeprecationWarning"]
ti2dac-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """ti2dac - Threat Intelligence to Detection-as-Code."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,169 @@
1
+ """AI provider health: limit detection, circuit breaker and request counting.
2
+
3
+ Shared by the CLI and the web UI through <state_dir>/ai_status.json, so that once a provider says
4
+ "quota exhausted" (or our own daily cap is reached) NO process sends further requests until the reset
5
+ time. Hammering a provider after it refused is wasteful and can look like abuse to the provider.
6
+
7
+ States per provider:
8
+ ok last call succeeded
9
+ rate_limited short-term limit (per-minute); calls resume after retry_at
10
+ quota_exhausted daily/monthly quota, credit or spend limit; calls blocked until retry_at (or manual reset)
11
+ daily_cap our own `ai.daily_request_limit` reached; resets at local midnight
12
+ auth_error key missing/invalid; blocked until reset
13
+ refused the model declined a request (informational; does not block)
14
+ unavailable provider down/overloaded after retries (informational; does not block)
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ from datetime import datetime, timedelta, timezone
21
+ from pathlib import Path
22
+
23
+ from .locks import file_lock
24
+
25
+ BLOCKING = {"rate_limited", "quota_exhausted", "daily_cap", "auth_error"}
26
+ DAILY_COUNTERS = ("requests_today", "input_tokens_today", "output_tokens_today", "cache_read_tokens_today", "cost_today_usd")
27
+ TOTAL_COUNTERS = ("input_tokens_total", "output_tokens_total", "cost_total_usd")
28
+
29
+
30
+ def _now() -> datetime:
31
+ return datetime.now(timezone.utc)
32
+
33
+
34
+ def _path(state_dir: str | Path) -> Path:
35
+ return Path(state_dir) / "ai_status.json"
36
+
37
+
38
+ def _locked(state_dir: str | Path):
39
+ """Read-modify-write of ai_status.json: the server, its job workers and the CLI all count requests."""
40
+ return file_lock(Path(state_dir) / "ai_status.lock")
41
+
42
+
43
+ def load(state_dir: str | Path) -> dict:
44
+ try:
45
+ return json.loads(_path(state_dir).read_text())
46
+ except (OSError, ValueError):
47
+ return {}
48
+
49
+
50
+ def _save(state_dir: str | Path, data: dict) -> None:
51
+ p = _path(state_dir)
52
+ p.parent.mkdir(parents=True, exist_ok=True)
53
+ tmp = p.with_suffix(f".{os.getpid()}.tmp")
54
+ tmp.write_text(json.dumps(data, indent=1))
55
+ tmp.replace(p)
56
+
57
+
58
+ def provider_status(state_dir: str | Path, provider: str) -> dict:
59
+ s = load(state_dir).get(provider, {})
60
+ today = datetime.now().date().isoformat()
61
+ if s.get("day") != today:
62
+ s = {**s, "day": today, **{k: 0 for k in DAILY_COUNTERS}}
63
+ if s.get("state") == "daily_cap":
64
+ s["state"] = "ok"
65
+ return s
66
+
67
+
68
+ def update(state_dir: str | Path, provider: str, **fields) -> dict:
69
+ with _locked(state_dir):
70
+ data = load(state_dir)
71
+ cur = provider_status(state_dir, provider) if provider in data else {
72
+ "day": datetime.now().date().isoformat(), "requests_today": 0}
73
+ cur.update(fields)
74
+ cur["updated_at"] = _now().isoformat(timespec="seconds")
75
+ data[provider] = cur
76
+ _save(state_dir, data)
77
+ return cur
78
+
79
+
80
+ def count_request(state_dir: str | Path, provider: str) -> int:
81
+ with _locked(state_dir):
82
+ data = load(state_dir)
83
+ cur = provider_status(state_dir, provider)
84
+ cur["requests_today"] = int(cur.get("requests_today", 0)) + 1
85
+ data[provider] = cur
86
+ _save(state_dir, data)
87
+ return cur["requests_today"]
88
+
89
+
90
+ def add_usage(state_dir: str | Path, provider: str, input_tokens: int, output_tokens: int,
91
+ cache_read_tokens: int, cost_usd: float) -> None:
92
+ """Token accounting per provider: today (resets at local midnight) and since the state dir was created."""
93
+ with _locked(state_dir):
94
+ data = load(state_dir)
95
+ cur = provider_status(state_dir, provider)
96
+ for key, val in (("input_tokens", input_tokens), ("output_tokens", output_tokens)):
97
+ cur[f"{key}_today"] = int(cur.get(f"{key}_today", 0)) + val
98
+ cur[f"{key}_total"] = int(cur.get(f"{key}_total", 0)) + val
99
+ cur["cache_read_tokens_today"] = int(cur.get("cache_read_tokens_today", 0)) + cache_read_tokens
100
+ cur["cost_today_usd"] = round(float(cur.get("cost_today_usd", 0)) + cost_usd, 4)
101
+ cur["cost_total_usd"] = round(float(cur.get("cost_total_usd", 0)) + cost_usd, 4)
102
+ data[provider] = cur
103
+ _save(state_dir, data)
104
+
105
+
106
+ def record_ok(state_dir: str | Path, provider: str) -> None:
107
+ update(state_dir, provider, state="ok", message="", retry_at="", last_success=_now().isoformat(timespec="seconds"))
108
+
109
+
110
+ def record_block(state_dir: str | Path, provider: str, state: str, message: str, retry_after_s: float | None) -> None:
111
+ retry_at = (_now() + timedelta(seconds=retry_after_s)).isoformat(timespec="seconds") if retry_after_s else ""
112
+ update(state_dir, provider, state=state, message=message[:500], retry_at=retry_at)
113
+
114
+
115
+ def reset(state_dir: str | Path, provider: str | None = None) -> None:
116
+ with _locked(state_dir):
117
+ data = load(state_dir)
118
+ for name in ([provider] if provider else list(data)):
119
+ if name in data:
120
+ data[name].update(state="ok", message="", retry_at="")
121
+ _save(state_dir, data)
122
+
123
+
124
+ def block_reason(state_dir: str | Path, provider: str, daily_limit: int = 0) -> str:
125
+ """Why calls to `provider` are blocked right now ('' = allowed). Never contacts the provider."""
126
+ s = provider_status(state_dir, provider)
127
+ state = s.get("state", "ok")
128
+ if daily_limit and int(s.get("requests_today", 0)) >= daily_limit:
129
+ return (f"{provider}: daily request cap reached ({s.get('requests_today')}/{daily_limit}, "
130
+ "ai.daily_request_limit). Resets at local midnight, or raise the cap.")
131
+ if state not in BLOCKING:
132
+ return ""
133
+ retry_at = s.get("retry_at", "")
134
+ if retry_at:
135
+ try:
136
+ if datetime.fromisoformat(retry_at) <= _now():
137
+ return "" # the provider's own reset time has passed
138
+ except ValueError:
139
+ pass
140
+ elif state == "rate_limited":
141
+ return ""
142
+ when = f" Retry after {retry_at} (UTC)." if retry_at else " Fix the cause, then reset with `ti2dac ai-status --reset` or the AI page."
143
+ return f"{provider}: {state.replace('_', ' ')}. {s.get('message', '')}".strip() + when
144
+
145
+
146
+ def human_wait(retry_at: str) -> str:
147
+ try:
148
+ secs = (datetime.fromisoformat(retry_at) - _now()).total_seconds()
149
+ except (ValueError, TypeError):
150
+ return ""
151
+ if secs <= 0:
152
+ return "now"
153
+ h, m = int(secs // 3600), int(secs % 3600 // 60)
154
+ return f"{h}h {m}m" if h else f"{m}m"
155
+
156
+
157
+ def key_status(provider: str) -> str:
158
+ """Where credentials would come from (never the key itself)."""
159
+ if provider == "gemini":
160
+ for var in ("GEMINI_API_KEY", "GOOGLE_API_KEY"):
161
+ if os.environ.get(var):
162
+ return f"{var} is set"
163
+ return "not configured"
164
+ for var in ("ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"):
165
+ if os.environ.get(var):
166
+ return f"{var} is set"
167
+ if os.environ.get("ANTHROPIC_PROFILE") or (Path.home() / ".config" / "anthropic").is_dir():
168
+ return "`ant auth login` profile found"
169
+ return "not configured"