Files
rengine/shared/models/api_key.py
T
Yogesh OjhaandClaude Opus 5 5bf2d0395c feat(vulnerabilities): rank findings by exploitation, re-scored nightly without rescanning
Severity alone is close to useless for ordering work: roughly 60% of CVEs
score 7.0 or above. Until now EPSS and KEV came from nuclei's template
metadata, frozen at authoring time, so a finding could never learn that the
world had changed around it.

Two keyless feeds now land nightly and re-score every finding already stored:
EPSS (368k CVEs) and the CISA KEV catalog (1,695). A feed that fails to
download leaves its table untouched, so an install with no egress keeps
ranking. CISA is authoritative for is_kev on anything carrying a CVE.

exploitation.py judges each finding the way interest/ judges hosts, and the
signals that matter cross dimensions no other tool joins:

  ransom_path     a KEV ransomware CVE on a host whose ports also expose
                  a service ransomware crews reach for
  fresh_exploit   a public exploit published after the scan that found it
  untestable      no scanner template covers this CVE, so no scan can
                  clear it -- BlueKeep, EternalBlue and HTTP/2 Rapid Reset
                  all report false here
  reachable       answering directly, with no CDN or WAF in front

vulnx is an optional ceiling, never the floor: per-CVE exploits and their
links, template coverage, internet-wide population and bounty activity,
cached forever. Measured at 10 requests a minute keyless with no Retry-After,
so the client paces off the remaining header and can only ever be a
background cache-fill.

Alerting is delta-only, on the same rule as new findings: intel_signals is
upserted so created_at is when a finding first earned a signal, and repeated
refreshes alert nothing.

The nightly download is opt-out (instance_settings.threat_intel_auto_sync).
It gates the beat task and the worker warm-up only -- Refresh now always
works, so nobody is stuck with a scheduled outbound request they did not ask
for. Bounty activity renders only under CAP_HACKERONE; the gate is
presentational, so switching mode never loses data.

Every count is a promise: coverage, the query it compiles to, and the rows
its sheet opens were verified equal across all five signals.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PwxTAkPFUXPHPvdYT8Rhtm
2026-09-07 11:18:04 +05:30

114 lines
3.4 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.enums.api_key import APIProvider
from shared.utils.datetime import utc_now
API_PROVIDER_META: dict[str, dict] = {
APIProvider.VIEWDNS: {
"name": "ViewDNS.info",
"description": "Used for DNS intelligence, reverse lookups and DNS history",
"docs_url": "https://viewdns.info/api/?src=reNgine",
"requires_username": False,
"icon": "scan-search",
"color": "#6366f1",
},
APIProvider.CHAOS: {
"name": "Chaos",
"description": "ProjectDiscovery Chaos subdomain dataset for recon",
"docs_url": "https://cloud.projectdiscovery.io",
"requires_username": False,
"icon": "radar",
"color": "#f59e0b",
},
APIProvider.NETLAS: {
"name": "Netlas",
"description": "Internet-wide asset and attack-surface intelligence",
"docs_url": "https://netlas.io",
"requires_username": False,
"icon": "globe",
"color": "#10b981",
},
APIProvider.SECURITYTRAILS: {
"name": "SecurityTrails",
"description": "DNS, domain and subdomain history intelligence",
"docs_url": "https://securitytrails.com",
"requires_username": False,
"icon": "route",
"color": "#3b82f6",
},
APIProvider.HACKERONE: {
"name": "HackerOne",
"description": "Pull program scope and reports from HackerOne",
"docs_url": "https://api.hackerone.com",
"requires_username": True,
"icon": "shield",
"color": "#ef4444",
},
APIProvider.VULNX: {
"name": "vulnx",
"description": "ProjectDiscovery vulnerability intelligence: exploits, coverage and exposure",
"docs_url": "https://cloud.projectdiscovery.io",
"requires_username": False,
"icon": "biohazard",
"color": "#f43f5e",
},
}
class APIKey(SQLModel, table=True):
__tablename__ = "api_keys"
__table_args__ = (UniqueConstraint("provider", name="uq_api_key_provider"),)
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True, index=True)
provider: APIProvider = Field(index=True)
key_value: str = Field(max_length=1000)
key_meta: dict | None = Field(default=None, sa_column=Column(JSON, nullable=True))
is_enabled: bool = Field(default=True)
usage_counter: int = Field(default=0)
last_used_at: datetime | None = Field(default=None)
created_at: datetime = Field(default_factory=utc_now)
updated_at: datetime = Field(default_factory=utc_now)
class APIKeyCreate(BaseModel):
provider: APIProvider
key_value: str
key_meta: dict | None = None
class APIKeyUpdate(BaseModel):
key_value: str | None = None
is_enabled: bool | None = None
key_meta: dict | None = None
class APIKeyRead(BaseModel):
id: uuid.UUID
provider: APIProvider
key_value_masked: str
key_meta: dict | None = None
is_enabled: bool
usage_counter: int
last_used_at: datetime | None
created_at: datetime
updated_at: datetime
meta: dict
class ProviderInfo(BaseModel):
provider: APIProvider
name: str
description: str
docs_url: str
icon: str = "package"
color: str = "#64748b"
requires_username: bool = False
configured: bool = False
is_enabled: bool = False