# Architecture RPCScope separates collection from interpretation so that external tool changes do not silently change the meaning of a finding. ```text targets (expanded, resolved, deduplicated, capped) | +-- independent TCP/135 + TCP/445 discovery | +-- RPC-only hosts remain in scope | +-- unreachable: stop here, retain discovery in JSON only | +-- reachable: queue host pipelines only +-- NetExec SMB inventory -------+ +-- enum4linux-ng JSON ----------+--> normalized HostEvidence +-- Impacket rpcdump ------------+ | +-- optional MSF scanner --------+ v +-- opt-in CLDAP precondition ---+ (not a patch detector) deterministic rules | +---------------+---------------+ | | report.json report.md ``` ## Trust boundaries - Collector output is untrusted input. Parsers extract only labelled facts and never execute data from results. - Credentials are passed to external tools only when the operator supplies them. Recorded command metadata redacts passwords. - Live collection is gated by `--i-am-authorized`; offline analysis does not require the flag. - A UUID match establishes interface exposure. It does not establish vulnerable code, reachable opnums, authorization, patch state, or exploitability. - A CVE-level finding requires a purpose-built scanner result or a separately implemented, testable evidence predicate. ## Data model `HostEvidence` is the stable seam of the project. Collectors contribute: - identity: address, hostname, domain, OS fingerprint; - SMB posture: SMBv1, signing, null/guest session state; - shares: name, read/write access, comment; - RPC surface: UUID, version, protocol label, provider, binding/endpoint; - checks: explicit scanner name and outcome; - check details: the actual attributed result marker and any evidence conflicts; - collection: pending, success, failure, or skipped state for each collector; - sources: raw artifact paths. Rules consume only this model. They do not parse terminal output and do not invoke tools. Dedicated checks are registered in `rpcscope/catalog.py`. CLI NetExec checks run in single-module, bounded live-host batches; unrelated modules remain separate processes. The five bind-only exposure methods share `METHOD=All` only when the complete unchanged set was selected. Each check keeps a separate normalized result. `longlogon` is native Python and estimates a naming precondition over CLDAP, not vulnerability or patch status. `smbghost` now uses `smb_negotiate.py`: one ordinary, unauthenticated SMB2 negotiate with bounded framing/context parsing and an absolute deadline. Compression is a capability exposure, not a CVE finding. `console.py` renders compact, optional-color host results separately from the complete saved evidence. The CLI's compact progress bar suppresses individual task lines; plain/auto modes retain diagnostic events. Per-invocation timings are retained, and recognized local RPC-dump startup failures stop further queued copies without suppressing remote errors or other checks. The registry also distinguishes interface exposure and information gathering. Accepted RPC binds are `exposed`; spooler registrations and OXID interface results are `observed`. They become informational findings rather than CVE verdicts. `CheckAttempt` preserves backend-specific evidence, and conflicting positive/negative results are highlighted directly in minimal reports. `advisories.py` contains a separate review-only metadata registry for 2020–2026 issues. `sync-cves` refreshes fixed Microsoft CNA records without changing `catalog.CHECKS`, importing code, or generating host vulnerability verdicts. Discovery uses `--discovery-workers` (256 by default) for lightweight TCP probing and NetExec inventory. A check batch has at most `--jobs` live targets (8 by default, capped at 128 per command) and the same NetExec thread limit. Batches run sequentially; afterward the Python host pool has at most `--jobs` active futures. This avoids nested concurrency multiplication. Host pipelines run native/optional MSF checks, RPC dump, then optional full enum4linux. Full enum is enabled by `--deep` in the CLI; the library retains the old defaults. Dead targets never receive pending checks or host futures. TCP timeout/closed are completed reachability outcomes; an unprobed target indicates a real discovery interruption/deadline, while local resource exhaustion is explicitly a scan failure. A thread-safe progress callback emits task start/finish events to stderr for reachable hosts and silently advances counts for unreachable ones. A separate callback publishes atomic report snapshots as host jobs finish. Parsing uses original in-memory output, while saved artifacts and check details redact supplied credentials; redaction cannot alter target addresses or detection markers before classification. Execution completion is independent of evidence certainty. `inconclusive` and `suspected` are completed results; ordinary skipped checks are counted outside the eligible denominator. Failed processes, pending work and cancellation remain partial. Text and Markdown show reachable hosts; JSON retains the full expanded scope and all discovery states. `netexec.py` plans batches and inspects installed CLI capabilities without importing scanner code. Help inspection is cached; read-only share semantics must be known. Output is indexed once by IP/module/port, and known zero-exit host exceptions do not masquerade as successful collection. See `NETEXEC_REVIEW.md` for pinned upstream references and verification limits. ## Rule quality bar Every rule should include a stable ID, severity, confidence, exact evidence, impact, remediation, and a primary reference when one exists. New rules need positive and negative fixtures. Avoid CVE claims based only on banners, UUIDs, OS names, or open ports. ## NDRaider boundary NDRaider can statically recover interface UUIDs and typed NDR method grammars from Windows binaries, then generate structure-aware fuzz inputs. That complements RPCScope in a lab workflow: 1. RPCScope observes a vendor UUID on an authorized target. 2. The assessor obtains the matching server binary through an approved process. 3. NDRaider analyzes the binary offline and exports grammar JSON. 4. A future importer correlates UUID/version/method counts with the remote observation. 5. Any live fuzzing remains a separate, explicit active-testing profile. Keeping that boundary prevents a fast inventory scan from unexpectedly becoming a fuzzer. See `CHECKER_POLICY.md` for the CVE synchronization and future proof-of-execution requirements.