An LSA password filter for Active Directory, written in C++. It runs inside LSASS
on a Domain Controller and is consulted before every password set/change, rejecting
passwords that fail policy:
- Compromised passwords - checked against the offline Have I Been Pwned "Pwned Passwords" corpus (the same k-anonymity dataset HIBP exposes online), using a compact Bloom filter so no hash ever leaves the host at runtime.
- Custom complexity rules - length, character classes, keyboard walks (
qwerty,123456), sequences and repeats, and identity terms (account/full name). - Company blacklist - configurable banned terms and exact-match lists.
- Event logging - every rejection is recorded to the Windows Event Log with metadata only (account, rule violated, timestamp). The password is never logged, anywhere.
- GPO-friendly deployment - registry-driven configuration (ADMX template), x64, with install/rollback scripts.
Status: v0.1.0. The validation core (unit-tested in CI), the LSASS shim, the offline Bloom pipeline and the deployment tooling are all in place. Treat it like any new LSASS component: validate on a lab Domain Controller before production.
A password filter runs in the most privileged process on a Domain Controller. A single
bug can lock every user out or crash LSASS and bugcheck the machine. Two principles
drive the architecture:
- The validation logic is pure, portable C++ with no Windows dependency (
pwfilter_core). It is unit-tested off-host (and in CI) so the code that decides "accept/reject" never needsLSASSto be exercised. TheLSASS-resident DLL is a thin, auditable shim. - Fail-safe by default. On any internal error (missing data file, unexpected exception) the filter allows the change and logs the fault, rather than risking a domain-wide lockout. It rejects only on an explicit policy match.
┌─────────────────────────────────────────┐
LSASS (on the DC) │ PasswordFilterDLL (thin shim) │
───────────────────► │ • SEH + secure zeroing │
PasswordFilter(...) │ • UNICODE_STRING ─► std::wstring_view │
│ • Event Log (metadata only) │
└───────────────┬─────────────────────────┘
│ (no windows.h below this line)
┌───────────────▼─────────────────────────┐
│ pwfilter_core (pure, unit-tested) │
│ complexity · blacklist · breach(bloom) │
└──────────────────────────────────────────┘
The DLL is built and tested in CI (GitHub Actions, windows-latest, MSVC v143 + Windows
SDK). To build locally you need the Visual Studio "Desktop development with C++" workload
(or Build Tools) with the Windows 10/11 SDK, plus CMake ≥ 3.21.
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failureThe pure pwfilter_core library and its tests also build on any C++17 compiler (no
Windows headers required), which is what makes the policy logic portable and testable.
Validate on a non-production Domain Controller first - this runs in LSASS.
# 1. Build the offline breach artifact once (Python only) from the HIBP dump:
python scripts\build_bloom.py pwnedpasswords.txt -o breach.bloom --count 1300000000
# 2. Install on a test DC (copies the DLL, sets policy, registers with LSA - merge-safe):
.\deploy\Install-PasswordFilter.ps1 -DllSource .\PasswordFilterDLL.dll
# place breach.bloom in %ProgramData%\PasswordFilter\, then REBOOT
# 3. Exercise it and read the Event Log:
.\deploy\Test-PasswordFilter.ps1 -SamAccountName test.user
# Rollback:
.\deploy\Uninstall-PasswordFilter.ps1Full guides: docs/DEPLOYMENT.md · docs/CONFIG.md ·
docs/ARCHITECTURE.md · SECURITY.md. GPO admins: an
ADMX/ADML template ships in deploy/.
| Path | What |
|---|---|
src/core/ |
Pure C++17 validation logic (no windows.h) - the testable heart |
src/dll/ |
The LSASS-resident shim (the three LSA exports) |
tests/ |
GoogleTest suite over pwfilter_core (run via CTest) |
scripts/build_bloom.py |
Offline builder: HIBP dump → Bloom artifact |
src/tools/pwhibp_check.py |
Admin/dev tool: online HIBP k-anonymity check (out of LSASS) |
deploy/ |
Install/uninstall scripts + ADMX template (GPO) |
docs/ |
Architecture, deployment and configuration guides |
| Capability | Where |
|---|---|
| LSA password filter for Active Directory, in C++ | src/dll/dllmain.cpp (3 LSA exports) + src/core/ (C++17) |
| Blocks compromised passwords, offline breach-list (HIBP) | src/core/breach_bloom.cpp, src/core/bloom.cpp, scripts/build_bloom.py |
| Have I Been Pwned k-anonymity model | src/tools/pwhibp_check.py (online range query); offline corpus is the same dataset |
| Custom complexity rules | src/core/complexity.cpp (length, classes, keyboard walks, sequences, repeats, identity) |
| Company blacklist | src/core/blacklist.cpp + blacklist.txt / company_terms.txt |
| Event logging (metadata only) | src/dll/eventlog.cpp + src/dll/messages.mc |
| GPO-friendly deployment | deploy/*.ps1 + deploy/PasswordFilter.admx / .adml |
| Open-source | this repository, MIT |
- F0 - CMake + CI scaffolding, three targets, smoke test
- F1 - Core: portable SHA-1, Bloom filter, secure zeroing, UTF-16→UTF-8
- F2 - Core: complexity rules, blacklist, policy evaluator
- F3 - Core: breach checker (SHA-1 → Bloom)
- F4 - Python Bloom builder + sample data + cross-language format test
- F5 - LSASS shim: fail-safe
dllmain, registry/file config, Event Log - F6 - Deployment scripts, ADMX, full documentation
- F7 - Optional online HIBP k-anonymity checker (admin tool, out of LSASS)
This is a defensive tool. See SECURITY.md for the threat model, the
LSASS-safety rules it follows, and recovery steps (DSRM) should a filter ever need to be
removed. Test only in a lab VM with a non-production Domain Controller.