Files
rengine/shared/models/bounty_program.py
T
Yogesh OjhaandClaude Fable 5.1 db030d733c feat(bounty-hub): watch a program
Scope-true continuous discovery for a bounty program, bug bounty mode only
(capability `program_watches`).

Watching a program imports its in-scope targets under the program's
organization, writes out-of-scope entries as exclusions and the rate ceiling
on a `Watch · <program>` scan context, schedules an optional baseline, and
streams certificate transparency for the watched apexes. A new in-scope host
is resolved, written into the covering scan with source `ct_log`, probed by a
focused run with a screenshot, evaluated against the watch's alert query and
alerted once per fingerprint through the chosen channels.

- ct-stream compose service: certspotter 0.24.2 under a supervisor that
  regenerates the watchlist from active watches, spools events through a
  hook script that emits only certspotter fields, and reports over Redis
- worker tasks: certificate, settle, recheck, reconcile; finalize dispatches
  settle for any probe run; reconcile after every program or feed sync
- notifier: named channels bypass per-channel preferences; attachments;
  new `watch` notification type
- API under /bounty-programs/watches with preview, ledger, events, marks
- Bounty Hub: Watching tab, Watch dialog, watch sheet, program badge,
  certificate stream card in Settings
- schedules carry an intensity; scan_schedules.intensity

Two review rounds folded in: compare-and-set probe claim, resolver-down
handling, spool kept on failed dispatch, schedule cap, mute keeps prior
state, ledger-derived totals, event filters, mode lockout of worker and
stream in corporate mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba2f7gwu2GXiETCKqRztyB
2026-09-12 21:51:18 +05:30

252 lines
7.7 KiB
Python

import uuid
from datetime import datetime
from pydantic import BaseModel
from sqlalchemy import Column, Text
from sqlalchemy.types import JSON
from sqlmodel import Field, SQLModel, UniqueConstraint
from shared.enums.target import TargetType
from shared.models.organization import OrganizationSummary
from shared.models.tag import TagSummary
from shared.utils.datetime import utc_now
class BountyProgram(SQLModel, table=True):
__tablename__ = "bounty_programs"
__table_args__ = (
UniqueConstraint("platform", "handle", name="uq_bounty_program_handle"),
)
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
platform: str = Field(max_length=32, index=True)
source: str = Field(default="api", max_length=16, index=True)
handle: str = Field(max_length=200, index=True)
name: str = Field(max_length=300)
url: str | None = Field(default=None, max_length=500)
profile_picture: str | None = Field(
default=None, sa_column=Column(Text, nullable=True)
)
program_state: str = Field(max_length=16, index=True)
raw_state: str | None = Field(default=None, max_length=32)
joined: bool = Field(default=False, index=True)
joined_at: datetime | None = Field(default=None)
submission_state: str = Field(max_length=16, index=True)
offers_bounties: bool = Field(default=False, index=True)
open_scope: bool | None = Field(default=None)
gold_standard_safe_harbor: bool | None = Field(default=None)
currency: str | None = Field(default=None, max_length=16)
started_accepting_at: datetime | None = Field(default=None, index=True)
bookmarked: bool = Field(default=False, index=True)
reports_for_user: int | None = Field(default=None)
earnings_for_user: float | None = Field(default=None)
min_payout: float | None = Field(default=None)
max_payout: float | None = Field(default=None)
payout_currency: str | None = Field(default=None, max_length=16)
safe_harbor: str | None = Field(default=None, max_length=32)
requires_2fa: bool | None = Field(default=None)
scopes_synced_at: datetime | None = Field(default=None)
synced_at: datetime = Field(default_factory=utc_now, index=True)
class BountyScope(SQLModel, table=True):
__tablename__ = "bounty_scopes"
__table_args__ = (
UniqueConstraint(
"program_id", "asset_type", "asset_identifier", name="uq_bounty_scope_asset"
),
)
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
program_id: uuid.UUID = Field(
foreign_key="bounty_programs.id", index=True, ondelete="CASCADE"
)
asset_type: str = Field(max_length=48, index=True)
asset_identifier: str = Field(max_length=1000)
scope_state: str = Field(max_length=16, index=True)
eligible_for_bounty: bool | None = Field(default=None)
max_severity: str | None = Field(default=None, max_length=16)
instruction: str | None = Field(default=None, sa_column=Column(Text))
reference: str | None = Field(default=None, max_length=500)
target_value: str | None = Field(default=None, max_length=500, index=True)
target_type: TargetType | None = Field(default=None)
raw: dict | None = Field(default=None, sa_column=Column(JSON, nullable=True))
synced_at: datetime = Field(default_factory=utc_now)
class BountyEventRow(SQLModel, table=True):
__tablename__ = "bounty_events"
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
platform: str = Field(max_length=32, index=True)
program_id: uuid.UUID = Field(
foreign_key="bounty_programs.id", index=True, ondelete="CASCADE"
)
handle: str = Field(max_length=200, index=True)
program_name: str = Field(max_length=300)
kind: str = Field(max_length=32, index=True)
asset_type: str | None = Field(default=None, max_length=48)
asset_identifier: str | None = Field(default=None, max_length=1000)
detail: str | None = Field(default=None, max_length=500)
created_at: datetime = Field(default_factory=utc_now, index=True)
class BountyEventRead(BaseModel):
id: uuid.UUID
platform: str
handle: str
program_name: str
kind: str
label: str = ""
description: str = ""
icon: str = "circle-help"
tone: str = "muted"
actionable: bool = False
asset_type: str | None
asset_identifier: str | None
detail: str | None
created_at: datetime
class BountyScopeRead(BaseModel):
id: uuid.UUID
asset_type: str
asset_type_label: str
asset_group: str
icon: str
asset_identifier: str
scope_state: str
eligible_for_bounty: bool | None
max_severity: str | None
instruction: str | None
target_value: str | None
target_type: str | None
importable: bool
already_target: bool = False
class BountyProgramRead(BaseModel):
id: uuid.UUID
platform: str
platform_label: str = ""
source: str = "api"
source_label: str = ""
handle: str
name: str
url: str | None
profile_picture: str | None
program_state: str
raw_state: str | None
raw_state_label: str = ""
joined: bool
joined_at: datetime | None
submission_state: str
offers_bounties: bool
open_scope: bool | None
gold_standard_safe_harbor: bool | None
currency: str | None
started_accepting_at: datetime | None
bookmarked: bool
reports_for_user: int | None
earnings_for_user: float | None
min_payout: float | None = None
max_payout: float | None = None
payout_currency: str | None = None
safe_harbor: str | None = None
requires_2fa: bool | None = None
scopes_synced_at: datetime | None
synced_at: datetime
in_scope_count: int = 0
out_of_scope_count: int = 0
importable_count: int = 0
imported_count: int = 0
watched: bool = False
watch_id: uuid.UUID | None = None
class BountyProgramDetail(BountyProgramRead):
scopes: list[BountyScopeRead] = []
unreachable: dict[str, int] = {}
class BountyScopeCounts(BaseModel):
total: int
in_scope: int
out_of_scope: int
importable: int
class BountySyncResult(BaseModel):
platform: str
programs: int
created: int
updated: int
duration_ms: int
error: str | None = None
class BountyImportRequest(BaseModel):
project_id: uuid.UUID
scope_ids: list[uuid.UUID] | None = None
include_out_of_scope: bool = False
group_by_program: bool = True
# blank falls back to the program name
organization_name: str | None = None
tags: list[str] = []
class BountyImportResult(BaseModel):
created: list[str]
existing: list[str]
skipped: list[str]
organization: OrganizationSummary | None = None
tags: list[TagSummary] = []
class PlatformCount(BaseModel):
platform: str
label: str
source: str
programs: int
class BountyStatus(BaseModel):
configured: bool
platform: str
username: str | None
programs: int
private_programs: int
last_synced_at: datetime | None
sync_interval: str
next_sync_at: datetime | None = None
unseen_events: int = 0
platforms: list[PlatformCount] = []
feed_interval: str = ""
feed_synced_at: datetime | None = None
error: str | None = None
class BountySettingsRead(BaseModel):
sync_interval: str
feed_interval: str
feed_synced_at: datetime | None
feed_next_sync_at: datetime | None
feed_programs: int
feed_source: str
feed_url: str
feed_license: str
notify: bool
notify_events: list[str]
notifiable_events: list[str]
last_synced_at: datetime | None
next_sync_at: datetime | None
programs: int
events_recorded: int
class BountySettingsUpdate(BaseModel):
sync_interval: str | None = None
feed_interval: str | None = None
notify: bool | None = None
notify_events: list[str] | None = None