contentctl 5.4.1__py3-none-any.whl → 5.5.1__py3-none-any.whl

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.
@@ -7,7 +7,6 @@ import pathlib
7
7
  import time
8
8
  import urllib.parse
9
9
  import uuid
10
- from shutil import copyfile
11
10
  from ssl import SSLEOFError, SSLZeroReturnError
12
11
  from sys import stdout
13
12
  from tempfile import TemporaryDirectory, mktemp
@@ -1402,7 +1401,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1402
1401
  f"The only valid indexes on the server are {self.all_indexes_on_server}"
1403
1402
  )
1404
1403
 
1405
- tempfile = mktemp(dir=tmp_dir)
1406
1404
  if not (
1407
1405
  str(attack_data_file.data).startswith("http://")
1408
1406
  or str(attack_data_file.data).startswith("https://")
@@ -1415,13 +1413,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1415
1413
  test_group_start_time,
1416
1414
  )
1417
1415
 
1418
- try:
1419
- copyfile(str(attack_data_file.data), tempfile)
1420
- except Exception as e:
1421
- raise Exception(
1422
- f"Error copying local Attack Data File for [{test_group.name}] - [{attack_data_file.data}]: "
1423
- f"{str(e)}"
1424
- )
1416
+ tempfile = str(attack_data_file.data)
1425
1417
  else:
1426
1418
  raise Exception(
1427
1419
  f"Attack Data File for [{test_group.name}] is local [{attack_data_file.data}], but does not exist."
@@ -1432,6 +1424,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1432
1424
  # We need to overwrite the file - mkstemp will create an empty file with the
1433
1425
  # given name
1434
1426
  try:
1427
+ tempfile = mktemp(dir=tmp_dir)
1435
1428
  # In case the path is a local file, try to get it
1436
1429
 
1437
1430
  self.format_pbar_string(
contentctl/contentctl.py CHANGED
@@ -68,6 +68,7 @@ def init_func(config: test):
68
68
 
69
69
 
70
70
  def validate_func(config: validate) -> DirectorOutputDto:
71
+ config.check_test_data_caches()
71
72
  validate = Validate()
72
73
  return validate.execute(config)
73
74
 
@@ -913,7 +913,7 @@ class Detection_Abstract(SecurityContentObject):
913
913
  return self
914
914
 
915
915
  @field_validator("tests", mode="before")
916
- def ensure_yml_test_is_unittest(cls, v: list[dict]):
916
+ def ensure_yml_test_is_unittest(cls, v: list[dict], info: ValidationInfo):
917
917
  """The typing for the tests field allows it to be one of
918
918
  a number of different types of tests. However, ONLY
919
919
  UnitTest should be allowed to be defined in the YML
@@ -941,7 +941,7 @@ class Detection_Abstract(SecurityContentObject):
941
941
  for unitTest in v:
942
942
  # This raises a ValueError on a failed UnitTest.
943
943
  try:
944
- UnitTest.model_validate(unitTest)
944
+ UnitTest.model_validate(unitTest, context=info.context)
945
945
  except ValueError as e:
946
946
  valueErrors.append(e)
947
947
  if len(valueErrors):
@@ -26,6 +26,7 @@ from pydantic import (
26
26
  field_validator,
27
27
  model_validator,
28
28
  )
29
+ from requests import RequestException, head
29
30
 
30
31
  from contentctl.helper.splunk_app import SplunkApp
31
32
  from contentctl.helper.utils import Utils
@@ -261,6 +262,37 @@ class init(Config_Base):
261
262
  )
262
263
 
263
264
 
265
+ # There can be a number of attack data file warning mapping exceptions, or errors,
266
+ # that can occur when using attack data caches. In order to avoid very complex
267
+ # output, we will only emit the verbose versions of these message once per file.
268
+ # This is a non-intuitive place to put this, but it is good enough for now.
269
+ ATTACK_DATA_CACHE_MAPPING_EXCEPTIONS: set[str] = set()
270
+
271
+
272
+ class AttackDataCache(BaseModel):
273
+ base_url: str = Field(
274
+ "This is the beginning of a URL that the data must begin with to map to this cache object."
275
+ )
276
+ base_directory_name: str = Field(
277
+ "This is the root folder name where the attack data should be downloaded to. Note that this path MUST be in the external_repos/ folder",
278
+ pattern=r"^external_repos/.+",
279
+ )
280
+ # suggested checkout information for our attack_data repo
281
+ # curl https://attack-range-attack-data.s3.us-west-2.amazonaws.com/attack_data.tar.zstd | zstd --decompress | tar -x -C attack_data/
282
+ # suggested YML values for this:
283
+ helptext: str | None = Field(
284
+ default="This repo is set up to use test_data_caches. This can be extremely helpful in validating correct links for test attack_data and speeding up testing.\n"
285
+ "Include the following in your contentctl.yml file to use this cache:\n\n"
286
+ "test_data_caches:\n"
287
+ "- base_url: https://media.githubusercontent.com/media/splunk/attack_data/master/\n"
288
+ " base_directory_name: external_repos/attack_data\n\n"
289
+ "In order to check out STRT Attack Data, you can use the following command:\n"
290
+ "mkdir -p external_repos; curl https://attack-range-attack-data.s3.us-west-2.amazonaws.com/attack_data.tar.zstd | zstd --decompress | tar -x -C external_repos/\n"
291
+ "or\n"
292
+ """echo "First ensure git-lfs is enabled"; git clone https://github.com/splunk/attack_data external_repos/attack_data"""
293
+ )
294
+
295
+
264
296
  class validate(Config_Base):
265
297
  model_config = ConfigDict(validate_default=True, arbitrary_types_allowed=True)
266
298
  enforce_deprecation_mapping_requirement: bool = Field(
@@ -291,10 +323,151 @@ class validate(Config_Base):
291
323
  default=False, description="Validate latest TA information from Splunkbase"
292
324
  )
293
325
 
326
+ test_data_caches: list[AttackDataCache] = Field(
327
+ default=[],
328
+ description="A list of attack data that can "
329
+ "be used in lieu of the HTTPS download links "
330
+ "of each test data file. This cache can significantly "
331
+ "increase overall test speed, ensure the correctness of "
332
+ "links at 'contentctl validate' time, and reduce errors "
333
+ "associated with failed responses from file servers.",
334
+ )
335
+
294
336
  @property
295
337
  def external_repos_path(self) -> pathlib.Path:
296
338
  return self.path / "external_repos"
297
339
 
340
+ # We can't make this a validator because the constructor
341
+ # is called many times - we don't want to print this out many times.
342
+ def check_test_data_caches(self) -> Self:
343
+ """
344
+ Check that the test data caches actually exist at the specified paths.
345
+ If they do exist, then do nothing. If they do not, then emit the helpext, but
346
+ do not raise an exception. They are not required, but can significantly speed up
347
+ and reduce the flakiness of tests by reducing failed HTTP requests.
348
+ """
349
+ if not self.verbose:
350
+ # Ignore the check and error output if we are not in verbose mode
351
+ return self
352
+ for cache in self.test_data_caches:
353
+ cache_path = self.path / cache.base_directory_name
354
+ if not cache_path.is_dir():
355
+ print(cache.helptext)
356
+ else:
357
+ build_date_file = cache_path / "cache_build_date.txt"
358
+ git_hash_file = cache_path / "git_hash.txt"
359
+
360
+ if build_date_file.is_file():
361
+ # This is a cache that was built by contentctl. We can use this to
362
+ # determine if the cache is out of date.
363
+ with open(build_date_file, "r") as f:
364
+ build_date = f.read().strip()
365
+ else:
366
+ build_date = "<UNKNOWN_DATE>"
367
+ if git_hash_file.is_file():
368
+ # This is a cache that was built by contentctl. We can use this to
369
+ # determine if the cache is out of date.
370
+ with open(git_hash_file, "r") as f:
371
+ git_hash = f.read().strip()
372
+ else:
373
+ git_hash = "<UNKNOWN_HASH>"
374
+
375
+ print(
376
+ f"Found attack data cache at [{cache_path}]\n**Cache Build Date: {build_date}\n**Repo Git Hash : {git_hash}\n"
377
+ )
378
+
379
+ return self
380
+
381
+ def map_to_attack_data_cache(
382
+ self, filename: HttpUrl | FilePath, verbose: bool = False
383
+ ) -> HttpUrl | FilePath:
384
+ if str(filename) in ATTACK_DATA_CACHE_MAPPING_EXCEPTIONS:
385
+ # This is already something that we have emitted a warning or
386
+ # Exception for. We don't want to emit it again as it will
387
+ # pollute the output.
388
+ return filename
389
+
390
+ # If this is simply a link to a file directly, then no mapping
391
+ # needs to take place. Return the link to the file.
392
+ if isinstance(filename, pathlib.Path):
393
+ return filename
394
+
395
+ if len(self.test_data_caches) == 0:
396
+ return filename
397
+
398
+ # Otherwise, this is a URL. See if its prefix matches one of the
399
+ # prefixes in the list of caches
400
+ for cache in self.test_data_caches:
401
+ root_folder_path = self.path / cache.base_directory_name
402
+ # See if this data file was in that path
403
+
404
+ if str(filename).startswith(cache.base_url):
405
+ new_file_name = str(filename).replace(cache.base_url, "")
406
+ new_file_path = root_folder_path / new_file_name
407
+
408
+ if not root_folder_path.is_dir():
409
+ # This has not been checked out. Even though we want to use this cache
410
+ # whenever possible, we don't want to force it.
411
+ return filename
412
+
413
+ if new_file_path.is_file():
414
+ # We found the file in the cache. Return the new path
415
+ return new_file_path
416
+
417
+ # Any thing below here is non standard behavior that will produce either a warning message,
418
+ # an error, or both. We onyl want to do this once for each file, even if it is used
419
+ # across multiple different detections.
420
+ ATTACK_DATA_CACHE_MAPPING_EXCEPTIONS.add(str(filename))
421
+
422
+ # The cache exists, but we didn't find the file. We will emit an informational warning
423
+ # for this, but this is not an exception. Instead, we will just fall back to using
424
+ # the original URL.
425
+ if verbose:
426
+ # Give some extra context about missing attack data files/bad mapping
427
+ try:
428
+ h = head(str(filename))
429
+ h.raise_for_status()
430
+
431
+ except RequestException:
432
+ raise ValueError(
433
+ f"Error resolving the attack_data file {filename}. "
434
+ f"It was missing from the cache {cache.base_directory_name} and a download from the server failed."
435
+ )
436
+ print(
437
+ f"\nFilename {filename} not found in cache {cache.base_directory_name}, but exists on the server. "
438
+ f"Your cache {cache.base_directory_name} may be out of date."
439
+ )
440
+ return filename
441
+ if verbose:
442
+ # Any thing below here is non standard behavior that will produce either a warning message,
443
+ # an error, or both. We onyl want to do this once for each file, even if it is used
444
+ # across multiple different detections.
445
+ ATTACK_DATA_CACHE_MAPPING_EXCEPTIONS.add(str(filename))
446
+
447
+ # Give some extra context about missing attack data files/bad mapping
448
+ url = f"Attack Data : {filename}"
449
+ prefixes = "".join(
450
+ [
451
+ f"\n Valid Prefix: {cache.base_url}"
452
+ for cache in self.test_data_caches
453
+ ]
454
+ )
455
+ # Give some extra context about missing attack data files/bad mapping
456
+ try:
457
+ h = head(str(filename))
458
+ h.raise_for_status()
459
+ except RequestException:
460
+ raise ValueError(
461
+ f"Error resolving the attack_data file {filename}. It was missing from all caches and a download from the server failed.\n"
462
+ f"{url}{prefixes}\n"
463
+ )
464
+
465
+ print(
466
+ f"\nAttack Data Missing from all caches, but present at URL:\n{url}{prefixes}"
467
+ )
468
+
469
+ return filename
470
+
298
471
  @property
299
472
  def mitre_cti_repo_path(self) -> pathlib.Path:
300
473
  return self.external_repos_path / "cti"
@@ -1,5 +1,19 @@
1
1
  from __future__ import annotations
2
- from pydantic import BaseModel, HttpUrl, FilePath, Field, ConfigDict
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from contentctl.objects.config import validate
7
+
8
+ from pydantic import (
9
+ BaseModel,
10
+ ConfigDict,
11
+ Field,
12
+ FilePath,
13
+ HttpUrl,
14
+ ValidationInfo,
15
+ field_validator,
16
+ )
3
17
 
4
18
 
5
19
  class TestAttackData(BaseModel):
@@ -11,3 +25,24 @@ class TestAttackData(BaseModel):
11
25
  sourcetype: str = Field(...)
12
26
  custom_index: str | None = None
13
27
  host: str | None = None
28
+
29
+ @field_validator("data", mode="after")
30
+ @classmethod
31
+ def check_for_existence_of_attack_data_repo(
32
+ cls, value: HttpUrl | FilePath, info: ValidationInfo
33
+ ) -> HttpUrl | FilePath:
34
+ # this appears to be called more than once, the first time
35
+ # info.context is always None. In this case, just return what
36
+ # was passed.
37
+ if not info.context:
38
+ return value
39
+
40
+ # When the config is passed, used it to determine if we can map
41
+ # the test data to a file on disk
42
+ if info.context.get("config", None):
43
+ config: validate = info.context.get("config", None)
44
+ return config.map_to_attack_data_cache(value, verbose=config.verbose)
45
+ else:
46
+ raise ValueError(
47
+ "config not passed to TestAttackData constructor in context"
48
+ )
@@ -1,7 +1,6 @@
1
1
  ### {{app.label}} DETECTIONS ###
2
2
 
3
3
  {% for detection in objects %}
4
- {% if (detection.type == 'TTP' or detection.type == 'Anomaly' or detection.type == 'Hunting' or detection.type == 'Correlation') %}
5
4
  [{{ detection.get_conf_stanza_name(app) }}]
6
5
  action.escu = 0
7
6
  action.escu.enabled = 1
@@ -9,23 +8,13 @@ description = {{ detection.status_aware_description | escapeNewlines() }}
9
8
  action.escu.mappings = {{ detection.mappings | tojson }}
10
9
  action.escu.data_models = {{ detection.datamodel | tojson }}
11
10
  action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
12
- {% if detection.how_to_implement %}
13
11
  action.escu.how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
14
- {% else %}
15
- action.escu.how_to_implement = none
16
- {% endif %}
17
- {% if detection.known_false_positives %}
18
12
  action.escu.known_false_positives = {{ detection.known_false_positives | escapeNewlines() }}
19
- {% else %}
20
- action.escu.known_false_positives = None
21
- {% endif %}
22
13
  action.escu.creation_date = {{ detection.date }}
23
14
  action.escu.modification_date = {{ detection.date }}
24
15
  action.escu.confidence = high
25
16
  action.escu.search_type = detection
26
- {% if detection.tags.product is defined %}
27
17
  action.escu.product = {{ detection.tags.product | tojson }}
28
- {% endif %}
29
18
  {% if detection.tags.atomic_guid %}
30
19
  action.escu.atomic_red_team_guids = {{ detection.tags.getAtomicGuidStringArray() | tojson }}
31
20
  {% endif %}
@@ -34,7 +23,6 @@ action.escu.providing_technologies = {{ detection.providing_technologies | tojso
34
23
  {% else %}
35
24
  action.escu.providing_technologies = null
36
25
  {% endif %}
37
- {% if detection.tags.analytic_story %}
38
26
  action.escu.analytic_story = {{ objectListToNameList(detection.tags.analytic_story) | tojson }}
39
27
  {% if detection.deployment.alert_action.rba.enabled%}
40
28
  action.risk = 1
@@ -43,25 +31,19 @@ action.risk.param._risk = {{ detection.risk | tojson }}
43
31
  action.risk.param._risk_score = 0
44
32
  action.risk.param.verbose = 0
45
33
  {% endif %}
46
- {% else %}
47
- action.escu.analytic_story = []
48
- {% endif %}
49
34
  cron_schedule = {{ detection.deployment.scheduling.cron_schedule }}
50
35
  dispatch.earliest_time = {{ detection.deployment.scheduling.earliest_time }}
51
36
  dispatch.latest_time = {{ detection.deployment.scheduling.latest_time }}
52
37
  action.correlationsearch.enabled = 1
38
+ action.correlationsearch.detection_type = ebd
53
39
  action.correlationsearch.label = {{ detection.get_action_dot_correlationsearch_dot_label(app) }}
54
40
  action.correlationsearch.annotations = {{ detection.annotations | tojson }}
55
41
  action.correlationsearch.metadata = {{ detection.metadata | tojson }}
56
- {% if detection.deployment.scheduling.schedule_window is defined %}
57
42
  schedule_window = {{ detection.deployment.scheduling.schedule_window }}
58
- {% endif %}
59
- {% if detection.deployment is defined %}
60
43
  {% if detection.deployment.alert_action.notable %}
61
44
  action.notable = 1
62
- {% if detection.nes_fields %}
45
+ action.notable.param._entities = [{"risk_object_field": "N/A", "risk_object_type": "N/A", "risk_score": 0}]
63
46
  action.notable.param.nes_fields = {{ detection.nes_fields }}
64
- {% endif %}
65
47
  action.notable.param.rule_description = {{ detection.deployment.alert_action.notable.rule_description | custom_jinja2_enrichment_filter(detection) | escapeNewlines()}}
66
48
  action.notable.param.rule_title = {% if detection.type | lower == "correlation" %}RBA: {{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% else %}{{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% endif +%}
67
49
  action.notable.param.security_domain = {{ detection.tags.security_domain }}
@@ -87,13 +69,8 @@ action.sendtophantom.param.phantom_server = {{ detection.deployment.alert_action
87
69
  action.sendtophantom.param.sensitivity = {{ detection.deployment.alert_action.phantom.sensitivity | custom_jinja2_enrichment_filter(detection) }}
88
70
  action.sendtophantom.param.severity = {{ detection.deployment.alert_action.phantom.severity | custom_jinja2_enrichment_filter(detection) }}
89
71
  {% endif %}
90
- {% endif %}
91
72
  alert.digest_mode = 1
92
- {% if detection.enabled_by_default %}
93
- disabled = false
94
- {% else %}
95
- disabled = true
96
- {% endif %}
73
+ disabled = {{ (not detection.enabled_by_default) | lower }}
97
74
  enableSched = 1
98
75
  allow_skew = 100%
99
76
  counttype = number of events
@@ -108,7 +85,6 @@ alert.suppress.period = {{ detection.tags.throttling.period }}
108
85
  {% endif %}
109
86
  search = {{ detection.search | escapeNewlines() }}
110
87
  action.notable.param.drilldown_searches = {{ detection.drilldowns_in_JSON | tojson | escapeNewlines() }}
111
- {% endif %}
112
88
 
113
89
  {% endfor %}
114
90
  ### END {{ app.label }} DETECTIONS ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 5.4.1
3
+ Version: 5.5.1
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -4,7 +4,7 @@ contentctl/actions/deploy_acs.py,sha256=w3OqO8GXzB_5zHrE8lDYbadAy4Etw7F2o84Gze74
4
4
  contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=94apBwLkXWsgdLSvE9f_KqCfQSdmDChMncMcsEdY1A8,10974
5
5
  contentctl/actions/detection_testing/GitService.py,sha256=HU1fKkb5463weqSZ3LrTVHtNrzBH_f5pE99-zD2j1A8,11345
6
6
  contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=bGUVKjKv96lTw1GZ4Kw1JX-Yicu4aOJWm-IL524e9HI,2302
7
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=nJVo_L3Y4V0Uk7VCGHY58waGCBKcfujIFmxKC83oVgY,61082
7
+ contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=Th2QFQeGL3a-aUp83fUCm0aApQIcXjspetNj920fd34,60776
8
8
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=qYWgRW7uc-15jzwv5xSUF2xyLDmtyGyMfuXkQK9j-aM,7221
9
9
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py,sha256=Q1ZfCYOp54O39bgTScZMInkmZiU-bGAM9Hiwr2mq5ms,370
10
10
  contentctl/actions/detection_testing/progress_bar.py,sha256=UrpNCqxTmQ4hfoRZgxPJ1xvDVwMrTq0UnotdryHN0gM,3232
@@ -21,7 +21,7 @@ contentctl/actions/reporting.py,sha256=GF32i7sHdc47bw-VWSW-nZ1QBaUl6Ni1JjV5_SOyi
21
21
  contentctl/actions/test.py,sha256=ftZazqoqv7bLNhyW23aRnDpetG9zltS8wr4Xq9Hls0k,6268
22
22
  contentctl/actions/validate.py,sha256=teqRVxNlUgzDvKQm-sXb05TST05duA2-NhJOzNxlBTw,6152
23
23
  contentctl/api.py,sha256=6s17vNOW1E1EzQqOCXAa5uWuhwwShu-JkGSgrsOFEMs,6329
24
- contentctl/contentctl.py,sha256=nR8nHxXY0elvQogVHFqsyid7Ch5sMnIiNAOFbCa0yzI,11755
24
+ contentctl/contentctl.py,sha256=Wpy5GmgQIVoWs9PBDDKS0E1U2810J8WEfzfxckMGB-c,11791
25
25
  contentctl/enrichments/attack_enrichment.py,sha256=68C9xQ8Q3YX-luRdK2hLnwWtRFpheFA2kE4v5GOLGEo,6358
26
26
  contentctl/enrichments/cve_enrichment.py,sha256=TsZ52ef2njt19lPf_VyclY_-5Z5iQ1boVOAxFbjGdSQ,2431
27
27
  contentctl/enrichments/splunk_app_enrichment.py,sha256=Xynxjjkqlw0_RtQ1thGSFwy1I3HdmPAJmNKZezyqniU,3419
@@ -32,7 +32,7 @@ contentctl/helper/utils.py,sha256=1_6cbvvbPXWxym3ZhRhL18ttmXLXiHbavpXAkROtGcg,21
32
32
  contentctl/input/director.py,sha256=Em7ZwYdIapgr7Qd--uX6UxkeSyT1oxwyY0pius2Wbqc,17431
33
33
  contentctl/input/new_content_questions.py,sha256=z2C4Mg7-EyxtiF2z9m4SnSbi6QO4CUPB3wg__JeMXIQ,4067
34
34
  contentctl/input/yml_reader.py,sha256=L27b14_xXQYypUV1eAzZrfMtwtkzAZx--6nRo3RNZnE,2729
35
- contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=Gsb6v44VpWeGnMi79viJ0zO81pZ9GuiaqQJyHM-ba5I,46861
35
+ contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=bvX3iDQvR7BI1KIgKFBbyX7QkijhPANo9GzBCMVwtm8,46905
36
36
  contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=un1tjoZdxKi07dSddPD5MXRN3tea3dj0poKmFrbCu-k,34206
37
37
  contentctl/objects/alert_action.py,sha256=iEvdEOT4TrTXT0z4rQ_W5v79hPJpPhFPSzo7TuHDxwA,1376
38
38
  contentctl/objects/annotated_types.py,sha256=xR4EKvdOpNDEt0doGs8XjxCzKK99J2NHZgHFAmt7p2c,424
@@ -42,7 +42,7 @@ contentctl/objects/base_test.py,sha256=JG6qlr7xe9P71n3CzKOro8_bsmDQGYDfTG9YooHQS
42
42
  contentctl/objects/base_test_result.py,sha256=TYYzTPKWqp9rHTebWoid50uxAp_iALZouril4sFwIcA,5197
43
43
  contentctl/objects/baseline.py,sha256=EMcuz_9sVgOFh3YCj871GSAA6v3FIkRTf90-LAHq-J0,3700
44
44
  contentctl/objects/baseline_tags.py,sha256=Eomy8y3HV-E6Lym5B5ZZTtsmQJYi6Jd4y8GZpTWGYgQ,1643
45
- contentctl/objects/config.py,sha256=8F_zpcnFyE_rSdGomm2qeVDG6tTpjhrVyK6BsNHM8js,49357
45
+ contentctl/objects/config.py,sha256=3sJN48BuWXsCRjrdYyHTjZnUmVHGt9aW3_QaMBaghRw,57953
46
46
  contentctl/objects/constants.py,sha256=VwwQtJBGC_zb3ukjb3A7P0CwAlyhacWiXczwAW5Jiog,5466
47
47
  contentctl/objects/content_versioning_service.py,sha256=BDk_TV1PTVoXpPcUxqTLa5_bjkfOs9PFYgqTuzOS9UI,20566
48
48
  contentctl/objects/correlation_search.py,sha256=tvcFeHGFRyZso50KHsEVCjTmHh3rHDegcfqaD-TjCFg,51473
@@ -84,7 +84,7 @@ contentctl/objects/savedsearches_conf.py,sha256=Dn_Pxd9i3RT6DwNh6JrgmfxjsO3q15xz
84
84
  contentctl/objects/security_content_object.py,sha256=2mEf-wt3hMsLEyo4yatyK66jKjgUOVjJHIN9fgQB5nA,246
85
85
  contentctl/objects/story.py,sha256=1JCiF9D1EZeVcoMXXDoWkOqHXQn4TsQgl8EtUN59a2E,5796
86
86
  contentctl/objects/story_tags.py,sha256=IYumFuBF2Bt7HtW4lBfCRo2EUpjMYlnNjpx24jBErs4,2365
87
- contentctl/objects/test_attack_data.py,sha256=7p-kOJguTZtG9y5th5U3qfPFvpiAWLST_OBw8dwWl_4,488
87
+ contentctl/objects/test_attack_data.py,sha256=jXpaThVBntzKkRm1CPjTpaXDbKPu8xNghwuQ1pcGkoo,1513
88
88
  contentctl/objects/test_group.py,sha256=r-dXyddok4yslv8SIjwOpqylbN1rdjsRi-HIijvpWD0,2602
89
89
  contentctl/objects/threat_object.py,sha256=CB3igcmiq06lqnEh7h-btxFrBfgZbHaA9p8kFDKY6lQ,712
90
90
  contentctl/objects/throttling.py,sha256=oupWmdtvwAXzLmD3MBJyAU18SD2L2ciEZWUcnL8MuGk,2309
@@ -124,7 +124,7 @@ contentctl/output/templates/header.j2,sha256=3usV7jm1q6J-QNnQrZzII9cN0XEGQjg_eVK
124
124
  contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaBLX2Dge7Y,390
125
125
  contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
126
126
  contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
127
- contentctl/output/templates/savedsearches_detections.j2,sha256=mbOSSDoHwTfzpxQRVz03FvFNQJ26lWqcUtl6uW6tUZY,5874
127
+ contentctl/output/templates/savedsearches_detections.j2,sha256=xb4G7ikTD89cK7FF2TxwuFO1t9AQ43-DmT2LaemUMP4,5326
128
128
  contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
129
129
  contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
130
130
  contentctl/output/templates/transforms.j2,sha256=TEKZi8DWpcCysRTNvuLEgAwx-g1SZ2E0CkLiu6v6AlU,1339
@@ -164,8 +164,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
164
164
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
165
165
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
166
166
  contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
167
- contentctl-5.4.1.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
168
- contentctl-5.4.1.dist-info/METADATA,sha256=GIUiuvC402I8PSCzCg-E3ylBBxJu0ctM_E_jAMYEV6Y,5134
169
- contentctl-5.4.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
170
- contentctl-5.4.1.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
171
- contentctl-5.4.1.dist-info/RECORD,,
167
+ contentctl-5.5.1.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
168
+ contentctl-5.5.1.dist-info/METADATA,sha256=XhbhGN1aMAw53wwWqnr7_ykYAR0P9tAwUVi6erB6Kzo,5134
169
+ contentctl-5.5.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
170
+ contentctl-5.5.1.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
171
+ contentctl-5.5.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.2
2
+ Generator: poetry-core 2.1.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any