mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-07-07 20:18:06 +02:00
Initial commit
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 FuzzingLabs
|
||||
#
|
||||
# Licensed under the Business Source License 1.1 (BSL). See the LICENSE file
|
||||
# at the root of this repository for details.
|
||||
#
|
||||
# After the Change Date (four years from publication), this version of the
|
||||
# Licensed Work will be made available under the Apache License, Version 2.0.
|
||||
# See the LICENSE-APACHE file or http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
"""
|
||||
Basic workflow submission example.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Connect to FuzzForge API
|
||||
2. List available workflows
|
||||
3. Submit a workflow for analysis
|
||||
4. Monitor the run status
|
||||
5. Retrieve findings when complete
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from fuzzforge_sdk import FuzzForgeClient, WorkflowSubmission
|
||||
from fuzzforge_sdk.utils import create_workflow_submission, format_sarif_summary, format_duration
|
||||
|
||||
|
||||
def main():
|
||||
"""Run basic workflow submission example."""
|
||||
# Initialize the client
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
|
||||
try:
|
||||
# Check API status
|
||||
print("🔗 Connecting to FuzzForge API...")
|
||||
status = client.get_api_status()
|
||||
print(f"✅ Connected to {status.name} v{status.version}")
|
||||
print(f"📊 {status.workflows_loaded} workflows loaded\n")
|
||||
|
||||
# List available workflows
|
||||
print("📋 Available workflows:")
|
||||
workflows = client.list_workflows()
|
||||
for workflow in workflows:
|
||||
print(f" • {workflow.name} v{workflow.version}")
|
||||
print(f" {workflow.description}")
|
||||
if workflow.tags:
|
||||
print(f" Tags: {', '.join(workflow.tags)}")
|
||||
print()
|
||||
|
||||
if not workflows:
|
||||
print("❌ No workflows available")
|
||||
return
|
||||
|
||||
# Select the first workflow for demo
|
||||
selected_workflow = workflows[0]
|
||||
print(f"🎯 Selected workflow: {selected_workflow.name}")
|
||||
|
||||
# Get workflow metadata
|
||||
metadata = client.get_workflow_metadata(selected_workflow.name)
|
||||
print(f"📝 Workflow metadata:")
|
||||
print(f" Author: {metadata.author}")
|
||||
print(f" Required modules: {metadata.required_modules}")
|
||||
print(f" Supported volume modes: {metadata.supported_volume_modes}")
|
||||
print()
|
||||
|
||||
# Prepare target path (use current directory as example)
|
||||
target_path = Path.cwd().absolute()
|
||||
print(f"🎯 Target path: {target_path}")
|
||||
|
||||
# Create workflow submission
|
||||
submission = create_workflow_submission(
|
||||
target_path=target_path,
|
||||
volume_mode="ro",
|
||||
timeout=300, # 5 minutes
|
||||
)
|
||||
|
||||
# Submit the workflow
|
||||
print(f"🚀 Submitting workflow '{selected_workflow.name}'...")
|
||||
response = client.submit_workflow(selected_workflow.name, submission)
|
||||
print(f"✅ Workflow submitted!")
|
||||
print(f" Run ID: {response.run_id}")
|
||||
print(f" Status: {response.status}")
|
||||
print()
|
||||
|
||||
# Monitor the run
|
||||
print("⏱️ Monitoring run progress...")
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
status = client.get_run_status(response.run_id)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
print(f" Status: {status.status} (elapsed: {format_duration(int(elapsed))})")
|
||||
|
||||
if status.is_completed:
|
||||
print("✅ Run completed successfully!")
|
||||
break
|
||||
elif status.is_failed:
|
||||
print("❌ Run failed!")
|
||||
print(f" Final status: {status.status}")
|
||||
return
|
||||
elif not status.is_running:
|
||||
print("⏸️ Run is not active")
|
||||
print(f" Current status: {status.status}")
|
||||
|
||||
# Wait before next check
|
||||
time.sleep(5)
|
||||
|
||||
print()
|
||||
|
||||
# Get findings
|
||||
print("📊 Retrieving findings...")
|
||||
try:
|
||||
findings = client.get_run_findings(response.run_id)
|
||||
print(f"✅ Findings retrieved for workflow: {findings.workflow}")
|
||||
|
||||
# Display SARIF summary
|
||||
sarif_summary = format_sarif_summary(findings.sarif)
|
||||
print(f"📈 {sarif_summary}")
|
||||
|
||||
# Display metadata
|
||||
if findings.metadata:
|
||||
print(f"🔍 Metadata:")
|
||||
for key, value in findings.metadata.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
print()
|
||||
|
||||
# Extract and display detailed findings
|
||||
from fuzzforge_sdk.utils import extract_sarif_results
|
||||
results = extract_sarif_results(findings.sarif)
|
||||
|
||||
if results:
|
||||
print("🔍 Detailed Findings:")
|
||||
print("=" * 60)
|
||||
|
||||
for i, result in enumerate(results, 1):
|
||||
print(f"\n📋 Finding #{i}")
|
||||
|
||||
# Rule information
|
||||
rule_id = result.get('ruleId', 'unknown')
|
||||
level = result.get('level', 'warning')
|
||||
message = result.get('message', {})
|
||||
|
||||
print(f" Rule ID: {rule_id}")
|
||||
print(f" Severity: {level.upper()}")
|
||||
|
||||
# Message
|
||||
if isinstance(message, dict):
|
||||
msg_text = message.get('text', 'No message')
|
||||
else:
|
||||
msg_text = str(message)
|
||||
print(f" Message: {msg_text}")
|
||||
|
||||
# Location information
|
||||
locations = result.get('locations', [])
|
||||
if locations:
|
||||
for loc in locations:
|
||||
physical_loc = loc.get('physicalLocation', {})
|
||||
artifact_loc = physical_loc.get('artifactLocation', {})
|
||||
region = physical_loc.get('region', {})
|
||||
|
||||
file_path = artifact_loc.get('uri', 'unknown file')
|
||||
start_line = region.get('startLine', 'unknown')
|
||||
start_col = region.get('startColumn', 'unknown')
|
||||
|
||||
print(f" Location: {file_path}:{start_line}:{start_col}")
|
||||
|
||||
# Show code snippet if available
|
||||
snippet = region.get('snippet', {})
|
||||
if snippet and isinstance(snippet, dict):
|
||||
snippet_text = snippet.get('text', '').strip()
|
||||
if snippet_text:
|
||||
print(f" Code: {snippet_text}")
|
||||
|
||||
# Additional properties
|
||||
properties = result.get('properties', {})
|
||||
if properties:
|
||||
print(f" Properties:")
|
||||
for prop_key, prop_value in properties.items():
|
||||
print(f" {prop_key}: {prop_value}")
|
||||
|
||||
print("-" * 40)
|
||||
|
||||
print(f"\n📁 Total findings: {len(results)}")
|
||||
|
||||
print("\n💾 Tip: Use save_sarif_to_file() to save findings to disk")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to retrieve findings: {e}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
async def async_main():
|
||||
"""Run basic workflow submission example (async version)."""
|
||||
# Initialize the async client
|
||||
async with FuzzForgeClient(base_url="http://localhost:8000") as client:
|
||||
try:
|
||||
# Check API status
|
||||
print("🔗 Connecting to FuzzForge API...")
|
||||
status = await client.aget_api_status()
|
||||
print(f"✅ Connected to {status.name} v{status.version}")
|
||||
print(f"📊 {status.workflows_loaded} workflows loaded\n")
|
||||
|
||||
# List available workflows
|
||||
print("📋 Available workflows:")
|
||||
workflows = await client.alist_workflows()
|
||||
for workflow in workflows:
|
||||
print(f" • {workflow.name} v{workflow.version}")
|
||||
print(f" {workflow.description}")
|
||||
if workflow.tags:
|
||||
print(f" Tags: {', '.join(workflow.tags)}")
|
||||
print()
|
||||
|
||||
if not workflows:
|
||||
print("❌ No workflows available")
|
||||
return
|
||||
|
||||
# Select the first workflow for demo
|
||||
selected_workflow = workflows[0]
|
||||
print(f"🎯 Selected workflow: {selected_workflow.name}")
|
||||
|
||||
# Prepare target path
|
||||
target_path = Path.cwd().absolute()
|
||||
submission = create_workflow_submission(
|
||||
target_path=target_path,
|
||||
volume_mode="ro",
|
||||
timeout=300,
|
||||
)
|
||||
|
||||
# Submit the workflow
|
||||
print(f"🚀 Submitting workflow '{selected_workflow.name}'...")
|
||||
response = await client.asubmit_workflow(selected_workflow.name, submission)
|
||||
print(f"✅ Workflow submitted! Run ID: {response.run_id}")
|
||||
|
||||
# Wait for completion
|
||||
print("⏱️ Waiting for completion...")
|
||||
final_status = await client.await_for_completion(
|
||||
response.run_id,
|
||||
poll_interval=3.0,
|
||||
timeout=600.0 # 10 minutes max
|
||||
)
|
||||
print(f"✅ Run completed with status: {final_status.status}")
|
||||
|
||||
# Get findings
|
||||
findings = await client.aget_run_findings(response.run_id)
|
||||
sarif_summary = format_sarif_summary(findings.sarif)
|
||||
print(f"📈 {sarif_summary}")
|
||||
|
||||
# Extract and display detailed findings
|
||||
from fuzzforge_sdk.utils import extract_sarif_results
|
||||
results = extract_sarif_results(findings.sarif)
|
||||
|
||||
if results:
|
||||
print("\n🔍 Detailed Findings:")
|
||||
print("=" * 60)
|
||||
|
||||
for i, result in enumerate(results, 1):
|
||||
print(f"\n📋 Finding #{i}")
|
||||
rule_id = result.get('ruleId', 'unknown')
|
||||
level = result.get('level', 'warning')
|
||||
message = result.get('message', {})
|
||||
|
||||
print(f" Rule ID: {rule_id}")
|
||||
print(f" Severity: {level.upper()}")
|
||||
|
||||
if isinstance(message, dict):
|
||||
msg_text = message.get('text', 'No message')
|
||||
else:
|
||||
msg_text = str(message)
|
||||
print(f" Message: {msg_text}")
|
||||
|
||||
locations = result.get('locations', [])
|
||||
if locations:
|
||||
for loc in locations:
|
||||
physical_loc = loc.get('physicalLocation', {})
|
||||
artifact_loc = physical_loc.get('artifactLocation', {})
|
||||
region = physical_loc.get('region', {})
|
||||
|
||||
file_path = artifact_loc.get('uri', 'unknown file')
|
||||
start_line = region.get('startLine', 'unknown')
|
||||
start_col = region.get('startColumn', 'unknown')
|
||||
|
||||
print(f" Location: {file_path}:{start_line}:{start_col}")
|
||||
|
||||
print("-" * 40)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--async":
|
||||
print("🔄 Running async version...")
|
||||
asyncio.run(async_main())
|
||||
else:
|
||||
print("🔄 Running synchronous version...")
|
||||
print("💡 Use --async flag to run async version")
|
||||
main()
|
||||
@@ -0,0 +1,399 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 FuzzingLabs
|
||||
#
|
||||
# Licensed under the Business Source License 1.1 (BSL). See the LICENSE file
|
||||
# at the root of this repository for details.
|
||||
#
|
||||
# After the Change Date (four years from publication), this version of the
|
||||
# Licensed Work will be made available under the Apache License, Version 2.0.
|
||||
# See the LICENSE-APACHE file or http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
"""
|
||||
Batch analysis example.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Analyze multiple projects or targets
|
||||
2. Run different workflows on the same target
|
||||
3. Collect and compare results
|
||||
4. Generate summary reports
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
import time
|
||||
|
||||
from fuzzforge_sdk import (
|
||||
FuzzForgeClient,
|
||||
WorkflowSubmission,
|
||||
WorkflowFindings,
|
||||
RunSubmissionResponse
|
||||
)
|
||||
from fuzzforge_sdk.utils import (
|
||||
create_workflow_submission,
|
||||
format_sarif_summary,
|
||||
count_sarif_severity_levels,
|
||||
save_sarif_to_file,
|
||||
get_project_files,
|
||||
estimate_analysis_time
|
||||
)
|
||||
|
||||
|
||||
class BatchAnalyzer:
|
||||
"""Batch analysis manager."""
|
||||
|
||||
def __init__(self, client: FuzzForgeClient):
|
||||
self.client = client
|
||||
self.results: List[Dict[str, Any]] = []
|
||||
|
||||
async def analyze_project(
|
||||
self,
|
||||
project_path: Path,
|
||||
workflows: List[str],
|
||||
output_dir: Path
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Analyze a single project with multiple workflows.
|
||||
|
||||
Args:
|
||||
project_path: Path to project to analyze
|
||||
workflows: List of workflow names to run
|
||||
output_dir: Directory to save results
|
||||
|
||||
Returns:
|
||||
Analysis results summary
|
||||
"""
|
||||
print(f"\n📁 Analyzing project: {project_path.name}")
|
||||
print(f" Path: {project_path}")
|
||||
print(f" Workflows: {', '.join(workflows)}")
|
||||
|
||||
project_results = {
|
||||
"project_name": project_path.name,
|
||||
"project_path": str(project_path),
|
||||
"workflows": {},
|
||||
"summary": {},
|
||||
"start_time": time.time()
|
||||
}
|
||||
|
||||
# Get project info
|
||||
try:
|
||||
files = get_project_files(project_path)
|
||||
project_results["file_count"] = len(files)
|
||||
project_results["total_size"] = sum(f.stat().st_size for f in files if f.exists())
|
||||
print(f" Files: {len(files)}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not analyze project structure: {e}")
|
||||
project_results["file_count"] = 0
|
||||
project_results["total_size"] = 0
|
||||
|
||||
# Create project output directory
|
||||
project_output_dir = output_dir / project_path.name
|
||||
project_output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Run each workflow
|
||||
for workflow_name in workflows:
|
||||
try:
|
||||
workflow_result = await self._run_workflow_on_project(
|
||||
project_path,
|
||||
workflow_name,
|
||||
project_output_dir
|
||||
)
|
||||
project_results["workflows"][workflow_name] = workflow_result
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Failed to run {workflow_name}: {e}")
|
||||
project_results["workflows"][workflow_name] = {
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
# Calculate summary
|
||||
project_results["end_time"] = time.time()
|
||||
project_results["duration"] = project_results["end_time"] - project_results["start_time"]
|
||||
project_results["summary"] = self._calculate_project_summary(project_results)
|
||||
|
||||
# Save project summary
|
||||
summary_file = project_output_dir / "analysis_summary.json"
|
||||
with open(summary_file, 'w') as f:
|
||||
json.dump(project_results, f, indent=2, default=str)
|
||||
|
||||
print(f" ✅ Analysis complete in {project_results['duration']:.1f}s")
|
||||
return project_results
|
||||
|
||||
async def _run_workflow_on_project(
|
||||
self,
|
||||
project_path: Path,
|
||||
workflow_name: str,
|
||||
output_dir: Path
|
||||
) -> Dict[str, Any]:
|
||||
"""Run a single workflow on a project."""
|
||||
print(f" 🔄 Running {workflow_name}...")
|
||||
|
||||
# Get workflow metadata for better parameter selection
|
||||
try:
|
||||
metadata = await self.client.aget_workflow_metadata(workflow_name)
|
||||
|
||||
# Determine appropriate timeout based on workflow type
|
||||
if "fuzzing" in metadata.tags:
|
||||
timeout = 1800 # 30 minutes for fuzzing
|
||||
volume_mode = "rw"
|
||||
elif "dynamic" in metadata.tags:
|
||||
timeout = 900 # 15 minutes for dynamic analysis
|
||||
volume_mode = "rw"
|
||||
else:
|
||||
timeout = 300 # 5 minutes for static analysis
|
||||
volume_mode = "ro"
|
||||
|
||||
except Exception:
|
||||
# Fallback settings
|
||||
timeout = 600
|
||||
volume_mode = "ro"
|
||||
|
||||
# Create submission
|
||||
submission = create_workflow_submission(
|
||||
target_path=project_path,
|
||||
volume_mode=volume_mode,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Submit workflow
|
||||
start_time = time.time()
|
||||
response = await self.client.asubmit_workflow(workflow_name, submission)
|
||||
|
||||
# Wait for completion
|
||||
try:
|
||||
final_status = await self.client.await_for_completion(
|
||||
response.run_id,
|
||||
poll_interval=10.0,
|
||||
timeout=float(timeout + 300) # Add buffer for completion timeout
|
||||
)
|
||||
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
|
||||
# Get findings if successful
|
||||
findings = None
|
||||
if final_status.is_completed and not final_status.is_failed:
|
||||
try:
|
||||
findings = await self.client.aget_run_findings(response.run_id)
|
||||
|
||||
# Save SARIF results
|
||||
sarif_file = output_dir / f"{workflow_name}_results.sarif.json"
|
||||
save_sarif_to_file(findings.sarif, sarif_file)
|
||||
|
||||
print(f" ✅ {workflow_name} completed: {format_sarif_summary(findings.sarif)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not retrieve findings for {workflow_name}: {e}")
|
||||
|
||||
result = {
|
||||
"status": "completed" if final_status.is_completed else "failed",
|
||||
"run_id": response.run_id,
|
||||
"duration": duration,
|
||||
"final_status": final_status.status,
|
||||
"findings_summary": format_sarif_summary(findings.sarif) if findings else None,
|
||||
"severity_counts": count_sarif_severity_levels(findings.sarif) if findings else None
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
print(f" ❌ {workflow_name} failed after {duration:.1f}s: {e}")
|
||||
|
||||
return {
|
||||
"status": "failed",
|
||||
"run_id": response.run_id,
|
||||
"duration": duration,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def _calculate_project_summary(self, project_results: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Calculate summary statistics for a project analysis."""
|
||||
workflows = project_results["workflows"]
|
||||
|
||||
total_findings = {}
|
||||
successful_workflows = 0
|
||||
failed_workflows = 0
|
||||
|
||||
for workflow_name, workflow_result in workflows.items():
|
||||
if workflow_result["status"] == "completed":
|
||||
successful_workflows += 1
|
||||
|
||||
# Aggregate severity counts
|
||||
severity_counts = workflow_result.get("severity_counts", {})
|
||||
for severity, count in severity_counts.items():
|
||||
total_findings[severity] = total_findings.get(severity, 0) + count
|
||||
|
||||
else:
|
||||
failed_workflows += 1
|
||||
|
||||
return {
|
||||
"successful_workflows": successful_workflows,
|
||||
"failed_workflows": failed_workflows,
|
||||
"total_workflows": len(workflows),
|
||||
"total_findings": total_findings,
|
||||
"total_issues": sum(total_findings.values())
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main batch analysis example."""
|
||||
# Configuration
|
||||
projects_to_analyze = [
|
||||
Path.cwd(), # Current directory
|
||||
# Add more project paths here
|
||||
# Path("/path/to/project1"),
|
||||
# Path("/path/to/project2"),
|
||||
]
|
||||
|
||||
workflows_to_run = [
|
||||
# "static-analysis",
|
||||
# "security-scan",
|
||||
# "dependency-check",
|
||||
# Add actual workflow names from your FuzzForge instance
|
||||
]
|
||||
|
||||
output_base_dir = Path("./analysis_results")
|
||||
|
||||
# Initialize client
|
||||
async with FuzzForgeClient(base_url="http://localhost:8000") as client:
|
||||
try:
|
||||
# Check API status
|
||||
print("🔗 Connecting to FuzzForge API...")
|
||||
status = await client.aget_api_status()
|
||||
print(f"✅ Connected to {status.name} v{status.version}")
|
||||
|
||||
# Get available workflows
|
||||
available_workflows = await client.alist_workflows()
|
||||
available_names = [w.name for w in available_workflows]
|
||||
|
||||
print(f"📋 Available workflows: {', '.join(available_names)}")
|
||||
|
||||
# Filter requested workflows to only include available ones
|
||||
valid_workflows = [w for w in workflows_to_run if w in available_names]
|
||||
|
||||
if not valid_workflows:
|
||||
print("⚠️ No valid workflows specified, using all available workflows")
|
||||
valid_workflows = available_names[:3] # Limit to first 3 for demo
|
||||
|
||||
print(f"🎯 Will run workflows: {', '.join(valid_workflows)}")
|
||||
|
||||
# Create output directory
|
||||
output_base_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Initialize batch analyzer
|
||||
analyzer = BatchAnalyzer(client)
|
||||
|
||||
# Analyze each project
|
||||
batch_start_time = time.time()
|
||||
|
||||
for project_path in projects_to_analyze:
|
||||
if not project_path.exists() or not project_path.is_dir():
|
||||
print(f"⚠️ Skipping invalid project path: {project_path}")
|
||||
continue
|
||||
|
||||
project_result = await analyzer.analyze_project(
|
||||
project_path,
|
||||
valid_workflows,
|
||||
output_base_dir
|
||||
)
|
||||
analyzer.results.append(project_result)
|
||||
|
||||
batch_end_time = time.time()
|
||||
batch_duration = batch_end_time - batch_start_time
|
||||
|
||||
# Generate batch summary report
|
||||
print(f"\n📊 Batch Analysis Complete!")
|
||||
print(f" Total time: {batch_duration:.1f}s")
|
||||
print(f" Projects analyzed: {len(analyzer.results)}")
|
||||
|
||||
# Create overall summary
|
||||
batch_summary = {
|
||||
"start_time": batch_start_time,
|
||||
"end_time": batch_end_time,
|
||||
"duration": batch_duration,
|
||||
"projects": analyzer.results,
|
||||
"overall_stats": {}
|
||||
}
|
||||
|
||||
# Calculate overall statistics
|
||||
total_successful = sum(r["summary"]["successful_workflows"] for r in analyzer.results)
|
||||
total_failed = sum(r["summary"]["failed_workflows"] for r in analyzer.results)
|
||||
total_issues = sum(r["summary"]["total_issues"] for r in analyzer.results)
|
||||
|
||||
batch_summary["overall_stats"] = {
|
||||
"total_successful_runs": total_successful,
|
||||
"total_failed_runs": total_failed,
|
||||
"total_issues_found": total_issues
|
||||
}
|
||||
|
||||
print(f" Successful runs: {total_successful}")
|
||||
print(f" Failed runs: {total_failed}")
|
||||
print(f" Total issues found: {total_issues}")
|
||||
|
||||
# Save batch summary
|
||||
batch_summary_file = output_base_dir / "batch_summary.json"
|
||||
with open(batch_summary_file, 'w') as f:
|
||||
json.dump(batch_summary, f, indent=2, default=str)
|
||||
|
||||
print(f"\n💾 Results saved to: {output_base_dir}")
|
||||
print(f" Batch summary: {batch_summary_file}")
|
||||
|
||||
# Display project summaries
|
||||
print(f"\n📈 Project Summaries:")
|
||||
for result in analyzer.results:
|
||||
print(f" {result['project_name']}: " +
|
||||
f"{result['summary']['successful_workflows']}/{result['summary']['total_workflows']} workflows successful, " +
|
||||
f"{result['summary']['total_issues']} issues found")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Batch analysis failed: {e}")
|
||||
|
||||
|
||||
def create_sample_batch_config():
|
||||
"""Create a sample batch configuration file."""
|
||||
config = {
|
||||
"projects": [
|
||||
{
|
||||
"name": "my-web-app",
|
||||
"path": "/path/to/my-web-app",
|
||||
"workflows": ["static-analysis", "security-scan"],
|
||||
"parameters": {
|
||||
"timeout": 600
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "api-service",
|
||||
"path": "/path/to/api-service",
|
||||
"workflows": ["dependency-check", "fuzzing"],
|
||||
"parameters": {
|
||||
"timeout": 1800
|
||||
}
|
||||
}
|
||||
],
|
||||
"output_directory": "./batch_analysis_results",
|
||||
"concurrent_limit": 2,
|
||||
"retry_failed": True
|
||||
}
|
||||
|
||||
config_file = Path("batch_config.json")
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print(f"📄 Sample batch configuration created: {config_file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--create-config":
|
||||
create_sample_batch_config()
|
||||
else:
|
||||
print("🔄 Starting batch analysis...")
|
||||
print("💡 Use --create-config to generate sample configuration")
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 FuzzingLabs
|
||||
#
|
||||
# Licensed under the Business Source License 1.1 (BSL). See the LICENSE file
|
||||
# at the root of this repository for details.
|
||||
#
|
||||
# After the Change Date (four years from publication), this version of the
|
||||
# Licensed Work will be made available under the Apache License, Version 2.0.
|
||||
# See the LICENSE-APACHE file or http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
"""
|
||||
Real-time fuzzing monitoring example.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Submit a fuzzing workflow
|
||||
2. Monitor fuzzing progress in real-time using WebSocket or SSE
|
||||
3. Display live statistics and crash reports
|
||||
4. Handle real-time data updates
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from fuzzforge_sdk import FuzzForgeClient, WorkflowSubmission
|
||||
from fuzzforge_sdk.utils import (
|
||||
create_workflow_submission,
|
||||
create_resource_limits,
|
||||
format_duration,
|
||||
format_execution_rate
|
||||
)
|
||||
|
||||
|
||||
class FuzzingMonitor:
|
||||
"""Real-time fuzzing monitor with graceful shutdown."""
|
||||
|
||||
def __init__(self, client: FuzzForgeClient):
|
||||
self.client = client
|
||||
self.running = True
|
||||
self.run_id = None
|
||||
|
||||
def signal_handler(self, signum, frame):
|
||||
"""Handle shutdown signals gracefully."""
|
||||
print(f"\n🛑 Received signal {signum}, shutting down...")
|
||||
self.running = False
|
||||
|
||||
async def monitor_websocket(self, run_id: str):
|
||||
"""Monitor fuzzing via WebSocket."""
|
||||
print("🔌 Starting WebSocket monitoring...")
|
||||
|
||||
try:
|
||||
async for message in self.client.monitor_fuzzing_websocket(run_id):
|
||||
if not self.running:
|
||||
break
|
||||
|
||||
if message.type == "stats_update":
|
||||
self.display_stats(message.data)
|
||||
elif message.type == "crash_report":
|
||||
self.display_crash(message.data)
|
||||
elif message.type == "heartbeat":
|
||||
print("💓 Heartbeat")
|
||||
else:
|
||||
print(f"📨 Received: {message.type}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"❌ WebSocket error: {e}")
|
||||
|
||||
def monitor_sse(self, run_id: str):
|
||||
"""Monitor fuzzing via Server-Sent Events."""
|
||||
print("📡 Starting SSE monitoring...")
|
||||
|
||||
try:
|
||||
for message in self.client.monitor_fuzzing_sse(run_id):
|
||||
if not self.running:
|
||||
break
|
||||
|
||||
if message.type == "stats":
|
||||
self.display_stats(message.data)
|
||||
elif message.type == "crash":
|
||||
self.display_crash(message.data)
|
||||
else:
|
||||
print(f"📨 Received: {message.type}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"❌ SSE error: {e}")
|
||||
|
||||
def display_stats(self, stats_data):
|
||||
"""Display fuzzing statistics."""
|
||||
# Clear screen and move cursor to top
|
||||
print("\033[2J\033[H", end="")
|
||||
|
||||
print("🎯 FuzzForge Live Fuzzing Monitor")
|
||||
print("=" * 50)
|
||||
print(f"Run ID: {stats_data.get('run_id', 'unknown')}")
|
||||
print(f"Workflow: {stats_data.get('workflow', 'unknown')}")
|
||||
print()
|
||||
|
||||
# Statistics
|
||||
executions = stats_data.get('executions', 0)
|
||||
exec_per_sec = stats_data.get('executions_per_sec', 0.0)
|
||||
crashes = stats_data.get('crashes', 0)
|
||||
unique_crashes = stats_data.get('unique_crashes', 0)
|
||||
coverage = stats_data.get('coverage')
|
||||
corpus_size = stats_data.get('corpus_size', 0)
|
||||
elapsed_time = stats_data.get('elapsed_time', 0)
|
||||
|
||||
print(f"📊 Statistics:")
|
||||
print(f" Executions: {executions:,}")
|
||||
print(f" Rate: {format_execution_rate(exec_per_sec)}")
|
||||
print(f" Runtime: {format_duration(elapsed_time)}")
|
||||
print(f" Corpus size: {corpus_size:,}")
|
||||
|
||||
if coverage is not None:
|
||||
print(f" Coverage: {coverage:.1f}%")
|
||||
|
||||
print()
|
||||
print(f"💥 Crashes:")
|
||||
print(f" Total crashes: {crashes}")
|
||||
print(f" Unique crashes: {unique_crashes}")
|
||||
|
||||
last_crash = stats_data.get('last_crash_time')
|
||||
if last_crash:
|
||||
crash_time = datetime.fromisoformat(last_crash.replace('Z', '+00:00'))
|
||||
print(f" Last crash: {crash_time.strftime('%H:%M:%S')}")
|
||||
|
||||
print()
|
||||
print("Press Ctrl+C to stop monitoring")
|
||||
print("-" * 50)
|
||||
|
||||
def display_crash(self, crash_data):
|
||||
"""Display new crash report."""
|
||||
print("\n🚨 NEW CRASH DETECTED!")
|
||||
print(f" Crash ID: {crash_data.get('crash_id')}")
|
||||
print(f" Signal: {crash_data.get('signal', 'unknown')}")
|
||||
print(f" Type: {crash_data.get('crash_type', 'unknown')}")
|
||||
print(f" Severity: {crash_data.get('severity', 'unknown')}")
|
||||
|
||||
if crash_data.get('input_file'):
|
||||
print(f" Input file: {crash_data['input_file']}")
|
||||
|
||||
print("-" * 30)
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main fuzzing monitoring example."""
|
||||
# Initialize client
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
monitor = FuzzingMonitor(client)
|
||||
|
||||
# Set up signal handlers
|
||||
signal.signal(signal.SIGINT, monitor.signal_handler)
|
||||
signal.signal(signal.SIGTERM, monitor.signal_handler)
|
||||
|
||||
try:
|
||||
# Check API status
|
||||
print("🔗 Connecting to FuzzForge API...")
|
||||
status = await client.aget_api_status()
|
||||
print(f"✅ Connected to {status.name} v{status.version}\n")
|
||||
|
||||
# List workflows and find fuzzing ones
|
||||
workflows = await client.alist_workflows()
|
||||
fuzzing_workflows = [w for w in workflows if "fuzz" in w.name.lower() or "fuzzing" in w.tags]
|
||||
|
||||
if not fuzzing_workflows:
|
||||
print("❌ No fuzzing workflows found")
|
||||
print("Available workflows:")
|
||||
for w in workflows:
|
||||
print(f" • {w.name} (tags: {w.tags})")
|
||||
return
|
||||
|
||||
# Select first fuzzing workflow
|
||||
selected_workflow = fuzzing_workflows[0]
|
||||
print(f"🎯 Selected fuzzing workflow: {selected_workflow.name}")
|
||||
|
||||
# Create submission with fuzzing-appropriate settings
|
||||
target_path = Path.cwd().absolute()
|
||||
|
||||
# Set longer timeout and resource limits for fuzzing
|
||||
resource_limits = create_resource_limits(
|
||||
cpu_limit="2", # 2 CPU cores
|
||||
memory_limit="4Gi", # 4GB memory
|
||||
cpu_request="1", # Guarantee 1 core
|
||||
memory_request="2Gi" # Guarantee 2GB
|
||||
)
|
||||
|
||||
submission = create_workflow_submission(
|
||||
target_path=target_path,
|
||||
volume_mode="rw", # Fuzzing may need to write files
|
||||
timeout=3600, # 1 hour timeout
|
||||
resource_limits=resource_limits,
|
||||
parameters={
|
||||
"max_len": 1024, # Maximum input length
|
||||
"timeout": 10, # Per-execution timeout
|
||||
"runs": 1000000, # Number of executions
|
||||
}
|
||||
)
|
||||
|
||||
print(f"🚀 Submitting fuzzing workflow...")
|
||||
response = await client.asubmit_workflow(selected_workflow.name, submission)
|
||||
monitor.run_id = response.run_id
|
||||
|
||||
print(f"✅ Fuzzing started!")
|
||||
print(f" Run ID: {response.run_id}")
|
||||
print(f" Initial status: {response.status}")
|
||||
print()
|
||||
|
||||
# Wait a moment for fuzzing to initialize
|
||||
await asyncio.sleep(5)
|
||||
|
||||
# Get initial stats to verify fuzzing is tracked
|
||||
try:
|
||||
stats = await client.aget_fuzzing_stats(response.run_id)
|
||||
print(f"📊 Fuzzing tracking initialized for workflow: {stats.workflow}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Warning: Fuzzing tracking not available: {e}")
|
||||
print(" Monitoring will show run status updates only")
|
||||
|
||||
# Choose monitoring method
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--sse":
|
||||
print("📡 Using Server-Sent Events for monitoring...")
|
||||
monitor.monitor_sse(response.run_id)
|
||||
else:
|
||||
print("🔌 Using WebSocket for monitoring...")
|
||||
await monitor.monitor_websocket(response.run_id)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Interrupted by user")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
finally:
|
||||
# Cleanup
|
||||
if monitor.run_id:
|
||||
try:
|
||||
print(f"\n🧹 Cleaning up fuzzing run {monitor.run_id}...")
|
||||
await client.acleanup_fuzzing_run(monitor.run_id)
|
||||
print("✅ Cleanup completed")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Cleanup failed: {e}")
|
||||
|
||||
await client.aclose()
|
||||
|
||||
|
||||
def sync_monitor_example():
|
||||
"""Example of synchronous SSE monitoring."""
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
|
||||
try:
|
||||
# This would require a pre-existing fuzzing run
|
||||
run_id = input("Enter fuzzing run ID to monitor: ").strip()
|
||||
if not run_id:
|
||||
print("❌ Run ID required")
|
||||
return
|
||||
|
||||
print(f"📡 Monitoring fuzzing run: {run_id}")
|
||||
print("Press Ctrl+C to stop")
|
||||
print()
|
||||
|
||||
monitor = FuzzingMonitor(client)
|
||||
monitor.monitor_sse(run_id)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ Monitoring stopped")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--sync":
|
||||
print("🔄 Running synchronous SSE monitoring...")
|
||||
sync_monitor_example()
|
||||
else:
|
||||
print("🔄 Running async WebSocket monitoring...")
|
||||
print("💡 Use --sse flag for Server-Sent Events")
|
||||
print("💡 Use --sync flag for synchronous monitoring")
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 FuzzingLabs
|
||||
#
|
||||
# Licensed under the Business Source License 1.1 (BSL). See the LICENSE file
|
||||
# at the root of this repository for details.
|
||||
#
|
||||
# After the Change Date (four years from publication), this version of the
|
||||
# Licensed Work will be made available under the Apache License, Version 2.0.
|
||||
# See the LICENSE-APACHE file or http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
"""
|
||||
Quick demo to save findings to a SARIF file.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from fuzzforge_sdk import FuzzForgeClient
|
||||
from fuzzforge_sdk.utils import create_workflow_submission, save_sarif_to_file, format_sarif_summary
|
||||
|
||||
def main():
|
||||
"""Save findings demo."""
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
|
||||
try:
|
||||
# List workflows
|
||||
workflows = client.list_workflows()
|
||||
if not workflows:
|
||||
print("❌ No workflows available")
|
||||
return
|
||||
|
||||
# Submit workflow
|
||||
workflow_name = workflows[0].name
|
||||
submission = create_workflow_submission(
|
||||
target_path=Path.cwd().absolute(),
|
||||
volume_mode="ro",
|
||||
timeout=300
|
||||
)
|
||||
|
||||
print(f"🚀 Submitting {workflow_name}...")
|
||||
response = client.submit_workflow(workflow_name, submission)
|
||||
|
||||
# Wait for completion
|
||||
print("⏱️ Waiting for completion...")
|
||||
final_status = client.wait_for_completion(response.run_id)
|
||||
|
||||
if final_status.is_completed:
|
||||
# Get findings
|
||||
findings = client.get_run_findings(response.run_id)
|
||||
summary = format_sarif_summary(findings.sarif)
|
||||
print(f"📈 {summary}")
|
||||
|
||||
# Save to file
|
||||
output_file = Path("fuzzforge_findings.sarif.json")
|
||||
save_sarif_to_file(findings.sarif, output_file)
|
||||
print(f"💾 Findings saved to: {output_file.absolute()}")
|
||||
|
||||
# Show file info
|
||||
print(f"📄 File size: {output_file.stat().st_size:,} bytes")
|
||||
|
||||
else:
|
||||
print(f"❌ Workflow failed: {final_status.status}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2025 FuzzingLabs
|
||||
#
|
||||
# Licensed under the Business Source License 1.1 (BSL). See the LICENSE file
|
||||
# at the root of this repository for details.
|
||||
#
|
||||
# After the Change Date (four years from publication), this version of the
|
||||
# Licensed Work will be made available under the Apache License, Version 2.0.
|
||||
# See the LICENSE-APACHE file or http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
"""
|
||||
Automated workflow testing example using FuzzForge SDK.
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Use the WorkflowTester for automated testing
|
||||
2. Test all workflows against vulnerable test projects
|
||||
3. Generate comprehensive test reports
|
||||
4. Handle test failures and debugging
|
||||
|
||||
Usage:
|
||||
python test_all_workflows.py [--detailed] [--workflow WORKFLOW_NAME]
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fuzzforge_sdk import (
|
||||
FuzzForgeClient,
|
||||
WorkflowTester,
|
||||
format_test_summary,
|
||||
DEFAULT_TEST_CONFIG
|
||||
)
|
||||
from fuzzforge_sdk.exceptions import FuzzForgeError
|
||||
|
||||
|
||||
def setup_logging(verbose: bool = False):
|
||||
"""Setup logging configuration."""
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test execution function."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Automated workflow testing for FuzzForge",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Test all workflows with detailed output
|
||||
python test_all_workflows.py --detailed
|
||||
|
||||
# Test only static analysis workflow
|
||||
python test_all_workflows.py --workflow static_analysis_scan
|
||||
|
||||
# Test with verbose logging
|
||||
python test_all_workflows.py --verbose --detailed
|
||||
|
||||
# Show available test configurations
|
||||
python test_all_workflows.py --show-config
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--base-url",
|
||||
default="http://localhost:8000",
|
||||
help="FuzzForge API base URL (default: http://localhost:8000)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--test-projects-path",
|
||||
help="Path to test projects directory (auto-detected if not provided)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--workflow",
|
||||
help="Test only specific workflow"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--detailed",
|
||||
action="store_true",
|
||||
help="Show detailed test results"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--verbose", "-v",
|
||||
action="store_true",
|
||||
help="Enable verbose logging"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--show-config",
|
||||
action="store_true",
|
||||
help="Show test configurations and exit"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
type=int,
|
||||
help="Override default timeout for workflow tests (seconds)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging
|
||||
setup_logging(args.verbose)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Show configuration and exit if requested
|
||||
if args.show_config:
|
||||
print("🔧 FuzzForge Workflow Test Configurations:")
|
||||
print("=" * 60)
|
||||
for workflow_name, config in DEFAULT_TEST_CONFIG.items():
|
||||
print(f"📋 {workflow_name}:")
|
||||
print(f" Test Project: {config['test_project']}")
|
||||
print(f" Expected Findings: ≥{config['expected_min_findings']}")
|
||||
print(f" Timeout: {config['timeout']}s")
|
||||
print(f" Description: {config['description']}")
|
||||
print()
|
||||
return
|
||||
|
||||
try:
|
||||
# Initialize client and tester
|
||||
print(f"🔗 Connecting to FuzzForge API at {args.base_url}")
|
||||
client = FuzzForgeClient(base_url=args.base_url)
|
||||
|
||||
# Verify API connection
|
||||
status = client.get_api_status()
|
||||
print(f"✅ Connected to {status.name} v{status.version}")
|
||||
|
||||
# Initialize tester
|
||||
tester = WorkflowTester(
|
||||
client=client,
|
||||
test_projects_base_path=args.test_projects_path
|
||||
)
|
||||
|
||||
# Check if test projects path was found
|
||||
if not Path(tester.test_projects_base_path).exists():
|
||||
print(f"❌ Test projects path not found: {tester.test_projects_base_path}")
|
||||
print("Please ensure test projects are available or specify --test-projects-path")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"📁 Using test projects from: {tester.test_projects_base_path}")
|
||||
print()
|
||||
|
||||
# Run tests
|
||||
if args.workflow:
|
||||
# Test single workflow
|
||||
print(f"🧪 Testing workflow: {args.workflow}")
|
||||
print("-" * 40)
|
||||
|
||||
# Override timeout if specified
|
||||
kwargs = {}
|
||||
if args.timeout:
|
||||
kwargs['timeout'] = args.timeout
|
||||
|
||||
result = tester.test_workflow(args.workflow, **kwargs)
|
||||
|
||||
# Display result
|
||||
status_icon = "✅" if result.passed else "❌"
|
||||
print(f"{status_icon} {result.workflow_name}")
|
||||
print(f" Project: {Path(result.test_project_path).name}")
|
||||
print(f" Findings: {result.findings_count} (expected ≥{result.expected_min_findings})")
|
||||
print(f" Duration: {result.execution_time:.1f}s")
|
||||
|
||||
if result.error:
|
||||
print(f" ❌ Error: {result.error}")
|
||||
|
||||
if result.run_id:
|
||||
print(f" 🔍 Run ID: {result.run_id}")
|
||||
|
||||
print()
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if result.passed else 1)
|
||||
|
||||
else:
|
||||
# Test all workflows
|
||||
print("🧪 Testing all available workflows...")
|
||||
print("-" * 40)
|
||||
|
||||
# Get available workflows
|
||||
workflows = client.list_workflows()
|
||||
print(f"Found {len(workflows)} workflows: {', '.join(w.name for w in workflows)}")
|
||||
print()
|
||||
|
||||
# Run comprehensive test suite
|
||||
summary = tester.test_all_workflows()
|
||||
|
||||
# Display results
|
||||
print(format_test_summary(summary, detailed=args.detailed))
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if summary.failed == 0 else 1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ Test execution interrupted by user")
|
||||
sys.exit(130)
|
||||
|
||||
except FuzzForgeError as e:
|
||||
logger.error(f"FuzzForge API error: {e}")
|
||||
print(f"❌ FuzzForge API error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Unexpected error: {e}")
|
||||
print(f"❌ Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def test_single_workflow_example():
|
||||
"""
|
||||
Example function showing how to test a single workflow programmatically.
|
||||
|
||||
This demonstrates the SDK usage for integration into other scripts.
|
||||
"""
|
||||
# Initialize client and tester
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
tester = WorkflowTester(client)
|
||||
|
||||
# Test a specific workflow
|
||||
result = tester.test_workflow(
|
||||
workflow_name="static_analysis_scan",
|
||||
expected_min_findings=3, # Override default expectation
|
||||
timeout=300 # 5 minute timeout
|
||||
)
|
||||
|
||||
# Handle results
|
||||
if result.passed:
|
||||
print(f"✅ Test passed! Found {result.findings_count} vulnerabilities")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Test failed: {result.error}")
|
||||
return False
|
||||
|
||||
|
||||
def validate_deployments_example():
|
||||
"""
|
||||
Example function showing how to validate workflow deployments.
|
||||
|
||||
Useful for health checks and deployment verification.
|
||||
"""
|
||||
client = FuzzForgeClient(base_url="http://localhost:8000")
|
||||
tester = WorkflowTester(client)
|
||||
|
||||
# Check all workflows are deployed
|
||||
workflows = client.list_workflows()
|
||||
|
||||
print("🔍 Validating workflow deployments:")
|
||||
all_valid = True
|
||||
|
||||
for workflow in workflows:
|
||||
is_valid = tester.validate_workflow_deployment(workflow.name)
|
||||
status_icon = "✅" if is_valid else "❌"
|
||||
print(f" {status_icon} {workflow.name}")
|
||||
|
||||
if not is_valid:
|
||||
all_valid = False
|
||||
|
||||
return all_valid
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user