mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-02-12 19:12:49 +00:00
chore(modules): remove redundant harness-validator module
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
FROM localhost/fuzzforge-modules-sdk:0.1.0
|
||||
|
||||
# Install build tools and Rust nightly for compiling fuzz harnesses
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
# Install cargo-fuzz for validation
|
||||
RUN cargo install cargo-fuzz --locked || true
|
||||
|
||||
COPY ./src /app/src
|
||||
COPY ./pyproject.toml /app/pyproject.toml
|
||||
|
||||
# Remove workspace reference since we're using wheels
|
||||
RUN sed -i '/\[tool\.uv\.sources\]/,/^$/d' /app/pyproject.toml
|
||||
|
||||
RUN uv sync --find-links /wheels
|
||||
@@ -1,45 +0,0 @@
|
||||
PACKAGE=$(word 1, $(shell uv version))
|
||||
VERSION=$(word 2, $(shell uv version))
|
||||
|
||||
PODMAN?=/usr/bin/podman
|
||||
|
||||
SOURCES=./src
|
||||
TESTS=./tests
|
||||
|
||||
.PHONY: bandit build clean format mypy pytest ruff version
|
||||
|
||||
bandit:
|
||||
uv run bandit --recursive $(SOURCES)
|
||||
|
||||
build:
|
||||
$(PODMAN) build --file ./Dockerfile --no-cache --tag $(PACKAGE):$(VERSION)
|
||||
|
||||
save: build
|
||||
$(PODMAN) save --format oci-archive --output /tmp/$(PACKAGE)-$(VERSION).oci $(PACKAGE):$(VERSION)
|
||||
|
||||
clean:
|
||||
@find . -type d \( \
|
||||
-name '*.egg-info' \
|
||||
-o -name '.mypy_cache' \
|
||||
-o -name '.pytest_cache' \
|
||||
-o -name '.ruff_cache' \
|
||||
-o -name '__pycache__' \
|
||||
\) -printf 'removing directory %p\n' -exec rm -rf {} +
|
||||
|
||||
cloc:
|
||||
cloc $(SOURCES)
|
||||
|
||||
format:
|
||||
uv run ruff format $(SOURCES) $(TESTS)
|
||||
|
||||
mypy:
|
||||
uv run mypy $(SOURCES)
|
||||
|
||||
pytest:
|
||||
uv run pytest $(TESTS)
|
||||
|
||||
ruff:
|
||||
uv run ruff check --fix $(SOURCES) $(TESTS)
|
||||
|
||||
version:
|
||||
@echo '$(PACKAGE)@$(VERSION)'
|
||||
@@ -1,46 +0,0 @@
|
||||
# FuzzForge Modules - FIXME
|
||||
|
||||
## Installation
|
||||
|
||||
### Python
|
||||
|
||||
```shell
|
||||
# install the package (users)
|
||||
uv sync
|
||||
# install the package and all development dependencies (developers)
|
||||
uv sync --all-extras
|
||||
```
|
||||
|
||||
### Container
|
||||
|
||||
```shell
|
||||
# build the image
|
||||
make build
|
||||
# run the container
|
||||
mkdir -p "${PWD}/data" "${PWD}/data/input" "${PWD}/data/output"
|
||||
echo '{"settings":{},"resources":[]}' > "${PWD}/data/input/input.json"
|
||||
podman run --rm \
|
||||
--volume "${PWD}/data:/data" \
|
||||
'<name>:<version>' 'uv run module'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```shell
|
||||
uv run module
|
||||
```
|
||||
|
||||
## Development tools
|
||||
|
||||
```shell
|
||||
# run ruff (formatter)
|
||||
make format
|
||||
# run mypy (type checker)
|
||||
make mypy
|
||||
# run tests (pytest)
|
||||
make pytest
|
||||
# run ruff (linter)
|
||||
make ruff
|
||||
```
|
||||
|
||||
See the file `Makefile` at the root of this directory for more tools.
|
||||
@@ -1,6 +0,0 @@
|
||||
[mypy]
|
||||
plugins = pydantic.mypy
|
||||
strict = True
|
||||
warn_unused_ignores = True
|
||||
warn_redundant_casts = True
|
||||
warn_return_any = True
|
||||
@@ -1,31 +0,0 @@
|
||||
[project]
|
||||
name = "harness-validator"
|
||||
version = "0.1.0"
|
||||
description = "FuzzForge module that validates fuzz harnesses compile correctly"
|
||||
authors = []
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"fuzzforge-modules-sdk==0.0.1",
|
||||
"pydantic==2.12.4",
|
||||
"structlog==25.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
lints = [
|
||||
"bandit==1.8.6",
|
||||
"mypy==1.18.2",
|
||||
"ruff==0.14.4",
|
||||
]
|
||||
tests = [
|
||||
"pytest==9.0.2",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
module = "module.__main__:main"
|
||||
|
||||
[tool.uv.sources]
|
||||
fuzzforge-modules-sdk = { workspace = true }
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
@@ -1,19 +0,0 @@
|
||||
line-length = 120
|
||||
|
||||
[lint]
|
||||
select = [ "ALL" ]
|
||||
ignore = [
|
||||
"COM812", # conflicts with the formatter
|
||||
"D100", # ignoring missing docstrings in public modules
|
||||
"D104", # ignoring missing docstrings in public packages
|
||||
"D203", # conflicts with 'D211'
|
||||
"D213", # conflicts with 'D212'
|
||||
"TD002", # ignoring missing author in 'TODO' statements
|
||||
"TD003", # ignoring missing issue link in 'TODO' statements
|
||||
]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"tests/*" = [
|
||||
"PLR2004", # allowing comparisons using unamed numerical constants in tests
|
||||
"S101", # allowing 'assert' statements in tests
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fuzzforge_modules_sdk.api import logs
|
||||
|
||||
from module.mod import Module
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fuzzforge_modules_sdk.api.modules.base import FuzzForgeModule
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""TODO."""
|
||||
logs.configure()
|
||||
module: FuzzForgeModule = Module()
|
||||
module.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,309 +0,0 @@
|
||||
"""Harness Validator module for FuzzForge.
|
||||
|
||||
This module validates that fuzz harnesses compile correctly.
|
||||
It takes a Rust project with a fuzz directory containing harnesses
|
||||
and runs cargo build to verify they compile.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import structlog
|
||||
|
||||
from fuzzforge_modules_sdk.api.constants import PATH_TO_INPUTS, PATH_TO_OUTPUTS
|
||||
from fuzzforge_modules_sdk.api.models import FuzzForgeModuleResults
|
||||
from fuzzforge_modules_sdk.api.modules.base import FuzzForgeModule
|
||||
|
||||
from module.models import Input, Output, ValidationResult, HarnessStatus
|
||||
from module.settings import Settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fuzzforge_modules_sdk.api.models import FuzzForgeModuleResource
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class Module(FuzzForgeModule):
|
||||
"""Harness Validator module - validates that fuzz harnesses compile."""
|
||||
|
||||
_settings: Settings | None
|
||||
_results: list[ValidationResult]
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize an instance of the class."""
|
||||
name: str = "harness-validator"
|
||||
version: str = "0.1.0"
|
||||
FuzzForgeModule.__init__(self, name=name, version=version)
|
||||
self._settings = None
|
||||
self._results = []
|
||||
|
||||
@classmethod
|
||||
def _get_input_type(cls) -> type[Input]:
|
||||
"""Return the input type."""
|
||||
return Input
|
||||
|
||||
@classmethod
|
||||
def _get_output_type(cls) -> type[Output]:
|
||||
"""Return the output type."""
|
||||
return Output
|
||||
|
||||
def _prepare(self, settings: Settings) -> None: # type: ignore[override]
|
||||
"""Prepare the module.
|
||||
|
||||
:param settings: Module settings.
|
||||
|
||||
"""
|
||||
self._settings = settings
|
||||
logger.info("harness-validator preparing", settings=settings.model_dump() if settings else {})
|
||||
|
||||
def _run(self, resources: list[FuzzForgeModuleResource]) -> FuzzForgeModuleResults:
|
||||
"""Run the harness validator.
|
||||
|
||||
:param resources: Input resources (fuzz project directory).
|
||||
:returns: Module execution result.
|
||||
|
||||
"""
|
||||
logger.info("harness-validator starting", resource_count=len(resources))
|
||||
|
||||
# Find the fuzz project directory
|
||||
fuzz_project_src = self._find_fuzz_project(resources)
|
||||
if fuzz_project_src is None:
|
||||
logger.error("No fuzz project found in resources")
|
||||
return FuzzForgeModuleResults.FAILURE
|
||||
|
||||
logger.info("Found fuzz project", path=str(fuzz_project_src))
|
||||
|
||||
# Copy the project to a writable location since /data/input is read-only
|
||||
# and cargo needs to write Cargo.lock and build artifacts
|
||||
import shutil
|
||||
work_dir = Path("/tmp/fuzz-build")
|
||||
if work_dir.exists():
|
||||
shutil.rmtree(work_dir)
|
||||
|
||||
# Copy entire project root (parent of fuzz directory)
|
||||
project_root = fuzz_project_src.parent
|
||||
work_project = work_dir / project_root.name
|
||||
shutil.copytree(project_root, work_project, dirs_exist_ok=True)
|
||||
|
||||
# Adjust fuzz_project to point to the copied location
|
||||
fuzz_project = work_dir / project_root.name / fuzz_project_src.name
|
||||
logger.info("Copied project to writable location", work_dir=str(fuzz_project))
|
||||
|
||||
# Find all harness targets
|
||||
targets = self._find_harness_targets(fuzz_project)
|
||||
if not targets:
|
||||
logger.error("No harness targets found")
|
||||
return FuzzForgeModuleResults.FAILURE
|
||||
|
||||
logger.info("Found harness targets", count=len(targets))
|
||||
|
||||
# Validate each harness
|
||||
all_valid = True
|
||||
for target in targets:
|
||||
result = self._validate_harness(fuzz_project, target)
|
||||
self._results.append(result)
|
||||
if result.status != HarnessStatus.VALID:
|
||||
all_valid = False
|
||||
logger.warning("Harness validation failed",
|
||||
target=target,
|
||||
status=result.status.value,
|
||||
errors=result.errors)
|
||||
else:
|
||||
logger.info("Harness valid", target=target)
|
||||
|
||||
# Set output data for results.json
|
||||
valid_targets = [r.target for r in self._results if r.status == HarnessStatus.VALID]
|
||||
invalid_targets = [r.target for r in self._results if r.status != HarnessStatus.VALID]
|
||||
|
||||
self.set_output(
|
||||
fuzz_project=str(fuzz_project),
|
||||
total_targets=len(self._results),
|
||||
valid_count=len(valid_targets),
|
||||
invalid_count=len(invalid_targets),
|
||||
valid_targets=valid_targets,
|
||||
invalid_targets=invalid_targets,
|
||||
results=[r.model_dump() for r in self._results],
|
||||
)
|
||||
|
||||
valid_count = sum(1 for r in self._results if r.status == HarnessStatus.VALID)
|
||||
logger.info("harness-validator completed",
|
||||
total=len(self._results),
|
||||
valid=valid_count,
|
||||
invalid=len(self._results) - valid_count)
|
||||
|
||||
return FuzzForgeModuleResults.SUCCESS
|
||||
|
||||
def _cleanup(self, settings: Settings) -> None: # type: ignore[override]
|
||||
"""Clean up after execution.
|
||||
|
||||
:param settings: Module settings.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def _find_fuzz_project(self, resources: list[FuzzForgeModuleResource]) -> Path | None:
|
||||
"""Find the fuzz project directory in the resources.
|
||||
|
||||
:param resources: List of input resources.
|
||||
:returns: Path to fuzz project or None.
|
||||
|
||||
"""
|
||||
for resource in resources:
|
||||
path = Path(resource.path)
|
||||
|
||||
# Check if it's a fuzz directory with Cargo.toml
|
||||
if path.is_dir():
|
||||
cargo_toml = path / "Cargo.toml"
|
||||
if cargo_toml.exists():
|
||||
# Check if it has fuzz_targets directory
|
||||
fuzz_targets = path / "fuzz_targets"
|
||||
if fuzz_targets.is_dir():
|
||||
return path
|
||||
|
||||
# Check for fuzz subdirectory
|
||||
fuzz_dir = path / "fuzz"
|
||||
if fuzz_dir.is_dir():
|
||||
cargo_toml = fuzz_dir / "Cargo.toml"
|
||||
if cargo_toml.exists():
|
||||
return fuzz_dir
|
||||
|
||||
return None
|
||||
|
||||
def _find_harness_targets(self, fuzz_project: Path) -> list[str]:
|
||||
"""Find all harness target names in the fuzz project.
|
||||
|
||||
:param fuzz_project: Path to the fuzz project.
|
||||
:returns: List of target names.
|
||||
|
||||
"""
|
||||
targets = []
|
||||
fuzz_targets_dir = fuzz_project / "fuzz_targets"
|
||||
|
||||
if fuzz_targets_dir.is_dir():
|
||||
for rs_file in fuzz_targets_dir.glob("*.rs"):
|
||||
# Target name is the file name without extension
|
||||
target_name = rs_file.stem
|
||||
targets.append(target_name)
|
||||
|
||||
return targets
|
||||
|
||||
def _validate_harness(self, fuzz_project: Path, target: str) -> ValidationResult:
|
||||
"""Validate a single harness by compiling it.
|
||||
|
||||
:param fuzz_project: Path to the fuzz project.
|
||||
:param target: Name of the harness target.
|
||||
:returns: Validation result.
|
||||
|
||||
"""
|
||||
harness_file = fuzz_project / "fuzz_targets" / f"{target}.rs"
|
||||
|
||||
if not harness_file.exists():
|
||||
return ValidationResult(
|
||||
target=target,
|
||||
file_path=str(harness_file),
|
||||
status=HarnessStatus.NOT_FOUND,
|
||||
errors=["Harness file not found"],
|
||||
)
|
||||
|
||||
# Try to compile just this target
|
||||
try:
|
||||
env = os.environ.copy()
|
||||
env["CARGO_INCREMENTAL"] = "0"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"cargo", "build",
|
||||
"--bin", target,
|
||||
"--message-format=json",
|
||||
],
|
||||
cwd=fuzz_project,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=self._settings.compile_timeout if self._settings else 120,
|
||||
env=env,
|
||||
)
|
||||
|
||||
# Parse cargo output for errors
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
for line in result.stdout.splitlines():
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
if msg.get("reason") == "compiler-message":
|
||||
message = msg.get("message", {})
|
||||
level = message.get("level", "")
|
||||
rendered = message.get("rendered", "")
|
||||
|
||||
if level == "error":
|
||||
errors.append(rendered.strip())
|
||||
elif level == "warning":
|
||||
warnings.append(rendered.strip())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# Also check stderr for any cargo errors
|
||||
if result.returncode != 0 and not errors:
|
||||
errors.append(result.stderr.strip() if result.stderr else "Build failed with unknown error")
|
||||
|
||||
if result.returncode == 0:
|
||||
return ValidationResult(
|
||||
target=target,
|
||||
file_path=str(harness_file),
|
||||
status=HarnessStatus.VALID,
|
||||
errors=[],
|
||||
warnings=warnings,
|
||||
)
|
||||
else:
|
||||
return ValidationResult(
|
||||
target=target,
|
||||
file_path=str(harness_file),
|
||||
status=HarnessStatus.COMPILE_ERROR,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return ValidationResult(
|
||||
target=target,
|
||||
file_path=str(harness_file),
|
||||
status=HarnessStatus.TIMEOUT,
|
||||
errors=["Compilation timed out"],
|
||||
)
|
||||
except Exception as e:
|
||||
return ValidationResult(
|
||||
target=target,
|
||||
file_path=str(harness_file),
|
||||
status=HarnessStatus.ERROR,
|
||||
errors=[str(e)],
|
||||
)
|
||||
|
||||
def _write_output(self, fuzz_project: Path) -> None:
|
||||
"""Write the validation results to output.
|
||||
|
||||
:param fuzz_project: Path to the fuzz project.
|
||||
|
||||
"""
|
||||
output_path = PATH_TO_OUTPUTS / "validation.json"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
valid_targets = [r.target for r in self._results if r.status == HarnessStatus.VALID]
|
||||
invalid_targets = [r.target for r in self._results if r.status != HarnessStatus.VALID]
|
||||
|
||||
output_data = {
|
||||
"fuzz_project": str(fuzz_project),
|
||||
"total_targets": len(self._results),
|
||||
"valid_count": len(valid_targets),
|
||||
"invalid_count": len(invalid_targets),
|
||||
"valid_targets": valid_targets,
|
||||
"invalid_targets": invalid_targets,
|
||||
"results": [r.model_dump() for r in self._results],
|
||||
}
|
||||
|
||||
output_path.write_text(json.dumps(output_data, indent=2))
|
||||
logger.info("wrote validation results", path=str(output_path))
|
||||
@@ -1,71 +0,0 @@
|
||||
"""Models for the harness-validator module."""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fuzzforge_modules_sdk.api.models import FuzzForgeModuleInputBase, FuzzForgeModuleOutputBase
|
||||
|
||||
from module.settings import Settings
|
||||
|
||||
|
||||
class HarnessStatus(str, Enum):
|
||||
"""Status of harness validation."""
|
||||
|
||||
VALID = "valid"
|
||||
COMPILE_ERROR = "compile_error"
|
||||
NOT_FOUND = "not_found"
|
||||
TIMEOUT = "timeout"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class ValidationResult(BaseModel):
|
||||
"""Result of validating a single harness."""
|
||||
|
||||
#: Name of the harness target
|
||||
target: str
|
||||
|
||||
#: Path to the harness file
|
||||
file_path: str
|
||||
|
||||
#: Validation status
|
||||
status: HarnessStatus
|
||||
|
||||
#: Compilation errors (if any)
|
||||
errors: list[str] = Field(default_factory=list)
|
||||
|
||||
#: Compilation warnings (if any)
|
||||
warnings: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class Input(FuzzForgeModuleInputBase[Settings]):
|
||||
"""Input for the harness-validator module.
|
||||
|
||||
Expects a fuzz project directory with:
|
||||
- Cargo.toml
|
||||
- fuzz_targets/ directory with .rs harness files
|
||||
"""
|
||||
|
||||
|
||||
class Output(FuzzForgeModuleOutputBase):
|
||||
"""Output from the harness-validator module."""
|
||||
|
||||
#: Path to the fuzz project
|
||||
fuzz_project: str = ""
|
||||
|
||||
#: Total number of harness targets
|
||||
total_targets: int = 0
|
||||
|
||||
#: Number of valid (compilable) harnesses
|
||||
valid_count: int = 0
|
||||
|
||||
#: Number of invalid harnesses
|
||||
invalid_count: int = 0
|
||||
|
||||
#: List of valid target names (ready for fuzzing)
|
||||
valid_targets: list[str] = Field(default_factory=list)
|
||||
|
||||
#: List of invalid target names (need fixes)
|
||||
invalid_targets: list[str] = Field(default_factory=list)
|
||||
|
||||
#: Detailed validation results per target
|
||||
results: list[ValidationResult] = Field(default_factory=list)
|
||||
@@ -1,13 +0,0 @@
|
||||
"""Settings for the harness-validator module."""
|
||||
|
||||
from fuzzforge_modules_sdk.api.models import FuzzForgeModulesSettingsBase
|
||||
|
||||
|
||||
class Settings(FuzzForgeModulesSettingsBase):
|
||||
"""Settings for the harness-validator module."""
|
||||
|
||||
#: Timeout for compiling each harness (seconds)
|
||||
compile_timeout: int = 120
|
||||
|
||||
#: Whether to stop on first error
|
||||
fail_fast: bool = False
|
||||
Reference in New Issue
Block a user