Files
rengine/shared/models/instance_settings.py
T
Yogesh OjhaandClaude Opus 5 b0a47ba087 feat(oast): an instance-owned callback server, and a tier that pays the wait once
Out-of-band testing is an instance setting — `off`, `self_hosted` or
`public` — with the server, the token (the INTERACTSH API key row) and the
wait resolved once in `shared/services/oast.py` and read on the stage
thread. A mode that cannot be used is off and says why: self-hosted with no
server, or public without the disclosure acknowledged. A fresh instance is
off. `POST /oast/test` asks a public address for `/poll` over HTTPS and
reports that the server answered, never that OAST works.

The engine keeps one switch. `FieldNeeds` puts the resource key on the
field and `GET /engines/catalog` fills `unavailable_reason`, so the builder
renders the instance reason beside a switch that stays settable.

Measured on nuclei v3.11.1: with `-no-interactsh` a check that needs a
callback still loads and the request is refused — 0 requests, exit 0. So
the batch reported COMPLETED and every origin in it read `scanned` for
checks that never touched the target. The selection now drops them when
nothing is listening, one coverage row reports the count and the reason,
and `Tier.OAST` is left out of the planned tiers.

Membership is what a check references, not what it is tagged: 615 of the
12,586 official rows reference an interactsh placeholder and 233 of those
carry no `oast` tag. `vuln_templates.needs_oast` is parsed at index time; a
library resync fills the column.

The wait is paid on out-of-band batches alone, which sweep for 30 minutes
so it is paid fewer times, and the tier names the server on its own rows.
A finding's callback is rendered on the sheet from one label map; the list
path is bared to the metadata.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 20:53:01 +05:30

114 lines
4.1 KiB
Python

import uuid
from datetime import datetime
from pydantic import BaseModel
from sqlalchemy import Column
from sqlalchemy.types import JSON
from sqlmodel import Field, SQLModel, UniqueConstraint
from shared.definitions.bounty_programs import (
DEFAULT_FEED_INTERVAL,
DEFAULT_SYNC_INTERVAL,
)
from shared.definitions.oast import (
DEFAULT_WAIT_SECONDS,
MAX_SERVER_LENGTH,
OastMode,
)
from shared.enums.instance import InstanceMode
from shared.utils.datetime import utc_now
SINGLETON_KEY = "instance"
class InstanceSettings(SQLModel, table=True):
__tablename__ = "instance_settings"
__table_args__ = (
UniqueConstraint("singleton_key", name="uq_instance_settings_singleton"),
)
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
singleton_key: str = Field(default=SINGLETON_KEY, max_length=20)
instance_name: str = Field(default="reNgine", max_length=120)
timezone: str = Field(default="UTC", max_length=64)
mode: str = Field(default=InstanceMode.BUG_BOUNTY.value)
onboarding_completed: bool = Field(default=False)
onboarding_completed_at: datetime | None = Field(default=None)
onboarding_completed_by: uuid.UUID | None = Field(default=None)
onboarding_step: int = Field(default=0)
onboarding_state: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
scan_history_retention_days: int = Field(default=90)
screenshot_retention_days: int = Field(default=30)
ai_enabled: bool = Field(default=False)
ai_provider: str | None = Field(default=None)
ai_model: str | None = Field(default=None)
ai_api_key_encrypted: str | None = Field(default=None)
ai_features: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
report_defaults: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
threat_intel_auto_sync: bool = Field(default=True)
cert_recheck_enabled: bool = Field(default=False)
bounty_sync_interval: str = Field(default=DEFAULT_SYNC_INTERVAL, max_length=16)
bounty_synced_at: datetime | None = Field(default=None)
bounty_events_seen_at: datetime | None = Field(default=None)
bounty_feed_interval: str = Field(default=DEFAULT_FEED_INTERVAL, max_length=16)
bounty_feed_synced_at: datetime | None = Field(default=None)
bounty_settings: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
mcp_enabled: bool = Field(default=False)
mcp_settings: dict = Field(
default_factory=dict, sa_column=Column(JSON, nullable=False)
)
new_checks_swept_at: datetime | None = Field(default=None)
oast_mode: str = Field(default=OastMode.OFF.value, max_length=16)
oast_server: str | None = Field(default=None, max_length=MAX_SERVER_LENGTH)
oast_public_acknowledged: bool = Field(default=False)
oast_wait_seconds: int = Field(default=DEFAULT_WAIT_SECONDS)
created_at: datetime = Field(default_factory=utc_now)
updated_at: datetime = Field(default_factory=utc_now)
class InstanceSettingsUpdate(BaseModel):
instance_name: str | None = None
timezone: str | None = None
mode: str | None = None
scan_history_retention_days: int | None = None
screenshot_retention_days: int | None = None
cert_recheck_enabled: bool | None = None
ai_enabled: bool | None = None
ai_provider: str | None = None
ai_model: str | None = None
ai_api_key: str | None = None
ai_features: dict | None = None
class InstanceSettingsRead(BaseModel):
id: uuid.UUID
singleton_key: str
instance_name: str
timezone: str
mode: str
onboarding_completed: bool
onboarding_completed_at: datetime | None
onboarding_completed_by: uuid.UUID | None
onboarding_step: int
onboarding_state: dict
scan_history_retention_days: int
screenshot_retention_days: int
cert_recheck_enabled: bool
ai_enabled: bool
ai_provider: str | None
ai_model: str | None
ai_api_key_masked: str | None
ai_configured: bool
ai_features: dict
capabilities: list[str]
created_at: datetime
updated_at: datetime