@hoanghainh/komagu 2.4.0 → 2.5.0
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.
- package/CHANGELOG.md +24 -0
- package/LICENSE +202 -0
- package/README.md +1 -0
- package/assess/lsa/scripts/detect_stack.py +13 -5
- package/assess/lsa/scripts/find_injection.py +35 -2
- package/assess/lsa/scripts/java_routes.py +323 -0
- package/assess/lsa/scripts/lsa_common.py +72 -2
- package/assess/lsa/scripts/map_routes.py +36 -160
- package/assess/lsa/scripts/merge_findings.py +2 -0
- package/assess/lsa/tests/test_java_legacy_realistic.py +453 -0
- package/contracts/schemas/persona-suite.schema.json +38 -2
- package/examples/member-portal/backend-dotnet/Dockerfile +2 -1
- package/examples/member-portal/backend-spring/Dockerfile +2 -0
- package/examples/member-portal/docker-compose.yml +1 -1
- package/examples/member-portal/frontend/Dockerfile +3 -2
- package/examples/member-portal/frontend/package-lock.json +24 -656
- package/examples/member-portal/frontend/package.json +1 -1
- package/operate/ai/runner.py +20 -2
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/fix.py +4 -0
- package/operate/cli/commands/leak_check.py +75 -0
- package/operate/cli/commands/protect.py +32 -3
- package/operate/cli/commands/test.py +131 -15
- package/operate/cli/main.py +5 -3
- package/operate/cross_user_tester/persona_matrix.py +118 -25
- package/operate/cross_user_tester/runner.py +190 -7
- package/operate/cross_user_tester/session_config.py +180 -0
- package/operate/cross_user_tester/session_http.py +288 -0
- package/operate/devsecops/leak_check.py +314 -0
- package/operate/remediator/runner.py +18 -1
- package/operate/remediator/strategies.py +2 -2
- package/operate/simulate/scenarios.py +2 -2
- package/operate/tests/mock_legacy_session_app.py +160 -0
- package/operate/tests/test_cross_user_session.py +565 -0
- package/operate/tests/test_docker_packaging.py +3 -3
- package/operate/tests/test_headless_ai.py +6 -0
- package/operate/tests/test_leak_check.py +250 -0
- package/operate/tests/test_legacy_session_edge.py +192 -0
- package/operate/tests/test_pilot_safeguards.py +245 -0
- package/operate/tests/test_remediator_java_legacy.py +330 -0
- package/package.json +2 -1
- package/remediate/authz/patcher.py +120 -11
- package/remediate/authz/tests/test_bola_patcher.py +4 -1
- package/remediate/edge/coraza/Caddyfile +1 -1
- package/remediate/edge/coraza/Dockerfile +9 -0
- package/remediate/edge/coraza/rules/coraza-crs-custom.conf +12 -0
- package/remediate/edge/coraza/virtual_patcher.py +24 -0
- package/remediate/edge/docker-compose.yml +7 -2
- package/remediate/edge/kong/kong-legacy-session.yaml +51 -0
- package/remediate/edge/mock-backend/Dockerfile +2 -0
- package/remediate/edge/tests/test_edge_security.py +21 -1
- package/remediate/resilience/terraform/backup_worm.tf +39 -1
- package/remediate/resilience/terraform/main.tf +15 -3
- package/remediate/resilience/terraform/variables.tf +11 -0
- package/remediate/resilience/tests/test_resilience.py +16 -0
|
@@ -9,8 +9,9 @@ to call it. Every other persona is sent the request and must be refused:
|
|
|
9
9
|
normal write plus privileged fields; reading the object back shows that a
|
|
10
10
|
privileged field was stored.
|
|
11
11
|
|
|
12
|
-
Tokens are read only from environment variables (``token_env``).
|
|
13
|
-
|
|
12
|
+
Tokens are read only from environment variables (``token_env``). Session
|
|
13
|
+
cookies come from an environment variable or a file (``cookie_env`` /
|
|
14
|
+
``cookie_file``). Neither appears in a suite file, a result or a report.
|
|
14
15
|
|
|
15
16
|
Python standard library only.
|
|
16
17
|
"""
|
|
@@ -24,14 +25,31 @@ import time
|
|
|
24
25
|
import urllib.error
|
|
25
26
|
import urllib.parse
|
|
26
27
|
import urllib.request
|
|
27
|
-
from dataclasses import dataclass, field
|
|
28
|
+
from dataclasses import dataclass, field, replace
|
|
28
29
|
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
|
29
30
|
|
|
31
|
+
from operate.cross_user_tester.session_config import read_cookie
|
|
32
|
+
from operate.cross_user_tester.session_http import (
|
|
33
|
+
ALLOWED,
|
|
34
|
+
DEFAULT_DENY_STATUS,
|
|
35
|
+
DENIED,
|
|
36
|
+
INCONCLUSIVE,
|
|
37
|
+
STRICT_DENY_STATUS,
|
|
38
|
+
CsrfConfig,
|
|
39
|
+
DenialSignal,
|
|
40
|
+
HttpReply,
|
|
41
|
+
SessionConfigError,
|
|
42
|
+
SessionRequest,
|
|
43
|
+
classify,
|
|
44
|
+
deliver,
|
|
45
|
+
is_form_action,
|
|
46
|
+
parse_body_format,
|
|
47
|
+
parse_csrf,
|
|
48
|
+
)
|
|
30
49
|
from operate.lsa_sarif.sarif_findings import findings_from_sarif, is_sarif
|
|
31
50
|
|
|
32
51
|
SUITE_VERSION = 2
|
|
33
|
-
DENY_STATUS =
|
|
34
|
-
STRICT_DENY_STATUS = (404,)
|
|
52
|
+
DENY_STATUS = DEFAULT_DENY_STATUS
|
|
35
53
|
ALLOW_STATUS = (200, 201, 202, 204)
|
|
36
54
|
WRITE_METHODS = ("POST", "PUT", "PATCH")
|
|
37
55
|
HTTP_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE")
|
|
@@ -40,8 +58,8 @@ MAX_BODY_BYTES = 1_000_000
|
|
|
40
58
|
|
|
41
59
|
_ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
42
60
|
_PLACEHOLDER = re.compile(r"\{([A-Za-z0-9_-]+)\}|:([A-Za-z_][A-Za-z0-9_]*)")
|
|
43
|
-
_PERSONA_KEYS = {"token_env", "description"}
|
|
44
|
-
_ROUTE_KEYS = {"name", "method", "path", "params", "payload", "allow", "mass_assignment_probe"}
|
|
61
|
+
_PERSONA_KEYS = {"token_env", "cookie_env", "cookie_file", "description"}
|
|
62
|
+
_ROUTE_KEYS = {"name", "method", "path", "params", "payload", "allow", "mass_assignment_probe", "body_format", "csrf"}
|
|
45
63
|
_PROBE_KEYS = {"fields", "persona", "readback"}
|
|
46
64
|
|
|
47
65
|
|
|
@@ -53,6 +71,8 @@ class SuiteError(ValueError):
|
|
|
53
71
|
class Persona:
|
|
54
72
|
name: str
|
|
55
73
|
token_env: Optional[str] = None
|
|
74
|
+
cookie_env: Optional[str] = None
|
|
75
|
+
cookie_file: Optional[str] = None
|
|
56
76
|
|
|
57
77
|
|
|
58
78
|
@dataclass(frozen=True)
|
|
@@ -68,6 +88,8 @@ class MatrixCase:
|
|
|
68
88
|
probe_fields: Optional[Dict[str, Any]] = None
|
|
69
89
|
readback: Optional[Tuple[str, str]] = None
|
|
70
90
|
finding_id: Optional[str] = None
|
|
91
|
+
body_format: str = "json"
|
|
92
|
+
csrf: Optional[CsrfConfig] = None
|
|
71
93
|
|
|
72
94
|
|
|
73
95
|
@dataclass(frozen=True)
|
|
@@ -121,7 +143,14 @@ def _parse_personas(raw: Any) -> Dict[str, Persona]:
|
|
|
121
143
|
env = spec.get("token_env")
|
|
122
144
|
if env is not None and (not isinstance(env, str) or not _ENV_NAME.match(env)):
|
|
123
145
|
raise SuiteError(f"persona '{name}': token_env must be an environment variable name")
|
|
124
|
-
|
|
146
|
+
cookie_env, cookie_file = spec.get("cookie_env"), spec.get("cookie_file")
|
|
147
|
+
if cookie_env is not None and (not isinstance(cookie_env, str) or not _ENV_NAME.match(cookie_env)):
|
|
148
|
+
raise SuiteError(f"persona '{name}': cookie_env must be an environment variable name")
|
|
149
|
+
if cookie_file is not None and not isinstance(cookie_file, str):
|
|
150
|
+
raise SuiteError(f"persona '{name}': cookie_file must be a path")
|
|
151
|
+
if sum(v is not None for v in (env, cookie_env, cookie_file)) > 1:
|
|
152
|
+
raise SuiteError(f"persona '{name}': use only one of token_env, cookie_env, cookie_file")
|
|
153
|
+
personas[name] = Persona(name=name, token_env=env, cookie_env=cookie_env, cookie_file=cookie_file)
|
|
125
154
|
return personas
|
|
126
155
|
|
|
127
156
|
|
|
@@ -154,6 +183,8 @@ def _route_cases(
|
|
|
154
183
|
route: Any,
|
|
155
184
|
personas: Dict[str, Persona],
|
|
156
185
|
default_id: str,
|
|
186
|
+
default_body_format: str = "json",
|
|
187
|
+
default_csrf: Optional[CsrfConfig] = None,
|
|
157
188
|
) -> List[MatrixCase]:
|
|
158
189
|
if not isinstance(route, dict):
|
|
159
190
|
raise SuiteError(f"routes[{index}] must be an object")
|
|
@@ -179,6 +210,9 @@ def _route_cases(
|
|
|
179
210
|
if payload is not None and not isinstance(payload, dict):
|
|
180
211
|
raise SuiteError(f"routes[{index}]: 'payload' must be an object")
|
|
181
212
|
|
|
213
|
+
body_format = parse_body_format(route.get("body_format"), f"routes[{index}]", default_body_format)
|
|
214
|
+
csrf = parse_csrf(route.get("csrf"), f"routes[{index}]", default_csrf)
|
|
215
|
+
|
|
182
216
|
path = fill_path(raw_path, params, default_id)
|
|
183
217
|
label = route.get("name") or f"{method} {raw_path}"
|
|
184
218
|
cases = [
|
|
@@ -189,6 +223,8 @@ def _route_cases(
|
|
|
189
223
|
method=method,
|
|
190
224
|
path=path,
|
|
191
225
|
payload=payload if method in WRITE_METHODS else None,
|
|
226
|
+
body_format=body_format,
|
|
227
|
+
csrf=csrf,
|
|
192
228
|
)
|
|
193
229
|
for name, p in personas.items()
|
|
194
230
|
if name not in allow
|
|
@@ -196,7 +232,11 @@ def _route_cases(
|
|
|
196
232
|
|
|
197
233
|
probe = route.get("mass_assignment_probe")
|
|
198
234
|
if probe is not None:
|
|
199
|
-
cases.append(
|
|
235
|
+
cases.append(replace(
|
|
236
|
+
_probe_case(index, label, method, path, payload, probe, allow, personas, params, default_id),
|
|
237
|
+
body_format=body_format,
|
|
238
|
+
csrf=csrf,
|
|
239
|
+
))
|
|
200
240
|
return cases
|
|
201
241
|
|
|
202
242
|
|
|
@@ -237,7 +277,12 @@ def _probe_case(
|
|
|
237
277
|
)
|
|
238
278
|
|
|
239
279
|
|
|
240
|
-
def load_persona_suite(
|
|
280
|
+
def load_persona_suite(
|
|
281
|
+
data: Dict[str, Any],
|
|
282
|
+
default_id: str = "1",
|
|
283
|
+
default_body_format: str = "json",
|
|
284
|
+
default_csrf: Optional[CsrfConfig] = None,
|
|
285
|
+
) -> List[MatrixCase]:
|
|
241
286
|
"""Build matrix cases from a parsed v2 suite."""
|
|
242
287
|
if not is_v2_suite(data):
|
|
243
288
|
raise SuiteError(f"suite 'version' must be {SUITE_VERSION}")
|
|
@@ -247,7 +292,7 @@ def load_persona_suite(data: Dict[str, Any], default_id: str = "1") -> List[Matr
|
|
|
247
292
|
raise SuiteError("'routes' must be a non-empty list")
|
|
248
293
|
cases: List[MatrixCase] = []
|
|
249
294
|
for i, route in enumerate(routes):
|
|
250
|
-
cases.extend(_route_cases(i, route, personas, default_id))
|
|
295
|
+
cases.extend(_route_cases(i, route, personas, default_id, default_body_format, default_csrf))
|
|
251
296
|
return cases
|
|
252
297
|
|
|
253
298
|
|
|
@@ -279,6 +324,9 @@ def generate_matrix_cases_from_findings(
|
|
|
279
324
|
token_env: Optional[str],
|
|
280
325
|
default_id: str = "12345",
|
|
281
326
|
min_consensus: float = 0.0,
|
|
327
|
+
cookie_env: Optional[str] = None,
|
|
328
|
+
body_format: str = "auto",
|
|
329
|
+
csrf: Optional[CsrfConfig] = None,
|
|
282
330
|
) -> List[MatrixCase]:
|
|
283
331
|
"""Turn BFLA and mass assignment findings (findings.json or SARIF) into matrix cases.
|
|
284
332
|
|
|
@@ -289,7 +337,7 @@ def generate_matrix_cases_from_findings(
|
|
|
289
337
|
if is_sarif(data):
|
|
290
338
|
data = findings_from_sarif(data)
|
|
291
339
|
items = data if isinstance(data, list) else (data.get("findings", []) if isinstance(data, dict) else [])
|
|
292
|
-
persona = Persona(name="user", token_env=token_env)
|
|
340
|
+
persona = Persona(name="user", token_env=None if cookie_env else token_env, cookie_env=cookie_env)
|
|
293
341
|
cases: List[MatrixCase] = []
|
|
294
342
|
for item in items:
|
|
295
343
|
kind = _finding_kind(item)
|
|
@@ -304,11 +352,14 @@ def generate_matrix_cases_from_findings(
|
|
|
304
352
|
methods = [m.strip().upper() for m in str(av.get("method") or ep.get("method") or "GET").split("|") if m.strip()]
|
|
305
353
|
fid = str(item.get("finding_id", ""))
|
|
306
354
|
title = item.get("title", path)
|
|
355
|
+
is_form = body_format == "form" or (body_format == "auto" and is_form_action(raw_path))
|
|
356
|
+
request_shape: Dict[str, Any] = {"body_format": "form" if is_form else "json", "csrf": csrf}
|
|
307
357
|
if kind == "bfla":
|
|
308
358
|
for m in methods:
|
|
309
359
|
cases.append(MatrixCase(
|
|
310
360
|
kind="bfla", name=f"[{fid}] {title} ({m})", persona=persona,
|
|
311
361
|
method=m, path=path, payload={} if m in WRITE_METHODS else None, finding_id=fid,
|
|
362
|
+
**request_shape,
|
|
312
363
|
))
|
|
313
364
|
continue
|
|
314
365
|
write = next((m for m in methods if m in WRITE_METHODS), None)
|
|
@@ -320,6 +371,7 @@ def generate_matrix_cases_from_findings(
|
|
|
320
371
|
kind="mass_assignment", name=f"[{fid}] {title} ({write})", persona=persona,
|
|
321
372
|
method=write, path=path, payload={}, probe_fields=dict(fields),
|
|
322
373
|
readback=(rb_method, fill_path(rb_path, {}, default_id)), finding_id=fid,
|
|
374
|
+
**request_shape,
|
|
323
375
|
))
|
|
324
376
|
return cases
|
|
325
377
|
|
|
@@ -351,6 +403,7 @@ class PersonaMatrixRunner:
|
|
|
351
403
|
auth_header: str = "Authorization",
|
|
352
404
|
token_prefix: str = "Bearer ",
|
|
353
405
|
timeout: float = 10.0,
|
|
406
|
+
denial: Optional[DenialSignal] = None,
|
|
354
407
|
) -> None:
|
|
355
408
|
parsed = urllib.parse.urlsplit(base_url)
|
|
356
409
|
if parsed.scheme not in ("http", "https") or not parsed.netloc:
|
|
@@ -358,6 +411,7 @@ class PersonaMatrixRunner:
|
|
|
358
411
|
self.base_url = base_url.rstrip("/")
|
|
359
412
|
self.env = os.environ if env is None else env
|
|
360
413
|
self.deny_status = STRICT_DENY_STATUS if strict else DENY_STATUS
|
|
414
|
+
self.denial = denial
|
|
361
415
|
self.auth_header = auth_header
|
|
362
416
|
self.token_prefix = token_prefix
|
|
363
417
|
self.timeout = timeout
|
|
@@ -371,7 +425,30 @@ class PersonaMatrixRunner:
|
|
|
371
425
|
return None, f"environment variable {persona.token_env} is not set"
|
|
372
426
|
return token, None
|
|
373
427
|
|
|
374
|
-
def
|
|
428
|
+
def _cookie(self, persona: Persona) -> Tuple[Optional[str], Optional[str]]:
|
|
429
|
+
"""Return (cookie, error). Personas without a cookie source have neither."""
|
|
430
|
+
try:
|
|
431
|
+
return read_cookie(persona.cookie_env, persona.cookie_file, self.env, persona.name), None
|
|
432
|
+
except SessionConfigError as err:
|
|
433
|
+
return None, str(err)
|
|
434
|
+
|
|
435
|
+
def _session_mode(self, cookie: Optional[str], csrf: Optional[CsrfConfig], body_format: str) -> bool:
|
|
436
|
+
return cookie is not None or self.denial is not None or csrf is not None or body_format == "form"
|
|
437
|
+
|
|
438
|
+
def _deliver(
|
|
439
|
+
self, method: str, path: str, token: Optional[str], cookie: Optional[str], body: Optional[Dict[str, Any]],
|
|
440
|
+
body_format: str, csrf: Optional[CsrfConfig],
|
|
441
|
+
) -> HttpReply:
|
|
442
|
+
auth = (self.auth_header, f"{self.token_prefix}{token}".strip()) if token else None
|
|
443
|
+
return deliver(self.base_url, SessionRequest(method, path, cookie, auth, body, body_format, csrf), self.timeout)
|
|
444
|
+
|
|
445
|
+
def _send(
|
|
446
|
+
self, method: str, path: str, token: Optional[str], body: Optional[Dict[str, Any]],
|
|
447
|
+
cookie: Optional[str] = None, body_format: str = "json", csrf: Optional[CsrfConfig] = None,
|
|
448
|
+
) -> Tuple[int, bytes, Optional[str]]:
|
|
449
|
+
if self._session_mode(cookie, csrf, body_format):
|
|
450
|
+
reply = self._deliver(method, path, token, cookie, body, body_format, csrf)
|
|
451
|
+
return reply.status, reply.body.encode("utf-8"), reply.error
|
|
375
452
|
headers: Dict[str, str] = {}
|
|
376
453
|
if token:
|
|
377
454
|
headers[self.auth_header] = f"{self.token_prefix}{token}".strip()
|
|
@@ -389,9 +466,9 @@ class PersonaMatrixRunner:
|
|
|
389
466
|
except Exception as ex: # network errors are reported, not raised
|
|
390
467
|
return 0, b"", f"{type(ex).__name__}: {ex}"
|
|
391
468
|
|
|
392
|
-
def _readback(self, case: MatrixCase, token: Optional[str]) -> Tuple[Optional[Any], Optional[str]]:
|
|
469
|
+
def _readback(self, case: MatrixCase, token: Optional[str], cookie: Optional[str]) -> Tuple[Optional[Any], Optional[str]]:
|
|
393
470
|
assert case.readback is not None
|
|
394
|
-
status, raw, err = self._send(case.readback[0], case.readback[1], token, None)
|
|
471
|
+
status, raw, err = self._send(case.readback[0], case.readback[1], token, None, cookie)
|
|
395
472
|
if err or status not in ALLOW_STATUS:
|
|
396
473
|
return None, err or f"readback {case.readback[0]} {case.readback[1]} returned HTTP {status}"
|
|
397
474
|
try:
|
|
@@ -399,7 +476,20 @@ class PersonaMatrixRunner:
|
|
|
399
476
|
except ValueError:
|
|
400
477
|
return None, "readback response is not JSON"
|
|
401
478
|
|
|
402
|
-
def
|
|
479
|
+
def _run_session_bfla(self, case: MatrixCase, token: Optional[str], cookie: Optional[str], start: float) -> MatrixResult:
|
|
480
|
+
signal = self.denial or DenialSignal(deny_status=self.deny_status)
|
|
481
|
+
reply = self._deliver(case.method, case.path, token, cookie, case.payload, case.body_format, case.csrf)
|
|
482
|
+
verdict, reason = classify(signal, reply.status, reply.location, reply.body) if not reply.error else (INCONCLUSIVE, reply.error)
|
|
483
|
+
return MatrixResult(
|
|
484
|
+
case=case, status_code=reply.status,
|
|
485
|
+
is_breach=verdict == ALLOWED, is_secure=verdict == DENIED,
|
|
486
|
+
duration_ms=(time.time() - start) * 1000.0,
|
|
487
|
+
error_message=reason if verdict == INCONCLUSIVE else None,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
def _run_bfla(self, case: MatrixCase, token: Optional[str], cookie: Optional[str], start: float) -> MatrixResult:
|
|
491
|
+
if self._session_mode(cookie, case.csrf, case.body_format):
|
|
492
|
+
return self._run_session_bfla(case, token, cookie, start)
|
|
403
493
|
status, _, err = self._send(case.method, case.path, token, case.payload)
|
|
404
494
|
return MatrixResult(
|
|
405
495
|
case=case, status_code=status,
|
|
@@ -407,26 +497,28 @@ class PersonaMatrixRunner:
|
|
|
407
497
|
duration_ms=(time.time() - start) * 1000.0, error_message=err,
|
|
408
498
|
)
|
|
409
499
|
|
|
410
|
-
def _run_probe(self, case: MatrixCase, token: Optional[str], start: float) -> MatrixResult:
|
|
500
|
+
def _run_probe(self, case: MatrixCase, token: Optional[str], cookie: Optional[str], start: float) -> MatrixResult:
|
|
411
501
|
fields = case.probe_fields or {}
|
|
412
502
|
|
|
413
503
|
def done(status: int, breach: bool, secure: bool, err: Optional[str] = None, persisted: Tuple[str, ...] = ()) -> MatrixResult:
|
|
414
504
|
return MatrixResult(case, status, breach, secure, (time.time() - start) * 1000.0, err, persisted)
|
|
415
505
|
|
|
416
|
-
before, err = self._readback(case, token)
|
|
506
|
+
before, err = self._readback(case, token, cookie)
|
|
417
507
|
if err:
|
|
418
508
|
return done(0, False, False, err)
|
|
419
509
|
already = [k for k, v in fields.items() if _contains_value(before, k, v)]
|
|
420
510
|
if already:
|
|
421
511
|
return done(0, False, False, f"inconclusive: {', '.join(already)} already has the probe value")
|
|
422
512
|
body = {**(case.payload or {}), **fields}
|
|
423
|
-
status, _, err = self._send(case.method, case.path, token, body)
|
|
513
|
+
status, _, err = self._send(case.method, case.path, token, body, cookie, case.body_format, case.csrf)
|
|
424
514
|
if err:
|
|
425
515
|
return done(status, False, False, err)
|
|
426
|
-
|
|
516
|
+
# With redirects not followed, a 3xx may be post/redirect/get after a stored write: let the readback decide.
|
|
517
|
+
accepted = status in ALLOW_STATUS or (self._session_mode(cookie, case.csrf, case.body_format) and 300 <= status < 400)
|
|
518
|
+
if not accepted:
|
|
427
519
|
# The write was refused (e.g. 400/422 for unknown fields): nothing stored.
|
|
428
520
|
return done(status, False, True)
|
|
429
|
-
after, err = self._readback(case, token)
|
|
521
|
+
after, err = self._readback(case, token, cookie)
|
|
430
522
|
if err:
|
|
431
523
|
return done(status, False, False, err)
|
|
432
524
|
persisted = tuple(k for k, v in fields.items() if _contains_value(after, k, v))
|
|
@@ -435,11 +527,12 @@ class PersonaMatrixRunner:
|
|
|
435
527
|
def execute(self, case: MatrixCase) -> MatrixResult:
|
|
436
528
|
start = time.time()
|
|
437
529
|
token, err = self._token(case.persona)
|
|
438
|
-
|
|
439
|
-
|
|
530
|
+
cookie, cookie_err = self._cookie(case.persona)
|
|
531
|
+
if err or cookie_err:
|
|
532
|
+
return MatrixResult(case, 0, False, False, 0.0, err or cookie_err)
|
|
440
533
|
if case.kind == "mass_assignment":
|
|
441
|
-
return self._run_probe(case, token, start)
|
|
442
|
-
return self._run_bfla(case, token, start)
|
|
534
|
+
return self._run_probe(case, token, cookie, start)
|
|
535
|
+
return self._run_bfla(case, token, cookie, start)
|
|
443
536
|
|
|
444
537
|
def run(self, cases: List[MatrixCase]) -> List[MatrixResult]:
|
|
445
538
|
return [self.execute(c) for c in cases]
|