mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 07:11:28 +02:00
A platform with a researcher API is now a provider: one module under shared/services/bounty_providers implementing programs() and scopes(), returning rows the sync engine stores. bounty_programs.py became the platform-agnostic engine, so adding a platform is one module, one APIProvider value, one PlatformSpec.api_provider and a key tester. Intigriti is the second connected platform, on a personal access token the hunter creates. On this instance: 220 programs, 54 of them private, 155 with scope and 65 the platform refuses, which accounts for all 220. Measured against the live API, against the docs: - Tier ids are unordered and undocumented. Id 4 is Tier 1, not No Bounty, so the first mapping stored the best-paying assets as paying nothing. Eligibility reads the label; out of scope is matched on id and label. - A 403 carrying FORBID001 is a refusal, not the documented rate-limit overage, and it covers public programs the account has not joined. Retrying it cost 11s a program; AccessDeniedError ends it in one request and records scope_access, keeping the failure budget for real breakage. - Membership is proved by the platform letting the token read a private program's scope. confidentialityLevel never reports "invite only", so joined was always false and its filter was dead. Bounty Hub follows what the data actually says: - source becomes sources, a JSONB array. A program is routinely in both a platform API and the public feed; the feed adds itself without taking an API row back, and facet counts overlap accordingly. - The row hint read "Bookmarked on HackerOne" while rendering Intigriti's follow flag. It now reads PlatformSpec.follow_label. - Refresh queued the sync and returned, so nothing changed on screen. It waits for the sweep, reloads, and says so. - The dashed connect card per platform is one dismissible Alert. - Connecting a platform no longer diffs API scope against feed scope, which would report every asset removed and re-added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SiHx8Sm34hS1nanrsPdBaD
288 lines
8.4 KiB
Python
288 lines
8.4 KiB
Python
from celery import Celery
|
|
|
|
from shared.config import BaseAppSettings
|
|
from shared.definitions.constants import SCANS_QUEUE
|
|
from shared.logging import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
_celery_client: Celery | None = None
|
|
|
|
|
|
def get_celery_client() -> Celery:
|
|
global _celery_client # noqa: PLW0603
|
|
if _celery_client is None:
|
|
_settings = BaseAppSettings()
|
|
_celery_client = Celery(
|
|
broker=_settings.celery_broker_url, set_as_current=False
|
|
)
|
|
_celery_client.conf.update(
|
|
task_serializer="json",
|
|
accept_content=["json"],
|
|
)
|
|
return _celery_client
|
|
|
|
|
|
def dispatch_whois_lookups(target_ids: list[str]) -> None:
|
|
if not target_ids:
|
|
return
|
|
logger.info("Dispatching WHOIS lookups for %d targets", len(target_ids))
|
|
get_celery_client().send_task(
|
|
"app.tasks.whois.perform_whois_lookups",
|
|
kwargs={"target_ids": target_ids},
|
|
queue="default",
|
|
)
|
|
|
|
|
|
def dispatch_ripestat_enrichment(target_ids: list[str]) -> None:
|
|
if not target_ids:
|
|
return
|
|
|
|
logger.info("Dispatching RIPEstat enrichment for %d targets", len(target_ids))
|
|
get_celery_client().send_task(
|
|
"app.tasks.ripestat.enrich_targets_bgp",
|
|
kwargs={"target_ids": target_ids},
|
|
queue="default",
|
|
)
|
|
|
|
|
|
def dispatch_dns_lookups(target_ids: list[str]) -> None:
|
|
if not target_ids:
|
|
return
|
|
|
|
logger.info(f"Dispatching DNS lookup for {len(target_ids)} target(s)")
|
|
get_celery_client().send_task(
|
|
"app.tasks.dns.perform_dns_lookups",
|
|
kwargs={"target_ids": target_ids},
|
|
queue="default",
|
|
)
|
|
|
|
|
|
def dispatch_scan_run(scan_id: str) -> None:
|
|
logger.info("Dispatching scan run %s", scan_id)
|
|
get_celery_client().send_task(
|
|
"app.tasks.scan.run_scan",
|
|
kwargs={"scan_id": scan_id},
|
|
queue=SCANS_QUEUE,
|
|
)
|
|
|
|
|
|
def revoke_scan_tasks(task_ids: list[str]) -> None:
|
|
"""SIGKILL-revoke a scan's celery tasks."""
|
|
if not task_ids:
|
|
return
|
|
logger.info("Revoking %d scan task(s)", len(task_ids))
|
|
try:
|
|
get_celery_client().control.revoke(
|
|
list(task_ids), terminate=True, signal="SIGKILL"
|
|
)
|
|
except Exception:
|
|
logger.warning("scan task revoke failed", exc_info=True)
|
|
|
|
|
|
def dispatch_template_sync() -> bool:
|
|
"""Kick off a library refresh."""
|
|
try:
|
|
get_celery_client().send_task("app.tasks.vuln_templates.sync", queue="default")
|
|
except Exception:
|
|
logger.warning("template sync dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_endpoint_verify(
|
|
scan_id: str, host: str, dir_path: str | None, limit: int
|
|
) -> bool:
|
|
"""Verify one branch of a scan's endpoints on demand."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.endpoints.verify_branch",
|
|
kwargs={
|
|
"scan_id": scan_id,
|
|
"host": host,
|
|
"dir_path": dir_path,
|
|
"limit": limit,
|
|
},
|
|
queue="default",
|
|
)
|
|
except Exception:
|
|
logger.warning("endpoint verify dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_report(report_id: str) -> bool:
|
|
"""Queue a report render."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.reports.generate", args=[report_id], queue="default"
|
|
)
|
|
except Exception:
|
|
logger.warning("report dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_interest_evaluation(
|
|
scan_id: str, *, include_ai: bool = True, notify: bool = True
|
|
) -> None:
|
|
logger.info("Dispatching interest evaluation for scan %s", scan_id)
|
|
get_celery_client().send_task(
|
|
"app.tasks.interest.evaluate_scan",
|
|
kwargs={"scan_id": scan_id, "include_ai": include_ai, "notify": notify},
|
|
queue="default",
|
|
)
|
|
|
|
|
|
def dispatch_interest_live(scan_id: str) -> None:
|
|
"""Re-judge a running scan from its rules alone."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.interest.evaluate_live",
|
|
kwargs={"scan_id": scan_id},
|
|
queue="default",
|
|
)
|
|
except Exception:
|
|
logger.warning("live interest dispatch failed", exc_info=True)
|
|
|
|
|
|
def dispatch_interest_refresh(project_id: str) -> None:
|
|
logger.info("Dispatching interest refresh for project %s", project_id)
|
|
get_celery_client().send_task(
|
|
"app.tasks.interest.refresh_project",
|
|
kwargs={"project_id": project_id},
|
|
queue="default",
|
|
)
|
|
|
|
|
|
def dispatch_threat_intel(scan_id: str, *, enrich: bool = True) -> None:
|
|
"""Score a finished scan from the feeds, then fill the provider cache behind it."""
|
|
try:
|
|
client = get_celery_client()
|
|
client.send_task(
|
|
"app.tasks.threat_intel.apply_scan", args=[scan_id], queue="default"
|
|
)
|
|
if enrich:
|
|
client.send_task(
|
|
"app.tasks.threat_intel.enrich", args=[scan_id], queue="default"
|
|
)
|
|
except Exception:
|
|
logger.warning("threat intel dispatch failed", exc_info=True)
|
|
|
|
|
|
def dispatch_threat_intel_refresh(*, force: bool = False) -> bool:
|
|
"""Kick off a feed refresh."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.threat_intel.refresh", kwargs={"force": force}, queue="default"
|
|
)
|
|
except Exception:
|
|
logger.warning("threat intel refresh dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_bounty_sync(*, scopes: bool = True, platform: str | None = None) -> bool:
|
|
"""Refresh the bug bounty program library."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.bounty_programs.sync",
|
|
kwargs={"scopes": scopes, "platform": platform},
|
|
queue="default",
|
|
)
|
|
except Exception:
|
|
logger.warning("bounty program sync dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_bounty_program_sync(handle: str, platform: str = "hackerone") -> bool:
|
|
"""Refresh one program's scope."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.bounty_programs.sync_program",
|
|
args=[handle, platform],
|
|
queue="default",
|
|
)
|
|
except Exception:
|
|
logger.warning("bounty program scope dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_bounty_feed_sync() -> bool:
|
|
"""Refresh the public program feed."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.bounty_programs.sync_feed", queue="default"
|
|
)
|
|
except Exception:
|
|
logger.warning("bounty feed sync dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_toolbox_run(
|
|
*,
|
|
run_id: str,
|
|
user_id: str,
|
|
tool: str,
|
|
payload: dict,
|
|
project_id: str | None,
|
|
) -> bool:
|
|
"""Hand a toolbox run to the worker."""
|
|
from shared.definitions.constants import CRITICAL_QUEUE # noqa: PLC0415
|
|
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.toolbox.run",
|
|
kwargs={
|
|
"run_id": run_id,
|
|
"user_id": user_id,
|
|
"tool": tool,
|
|
"payload": payload,
|
|
"project_id": project_id,
|
|
},
|
|
queue=CRITICAL_QUEUE,
|
|
)
|
|
except Exception:
|
|
logger.warning("toolbox dispatch failed", exc_info=True)
|
|
return False
|
|
return True
|
|
|
|
|
|
def dispatch_watch_certificate(cert: dict) -> bool:
|
|
"""One discovered certificate from the stream."""
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.watch.certificate", kwargs={"cert": cert}, queue="default"
|
|
)
|
|
return True
|
|
except Exception:
|
|
logger.warning("watch certificate dispatch failed", exc_info=True)
|
|
return False
|
|
|
|
|
|
def dispatch_watch_settle(scan_id: str) -> bool:
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.watch.settle", kwargs={"scan_id": scan_id}, queue="default"
|
|
)
|
|
return True
|
|
except Exception:
|
|
logger.warning("watch settle dispatch failed", exc_info=True)
|
|
return False
|
|
|
|
|
|
def dispatch_watch_reconcile(program_id: str | None = None) -> bool:
|
|
try:
|
|
get_celery_client().send_task(
|
|
"app.tasks.watch.reconcile",
|
|
kwargs={"program_id": program_id},
|
|
queue="default",
|
|
)
|
|
return True
|
|
except Exception:
|
|
logger.warning("watch reconcile dispatch failed", exc_info=True)
|
|
return False
|