Files
bigbrother/presence/FIXES.md
T
n0mad1k ccc6b729de Initial public release
Full BigBrother network implant - passive SOC + active exploitation.
Personal identifiers removed; all capabilities intact.
See README.md for setup and docs/deployment.md for detailed deployment.
2026-06-26 09:52:50 -04:00

278 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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