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

16 KiB
Raw Blame History

agent, status, timestamp, total_findings_raw, total_findings_deduped, p1_count, p2_count, p3_count, p4_count, devtrack_items_created, errors
agent status timestamp total_findings_raw total_findings_deduped p1_count p2_count p3_count p4_count devtrack_items_created errors
fix-planner COMPLETE 2026-04-10T13:45:00Z 32 29 4 6 12 7
551
552
553
554
555
556
557
558
559
560

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)

  • [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
  • [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
  • [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:
      # 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:
      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:
        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
  • [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:
      # 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:
      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():
      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