mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-02-12 17:12:46 +00:00
Merge branch 'master' into dev for v0.7.0 release
Resolved conflicts: - Kept monitor.py (dev version - required for live monitoring) - Kept workflow_exec.py (dev version - includes worker management, --live, --fail-on, --export-sarif) - Kept main.py (dev version - includes new command structure) All conflicts resolved in favor of dev branch features for 0.7.0 release.
This commit is contained in:
70
.github/workflows/ci-python.yml
vendored
Normal file
70
.github/workflows/ci-python.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Python CI
|
||||
|
||||
# This is a dumb Ci to ensure that the python client and backend builds correctly
|
||||
# It could be optimized to run faster, building, testing and linting only changed code
|
||||
# but for now it is good enough. It runs on every push and PR to any branch.
|
||||
# It also runs on demand.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
paths:
|
||||
- "ai/**"
|
||||
- "backend/**"
|
||||
- "cli/**"
|
||||
- "sdk/**"
|
||||
- "src/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "ai/**"
|
||||
- "backend/**"
|
||||
- "cli/**"
|
||||
- "sdk/**"
|
||||
- "src/**"
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: ci
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v6
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
- name: Set up Python
|
||||
run: uv python install
|
||||
|
||||
# Validate no obvious issues
|
||||
# Quick hack because CLI returns non-zero exit code when no args are provided
|
||||
- name: Run base command
|
||||
run: |
|
||||
set +e
|
||||
uv run ff
|
||||
if [ $? -ne 2 ]; then
|
||||
echo "Expected exit code 2 from 'uv run ff', got $?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build fuzzforge_ai package
|
||||
run: uv build
|
||||
|
||||
- name: Build ai package
|
||||
working-directory: ai
|
||||
run: uv build
|
||||
|
||||
- name: Build cli package
|
||||
working-directory: cli
|
||||
run: uv build
|
||||
|
||||
- name: Build sdk package
|
||||
working-directory: sdk
|
||||
run: uv build
|
||||
|
||||
- name: Build backend package
|
||||
working-directory: backend
|
||||
run: uv build
|
||||
5
.github/workflows/docs-deploy.yml
vendored
5
.github/workflows/docs-deploy.yml
vendored
@@ -1,10 +1,13 @@
|
||||
name: Deploy Docusaurus to GitHub Pages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
9
.github/workflows/docs-test-deploy.yml
vendored
9
.github/workflows/docs-test-deploy.yml
vendored
@@ -1,9 +1,14 @@
|
||||
name: Docusaurus test deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
paths:
|
||||
- "docs/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
test-deploy:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<p align="center"><strong>AI-powered workflow automation and AI Agents for AppSec, Fuzzing & Offensive Security</strong></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.com/invite/acqv9FVG"><img src="https://img.shields.io/discord/1420767905255133267?logo=discord&label=Discord" alt="Discord"></a>
|
||||
<a href="https://discord.gg/8XEX33UUwZ/"><img src="https://img.shields.io/discord/1420767905255133267?logo=discord&label=Discord" alt="Discord"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-BSL%20%2B%20Apache-orange" alt="License: BSL + Apache"></a>
|
||||
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"/></a>
|
||||
<a href="https://fuzzforge.ai"><img src="https://img.shields.io/badge/Website-fuzzforge.ai-blue" alt="Website"/></a>
|
||||
@@ -165,7 +165,7 @@ _AI agents automatically analyzing code and providing security insights_
|
||||
|
||||
- 🌐 [Website](https://fuzzforge.ai)
|
||||
- 📖 [Documentation](https://docs.fuzzforge.ai)
|
||||
- 💬 [Community Discord](https://discord.com/invite/acqv9FVG)
|
||||
- 💬 [Community Discord](https://discord.gg/8XEX33UUwZ)
|
||||
- 🎓 [FuzzingLabs Academy](https://academy.fuzzinglabs.com/?coupon=GITHUB_FUZZFORGE)
|
||||
|
||||
---
|
||||
@@ -194,7 +194,7 @@ Planned features and improvements:
|
||||
- ☁️ Multi-tenant SaaS platform with team collaboration
|
||||
- 📊 Advanced reporting & analytics
|
||||
|
||||
👉 Follow updates in the [GitHub issues](../../issues) and [Discord](https://discord.com/invite/acqv9FVG).
|
||||
👉 Follow updates in the [GitHub issues](../../issues) and [Discord](https://discord.gg/8XEX33UUwZ)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -80,8 +80,6 @@ fuzzforge workflows info security_assessment
|
||||
# Submit a workflow for analysis
|
||||
fuzzforge workflow security_assessment /path/to/your/code
|
||||
|
||||
# Monitor progress in real-time
|
||||
fuzzforge monitor live <execution-id>
|
||||
|
||||
# View findings when complete
|
||||
fuzzforge finding <execution-id>
|
||||
@@ -222,7 +220,6 @@ $ ff workflow security_assessment ./my-project
|
||||
- `--timeout, -t` - Execution timeout in seconds
|
||||
- `--interactive/--no-interactive, -i/-n` - Interactive parameter input
|
||||
- `--wait, -w` - Wait for execution to complete
|
||||
- `--live, -l` - Show live monitoring during execution
|
||||
|
||||
**Worker Lifecycle Options (v0.7.0):**
|
||||
- `--auto-start/--no-auto-start` - Auto-start required worker (default: from config)
|
||||
@@ -320,39 +317,6 @@ fuzzforge finding export abc123def456 --format csv --output report.csv
|
||||
fuzzforge finding export --format html --output report.html
|
||||
```
|
||||
|
||||
### Real-time Monitoring
|
||||
|
||||
#### `fuzzforge monitor stats <execution-id>`
|
||||
Show current fuzzing statistics.
|
||||
|
||||
```bash
|
||||
# Show stats once
|
||||
fuzzforge monitor stats abc123def456 --once
|
||||
|
||||
# Live updating stats (default)
|
||||
fuzzforge monitor stats abc123def456 --refresh 5
|
||||
```
|
||||
|
||||
#### `fuzzforge monitor crashes <run-id>`
|
||||
Display crash reports for a fuzzing run.
|
||||
|
||||
```bash
|
||||
fuzzforge monitor crashes abc123def456 --limit 50
|
||||
```
|
||||
|
||||
#### `fuzzforge monitor live <run-id>`
|
||||
Real-time monitoring dashboard with live updates.
|
||||
|
||||
```bash
|
||||
fuzzforge monitor live abc123def456 --refresh 3
|
||||
```
|
||||
|
||||
Features:
|
||||
- Live updating statistics
|
||||
- Progress indicators and bars
|
||||
- Run status monitoring
|
||||
- Automatic completion detection
|
||||
|
||||
### Configuration Management
|
||||
|
||||
#### `fuzzforge config show`
|
||||
@@ -560,7 +524,6 @@ cli/
|
||||
│ ├── workflows.py # Workflow management
|
||||
│ ├── runs.py # Run management
|
||||
│ ├── findings.py # Findings management
|
||||
│ ├── monitor.py # Real-time monitoring
|
||||
│ ├── config.py # Configuration commands
|
||||
│ └── status.py # Status information
|
||||
├── pyproject.toml # Project configuration
|
||||
@@ -641,7 +604,6 @@ fuzzforge --help
|
||||
# Command-specific help
|
||||
ff workflows --help
|
||||
ff workflow run --help
|
||||
ff monitor live --help
|
||||
|
||||
# Show version
|
||||
fuzzforge --version
|
||||
@@ -683,4 +645,4 @@ Contributions are welcome! Please see the main FuzzForge repository for contribu
|
||||
|
||||
---
|
||||
|
||||
**FuzzForge CLI** - Making security testing workflows accessible and efficient from the command line.
|
||||
**FuzzForge CLI** - Making security testing workflows accessible and efficient from the command line.
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from typing import Optional
|
||||
|
||||
@@ -32,17 +31,20 @@ app = typer.Typer()
|
||||
@app.command()
|
||||
def project(
|
||||
name: Optional[str] = typer.Option(
|
||||
None, "--name", "-n",
|
||||
help="Project name (defaults to current directory name)"
|
||||
None, "--name", "-n", help="Project name (defaults to current directory name)"
|
||||
),
|
||||
api_url: Optional[str] = typer.Option(
|
||||
None, "--api-url", "-u",
|
||||
help="FuzzForge API URL (defaults to http://localhost:8000)"
|
||||
None,
|
||||
"--api-url",
|
||||
"-u",
|
||||
help="FuzzForge API URL (defaults to http://localhost:8000)",
|
||||
),
|
||||
force: bool = typer.Option(
|
||||
False, "--force", "-f",
|
||||
help="Force initialization even if project already exists"
|
||||
)
|
||||
False,
|
||||
"--force",
|
||||
"-f",
|
||||
help="Force initialization even if project already exists",
|
||||
),
|
||||
):
|
||||
"""
|
||||
📁 Initialize a new FuzzForge project in the current directory.
|
||||
@@ -58,24 +60,20 @@ def project(
|
||||
# Check if project already exists
|
||||
if fuzzforge_dir.exists() and not force:
|
||||
if fuzzforge_dir.is_dir() and any(fuzzforge_dir.iterdir()):
|
||||
console.print("❌ FuzzForge project already exists in this directory", style="red")
|
||||
console.print(
|
||||
"❌ FuzzForge project already exists in this directory", style="red"
|
||||
)
|
||||
console.print("Use --force to reinitialize", style="dim")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Get project name
|
||||
if not name:
|
||||
name = Prompt.ask(
|
||||
"Project name",
|
||||
default=current_dir.name,
|
||||
console=console
|
||||
)
|
||||
name = Prompt.ask("Project name", default=current_dir.name, console=console)
|
||||
|
||||
# Get API URL
|
||||
if not api_url:
|
||||
api_url = Prompt.ask(
|
||||
"FuzzForge API URL",
|
||||
default="http://localhost:8000",
|
||||
console=console
|
||||
"FuzzForge API URL", default="http://localhost:8000", console=console
|
||||
)
|
||||
|
||||
# Confirm initialization
|
||||
@@ -117,15 +115,15 @@ def project(
|
||||
]
|
||||
|
||||
if gitignore_path.exists():
|
||||
with open(gitignore_path, 'r') as f:
|
||||
with open(gitignore_path, "r") as f:
|
||||
existing_content = f.read()
|
||||
|
||||
if "# FuzzForge CLI" not in existing_content:
|
||||
with open(gitignore_path, 'a') as f:
|
||||
with open(gitignore_path, "a") as f:
|
||||
f.write(f"\n{chr(10).join(gitignore_entries)}\n")
|
||||
console.print("📝 Updated .gitignore with FuzzForge entries")
|
||||
else:
|
||||
with open(gitignore_path, 'w') as f:
|
||||
with open(gitignore_path, "w") as f:
|
||||
f.write(f"{chr(10).join(gitignore_entries)}\n")
|
||||
console.print("📝 Created .gitignore")
|
||||
|
||||
@@ -145,9 +143,6 @@ fuzzforge workflows
|
||||
# Submit a workflow for analysis
|
||||
fuzzforge workflow <workflow-name> /path/to/target
|
||||
|
||||
# Monitor run progress
|
||||
fuzzforge monitor live <run-id>
|
||||
|
||||
# View findings
|
||||
fuzzforge finding <run-id>
|
||||
```
|
||||
@@ -159,7 +154,7 @@ fuzzforge finding <run-id>
|
||||
- `.fuzzforge/findings.db` - Local database for runs and findings
|
||||
"""
|
||||
|
||||
with open(readme_path, 'w') as f:
|
||||
with open(readme_path, "w") as f:
|
||||
f.write(readme_content)
|
||||
console.print("📚 Created README.md")
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ Provides "Did you mean...?" functionality and intelligent command/parameter sugg
|
||||
#
|
||||
# Additional attribution and requirements are provided in the NOTICE file.
|
||||
|
||||
|
||||
import difflib
|
||||
from typing import List, Optional, Dict, Any, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
@@ -34,10 +34,9 @@ class FuzzyMatcher:
|
||||
"workflows": ["list", "info"],
|
||||
"runs": ["submit", "status", "list", "rerun"],
|
||||
"findings": ["get", "list", "export", "all"],
|
||||
"monitor": ["stats", "crashes", "live"],
|
||||
"config": ["set", "get", "list", "init"],
|
||||
"ai": ["ask", "summarize", "explain"],
|
||||
"ingest": ["project", "findings"]
|
||||
"ingest": ["project", "findings"],
|
||||
}
|
||||
|
||||
# Common workflow names
|
||||
@@ -47,7 +46,7 @@ class FuzzyMatcher:
|
||||
"infrastructure_scan",
|
||||
"static_analysis_scan",
|
||||
"penetration_testing_scan",
|
||||
"secret_detection_scan"
|
||||
"secret_detection_scan",
|
||||
]
|
||||
|
||||
# Common parameter names
|
||||
@@ -60,24 +59,25 @@ class FuzzyMatcher:
|
||||
"param-file",
|
||||
"interactive",
|
||||
"wait",
|
||||
"live",
|
||||
"format",
|
||||
"output",
|
||||
"severity",
|
||||
"since",
|
||||
"limit",
|
||||
"stats",
|
||||
"export"
|
||||
"export",
|
||||
]
|
||||
|
||||
# Common values
|
||||
self.common_values = {
|
||||
"volume_mode": ["ro", "rw"],
|
||||
"format": ["json", "csv", "html", "sarif"],
|
||||
"severity": ["critical", "high", "medium", "low", "info"]
|
||||
"severity": ["critical", "high", "medium", "low", "info"],
|
||||
}
|
||||
|
||||
def find_closest_command(self, user_input: str, command_group: Optional[str] = None) -> Optional[Tuple[str, float]]:
|
||||
def find_closest_command(
|
||||
self, user_input: str, command_group: Optional[str] = None
|
||||
) -> Optional[Tuple[str, float]]:
|
||||
"""Find the closest matching command."""
|
||||
if command_group and command_group in self.commands:
|
||||
# Search within a specific command group
|
||||
@@ -86,9 +86,7 @@ class FuzzyMatcher:
|
||||
# Search all main commands
|
||||
candidates = list(self.commands.keys())
|
||||
|
||||
matches = difflib.get_close_matches(
|
||||
user_input, candidates, n=1, cutoff=0.6
|
||||
)
|
||||
matches = difflib.get_close_matches(user_input, candidates, n=1, cutoff=0.6)
|
||||
|
||||
if matches:
|
||||
match = matches[0]
|
||||
@@ -114,7 +112,7 @@ class FuzzyMatcher:
|
||||
def find_closest_parameter(self, user_input: str) -> Optional[Tuple[str, float]]:
|
||||
"""Find the closest matching parameter name."""
|
||||
# Remove leading dashes
|
||||
clean_input = user_input.lstrip('-')
|
||||
clean_input = user_input.lstrip("-")
|
||||
|
||||
matches = difflib.get_close_matches(
|
||||
clean_input, self.parameter_names, n=1, cutoff=0.6
|
||||
@@ -139,7 +137,9 @@ class FuzzyMatcher:
|
||||
|
||||
return []
|
||||
|
||||
def get_command_suggestions(self, user_command: List[str]) -> Optional[Dict[str, Any]]:
|
||||
def get_command_suggestions(
|
||||
self, user_command: List[str]
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get suggestions for a user command that may have typos."""
|
||||
if not user_command:
|
||||
return None
|
||||
@@ -153,11 +153,9 @@ class FuzzyMatcher:
|
||||
if closest:
|
||||
match, confidence = closest
|
||||
suggestions["type"] = "main_command"
|
||||
suggestions["suggestions"].append({
|
||||
"text": match,
|
||||
"confidence": confidence,
|
||||
"type": "command"
|
||||
})
|
||||
suggestions["suggestions"].append(
|
||||
{"text": match, "confidence": confidence, "type": "command"}
|
||||
)
|
||||
|
||||
# Check subcommand if present
|
||||
elif len(user_command) > 1:
|
||||
@@ -167,11 +165,13 @@ class FuzzyMatcher:
|
||||
if closest:
|
||||
match, confidence = closest
|
||||
suggestions["type"] = "subcommand"
|
||||
suggestions["suggestions"].append({
|
||||
"text": f"{main_cmd} {match}",
|
||||
"confidence": confidence,
|
||||
"type": "subcommand"
|
||||
})
|
||||
suggestions["suggestions"].append(
|
||||
{
|
||||
"text": f"{main_cmd} {match}",
|
||||
"confidence": confidence,
|
||||
"type": "subcommand",
|
||||
}
|
||||
)
|
||||
|
||||
return suggestions if suggestions["suggestions"] else None
|
||||
|
||||
@@ -210,17 +210,19 @@ def display_command_suggestion(suggestions: Dict[str, Any]):
|
||||
|
||||
# Add helpful context
|
||||
if suggestion_type == "main_command":
|
||||
text.append("\n💡 Use 'fuzzforge --help' to see all available commands", style="dim")
|
||||
text.append(
|
||||
"\n💡 Use 'fuzzforge --help' to see all available commands", style="dim"
|
||||
)
|
||||
elif suggestion_type == "subcommand":
|
||||
main_cmd = suggestions["original"][0]
|
||||
text.append(f"\n💡 Use 'fuzzforge {main_cmd} --help' to see available subcommands", style="dim")
|
||||
text.append(
|
||||
f"\n💡 Use 'fuzzforge {main_cmd} --help' to see available subcommands",
|
||||
style="dim",
|
||||
)
|
||||
|
||||
console.print(Panel(
|
||||
text,
|
||||
title="🤔 Command Suggestion",
|
||||
border_style="yellow",
|
||||
expand=False
|
||||
))
|
||||
console.print(
|
||||
Panel(text, title="🤔 Command Suggestion", border_style="yellow", expand=False)
|
||||
)
|
||||
|
||||
|
||||
def display_workflow_suggestion(original: str, suggestion: str):
|
||||
@@ -234,14 +236,13 @@ def display_workflow_suggestion(original: str, suggestion: str):
|
||||
text.append(f"'{suggestion}'", style="bold green")
|
||||
text.append("?\n\n")
|
||||
|
||||
text.append("💡 Use 'fuzzforge workflows' to see all available workflows", style="dim")
|
||||
text.append(
|
||||
"💡 Use 'fuzzforge workflows' to see all available workflows", style="dim"
|
||||
)
|
||||
|
||||
console.print(Panel(
|
||||
text,
|
||||
title="🔧 Workflow Suggestion",
|
||||
border_style="yellow",
|
||||
expand=False
|
||||
))
|
||||
console.print(
|
||||
Panel(text, title="🔧 Workflow Suggestion", border_style="yellow", expand=False)
|
||||
)
|
||||
|
||||
|
||||
def display_parameter_suggestion(original: str, suggestion: str):
|
||||
@@ -257,12 +258,9 @@ def display_parameter_suggestion(original: str, suggestion: str):
|
||||
|
||||
text.append("💡 Use '--help' to see all available parameters", style="dim")
|
||||
|
||||
console.print(Panel(
|
||||
text,
|
||||
title="⚙️ Parameter Suggestion",
|
||||
border_style="yellow",
|
||||
expand=False
|
||||
))
|
||||
console.print(
|
||||
Panel(text, title="⚙️ Parameter Suggestion", border_style="yellow", expand=False)
|
||||
)
|
||||
|
||||
|
||||
def enhanced_command_not_found_handler(command_parts: List[str]):
|
||||
@@ -306,4 +304,4 @@ def enhanced_parameter_not_found_handler(parameter_name: str):
|
||||
|
||||
|
||||
# Global fuzzy matcher instance
|
||||
fuzzy_matcher = FuzzyMatcher()
|
||||
fuzzy_matcher = FuzzyMatcher()
|
||||
|
||||
Reference in New Issue
Block a user