mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-02-12 21:12:56 +00:00
feat(modules): add harness-tester module for Rust fuzzing pipeline
This commit is contained in:
@@ -25,6 +25,9 @@ class ImageInfo:
|
||||
#: Image size in bytes.
|
||||
size: int | None = None
|
||||
|
||||
#: Image labels/metadata.
|
||||
labels: dict[str, str] | None = None
|
||||
|
||||
|
||||
class AbstractFuzzForgeSandboxEngine(ABC):
|
||||
"""Abstract class used as a base for all FuzzForge sandbox engine classes."""
|
||||
@@ -279,3 +282,17 @@ class AbstractFuzzForgeSandboxEngine(ABC):
|
||||
"""
|
||||
message: str = f"method 'list_containers' is not implemented for class '{self.__class__.__name__}'"
|
||||
raise NotImplementedError(message)
|
||||
|
||||
@abstractmethod
|
||||
def read_file_from_image(self, image: str, path: str) -> str:
|
||||
"""Read a file from inside an image without starting a container.
|
||||
|
||||
Creates a temporary container, copies the file, and removes the container.
|
||||
|
||||
:param image: Image reference (e.g., "fuzzforge-rust-analyzer:latest").
|
||||
:param path: Path to file inside image.
|
||||
:returns: File contents as string.
|
||||
|
||||
"""
|
||||
message: str = f"method 'read_file_from_image' is not implemented for class '{self.__class__.__name__}'"
|
||||
raise NotImplementedError(message)
|
||||
|
||||
@@ -99,6 +99,17 @@ class DockerCLI(AbstractFuzzForgeSandboxEngine):
|
||||
if filter_prefix and filter_prefix not in reference:
|
||||
continue
|
||||
|
||||
# Try to get labels from image inspect
|
||||
labels = {}
|
||||
try:
|
||||
inspect_result = self._run(["image", "inspect", reference], check=False)
|
||||
if inspect_result.returncode == 0:
|
||||
inspect_data = json.loads(inspect_result.stdout)
|
||||
if inspect_data and len(inspect_data) > 0:
|
||||
labels = inspect_data[0].get("Config", {}).get("Labels") or {}
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
pass
|
||||
|
||||
images.append(
|
||||
ImageInfo(
|
||||
reference=reference,
|
||||
@@ -106,6 +117,7 @@ class DockerCLI(AbstractFuzzForgeSandboxEngine):
|
||||
tag=tag,
|
||||
image_id=image.get("ID", "")[:12],
|
||||
size=image.get("Size"),
|
||||
labels=labels,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -404,3 +416,43 @@ class DockerCLI(AbstractFuzzForgeSandboxEngine):
|
||||
]
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
def read_file_from_image(self, image: str, path: str) -> str:
|
||||
"""Read a file from inside an image without starting a long-running container.
|
||||
|
||||
Creates a temporary container, reads the file via cat, and removes it.
|
||||
|
||||
:param image: Image reference (e.g., "fuzzforge-rust-analyzer:latest").
|
||||
:param path: Path to file inside image.
|
||||
:returns: File contents as string.
|
||||
|
||||
"""
|
||||
logger = get_logger()
|
||||
|
||||
# Create a temporary container (don't start it)
|
||||
create_result = self._run(
|
||||
["create", "--rm", image, "cat", path],
|
||||
check=False,
|
||||
)
|
||||
|
||||
if create_result.returncode != 0:
|
||||
logger.debug("failed to create container for file read", image=image, path=path)
|
||||
return ""
|
||||
|
||||
container_id = create_result.stdout.strip()
|
||||
|
||||
try:
|
||||
# Start the container and capture output (cat will run and exit)
|
||||
start_result = self._run(
|
||||
["start", "-a", container_id],
|
||||
check=False,
|
||||
)
|
||||
|
||||
if start_result.returncode != 0:
|
||||
logger.debug("failed to read file from image", image=image, path=path)
|
||||
return ""
|
||||
|
||||
return start_result.stdout
|
||||
finally:
|
||||
# Cleanup: remove the container (may already be removed due to --rm)
|
||||
self._run(["rm", "-f", container_id], check=False)
|
||||
|
||||
@@ -172,3 +172,8 @@ class Docker(AbstractFuzzForgeSandboxEngine):
|
||||
"""List containers."""
|
||||
message: str = "Docker engine list_containers is not yet implemented"
|
||||
raise NotImplementedError(message)
|
||||
|
||||
def read_file_from_image(self, image: str, path: str) -> str:
|
||||
"""Read a file from inside an image without starting a long-running container."""
|
||||
message: str = "Docker engine read_file_from_image is not yet implemented"
|
||||
raise NotImplementedError(message)
|
||||
|
||||
@@ -166,6 +166,9 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
repo = name
|
||||
tag = "latest"
|
||||
|
||||
# Get labels if available
|
||||
labels = image.get("Labels") or {}
|
||||
|
||||
images.append(
|
||||
ImageInfo(
|
||||
reference=name,
|
||||
@@ -173,6 +176,7 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
tag=tag,
|
||||
image_id=image.get("Id", "")[:12],
|
||||
size=image.get("Size"),
|
||||
labels=labels,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -474,6 +478,46 @@ class PodmanCLI(AbstractFuzzForgeSandboxEngine):
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
def read_file_from_image(self, image: str, path: str) -> str:
|
||||
"""Read a file from inside an image without starting a long-running container.
|
||||
|
||||
Creates a temporary container, reads the file via cat, and removes it.
|
||||
|
||||
:param image: Image reference (e.g., "fuzzforge-rust-analyzer:latest").
|
||||
:param path: Path to file inside image.
|
||||
:returns: File contents as string.
|
||||
|
||||
"""
|
||||
logger = get_logger()
|
||||
|
||||
# Create a temporary container (don't start it)
|
||||
create_result = self._run(
|
||||
["create", "--rm", image, "cat", path],
|
||||
check=False,
|
||||
)
|
||||
|
||||
if create_result.returncode != 0:
|
||||
logger.debug("failed to create container for file read", image=image, path=path)
|
||||
return ""
|
||||
|
||||
container_id = create_result.stdout.strip()
|
||||
|
||||
try:
|
||||
# Start the container and capture output (cat will run and exit)
|
||||
start_result = self._run(
|
||||
["start", "-a", container_id],
|
||||
check=False,
|
||||
)
|
||||
|
||||
if start_result.returncode != 0:
|
||||
logger.debug("failed to read file from image", image=image, path=path)
|
||||
return ""
|
||||
|
||||
return start_result.stdout
|
||||
finally:
|
||||
# Cleanup: remove the container (may already be removed due to --rm)
|
||||
self._run(["rm", "-f", container_id], check=False)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Utility Methods
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -494,3 +494,40 @@ class Podman(AbstractFuzzForgeSandboxEngine):
|
||||
}
|
||||
for c in containers
|
||||
]
|
||||
|
||||
def read_file_from_image(self, image: str, path: str) -> str:
|
||||
"""Read a file from inside an image without starting a long-running container.
|
||||
|
||||
Creates a temporary container, reads the file, and removes the container.
|
||||
|
||||
:param image: Image reference (e.g., "fuzzforge-rust-analyzer:latest").
|
||||
:param path: Path to file inside image.
|
||||
:returns: File contents as string.
|
||||
|
||||
"""
|
||||
logger = get_logger()
|
||||
client: PodmanClient = self.get_client()
|
||||
|
||||
with client:
|
||||
try:
|
||||
# Create a container that just runs cat on the file
|
||||
container = client.containers.create(
|
||||
image=image,
|
||||
command=["cat", path],
|
||||
remove=True,
|
||||
)
|
||||
|
||||
# Start it and wait for completion
|
||||
container.start()
|
||||
container.wait()
|
||||
|
||||
# Get the logs (which contain stdout)
|
||||
output = container.logs(stdout=True, stderr=False)
|
||||
|
||||
if isinstance(output, bytes):
|
||||
return output.decode("utf-8", errors="replace")
|
||||
return str(output)
|
||||
|
||||
except Exception as exc:
|
||||
logger.debug("failed to read file from image", image=image, path=path, error=str(exc))
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user