A modular, open-source host auditing engine for defensive security use: detecting supply-chain compromise, unauthorized system-level telemetry, insecure UEFI/platform configuration, hidden persistence, and coprocessor/ out-of-band management exposure on your own machine.
Anchorroot is a Python 3.10+ CLI, installed as the anchorroot console
script. Every auditor is independent, degrades gracefully when a dependency or
elevated privilege is unavailable, and reports through one shared JSON +
CLI scoring engine.
pyproject.toml PEP 621 packaging: [project.scripts] installs the `anchorroot` command
requirements.txt pip-install convenience mirror of pyproject's optional-dependencies
anchorroot/
__init__.py exposes __version__ (sourced from config.py)
config.py global constants: version, app name/aliases, default paths, severity weights
cli.py argparse CLI: `audit` (default) and `baseline save` subcommands
mocks.py offline fixture registry for --mock/--dry-run (TPM, CHIPSEC, ME, efivars, ...)
core/
base.py BaseAuditor interface every auditor implements
models.py Finding / ModuleResult / AuditReport dataclasses, Severity enum
reporter.py JSON export engine + rich/plain-text terminal UI
diff.py State Baseline & Differential Engine (TPM PCRs / root CAs / kernel modules / EFI boot vars)
utils.py subprocess wrapper, privilege detection, platform helpers, mock-mode toggle
modules/
uefi_platform.py Secure Boot, SPI write-protect, SMM lock (CHIPSEC + fallbacks)
firmware_integrity.py Firmware image extraction, hashing, baseline diff
network.py Passive outbound traffic + DNS-bypass detection
persistence.py Startup items / kernel modules / scheduled tasks
tpm_auditor.py TPM 2.0 PCR readout + baseline drift detection
dma_auditor.py IOMMU (VT-d/AMD-Vi) / Kernel DMA Protection
me_auditor.py Intel ME / AMD PSP state + AMT out-of-band probe
ca_auditor.py Root CA store scrutiny + UEFI dbx inspection
utils/
logger.py centralized logging, every line tagged `[anchorroot]`
security.py privilege-aware remediation messages + output sanitization
resources/
default_whitelist.yaml sample network whitelist
tests/
conftest.py shared fixtures: mock-mode auto-reset, TPM/dmesg/EFI-variable sample data
test_auditors.py cross-cutting tests over every registered auditor
test_*.py per-module parsing/behavior tests
reports/ JSON report output (created on first run)
baselines/ saved TPM/firmware baselines, and the combined state baseline
Every auditor returns a ModuleResult containing zero or more Findings,
each tagged CRITICAL / WARNING / INFO. The reporting engine
(anchorroot/core/reporter.py) aggregates these into a JSON file and a
CLI summary table (via rich if installed, plain text otherwise) with an
overall weighted risk score.
Note on module identifiers: each auditor's
.name(shown in the CLI and JSON report) matches its file:uefi_platform,firmware_integrity,network,persistence,tpm,dma,me,ca.
| Check | Needs root/Administrator? |
|---|---|
| CHIPSEC SPI write-protect / SMM lock bit checks | Yes |
| Secure Boot state / PK/KEK/db/dbx read | No (read-only OS query) |
| Firmware image extraction & hashing | No (operates on a file you already dumped) |
Dumping firmware with flashrom in the first place |
Yes (not done by Anchorroot) |
| scapy packet capture (network module) | Yes (falls back to unprivileged psutil polling) |
| Full driver/service enumeration | Partial -- some fields hidden unprivileged |
TPM PCR read (tpm2_pcrread) |
Usually no (device-group permissions) |
| IOMMU/dmesg check | Partial -- dmesg may be root-restricted; falls back to journalctl |
| Intel ME mode / AMD PSP driver presence | No |
| AMT port probe | No |
| CA store enumeration / dbx read | No (Windows dbx byte read needs SeSystemEnvironmentPrivilege, implied by Administrator) |
Auditors never abort the whole run because one check needs elevation --
that check is skipped or degraded and clearly labeled as such in its
finding, with a remediation hint (e.g. "re-run elevated"). Where a command
is known to require elevation, anchorroot.utils.security.run_privileged
skips attempting it entirely when unprivileged and returns a specific
message (what's missing and how to fix it) instead of a raw OS permission
error; anchorroot.utils.security.describe_missing_binary does the same
for a missing external tool, naming the package to install where known.
Every check can run against synthetic data instead of real hardware/OS
state, via --mock (alias --dry-run) on either subcommand:
anchorroot --all --mock
anchorroot baseline save --mockIn mock mode, which()/run_command()/run_powershell()/is_elevated()
are all redirected through the fixture registry in anchorroot/mocks.py
-- no real subprocess is ever invoked, no root/Administrator is required,
and no TPM, CHIPSEC driver, or ME/PSP hardware needs to be present. This is
the single interception point in anchorroot/core/utils.py; individual
modules don't know mock mode exists. The fixtures are a deliberate mix of
clean and flagged results (an unsigned kernel module, a disabled SMM lock
bit, a Superfish-style root CA, a listening AMT port) so a --mock run
exercises -- and demonstrates -- the full severity range, not just the
all-clear path. Useful for demos, CI smoke tests, and developing/testing
on hardware that doesn't have a TPM or CHIPSEC-supported chipset.
Beyond the per-artifact TPM/firmware baselines below, Anchorroot can
snapshot four broader trust-chain indicators -- TPM PCRs, installed root
CAs, loaded kernel modules, and EFI boot manager variables -- and flag
anything that changed since a saved baseline, with high-visibility
CRITICAL/WARNING findings appended to the normal report as a
baseline_diff module:
# Save a baseline once, on a known-good system (default path: baselines/baseline.json)
anchorroot baseline save
# On later runs, compare against it
anchorroot audit --all --diff
# or a specific baseline path:
anchorroot audit --all --diff baselines/baseline.jsonEach category is only compared when both the baseline and the current
run actually collected data for it -- a collection failure (no TPM this
boot, running unprivileged, non-UEFI system) is reported as a skipped
comparison, never as "everything in the baseline was removed". See
anchorroot/core/diff.py for the full collection/comparison logic.
python -m venv .venv
# Windows: .venv\Scripts\activate Linux/macOS: source .venv/bin/activate
pip install -e ".[full,dev]"This installs the anchorroot command into the venv (via pyproject.toml's
[project.scripts]). python -m anchorroot works identically without
installing, if you'd rather not.
Run everything (uses --all implicitly when no module flags are given):
anchorrootRun specific modules:
anchorroot --audit-uefi --audit-tpm --audit-caRun elevated for the checks that need it (recommended for a full audit):
# Linux/macOS
sudo anchorroot --all
# Windows (from an elevated PowerShell/terminal)
anchorroot --allFirmware integrity, against a dumped image:
# 1. Dump SPI flash (requires root + a supported programmer)
sudo flashrom -p internal -r image.bin
# 2. Save a trusted baseline once, on a known-good system
anchorroot --save-firmware-baseline baselines/firmware.json --firmware-image image.bin
# 3. On later runs, diff against it
anchorroot --audit-firmware --firmware-image image.bin --firmware-baseline baselines/firmware.jsonTPM PCR baseline, similarly:
anchorroot --save-tpm-baseline baselines/tpm.json
anchorroot --audit-tpm --tpm-baseline baselines/tpm.jsonPassive network capture for 60 seconds against a custom whitelist:
anchorroot --audit-network --network-duration 60 --network-whitelist my_whitelist.yamlCA store check with a Mozilla-derived fingerprint reference list and a specific dbx revocation-hash expectation:
anchorroot --audit-ca --mozilla-ca-list mozilla_fingerprints.json --expected-dbx-hashes advisory_hashes.jsonFull flag reference:
anchorroot --help
anchorroot baseline --helpGate CI/scripts on findings of a given severity:
anchorroot --all --fail-on critical # exit code 1 if any CRITICAL finding existsEvery run writes a JSON report (default reports/anchorroot_report.json)
with the full finding list and metadata, and (unless --no-table) prints a
CLI summary table like:
Anchorroot - Audit Summary
Host: mybox OS: Linux 6.8.0 (...)
Risk Score: 145 CRITICAL: 1 WARNING: 6 INFO: 12
## uefi_platform
[CRITICAL] Secure Boot disabled
UEFI Secure Boot is disabled, allowing unsigned bootloaders...
...
Progress and diagnostic messages (running: X ..., elevation notices,
errors) go to stderr through the centralized logger
(anchorroot/utils/logger.py), each line tagged [anchorroot] --
easy to grep for or filter out when piping the CLI summary elsewhere.
None of these are pip-installable; install via your OS package manager.
Every auditor works without them, at reduced fidelity (see each auditor's
INFO-level "not available" findings for exactly what was skipped).
| Tool | Used by | Purpose |
|---|---|---|
| CHIPSEC | uefi_platform | Register-level SPI/SMM checks (needs its kernel driver) |
mokutil |
uefi_platform | Linux Secure Boot state fallback |
UEFITool (UEFIExtract) |
firmware_integrity | Preferred firmware image extractor |
binwalk |
firmware_integrity | Fallback extractor |
flashrom |
(you, manually) | Dumping the SPI image Anchorroot then analyzes |
osquery (osqueryi) |
persistence | Cross-platform startup/task/module queries |
tpm2-tools (tpm2_pcrread) |
tpm | PCR readout, Linux and Windows (via TBS) |
| intelmetool | me | Intel ME HAP-bit/mode detail on Linux |
openssl (CLI) |
ca | CA store enumeration on Linux |
pip install -e ".[dev]"
pytest -qtests/conftest.py provides shared fixtures (mock-mode auto-reset between
every test, sample TPM/dmesg/EFI-variable payloads);
tests/test_auditors.py sweeps every registered auditor for the shared
contract (subclasses BaseAuditor, has a stable name, runs cleanly under
--mock); everything else targets the pure-parsing logic in each module
(PCR output parsing, EFI signature list decoding, IOMMU dmesg heuristics,
path classification, CHIPSEC output classification, etc.), the --mock
fixture registry, the diff engine's snapshot comparison logic, output
sanitization and privilege-remediation messaging, and a handful of
CLI-level end-to-end runs in --mock mode -- none of it requires root, a
TPM, or any of the external tools above.
Every auditor subclasses anchorroot.core.base.BaseAuditor, sets name
/ description / requires_root, and implements audit(), calling
self.add_finding(title, severity, description, remediation=..., **metadata)
for each observation. The base class handles privilege gating, timing, and
turning an unhandled exception into a recorded ModuleResult.error instead
of crashing the whole run -- a new auditor needs no changes anywhere else
except registering itself in anchorroot/modules/__init__.py and adding
a --audit-* flag in anchorroot/cli.py.
persistence's path classifier does not resolve legacy DOS 8.3 short paths (C:\PROGRA~2\...) to their long form, so a legitimate vendor tool installed under a short-path alias may show as aWARNINGneeding manual review rather than being auto-cleared.cadoes not ship a bundled Mozilla CA list or specific BlackLotus/Baton-Drop dbx revocation hashes -- supply them yourself via--mozilla-ca-list/--expected-dbx-hashesfrom the authoritative source, since bundling a point-in-time copy would go stale and become actively misleading.mecannot read the Intel HAP bit / ME operating mode on Windows without Intel's own MEInfo tool (not automated here); AMD PSP detail beyond driver presence is Linux-only in this build.network's DNS-bypass detection (flagging a destination IP that was never preceded by a local DNS lookup) requires the scapy/Npcap capture path; the unprivilegedpsutilfallback can only see connection state, not DNS traffic.core.diff's EFI boot variable collector uses a coarse per-entry hash (viabcdedit /enum firmwareon Windows, raw efivarfs bytes on Linux) rather than fully parsingUEFI_LOAD_OPTIONstructures -- it reliably detects that an entry was added/removed/changed, not what specific field changed within it; cross-reference withefibootmgr/bcdeditdirectly to see the actual entry contents.--mockintercepts every subprocess-based check and the direct EFI variable reads used for Secure Boot keys and dbx, but not anything a module might read directly from the filesystem/network outside those paths -- if you add a new auditor that reads OS state some other way, give it a mock-mode branch too (seeuefi_platform._read_efivarorme_auditor._probe_amt_portsfor the pattern).- The repository checkout folder on disk is not required to be named
anchorroot-- the package name (importable asanchorroot, and installed as theanchorrootcommand) is independent of whatever directory you cloned it into.