Files
rengine/shared/models/scan_engine.py
T
Yogesh Ojha 5aded1d7fd feat(engine): run the HTTP probe automatically on every run above passive
http_probe becomes always-on support: saved engines, YAML and run overrides
drop its switch, passive still turns it off. Its every-port setting moves to
port_scan.http_on_every_port, the Passive Recon preset carries passive
intensity, and the launch solver includes the probe without letting it pull
in active support stages.
2026-09-24 21:56:14 +05:30

191 lines
5.9 KiB
Python

import uuid
from datetime import datetime
from functools import partial
from pydantic import BaseModel, field_validator
from pydantic import Field as PydanticField
from sqlalchemy import Column
from sqlalchemy.types import JSON, Text
from sqlmodel import Field, SQLModel
from shared.enums.scan import Intensity
from shared.models.scan_context import ScanContextCreate
from shared.utils.datetime import utc_now
from shared.utils.validation import clean_name, clean_optional_name
class ScanEngine(SQLModel, table=True):
__tablename__ = "scan_engines"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
project_id: uuid.UUID = Field(foreign_key="projects.id", index=True)
created_by: uuid.UUID = Field(foreign_key="users.id")
name: str = Field(max_length=200)
description: str | None = Field(default=None, max_length=1000)
intensity: str = Field(default=Intensity.NORMAL.value)
global_headers: list = Field(
default_factory=list, sa_column=Column(JSON, nullable=False)
)
stages: dict = Field(default_factory=dict, sa_column=Column(JSON, nullable=False))
yaml_source: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
tool_options: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
builtin: bool = Field(default=False)
created_at: datetime = Field(default_factory=utc_now)
updated_at: datetime = Field(default_factory=utc_now)
last_used_at: datetime | None = Field(default=None)
class ScanEngineCreate(BaseModel):
name: str
description: str | None = None
intensity: str = Intensity.NORMAL.value
global_headers: list[str] = PydanticField(default_factory=list)
stages: dict[str, dict] = PydanticField(default_factory=dict)
yaml_source: str | None = None
tool_options: dict[str, str] = PydanticField(default_factory=dict)
_validate_name = field_validator("name")(partial(clean_name, max_len=200))
class ScanEngineUpdate(BaseModel):
name: str | None = None
description: str | None = None
intensity: str | None = None
global_headers: list[str] | None = None
stages: dict[str, dict] | None = None
yaml_source: str | None = None
tool_options: dict[str, str] | None = None
_validate_name = field_validator("name")(partial(clean_optional_name, max_len=200))
class EngineUsage(BaseModel):
schedules: int = 0
scans: int = 0
class ScanEngineRead(BaseModel):
id: uuid.UUID
project_id: uuid.UUID
created_by: uuid.UUID
name: str
description: str | None
intensity: str
global_headers: list[str]
stages: dict[str, dict]
yaml_source: str | None
tool_options: dict[str, str] = PydanticField(default_factory=dict)
usage: EngineUsage = PydanticField(default_factory=EngineUsage)
builtin: bool = False
created_at: datetime
updated_at: datetime
last_used_at: datetime | None
class StageField(BaseModel):
name: str
title: str
description: str | None = None
type: str
default: object = None
options: list[str] | None = None
option_labels: dict[str, str] | None = None
minimum: int | None = None
maximum: int | None = None
tier: str = "basic"
widget: str | None = None
kind: str | None = None
launch: bool = False
needs: str | None = None
unavailable_reason: str | None = None
class StageTransport(BaseModel):
tool: str
rates: dict[str, int | None]
threads: dict[str, int]
timeout: int
class StageCatalogEntry(BaseModel):
name: str
title: str
description: str
phase: str
level: int
applies_to: list[str]
tools: list[str]
api_keys: list[str]
requires_api_keys: bool = False
touches_target: bool = True
passive_capable: bool = False
always_on: bool = False
launch_fields: list[str] = PydanticField(default_factory=list)
group: str
role: str
consumes: list[str] = PydanticField(default_factory=list)
produces: list[str] = PydanticField(default_factory=list)
transport: StageTransport | None = None
defaults: dict
fields: list[StageField] = PydanticField(default_factory=list)
class StageGroupEntry(BaseModel):
key: str
label: str
class ToolOption(BaseModel):
name: str
label: str
phase: str
example: str
class EnginePreset(BaseModel):
name: str
title: str
description: str
intensity: str
stages: dict[str, dict]
class EngineCatalog(BaseModel):
phases: list[str]
stages: list[StageCatalogEntry]
rate_tools: list[str]
tool_options: list[ToolOption]
presets: list[EnginePreset]
target_types: list[str]
groups: list[StageGroupEntry] = PydanticField(default_factory=list)
seed_produces: dict[str, list[str]] = PydanticField(default_factory=dict)
class PreviewResolved(BaseModel):
header_names: list[str] = PydanticField(default_factory=list)
global_rate_limit_ceiling: int | None = None
per_tool_rate_limits: dict[str, int] = PydanticField(default_factory=dict)
excluded_subdomains: list[str] = PydanticField(default_factory=list)
excluded_paths: list[str] = PydanticField(default_factory=list)
excluded_ips: list[str] = PydanticField(default_factory=list)
included_subdomains: list[str] = PydanticField(default_factory=list)
follow_redirects: bool | None = None
http_protocol: str = "both"
class EnginePreviewResult(BaseModel):
phases: list = PydanticField(default_factory=list)
resolved_stages: dict[str, dict] = PydanticField(default_factory=dict)
resolved: PreviewResolved = PydanticField(default_factory=PreviewResolved)
warnings: list[str] = PydanticField(default_factory=list)
class EnginePreviewRequest(BaseModel):
target_type: str
context_id: uuid.UUID | None = None
context: ScanContextCreate | None = None
intensity: str = Intensity.NORMAL.value
stages: dict[str, dict] = PydanticField(default_factory=dict)