abuse: классификатор ловит панели/github/hn и мульти-теги, реальный total из индекса, кап API поднят до 3000
Some checks failed
ci / test (3.10) (push) Has been cancelled
ci / test (3.11) (push) Has been cancelled
ci / test (3.12) (push) Has been cancelled

This commit is contained in:
Vlad Gothbreach
2026-10-27 01:28:28 +03:00
parent 61f15f88f2
commit 8de421b6af
13 changed files with 3082 additions and 93 deletions

View File

@@ -1,9 +1,8 @@
"""Кандидаты для расширения реестра omni: security/CVE/leak/CN/RU/tg.
"""Кандидаты для расширения реестра omni: security/CVE/leaks/CN/RU/tg.
Источник истины для tools/probe_fresh.py. Живые строки после прогона
переезжают в omni/sources_fresh.py (генерируется `--emit`).
Формат dict совпадает с Source(**d). Адаптеры — только из feeds.ADAPTERS.
Формат dict совпадает с Source(**d); адаптеры — только из feeds.ADAPTERS.
"""
import datetime
@@ -13,50 +12,48 @@ def d(name, category, kind, url, adapter="rss", tags=(), limit=15, detail=False,
"tags": tuple(tags), "limit": limit, "detail": detail, "extra": extra or {}}
# NVD recent: окно считается на импорте (API требует обе даты и <=120 дней)
_NOW = datetime.datetime.utcnow()
_NVD_SINCE = (_NOW - datetime.timedelta(days=7)).strftime("%Y-%m-%dT00:00:00.000")
_NVD_UNTIL = _NOW.strftime("%Y-%m-%dT%H:%M:%S.000")
CANDIDATES = [
# ---------------- 1. security news / research (RSS) ----------------
# ---------------- 1. security news / research ----------------
d("hackread", "security", "rss", "https://hackread.com/feed/", "rss", ("news",)),
d("gbhackers", "security", "rss", "https://gbhackers.com/feed/", "rss", ("news",)),
d("cybersecuritynews", "security", "rss", "https://cybersecuritynews.com/feed/", "rss", ("news",)),
d("scmedia", "security", "rss", "https://www.scmagazine.com/rss.xml", "rss", ("news",)),
d("securityaffairs", "security", "rss", "https://securityaffairs.com/feed", "rss", ("news",)),
d("seclists_fulldisclosure", "security", "rss", "https://seclists.org/rss/fulldisclosure.rss", "rss", ("mailing", "exploit")),
d("seclists_bugtraq", "security", "rss", "https://seclists.org/rss/bugtraq.rss", "rss", ("mailing", "exploit")),
d("seclists_nmap", "security", "rss", "https://seclists.org/rss/nmap-dev.rss", "rss", ("mailing",)),
d("huntress_blog", "security", "rss", "https://www.huntress.com/blog/rss.xml", "rss", ("research", "dfir")),
d("recordedfuture_blog", "security", "rss", "https://www.recordedfuture.com/feed", "rss", ("research", "cti")),
d("cisco_security_blog", "security", "rss", "https://blogs.cisco.com/security/feed", "rss", ("research", "cti")),
d("helpnetsecurity", "security", "rss", "https://www.helpnetsecurity.com/feed/", "rss", ("news",)),
d("infosecurity_magazine", "security", "rss", "https://www.infosecurity-magazine.com/rss/news/", "rss", ("news",)),
d("cybersecuritydive", "security", "rss", "https://www.cybersecuritydive.com/feeds/news/", "rss", ("news",)),
d("theregister_security", "security", "rss", "https://www.theregister.com/security/headlines.atom", "rss", ("news",)),
d("arstechnica_security", "security", "rss", "https://arstechnica.com/security/feed/", "rss", ("news",)),
d("csoonline", "security", "rss", "https://www.csoonline.com/feed/", "rss", ("news",)),
d("industrialcyber", "security", "rss", "https://industrialcyber.co/feed/", "rss", ("ics", "news")),
d("malwaretech_blog", "security", "rss", "https://www.malwaretech.com/feed", "rss", ("research",)),
d("hackmag", "security", "rss", "https://hackmag.com/rss", "rss", ("ru", "research")),
# ------------- research / vendor labs -------------
d("recordedfuture_blog", "security", "rss", "https://www.recordedfuture.com/feed", "rss", ("cti",)),
d("cisco_security_blog", "security", "rss", "https://blogs.cisco.com/security/feed", "rss", ("cti", "research")),
d("checkpoint_research", "security", "rss", "https://research.checkpoint.com/feed/", "rss", ("research",)),
d("sophos_news", "security", "rss", "https://news.sophos.com/en-us/feed/", "rss", ("research",)),
d("trendmicro_si", "security", "rss", "https://www.trendmicro.com/rss/security-intelligence.xml", "rss", ("research",)),
d("volexity_blog", "security", "rss", "https://www.volexity.com/blog/feed/", "rss", ("research", "dfir")),
d("redcanary_blog", "security", "rss", "https://redcanary.com/blog/feed/", "rss", ("dfir",)),
d("elastic_security_labs", "security", "rss", "https://www.elastic.co/security-labs/rss/feed", "rss", ("research",)),
d("greynoise_blog", "security", "rss", "https://www.greynoise.io/blog/rss.xml", "rss", ("research",)),
d("intezer_blog", "security", "rss", "https://intezer.com/blog/feed/", "rss", ("malware",)),
d("sekoia_blog", "security", "rss", "https://blog.sekoia.io/feed/", "rss", ("research",)),
d("bitdefender_labs", "security", "rss", "https://www.bitdefender.com/blog/api/rss/labs/", "rss", ("research",)),
d("zscaler_research", "security", "rss",
"https://www.zscaler.com/blogs/security-research/rss.xml", "rss", ("research",)),
d("paloalto_blog", "security", "rss", "https://www.paloaltonetworks.com/blog/feed/", "rss", ("research",)),
d("arcticwolf_blog", "security", "rss", "https://arcticwolf.com/resources/blog/feed/", "rss", ("cti",)),
d("dragos_blog", "security", "rss", "https://www.dragos.com/feed/", "rss", ("ics",)),
d("citizenlab", "security", "rss", "https://citizenlab.ca/feed/", "rss", ("research", "apt")),
d("dfir_report", "security", "rss", "https://thedfirreport.com/feed/", "rss", ("dfir", "ransomware")),
d("sentinelone_blog", "security", "rss", "https://www.sentinelone.com/blog/feed/", "rss", ("research",)),
d("microsoft_security_blog", "security", "rss", "https://www.microsoft.com/en-us/security/blog/feed/", "rss", ("research",)),
d("virustotal_blog", "security", "rss", "https://blog.virustotal.com/feeds/posts/default", "rss", ("malware",)),
d("jfrog_blog", "security", "rss", "https://jfrog.com/blog/feed/", "rss", ("supply-chain",)),
d("gitguardian_blog", "security", "rss", "https://blog.gitguardian.com/rss/", "rss", ("secrets",)),
d("truffle_security", "security", "rss", "https://trufflesecurity.com/blog/rss.xml", "rss", ("secrets",)),
d("sonatype_blog", "security", "rss", "https://www.sonatype.com/blog/rss.xml", "rss", ("supply-chain",)),
d("acunetix_blog", "security", "rss", "https://www.acunetix.com/blog/feed/", "rss", ("appsec",)),
d("socradar_blog", "security", "rss", "https://socradar.io/feed/", "rss", ("cti",)),
d("hudsonrock_blog", "security", "rss", "https://www.hudsonrock.com/blog/rss.xml", "rss", ("stealer",)),
d("infosec_writeups", "community", "rss", "https://infosecwriteups.com/feed", "rss", ("blog", "writeup")),
d("arcticwolf_blog", "security", "rss", "https://arcticwolf.com/feed/", "rss", ("cti",)),
d("citizenlab", "security", "rss", "https://citizenlab.ca/feed/", "rss", ("apt", "research")),
d("dfir_report", "security", "rss", "https://thedfirreport.com/feed/", "rss", ("dfir", "ransomware")),
d("ncsc_uk", "security", "rss", "https://www.ncsc.gov.uk/api/1/services/v1/all-rss-feed.xml", "rss", ("gov", "cve")),
d("0xdf", "security", "rss", "https://0xdf.gitlab.io/feed.xml", "rss", ("writeup", "htb")),
d("hackmag", "security", "rss", "https://hackmag.com/feed/", "rss", ("ru",)),
d("sysdig_blog", "security", "rss", "https://sysdig.com/blog/feed/", "rss", ("cloud",)),
d("infosec_writeups", "community", "rss", "https://infosecwriteups.com/feed", "rss", ("writeup",)),
# ---------------- 2. CVE / vulnerabilities ----------------
d("nvd_recent", "security", "json",
@@ -67,54 +64,38 @@ CANDIDATES = [
"nvd", ("cve", "exploited")),
d("github_advisories_api", "security", "json",
"https://api.github.com/advisories?per_page=30&sort=published&direction=desc", "ghsa", ("cve", "ghsa")),
d("cve_circl_last", "security", "json", "https://cve.circl.lu/api/last/40", "cve_circl", ("cve",)),
d("ubuntu_usn", "security", "rss", "https://ubuntu.com/security/notices/rss.xml", "rss", ("cve", "linux")),
d("gentoo_glsa", "security", "rss", "https://security.gentoo.org/glsa/feed.xml", "rss", ("cve", "linux")),
d("arch_linux_asa", "security", "rss", "https://security.archlinux.org/feed.atom", "rss", ("cve", "linux")),
d("debian_dsa", "security", "html", "https://www.debian.org/security/", "html_listing", ("cve", "linux"),
detail=False, extra={"include_prefixes": ["/security/2026/"], "exclude_exact": ["/security/"]}),
d("oss_security_openwall", "security", "html", "https://www.openwall.com/lists/oss-security/", "html_listing",
("mailing", "cve"), extra={"include_prefixes": ["/lists/oss-security/2026/"]}),
d("arch_linux_asa", "security", "rss", "https://security.archlinux.org/advisory/feed.atom", "rss", ("cve", "linux")),
d("mozilla_advisories", "security", "html", "https://www.mozilla.org/en-US/security/advisories/", "html_listing",
("cve", "browser"), extra={"include_prefixes": ["/en-US/security/advisories/mfsa"]}),
d("chrome_releases_security", "security", "rss", "https://chromereleases.googleblog.com/feeds/posts/default",
"rss", ("cve", "browser")),
d("cvefeed_latest", "security", "rss", "https://cvefeed.io/rssfeed/latest.xml", "rss", ("cve",)),
d("vuldb_recent", "security", "rss", "https://vuldb.com/?rss.recent", "rss", ("cve",)),
d("cxsecurity", "security", "rss", "https://cxsecurity.com/rss/", "rss", ("exploit",)),
d("vulmon_recent", "security", "rss", "https://vulmon.com/rss/recent", "rss", ("cve",)),
d("cisa_ics_advisories", "security", "rss",
"https://www.cisa.gov/cybersecurity-advisories/ics-advisories.xml", "rss", ("ics", "cve")),
d("cisa_advisories", "security", "rss", "https://www.cisa.gov/cybersecurity-advisories/all.xml", "rss", ("cve",)),
d("epss_recent", "security", "json", "https://api.first.org/data/v1/epss?limit=25&order=!epss", "generic_json",
("cve", "epss")),
d("sploitus_rss", "security", "rss", "https://sploitus.com/rss", "rss", ("exploit",)),
d("seclists_fulldisclosure", "security", "rss", "https://seclists.org/rss/fulldisclosure.rss", "rss",
("mailing", "exploit")),
d("seclists_nmap", "security", "rss", "https://seclists.org/rss/nmap-dev.rss", "rss", ("mailing",)),
# ---------------- 3. leaks / ransomware / underground ----------------
d("darknetlive", "abuse", "rss", "https://darknetlive.com/feed/", "rss", ("darknet", "news")),
d("darkwebinformer", "abuse", "rss", "https://darkwebinformer.com/feed", "rss", ("leak", "news")),
d("databreaches_net", "abuse", "rss", "https://databreaches.net/feed/", "rss", ("breach", "news")),
d("redpacketsecurity", "abuse", "rss", "https://www.redpacketsecurity.com/feed/", "rss", ("cve", "leak")),
d("hackread_breaches", "abuse", "rss", "https://hackread.com/category/data-breaches/feed/", "rss", ("breach",)),
d("darknetlive", "abuse", "rss", "https://darknetlive.com/feed/", "rss", ("darknet",)),
d("ransomware_live_recent", "abuse", "json", "https://api.ransomware.live/recentvictims", "generic_json",
("ransomware",)),
d("ransomlook_recent", "abuse", "json", "https://www.ransomlook.io/api/recent", "generic_json", ("ransomware",)),
d("ransomware_live_groups", "abuse", "json", "https://api.ransomware.live/groups", "generic_json", ("ransomware",)),
d("shadowserver_news", "abuse", "rss", "https://www.shadowserver.org/news/feed/", "rss", ("infra",)),
# ---------------- 4. китайские security ----------------
d("anquanke_rss", "security", "rss", "https://www.anquanke.com/rss", "rss", ("cn", "news")),
d("freebuf_feed", "security", "rss", "https://www.freebuf.com/feed", "rss", ("cn", "news")),
# ---------------- 4. китайские ----------------
d("sihaoyou_feed", "security", "rss", "https://www.4hou.com/feed", "rss", ("cn", "research")),
d("seebug_paper", "security", "rss", "https://paper.seebug.org/rss/", "rss", ("cn", "research")),
d("xz_aliyun", "security", "html", "https://xz.aliyun.com/", "html_listing", ("cn", "research"),
extra={"include_prefixes": ["/t/"]}),
d("aqniu", "security", "rss", "https://www.aqniu.com/feed", "rss", ("cn", "news")),
d("knownsec_blog", "security", "rss", "https://blog.knownsec.com/feed/", "rss", ("cn", "research")),
d("butian_forum", "forum", "html", "https://forum.butian.net/", "html_listing", ("cn", "forum"),
extra={"include_prefixes": ["/share/"]}),
d("kanxue_news_html", "forum", "html", "https://bbs.kanxue.com/", "html_listing", ("cn", "forum"),
extra={"include_prefixes": ["/thread-"]}),
d("t00ls_html", "forum", "html", "https://www.t00ls.com/", "html_listing", ("cn", "forum"),
extra={"include_prefixes": ["/article-"]}),
d("secrss", "security", "html", "https://www.secrss.com/", "html_listing", ("cn", "news"),
extra={"include_prefixes": ["/articles/"], "exclude_exact": ["/articles"]}),
d("anquanke_news", "security", "html", "https://www.anquanke.com/news", "html_listing", ("cn", "news"),
extra={"include_prefixes": ["/post/id/"]}),
d("t00ls", "forum", "html", "https://www.t00ls.com/", "html_listing", ("cn", "forum"), detail=True,
extra={"include_prefixes": ["/articles-", "/Vuls-"]}),
d("freebuf_feed", "security", "rss", "https://www.freebuf.com/feed", "rss", ("cn", "news")),
# ---------------- 5. русскоязычные ----------------
d("xakep_ru", "security", "rss", "https://xakep.ru/feed/", "rss", ("ru", "news")),
@@ -128,22 +109,26 @@ CANDIDATES = [
d("bdu_fstec", "security", "rss", "https://bdu.fstec.ru/rss/vulxml", "rss", ("ru", "cve")),
d("codeby_forum", "forum", "html", "https://codeby.net/forums/", "xenforo_latest", ("ru", "forum")),
# ---------------- 6. telegram (t.me/s, публичное превью) ----------------
d("tg_thehackersnews", "security", "html", "https://t.me/s/TheHackersNews", "tg_web", ("tg", "news")),
# ---------------- 6. telegram (t.me/s) ----------------
d("tg_thehackernews", "security", "html", "https://t.me/s/thehackernews", "tg_web", ("tg", "news")),
d("tg_bleepingcomputer", "security", "html", "https://t.me/s/bleepingcomputer", "tg_web", ("tg", "news")),
d("tg_vxunderground", "security", "html", "https://t.me/s/vxunderground", "tg_web", ("tg", "malware")),
d("tg_secator", "security", "html", "https://t.me/s/SecAtor", "tg_web", ("tg", "ru")),
d("tg_itsecforu", "security", "html", "https://t.me/s/itsecforu", "tg_web", ("tg", "ru")),
d("tg_hitech_crime", "security", "html", "https://t.me/s/hitech_crime", "tg_web", ("tg", "ru")),
d("tg_codeby", "security", "html", "https://t.me/s/codeby", "tg_web", ("tg", "ru")),
d("tg_darkwebinformer", "abuse", "html", "https://t.me/s/DarkWebInformer", "tg_web", ("tg", "leak")),
d("tg_breachdetector", "abuse", "html", "https://t.me/s/breachdetector", "tg_web", ("tg", "leak")),
d("tg_databreaches", "abuse", "html", "https://t.me/s/databreaches", "tg_web", ("tg", "leak")),
d("tg_ransomware_live", "abuse", "html", "https://t.me/s/ransomware_live", "tg_web", ("tg", "ransomware")),
d("tg_cybersecuritynews", "security", "html", "https://t.me/s/cybersecuritynews", "tg_web", ("tg", "news")),
d("tg_true_secator", "security", "html", "https://t.me/s/true_secator", "tg_web", ("tg", "ru")),
d("tg_infosec_ru", "security", "html", "https://t.me/s/infosec_ru", "tg_web", ("tg", "ru")),
d("tg_hackernews_ru", "security", "html", "https://t.me/s/hackernews_ru", "tg_web", ("tg", "ru")),
d("tg_bugbounty", "security", "html", "https://t.me/s/bugbounty", "tg_web", ("tg", "bugbounty")),
d("tg_kalilinux", "security", "html", "https://t.me/s/kalilinux", "tg_web", ("tg", "tools")),
d("tg_vulns", "abuse", "html", "https://t.me/s/vulns", "tg_web", ("tg", "leak", "ru")),
d("tg_ai_newz", "community", "html", "https://t.me/s/ai_newz", "tg_web", ("tg", "ai", "ru")),
d("tg_ai_machinelearning", "community", "html", "https://t.me/s/ai_machinelearning_big_data", "tg_web",
("tg", "ai", "ru")),
d("tg_denissexy", "community", "html", "https://t.me/s/denissexy", "tg_web", ("tg", "ai", "ru")),
d("tg_bo0om", "abuse", "html", "https://t.me/s/bo0om", "tg_web", ("tg", "leak", "ru")),
# ---------------- reddit (forum) ----------------
d("reddit_blueteamsec", "forum", "json", "https://www.reddit.com/r/blueteamsec/.json?limit=25", "reddit_listing",
("reddit", "blue-team")),
d("reddit_exploitdev", "forum", "json", "https://www.reddit.com/r/ExploitDev/.json?limit=25", "reddit_listing",
("reddit", "exploit")),
d("reddit_purpleteamsec", "forum", "json", "https://www.reddit.com/r/purpleteamsec/.json?limit=25",
"reddit_listing", ("reddit", "purple-team")),
]

45
tools/probe_api.py Normal file
View File

@@ -0,0 +1,45 @@
"""Замер API панели: код, время, ключи ответа. Без секретов, только метрики."""
import json
import time
import urllib.parse
import urllib.request
BASE = "http://127.0.0.1:8078"
PATHS = [
"/api/health",
"/api/stats",
"/api/timeline?days=30",
"/api/items?limit=5",
"/api/abuse?limit=3",
"/api/tg/status",
"/api/keys",
"/api/watch",
"/api/digest?days=1",
"/api/suggest?q=cve",
]
def main():
for p in PATHS:
t0 = time.time()
try:
raw = urllib.request.urlopen(BASE + p, timeout=90).read().decode("utf-8", "ignore")
d = json.loads(raw)
keys = list(d.keys()) if isinstance(d, dict) else type(d).__name__
extra = ""
if isinstance(d, dict):
if "items" in d and isinstance(d["items"], list):
extra = " n=%d" % len(d["items"])
if d["items"]:
extra += " keys=%s" % ",".join(list(d["items"][0].keys())[:14])
if "me" in d:
extra = " " + json.dumps(d, ensure_ascii=False)[:200]
if "md" in d:
extra = " md=%d chars" % len(d["md"])
print("%-34s %6.2fs %s%s" % (p, time.time() - t0, keys, extra))
except Exception as e:
print("%-34s %6.2fs ERR %s" % (p, time.time() - t0, e))
if __name__ == "__main__":
main()

View File

@@ -59,11 +59,37 @@ def probe_one(src, limit):
"samples": [{"title": i.title[:160], "url": i.url[:200]} for i in items[:3]]}
def emit(rows, dead, path):
def _norm(u):
u = u.strip().lower().rstrip("/")
for pre in ("https://", "http://"):
if u.startswith(pre):
u = u[len(pre):]
return u[4:] if u.startswith("www.") else u
def _registry_urls():
from omni.sources import all_sources
out = set()
for s in all_sources():
out.add(_norm(s.url))
out.add(_norm(s.url).split("?")[0])
return out
def emit(rows, dead, path, dedupe=True):
"""omni/sources_fresh.py: только живые, без импортов из omni.sources."""
def q(s):
return '"%s"' % str(s).replace('"', '\\"')
if dedupe:
have = _registry_urls()
kept, skipped = [], []
for r in rows:
n = _norm(r["url"])
(skipped if (n in have or n.split("?")[0] in have) else kept).append(r)
print("emit: уже есть в реестре -> %d: %s" % (len(skipped), ", ".join(r["name"] for r in skipped)))
rows = kept
lines = [
'"""Свежая волна источников omni: security/CVE/leaks/CN/RU/tg.',
"",
@@ -104,8 +130,19 @@ def main():
ap.add_argument("--out", default="data/fresh_probe.json")
ap.add_argument("--emit", action="store_true")
ap.add_argument("--fresh", action="store_true", help="прогонять только то, что уже в sources_fresh.py")
ap.add_argument("--emit-only", action="store_true", help="перегенерировать sources_fresh.py из готового JSON")
ap.add_argument("--no-dedupe", action="store_true", help="не отбрасывать источники, уже присутствующие в реестре")
a = ap.parse_args()
if a.emit_only:
rep = json.loads((ROOT / a.out).read_text(encoding="utf-8"))
rows = rep["results"]
alive = [r for r in rows if r["ok"]]
dead = [r for r in rows if not r["ok"]]
n = emit(alive, dead, ROOT / "omni" / "sources_fresh.py", dedupe=not a.no_dedupe)
print("omni/sources_fresh.py: %d источников" % n)
return 0
cands = [c for c in CANDIDATES if not a.only or a.only in c["name"]]
srcs = []
for c in cands:
@@ -137,7 +174,7 @@ def main():
print(" dead %-24s %s" % (r["name"], r["err"][:110]))
if a.emit:
n = emit(alive, dead, ROOT / "omni" / "sources_fresh.py")
n = emit(alive, dead, ROOT / "omni" / "sources_fresh.py", dedupe=not a.no_dedupe)
print("omni/sources_fresh.py: %d источников" % n)
return 0

32
tools/ui_debug.py Normal file
View File

@@ -0,0 +1,32 @@
"""Дебаг панели: клик по каждому разделу, hash, начало DOM, ошибки консоли."""
import asyncio
import sys
from playwright.async_api import async_playwright
async def main(url="http://127.0.0.1:8078/"):
msgs = []
async with async_playwright() as p:
b = await p.chromium.launch()
pg = await b.new_page(viewport={"width": 1400, "height": 900})
pg.on("console", lambda m: msgs.append([m.type, m.text[:200]]))
pg.on("pageerror", lambda e: msgs.append(["pageerror", str(e)[:300]]))
await pg.goto(url, wait_until="domcontentloaded")
await pg.wait_for_selector(".kpi", timeout=20000)
for nid in ("feed", "abuse", "sources", "runs", "tg", "keys", "digest", "watch", "overview"):
await pg.click(f"[data-nav={nid}]")
await pg.wait_for_timeout(1600)
h = await pg.evaluate("() => location.hash")
head = (await pg.locator("#view").inner_text())[:180].replace("\n", " | ")
items = await pg.locator("#view .item").count()
rows = await pg.locator("#view table tbody tr").count()
print(f"[{nid}] hash={h!r} items={items} rows={rows}\n {head}")
print("\nCONSOLE:")
for m in msgs[-25:]:
print(" ", m)
await b.close()
if __name__ == "__main__":
asyncio.run(main(*(sys.argv[1:] or [])))