Initial commit

This commit is contained in:
Tanguy Duhamel
2025-09-29 21:26:41 +02:00
parent ecf8d49dde
commit 0547b78429
208 changed files with 72069 additions and 53 deletions
+311
View File
@@ -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()
+399
View File
@@ -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())
+286
View File
@@ -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())
+70
View File
@@ -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()
+274
View File
@@ -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()