PivotCheck
PivotCheck is a passive network discovery and pivot-path validation engine for authorized security assessments. It normalizes a host's observed network state — interfaces, routes, neighbors, DNS configuration, and listening sockets — into a single deterministic model, classifies each reachable network by evidence confidence, correlates transit evidence into explicit pivot assessments, and performs strictly bounded, operator-directed validation of exactly one target at a time.
PivotCheck performs no scanning. Discovery is passive (zero network I/O); validation occurs only on an explicit operator command against one explicit target. No output presents an inference as an observed fact, and no passive result is ever reported as reachability.
Design Principles
The following invariants are architectural and non-negotiable:
- Passive by default. Discovery reads local operating-system state only: no host sweeps, no ICMP probes, no traceroute, no port scans.
- Strict layer separation. Output maintains four distinct levels:
observed evidence → inference → priority → explicit validation. An inference is never rendered as an observed fact. - One target, one port, one attempt. Every validation action is bound to a single explicit destination. Port ranges and CIDR expansion are rejected at argument parsing; there are no retries, no fallback chains, and no parallel attempts.
- Timeout is ambiguous. A
TIMEOUTresult is never reported as proof of a host being down (or up). Absence of a response is not negative evidence. - Authentication is not authorization. A successful authentication handshake never implies execution capability, lateral-movement capability, or application health.
- Absence of evidence is never negative evidence. Gap analysis preserves the distinction between what was observed, what was not observed, what could not be collected, and what was never attempted.
- Deterministic output.
--jsonoutput is stable, ordered, and ANSI-free, with schema and provenance fields on every command.
Architecture
pivotcheck/
├── discovery/ OS-level collectors: interfaces, routes, neighbors,
│ DNS, sockets (Linux, Windows, macOS; SSH remotes)
├── models/ Frozen dataclasses: baseline, network, check, credentials
├── analysis/ Confidence classification, correlation, gap analysis,
│ next-step selection, explanation, what-if engine
├── checks/ Validation providers: tcp, http, ssh, smb, winrm,
│ rdp, proxy, resolver
├── output/ Terminal (TTY, color-aware) and JSON renderers
├── storage/ Baseline persistence (plaintext or Fernet+scrypt at rest)
├── utils/ Credential loading, redaction, validation, system info
└── cli.py Argparse wiring, argument validation, exit-code contract
Discovery collectors degrade to warnings when a tool is unavailable — never to failures. All credential material is supplied via environment variables (never the command line), held in memory only, and redacted in every output path.
Installation
pip install pivotcheck
Requires Python 3.10+. The core has zero runtime dependencies beyond the standard library; protocol backends are opt-in extras:
| Extra | Enables | Dependencies |
|---|---|---|
socks |
SOCKS5 CONNECT validation (proxy-check) |
PySocks ≥ 1.7.1 |
smb |
SMB / NTLM pass-the-hash validation (check --protocol smb) |
smbprotocol ≥ 1.15 |
winrm |
WinRM WS-Man authentication validation | pywinrm ≥ 0.5.0 |
kerberos |
Kerberos-backed WinRM (non-Windows hosts) | gssapi, krb5 |
encrypt |
Encrypted-at-rest baseline storage | cryptography ≥ 42.0 |
dev |
Lint and type-check toolchain (ruff, mypy) | — |
Install with extras:
pip install "pivotcheck[smb,winrm,encrypt]"
Verify:
pivotcheck --version # pivotcheck 1.0.0
Quick Start
pivotcheck discover # full passive discovery of this host
pivotcheck map --show-pivots # topology view with inferred pivot context
pivotcheck next # highest-priority investigation candidate
pivotcheck explain 10.50.0.0/16 # evidence chain for one network
pivotcheck gaps 10.50.0.0/16 # what evidence is missing?
pivotcheck check 10.10.20.25 --port 445 # explicit single-target TCP validation
pivotcheck proxy-check --proxy socks5://127.0.0.1:1080 10.10.20.25 --port 445
Canonical workflow: discover → next → explain the candidate → check
one explicit target → baseline create → compare after network changes.
Command Reference
| Command | Purpose |
|---|---|
pivotcheck discover |
Passive discovery of interfaces, routes, neighbors, DNS, and sockets |
pivotcheck map |
Topology-focused view of the same data with confidence classification |
pivotcheck next |
Select the single highest-priority investigation candidate, deterministically |
pivotcheck gaps NETWORK |
Classify which evidence exists, is missing, or was never collected |
pivotcheck explain NETWORK |
Full evidence → inference → priority chain for one network |
pivotcheck check TARGET --port P |
Explicit single-target validation (TCP, HTTP, SSH, SMB, WinRM, RDP) |
pivotcheck proxy-check --proxy URL TARGET --port P |
SOCKS5 CONNECT validation through one operator-supplied proxy |
pivotcheck baseline |
Create, list, show, and delete discovery snapshots (create --encrypt --password-env VAR for encryption at rest) |
pivotcheck compare BASELINE |
Diff current state against a saved baseline, with recommendations |
pivotcheck compare A B |
Diff two saved baselines offline (no discovery performed) |
pivotcheck opsec --action A --platform P |
Predictive observability analysis for a validation action |
pivotcheck what-if --network CIDR --gateway IP --interface NAME |
Hypothetical evaluation of assumed routing changes |
Global options: --json (stable machine output), --data-dir DIR
(baseline storage location), -v (verbosity), --no-color.
discover, map, gaps, and explain also accept SSH options to collect
from a remote vantage point using your existing agent and keys.
Evidence Model
PivotCheck keeps four levels strictly separate:
observed evidence → inference → priority → explicit validation
Every evidence type carries exactly one of six explicit statuses
(pivotcheck.analysis.evidence_gaps.EvidenceStatus):
| Status | Meaning |
|---|---|
OBSERVED |
Collector ran and found evidence |
NOT_OBSERVED |
Collector ran but found no evidence for this network |
NOT_COLLECTED |
Collector was unavailable or degraded |
NEGATIVE_EVIDENCE |
Collector explicitly found absence (e.g., a neighbor entry in FAILED state) |
NOT_APPLICABLE |
The evidence type does not apply to this context |
NOT_PERFORMED |
The corresponding action was not performed |
Absence of evidence is never quietly promoted to negative evidence. A
TIMEOUT from check is reported as ambiguous, never as proof either way.
Networks are classified HIGH, MEDIUM, or LOW confidence; a routed
network is MEDIUM — inferred from routing evidence, never presented as
reachable. The exact confidence rules are specified in
docs/confidence-model.md.
Validation Reference
All validation is strictly operator-directed: one explicit target, one explicit port, one attempt, per command.
TCP — check TARGET --port P (up to 16 explicit ports; ranges rejected).
One connect attempt per address:port. Result classes distinguish connection
established, refused, filtered, and timeout (ambiguous by definition).
HTTP — check TARGET --port P --protocol http. One HEAD request to
one target:port. Add --http-tls for HTTPS with certificate verification
(TLS failures are reported and never bypassed). No crawling, no redirects,
no enumeration. An HTTP response is an observation, never proof of
authorization or application health.
SSH — check TARGET --port P --protocol ssh --ssh-key-env VAR. One
public-key authentication attempt using the key named by the environment
variable. Strict host-key verification by default; server-identity
verification is reported separately from authentication success. No command
execution.
SMB / Pass-the-Hash — check TARGET --port 445 --protocol smb --credential-env VAR --smb-user USER (requires the smb extra). Without
--smb-hash, VAR holds the account password. With --smb-hash, VAR
holds an NTLM hash (32 hex chars, or LM:NT) and the backend authenticates
via a typed hash credential — the hash is never converted to a password and
never surfaces in diagnostics. --smb-user is required for pass-the-hash
(a hash authenticates an account; it does not identify one). One NTLM
session-setup attempt; no share enumeration, no fallback chains.
WinRM — check TARGET --port 5985 --protocol winrm (requires the
winrm extra). Read-only WS-Man authentication probe: password
(--credential-env), NTLM hash (--winrm-hash), or Kerberos ticket
(--winrm-ticket-env; mutually exclusive). No shells, no commands, no
session establishment.
RDP observation — check TARGET --port 3389 --protocol rdp. One bounded
TPKT/X.224 pre-authentication exchange. Zero credentials, zero
authentication, zero session establishment. RDP_RESPONSE_OBSERVED plus the
server-selected security protocol (PROTOCOL_RDP / SSL / HYBRID / HYBRID_EX)
is an observation only; malformed and non-RDP responses fail closed.
SOCKS5 CONNECT — proxy-check --proxy socks5://HOST:PORT TARGET --port P
(requires the socks extra). One proxy, one destination, one port, one
attempt. Hostnames are resolved by the proxy, never locally. Proxy
credentials via --proxy-auth-env or the proxy URL; redacted everywhere.
UDP is deliberately unsupported: no response is not evidence of unreachability, and PivotCheck does not make claims it cannot support.
OPSEC Intelligence
pivotcheck opsec --action smb-auth --platform windows
Predictive analysis of the telemetry a validation action is reasonably
expected to produce on a named platform (windows, linux, macos;
actions: ssh-auth, smb-auth, winrm-auth, tcp-connect,
socks5-connect, http-request). Strictly predictive: PivotCheck never
observes target-side telemetry, never guarantees event generation, and
provides no evasion guidance. HTTPS is a distinct visibility profile
(http-request documents how TLS changes which bytes are observable on the
path) — visibility-changing, not stealthy.
What-If Analysis
pivotcheck what-if \
--network 10.50.0.0/16 --gateway 10.40.1.254 --interface eth1 \
--network 10.60.0.0/16 --gateway 10.40.1.254 --interface eth1
Evaluates hypothetical routing changes (repeatable
--network/--gateway/--interface triplets) against observed evidence.
Every result is explicitly marked hypothetical: contradictions with observed
evidence are preserved and reported, unknowns are named, and nothing is
claimed as reachable, validated, or executable. Uses passive local discovery
only — no active validation is performed by what-if.
Output Contract
Human output is evidence-first, with color on TTYs (--no-color disables).
Every command accepts --json, producing stable, deterministic, ANSI-free
output with schema and provenance fields:
{
"schema_version": "1.1",
"tool": "pivotcheck",
"version": "1.0.0",
"command": "next",
"candidate": null,
"message": "NO INVESTIGATION CANDIDATES"
}
Exit Codes
| Code | Constant | Meaning |
|---|---|---|
| 0 | EXIT_OK |
Success |
| 1 | EXIT_FATAL |
Unrecoverable runtime failure |
| 2 | EXIT_USAGE |
Invalid arguments or failed argument validation |
| 3 | EXIT_RESOLVE / EXIT_BASELINE_NOT_FOUND |
Target network not present in the current model; baseline not found |
| 4 | EXIT_BASELINE_SCHEMA |
Baseline file failed schema validation |
Platform Support
| Platform | Collectors |
|---|---|
| Linux | ip, ss, /etc/resolv.conf |
| Windows | ipconfig, route print, arp, netstat (English-locale output required) |
| macOS | ifconfig, netstat, arp, ndp, scutil --dns |
macOS IPv6 neighbors are read from the local ndp neighbor table — observed
L2 presence only, never reachability. Remote collection over SSH expects
Linux-style tooling on the remote vantage point. Collectors that cannot run
degrade to warnings, never failures.
Limitations
- Passive analysis shows what the evidence supports; it does not prove end-to-end reachability.
- A routed network is classified
MEDIUMconfidence, never presented as reachable. checkTIMEOUTis ambiguous by nature and is never reported as proof either way.- Baselines contain reconnaissance detail and are stored unencrypted by
default — protect them accordingly. Encrypted-at-rest storage is available
via the
encryptextra:baseline create --encrypt --password-env VAR(password supplied from the named environment variable, never the command line; Fernet + scrypt; wrong passwords and tampered files fail closed; a lost password means the baseline cannot be recovered). Reading or listing encrypted baselines requires the same--password-envflag. Encryption protects stored contents at rest only — it does not protect a compromised host, process, or password. - Application-layer interaction beyond a single HTTP
HEADobservation, SMB share access, remote execution, and SSH command execution are out of scope by design.
Documentation
Focused documentation lives in docs/:
- Confidence model
- Baseline workflow
- Comparison semantics
- Operator intelligence
- Operator findings
- Perspective map
- SSH session providers
Contributing
Contributions are welcome — see CONTRIBUTING.md for development setup and pull-request guidelines.
Security
Report vulnerabilities privately per SECURITY.md. Do not open public issues for security reports.
Feedback
Found a bug, usability issue, compatibility problem, or have a feature request? Use the structured channels:
- Report a Bug — reproducible defects
- Request a Feature — new capability requests (evidence-gated)
- Share Feedback — usability, documentation, compatibility, false positives/negatives
Never include passwords, private keys, NTLM hashes, Kerberos tickets, API keys, or sensitive network data in any report. Redact before submitting.
License
GNU General Public License v3.0 — GPL-3.0-only.
Metadata
Release files for pivotcheck 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pivotcheck-1.0.1.tar.gz | 185.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pivotcheck-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 414.0 kB
Release files / pivotcheck-1.0.1.tar.gz
| Download URL | pivotcheck-1.0.1.tar.gz |
|---|---|
| Size | 185.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b7f5469acb1532fa987ffebb0d1e6931e91187e71feba1999faf1f4cdf76b75a
|
|
BLAKE2b-256 checksum How to use checksums |
41d1d9e96f04c3be77dea73d73ccf9979e903fb44c1cb2537ee1fb5d7e341d32
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logRelease files / pivotcheck-1.0.1-py3-none-any.whl
| Download URL | pivotcheck-1.0.1-py3-none-any.whl |
|---|---|
| Size | 228.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8de883a25665e2917c3a4ca218f1520ca2bf4c1e04dbaa2d521296a9eee30fc3
|
|
BLAKE2b-256 checksum How to use checksums |
7bfcc1807c0ae265a36417c0042dd576eb3352302cab24cf367de8cafd1e533c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency log