mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-21 07:41:28 +02:00
A full static review of the 3.0 tree, run as two fan-out passes with every
finding verified against the running stack rather than taken on inspection.
Correctness and security
- instance settings encrypted the mask over the stored AI key: `!= MASK` never
matched `••••••••abcd`, so saving settings destroyed the key and `test_ai`
posted the mask as a bearer token
- the MFA token was replayable for its full five-minute life; the gate now
checks revocation and consumes the token after a successful exchange
- logout revoked nothing for a bearer client while answering "Logged out"
- `POST /api-keys/{id}/test` returned the plaintext key inside httpx's error
string, which embeds the whole URL
- an interest rule could be edited or deleted through another project's id
- redis ran the celery broker under `allkeys-lru`, free to evict a queued task
or a chord counter; now `noeviction`
- nuclei's stored command carried the OAST token and every auth header
- a finalize step could poison the session and leave a scan RUNNING for ever
- `is:issue` raised TypeError on every search
- offset paging repeated and skipped rows in four dimensions
- a cancelled run kept a stale `scans.*_found` rollup
- every PDF report raised `KeyError: 'software'`
- `version_key` truncated a wide numeric run and inverted release ordering
- psycopg raises a NUL as a bare ValueError, above StatementError, so every
savepoint guard was blind to the case it was written for
Performance
- endpoints.host had no trigram index: 35.9ms seq scan -> 0.389ms
- free text compiled to one OR, not the prescribed UNION ALL: 132ms -> 41ms
- `/http-assets/summary` and `/ips/summary` materialised whole tables
- the cross-link `ip` kind ran an estate query per page and drew nothing
Frontend
- `$effect` loaders that read the state they write, an unbounded request loop
on a failing endpoint
- error states that rendered as empty states
- 2FA backup codes were unusable while the copy said they worked
- a "Scan history" dialog that always reported none
- ~400 duplicated lines across five filter bars, now one component
Build and tooling
- every scanner installed at @latest; all 13 pinned to the measured versions
- CI built `web/`, gone since 2.x, with tags that cannot be valid
- the prettier pre-commit hook discarded its filenames and always passed
- the test suite destroyed itself when run twice
Also: subdomains from public code, via a pinned github-subdomains source and a
GitHub API key that subfinder receives too; sublist3r and oneforall removed
after measuring one unique host in 89 across four targets.
Verified: 853 tests, ruff clean over eleven packages, svelte-check 0 errors,
the count-is-a-promise gate at 131 checks and 0 mismatches, and 143 GET routes
with no 5xx. Four migrations, each with an exercised downgrade.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVkdx5ynZ6aof5kLFczmhX
269 lines
8.5 KiB
Python
269 lines
8.5 KiB
Python
"""Program libraries from the platforms with a researcher API: sync, events and settings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
|
|
from sqlalchemy import delete, select, text
|
|
from sqlalchemy.orm import Session
|
|
|
|
from shared.definitions.bounty_programs import (
|
|
DEFAULT_SYNC_INTERVAL,
|
|
MAX_EVENT_DETAIL,
|
|
SYNC_INTERVAL_HOURS,
|
|
BountyEvent,
|
|
ProgramState,
|
|
ScopeAccess,
|
|
ScopeState,
|
|
SubmissionState,
|
|
asset_type_spec,
|
|
)
|
|
from shared.logging import get_logger
|
|
from shared.models.bounty_program import BountyEventRow, BountyProgram, BountyScope
|
|
from shared.services.bounty_providers import AccessDeniedError, BountyProvider
|
|
from shared.utils.datetime import utc_now
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def event_row(program: BountyProgram, kind: str, **extra) -> BountyEventRow:
|
|
return BountyEventRow(
|
|
platform=program.platform,
|
|
program_id=program.id,
|
|
handle=program.handle,
|
|
program_name=program.name,
|
|
kind=kind,
|
|
**extra,
|
|
)
|
|
|
|
|
|
def _program_changes(current: BountyProgram, row: dict) -> list[str]:
|
|
"""What moved on a program the library already knew about."""
|
|
kinds: list[str] = []
|
|
if current.submission_state != row["submission_state"]:
|
|
kinds.append(
|
|
BountyEvent.SUBMISSIONS_OPENED.value
|
|
if row["submission_state"] == SubmissionState.OPEN.value
|
|
else BountyEvent.SUBMISSIONS_CLOSED.value
|
|
)
|
|
if (
|
|
current.program_state != row["program_state"]
|
|
and row["program_state"] == ProgramState.PUBLIC.value
|
|
):
|
|
kinds.append(BountyEvent.PROGRAM_WENT_PUBLIC.value)
|
|
if row["offers_bounties"] and not current.offers_bounties:
|
|
kinds.append(BountyEvent.BOUNTIES_STARTED.value)
|
|
return kinds
|
|
|
|
|
|
def sync_programs(session: Session, provider: BountyProvider) -> dict[str, int]:
|
|
"""Refresh one platform's program list."""
|
|
started = time.monotonic()
|
|
rows = provider.programs()
|
|
existing = {
|
|
p.handle: p
|
|
for p in session.execute(
|
|
select(BountyProgram).where(BountyProgram.platform == provider.platform)
|
|
).scalars()
|
|
}
|
|
baseline = bool(existing)
|
|
created = updated = 0
|
|
seen: set[str] = set()
|
|
events: list[BountyEventRow] = []
|
|
for row in rows:
|
|
handle = row.get("handle")
|
|
if not handle or handle in seen:
|
|
continue
|
|
seen.add(handle)
|
|
current = existing.get(handle)
|
|
row["sources"] = sorted({*(current.sources if current else []), row["source"]})
|
|
if current is None:
|
|
program = BountyProgram(**row)
|
|
session.add(program)
|
|
session.flush()
|
|
created += 1
|
|
if baseline:
|
|
events.append(event_row(program, BountyEvent.PROGRAM_ADDED.value))
|
|
continue
|
|
for kind in _program_changes(current, row):
|
|
events.append(event_row(current, kind))
|
|
if current.source != row["source"]:
|
|
current.scopes_synced_at = None
|
|
current.scope_access = None
|
|
for key, value in row.items():
|
|
setattr(current, key, value)
|
|
updated += 1
|
|
session.add_all(events)
|
|
session.commit()
|
|
return {
|
|
"platform": provider.platform,
|
|
"programs": len(seen),
|
|
"created": created,
|
|
"updated": updated,
|
|
"duration_ms": int((time.monotonic() - started) * 1000),
|
|
"events": len(events),
|
|
}
|
|
|
|
|
|
_SCOPE_TRANSITIONS = {
|
|
(ScopeState.IN_SCOPE.value, ScopeState.OUT_OF_SCOPE.value): (
|
|
BountyEvent.WENT_OUT_OF_SCOPE.value
|
|
),
|
|
(ScopeState.OUT_OF_SCOPE.value, ScopeState.IN_SCOPE.value): (
|
|
BountyEvent.CAME_INTO_SCOPE.value
|
|
),
|
|
}
|
|
|
|
|
|
def scope_changes(
|
|
program: BountyProgram, before: dict[tuple[str, str], str], after: dict
|
|
) -> list[BountyEventRow]:
|
|
"""Added, removed and flipped assets, never on a program's first read."""
|
|
events: list[BountyEventRow] = []
|
|
for key, row in after.items():
|
|
asset_type, identifier = key
|
|
was = before.get(key)
|
|
if was is None:
|
|
kind = BountyEvent.SCOPE_ADDED.value
|
|
else:
|
|
kind = _SCOPE_TRANSITIONS.get((was, row["scope_state"]))
|
|
if kind is None:
|
|
continue
|
|
events.append(
|
|
event_row(
|
|
program,
|
|
kind,
|
|
asset_type=asset_type,
|
|
asset_identifier=identifier,
|
|
detail=(row.get("instruction") or None)
|
|
and row["instruction"][:MAX_EVENT_DETAIL],
|
|
)
|
|
)
|
|
for key in before.keys() - after.keys():
|
|
events.append(
|
|
event_row(
|
|
program,
|
|
BountyEvent.SCOPE_REMOVED.value,
|
|
asset_type=key[0],
|
|
asset_identifier=key[1],
|
|
)
|
|
)
|
|
return events
|
|
|
|
|
|
def sync_scopes(
|
|
session: Session, program: BountyProgram, provider: BountyProvider
|
|
) -> int:
|
|
"""Replace one program's scope rows with what the platform reports now."""
|
|
try:
|
|
fetched = provider.scopes(program)
|
|
except AccessDeniedError:
|
|
logger.info(
|
|
"bounty scope refused",
|
|
platform=provider.platform,
|
|
handle=program.handle,
|
|
)
|
|
program.scope_access = ScopeAccess.DENIED.value
|
|
program.joined = False
|
|
session.commit()
|
|
return 0
|
|
deduped: dict[tuple[str, str], dict] = {}
|
|
for row in fetched.entries:
|
|
deduped[(row["asset_type"], row["asset_identifier"])] = {
|
|
**row,
|
|
"program_id": program.id,
|
|
}
|
|
|
|
before: dict[tuple[str, str], str] = {}
|
|
if program.scopes_synced_at is not None:
|
|
before = {
|
|
(s.asset_type, s.asset_identifier): s.scope_state
|
|
for s in session.execute(
|
|
select(BountyScope).where(BountyScope.program_id == program.id)
|
|
).scalars()
|
|
}
|
|
session.add_all(scope_changes(program, before, deduped))
|
|
|
|
session.execute(delete(BountyScope).where(BountyScope.program_id == program.id))
|
|
session.add_all([BountyScope(**row) for row in deduped.values()])
|
|
for key, value in fetched.program_updates.items():
|
|
setattr(program, key, value)
|
|
program.scope_access = None
|
|
program.scopes_synced_at = utc_now()
|
|
session.commit()
|
|
return len(deduped)
|
|
|
|
|
|
def scopes_to_refresh(
|
|
session: Session, provider: BountyProvider
|
|
) -> list[BountyProgram]:
|
|
"""Every program whose scope this sync must read, narrowed by the platform's own feed."""
|
|
programs = list(
|
|
session.execute(
|
|
select(BountyProgram).where(BountyProgram.platform == provider.platform)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
denied = ScopeAccess.DENIED.value
|
|
read_at = [p.scopes_synced_at for p in programs if p.scopes_synced_at is not None]
|
|
unread = [
|
|
p for p in programs if p.scopes_synced_at is None and p.scope_access != denied
|
|
]
|
|
if unread or not read_at:
|
|
return programs
|
|
oldest = min(read_at)
|
|
try:
|
|
changed = provider.changed_since(oldest)
|
|
except Exception as exc:
|
|
logger.info(
|
|
"bounty scope change feed unavailable",
|
|
platform=provider.platform,
|
|
error=str(exc),
|
|
)
|
|
return programs
|
|
if changed is None:
|
|
return programs
|
|
return [
|
|
p
|
|
for p in programs
|
|
if not p.external_id or p.external_id in changed or p.scope_access == denied
|
|
]
|
|
|
|
|
|
def sync_settings(session: Session) -> tuple[str, datetime | None]:
|
|
row = session.execute(
|
|
text(
|
|
"SELECT bounty_sync_interval, bounty_synced_at FROM instance_settings LIMIT 1"
|
|
)
|
|
).first()
|
|
if not row:
|
|
return DEFAULT_SYNC_INTERVAL, None
|
|
return (row[0] or DEFAULT_SYNC_INTERVAL), row[1]
|
|
|
|
|
|
def sync_due(session: Session) -> bool:
|
|
"""Whether the schedule says to sync now."""
|
|
interval, last = sync_settings(session)
|
|
hours = SYNC_INTERVAL_HOURS.get(interval)
|
|
if hours is None:
|
|
return False
|
|
return last is None or (utc_now() - last) >= timedelta(hours=hours)
|
|
|
|
|
|
def mark_synced(session: Session) -> None:
|
|
session.execute(text("UPDATE instance_settings SET bounty_synced_at = now()"))
|
|
session.commit()
|
|
|
|
|
|
def unreachable_summary(scopes: list[BountyScope]) -> dict[str, int]:
|
|
"""What the program lists that no scan can reach, counted by asset type label."""
|
|
counts: dict[str, int] = {}
|
|
for scope in scopes:
|
|
if scope.target_value:
|
|
continue
|
|
label = asset_type_spec(scope.asset_type).label
|
|
counts[label] = counts.get(label, 0) + 1
|
|
return counts
|