#!/usr/bin/env python3
"""Scope CVE-2026-56290 exposure from asset inventories and exported product logs.
Input: a directory containing CSV, JSON, JSONL, TXT, LOG, YAML, or XML exports.
The script does not contact the network. It looks for the CVE, vendor/product
selectors, and affected-version selectors recorded in the Halting Problems profile.
"""
from __future__ import annotations
import csv, json, os, sys
from pathlib import Path
CVE_ID = "CVE-2026-56290"
VENDOR = "Joomlack"
PRODUCT = "Page Builder"
AFFECTED_VERSION_SELECTORS = [
"joomlack-page_builder_ck < 3.6.0"
]
SOURCE_SELECTORS = [
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
"https://nvd.nist.gov/vuln/detail/CVE-2026-56290",
"https://www.joomlack.fr/",
"https://forum.joomlack.fr/index.php/page-builder-ck/21627-nouvelle-version-de-pbck-et-joomla-3",
"https://mysites.guru/blog/pagebuilderck-unauthenticated-file-upload-rce/",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-56290"
]
DOMAINS = []
HASHES = []
URLS = []
FILES = []
PROCESS_PATTERNS = [PRODUCT, VENDOR, CVE_ID]
NETWORK_PATTERNS = []
INDICATORS = [
"CVE-2026-56290",
"Joomlack",
"Page Builder",
"joomlack-page_builder_ck < 3.6.0",
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
"https://nvd.nist.gov/vuln/detail/CVE-2026-56290",
"https://www.joomlack.fr/",
"https://forum.joomlack.fr/index.php/page-builder-ck/21627-nouvelle-version-de-pbck-et-joomla-3",
"https://mysites.guru/blog/pagebuilderck-unauthenticated-file-upload-rce/",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-56290"
]
OUT = os.environ.get("OUT", "hp-joomlack-page-builder-ck-cve-2026-56290-kev-scope")
TEXT_SUFFIXES = {'.csv','.json','.jsonl','.txt','.log','.yaml','.yml','.xml','.conf','.ini','.md'}
EXCLUDED_DIR_NAMES = {'.git','node_modules','vendor','dist','__pycache__','.venv'}
def read_text(path: Path) -> str:
try:
return path.read_text(encoding='utf-8', errors='ignore')
except Exception:
return ''
def iter_files(root: Path):
if root.is_file():
yield root; return
for p in root.rglob('*'):
if p.is_file() and not any(part in EXCLUDED_DIR_NAMES for part in p.parts):
if not p.suffix or p.suffix.lower() in TEXT_SUFFIXES:
yield p
def scan(root: Path):
matches=[]
lowered=[s.lower() for s in INDICATORS if s]
for path in iter_files(root):
text=read_text(path)
low=text.lower()
hits=sorted({INDICATORS[i] for i,s in enumerate(lowered) if s in low})
if hits:
matches.append({'path': str(path), 'hits': hits})
return matches
def main(argv=None):
argv=argv or sys.argv[1:]
root=Path(argv[0]) if argv else Path('.')
matches=scan(root)
out_dir=Path(OUT); out_dir.mkdir(parents=True, exist_ok=True)
report={'cve': CVE_ID, 'vendor': VENDOR, 'product': PRODUCT, 'affected_version_selectors': AFFECTED_VERSION_SELECTORS, 'match_count': len(matches), 'matches': matches}
(out_dir/'scope_report.json').write_text(json.dumps(report, indent=2), encoding='utf-8')
print(json.dumps({'cve': CVE_ID, 'match_count': len(matches), 'report': str(out_dir/'scope_report.json')}, indent=2))
return 2 if matches else 0
if __name__ == '__main__':
raise SystemExit(main())