T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/query_live_hr.py:81- Finding
Live heart-rate queries do not enforce user-level data isolation
- Content
View full analysis
= ?"] params: list[object] = [since] if args.session_id: clauses.append("session_id = ?") params.append(args.session_id) if args.device_id: clauses.append("device_id = ?") params.append(args.device_id) where = " AND ".join(clauses) sql = f""" SELECT session_id, seq, ts, value, unit, source_kind, source_vendor, device_id, source_device_name, received_at FROM live_events WHERE {where} ORDER BY ts DESC LIMIT ? """ params.append(args.limit) rows = conn.execute(sql, params).fetchall() ``` ### Technical Analysis The query applies time, session, and device filters but does not require a user identifier or include a `user_id = ?` ownership condition. This differs from `query_health.py` and `query_whoop.py`, which require `AHB_USER_ID` or `--user-id` and scope their SQL queries accordingly. If `live_events` contains records for multiple users, the documented default invocation returns recent records belonging to every user. Optional `session_id` and `device_id` parameters narrow the result but do not establish authorization. The selected fields include heart-rate values, precise event timestamps, session identifiers, and device metadata. The dynamically assembled SQL is not itself an SQL-injection issue because the clause strings are fixed and user-controlled values are parameterized. The vulnerability is the absent ownership boundary. ### Attack Path 1. Multiple users' live heart-rate events are stored in the same SQLite `live_events` table. 2. A caller obtains permission to invoke the Skill or directly execute `scripts/query_live_hr.py`. 3. The caller runs the documented command without an ownership filter: ```bash python scripts/query_live_hr ...[truncated 967 chars]- Remediation
View remediation
= ?"] params = [args.user_id, since] ``` 3. Reject execution when no user identifier is available. 4. Ensure the user identifier is derived from an authenticated context rather than accepted solely as an arbitrary caller-controlled value. 5. Verify that the database schema associates every `live_events` row with a non-null owner. 6. Add tests with at least two users to confirm that one user's query never returns another user's events. 7. Omit `device_id`, `source_device_name`, and similar metadata by default. Return them only when required by the user's request. 8. Where feasible, use separate databases or database views per user to provide defense in depth. ]]>
