Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion pyrit/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@
import pyrit
from pyrit.backend.middleware import RequestIdMiddleware, SecurityHeadersMiddleware, register_error_handlers
from pyrit.backend.middleware.auth import EntraAuthMiddleware
from pyrit.backend.routes import attacks, auth, converters, health, labels, media, scenarios, targets, version
from pyrit.backend.routes import (
attacks,
auth,
converters,
health,
initializers,
labels,
media,
scenarios,
targets,
version,
)
from pyrit.memory import CentralMemory

# Check for development mode from environment variable
Expand Down Expand Up @@ -86,6 +97,7 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
app.include_router(targets.router, prefix="/api", tags=["targets"])
app.include_router(converters.router, prefix="/api", tags=["converters"])
app.include_router(scenarios.router, prefix="/api", tags=["scenarios"])
app.include_router(initializers.router, prefix="/api", tags=["initializers"])
app.include_router(labels.router, prefix="/api", tags=["labels"])
app.include_router(health.router, prefix="/api", tags=["health"])
app.include_router(auth.router, prefix="/api", tags=["auth"])
Expand Down
11 changes: 11 additions & 0 deletions pyrit/backend/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@
CreateConverterResponse,
PreviewStep,
)
from pyrit.backend.models.initializers import (
InitializerParameterSummary,
ListRegisteredInitializersResponse,
RegisteredInitializer,
)
from pyrit.backend.models.scenarios import (
ListRegisteredScenariosResponse,
RegisteredScenario,
ScenarioParameterSummary,
)
from pyrit.backend.models.targets import (
CreateTargetRequest,
Expand Down Expand Up @@ -99,6 +105,11 @@
# Scenarios
"ListRegisteredScenariosResponse",
"RegisteredScenario",
"ScenarioParameterSummary",
# Initializers
"InitializerParameterSummary",
"ListRegisteredInitializersResponse",
"RegisteredInitializer",
# Targets
"CreateTargetRequest",
"TargetCapabilitiesInfo",
Expand Down
44 changes: 44 additions & 0 deletions pyrit/backend/models/initializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""
Initializer API response models.

Initializers configure the PyRIT environment (targets, datasets, env vars)
before scenario execution. These models represent initializer metadata.
"""

from typing import Optional

from pydantic import BaseModel, Field

from pyrit.backend.models.common import PaginationInfo


class InitializerParameterSummary(BaseModel):
"""Summary of an initializer-declared parameter."""

name: str = Field(..., description="Parameter name")
description: str = Field(..., description="Human-readable description of the parameter")
default: Optional[list[str]] = Field(None, description="Default value(s), or None if required")


class RegisteredInitializer(BaseModel):
"""Summary of a registered initializer."""

initializer_name: str = Field(..., description="Initializer registry name (e.g., 'target')")
initializer_type: str = Field(..., description="Initializer class name (e.g., 'TargetInitializer')")
description: str = Field("", description="Human-readable description of the initializer")
required_env_vars: list[str] = Field(
default_factory=list, description="Environment variables required by this initializer"
)
supported_parameters: list[InitializerParameterSummary] = Field(
default_factory=list, description="Parameters accepted by this initializer"
)


class ListRegisteredInitializersResponse(BaseModel):
"""Response for listing initializers."""

items: list[RegisteredInitializer] = Field(..., description="List of initializer summaries")
pagination: PaginationInfo = Field(..., description="Pagination metadata")
17 changes: 15 additions & 2 deletions pyrit/backend/models/scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
from pyrit.backend.models.common import PaginationInfo


class ScenarioParameterSummary(BaseModel):
"""Summary of a scenario-declared parameter."""

name: str = Field(..., description="Parameter name (e.g., 'max_turns')")
description: str = Field(..., description="Human-readable description of the parameter")
default: str | None = Field(None, description="Default value as a display string, or None if required")
param_type: str = Field(..., description="Type of the parameter as a display string (e.g., 'int', 'str')")
choices: str | None = Field(None, description="Allowed values as a display string, or None if unconstrained")


class RegisteredScenario(BaseModel):
"""Summary of a registered scenario."""

Expand All @@ -31,6 +41,9 @@ class RegisteredScenario(BaseModel):
all_strategies: list[str] = Field(..., description="All available concrete strategy names")
default_datasets: list[str] = Field(..., description="Default dataset names used by the scenario")
max_dataset_size: Optional[int] = Field(None, description="Maximum items per dataset (None means unlimited)")
supported_parameters: list[ScenarioParameterSummary] = Field(
default_factory=list, description="Scenario-declared custom parameters"
)


class ListRegisteredScenariosResponse(BaseModel):
Expand Down Expand Up @@ -100,8 +113,8 @@ class ScenarioRunSummary(BaseModel):
error: str | None = Field(None, description="Error message if status is FAILED")
error_type: str | None = Field(None, description="Exception class name if status is FAILED")
strategies_used: list[str] = Field(default_factory=list, description="Strategy names that were executed")
total_attacks: int = Field(0, ge=0, description="Total number of atomic attacks")
completed_attacks: int = Field(0, ge=0, description="Number of attacks that completed")
total_attacks: int = Field(0, ge=0, description="Total number of attack results persisted for this run")
completed_attacks: int = Field(0, ge=0, description="Number of attacks that reached a terminal outcome")
objective_achieved_rate: int = Field(0, ge=0, le=100, description="Success rate as percentage (0-100)")
labels: dict[str, str] = Field(default_factory=dict, description="Labels attached to this run")
completed_at: datetime | None = Field(None, description="When the scenario finished")
Expand Down
3 changes: 2 additions & 1 deletion pyrit/backend/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
API route handlers.
"""

from pyrit.backend.routes import attacks, converters, health, labels, media, scenarios, targets, version
from pyrit.backend.routes import attacks, converters, health, initializers, labels, media, scenarios, targets, version

__all__ = [
"attacks",
"converters",
"health",
"initializers",
"labels",
"media",
"scenarios",
Expand Down
75 changes: 75 additions & 0 deletions pyrit/backend/routes/initializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""
Initializer API routes.

Provides endpoints for listing available initializers and their metadata.

Route structure:
/api/initializers — list all initializers
/api/initializers/{name} — get single initializer detail
"""

from typing import Optional

from fastapi import APIRouter, HTTPException, Query, status

from pyrit.backend.models.common import ProblemDetail
from pyrit.backend.models.initializers import (
ListRegisteredInitializersResponse,
RegisteredInitializer,
)
from pyrit.backend.services.initializer_service import get_initializer_service

router = APIRouter(prefix="/initializers", tags=["initializers"])


@router.get(
"",
response_model=ListRegisteredInitializersResponse,
)
async def list_initializers(
limit: int = Query(50, ge=1, le=200, description="Maximum items per page"),
cursor: Optional[str] = Query(None, description="Pagination cursor (initializer_name to start after)"),
) -> ListRegisteredInitializersResponse:
"""
List all available initializers.

Returns initializer metadata including required environment variables,
supported parameters, and descriptions.

Returns:
ListRegisteredInitializersResponse: Paginated list of initializer summaries.
"""
service = get_initializer_service()
return await service.list_initializers_async(limit=limit, cursor=cursor)


@router.get(
"/{initializer_name}",
response_model=RegisteredInitializer,
responses={
404: {"model": ProblemDetail, "description": "Initializer not found"},
},
)
async def get_initializer(initializer_name: str) -> RegisteredInitializer:
"""
Get details for a specific initializer.

Args:
initializer_name: Registry name of the initializer (e.g., 'target').

Returns:
RegisteredInitializer: Full initializer metadata.
"""
service = get_initializer_service()

initializer = await service.get_initializer_async(initializer_name=initializer_name)
if not initializer:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Initializer '{initializer_name}' not found",
)

return initializer
6 changes: 6 additions & 0 deletions pyrit/backend/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
ConverterService,
get_converter_service,
)
from pyrit.backend.services.initializer_service import (
InitializerService,
get_initializer_service,
)
from pyrit.backend.services.scenario_run_service import (
ScenarioRunService,
get_scenario_run_service,
Expand All @@ -33,6 +37,8 @@
"get_attack_service",
"ConverterService",
"get_converter_service",
"InitializerService",
"get_initializer_service",
"ScenarioService",
"get_scenario_service",
"ScenarioRunService",
Expand Down
Loading
Loading