Compare commits

..

104 Commits

Author SHA1 Message Date
Cobra 3ea6c40352 Remove transient AI workflow audit artifacts and gitignore future ones 2026-07-21 13:10:27 -04:00
Cobra 3ba8a33b65 housekeeping: add .claude/ to .gitignore 2026-04-22 15:22:31 -04:00
Cobra 047635c304 Fix arrival/departure gates dropping LAA MACs; halve ARP scan interval
The phone/wearable gate used a manual OUI check that missed privately-addressed
MACs (iOS 14+/Android 10+ randomize per network). Any phone not pre-configured
in device_labels was silently enrolled with no alert, causing ARRIVED/OCCUPIED
to never fire. Replacing the gate with is_personal_device() catches OUI, labels,
and LAA bit uniformly.

ARP scan interval drops from 30s to 15s so worst-case arrival detection is 15s.
2026-04-15 15:25:32 -04:00
Cobra d8c742cea6 Reduce departure latency from 30min worst-case to 6min
Watchdog timeout and departure debounce were both 900s — stacked,
worst-case VACANT alert was 30 minutes after leaving. With active ARP
scanning every 30s we can confirm absence quickly, so both timers drop
to 180s. Override via NET_ALERTER_WATCHDOG_TIMEOUT and
NET_ALERTER_DEPARTURE_DEBOUNCE env vars.
2026-04-15 14:50:19 -04:00
Cobra 44c8e87959 Gate arrival/departure alerts to phones and wearables only
Non-phone OUIs (IoT, thermostats, TVs, etc.) are silently enrolled
for presence tracking but never trigger Matrix alerts. Only devices
matching MOBILE_DEVICE_OUIS or with an explicit label in device_labels
produce alerts.
2026-04-15 13:19:05 -04:00
Cobra 9b2f747775 Add active ARP scanning to bypass AP proxy on WiFi networks
Passive ARP sniffing fails on modern WiFi because the AP intercepts
ARP broadcasts and responds on behalf of clients. Active scanning
sends who-has to all 254 /24 addresses and collects replies directly,
making presence detection reliable regardless of AP configuration.

Also adds monitor mode sniffer thread that activates automatically if
a second WiFi interface is present (e.g. USB dongle). Reads raw 802.11
frames, bypassing the AP stack entirely for station-transmitted data
and management frames.
2026-04-15 12:58:59 -04:00
Cobra a9d43ce4d3 Suppress BlueZ teardown race on SIGTERM in ble_alerter
When systemd sends SIGTERM, asyncio cancels the scan task before
BleakScanner.__aexit__ runs. BlueZ already stopped discovery by then,
so stop() throws 'No discovery started'. This was crashing the service
and triggering a 22-restart loop before systemd settled. Catch the
specific error string and treat it as a debug-level no-op.
2026-04-15 00:30:32 -04:00
Cobra f28b11801a Fix MAC normalization in ARP opcode=2 trust gate
is_labeled compared raw src_mac against normalized device_labels keys,
always evaluating False for labeled devices. ARP replies (opcode=2) from
personal MACs were never trusted, so on_arrival only fired on ARP
requests (opcode=1) which are rare. Labeled devices now correctly pass
the opcode=2 gate, triggering arrival alerts with their display name.
2026-04-15 00:24:59 -04:00
Cobra d4f95a550c Add BLE GATT Device Name probing and WiFi+BLE arrival correlation
ble_alerter.py: probe Apple BLE devices via GATT characteristic 0x2A00
to discover device names (iPhone, Apple TV, etc). Names persist to
ble_names.json with last_seen timestamps. GATT_PROBE=1 enables active
probing; without it, Apple device last_seen timestamps still tracked.

net_alerter.py: on WiFi device arrival, read ble_names.json and log
any Apple BLE devices seen within the last 60s as correlation candidates.
Gives operator visibility into which BLE MACs co-arrived with a WiFi device.

Shared state file: /opt/net_alerter/ble_names.json
2026-04-15 00:03:35 -04:00
Cobra d4f16425f9 Label takes precedence over DHCP hostname in arrival/departure alerts
iOS sends 'My iPhone' via DHCP option 12 regardless of the actual device
name set on the phone. Operator-set labels are authoritative and should
not be overridden by privacy-obscured DHCP hostnames.
2026-04-14 23:50:23 -04:00
Cobra 3547c22d83 Delay arrival alert 2s so mDNS hostname resolves before alert fires
DHCP triggers on_arrival immediately but iPhone sends mDNS frames
milliseconds later. Without the delay the alert fires with no hostname
and falls back to the device label. Timer reads from known_devices at
fire time so mDNS-updated hostname is used.
2026-04-14 23:37:06 -04:00
Cobra 0d48b78463 Prefer auto-discovered hostname over device label in alerts
Label in device_labels is for tracking/identification. When mDNS reveals
the real device name, use it. Label serves as fallback when no hostname
has been discovered yet.
2026-04-14 23:34:34 -04:00
Cobra b9a3411345 Extract Bonjour hostname from mDNS and use it in arrival/departure alerts
_extract_mdns_hostname scans A records in mDNS payload before on_arrival fires,
so the ARRIVED alert shows the device's actual name instead of the IP.
_parse_mdns_for_names also now updates known_devices hostname on subsequent frames,
so departure alerts get the real name even if the first frame had no A record.
2026-04-14 23:23:07 -04:00
Cobra a77323b1a0 Skip all personal devices from ARP seed, not just labeled ones
Operator needs ARRIVED for any phone or wearable on site, including
unknown devices. Silent seeding only applies to infrastructure and
non-personal devices.
2026-04-14 23:17:13 -04:00
Cobra a2597f5f3d Prevent labeled devices from being silently seeded; detect opcode=2 ARP for labeled MACs
seed_from_arp_cache was adding labeled personal devices to known_devices at startup,
causing all subsequent arrivals to be ignored (already_known=True). Now skips them
so on_arrival fires normally when they rejoin.

ARP opcode=2 (replies) are now treated as genuine signals for explicitly labeled devices.
iPhones with privacy MACs frequently emit only opcode=2 after initial association — the
AP proxy forgery concern doesn't apply to devices the operator has explicitly named.
2026-04-14 23:06:05 -04:00
Cobra d3da6ea89c Prevent churn suppression from silencing labeled personal devices
Phone (c6:37:b0:b2:07:30) was cycling through arrivals/departures faster
than the 30-min churn window threshold, causing _check_churn to add its
IP to infrastructure_ips. All subsequent on_arrival calls were silently
dropped by the infrastructure IP check, so no ARRIVED alerts fired.

Two fixes:
- _check_churn: skip churn suppression entirely for MACs in device_labels
  or personal_devices — labeled devices are never infrastructure
- on_arrival already-known path: upgrade ip from '0.0.0.0'/'unknown'
  (ARP probe placeholder) to real IP when netlink delivers the actual address
2026-04-14 22:45:15 -04:00
Cobra 7154e2b295 Exclude infrastructure MACs from personal device detection via NET_ALERTER_INFRA_MACS
Travel routers and other LAA-bit devices get misclassified as personal phones
because the LAA heuristic fires on any MAC with bit 1 set (b6:cc is 0xb6).
Add infrastructure_macs set checked before the LAA heuristic in is_personal_device().
NET_ALERTER_INFRA_MACS env var (comma-separated MACs) populates the set at startup.
2026-04-14 22:10:28 -04:00
Cobra 409752eb2a Remove Tailscale exclusion machinery — root cause was a bad .env label
b6:cc was mislabeled as 'Dane iPhone' causing it to be treated as a
personal device. Removing the label fixes the problem without needing
any Tailscale-specific code. Keep mDNS and ARP arrival fixes.
2026-04-14 22:06:58 -04:00
Cobra b73d497010 Fix seed_tailscale_peers to use CurAddr field not Endpoints
tailscale status --json uses CurAddr for the active direct endpoint,
not Endpoints (which is always null/absent). Without this fix the
Tailscale exclusion set was always empty.
2026-04-14 21:51:52 -04:00
Cobra 83894bf84d Fix three presence monitoring bugs: Tailscale exclusion, mDNS arrival, ARP personal device arrival
Tailscale peers (travel router b6:cc) were kept perpetually REACHABLE in the
kernel ARP cache by OPi's WireGuard keepalives, refreshing last_seen and
blocking departure alerts. seed_tailscale_peers() excludes their MACs.

mDNS was only refreshing the watchdog timer for already-known devices. Apple
devices send mDNS on WiFi join as the primary signal — now calls on_arrival()
so first-seen mDNS triggers ARRIVED alert and occupancy update.

ARP opcode 1 for personal devices never called on_arrival(), so devices that
joined without DHCP (c6:37 My iPhone at 01:25) got no alert. Now fires
on_arrival() for personal devices not yet in known_devices.
2026-04-14 21:51:02 -04:00
Cobra 54b389e767 Prevent watchdog from re-triggering departure on already-departing devices
Repeated watchdog calls for a device already in departing=True state
were feeding the churn suppressor, which misclassified the iPhone's IP
as infrastructure after 3 calls within 30 min. This blocked all
subsequent departure processing and caused a 31-min alert delay.

Fix: gate the on_departure() call on a should_depart flag set only when
the device is known and not already departing.
2026-04-14 20:40:09 -04:00
Cobra 7d87f29a47 Fix arrival suppression when device returns during departure debounce
Watchdog-triggered departures set departing=True after 15 min of silence.
If the device returned during the 15-min debounce window, on_arrival()
treated it as a rapid flap and suppressed the ARRIVED alert — even though
the device had been genuinely absent for 15+ minutes.

Store depart_initiated timestamp when setting departing=True. On re-arrival,
check the gap: < 120s = true rapid flap (transient Netlink/DHCP event),
suppress as before. >= 120s = real absence confirmed by watchdog, pop the
device from known_devices and let it fall through to the new-device path
so the ARRIVED alert fires.

Also removes debug traceback instrumentation left in _update_last_seen
from the prior investigation session.
2026-04-14 19:49:33 -04:00
Cobra 57cf43eaea Disable active probes — passive-only operation required
arping/ICMP probes on device appearance violate the passive-only
design constraint. Stub out _fire_active_probes to return immediately.
2026-04-14 16:18:12 -04:00
Cobra 936e33d373 Seed last_seen from ARP cache on startup so watchdog has a baseline
Without this, last_seen is empty after every restart and the watchdog
skips all devices (ts is None → continue). Devices that are genuinely
active refresh last_seen via ARP requests or mDNS. AP proxy ghosts only
send ARP replies (now filtered), so they go silent and the watchdog
fires departure after WATCHDOG_TIMEOUT_SEC.
2026-04-14 16:07:47 -04:00
Cobra 51fff82867 Ignore ARP replies in last_seen updates to block AP proxy spoofing
AP ARP proxy sends REPLY frames with the disconnected client's MAC as
the Ethernet source. Treating those as genuine device signals kept
last_seen fresh, preventing the watchdog from detecting departure.

Only ARP REQUEST (opcode 1) frames originate from the device itself.
Skip _update_last_seen() for opcode 2 (REPLY) frames.
2026-04-14 15:57:56 -04:00
Cobra 88bc36af61 Fix departure detection for AP ARP-proxy networks (passive-only)
ARP proxy on WiFi APs causes the kernel neighbor table to show devices
as REACHABLE even after they disconnect. This made last_seen refresh
continuously from on_arrival() (netlink path), preventing the watchdog
from ever detecting departure.

Two fixes:
1. Remove _update_last_seen() from on_arrival() — netlink events are
   not reliable as a genuine-traffic source on ARP-proxy networks.
   last_seen is now only updated by the raw packet capture path (ARP
   requests, mDNS, DHCP) where src_mac must be the device itself.

2. Watchdog now covers all is_personal_device() entries in known_devices,
   not just the personal_devices enrollment set. device_labels entries
   (like named phones) were previously unmonitored by the watchdog.
2026-04-14 15:52:26 -04:00
Cobra 52fc12b97a Fix false presence: only trigger arrival on NUD_REACHABLE, not STALE/DELAY/PROBE
Kernel fires RTM_NEWNEIGH for NUD_STALE, NUD_DELAY, and NUD_PROBE states
as it probes a potentially-departed device. Each probe was calling on_arrival()
which refreshed last_seen, preventing the watchdog from ever detecting departure.

Only NUD_REACHABLE confirms a device is actually on the network.
2026-04-14 15:38:30 -04:00
Cobra e92bec1fcd Add Garmin/Fitbit OUIs; consolidate personal device detection into is_personal_device()
Garmin and Fitbit wearables use globally-assigned OUI MACs (no private addressing)
so the LAA bit check misses them. Added their OUIs to MOBILE_DEVICE_OUIS.

Consolidated all three detection signals into is_personal_device():
1. Explicit config (device_labels, personal_devices)
2. OUI table (phones + Garmin/Fitbit)
3. LAA bit (iOS 14+/Android 10+ private MACs)

Occupancy _is_personal now delegates to is_personal_device instead of duplicating logic.
2026-04-14 15:01:49 -04:00
Cobra 7b039cbefd Use LAA MAC bit to auto-detect phones/wearables for occupancy
Modern phones use private/random MACs (iOS 14+, Android 10+) which
always have the locally-administered bit set (bit 1 of first octet).
TVs, gateways, and smart home devices use globally-assigned OUI MACs
where this bit is clear.

No configuration needed — any LAA MAC counts toward occupancy.
Explicit device_labels/personal_devices still work as fallback.
2026-04-14 14:58:53 -04:00
Cobra df6ecba5e5 Restrict occupancy to personal devices only (phones/wearables)
Previously counted every non-infrastructure device toward OCCUPIED state,
causing TVs and other stationary equipment to falsely indicate occupancy.

Now only MACs in device_labels or personal_devices count. Everything else
is observed but ignored for presence purposes.
2026-04-14 14:56:20 -04:00
Cobra be852dbcf3 Fix MAC normalization in device_labels lookup; add label support to arrival/departure alerts
device_labels keys are normalized (no colons, uppercase) but known_devices keys
are colon-lowercase. device_labels.get(mac) always returned None.

Also extend label display to arrival and departure alerts — if a MAC has a
configured label, show that instead of vendor/hostname.
2026-04-14 14:52:41 -04:00
Cobra ba18b2fba8 Add device labels to occupancy alerts — include who is present
NET_ALERTER_DEVICE_LABELS=MAC=Name,MAC=Name maps known devices to
friendly names. OCCUPIED alerts now show which devices are present.
Falls back to vendor string then MAC tail if no label configured.
2026-04-14 14:46:18 -04:00
Cobra 2fb6e7e8e1 Fire occupancy alert on startup — UNKNOWN→OCCUPIED was silently suppressed
Previously the service would suppress the startup OCCUPIED alert, meaning
if phones were already present when the service started, no Matrix alert
ever fired until a departure+return cycle. Users never received confirmation
the sensor was working.
2026-04-14 14:39:02 -04:00
Cobra a3df9ed958 Any enrolled non-infrastructure device marks location as occupied 2026-04-14 14:25:32 -04:00
Cobra c01064c12e Enroll on first contact — 1 source is better than none 2026-04-14 14:21:52 -04:00
Cobra b50fc4238b Implement mDNS as standalone enrollment signal and active probes on device discovery
Changes:
1. mDNS as separate signal category: Map 'mdns' signal_type to its own signal_category (not grouped with 'wifi'). Enrollment now triggers when EITHER (a) len(signal_types) >= 2 OR (b) 'mdns' in signal_types.

2. Active probes on device discovery: When new identity is created, fire non-blocking probes to collect additional signals. Uses arping or ping (no new deps) to probe IP if available. Runs in daemon thread to avoid stalling correlation loop.

This allows devices announcing via mDNS to be enrolled immediately without waiting for a second signal type, while still collecting additional signals for richer device profiles.
2026-04-14 14:12:36 -04:00
Cobra f73ef29c1e net_alerter: Add Strategy 0 MAC deduplication and fix Strategy 3 overwrite bug
Strategy 0 now checks if a MAC already exists in any identity's wifi_macs/ble_macs
sets before creating a new record. Prevents duplicate identity creation on repeated
mDNS/ARP signals.

Strategy 3 now conditionally updates existing records instead of unconditionally
overwriting them. Preserves signal_count and signal_types accumulation needed for
auto-enrollment (2+ signal types).

Fixes issue where devices seen only via mDNS/ARP never reached enrollment threshold.
2026-04-14 13:39:37 -04:00
Cobra 6a5d1f0670 Exit cleanly when no BT adapter found instead of crash-looping
Wrap BleakScanner context manager in try-except to detect adapter
initialization errors. If 'No Bluetooth adapters found' or similar, log
warning and set shutdown event to trigger graceful exit with code 0.
Prevents systemd restart loop on hardware without BT adapter (e.g. OPi).
2026-04-14 13:29:29 -04:00
Cobra 0a8d6a2019 Add audit artifacts from #671 review pipeline 2026-04-14 13:18:35 -04:00
Cobra cfa2d9e0e7 Support wpa_supplicant config format on OPi/Armbian targets
- Add detect_wifi_config_type() to identify config format (netplan vs wpa_supplicant)
- Add read_wpa_supplicant() to parse wpa_supplicant-wlan0.conf
- Add write_wpa_supplicant() to write updated config with proper formatting
- Update run_interactive(), run_list(), run_add() to detect and dispatch to correct handler
- Both formats now auto-detected; existing netplan paths continue to work
- wpa_supplicant format used by OPi Zero 3 / Armbian defaults
2026-04-14 13:14:35 -04:00
Cobra b67127677f Fix P1/P2 audit findings from #671 review
P1 Fixes:
- Signal count enrollment logic: Changed from broken signal_count increment to tracking distinct signal types (BLE vs WiFi) using a set. Device enrolls when len(signal_types) >= 2, ensuring multi-source correlation.
- DNS mDNS pointer endianness: Added bounds check to prevent out-of-bounds reads when following DNS compression pointers. Checks pointer_offset < offset and pointer_offset < len(payload) before recursing.
- Nested RLock fragility: Refactored enrollment callback to not acquire lock (caller _ingest_signal holds it). Renamed _on_device_enrolled() to _fire_enrollment_callback() and removed lock acquisition.

P2 Fixes:
- BLE Handoff parsing: Implemented full HCI packet parsing to extract Apple Company ID (0x004C), Handoff message type (0x0C), and sequence number (bytes 4-5, big-endian). Calls _ingest_signal() with handoff_seq parameter.
- DNS record count overflow: Capped total_records at 1000 to prevent unbounded loop DoS on crafted mDNS packets.
- device_store unbounded growth: Added simple eviction when store exceeds 500 entries - evicts 100 oldest by first_seen timestamp. No LRU needed for MVP.

All 40 existing tests continue to pass.
2026-04-14 12:46:44 -04:00
Cobra 5077e30c66 Add multi-source device identity store and zero-config enrollment (#671)
Track devices across MAC rotations using BLE Handoff sequence numbers
and DHCP Option 55 fingerprinting. Auto-enroll personal devices when
seen on 2+ independent signal types. Handle iOS MAC rotation silently.
Confidence-based departure only fires for enrolled devices.

Changes:
- New device_store dict for stable device identity tracking (keyed by
  handoff seq anchor or DHCP fingerprint hash)
- _dhcp_fingerprint_hash() computes stable DHCP Option 55 fingerprint
- _create_identity_record() initializes device records
- _on_device_enrolled() triggers silent enrollment when signal_count >= 2
- _correlate_or_create_identity() finds or creates identities via BLE seq
  or DHCP fingerprint, handles MAC rotation detection
- _ingest_signal() ingests signals from multiple sources (BLE, DHCP, ARP,
  mDNS) and triggers enrollment when criteria met
- ble_sniffer() thread for Apple Continuity Protocol parsing (HCI socket +
  hcidump fallback); graceful failure on missing hardware
- Modified parse_frame() to extract DHCP Option 55 and ingest signals for
  ARP, mDNS, DHCP packets
- Updated main() to start BLE sniffer thread and log device_store stats
2026-04-14 12:38:32 -04:00
Cobra e49ca1ace5 Add tests for iOS departure detection features
New tests for net_alerter iOS departure detection:
- test_update_last_seen_updates_dict: verifies _update_last_seen dict updates
- test_personal_watchdog_fires_on_departure_after_timeout: timeout detection
- test_personal_watchdog_does_not_fire_within_timeout: no false positives
- test_mdns_name_matching_adds_mac_to_personal_devices: mDNS name discovery
- test_load_personal_macs_from_env_does_not_log_individual_macs: privacy logging

All 21 tests passing (16 existing + 5 new).
Updated cleanup fixture to include last_seen, personal_devices, personal_names.
2026-04-13 20:30:49 -04:00
Cobra 3f4e79dd68 Add iOS departure detection via passive ARP/mDNS capture and watchdog
Implement passive observation tracking for iOS devices:
- Add last_seen dict and lock for tracking passive frame observations
- Parse ARP frames (ethertype 0x0806) for MAC observation
- Parse mDNS frames (UDP port 5353) for device name auto-discovery
- Rename parse_dhcp to parse_frame to reflect broader scope
- Add personal_names set for device name matching
- Parse NET_ALERTER_PERSONAL_NAMES env var (comma-separated device names)
- Add mDNS DNS message parsing to extract PTR/SRV/A record names
- Auto-add MACs to personal_devices when mDNS name matches personal_names
- Call _update_last_seen on all frame types (ARP, mDNS, DHCP)
- Add personal_watchdog thread to fire on_departure after WATCHDOG_TIMEOUT_SEC
- Add NET_ALERTER_WATCHDOG_TIMEOUT env config (default 900s)
- Fix load_personal_macs_from_env logging to show counts instead of MACs

Changes are minimal and focused:
- No socket changes (existing AF_PACKET socket already sees all frames)
- No new dependencies (stdlib only)
- Deadlock prevention (watchdog does not hold locks when calling on_departure)
- Backward compatible (personal_names optional, watchdog is passive)
2026-04-13 20:27:00 -04:00
Cobra 979ebe8441 fix _check_churn self-deadlock — remove redundant known_lock nesting
on_departure() holds known_lock when calling _check_churn(). When churn
threshold is hit, the nested `with known_lock:` inside _check_churn tried
to reacquire an already-held non-reentrant lock → permanent deadlock.

This killed the production process on the OPi Zero 3 after ~15 hours of
runtime on 2026-04-12 at ~02:12 UTC. infrastructure_ips.add() is safe
without the inner lock since the caller already holds known_lock.

Also: reset _churn_tracker between tests to prevent cross-test state
accumulation that triggered the churn threshold in test isolation.
2026-04-13 17:07:25 -04:00
Cobra 13900f9045 Remove Matrix credentials from global memory — load at call time
Fix #605: Remove MATRIX_HOMESERVER, MATRIX_ACCESS_TOKEN, MATRIX_ROOM_ID from module-level globals.
Credentials are now read at call time in send_alert() via _read_env_value() to minimize
credential lifetime in process memory.

Fix #606: Update deploy.sh to use Infisical creds CLI instead of .secrets file.
Fetches NET_ALERTER_PASSWORD from matrix folder in Infisical vault, then generates
access token at deploy time.

All 16 net_alerter tests pass when run individually or in groups.
2026-04-13 04:00:56 -04:00
Cobra c87967f2c4 Fix timer.cancel() deadlock and add MAC format validation
Two P1 bugs fixed:

1. Timer cancel deadlock: timer.cancel() was called while holding locks, which would deadlock if the callback was running/paused waiting for those same locks. Fixed by extracting timers outside of all locks before calling cancel().

2. MAC validation: _normalize_mac() now validates format with regex and returns empty string for invalid MACs. Callers (load_personal_macs_from_env, is_personal_device) skip empty results and log warnings.

All 16 tests pass.
2026-04-12 08:41:04 -04:00
Cobra 0b468ee548 Fix P1 bugs in occupancy tracking: lock ordering deadlock, MAC normalization, and race condition
- P1-1: Fix lock ordering deadlock by establishing canonical order (known_lock → departure_lock → occupancy_lock). Refactor on_arrival() to acquire locks in correct order. Fix send_departure_alert() callback to hold occupancy_lock while updating state.
- P1-2: Add _normalize_mac() helper to strip colons/hyphens, apply to load_personal_macs_from_env() and is_personal_device() for consistent MAC comparison.
- P1-3: Remove occupancy_lock acquisition from _update_occupancy_state_unlocked() - callers must hold lock. Update all callers to acquire occupancy_lock before calling.

All individual tests pass. Fixes allow on_arrival() and timer callbacks to execute without deadlock.
2026-04-12 07:57:37 -04:00
Cobra f690370402 Implement occupancy tracking with personal device detection and deadlock fix
- Add MOBILE_DEVICE_OUIS set with 55+ OUI prefixes for personal devices
- Add is_personal_device() function to detect personal devices by OUI or manual config
- Add load_personal_macs_from_env() to parse NET_ALERTER_PERSONAL_MACS
- Add location_occupancy state tracking (VACANT/OCCUPIED/UNKNOWN)
- Add update_occupancy_state() function with thread-safe alerts on transitions
- Add _update_occupancy_state_unlocked() helper to prevent deadlock
- Fix deadlock in on_arrival() by using unlocked version when holding known_lock
- Update test suite with 5 new occupancy tests
- Fix test assertions to exclude occupancy alerts for device arrival tests
2026-04-12 07:26:57 -04:00
Cobra 92c85f4e43 Apply Delta Reviewer QA corrections to net_alerter security review
- Correction 1: Add severity justification notes to all 15 CRITICAL findings explaining uprates from source agent JSON
- Correction 2: Complete triage mapping with explicit (FIX-NOW)/(FIX-DETECTION)/(FIX-OPSEC) labels on all findings
- Correction 3: Add Appendix A deduplication mapping showing raw→consolidated finding ID merges (54 raw → 29 consolidated HIGH+CRITICAL)
- Correction 4: Preserve agent-specific structured fields in consolidated findings:
  - RT findings: Tools, Exploitation Time, Attack Chain steps
  - IR findings: Failure Mode scenarios, Impact window
  - BT findings: Detection Source, Theoretical TTD minutes
- Correction 5: Add explicit triage labels to High-Severity section findings

All 5 Delta Reviewer QA corrections applied. Report now ready for gate review.
2026-04-10 23:34:42 -04:00
Cobra f243e1e049 Append Delta Reviewer QA report to consolidated security findings 2026-04-10 23:28:24 -04:00
Cobra 1af00e7da9 Consolidate 6-agent security review into unified report
- Deduplicate 105 raw findings into 52 unique consolidated issues
- Apply severity calibration rubric across SA, OA, BT, APT, RT, IR agents
- Map findings to 6 triage categories (FIX-NOW, FIX-DETECTION, FIX-OPSEC, FIX-QUALITY, NOT-FIXING, DEFERRED)
- Prioritize 4 user-specified concerns: flap suppression blind spot, gateway exclusion, debounce aggressiveness, flap detection OPSEC
- Include cross-agent consensus table (28 multi-agent findings, 54% of total)
- Provide pre-deployment checklist and remediation effort estimates
- Executive summary with 15 CRITICAL, 22 HIGH, 13 MEDIUM, 2 LOW findings
2026-04-10 23:23:44 -04:00
Cobra d1e89bddf5 Add three dynamic infrastructure auto-suppression mechanisms to net_alerter
- Option A: OUI-based auto-exclusion for network equipment (Ubiquiti, Cisco, Aruba, TP-Link, Netgear, MikroTik, Ruckus)
- Option B: Churn-based suppression (cycles 3+ times in 30min auto-suppresses device)
- Option C: Verify NET_ALERTER_INFRA_IPS env var is documented and functional

Implementation:
- Added NETWORK_EQUIPMENT_OUIS set with 45 common network equipment OUI prefixes
- Added _check_churn() function to track departure events within 30min window
- Integrated OUI check into on_arrival() and seed_infrastructure_ips()
- Integrated churn check into on_departure() before timer start
- Enhanced seed_infrastructure_ips() docstring to document all 5 seeding steps
- Removed interactive BB_ALERTER_INFRA_IPS prompt from operator_setup.sh (post-deploy config only)
2026-04-10 22:57:40 -04:00
Cobra 98ee896916 Add NET_ALERTER_INFRA_IPS config to setup.sh .env template and operator_setup.sh prompt 2026-04-10 22:50:56 -04:00
Cobra 39ae11f3d1 Add flap state cleanup to test_net_alerter for test isolation
Tests now call cleanup_flap_state() at the start of each test to properly
reset the interface flap detection state (_flap_window, _interface_recovering,
_recovery_timer) between tests, preventing state carryover failures.
2026-04-10 22:46:07 -04:00
Cobra 91a84b9d6f Add interface flap detection and env var for infrastructure IPs in net_alerter
- Fix 1: Interface flap detection suppresses departure storms when wlan0 briefly loses association and triggers simultaneous RTM_DELNEIGH events for all cached neighbors
  - Tracks 3-second window of departure MACs
  - Locks recovery state for 60 seconds if 3+ departures occur in rapid succession
  - Prevents false positive alerts during interface recovery

- Fix 2: Additional infrastructure IPs via NET_ALERTER_INFRA_IPS env var
  - Allows operator to suppress alerts for devices like access points that cycle regularly
  - Comma-separated list of IP addresses to be seeded as infrastructure at startup
  - Example: NET_ALERTER_INFRA_IPS=10.0.0.0,10.0.0.0

Addresses DevTrack #467 (net_alerter false positive alerts)
2026-04-10 22:45:15 -04:00
Cobra 06c84e1800 Deploy presence daemon to OPi Zero 3 test implant
sensor.service running at 10.0.0.0. ARP + DHCP sensors active.
Probe sniffer degraded (no wlan1). BLE degraded (BlueZ too old for or_patterns).
Untracked audit files committed for reference.
2026-04-10 15:49:03 -04:00
Cobra 2169c868c3 Add bounds checks to netlink and DHCP parsers — prevent exception flood on malformed packets 2026-04-10 15:49:03 -04:00
Cobra fd1eeeda47 Fix P1 blockers: configurable port, connection leak, race condition, DB path + WAL
- P1 #551: Add STATUS_PORT env var (default 9191) for configurable HTTP status server port
- P1 #552: Fix SQLite connection leak — wrap all sqlite3.connect() with try/finally in lookup_person(), seed_persons_from_db(), log_signal(), log_presence_change()
- P1 #553: Fix race condition in update_person_state() — compute certainty snapshot under devices_lock, determine state transition under persons_lock, send alert outside locks to prevent stale snapshot
- P2 #559 (included): Add WAL mode + pragmas to init_db() for concurrent write safety
- Database path: Align to /opt/sensor/presence.db in both daemon and install.sh
- Increase connection timeout from 1s to 10s across all functions
- install.sh: Document PRESENCE_STATUS_PORT and PRESENCE_MATRIX_WEBHOOK env vars
2026-04-10 15:49:03 -04:00
Cobra 09b81deb45 Add presence daemon user documentation and architecture guide 2026-04-10 15:49:03 -04:00
Cobra fd014b2a38 Add passive presence daemon — multi-signal WiFi/ARP/DHCP/BLE with exponential decay
Implements DevTrack #530: Person Presence Intelligence daemon for BigBrother drop implant.

Core components:
- presence_daemon.py: 4 sensor threads (probe, ARP, DHCP, BLE) with exponential decay
- presence_schema.sql: SQLite schema for persons, devices, signals, occupancy log
- install.sh: Deployment script (copies to /opt/sensor/, creates systemd service)
- AUDIT_impl.md: Implementation notes, limitations, audit checklist

Features:
- Exponential decay model (lambda=0.08/min) for signal certainty
- Presence anchor (45-min hold time prevents false VACANT)
- Person state machine: UNKNOWN → PRESENT ↔ ABSENT
- Multi-signal fusion: max + 0.08×min for device aggregate
- Thread-safe in-memory state with SQLite persistence
- Matrix webhook alerting (if configured)
- HTTP status endpoint on 127.0.0.1:9191
- Graceful BLE degrade if bleak unavailable
- No tool fingerprints in output or service names
2026-04-10 15:49:03 -04:00
Cobra 722a632cee Revert "Add presence DB schema; fix BLE passive scan mode (bleak #1440)"
This reverts commit 2499db6147be65c76e7e202190b81855ba7b9100.
2026-04-10 15:49:03 -04:00
Cobra 3ddb854685 Revert "Add Phase 1 WiFi presence daemon — mDNS + ARP fusion with exponential decay and presence anchor"
This reverts commit b9adf498106c52f1c7508a791a7aac30d5ca1955.
2026-04-10 15:49:03 -04:00
Cobra f7460b0416 Add Phase 1 WiFi presence daemon — mDNS + ARP fusion with exponential decay and presence anchor 2026-04-10 15:49:03 -04:00
Cobra 0994385f7c Add presence DB schema; fix BLE passive scan mode (bleak #1440) 2026-04-10 15:49:03 -04:00
Cobra 982e0c526c Apply QA corrections to consolidated security review: deconflict APT-001 triage, add deduplication transparency, document exploit chain dependencies 2026-04-10 15:49:03 -04:00
n0mad1k c04ec0b9ea Add comprehensive test suites for 7 bigbrother modules
Cover packet_capture (rotation/zstd/AES), credential_db (dedup/hashcat/bulk),
ja3_spoofer (profiles/cipher mapping/randomization), responder_mgr (hash
capture/dedup/conf gen), ntlm_relay (protocol inference/command building/
coordination), ids_tester (risk assessment/preflight/caplet), and bridge
(setup/teardown/ebtables/watchdog). 187 tests total, all passing.
2026-04-10 07:38:28 -04:00
n0mad1k 0b5c7f3907 Add responder_mgr NTLM hash capture validation tests 2026-04-10 07:37:22 -04:00
n0mad1k 7a3b8e40d9 Add ntlm_relay wrapper and ADCS relay validation tests 2026-04-10 07:36:16 -04:00
n0mad1k edfa067f68 Add ids_tester IDS evasion self-test validation tests 2026-04-10 07:34:49 -04:00
n0mad1k 4ca9d3cdf1 Add validation tests for packet_capture, credential_db, and ja3_spoofer
Tests cover:
- packet_capture: zstd compression, AES-256-GCM encryption, rotation
  pipeline, disk purge, file locking (15 tests)
- credential_db: ingestion, deduplication at scale (1000+), hashcat
  export filtering, crack status management, bulk update, CSV/JSON
  export, query helpers (25 tests)
- ja3_spoofer: builtin profile validation, cipher ID mapping, external
  DB loading, auto-selection, SSL context config, randomization
  validation (28 tests)

Addresses DevTrack items #424, #425, #437.
2026-04-10 07:29:08 -04:00
n0mad1k 027eaa39b2 Expand p0f OS fingerprint signatures from 16 to 102 entries
Comprehensive p0f-style TCP SYN signatures covering Linux (kernel 2.0-6.x,
Android, Chrome OS, Alpine, containers), Windows (2000 through 11/Server
2022, XP, Embedded), macOS (Tiger through Sonoma), iOS/iPadOS/tvOS/watchOS,
BSDs (Free/Open/Net/DragonFly), Solaris/illumos, network devices (Cisco
IOS/NX-OS/IOS-XE, Juniper, MikroTik, Ubiquiti, pfSense, OPNsense,
Fortinet, Palo Alto, Aruba, F5, HP ProCurve, Arista), printers (HP,
Brother, Epson, Canon, Xerox, Ricoh, Kyocera, Lexmark, Zebra), embedded/
IoT (lwIP, uIP, VxWorks, QNX, Zephyr, ESP-IDF, FreeRTOS, Contiki-NG,
Windows IoT), gaming consoles, and virtualization guests.

DevTrack: bigbrother #419
2026-04-10 07:06:50 -04:00
n0mad1k 45c4d4a57b Expand MAC profiles from 55 to 132 innocuous device profiles
Added profiles for: more streaming devices (Shield TV, Roku 4K),
additional smart TVs (Hisense, Insignia, Philips, Toshiba), more smart
speakers (Sonos Era, HomePod, Bose, Echo Pop/Studio), extensive IoT
devices (Arlo, Eufy, Chamberlain, Lutron, Lifx, Roborock, SmartThings,
August, Honeywell), more printers (Kyocera, Lexmark, Ricoh, Xerox),
additional gaming (Xbox Series S, Switch OLED, Steam Deck), more phones
(Pixel, OnePlus, Xiaomi), wearables (Apple Watch, Fitbit, Garmin,
Galaxy Watch), network gear (Linksys, ASUS, eero), and NAS devices
(Synology, QNAP). New device_type categories: wearable, network, nas.

DevTrack: bigbrother #417
2026-04-10 07:05:25 -04:00
n0mad1k 0d92b97682 Expand JA3 fingerprint database from 4 to 501 profiles
New seed_ja3.py generates comprehensive JA3 TLS fingerprint profiles
covering Chrome/Chromium (100-124 x 4 platforms), Firefox (100-125 x 3),
Edge, Safari (macOS + iOS), Opera, Brave, Tor Browser, Vivaldi, ChromeOS,
TLS libraries (OpenSSL, BoringSSL, GnuTLS, NSS, Go, Java, .NET, Node.js,
mbedTLS, wolfSSL, rustls), common tools (curl, wget, Python, httpx),
mobile clients (Samsung Internet, Android WebView, iOS WKWebView),
VPN clients (AnyConnect, GlobalProtect, OpenVPN, WARP), Electron apps,
and IoT/embedded devices (ESP32, AWS IoT, smart home).

JA3 hashes are computed correctly from TLS ClientHello parameters using
the standard MD5(version,ciphers,extensions,curves,formats) formula.

DevTrack: bigbrother #416
2026-04-10 07:02:56 -04:00
Cobra fa0e2491cf Fix ble_alerter: MAC name filter, Matrix 429 backoff, active scanner
- Reject dash/colon MAC-format device names (e.g. 45-48-39-7F-73-C7)
  from open mode — were slipping past is_generic_name()
- Add exponential backoff (1s, 2s) on Matrix 429/5xx, max 3 attempts —
  startup burst was rate-limiting and dropping alerts
- Commit active scanner flag (scanning_mode=active) that was in working
  tree but not in prior commit c0b91e9 despite the commit message
- Add 3 tests: MAC format filter, 429 retry success, 429 give-up

Fixes DevTrack #518
2026-04-10 06:44:13 -04:00
Cobra c0b91e9b0b Switch BLE scanner to active mode — bleak 0.20 passive requires or_patterns on BlueZ
Passive mode fails on Debian bookworm bleak 0.20.2 without or_patterns filter.
Active mode is functionally equivalent for building-level presence detection
and actually yields more complete advertisement data including device names.
2026-04-10 06:37:30 -04:00
Cobra 85e53fed68 Fix test_too_short test (abc/abd are hex characters) 2026-04-10 06:33:38 -04:00
Cobra 40eb8cf621 Restore execute permission on operator_setup.sh 2026-04-10 06:32:31 -04:00
Cobra 47e45453a7 Add BLE alerter configuration to operator_setup.sh (mode, devices, timeout, Matrix creds) 2026-04-10 06:32:29 -04:00
Cobra 7ef6fc7c3f Update deploy-daemon.sh to deploy both net_alerter and ble_alerter 2026-04-10 06:31:55 -04:00
Cobra 485b124c11 Add unit tests for BLE alerter (mocked bleak, async scenarios) 2026-04-10 06:31:41 -04:00
Cobra a40e86be10 Add systemd unit for BLE alerter (after bluetooth.target) 2026-04-10 06:31:39 -04:00
Cobra 77b0cd6512 Add BLE alerter daemon (passive scan, named-device tracking, Matrix alerts) 2026-04-10 06:31:38 -04:00
Cobra 71cd3332d2 Fix ARRIVED spam — mark departing instead of popping from known_devices
When on_departure popped the device from known_devices, rapid RTM_NEWNEIGH
events during ARP cache rebuild each triggered on_arrival as if the device
was brand new, flooding the Matrix room with 5-10+ duplicate ARRIVED alerts.

Fix: keep device in known_devices with departing=True flag. on_arrival
silently cancels pending departure timers and returns without alerting when
it sees this flag. Timer callback checks flag before sending the alert and
only pops the device when departure is confirmed after 15 minutes.
2026-04-09 14:34:46 -04:00
Cobra b66380f25d Fix on_arrival() to always cancel departure timer on device re-arrival
- Bug: device departs → timer starts, but last_departed_time not set yet
  If device re-arrives before timer fires, timer never cancelled
  Result: false DEPARTED alert sent after re-arrival debounce expires

- Fix: Cancel any pending departure timer immediately on re-arrival,
  before checking last_departed_time

- Test: Added regression test that verifies timer is cancelled when
  device re-arrives before 15-min debounce expires, preventing false alerts

- All 10 tests passing
2026-04-09 13:28:34 -04:00
Cobra 0e178e34d4 Deploy OUI database as part of net_alerter daemon installation
- scp oui.db to /opt/net_alerter/oui.db after daemon script
- Verify DB on remote via Python/sqlite3
- Confirm row count on target host
2026-04-09 13:05:34 -04:00
Cobra 4ffe29d4d3 Add SQLite OUI database lookup to net_alerter
- lookup_oui() now tries SQLite first (/opt/net_alerter/oui.db)
- Falls back to inline OUI_DICT if DB missing or query fails
- No network calls, local SQLite reads only
- Debug-level logging for failures (no output noise)
2026-04-09 13:05:18 -04:00
Cobra a3c9353d3a Add false positive suppression to net_alerter — fix for #467
- Infrastructure IPs (gateway, self, broadcast) silently ignored in
  on_arrival() and on_departure(); seeded at startup from 'ip route'
- 15-minute departure debounce: alert only fires if device is still
  absent after 900s, eliminating ARP cache flush false positives
- 5-minute re-arrival suppression: ARRIVED alert skipped when device
  returns within 5 min of a recorded departure (covers interface flap)
- DHCP renewal dedup: known device re-announcing within 30 min skips
  ARRIVED alert
- Tests added for all four suppression paths (9 total, all passing)
2026-04-09 09:44:37 -04:00
Cobra 82228d79b3 Append QA review findings to consolidated security report
Delta reviewer validation completed:
- 12-item checklist validated (10 PASS, 2 PASS WITH CORRECTIONS, 1 FAIL)
- All 101 agent findings verified as complete and accurate
- 3 corrective actions identified: APT-001 deconfliction, bidirectional cross-refs, dedup transparency
- Report ready for remediation planning with low-effort corrections
2026-04-09 08:31:26 -04:00
Cobra 87e7587136 Append 4 operator Q&A sections with detailed exploitation scenarios and OPSEC analysis 2026-04-09 08:25:52 -04:00
Cobra d4354074c8 Make net_alerter alerts readable — fix hostname dedup and inline OUI lookup
- Replace broken OUI file lookup with inline dict of 200+ common vendors (works on Armbian)
- Fix hostname/IP duplication when reverse DNS fails (show IP once, not twice)
- Add format_arrival() and format_departure() helpers for consistent alert formatting
- Update on_arrival/on_departure to use new formatting functions
- Add comprehensive tests for hostname dedup and OUI lookup
2026-04-08 22:40:35 -04:00
Cobra 4d2e46e478 Update module count assertions to reflect reverse_tunnel removal 2026-04-08 22:26:06 -04:00
Cobra ffd384f64b Strip OPSEC tool identity fingerprints
Replace all sensor.* logger namespaces with __name__ (generic module
identifiers instead of discoverable 'sensor.*' prefixes).

Change hardcoded 'bb' API user to 'admin' in config and code defaults.

Change hardcoded relay_user 'bb' to 'operator' — prevents network
profiling from exposing tool identity via SSH config.

Fixes #457, #458, #459
2026-04-08 22:18:35 -04:00
Cobra b34aab7986 Add net_alerter README with quick start guide 2026-04-08 21:56:28 -04:00
Cobra 424b4f4552 Add Orange Pi Zero 3 specific deployment guide 2026-04-08 21:56:03 -04:00
Cobra a54acf32b8 Add net_alerter daemon deployment documentation 2026-04-08 21:55:39 -04:00
Cobra 52872794ee Add deployment script for net_alerter daemon 2026-04-08 21:55:18 -04:00
Cobra 431571dd82 Rebuild net_alerter as persistent daemon — DHCP sniffer + Netlink RTM_NEWNEIGH/DELNEIGH; removes 5-min cron, zero active probing 2026-04-08 21:55:02 -04:00
Cobra 402c4e849b Replace nmap ARP scan with passive /proc/net/arp read — no active probing (#449)
- Removed get_local_subnets() and scan_subnet() — no longer needed
- Added read_arp_cache() to read kernel ARP table from /proc/net/arp
- Added fallback to 'ip neigh show' if /proc/net/arp unavailable
- Added lookup_oui() to query system /usr/share/hwdata/oui.txt for vendor names
- Removed subprocess nmap call and re import (no longer needed)
- run() now directly reads ARP cache instead of scanning subnets
- Zero active network traffic — purely passive monitoring
- Removed sudo requirement
2026-04-08 21:01:12 -04:00
Cobra 57d70feb20 Add missing detect_interface_with_retry function to fix engine startup 2026-04-08 20:59:35 -04:00
Cobra 3153b7e8a3 Robust interface detection — retry fallback chain for headless boot (#206)
Fixes interface auto-detection failures on Pi Zero 2W where wlan0 doesn't exist
at boot. Implementation:

- Added detect_interface_with_retry() function with 3x retry fallback chain
- Config-supplied interface preferred, then /proc/net/route, then UP interface
- Exponential backoff: 5s, 10s, 20s delays between retries
- Filters excluded prefixes: lo, tailscale*, wg*, tun*, docker*, veth*, br-*
- Used in Engine.__init__(), PacketCapture.start(), MacManager.start()
- Full test coverage: 10 tests for detection, backoff, filtering

Addresses issue where network stack initializes after boot and interfaces
become available after 5-10 second delay.
2026-04-08 20:58:43 -04:00
Cobra 1f43d97f03 Add test to verify Engine instantiates and injects CaptureBus into passive modules (#204) 2026-04-08 20:57:10 -04:00
Cobra 967fac5ce5 Enrich net_alerter alerts with OUI vendor and reverse DNS hostname
Parse nmap vendor name from MAC Address line (filtering out 'Unknown').
Fall back to socket.gethostbyaddr for IPs nmap didn't resolve a hostname
for. Alert label is now: 'hostname (vendor)' | 'hostname' | 'vendor' |
'unknown', in that priority order.
2026-04-08 20:31:49 -04:00
Cobra d9c27eaf66 Fix Cloudflare bot detection blocking net_alerter Matrix sends
urllib's default Python-urllib user agent is flagged by Cloudflare Bot
Fight Mode (error 1010) when m.example.org homeserver sits behind
Cloudflare. Add a browser-like User-Agent to Matrix PUT/POST requests so
Cloudflare passes the traffic through.
2026-04-08 20:19:24 -04:00

Diff Content Not Available