#!/usr/bin/env python3
"""Scope CVE-2026-34908 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 json, os, sys
from pathlib import Path
CVE_ID = 'CVE-2026-34908'
VENDOR = 'Ubiquiti'
PRODUCT = 'UniFi OS'
AFFECTED_VERSION_SELECTORS = [
"ubiquiti-unifi-os unknown_after_direct_source_review",
]
SOURCE_SELECTORS = [
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
"https://nvd.nist.gov/vuln/detail/CVE-2026-34908",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-34908",
"https://community.ui.com/releases/Security-Advisory-Bulletin-064-064/84811c09-4cf4-42ab-bd61-cc994445963b",
]
DOMAINS = []
HASHES = []
URLS = []
FILES = []
PROCESS_PATTERNS = [PRODUCT, VENDOR, CVE_ID]
NETWORK_PATTERNS = []
INDICATORS = [
"CVE-2026-34908",
"Ubiquiti",
"UniFi OS",
"ubiquiti-unifi-os unknown_after_direct_source_review",
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
"https://nvd.nist.gov/vuln/detail/CVE-2026-34908",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-34908",
"https://community.ui.com/releases/Security-Advisory-Bulletin-064-064/84811c09-4cf4-42ab-bd61-cc994445963b",
]
OUT = os.environ.get("OUT", "hp-ubiquiti-unifi-os-cve-2026-34908-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 q in root.rglob('*'):
if q.is_file() and not any(part in EXCLUDED_DIR_NAMES for part in q.parts):
if not q.suffix or q.suffix.lower() in TEXT_SUFFIXES: yield q
def scan(root: Path):
matches=[]; lowered=[s.lower() for s in INDICATORS if s]
for path in iter_files(root):
low=read_text(path).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())