Skip to content

Security review: CarWatch dashboard (:8088) surface #14

Description

@ThinkOffApp

Scope

Security review of CarWatch itself — the Python in carwatch/ and the
in-car dashboard it serves on :8088. That port listens on all interfaces
and carries the sensitive controls (OBD commands, self-update, the
manufacturer-cloud provider), so it is the real attack surface.

Note for reviewers: findings about su2 / xforbot / /api/sync /
Supabase belong to other codebases, not this repo. CarWatch is pure
Python standard library — no Supabase, no JS, no service-role keys
(verified). Please review against carwatch/*.py only.

Auth model (context)

webchat.py gates by network origin: a request that did not arrive
through the dial-out tunnel (i.e. it is on the same LAN/hotspot as the Pi) is
trusted and needs no token; a request that came through the tunnel must carry
the dashboard token (Authorization: Bearer or ?t=). do_GET and
do_POST both enforce this.

Findings

1. HA token can be exfiltrated by a same-LAN device (real, fix in progress)

POST /api/cloudcar/ha-url repoints the Mercedes provider at an arbitrary
URL, and the provider then sends the Home Assistant Bearer token to that
URL on its next poll. Because same-LAN requests are unauthenticated, any
device sharing the Pi's LAN/hotspot can point the provider at an attacker
host and capture the HA token — which grants persistent remote control of the
victim's whole Home Assistant, even after leaving the LAN. This is an
escalation beyond the existing LAN-trust boundary (LAN access → permanent
remote HA access).

Fix: only ever send the HA token to a private / loopback / Tailscale /
.local target; refuse public hosts. This also matches the
Tailscale remote-access direction (HA
should be reached over a private mesh, never a public URL). Landing with the
Tailscale switch so cloud access has no downtime.

To review (open)

  • webchat.py: the tunnel-origin auth (header-based _came_through_tunnel)
    and whether any sensitive action should require the token even on-LAN.
  • mercedesme.py: the command allowlist (lock / close-windows only — unlock/
    open/engine are intentionally not expressible) and VIN handling (kept out
    of serialized state).
  • elm327.py / obdwatch.py: input handling on the serial/CAN parse path.
  • /api/update reachable from anywhere with the token — confirm that is the
    intended trade-off.

Not affected

No secrets in the repo or its history (independent scans clean). Zero
third-party dependencies. The manufacturer-cloud module is read-only by
construction except the two make-safe, PIN-free commands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions