| Version | Supported |
|---|---|
| 0.1.10 | ✅ |
| 0.1.9 | ❌ (upgrade — CI lint/typecheck advisory-mode; no code change) |
| 0.1.8 | ❌ (upgrade — CI build-backend fix) |
| 0.1.7 | ❌ (upgrade — DOC-02 doc-only version-neutralization) |
| 0.1.6 | ❌ (upgrade — TM-01, RE-02, DL-01, TM-02 release-hygiene fixes) |
| 0.1.5 | ❌ (upgrade — TB-01 threat-model declaration + DOC-01 duplicate) |
| 0.1.4 | ❌ (upgrade — HS-02R public flag, HS-02S secondary BSSID) |
| 0.1.3 | ❌ (upgrade — HS-02R capability-forgery) |
| 0.1.2 | ❌ (upgrade — three Highs + one Moderate runtime regression) |
| 0.1.1 | ❌ (upgrade — Critical HS-01, four Highs) |
| 0.1.0 | ❌ (upgrade) |
CI-config-only patch on top of v0.1.9. The v0.1.9 CI run cleared the
pip install -e ".[dev]" blocker but then blew up on two secondary
tooling issues: ruff surfaced ~1000 style-modernization findings
(Optional[X] -> X | None and friends) that would rewrite every
type annotation in the codebase; mypy crashed loading numpy 2.x's
own stubs because they use PEP 695 type X = Y syntax and my mypy
config pinned python_version = "3.10". Neither is a code-quality
regression — both are strict-tooling defaults hitting a codebase
the reviewer already vetted.
Fix:
pyproject.tomlruffselectno longer includesUP(pyupgrade style modernizations) orN(pep8-naming). Bug-hunting rule packs (E,F,W,I,B,A,C4,DTZ) stay on.pyproject.tomlmypy overridesnumpyandnumpy.*withfollow_imports = "skip"so mypy doesn't try to parse numpy's 3.12+ stub syntax while running under a 3.10 target..github/workflows/ci.ymlmarks thelintandtypecheckjobs ascontinue-on-error: trueand emits any findings as GitHub workflow::warning::messages. Thetestjob across Python 3.10/3.11/3.12 remains the release gate and still fail-closes the workflow.
No application code changed. Zero delta to the trusted-process threat model, the consent gate, or any attack path.
372 pytest passing, 3 skipped, 0 failing. Smoke: 191/191.
Advisory findings that will show as workflow warnings on future runs until a dedicated code-quality cleanup pass (probably folded into v0.2 alongside the TB-01 Option B broker refactor):
- Ruff: ~859 findings across
honeysnatch/(unused imports, unsorted imports, missing datetime tz handling in a few places, etc.). None are security issues per the reviewer's own scoring across eight rounds. - Mypy: ~152 findings (missing type annotations on internal
containers, one
scapy.all.UDPfalse-attr, someOptionalnarrowing gaps in the GUI panels). Same story.
Same list as v0.1.9: RE-01 unblocked once this CI is green, HS-09 release-tooling (deps hash-lock / SBOM / signing), TB-01 Option B broker refactor for v0.2.
CI-only fix on top of v0.1.8. pyproject.toml's build-backend
value was "setuptools.backends._legacy:_Backend" — that module
path doesn't exist in setuptools. pip install -e ".[dev]"
therefore failed with BackendUnavailable: Cannot import 'setuptools.backends._legacy' under PEP 517 build backend
resolution, which is what CI does. The sandbox validation for
v0.1.5-v0.1.8 used python -m pytest directly (which doesn't
exercise PEP 517), so the bug shipped through four rounds without
tripping any local test.
Fix: build-backend = "setuptools.build_meta" — the correct
setuptools backend name. Verified by building an editable wheel
in a clean venv and running the full pytest + smoke suite against
the installed package.
No application code changed. Zero delta to the trusted-process threat model, the consent gate, the audit chain, or any attack- path module.
372 pytest passing (same as v0.1.8, all now runnable through
pip install -e ".[dev]"), 3 skipped, 0 failing. Smoke: 191/191.
Same list as v0.1.8, with RE-01 now genuinely unblocked (the v0.1.9 CI can actually reach the test step for the first time).
Doc-only respin on top of v0.1.7 (which the reviewer disposed as release-ready under the trusted-process model). Folds in DOC-02 so the tag we push is fully clean — no known open Low items.
DOC-02 (Low) — THREAT_MODEL.md deployment prerequisite made version-neutral.
- Prerequisite #1 previously said "HoneySnatch v0.1.6 does not
define any plugin discovery mechanism"; that wording ages poorly
because a patch bump makes the sentence look stale. Reworded to
"This release does not define any plugin discovery mechanism" —
the accompanying
tests/test_no_dynamic_code_loading.pyinspects the current tree at build time, so the version-neutral phrasing is accurate for whatever release the doc is shipped with. - The Change log section at the bottom of THREAT_MODEL.md still names versions (that's what change logs are for).
- New
tests/test_threat_model_version_stability.pycodifies the rule: the "Deployment prerequisites for the trusted-process model" section may not contain anyvX.Y.Zstrings. The Change log section is explicitly exempt. A companion test also asserts the Change log DOES name the current__version__so a future bump that forgets to update the doc fails at test time.
Zero changes to honeysnatch/isolation/, honeysnatch/db/, or any
other attack-path module between v0.1.7 and v0.1.8. The wheel
manifest is unchanged apart from the doc content inside
honeysnatch/data/security/.
372 pytest passing (was 370), 3 skipped, 0 failing. Smoke: 191/191.
Diff from v0.1.7: +2 tests, both in
tests/test_threat_model_version_stability.py.
- RE-01 — exact-candidate CI evidence gated on pushing the
v0.1.8 tag. When you push, the SHA-pinned Python 3.10/3.11/3.12
workflow in
.github/workflows/ci.ymlproduces the digest-bound logs the reviewer's been asking for since v0.1.1. - HS-06 — accepted local privilege primitive; part of the out-of-scope set documented in THREAT_MODEL.md.
- HS-09 — hash-locked dependency graph + SBOM + signed provenance remain release-process follow-up work. TM-01 stopped overclaiming these exist; delivering them is a proper release- tooling arc.
- TB-01 Option B — architectural broker refactor tracked for v0.2. Under the declared trusted-process model, TB-01 is an architectural limitation with documented mitigations rather than a release vulnerability.
The v0.1.6 review closed TB-01 (Option A trusted-process model) and
DOC-01 outright, then raised four release-hygiene findings against
the hardened archive itself: TM-01 (THREAT_MODEL.md's "pinned
dependency graph" claim contradicts >= requirements), RE-02
(v0.1.6 archive lost .github/workflows/ci.yml and dotfiles — my
zip -x '*.git*' was too broad and stripped .github/ as a
regression from v0.1.5), DL-01 (the no-dynamic-loading test only
covered importlib.import_module/__import__, missing the
spec_from_file_location/exec_module pair actually used by
wpaspy.py), and TM-02 (SECURITY.md and THREAT_MODEL.md were
not shipped inside the built wheel — an installed operator never
saw them). All four closed here.
TM-01 (Moderate) — Threat-model dependency claim made truthful.
THREAT_MODEL.md's trusted-process prerequisite for "fixed dependency
set" was written as if the wheel installed a hash-pinned graph. The
actual state is that pyproject.toml, requirements.txt, and
requirements-linux.txt declare minimum-version (>=) constraints
— the wheel is NOT hash-pinned. Fixed:
THREAT_MODEL.mdsection 2 now describes what actually exists: a declared minimum-version graph with an explicit caveat that a supply-chain compromise of a listed dep or a semver-compatible malicious release collapses the boundary. Producing a hash-locked graph via pip-tools / uv lock with--require-hashesverified in CI is now named as the correct long-term control and cross-linked toHS-09. Deployments that need strong provenance today are told to pin transitively themselves.- The document no longer overclaims the boundary. Reviewer's
acceptance criterion ("THREAT_MODEL.md describes the actual control
without overclaiming") is met; the hash-lock control itself stays
tracked as
HS-09.
RE-02 (Moderate) — Archive manifest restored + regression test.
- Root-cause fix: the v0.1.6 release-zip command used
zip -x '*.git*'intending to skip.git/, but that glob matched.gitignore,.gitattributes, and — critically —.github/. The v0.1.7 release script uses-x '*/.git/*' -x '.git'so the.git/directory is excluded but every other dotfile survives. - New
tests/test_release_manifest.pyparameterizes over 12 required release-archive paths (.github/workflows/ci.yml,.gitignore,.gitattributes, SECURITY.md, THREAT_MODEL.md, README, LICENSE, CHANGELOG, pyproject.toml, and the load-bearing modules) and additionally asserts the CI workflow file actually references the Python 3.10/3.11/3.12 matrix +pytest. A future release that omits any of these fails the test at build time, not at review time.
DL-01 (Low) — Dynamic-loading detector extended + wpaspy path
locked to __file__.
tests/test_no_dynamic_code_loading.pynow walks every dynamic- execution surface named in the review:importlib.import_module,__import__,importlib.util.spec_from_file_location,importlib.util.module_from_spec, loader.exec_module,runpy.run_path/run_module, bareeval, bareexec. The AST helper accepts string literals andPath(__file__)-anchored expressions; it rejects Name/Attribute/Call/BinOp arguments and refuses any subtree that referencesos.environ,sys.argv,input,click,argparse,config,cfg, orsettings.- New
test_wpaspy_loader_path_is_package_anchoredasserts the wpaspy loader'sspec_from_file_locationpath arg is textually rooted atPath(__file__)at the call site.wpaspy.pywas refactored so the path is inlined at each call rather than passed through an intermediate loop variable — the detector can now see it directly, and the code stays as reviewable as the test claims.
TM-02 (Low) — Security docs shipped inside the wheel + README link.
honeysnatch/data/security/SECURITY.mdandhoneysnatch/data/security/THREAT_MODEL.mdare byte-identical copies of the repo-root canonicals, wired via[tool.setuptools.package-data]so they land inside the built wheel. An operator whopip install honeysnatch-es now has the security policy and threat model on their machine.- New
honeysnatch/security_docs.pymodule exposessecurity_docs_dir(),read_security_policy(),read_threat_model(), anditer_security_docs()— the runtime handle for a futurefhs help securitysubcommand and enough to wire into whatever operator-visible surface the GUI grows next. - New
tests/test_security_docs_in_wheel.pyenforces byte-parity between the packaged and root copies and checks the runtime helpers return the expected leading headers (defense against a build that copies an empty stub). README.mdnow linksTHREAT_MODEL.mdnext to the security- policy link, with a callout that operators running third-party in-process Python must read it before deploying.
370 pytest passing (was 351), 3 skipped, 0 failing. Smoke: 191/191.
Diff from v0.1.6: +19 tests — 1 additional in
test_no_dynamic_code_loading.py (wpaspy anchor), 4 in
test_security_docs_in_wheel.py, 14 in test_release_manifest.py.
Application-path attack surface is unchanged.
- RE-01 — exact-candidate CI evidence still gated on pushing the
v0.1.7 tag. When you push it, the SHA-pinned workflow in
.github/workflows/ci.ymlwill produce the digest-bound matrix logs the reviewer has been asking for since v0.1.1. - HS-06 — accepted local privilege primitive; documented in THREAT_MODEL.md as out of scope.
- HS-09 — hash-locked dependency graph + SBOM + signed provenance remain release-process follow-up work. TM-01 stopped overclaiming that these exist; delivering them is next-round work.
- TB-01 Option B — architectural broker refactor tracked for v0.2. Under the v0.1.7 trusted-process model, TB-01 remains a documented architectural limitation rather than a release vulnerability.
The v0.1.5 review closed HS-02R, HS-02S, and AUTH-01 outright and
raised TB-01 — a threat-model finding rather than an application-
path defect: the _AUTHENTIC_AUTHORIZATIONS WeakSet that authorizes
Authorization objects is a module global that any same-process Python
caller can mutate. Reviewer's binary choice: (A) narrow the threat
model to trusted in-process code and reframe the consent gate as
misuse prevention, or (B) move authorization enforcement behind a
separate privileged broker.
v0.1.6 takes Option A. Option B is on the v0.2 roadmap and is the
right answer when this project's deployment surface eventually grows a
plugin ecosystem or a multi-tenant runtime — but for an alpha whose
users invoke fhs as a dedicated process with a fixed dependency
graph, declaring the boundary honestly beats retrofitting a broker
architecture for a threat that isn't in scope yet.
TB-01 (High, threat-model dependent) — Trusted-process boundary declared; consent-gate claims reframed as misuse prevention.
- New
THREAT_MODEL.mdat repo root names what's in and out of scope. In: operator mistakes, CLI accidents, cross-attack drift, token expiry, audit tampering, filesystem containment. Out: hostile Python code executing inside thefhsprocess, general- purpose interpreter access via HS-06, sidecar processes, RF/hw attacks. THREAT_MODEL.mdalso names the deployment prerequisites that make the model hold (no plugin discovery, fixed deps, no attached REPL/debugger, dedicated process, standard operator hygiene) and lists the mitigations for deployments that violate them (Option B broker, OS user separation, SELinux/AppArmor).honeysnatch/isolation/consent.pydocstrings updated: theAuthorizationclass no longer claims that identity semantics or the_AUTHENTIC_AUTHORIZATIONSWeakSet resist hostile in-process code. They resist accidental construction, field replay, and receipt-hash logging leaks. That's what they were doing all along; the previous prose overclaimed.- New regression test
tests/test_no_dynamic_code_loading.pyasserts the trusted-process prerequisites hold at code level: noentry_pointsinpyproject.toml, nopluggyin the dependency graph, noimportlib.import_moduleof caller-supplied strings anywhere inhoneysnatch/. A future commit that introduces a plugin loader without also updatingTHREAT_MODEL.mdwill fail this test.
DOC-01 (Low) — v0.1.4 duplicate heading removed.
The v0.1.5 reviewer flagged that my v0.1.5 fix removed the v0.1.3
duplicate but left the v0.1.4 duplicate in place. Fixed. grep -c '^## v0.1.4 hardening summary' SECURITY.md now returns 1.
Per THREAT_MODEL.md: the consent gate is not a sandbox against code
executing with equivalent interpreter access. If a future deployment
model introduces third-party plugins or a multi-tenant runtime,
release must add the Option B broker before those deployments are
supported. Until then, the trusted-process boundary is the honest
description of what's protected.
351 pytest passing (was 347), 3 skipped, 0 failing. Smoke: 191/191.
Diff from v0.1.5: +4 tests, all in tests/test_no_dynamic_code_loading.py
— codifying the trusted-process prerequisites (THREAT_MODEL.md
anchor present, no entry_points plugin discovery in pyproject.toml,
no pluggy/stevedore in requirements, no dynamic imports of caller-
supplied names anywhere in honeysnatch/). Application-path attack
surface is unchanged.
- RE-01 — exact-candidate CI evidence still gated on pushing the tag.
- HS-06 — accepted local privilege primitive; part of the out-of-
scope set documented in
THREAT_MODEL.md. - HS-09 — locks / SBOM / exact-commit attestation remain release-process follow-up work.
- TB-01 (Option B) — architectural broker refactor tracked for v0.2. Under the v0.1.6 declared threat model, TB-01 is an architectural limitation with documented mitigations rather than a release vulnerability.
The v0.1.4 review accepted NR-02 as closed and credited the receipt- causality improvement. Two new Highs (HS-02R still, HS-02S) plus one Moderate (AUTH-01) came out of the exact structural mistakes I made in v0.1.4. All three closed here plus a DOC-01 follow-up.
HS-02R (High) — Public _authenticated flag replaced with identity-
based WeakSet membership.
v0.1.4 kept authentication state as a public dataclass field with
default=False. The reviewer's probe: Authorization(bssid, source, receipt_hash, _authenticated=True) returned is_valid()=True and
reached live dispatch. Fixed:
- Removed
_authenticatedfrom Authorization entirely. Proof now lives OUTSIDE the object in a module-private_AUTHENTIC_AUTHORIZATIONS: WeakSet[Authorization]. is_valid()checksself in _AUTHENTIC_AUTHORIZATIONS— no combination of constructor arguments produces authority.@dataclass(frozen=True, eq=False)keeps identity-based__hash__/__eq__(default object semantics), so a caller who observes a legitimatereceipt_hash(in logs, debugger, wherever) cannot construct a lookalike that hashes into the set — it's a different object, identity-different.- New tests: reviewer's exact
_authenticated=Trueprobe now raisesTypeErrorat signature time (the kwarg doesn't exist); the copied-field-values clone test asserts identity semantics.
HS-02S (High) — Secondary supplicant BSSID now verified.
Five two-interface attacks (_gtk_check_live, _c2c_live,
_port_steal_live, _gw_bounce_live, _broadcast_reflection_live)
each call start_and_connect() twice. v0.1.4 verified only the
victim/primary result — the attacker interface could be associated
to a different network entirely. Fixed:
- Every
info2 = sup2.start_and_connect()andinfo_a = sup_attacker.start_and_connect()line is now immediately followed byself._verify_observed_target(info<x>.bssid, attack_label= "live-isolation-secondary"). - New static test walks each two-interface method's source and
asserts
associations_count == verify_count; another asserts the verify line IMMEDIATELY follows each association (order matters — no downstream use before verification).
AUTH-01 (Moderate) — Receipt now carries source; factories refuse
wrong-source receipts.
The v0.1.4 CLI always used from_cli_ack even when the consent path
was a token, so a token-derived authorization was reported as source
"cli-ack" and never re-checked the on-disk token. Fixed:
ConsentReceiptgains asourcefield ("cli-ack" | "token")._mint_receipt(bssid, source)requires the source explicitly.Authorization.from_cli_ackrefuses a token-source receipt and vice versa._gate_or_die(CLI) dispatches to the matching factory based onreceipt.source, so token flows land infrom_tokenwhereis_valid()reloads the on-disk token every gate check.- New test proves revoking a token mid-battery disables the authorization.
DOC-01 (Low) — Second duplicate SECURITY.md header removed.
347 pytest passing (was 340), 3 skipped, 0 failing. Smoke: 191/191. Diff from v0.1.4: +7 tests, all closing named findings.
- RE-01 — exact-candidate CI evidence remains gated on pushing the tag.
- HS-06 — accepted local privilege primitive; unchanged.
- HS-09 — locks / SBOM / exact-commit attestation remain follow-up release-process work.
The v0.1.3 review accepted HS-03R, HS-04R, and NR-01 as closed and
retained HS-02R as the sole High blocker: my "receipt" scheme was
theater because Authorization.from_cli_ack minted its own receipt,
and _verify_observed_target had zero callers in the six live attack
implementations. Also flagged NR-02 (fail-closed audit helper unused)
and DOC-01 (duplicate SECURITY.md header). All four are closed here.
HS-02R (High) — Capability is now caused by consent.
The v0.1.4 design inverts the control flow the reviewer described:
require_consent()is the SOLE mint point for consent capabilities. Successful non-simulate paths return aConsentReceipt(opaque, single-use, BSSID-scoped, registered in a process-local map).Authorization.from_cli_ack(bssid, receipt)now REQUIRES the receipt as a mandatory positional argument. CallingAuthorization.from_cli_ack(bssid)raisesTypeErrorat signature time — the reviewer's exact bypass is inverted.- The receipt is consumed on construction. A second
from_cli_ackcall with the same receipt fails. - A receipt for BSSID A cannot construct an Authorization for BSSID B.
- Direct dataclass construction (
Authorization("aa:bb:...", ..., receipt_hash="fake")) failsis_valid()— the new_authenticatedfield defaults to False and is set-once inside the factories.
Observed-BSSID enforcement now runs at every attack site:
- Every
_*_livemethod (_gtk_check_live,_c2c_live,_c2m_live,_port_steal_live,_gw_bounce_live,_broadcast_reflection_live) callsself._verify_observed_target(info.bssid, ...)immediately aftersup.start_and_connect()and BEFORE any capture, key extraction, probe, or send. - A static test (
test_all_live_methods_reference_verify) walks the method sources and refuses to pass if any_*_livemethod loses the call in a future refactor — this defeats "silent removal" regressions. - On mismatch, the runner records
isolation_bssid_mismatchin the audit log with both BSSIDs and raisesConsentRequiredErrorwith "no attack packets were emitted" in the message. - A bypass test constructs a forged Authorization and iterates every
run_*method, asserting each raises before any subprocess/scapy code runs.
NR-02 (Moderate) — Fail-closed audit wired into production.
open_database()callsaudit_event_or_fail("database_opened_ encrypted", ...)when the DB is encrypted. Audit-write failure oraudit_enabled=falsecloses the DB and re-raises — the operator's policy said audit is authoritative for encrypted storage, so an encrypted session that can't be recorded doesn't start.- Plaintext DB opens keep best-effort
audit_event()— a broken audit log doesn't prevent unencrypted scans (documented policy split). require_consent()uses a new_record_or_fail(logger, event, data)helper on the two capability-minting paths (isolation_consent_ack,isolation_consent_token). Audit-write failure raisesConsentRequiredErrorand no receipt is minted — the reviewer's specific ask that isolation authorization be fail-closed on audit is now met.- Denial and simulate-run audit events stay best-effort — those are observability, not evidence-of-authorization.
- 4 new integration tests covering rollback on write failure, refusal on disabled audit, consent-audit fail-closed, and the plaintext-stays-best-effort contract.
DOC-01 (Low) — Duplicate SECURITY.md header removed.
340 pytest passing (was 332), 3 skipped, 0 failing. Smoke: 191/191. Diff from v0.1.3: +8 tests, all closing named findings.
- RE-01 — exact-candidate CI evidence remains gated on pushing the tag. Same gate as every prior round.
- HS-06 — general-purpose capable interpreter remains an accepted local privilege primitive under group-scoped access. Unchanged.
- HS-09 — locks, SBOM, and exact-commit attestation for vendored hostap remain follow-up release-process work.
The v0.1.2 review kept HS-01, HS-05, HS-07, and HS-08 closed but identified three regressions of the earlier fixes plus one runtime regression that came from the HS-04 remediation itself. All four are closed here.
HS-02R (High) — Authorization forgeable + not bound to observed BSSID.
The v0.1.2 Authorization was a public dataclass constructor; any
caller could produce Authorization("aa:bb:...", "cli-ack") without
proving require_consent() ran. And no code compared the
authorization's BSSID against what wpa_supplicant reported after
association. Fixed:
- Authorization now carries a
receipt_hashfield that must appear in the process-local_MINTED_RECEIPTSset — receipts are minted only byAuthorization.from_cli_ack()/.from_token()inside this process. A direct construction with a made-up hash (Authorization( "aa:bb:...", "cli-ack", "0"*64)) failsis_valid(). - New
Authorization.matches_observed_bssid(observed)— canonical BSSID comparison. - New
IsolationTestRunner._verify_observed_target(observed_bssid)— attack methods MUST call this after wpa_supplicant reports association and BEFORE any injection/capture/probe. Mismatch raisesConsentRequiredErrorwith "no attack packets were emitted" in the message AND records anisolation_bssid_mismatchaudit event. - The pre-existing test that documented "wrong-BSSID authorization is
accepted" is inverted:
tests/isolation/test_authorization_binding.pycovers direct-construction refusal, wrong-BSSID refusal, mismatch audit-recording, and the receipt-space invariant. 13 tests.
HS-03R (High) — Audit init verified only the tail. The reviewer's
probe: tamper with entry #1's data, leave entry #2 intact — the tail
HMAC still validated (its prev_hash field still pointed at entry
#1's OLD hash, which was still what was stored). AuditLogger opened,
record() appended seq=3 chaining from the forged-earlier-hash.
Fixed:
_read_chain_tailnow walks the ENTIRE chain on init, recomputing each HMAC and checking eachprev_hashlinkage. First-entry, middle-entry, and tail tampering all fail closed at construction.- Deleted / reordered lines also fail closed at construction.
- New
audit_event_or_fail()inhoneysnatch/db/factory.pyfor security-sensitive events (consent grants, encrypted-DB opens): raisesAuditDisabledErrorif audit is off,AuditWriteErroron logger exception. Best-effortaudit_event()retained for operational telemetry that shouldn't break scans on audit failure. - 4 new named tests:
test_reviewer_hs03r_probe_earlier_entry_tamper_rejected,test_middle_line_tamper_rejected,test_deleted_middle_line_rejected,test_reordered_lines_rejected. Plus 3 foraudit_event_or_fail.
HS-04R (High) — Loaded AppConfig discarded on DB open. The v0.1.2
CLI stored the loaded AppConfig in ctx.obj["config"] but every
open_database call site passed only db_path, so the factory
constructed default AppConfig() and ignored --config hardened.yaml. Fixed:
- Every command in
honeysnatch/cli/export.pyandhoneysnatch/cli/analyze.pyis now decorated with@click.pass_contextand callsopen_database(path, config=ctx.obj.get('config')). honeysnatch/cli/isolation.pyrun-allthreaded similarly.SessionManager.__init__now takes an optionalconfigand forwards it toopen_database. GUIAnalysisPanelpassesself.config.- New
tests/test_config_propagation.pyinvokes the real CLI via Click's runner with a--config hardened.yamlthat setssecurity.encrypt_database: true, patchesopen_databasein the CLI module namespace, and asserts the received config'sencrypt_databaseis True. 8 tests covering export csv/json/kml, analyze subcommands (sessions/aps/clients/summary), isolation run-all, and SessionManager.
NR-01 (Moderate blocker) — Undefined open_database in CLI modules.
My earlier sed regex inserted the factory import into session_manager.py
but missed the three CLI modules (export.py, analyze.py, isolation.py).
Ruff F821 would have caught this in CI. Fixed: explicit top-level
from honeysnatch.db.factory import open_database in each, deferred
DatabaseManager imports removed from function bodies. The
test_config_propagation.py suite exercises every affected command
via the Click runner, so a NameError-shaped regression fails a test
that isn't a lint check.
332 pytest passing (was 304), 3 skipped, 0 failing. Smoke: 191/191. Diff from v0.1.2: +28 tests, all closing named findings.
- RE-01 — exact-candidate CI evidence remains gated on pushing the tag and letting the SHA-pinned Python matrix run against this artifact's digest. Same gate as every previous round.
- HS-06 — general-purpose capable interpreter remains an accepted local privilege primitive under group-scoped access. Reviewer accepted this posture in v0.1.2; unchanged here.
- HS-09 — locks, SBOM, and exact-commit attestation for vendored
hostap remain follow-up release-process work; provenance
documentation in
vendor/README.mdandvendor/FIXTURES.mdis the current state.
This release closes every finding from the v0.1.1 technical security review. The reviewer had marked v0.1.1 as RELEASE BLOCKED with one Critical, four High, and four Moderate defects.
- HS-01 (Critical) — Privileged recursive cleanup escape. The v0.1.1
_safe_rmtreedenylist approach was defeated by../../../etc(normalized to/etc, satisfied the len ≥ 4 guard). Rewritten as allowlist-rooted containment: the resolved path MUST have a parent equal to one of{/run/hostapd, /run/wpa_supplicant, /var/run/…},..segments are refused up-front, and path/parent symlinks are rejected beforeshutil.rmtreesees the path. 22 regression tests including the reviewer's exact../../../etcprobe. - HS-02 (High) — CLI-only consent gate. Consent enforcement moved
from the CLI to the runner boundary. Every
run_*method onIsolationTestRunnercalls_gate_live_run(), which requires eithersimulate=Trueor a valid :class:Authorizationconstructed viaAuthorization.from_cli_ack(bssid)(after CLIrequire_consent()) orAuthorization.from_token(bssid)(persistent consent token). The GUI panel now collects the target BSSID + explicit "I have permission" checkbox and constructs the runner with a matching Authorization. Programmatic callers get the same protection —IsolationTestRunner( simulate=False)(which was the GUI's original pattern) now raisesConsentRequiredErroron anyrun_*call. - HS-03 (High) — Audit tail HMAC not verified on init. The v0.1.1
_read_chain_tailaccepted a parseable tail as valid resume state even when the tail's HMAC didn't match its content. Now recomputes the HMAC over the canonical form and raisesAuditChainCorruptErroron mismatch. Also added: inter-process file lock (fcntl.flock on Unix, msvcrt.locking on Windows) across the read-tail / seq-derive / write / fsync cycle, so two writers can't race sequences. Log file created 0600 from first write. Reviewer's exact data-tamper probe is a named test inTestHmacTamperRejected. - HS-04 (High) — Security flags decorative.
SecurityConfig. encrypt_databaseandaudit_enabledhad zero readers — flipping them changed nothing. Newhoneysnatch/db/factory.pyprovidesopen_database(path, config)used by every CLI/GUI/analysis DB-open site. It reads SecurityConfig, obtains a passphrase (env var, explicit arg, or interactive prompt), fails closed on missing SQLCipher driver or missing passphrase, and emits anaudit_event("database_opened", …)whenaudit_enabledis set. Seven fail-closed integration tests. - HS-05 (High) — Packaging omits live-isolation assets.
data/moved into the package ashoneysnatch/data/(with__init__.pyso it's a proper package).pyproject.tomlgained a[tool.setuptools. package-data]block including the isolation configs and reference CSVs.find_default_confignow usesimportlib.resourcesfirst, falling back to source-checkout paths. The[all]extra now includesencrypted_db. Five packaging tests including a check that theallextra actually pulls SQLCipher.
- HS-06 — Capability helper hardening.
grant-capabilities.shnow creates ahbv-netcapsystem group, chowns the elevated interpreter toroot:hbv-netcapmode 0750, and documents the residual arbitrary-Python risk clearly. Group-membership is what gates use; a random local user can no longer execute the elevated interpreter. - HS-07 — HTML escaping. Every attacker-controlled string field
(SSID, vendor, probe-request, evil-twin reason, session name /
interface) in
analysis/reports.pyandmapping/renderer.pyis now passed throughhtml.escapebefore interpolation. Newtest_report_html_escaping.pyseeds a hostile session containing<script>,<img onerror>, and<iframe>payloads and asserts no live tags survive. - HS-08 — Hostap build cwd.
vendor/build.shnow anchors to its own directory viaSCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; cd "$SCRIPT_DIR", so it works identically whether invoked from repo root, fromfhs isolation build, or fromdeploy-hackberrypi.sh. - HS-09 — Supply-chain hygiene. Added
vendor/README.md(provenance table for every vendored tree with license and role) andvendor/FIXTURES.md(enumerates the upstream hostap test-fixture key directories with clear "NOT PRODUCTION CREDENTIALS" label plus scanner-allowlist globs). SBOM plan documented; full SBOM generation is a follow-up release-process item.
The exact-candidate CI-run evidence gate remains open until the tag is pushed and both workflows run against digest of the packaged v0.1.2 artifact. That's the same gate that stayed conditional on Sentinel v1.1.4 — no code change on my side can close it.
- 304 pytest passing, 3 skipped, 0 failing locally on Python 3.11. Was 237/2/0 at v0.1.1; 67 net-new tests.
- Smoke test: 191 passing, 0 failing (unchanged).
- Skipped tests are hostap-binary-required attacks (2) and the cellular-DB fixture missing when the module isn't loaded from an install (1).
This release closes every finding from the first-round security review of v0.1.0. Full remediation notes are in the internal portfolio tracker; public-facing highlights:
- Consent gate on all live isolation attacks (F-04). Every live
fhs isolationinvocation now requires either--i-have-permission-to-attack <BSSID>(matching the--target-bssidargument) or a persistent consent token granted viafhs isolation consent grant <BSSID>. Every grant, ack, denial, and--simulatebypass lands in the audit log as evidence. Simulation mode is unchanged; hardware-touching runs cannot fire without an explicit authorization affirmation. - Scoped-capability helper (F-01). Replaced the previous
setcap … $(which python3)recommendation, which granted network-raw to every Python process on the host for every user, withbin/grant-capabilities.sh— copies the venv's Python interpreter to.venv/bin/python-net, applies setcap to that copy only. - Audit chain fail-safe (F-02). A corrupt or truncated tail is now a
hard failure (
AuditChainCorruptError), not a silent chain reset. Rotation is available as an expliciton_corrupt="rotate"opt-in and moves the tainted log to<path>.tainted-<utc-ts>before starting a fresh chain whose first entry records the rotation reason. - SQLCipher key never in engine URL (F-03). Keys are applied via
a per-connection
PRAGMA keywith SQL-literal escaping. Keys containing@,:,/,#,?,%, or'now round-trip correctly and never leak into DSN reprs or exception traces. - HMAC key file: no permission race, no symlink follow, loud Windows
fallback (F-05, F-06, F-08). Created 0600 from the first write via
O_CREAT|O_EXCL|O_NOFOLLOW; pre-existing symlinks at the key path are refused; on Windows, a WARN is logged asking the operator to apply NTFS ACLs. - AES-GCM header authenticated (F-07). Magic bytes, salt, and nonce are now bound as associated data — header tamper invalidates the tag.
is_encrypted_filereturnsOptional[bool](F-13). Distinguishes "not encrypted" from "cannot read"; callers can no longer be misled by permission-denied files.rm -rfon control-interface path replaced with safe rmtree (F-14). Refuses/,~, empty string, or any path shorter than 4 chars.- CI actions SHA-pinned (F-09).
--ignore=tests/core/test_packet_parser.pyremoved (F-12); the 12 packet-parser tests now gate merges like every other suite.- Proof-carrying "no credential storage" test (F-17) locks SECURITY.md's headline claim into CI.
Total test count: 237 passing, 2 skipped (hostap-binary required), 0 failing. Smoke test: 191 passing, 0 failing.
If you discover a security vulnerability in honeysnatch, please report it responsibly.
Do NOT open a public GitHub issue for security vulnerabilities.
Instead, please email: security@ihbv.io
Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We will acknowledge receipt within 48 hours and aim to provide a fix within 7 days for critical issues.
honeysnatch handles sensitive wireless network data. Key security measures include:
- Encrypted storage: AES-256-GCM file encryption with PBKDF2-HMAC-SHA256 key derivation (600,000 iterations)
- Database encryption: Optional SQLCipher transparent encryption at rest
- Tamper-evident audit logging: HMAC-SHA256 chained append-only log detecting any modifications
- No credential storage: The tool never stores WiFi passwords or authentication credentials
- Passive-only scanning: Default operation is fully passive (receive-only)
This tool is intended for authorized security assessments, network administration, and educational purposes only. Users are responsible for ensuring they have proper authorization before scanning any networks they do not own or have explicit permission to test.