scythe-ttp 0.17.4__py3-none-any.whl → 0.17.6__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.

Potentially problematic release.


This version of scythe-ttp might be problematic. Click here for more details.

scythe/auth/cookie_jwt.py CHANGED
@@ -49,14 +49,17 @@ class CookieJWTAuth(Authentication):
49
49
 
50
50
  Behavior:
51
51
  - In API mode: JourneyExecutor will call get_auth_cookies(); this class will
52
- perform a POST to login_url (if token not cached), parse JSON, extract the
53
- token via jwt_json_path, and return {cookie_name: token}.
52
+ perform a POST to login_url (if token not cached), extract the token, and
53
+ return {cookie_name: token}.
54
54
  - In UI mode: authenticate() will ensure the browser has the cookie set for
55
55
  the target domain.
56
56
 
57
57
  Parameters:
58
58
  - content_type: Either "json" (default) to send payload as JSON, or "form"
59
59
  to send as application/x-www-form-urlencoded form data.
60
+ - jwt_source: Either "json" (default) to extract JWT from the JSON response body
61
+ using jwt_json_path, or "cookie" to extract it from the Set-Cookie response header
62
+ using cookie_name.
60
63
  """
61
64
 
62
65
  def __init__(self,
@@ -69,6 +72,7 @@ class CookieJWTAuth(Authentication):
69
72
  jwt_json_path: str = "token",
70
73
  cookie_name: str = "stellarbridge",
71
74
  content_type: str = "json",
75
+ jwt_source: str = "json",
72
76
  session: Optional[requests.Session] = None,
73
77
  description: str = "Authenticate via API and set JWT cookie"):
74
78
  super().__init__(
@@ -84,6 +88,7 @@ class CookieJWTAuth(Authentication):
84
88
  self.jwt_json_path = jwt_json_path
85
89
  self.cookie_name = cookie_name
86
90
  self.content_type = content_type
91
+ self.jwt_source = jwt_source
87
92
  # Avoid importing requests in test environments; allow injected session
88
93
  self._session = session or (requests.Session() if requests is not None else None)
89
94
  self.token: Optional[str] = None
@@ -99,15 +104,32 @@ class CookieJWTAuth(Authentication):
99
104
  resp = self._session.post(self.login_url, json=payload, timeout=15)
100
105
  # try json; raise on non-2xx to surface errors
101
106
  resp.raise_for_status()
102
- data = resp.json()
103
107
  except Exception as e:
104
108
  raise AuthenticationError(f"Login request failed: {e}", self.name)
105
- token = _extract_by_dot_path(data, self.jwt_json_path)
106
- if not token or not isinstance(token, str):
107
- raise AuthenticationError(
108
- f"JWT not found at path '{self.jwt_json_path}' in login response",
109
- self.name,
110
- )
109
+
110
+ # Extract token from either response cookies or JSON body
111
+ token = None
112
+ if self.jwt_source == "cookie":
113
+ # Extract from response cookies
114
+ token = resp.cookies.get(self.cookie_name)
115
+ if not token or not isinstance(token, str):
116
+ raise AuthenticationError(
117
+ f"JWT cookie '{self.cookie_name}' not found in login response",
118
+ self.name,
119
+ )
120
+ else:
121
+ # Extract from JSON response body
122
+ try:
123
+ data = resp.json()
124
+ except Exception as e:
125
+ raise AuthenticationError(f"Failed to parse JSON response: {e}", self.name)
126
+ token = _extract_by_dot_path(data, self.jwt_json_path)
127
+ if not token or not isinstance(token, str):
128
+ raise AuthenticationError(
129
+ f"JWT not found at path '{self.jwt_json_path}' in login response",
130
+ self.name,
131
+ )
132
+
111
133
  self.token = token
112
134
  self.store_auth_data('jwt', token)
113
135
  self.store_auth_data('login_time', time.time())
scythe/cli/main.py CHANGED
@@ -71,16 +71,42 @@ def scythe_test_definition(args) -> bool:
71
71
 
72
72
  # Example usage with TTPExecutor:
73
73
  # from scythe.core.executor import TTPExecutor
74
- # executor = TTPExecutor(ttp=my_ttp, target_url=args.url)
74
+ # from scythe.ttps.web.login_bruteforce import LoginBruteforceTTP
75
+ #
76
+ # ttp = LoginBruteforceTTP(
77
+ # payloads=['admin', 'root', 'test'],
78
+ # expected_result=False # Expect security controls to block attempts
79
+ # )
80
+ # executor = TTPExecutor(ttp=ttp, target_url=args.url)
75
81
  # executor.run()
76
82
  # return executor.was_successful() # Returns True if all results matched expectations
77
83
 
78
84
  # Example usage with JourneyExecutor:
79
85
  # from scythe.journeys.executor import JourneyExecutor
80
- # executor = JourneyExecutor(journey=my_journey, target_url=args.url)
86
+ # from scythe.journeys.base import Journey, Step
87
+ # from scythe.journeys.actions import NavigateAction, FillFormAction, ClickAction
88
+ #
89
+ # journey = Journey(
90
+ # name="Login Journey",
91
+ # description="Test user login flow",
92
+ # expected_result=True # Expect journey to succeed
93
+ # )
94
+ # journey.add_step(Step("Navigate").add_action(NavigateAction(url=args.url)))
95
+ # executor = JourneyExecutor(journey=journey, target_url=args.url)
81
96
  # executor.run()
82
97
  # return executor.was_successful() # Returns True if journey succeeded as expected
83
98
 
99
+ # Example usage with Orchestrators:
100
+ # from scythe.orchestrators.scale import ScaleOrchestrator
101
+ # from scythe.orchestrators.base import OrchestrationStrategy
102
+ #
103
+ # orchestrator = ScaleOrchestrator(
104
+ # strategy=OrchestrationStrategy.PARALLEL,
105
+ # max_workers=10
106
+ # )
107
+ # result = orchestrator.orchestrate_ttp(ttp=my_ttp, target_url=args.url, replications=100)
108
+ # return orchestrator.exit_code(result) == 0 # Returns True if all executions succeeded
109
+
84
110
  return True
85
111
 
86
112
 
scythe/core/executor.py CHANGED
@@ -344,3 +344,12 @@ class TTPExecutor:
344
344
  True if all test results matched expectations, False otherwise
345
345
  """
346
346
  return not self.has_test_failures
347
+
348
+ def exit_code(self) -> int:
349
+ """
350
+ Get the exit code for this test execution.
351
+
352
+ Returns:
353
+ 0 if test was successful (results matched expectations), 1 otherwise
354
+ """
355
+ return 0 if self.was_successful() else 1
@@ -463,6 +463,15 @@ class JourneyExecutor:
463
463
  actual = self.execution_results.get('overall_success', False)
464
464
  expected = self.execution_results.get('expected_result', True)
465
465
  return actual == expected
466
+
467
+ def exit_code(self) -> int:
468
+ """
469
+ Get the exit code for this journey execution.
470
+
471
+ Returns:
472
+ 0 if journey was successful (results matched expectations), 1 otherwise
473
+ """
474
+ return 0 if self.was_successful() else 1
466
475
 
467
476
 
468
477
  class JourneyRunner:
@@ -281,6 +281,24 @@ class Orchestrator(ABC):
281
281
  self.logger.warning(f" ... and {len(result.errors) - 3} more errors")
282
282
 
283
283
  self.logger.info("="*60)
284
+
285
+ def exit_code(self, result: OrchestrationResult) -> int:
286
+ """
287
+ Get the exit code for an orchestration result.
288
+
289
+ An orchestration is considered successful if all executions completed
290
+ successfully (matching their expected results).
291
+
292
+ Args:
293
+ result: OrchestrationResult to evaluate
294
+
295
+ Returns:
296
+ 0 if all executions were successful, 1 otherwise
297
+ """
298
+ # Check if any executions failed or if there were errors
299
+ if result.failed_executions > 0 or len(result.errors) > 0:
300
+ return 1
301
+ return 0
284
302
 
285
303
 
286
304
  class ExecutionContext:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scythe-ttp
3
- Version: 0.17.4
3
+ Version: 0.17.6
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
@@ -3,7 +3,7 @@ scythe/auth/__init__.py,sha256=InEANqWEIAULFyzH9IyxWDPs_gJd3m_JYmzoaBk_37M,420
3
3
  scythe/auth/base.py,sha256=DllKaPGj0MRyRh4PQgQ2TUFgeAXjgXOT2h6zUz2ZAag,3807
4
4
  scythe/auth/basic.py,sha256=H4IG9-Y7wFe7ZQCNHmmqhre-Pp9CnBxlT23h2uvOPWo,14354
5
5
  scythe/auth/bearer.py,sha256=ngOL-sS6FcfB8XAKMR-CZbpqyySu2MaKxUl10SyBmmI,12687
6
- scythe/auth/cookie_jwt.py,sha256=YJP40A74hYrKSFXDcMSfmWlouOc_tPI4321UAswvRa4,6652
6
+ scythe/auth/cookie_jwt.py,sha256=A5ysCd6mVPNRj3aGNhk68GyM8A2hNW4BqCLZOQ8sa5U,7610
7
7
  scythe/behaviors/__init__.py,sha256=w-WRBGRgna5a1N8oHP2aXSQnkQUHyOXiujpwEVf_ZyM,291
8
8
  scythe/behaviors/base.py,sha256=INvIYKVIWzEi5w_4njOwKZ3X9IvySvqiMJnYX7_2Lns,3955
9
9
  scythe/behaviors/default.py,sha256=MDx4N-KwC23pPLGu1-ZIkGiTRNUG3Lxjbvo7SJ3UwMc,2117
@@ -11,17 +11,17 @@ 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=DFvOB39tX4FeiOxitJwXfq28J13GjewBZ9gOA_0HOjI,26003
14
+ scythe/cli/main.py,sha256=LUaaf8D6barO6IxdV_F-ML6hLyNE0rINGH32NRhryG4,27130
15
15
  scythe/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- scythe/core/executor.py,sha256=FTuhvWiNOsN1LZ4scEsVGDXx9D3yF3tiAoDt-HW2QRI,16038
16
+ scythe/core/executor.py,sha256=F8r_t5vSvsxNuUFx-Y0JzSe051cj_w42iyZrS7wocb4,16306
17
17
  scythe/core/headers.py,sha256=AokCQ3F5QGUcfoK7iO57hA1HHL4IznZeWV464_MqYcE,16670
18
18
  scythe/core/ttp.py,sha256=tEYIhDdr8kcwQrlcfVmdeLFiAfOvc0BhPOVxPh8TiWo,5676
19
19
  scythe/journeys/__init__.py,sha256=Odi8NhRg7Hefmo1EJj1guakrCSPhsuus4i-_62uUUjs,654
20
20
  scythe/journeys/actions.py,sha256=URr53p1GQxSIBZo0IubchQ1dlfvnPHgCtmkRfLSoi7A,40333
21
21
  scythe/journeys/base.py,sha256=vXIgEnSW__iYTriBbuMG4l_XCM96xojJH_fyFScKoBY,24969
22
- scythe/journeys/executor.py,sha256=uJkjO3PALSLZh3IOSxgX18gRJX_Bck3gW9OClusiQeE,24949
22
+ scythe/journeys/executor.py,sha256=wSAFFU9qwyYA2Q_5TZBUDgafVb1slwvj0VNJ7cR46FE,25223
23
23
  scythe/orchestrators/__init__.py,sha256=_vemcXjKbB1jI0F2dPA0F1zNsyUekjcXImLDUDhWDN0,560
24
- scythe/orchestrators/base.py,sha256=YOZV0ewlzJ49H08P_LKnimutUms8NnDrQprFpSKhOeM,13595
24
+ scythe/orchestrators/base.py,sha256=oZ68VmX18mBITvFUXfHJYvIE20PCfEAKrsikdr6_0Qg,14219
25
25
  scythe/orchestrators/batch.py,sha256=FpK501kk-earJzz6v7dcuw2y708rTvt_IMH_5qjKdrc,26635
26
26
  scythe/orchestrators/distributed.py,sha256=ts19NZzPfr0ouFbUFrktPO-iL5jBNB57mcOh0eDJDmE,33554
27
27
  scythe/orchestrators/scale.py,sha256=l2-4U6ISeBDBZz7CG6ef9W-zyF6LiAM4uXXKlfczLB0,21394
@@ -32,9 +32,9 @@ scythe/ttps/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  scythe/ttps/web/login_bruteforce.py,sha256=ybmN2Vl9-p58YbOchirY1193GvlaTmUTW1qlluN_l3I,7816
33
33
  scythe/ttps/web/sql_injection.py,sha256=rIRHaRUilSrMA5q5MO1RqR6-TM_fRIiCanPaFz5wKKs,11712
34
34
  scythe/ttps/web/uuid_guessing.py,sha256=JwNt_9HVynMWFPPU6UGJFcpxvMVDsvc_wAnJVtcYbps,1235
35
- scythe_ttp-0.17.4.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
- scythe_ttp-0.17.4.dist-info/METADATA,sha256=GxFEimDo4r_WDY2KHy26MvpRJZJmM07DKV9q2wfioCw,30188
37
- scythe_ttp-0.17.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- scythe_ttp-0.17.4.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
- scythe_ttp-0.17.4.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
- scythe_ttp-0.17.4.dist-info/RECORD,,
35
+ scythe_ttp-0.17.6.dist-info/licenses/LICENSE,sha256=B7iB4Fv6zDQolC7IgqNF8F4GEp_DLe2jrPPuR_MYMOM,1064
36
+ scythe_ttp-0.17.6.dist-info/METADATA,sha256=aDbgWW5k5aPpO5qgiORRyrYbSNLk6J-KQ6qaIGtnTx0,30188
37
+ scythe_ttp-0.17.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ scythe_ttp-0.17.6.dist-info/entry_points.txt,sha256=rAAsFBcCm0OX3I4uRyclfx4YJGoTuumZKY43HN7R5Ro,48
39
+ scythe_ttp-0.17.6.dist-info/top_level.txt,sha256=BCKTrPuVvmLyhOR07C1ggOh6sU7g2LoVvwDMn46O55Y,7
40
+ scythe_ttp-0.17.6.dist-info/RECORD,,