openhound-collector-common
Shared pure-Python, in-process infrastructure for OpenHound on-prem / Windows-auth
collectors. Factored out of the SCCM extension so collectors (starting with mssql) reuse
one implementation of the hard parts instead of copying them.
No child processes, no native database/auth binaries — TDS, NTLM, Kerberos, EPA channel
binding, SPNEGO, LDAP, WMI, and SOCKS5 are all done in Python via impacket / ldap3 /
pywin32 and the stdlib.
Layout (built incrementally)
| Subpackage | Purpose |
|---|---|
clients/mssql.py |
TDS + TLS-over-TDS + NTLMv2 (EPA CBT/SPN) + Kerberos + SSPI; EPA detection. TLS-1.2 cap. |
clients/auth.py |
Kerberos AP-REQ/SPNEGO + NTLM + current-user SSPI token minting. |
clients/ad.py |
LDAP transport×bind waterfall (lockout-safe), SPN search, SID→AD-object resolution. |
clients/wmi.py |
WMI over impacket DCOM + pywin32 (Win32_GroupUser, Win32_Service). |
discovery/dns.py |
DC/SRV/A discovery + DNS resolver override (proxy-aware). |
dlt/source_bridge.py |
push→pull DLT emit-resource bridge for multi-target collection. |
dlt/convert_pipeline.py |
convert-reads-DuckDB (self-run pipeline + opengraph_file + no-op source). |
dlt/duckdb_safe.py |
_safe/_ensure_columns/_arr defenses against dlt column-dropping. |
logging/log_context.py |
VERBOSE tier + [target]/[phase] contextvar tagging. |
graph/stub_node.py, graph/graph_edge.py |
generic edge model + edge-endpoint stub backfill. |
proxy/socks.py |
pure-Python SOCKS5 dialer. |
orchestration/run.py |
After convert, run_end_to_end flat-zips the emitted graph *.json into the graph dir (name from graph_zip_name, default <app.name>_collection.zip), ready for BloodHound File Ingest (loose files kept), and reports it back on StagePaths.graph_zip — the archive that was written, or None if convert emitted no *.json. derive_stage_paths leaves it None, since it runs before anything exists (including on a collector's failure path). |
integration_testing/ |
Collector-agnostic assert-and-diff engine: payload loader (dir or zip), pattern matcher, fixture case runner, schema coverage, and the property-level graph comparator behind openhound-compare. |
Consumers
- ConfigManBearPig 2.0 (PyPI:
configmanbearpig) — the SCCM collector. Depends on a published, capped release (>=0.1.0,<0.2.0). mssql— the in-progress MSSQL collector.
This is a library: it is never installed on its own by an end user. It arrives as a transitive dependency when someone installs a collector:
uv tool install openhound --with configmanbearpig
It ships exactly one command, openhound-compare (below); everything else is imported, not invoked.
openhound-compare
Diff two OpenGraph payloads down to property name and value. Installed with this package as a console script — no collection and no framework CLI involved.
openhound-compare BASELINE CANDIDATE [--json PATH] [-q]
Each side may be a .zip or a directory of *.json payloads, in any combination — an archived
baseline against a fresh convert output directory is the common case.
Baseline is what came first. A regression is anything present in the baseline and missing or
reduced in the candidate: a dropped node or edge, a lost property, a property emptied to
null/""/[]/{}, a list-valued property that lost items, or a property name that no longer
appears on a kind at all. Additions and ordinary value changes are reported under ADDED and do not
fail the run.
| Exit | Meaning |
|---|---|
0 |
No regressions. Additions and value changes may still be reported |
1 |
The candidate lost something the baseline had |
2 |
A path was neither a .zip nor a directory, or could not be read |
There is no flag to disable the exit code. Comparing payloads from two different tools will normally report regressions — each tool emits things the other does not — so a non-zero exit there means "these differ", not "something broke".
Developing
You need a system Python 3.13 or 3.14 on PATH, plus uv.
pyproject.toml sets python-preference = "only-system", so uv will not download an interpreter for
you — it fails with "No interpreter found for Python >=3.13" rather than fetching one. That is
deliberate: uv's bundled CPython ships a libcrypto without the OPENSSL_Applink shim, and on Windows
any TLS handshake — LDAPS, MSSQL TDS — aborts the process. The constraint is not Windows-only, which is
why CI provisions Python explicitly instead of relying on the runner's default.
git clone git@github.com:Mayyhem/openhound-collector-common.git
cd openhound-collector-common
uv sync --group dev
Run the checks
These three are exactly what ci.yml runs, so a green local run means a
green pull request. Unlike a collector's curated subset, the whole test directory runs offline — no lab,
no fixture database. The one test that opens a socket binds a loopback echo server.
uv run ruff check src tests
uv run mypy src/openhound_collector_common
uv run pytest tests -q
Working on this library and a collector at once
Editing here and testing there needs a live link, not a reinstall after every change. Clone the two as
siblings — the path is resolved relative to the collector's pyproject.toml, so the layout is
load-bearing:
~/Desktop/ConfigManBearPig/ a collector
~/Desktop/openhound-collector-common/ this library
Then add this to the collector's pyproject.toml, immediately above [tool.uv]:
[tool.uv.sources]
openhound-collector-common = { path = "../openhound-collector-common", editable = true }
Do not commit that. A committed path redirect breaks every environment without the sibling checkout, starting with the collector's CI, whose runner checks out that repository alone. Keep git from staging it, then sync:
git update-index --skip-worktree pyproject.toml
uv sync --group dev
Two alternatives that look right and are not, both tested: uv pip install -e ../openhound-collector-common
installs a copy, so your edits are invisible and the next uv sync silently reverts it to the
published version; and [sources] in a gitignored uv.toml is rejected outright — "sources is only
applicable in the context of a project".
Releasing
The tag is the release. hatch-vcs derives the package version from it, so there is no number to
bump by hand and nothing to upload manually:
git tag -a v0.1.3 -m "what changed"
git push origin v0.1.3
Pushing a v* tag triggers release.yml, which builds, asserts the
built version matches the tag, and publishes to PyPI through trusted publishing. The job runs in the
pypi GitHub environment, so a required reviewer there turns each release into a deliberate click.
PyPI filenames are immutable — a tag on the wrong commit burns that version number permanently.
Blast radius
Two collectors consume this package, so a change here is never local to one of them. Both pin a capped
range (>=x.y.z,<0.2.0), which means a patch release reaches them on their next lock refresh — not on
publish. Treat any change to a shared signature or output format as breaking for both, even when the
version number says patch.
License
Apache-2.0 (see LICENSE). This code was factored out of the SCCM collector, itself a port of
the Apache-2.0 ConfigManBearPig.ps1, so it inherits those terms.
Metadata
Release files for openhound-collector-common 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openhound_collector_common-0.1.4.tar.gz | 189.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openhound_collector_common-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 298.6 kB
Release files / openhound_collector_common-0.1.4.tar.gz
| Download URL | openhound_collector_common-0.1.4.tar.gz |
|---|---|
| Size | 189.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
050d80e24a5d8ef5cc8faeeed5b058c5a83360557a86f9a4fb5d2ce228e60df4
|
|
BLAKE2b-256 checksum How to use checksums |
6de6fa23148ed5e9945cfb76c50c33239ed9f4d36f72ffd3224720e794cff22b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / openhound_collector_common-0.1.4-py3-none-any.whl
| Download URL | openhound_collector_common-0.1.4-py3-none-any.whl |
|---|---|
| Size | 108.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
90ef26d70a6e96f4818ad0cbf92426a41cac174fc2ae4d0078989ec49be196d9
|
|
BLAKE2b-256 checksum How to use checksums |
2b9a6acbc025e51252abc751198e89e7a4a1eec31077b0471d1346c166ec68b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|