-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
hightower is a Cargo workspace laid out as a hexagonal (ports & adapters) architecture, with one crate per ring. The split is not cosmetic: it is enforced by the compiler, not by code review.
hightower/
├── core/ domain + ports (traits). ZERO OS dependencies.
├── adapters/ driven side: Windows implementations of the ports.
└── cli/ driving side: clap + the composition root.
The pure center: the process model (ProcessInfo, ProcessVerdict,
RiskLevel, ...) and the ports -- traits that describe what the outer rings
must provide (ProcessLister, SignatureVerifier, ProcessKnowledgeRepository,
RiskRule).
Nothing here touches the operating system. Crucially, core does not declare
the windows crate as a dependency, so a stray use windows::... in the
domain simply fails to compile. The boundary that "the domain must not depend
on infrastructure" -- usually a rule you keep in your head or a linter -- is here
a fact the compiler checks.
Why it matters: the risk rules can be unit-tested against fake, in-memory adapters, with no real Windows machine required.
The only place allowed to call the OS. It holds the real implementations of the core ports:
-
ToolHelpProcessLister-- enumerates processes via the Windows ToolHelp32 API (CreateToolhelp32Snapshot,Process32FirstW/NextW,QueryFullProcessImageNameW). -
AuthenticodeVerifier-- checks embedded Authenticode signatures viaWinVerifyTrust. -
EmbeddedKnowledgeRepository-- the embedded known-process database (include_str!+serde_json).
All unsafe lives here, and every unsafe block carries a // SAFETY: comment
explaining the invariant upheld by hand -- enforced at build time by
#![deny(clippy::undocumented_unsafe_blocks)].
The windows crate is a cfg(windows)-gated dependency, so it is only pulled in
on the one platform where the binary actually runs.
Parses the command line (with clap) and acts as the composition root: the
one place that constructs the concrete adapters and injects them into the
domain. This is dependency injection without a framework -- a main() that
builds the pieces and wires them together.
| Pattern | Where | Note |
|---|---|---|
| Ports & adapters (hexagonal) | the crate split | compiler-enforced boundary |
| Strategy | each RiskRule
|
one small struct per heuristic |
| Chain / pipeline | the classifier | runs each rule, aggregates findings |
| Repository | ProcessKnowledgeRepository |
hides where known-process data lives |
Trait objects (Box<dyn ...>) |
classifier ports | dynamic dispatch, chosen for readability over generics |
Classic hexagonal is justified by having multiple real implementations of a port (e.g. a Windows and a Linux scanner). hightower is Windows-only by nature -- ToolHelp32 and Authenticode only exist there -- so there is exactly one real implementation of each port. We keep the ring separation (for testability), but do not add a folder-per-adapter that would mimic the shape of the pattern without its reason.
-
test-coreruns on both Windows and Ubuntu. On Ubuntu the binary does not even build (it needswindows), so a green Ubuntutest-coreis a cheap proof that the domain stayed OS-free. -
build-cli,clippyrun on Windows, where the whole thing exists. -
fmt,auditround out the checks;-D warningsmakes every lint fatal.