txt2detection 1.0.2__tar.gz → 1.0.5__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.

Potentially problematic release.


This version of txt2detection might be problematic. Click here for more details.

Files changed (55) hide show
  1. {txt2detection-1.0.2 → txt2detection-1.0.5}/PKG-INFO +25 -1
  2. {txt2detection-1.0.2 → txt2detection-1.0.5}/README.md +24 -0
  3. {txt2detection-1.0.2 → txt2detection-1.0.5}/pyproject.toml +1 -1
  4. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_main.py +6 -0
  5. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_utils.py +1 -1
  6. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/__main__.py +8 -1
  7. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/base.py +11 -1
  8. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/bundler.py +2 -2
  9. txt2detection-1.0.5/txt2detection/credential_checker.py +80 -0
  10. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/models.py +4 -4
  11. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/utils.py +6 -4
  12. {txt2detection-1.0.2 → txt2detection-1.0.5}/.env.example +0 -0
  13. {txt2detection-1.0.2 → txt2detection-1.0.5}/.env.markdown +0 -0
  14. {txt2detection-1.0.2 → txt2detection-1.0.5}/.github/workflows/create-release.yml +0 -0
  15. {txt2detection-1.0.2 → txt2detection-1.0.5}/.github/workflows/run-tests.yml +0 -0
  16. {txt2detection-1.0.2 → txt2detection-1.0.5}/.gitignore +0 -0
  17. {txt2detection-1.0.2 → txt2detection-1.0.5}/LICENSE +0 -0
  18. {txt2detection-1.0.2 → txt2detection-1.0.5}/config/detection_languages.yaml +0 -0
  19. {txt2detection-1.0.2 → txt2detection-1.0.5}/docs/README.md +0 -0
  20. {txt2detection-1.0.2 → txt2detection-1.0.5}/docs/txt2detection.png +0 -0
  21. {txt2detection-1.0.2 → txt2detection-1.0.5}/requirements.txt +0 -0
  22. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/CVE-2024-56520.txt +0 -0
  23. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/EC2-exfil.txt +0 -0
  24. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/observables.txt +0 -0
  25. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-custom-tags.yml +0 -0
  26. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-existing-related.yml +0 -0
  27. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-master.yml +0 -0
  28. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-author.yml +0 -0
  29. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-date.yml +0 -0
  30. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-description.yml +0 -0
  31. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-level.yml +0 -0
  32. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-license.yml +0 -0
  33. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-status.yml +0 -0
  34. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-tags.yml +0 -0
  35. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-no-title.yml +0 -0
  36. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-observables.yml +0 -0
  37. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/files/sigma-rule-one-date.yml +0 -0
  38. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/manual-tests/README.md +0 -0
  39. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/__init__.py +0 -0
  40. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/requirements.txt +0 -0
  41. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_bundler.py +0 -0
  42. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_main_run_txt2detction.py +0 -0
  43. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_models.py +0 -0
  44. {txt2detection-1.0.2 → txt2detection-1.0.5}/tests/src/test_observables.py +0 -0
  45. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/__init__.py +0 -0
  46. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/__init__.py +0 -0
  47. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/anthropic.py +0 -0
  48. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/deepseek.py +0 -0
  49. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/gemini.py +0 -0
  50. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/openai.py +0 -0
  51. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/openrouter.py +0 -0
  52. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/prompts.py +0 -0
  53. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/ai_extractor/utils.py +0 -0
  54. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection/observables.py +0 -0
  55. {txt2detection-1.0.2 → txt2detection-1.0.5}/txt2detection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: txt2detection
3
- Version: 1.0.2
3
+ Version: 1.0.5
4
4
  Summary: A command line tool that takes a txt file containing threat intelligence and turns it into a detection rule.
5
5
  Project-URL: Homepage, https://github.com/muchdogesec/txt2detection
6
6
  Project-URL: Issues, https://github.com/muchdogesec/txt2detection/issues
@@ -100,6 +100,30 @@ cp .env.example .env
100
100
 
101
101
  To see more information about how to set the variables, and what they do, read the `.env.markdown` file.
102
102
 
103
+ Then test your configoration
104
+
105
+ ```shell
106
+ python3 txt2detection.py \
107
+ check-credentials
108
+ ```
109
+
110
+ It will return a response to show what API keys are working
111
+
112
+ ```txt
113
+ ============= Service Statuses ===============
114
+ ctibutler : authorized ✔
115
+ vulmatch : authorized ✔
116
+
117
+ LLMS:
118
+ openai : authorized ✔
119
+ deepseek : unsupported –
120
+ gemini : unsupported –
121
+ openrouter : unsupported –
122
+ anthropic : unsupported –
123
+ ```
124
+
125
+ Not all services need to be configured, if you have no intention of using them.
126
+
103
127
  ### Run
104
128
 
105
129
  ```shell
@@ -67,6 +67,30 @@ cp .env.example .env
67
67
 
68
68
  To see more information about how to set the variables, and what they do, read the `.env.markdown` file.
69
69
 
70
+ Then test your configoration
71
+
72
+ ```shell
73
+ python3 txt2detection.py \
74
+ check-credentials
75
+ ```
76
+
77
+ It will return a response to show what API keys are working
78
+
79
+ ```txt
80
+ ============= Service Statuses ===============
81
+ ctibutler : authorized ✔
82
+ vulmatch : authorized ✔
83
+
84
+ LLMS:
85
+ openai : authorized ✔
86
+ deepseek : unsupported –
87
+ gemini : unsupported –
88
+ openrouter : unsupported –
89
+ anthropic : unsupported –
90
+ ```
91
+
92
+ Not all services need to be configured, if you have no intention of using them.
93
+
70
94
  ### Run
71
95
 
72
96
  ```shell
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "txt2detection"
7
- version = "1.0.2"
7
+ version = "1.0.5"
8
8
  authors = [
9
9
  { name = "dogesec" }
10
10
  ]
@@ -135,3 +135,9 @@ def test_parse_args_sigma_mode_no_ai_provider(monkeypatch):
135
135
  assert getattr(args, "ai_provider", None) is None
136
136
  assert hasattr(args, "report_id")
137
137
  assert hasattr(args, "sigma_file")
138
+
139
+
140
+ def test_parse_args_check_credentials(monkeypatch):
141
+ monkeypatch.setattr(sys, "argv", ["prog", "check-credentials"])
142
+ with pytest.raises(SystemExit):
143
+ parse_args()
@@ -23,7 +23,7 @@ def test_as_date_with_datetime_and_date():
23
23
  assert as_date(d_obj) == d_obj
24
24
 
25
25
  def test_validate_token_count():
26
- provider = parse_model('openai')
26
+ provider = parse_model('anthropic')
27
27
  with patch.object(type(provider), 'count_tokens') as mock_count_tokens:
28
28
  mock_count_tokens.return_value = 1025
29
29
  with pytest.raises(Exception):
@@ -13,6 +13,7 @@ import uuid
13
13
  from stix2 import Identity
14
14
  import yaml
15
15
 
16
+ from txt2detection import credential_checker
16
17
  from txt2detection.ai_extractor.base import BaseAIExtractor
17
18
  from txt2detection.models import TAG_PATTERN, DetectionContainer, Level, SigmaRuleDetection
18
19
  from txt2detection.utils import validate_token_count
@@ -91,6 +92,7 @@ def parse_args():
91
92
  file = mode.add_parser('file', help="process a file input using ai")
92
93
  text = mode.add_parser('text', help="process a text argument using ai")
93
94
  sigma = mode.add_parser('sigma', help="process a sigma file without ai")
95
+ check_credentials = mode.add_parser('check-credentials', help="show status of external services with respect to credentials")
94
96
 
95
97
  for mode_parser in [file, text, sigma]:
96
98
  mode_parser.add_argument('--report_id', type=uuid.UUID, help='report_id to use for generated report')
@@ -115,7 +117,10 @@ def parse_args():
115
117
  sigma.add_argument('--level', help="If passed, will overwrite any existing `level` recorded in the rule", choices=Level._member_names_)
116
118
 
117
119
  args: Args = parser.parse_args()
118
- print(args)
120
+ if args.mode == "check-credentials":
121
+ statuses = credential_checker.check_statuses(test_llms=True)
122
+ credential_checker.format_statuses(statuses)
123
+ sys.exit(0)
119
124
 
120
125
  if args.mode != 'sigma':
121
126
  assert args.ai_provider, "--ai_provider is required in file or txt mode"
@@ -129,6 +134,8 @@ def parse_args():
129
134
  return args
130
135
 
131
136
 
137
+
138
+
132
139
  def run_txt2detection(name, identity, tlp_level, input_text: str, labels: list[str], report_id: str|uuid.UUID, ai_provider: BaseAIExtractor, **kwargs) -> Bundler:
133
140
  if sigma := kwargs.get('sigma_file'):
134
141
  detection = get_sigma_detections(sigma)
@@ -54,4 +54,14 @@ class BaseAIExtractor():
54
54
 
55
55
  @property
56
56
  def extractor_name(self):
57
- return f"{self.provider}:{self.llm.model}"
57
+ return f"{self.provider}:{self.llm.model}"
58
+
59
+ def check_credential(self):
60
+ try:
61
+ return "authorized" if self._check_credential() else "unauthorized"
62
+ except:
63
+ return "unknown"
64
+
65
+ def _check_credential(self):
66
+ self.llm.complete("say 'hi'")
67
+ return True
@@ -244,7 +244,7 @@ class Bundler:
244
244
 
245
245
  headers = {}
246
246
  if api_key := os.environ.get('CTIBUTLER_API_KEY'):
247
- headers['Authorization'] = "Bearer " + api_key
247
+ headers['API-KEY'] = api_key
248
248
 
249
249
  return self._get_objects(endpoint, headers)
250
250
 
@@ -255,7 +255,7 @@ class Bundler:
255
255
  endpoint = urljoin(os.environ['VULMATCH_BASE_URL'] + '/', f"v1/cve/objects/?cve_id="+','.join(cve_ids))
256
256
  headers = {}
257
257
  if api_key := os.environ.get('VULMATCH_API_KEY'):
258
- headers['Authorization'] = "Bearer " + api_key
258
+ headers['API-KEY'] = api_key
259
259
 
260
260
  return self._get_objects(endpoint, headers)
261
261
 
@@ -0,0 +1,80 @@
1
+ import argparse
2
+ import os
3
+ import random
4
+ from urllib.parse import urljoin
5
+ import requests
6
+
7
+
8
+
9
+ def check_llms():
10
+ from txt2detection.__main__ import parse_model
11
+
12
+ auth_info = dict()
13
+ for model_name in ["openai", "deepseek", "gemini", "openrouter", "anthropic"]:
14
+ try:
15
+ model = parse_model(model_name)
16
+ auth_info[model_name] = model.check_credential()
17
+ except argparse.ArgumentTypeError:
18
+ auth_info[model_name] = "unsupported"
19
+ except:
20
+ auth_info[model_name] = "unauthorized"
21
+ return auth_info
22
+
23
+
24
+ def check_ctibutler_vulmatch(service):
25
+ session = requests.Session()
26
+ if service == 'vulmatch':
27
+ base_url = os.getenv('VULMATCH_BASE_URL')
28
+ url = urljoin(base_url, 'v1/cve/objects/vulnerability--f552f6f4-39da-48dc-8717-323772c99588/')
29
+ session.headers['API-KEY'] = os.environ.get('VULMATCH_API_KEY')
30
+ elif service == 'ctibutler':
31
+ base_url = os.getenv('CTIBUTLER_BASE_URL')
32
+ url = urljoin(base_url, 'v1/location/versions/available/')
33
+ session.headers['API-KEY'] = os.environ.get('CTIBUTLER_API_KEY')
34
+
35
+ try:
36
+ resp = session.get(url)
37
+ match resp.status_code:
38
+ case 401 | 403:
39
+ return "unauthorized"
40
+ case 200:
41
+ return "authorized"
42
+ case _:
43
+ return "unknown"
44
+ except:
45
+ return "offline"
46
+
47
+
48
+ def check_statuses(test_llms=False):
49
+ statuses = dict(
50
+ ctibutler=check_ctibutler_vulmatch("ctibutler"),
51
+ vulmatch=check_ctibutler_vulmatch("vulmatch"),
52
+ )
53
+ if test_llms:
54
+ statuses.update(llms=check_llms())
55
+ return statuses
56
+
57
+
58
+ def format_statuses(status_dict):
59
+ def get_marker(status):
60
+ """Return a checkmark, cross, or dash based on status."""
61
+ match status.lower():
62
+ case "authorized":
63
+ return "✔"
64
+ case "unauthorized":
65
+ return "✖"
66
+ case "unknown" | "offline" | "unsupported":
67
+ return "–"
68
+ case _:
69
+ return "?"
70
+
71
+ print("============= Service Statuses ===============")
72
+ for key, value in status_dict.items():
73
+ if key == "llms" and isinstance(value, dict):
74
+ print(f"\n {key.upper()}:")
75
+ for llm_name, llm_status in value.items():
76
+ marker = get_marker(llm_status)
77
+ print(f" {llm_name:<12}: {llm_status:<15} {marker}")
78
+ else:
79
+ marker = get_marker(value)
80
+ print(f" {key:<12}: {value:<15} {marker}")
@@ -3,9 +3,10 @@ import json
3
3
  import re
4
4
  import typing
5
5
  import uuid
6
+ import requests
6
7
  from slugify import slugify
7
8
  from datetime import date as dt_date
8
- from typing import Any, List, Literal, Optional, Union
9
+ from typing import Any, ClassVar, List, Literal, Optional, Union
9
10
  from uuid import UUID
10
11
 
11
12
  import jsonschema
@@ -194,6 +195,7 @@ class BaseDetection(BaseModel):
194
195
  level: Level
195
196
  _custom_id = None
196
197
  _extra_data: dict
198
+ sigma_json_schema: ClassVar = requests.get("https://github.com/SigmaHQ/sigma-specification/raw/refs/heads/main/json-schema/sigma-detection-rule-schema.json").json()
197
199
 
198
200
  def model_post_init(self, __context):
199
201
  self.tags = self.tags or []
@@ -249,9 +251,7 @@ class BaseDetection(BaseModel):
249
251
  def validate_rule_with_json_schema(self, rule):
250
252
  jsonschema.validate(
251
253
  rule,
252
- {
253
- "$ref": "https://github.com/SigmaHQ/sigma-specification/raw/refs/heads/main/json-schema/sigma-detection-rule-schema.json"
254
- },
254
+ self.sigma_json_schema,
255
255
  )
256
256
 
257
257
  @property
@@ -55,11 +55,13 @@ def validate_token_count(max_tokens, input, extractor: BaseAIExtractor):
55
55
  if token_count > max_tokens:
56
56
  raise Exception(f"{extractor.extractor_name}: input_file token count ({token_count}) exceeds INPUT_TOKEN_LIMIT ({max_tokens})")
57
57
 
58
+
59
+ @lru_cache(maxsize=5)
60
+ def get_licenses(date):
61
+ resp = requests.get("https://github.com/spdx/license-list-data/raw/refs/heads/main/json/licenses.json")
62
+ return {l['licenseId']: l['name'] for l in resp.json()['licenses']}
63
+
58
64
  def valid_licenses():
59
- @lru_cache(maxsize=5)
60
- def get_licenses(date):
61
- resp = requests.get("https://github.com/spdx/license-list-data/raw/refs/heads/main/json/licenses.json")
62
- return {l['licenseId']: l['name'] for l in resp.json()['licenses']}
63
65
  return get_licenses(datetime.now().date().isoformat())
64
66
 
65
67
 
File without changes
File without changes