ccc6b729de
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.
278 lines
16 KiB
Markdown
278 lines
16 KiB
Markdown
---
|
||
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
|