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
39 lines
1.1 KiB
Python
39 lines
1.1 KiB
Python
from shared.enums.instance import InstanceMode
|
|
|
|
CAP_BOUNTY_PLATFORMS = "bounty_platforms"
|
|
CAP_BOUNTY_PROGRAMS = "bounty_programs"
|
|
CAP_BB_RECON_PRESETS = "bb_recon_presets"
|
|
CAP_PROGRAM_WATCHES = "program_watches"
|
|
|
|
_MODE_CAPABILITIES: dict[str, set[str]] = {
|
|
InstanceMode.BUG_BOUNTY.value: {
|
|
CAP_BOUNTY_PLATFORMS,
|
|
CAP_BOUNTY_PROGRAMS,
|
|
CAP_BB_RECON_PRESETS,
|
|
CAP_PROGRAM_WATCHES,
|
|
},
|
|
InstanceMode.CORPORATE.value: set(),
|
|
}
|
|
|
|
BUG_BOUNTY_PROVIDERS: frozenset[str] = frozenset({"hackerone", "intigriti"})
|
|
|
|
VALID_MODES: frozenset[str] = frozenset(
|
|
{InstanceMode.BUG_BOUNTY.value, InstanceMode.CORPORATE.value}
|
|
)
|
|
|
|
|
|
def capabilities_for(mode: str | None) -> list[str]:
|
|
if mode not in _MODE_CAPABILITIES:
|
|
return sorted(_MODE_CAPABILITIES[InstanceMode.BUG_BOUNTY.value])
|
|
return sorted(_MODE_CAPABILITIES[mode])
|
|
|
|
|
|
def has_capability(mode: str | None, capability: str) -> bool:
|
|
return capability in capabilities_for(mode)
|
|
|
|
|
|
def provider_allowed(mode: str | None, provider: str) -> bool:
|
|
if provider in BUG_BOUNTY_PROVIDERS:
|
|
return has_capability(mode, CAP_BOUNTY_PLATFORMS)
|
|
return True
|