#!/usr/bin/env python3
"""Scope CVE-2026-12569 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-12569'
VENDOR = 'PTC'
PRODUCT = 'Windchill and FlexPLM'
AFFECTED_VERSION_SELECTORS = [
"ptc-windchill-and-flexplm 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-12569",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-12569",
"https://www.ptc.com/en/support/article/CS473270",
]
DOMAINS = []
HASHES = []
URLS = []
FILES = []
PROCESS_PATTERNS = [PRODUCT, VENDOR, CVE_ID]
NETWORK_PATTERNS = []
INDICATORS = [
"CVE-2026-12569",
"PTC",
"Windchill and FlexPLM",
"ptc-windchill-and-flexplm 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-12569",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-12569",
"https://www.ptc.com/en/support/article/CS473270",
]
OUT = os.environ.get("OUT", "hp-ptc-windchill-flexplm-cve-2026-12569-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())