opencode-api-security-testing 3.0.10 → 3.0.11

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.
Files changed (71) hide show
  1. package/README.md +74 -0
  2. package/SKILL.md +1797 -0
  3. package/core/advanced_recon.py +788 -0
  4. package/core/agentic_analyzer.py +445 -0
  5. package/core/analyzers/api_parser.py +210 -0
  6. package/core/analyzers/response_analyzer.py +212 -0
  7. package/core/analyzers/sensitive_finder.py +184 -0
  8. package/core/api_fuzzer.py +422 -0
  9. package/core/api_interceptor.py +525 -0
  10. package/core/api_parser.py +955 -0
  11. package/core/browser_tester.py +479 -0
  12. package/core/cloud_storage_tester.py +1330 -0
  13. package/core/collectors/__init__.py +23 -0
  14. package/core/collectors/api_path_finder.py +300 -0
  15. package/core/collectors/browser_collect.py +645 -0
  16. package/core/collectors/browser_collector.py +411 -0
  17. package/core/collectors/http_client.py +111 -0
  18. package/core/collectors/js_collector.py +490 -0
  19. package/core/collectors/js_parser.py +780 -0
  20. package/core/collectors/url_collector.py +319 -0
  21. package/core/context_manager.py +682 -0
  22. package/core/deep_api_tester_v35.py +844 -0
  23. package/core/deep_api_tester_v55.py +366 -0
  24. package/core/dynamic_api_analyzer.py +532 -0
  25. package/core/http_client.py +179 -0
  26. package/core/models.py +296 -0
  27. package/core/orchestrator.py +890 -0
  28. package/core/prerequisite.py +227 -0
  29. package/core/reasoning_engine.py +1042 -0
  30. package/core/response_classifier.py +606 -0
  31. package/core/runner.py +938 -0
  32. package/core/scan_engine.py +599 -0
  33. package/core/skill_executor.py +435 -0
  34. package/core/skill_executor_v2.py +670 -0
  35. package/core/skill_executor_v3.py +704 -0
  36. package/core/smart_analyzer.py +687 -0
  37. package/core/strategy_pool.py +707 -0
  38. package/core/testers/auth_tester.py +264 -0
  39. package/core/testers/idor_tester.py +200 -0
  40. package/core/testers/sqli_tester.py +211 -0
  41. package/core/testing_loop.py +655 -0
  42. package/core/utils/base_path_dict.py +255 -0
  43. package/core/utils/payload_lib.py +167 -0
  44. package/core/utils/ssrf_detector.py +220 -0
  45. package/core/verifiers/vuln_verifier.py +536 -0
  46. package/package.json +1 -1
  47. package/references/README.md +72 -0
  48. package/references/asset-discovery.md +119 -0
  49. package/references/fuzzing-patterns.md +129 -0
  50. package/references/graphql-guidance.md +108 -0
  51. package/references/intake.md +84 -0
  52. package/references/pua-agent.md +192 -0
  53. package/references/report-template.md +156 -0
  54. package/references/rest-guidance.md +76 -0
  55. package/references/severity-model.md +76 -0
  56. package/references/test-matrix.md +86 -0
  57. package/references/validation.md +78 -0
  58. package/references/vulnerabilities/01-sqli-tests.md +1128 -0
  59. package/references/vulnerabilities/02-user-enum-tests.md +423 -0
  60. package/references/vulnerabilities/03-jwt-tests.md +499 -0
  61. package/references/vulnerabilities/04-idor-tests.md +362 -0
  62. package/references/vulnerabilities/05-sensitive-data-tests.md +466 -0
  63. package/references/vulnerabilities/06-biz-logic-tests.md +501 -0
  64. package/references/vulnerabilities/07-security-config-tests.md +511 -0
  65. package/references/vulnerabilities/08-brute-force-tests.md +457 -0
  66. package/references/vulnerabilities/09-vulnerability-chains.md +465 -0
  67. package/references/vulnerabilities/10-auth-tests.md +537 -0
  68. package/references/vulnerabilities/11-graphql-tests.md +355 -0
  69. package/references/vulnerabilities/12-ssrf-tests.md +396 -0
  70. package/references/vulnerabilities/README.md +148 -0
  71. package/references/workflows.md +192 -0
@@ -0,0 +1,655 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Insight-Driven Testing Loop - 洞察驱动测试循环
4
+
5
+ 实现:
6
+ - 观察 → 推理 → 策略 → 执行 → 验证闭环
7
+ - 结果反馈机制
8
+ - 收敛检测
9
+ """
10
+
11
+ import time
12
+ from datetime import datetime
13
+ from typing import Dict, List, Set, Optional, Any, Callable, Generator, Tuple
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from collections import defaultdict
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class LoopState(Enum):
23
+ """循环状态"""
24
+ IDLE = "idle"
25
+ RUNNING = "running"
26
+ PAUSED = "paused"
27
+ CONVERGED = "converged"
28
+ BLOCKED = "blocked"
29
+ COMPLETED = "completed"
30
+
31
+
32
+ @dataclass
33
+ class TestAction:
34
+ """测试动作"""
35
+ id: str
36
+ type: str
37
+ target: str
38
+ params: Dict[str, Any] = field(default_factory=dict)
39
+
40
+ priority: int = 0
41
+ timeout: float = 30.0
42
+ expected_outcome: Optional[str] = None
43
+
44
+ executed_at: Optional[datetime] = None
45
+ result: Optional[str] = None
46
+ actual_outcome: Optional[str] = None
47
+
48
+
49
+ @dataclass
50
+ class ActionResult:
51
+ """动作执行结果"""
52
+ action: TestAction
53
+ success: bool
54
+ response_time: float
55
+
56
+ status_code: Optional[int] = None
57
+ content_length: int = 0
58
+ content_preview: str = ""
59
+ actual_outcome: Optional[str] = None
60
+
61
+ deviation: float = 0.0
62
+ new_insights: List[Any] = field(default_factory=list)
63
+
64
+ error: Optional[str] = None
65
+
66
+
67
+ @dataclass
68
+ class Validation:
69
+ """验证结果"""
70
+ is_expected: bool
71
+ deviation: float
72
+
73
+ new_insights: List[Any] = field(default_factory=list)
74
+ strategy_adjustment: Optional[Dict] = None
75
+
76
+ confidence_impact: float = 0.0
77
+
78
+
79
+ @dataclass
80
+ class LoopProgress:
81
+ """循环进度"""
82
+ state: LoopState
83
+
84
+ iterations: int = 0
85
+ max_iterations: int = 100
86
+
87
+ actions_executed: int = 0
88
+ actions_total: int = 0
89
+
90
+ insights_generated: int = 0
91
+ vulnerabilities_found: int = 0
92
+
93
+ convergence_score: float = 0.0
94
+ effectiveness: float = 0.0
95
+
96
+ elapsed_time: float = 0.0
97
+ estimated_remaining: float = 0.0
98
+
99
+ blockers: List[str] = field(default_factory=list)
100
+ suggestions: List[str] = field(default_factory=list)
101
+
102
+
103
+ class Validator:
104
+ """
105
+ 验证器
106
+
107
+ 职责:
108
+ - 预期对比验证
109
+ - 偏差计算
110
+ - 新洞察生成
111
+ """
112
+
113
+ def __init__(self):
114
+ self.validation_history: List[Validation] = []
115
+ self.expected_outcomes: Dict[str, str] = {}
116
+
117
+ def set_expected_outcome(self, action_id: str, outcome: str):
118
+ """设置预期结果"""
119
+ self.expected_outcomes[action_id] = outcome
120
+
121
+ def validate_result(self, action: TestAction, result: ActionResult) -> Validation:
122
+ """
123
+ 验证结果是否符合预期
124
+
125
+ Args:
126
+ action: 执行的动作
127
+ result: 执行结果
128
+
129
+ Returns:
130
+ 验证结果
131
+ """
132
+ expected = self.expected_outcomes.get(action.id, action.expected_outcome)
133
+
134
+ if expected is None:
135
+ return Validation(
136
+ is_expected=True,
137
+ deviation=0.0
138
+ )
139
+
140
+ is_expected = False
141
+ deviation = 1.0
142
+
143
+ if result.error:
144
+ is_expected = expected == 'error'
145
+ deviation = 1.0 if not is_expected else 0.0
146
+
147
+ elif result.status_code:
148
+ expected_codes = self._parse_expected_code(expected)
149
+ is_expected = result.status_code in expected_codes
150
+
151
+ if expected_codes:
152
+ deviation = 0.0 if is_expected else 1.0
153
+
154
+ elif result.content_preview:
155
+ is_expected = self._check_content_match(expected, result.content_preview)
156
+ deviation = 0.0 if is_expected else 0.5
157
+
158
+ validation = Validation(
159
+ is_expected=is_expected,
160
+ deviation=deviation,
161
+ confidence_impact=1.0 - deviation
162
+ )
163
+
164
+ self.validation_history.append(validation)
165
+
166
+ return validation
167
+
168
+ def _parse_expected_code(self, expected: str) -> Set[int]:
169
+ """解析预期状态码"""
170
+ codes = set()
171
+
172
+ if '2xx' in expected or 'success' in expected.lower():
173
+ codes.update([200, 201, 202, 204])
174
+ elif '3xx' in expected or 'redirect' in expected.lower():
175
+ codes.update([301, 302, 303, 307, 308])
176
+ elif '4xx' in expected or 'client_error' in expected.lower():
177
+ codes.update([400, 401, 403, 404])
178
+ elif '5xx' in expected or 'server_error' in expected.lower():
179
+ codes.update([500, 502, 503])
180
+ else:
181
+ try:
182
+ codes.add(int(expected))
183
+ except ValueError:
184
+ pass
185
+
186
+ return codes
187
+
188
+ def _check_content_match(self, expected: str, content: str) -> bool:
189
+ """检查内容匹配"""
190
+ content_lower = content.lower()
191
+ expected_lower = expected.lower()
192
+
193
+ if expected_lower in content_lower:
194
+ return True
195
+
196
+ keywords = expected_lower.split()
197
+ return any(kw in content_lower for kw in keywords)
198
+
199
+ def check_convergence(self, recent_validations: List[Validation]) -> Tuple[bool, float]:
200
+ """
201
+ 检查测试是否收敛
202
+
203
+ Returns:
204
+ (是否收敛, 收敛分数 0-1)
205
+ """
206
+ if len(recent_validations) < 3:
207
+ return False, 0.0
208
+
209
+ recent = recent_validations[-10:]
210
+
211
+ deviations = [v.deviation for v in recent]
212
+ avg_deviation = sum(deviations) / len(deviations)
213
+
214
+ deviation_trend = 0.0
215
+ if len(recent) >= 5:
216
+ first_half = sum(deviations[:len(deviations)//2]) / (len(deviations)//2)
217
+ second_half = sum(deviations[len(deviations)//2:]) / (len(deviations) - len(deviations)//2)
218
+ deviation_trend = first_half - second_half
219
+
220
+ convergence_score = 1.0 - avg_deviation
221
+
222
+ if deviation_trend > 0.1:
223
+ convergence_score *= 0.8
224
+
225
+ is_converged = convergence_score > 0.8 and len(recent_validations) >= 5
226
+
227
+ return is_converged, convergence_score
228
+
229
+ def get_false_negative_risk(self) -> float:
230
+ """
231
+ 评估假阴性风险
232
+
233
+ Returns:
234
+ 风险分数 0-1 (越高风险越大)
235
+ """
236
+ if len(self.validation_history) < 3:
237
+ return 0.3
238
+
239
+ recent = self.validation_history[-10:]
240
+
241
+ high_deviation_count = sum(1 for v in recent if v.deviation > 0.5)
242
+ high_deviation_ratio = high_deviation_count / len(recent)
243
+
244
+ consistent_no_finding = all(v.is_expected and v.deviation < 0.1 for v in recent[-5:])
245
+
246
+ risk = high_deviation_ratio * 0.5
247
+
248
+ if consistent_no_finding:
249
+ risk += 0.3
250
+
251
+ return min(risk, 1.0)
252
+
253
+ def generate_validation_report(self) -> Dict:
254
+ """生成验证报告"""
255
+ recent = self.validation_history[-20:] if self.validation_history else []
256
+
257
+ if not recent:
258
+ return {
259
+ 'total_validations': 0,
260
+ 'convergence_score': 0.0,
261
+ 'false_negative_risk': 0.3,
262
+ 'recommendation': 'insufficient_data'
263
+ }
264
+
265
+ is_converged, convergence_score = self.check_convergence(recent)
266
+ fn_risk = self.get_false_negative_risk()
267
+
268
+ recommendations = []
269
+ if fn_risk > 0.5:
270
+ recommendations.append("假阴性风险较高,建议扩大测试范围")
271
+ if convergence_score < 0.5:
272
+ recommendations.append("收敛度低,可能存在遗漏")
273
+ if is_converged:
274
+ recommendations.append("测试已收敛,可以生成报告")
275
+
276
+ return {
277
+ 'total_validations': len(self.validation_history),
278
+ 'recent_validations': len(recent),
279
+ 'expected_count': sum(1 for v in recent if v.is_expected),
280
+ 'unexpected_count': sum(1 for v in recent if not v.is_expected),
281
+ 'convergence_score': convergence_score,
282
+ 'is_converged': is_converged,
283
+ 'false_negative_risk': fn_risk,
284
+ 'recommendations': recommendations
285
+ }
286
+
287
+
288
+ class InsightDrivenLoop:
289
+ """
290
+ 洞察驱动测试循环
291
+
292
+ 实现:观察 → 推理 → 策略 → 执行 → 验证 闭环
293
+ """
294
+
295
+ def __init__(
296
+ self,
297
+ reasoner: Any,
298
+ strategist: Any,
299
+ context_manager: Any,
300
+ executor: Optional[Callable] = None
301
+ ):
302
+ self.reasoner = reasoner
303
+ self.strategist = strategist
304
+ self.context_manager = context_manager
305
+ self.executor = executor
306
+
307
+ self.validator = Validator()
308
+
309
+ self.state = LoopState.IDLE
310
+ self.progress = LoopProgress(state=LoopState.IDLE)
311
+
312
+ self.action_queue: List[TestAction] = []
313
+ self.execution_history: List[ActionResult] = []
314
+
315
+ self.callbacks: Dict[str, List[Callable]] = defaultdict(list)
316
+
317
+ def on(self, event: str, callback: Callable):
318
+ """注册回调"""
319
+ self.callbacks[event].append(callback)
320
+
321
+ def _emit(self, event: str, data: Any):
322
+ """触发回调"""
323
+ for callback in self.callbacks.get(event, []):
324
+ try:
325
+ callback(data)
326
+ except Exception as e:
327
+ logger.warning(f"Callback error ({event}): {e}")
328
+
329
+ def add_action(self, action: TestAction):
330
+ """添加测试动作"""
331
+ self.action_queue.append(action)
332
+ self.action_queue.sort(key=lambda a: a.priority, reverse=True)
333
+ self.progress.actions_total += 1
334
+
335
+ def add_actions_batch(self, actions: List[TestAction]):
336
+ """批量添加测试动作"""
337
+ for action in actions:
338
+ self.add_action(action)
339
+
340
+ def run(
341
+ self,
342
+ max_iterations: int = 100,
343
+ max_duration: float = 3600.0,
344
+ convergence_threshold: float = 0.8
345
+ ) -> Dict:
346
+ """
347
+ 运行测试循环
348
+
349
+ Args:
350
+ max_iterations: 最大迭代次数
351
+ max_duration: 最大运行时长(秒)
352
+ convergence_threshold: 收敛阈值
353
+
354
+ Returns:
355
+ 测试结果
356
+ """
357
+ self.state = LoopState.RUNNING
358
+ self.progress = LoopProgress(
359
+ state=LoopState.RUNNING,
360
+ max_iterations=max_iterations
361
+ )
362
+
363
+ start_time = time.time()
364
+
365
+ logger.info("Starting insight-driven testing loop")
366
+
367
+ try:
368
+ while self.state == LoopState.RUNNING:
369
+ if self._should_terminate(max_iterations, max_duration):
370
+ break
371
+
372
+ self._iterate()
373
+
374
+ self.progress.elapsed_time = time.time() - start_time
375
+
376
+ self._emit('iteration', self.progress)
377
+
378
+ except Exception as e:
379
+ logger.error(f"Loop error: {e}")
380
+ self.state = LoopState.BLOCKED
381
+ self.progress.blockers.append(str(e))
382
+
383
+ finally:
384
+ score = 0.0
385
+ if self.state == LoopState.RUNNING:
386
+ is_converged, score = self.validator.check_convergence(
387
+ self.validator.validation_history[-10:]
388
+ )
389
+ if is_converged:
390
+ self.state = LoopState.CONVERGED
391
+ else:
392
+ self.state = LoopState.COMPLETED
393
+
394
+ self.progress.state = self.state
395
+ self.progress.convergence_score = score
396
+
397
+ return self._generate_report()
398
+
399
+ def _should_terminate(self, max_iterations: int, max_duration: float) -> bool:
400
+ """检查是否应该终止"""
401
+ if self.progress.iterations >= max_iterations:
402
+ logger.info("Max iterations reached")
403
+ return True
404
+
405
+ if self.progress.elapsed_time >= max_duration:
406
+ logger.info("Max duration reached")
407
+ return True
408
+
409
+ if self.progress.blockers:
410
+ logger.warning(f"Blocked: {self.progress.blockers}")
411
+ return True
412
+
413
+ is_converged, _ = self.validator.check_convergence(
414
+ self.validator.validation_history[-5:]
415
+ )
416
+ if is_converged:
417
+ logger.info("Convergence achieved")
418
+ return True
419
+
420
+ return False
421
+
422
+ def _iterate(self):
423
+ """执行一次迭代"""
424
+ self.progress.iterations += 1
425
+
426
+ if not self.action_queue:
427
+ if self.progress.insights_generated > 0:
428
+ self._generate_more_actions()
429
+ else:
430
+ self.state = LoopState.COMPLETED
431
+ return
432
+
433
+ action = self.action_queue.pop(0)
434
+
435
+ result = self._execute_action(action)
436
+
437
+ self.execution_history.append(result)
438
+ self.progress.actions_executed += 1
439
+
440
+ validation = self.validator.validate_result(action, result)
441
+
442
+ if not validation.is_expected:
443
+ self._handle_unexpected_result(action, result, validation)
444
+
445
+ insights = self._process_insights(result, validation)
446
+
447
+ self.progress.insights_generated += len(insights)
448
+
449
+ self._update_strategy(insights, result)
450
+
451
+ self._emit('action_completed', {
452
+ 'action': action,
453
+ 'result': result,
454
+ 'validation': validation,
455
+ 'insights': insights
456
+ })
457
+
458
+ def _execute_action(self, action: TestAction) -> ActionResult:
459
+ """执行动作"""
460
+ action.executed_at = datetime.now()
461
+
462
+ if self.executor:
463
+ try:
464
+ response = self.executor(action)
465
+
466
+ return ActionResult(
467
+ action=action,
468
+ success=True,
469
+ response_time=response.get('time', 0.0),
470
+ status_code=response.get('status', 0),
471
+ content_length=len(response.get('content', '')),
472
+ content_preview=response.get('content', '')[:200]
473
+ )
474
+
475
+ except Exception as e:
476
+ return ActionResult(
477
+ action=action,
478
+ success=False,
479
+ response_time=0.0,
480
+ error=str(e)
481
+ )
482
+
483
+ return ActionResult(
484
+ action=action,
485
+ success=False,
486
+ response_time=0.0,
487
+ error="No executor configured"
488
+ )
489
+
490
+ def _process_insights(self, result: ActionResult, validation: Validation) -> List[Any]:
491
+ """处理洞察"""
492
+ insights = list(validation.new_insights)
493
+
494
+ if result.content_preview:
495
+ response_data = {
496
+ 'url': result.action.target,
497
+ 'method': result.action.type,
498
+ 'status_code': result.status_code or 0,
499
+ 'content_type': '',
500
+ 'content': result.content_preview,
501
+ 'response_time': result.response_time
502
+ }
503
+
504
+ new_insights = self.reasoner.observe_and_reason(response_data)
505
+ insights.extend(new_insights)
506
+
507
+ return insights
508
+
509
+ def _handle_unexpected_result(
510
+ self,
511
+ action: TestAction,
512
+ result: ActionResult,
513
+ validation: Validation
514
+ ):
515
+ """处理意外结果"""
516
+ logger.info(f"Unexpected result for {action.type}: {result.actual_outcome}")
517
+
518
+ if validation.deviation > 0.5:
519
+ self.progress.suggestions.append(
520
+ f"高偏差动作: {action.type} -> 考虑调整策略"
521
+ )
522
+
523
+ def _generate_more_actions(self):
524
+ """生成更多测试动作"""
525
+ if self.progress.insights_generated == 0:
526
+ self.state = LoopState.COMPLETED
527
+ return
528
+
529
+ context = {
530
+ 'insights': self.reasoner.insight_store.get_active(),
531
+ 'endpoints': self.context_manager.context.discovered_endpoints,
532
+ 'phase': self.context_manager.context.current_phase.value
533
+ }
534
+
535
+ strategy = self.strategist.get_best_strategy_for_context(context)
536
+
537
+ for action_def in strategy.actions:
538
+ action = TestAction(
539
+ id=f"generated_{int(time.time() * 1000)}",
540
+ type=action_def.type,
541
+ target="",
542
+ params=action_def.params,
543
+ priority=action_def.priority,
544
+ timeout=action_def.timeout
545
+ )
546
+ self.add_action(action)
547
+
548
+ def _update_strategy(self, insights: List[Any], result: ActionResult):
549
+ """更新策略"""
550
+ if not insights:
551
+ return
552
+
553
+ context = {
554
+ 'insights': insights,
555
+ 'result_success': result.success
556
+ }
557
+
558
+ try:
559
+ self.strategist.record_execution(
560
+ self.strategist.current_plan.primary_strategy.id if self.strategist.current_plan else 'unknown',
561
+ {
562
+ 'effectiveness': 1.0 - result.deviation if hasattr(result, 'deviation') else 0.5,
563
+ 'vulnerabilities_found': self.progress.vulnerabilities_found
564
+ }
565
+ )
566
+ except Exception as e:
567
+ logger.warning(f"Strategy update error: {e}")
568
+
569
+ def pause(self):
570
+ """暂停循环"""
571
+ self.state = LoopState.PAUSED
572
+ self.progress.state = LoopState.PAUSED
573
+
574
+ def resume(self):
575
+ """恢复循环"""
576
+ if self.state == LoopState.PAUSED:
577
+ self.state = LoopState.RUNNING
578
+
579
+ def stop(self):
580
+ """停止循环"""
581
+ self.state = LoopState.COMPLETED
582
+ self.progress.state = LoopState.COMPLETED
583
+
584
+ def get_progress(self) -> LoopProgress:
585
+ """获取进度"""
586
+ return self.progress
587
+
588
+ def _generate_report(self) -> Dict:
589
+ """生成报告"""
590
+ validation_report = self.validator.generate_validation_report()
591
+
592
+ return {
593
+ 'state': self.state.value,
594
+ 'progress': {
595
+ 'iterations': self.progress.iterations,
596
+ 'actions_executed': self.progress.actions_executed,
597
+ 'actions_total': self.progress.actions_total,
598
+ 'insights_generated': self.progress.insights_generated,
599
+ 'vulnerabilities_found': self.progress.vulnerabilities_found,
600
+ 'elapsed_time': self.progress.elapsed_time,
601
+ 'convergence_score': self.progress.convergence_score
602
+ },
603
+ 'validation': validation_report,
604
+ 'blockers': self.progress.blockers,
605
+ 'suggestions': self.progress.suggestions
606
+ }
607
+
608
+
609
+ def create_test_loop(
610
+ reasoner: Any,
611
+ strategist: Any,
612
+ context_manager: Any,
613
+ executor: Optional[Callable] = None
614
+ ) -> InsightDrivenLoop:
615
+ """创建测试循环工厂函数"""
616
+ return InsightDrivenLoop(reasoner, strategist, context_manager, executor)
617
+
618
+
619
+ if __name__ == "__main__":
620
+ from core.reasoning_engine import create_reasoner
621
+ from core.strategy_pool import create_strategist
622
+ from core.context_manager import create_context_manager
623
+
624
+ reasoner = create_reasoner()
625
+ strategist = create_strategist()
626
+ context_manager = create_context_manager("http://example.com")
627
+
628
+ loop = create_test_loop(reasoner, strategist, context_manager)
629
+
630
+ loop.add_action(TestAction(
631
+ id="test_1",
632
+ type="GET",
633
+ target="http://example.com/api",
634
+ priority=10,
635
+ expected_outcome="2xx"
636
+ ))
637
+
638
+ loop.add_action(TestAction(
639
+ id="test_2",
640
+ type="GET",
641
+ target="http://example.com/login",
642
+ priority=8,
643
+ expected_outcome="html"
644
+ ))
645
+
646
+ print("Test loop initialized")
647
+ print(f"Actions in queue: {len(loop.action_queue)}")
648
+
649
+ report = loop.run(max_iterations=2)
650
+
651
+ print("\nLoop Report:")
652
+ print(f"State: {report['state']}")
653
+ print(f"Iterations: {report['progress']['iterations']}")
654
+ print(f"Actions executed: {report['progress']['actions_executed']}")
655
+ print(f"Validation: {report['validation']}")