scythe-ttp 0.15.10__py3-none-any.whl → 0.16.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.
scythe/cli/main.py CHANGED
@@ -226,10 +226,8 @@ def _create_test(project_root: str, name: str) -> str:
226
226
 
227
227
  return filepath
228
228
 
229
-
230
- _VERSION_RE = re.compile(r"X-SCYTHE-TARGET-VERSION\s*[:=]\s*([\w\.-]+)")
231
- _DETECTED_LIST_RE = re.compile(r"Detected target versions: \[?([^\]]*)\]?")
232
-
229
+ _VERSION_RE = re.compile(r"['\"]?X-Scythe-Target-Version['\"]?\s*:\s*['\"]?([\w.-]+)['\"]?")
230
+ _DETECTED_LIST_RE = re.compile(r"Target versions detected:\s*\[?([^]]*)\]?")
233
231
 
234
232
  def _parse_version_from_output(output: str) -> Optional[str]:
235
233
  m = _VERSION_RE.search(output)
@@ -240,7 +238,7 @@ def _parse_version_from_output(output: str) -> Optional[str]:
240
238
  if m:
241
239
  inner = m.group(1)
242
240
  # extract first version-like token
243
- mv = re.search(r"[\d]+(?:\.[\w\-]+)+", inner)
241
+ mv = re.search(r"\d+(?:\.[\w\-]+)+", inner)
244
242
  if mv:
245
243
  return mv.group(0)
246
244
  return None
@@ -659,6 +659,7 @@ class ApiRequestAction(Action):
659
659
  def __init__(self,
660
660
  method: str,
661
661
  url: str,
662
+ flush: bool = False,
662
663
  params: Optional[Dict[str, Any]] = None,
663
664
  body_json: Optional[Dict[str, Any]] = None,
664
665
  data: Optional[Dict[str, Any]] = None,
@@ -673,6 +674,7 @@ class ApiRequestAction(Action):
673
674
  fail_on_validation_error: bool = False):
674
675
  self.method = method.upper()
675
676
  self.url = url
677
+ self.flush = flush
676
678
  self.params = params or {}
677
679
  self.body_json = body_json
678
680
  self.data = data
@@ -692,15 +694,15 @@ class ApiRequestAction(Action):
692
694
  if session is None:
693
695
  session = requests.Session()
694
696
  context['requests_session'] = session
695
-
696
- # Build headers: auth headers from context + action headers (action overrides)
697
+
698
+ # Build headers: auth headers from context and action headers (action overrides)
697
699
  final_headers = {}
698
700
  auth_headers = context.get('auth_headers', {}) or {}
699
701
  if auth_headers:
700
702
  final_headers.update(auth_headers)
701
703
  if self.headers:
702
704
  final_headers.update(self.headers)
703
-
705
+
704
706
  # Simple masking for sensitive headers
705
707
  def _mask_headers(headers: Dict[str, Any]) -> Dict[str, Any]:
706
708
  masked = {}
@@ -713,7 +715,7 @@ class ApiRequestAction(Action):
713
715
  else:
714
716
  masked[k] = v
715
717
  return masked
716
-
718
+
717
719
  # Resolve URL: absolute or join with target_url from context
718
720
  from urllib.parse import urljoin
719
721
  from ..core.headers import HeaderExtractor
@@ -725,7 +727,7 @@ class ApiRequestAction(Action):
725
727
  resolved_url = self.url
726
728
  else:
727
729
  resolved_url = urljoin(base_url, self.url)
728
-
730
+
729
731
  # Store request details early
730
732
  self.store_result('request_method', self.method)
731
733
  self.store_result('url', resolved_url)
@@ -736,7 +738,7 @@ class ApiRequestAction(Action):
736
738
  if self.data is not None:
737
739
  self.store_result('request_data', self.data)
738
740
  self.store_result('request_headers', _mask_headers(final_headers))
739
-
741
+
740
742
  logger = logging.getLogger("Journey.ApiRequestAction")
741
743
  # Honor any pending rate-limit resume time set by previous actions/steps
742
744
  try:
@@ -861,14 +863,25 @@ class ApiRequestAction(Action):
861
863
 
862
864
  # Determine success (status-based by default)
863
865
  if self.expected_status is not None:
864
- http_ok = (getattr(response, 'status_code', None) == self.expected_status)
866
+ http_ok = (status_code == self.expected_status)
867
+ if not http_ok:
868
+ self.store_result('status_mismatch', f"Expected status {self.expected_status}, got {status_code}")
869
+ logger.warning(f"API request status mismatch: expected {self.expected_status}, got {status_code}")
865
870
  else:
866
871
  http_ok = bool(getattr(response, 'ok', False))
867
872
 
873
+ # Store the final status check result
874
+ self.store_result('http_status_ok', http_ok)
875
+
868
876
  # Optionally fail on validation error
869
877
  if self.response_model is not None and self.fail_on_validation_error and self.get_result('response_validation_error'):
870
878
  return False
871
879
 
880
+ if self.flush:
881
+ clear_requests_session(context)
882
+
883
+ # Return False if status doesn't match expected, regardless of expected_result
884
+ # The framework will then compare this with expected_result to determine test outcome
872
885
  return http_ok
873
886
  except Exception as e:
874
887
  last_exception = e
@@ -878,3 +891,12 @@ class ApiRequestAction(Action):
878
891
 
879
892
  # If we got here and had an exception or no return, fail
880
893
  return False
894
+
895
+ def clear_requests_session(context: Dict[str, Any]):
896
+ """Clear the request session from the context."""
897
+ logger = logging.getLogger("Journey.ApiRequestAction")
898
+ session = context.get('requests_session')
899
+ if session is not None:
900
+ session.close()
901
+ context['requests_session'] = None
902
+ logger.info("Cleared requests session from context")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.15.10
3
+ Version: 0.16.1
4
4
  Summary: An extensible framework for emulating attacker TTPs with Selenium.
5
5
  Author-email: EpykLab <cyber@epyklab.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -11,13 +11,13 @@ scythe/behaviors/human.py,sha256=1PqYvE7cnxlj-KDmDIr3uzfWHvDAbbxQxJ0V0iTl9yo,102
11
11
  scythe/behaviors/machine.py,sha256=NDMUq3mDhpCvujzAFxhn2eSVq78-J-LSBhIcvHkzKXo,4624
12
12
  scythe/behaviors/stealth.py,sha256=xv7MrPQgRCdCUJyYTcXV2aasWZoAw8rAQWg-AuQVb7U,15278
13
13
  scythe/cli/__init__.py,sha256=9EVxmFiWsAoqWJ6br1bc3BxlA71JyOQP28fUHhX2k7E,43
14
- scythe/cli/main.py,sha256=_6O6wxp4JV4k1vCSl5mMjO42-uIwTI0HoK7JRBfBHKk,21356
14
+ scythe/cli/main.py,sha256=-PXmdzYzlS5KWndVd35CoxAK17wxfLEmF_KUotJrHc0,21373
15
15
  scythe/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  scythe/core/executor.py,sha256=x1w2nByVu2G70sh7t0kOh6urlrTm_r_pbk0S7v1Ov28,9736
17
17
  scythe/core/headers.py,sha256=AokCQ3F5QGUcfoK7iO57hA1HHL4IznZeWV464_MqYcE,16670
18
18
  scythe/core/ttp.py,sha256=Xw9GgptYsjZ-pMLdyPv64bhiwGKobrXHdF32pjIY7OU,3102
19
19
  scythe/journeys/__init__.py,sha256=Odi8NhRg7Hefmo1EJj1guakrCSPhsuus4i-_62uUUjs,654
20
- scythe/journeys/actions.py,sha256=cDBYdhY5pCXKG-57-op8gH8z9u3_wbIOhwqSZ2Z_jDs,36432
20
+ scythe/journeys/actions.py,sha256=k9WjfGR1nhJWyhDU_lHr7vFy5qAl7hyyV6kCL7ZQRMo,37479
21
21
  scythe/journeys/base.py,sha256=vXIgEnSW__iYTriBbuMG4l_XCM96xojJH_fyFScKoBY,24969
22
22
  scythe/journeys/executor.py,sha256=_q2hzl4G9iv07I6NVMtNaK3O8QGLDwLNMiaxIle-nsY,24654
23
23
  scythe/orchestrators/__init__.py,sha256=_vemcXjKbB1jI0F2dPA0F1zNsyUekjcXImLDUDhWDN0,560
@@ -32,9 +32,9 @@ scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  scythe/ttps/web/login_bruteforce.py,sha256=D4G8zB_nU9LD5w3Vv2ABTuOl4XTeg2BgZwYMObt4JJw,2488
33
33
  scythe/ttps/web/sql_injection.py,sha256=aWk4DFePbtFDsieOOj03Ux-5OiykyOs2_d_3SvWMOVE,2910
34
34
  scythe/ttps/web/uuid_guessing.py,sha256=JwNt_9HVynMWFPPU6UGJFcpxvMVDsvc_wAnJVtcYbps,1235
35
- scythe_ttp-0.15.10.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
- scythe_ttp-0.15.10.dist-info/METADATA,sha256=4X30Y0NEHNX67xf1fVA_PQOnLEwnVeDdbNv8mTL6wV8,30162
37
- scythe_ttp-0.15.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- scythe_ttp-0.15.10.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
- scythe_ttp-0.15.10.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
- scythe_ttp-0.15.10.dist-info/RECORD,,
35
+ scythe_ttp-0.16.1.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
+ scythe_ttp-0.16.1.dist-info/METADATA,sha256=LxmrtQniVraYXKXFAlrTE66DYvHC-vGQNp6Wk6HmCDA,30161
37
+ scythe_ttp-0.16.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ scythe_ttp-0.16.1.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
+ scythe_ttp-0.16.1.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
+ scythe_ttp-0.16.1.dist-info/RECORD,,