Tags: ulm0/argus
Tags
Fix/codebase audit (#30) * fix: resolve bugs, races, and perf issues across backend and web Codebase-wide audit fixing ~120 verified issues. Data-loss / corruption: - cleanup: reject malformed execute body instead of defaulting to a real (non-dry-run) deletion - quick_edit: require a clean RW unmount before restoring the gadget LUN, so the car and local kernel never write one FAT image at once; wait out the callback on timeout; refresh the lock mtime while busy - mount: kill only foreign mount holders on unmount retry, never the argus daemon itself (was `fuser -km`) - loop: drop LO_FLAGS_AUTOCLEAR so the device can't self-detach before callers use it - fsck: refuse to check an image that is live (present mode or an open quick-edit window) - chime: reject writes when the partition is unmounted instead of writing to CWD and reporting success Correctness: - api: share one chime.Service so schedule/group edits take effect and are not clobbered by the running scheduler - telegram: run watcher/queue regardless of boot-time enabled so runtime enable works; guard config fields and queue persistence - telegram/watcher: retry event video lookup instead of dropping events whose clips are not yet written - config: validate the whole PATCH before mutating live config; store generated config 0600 (holds secrets) - web: fix folder-drop >100 file loss, schedule loading, upload error handling, SSE reconnect, fetch races, and audio/interval leaks Security: - login brute-force backoff, Sec-Fetch-Site CSRF guard, X-Real-IP/XFF validation, and no default-cred disclosure to anonymous callers Perf: - stream uploads, skip gzip on binary media, bound SEI parser allocations, and stop hidden SystemPanel polling on phones Also removes dead internal/system/fsck package. * chore(web): bump postcss and @babel/core past Dependabot alerts next 16.2.6 pulls postcss 8.4.31 (GHSA-qx2v-qp2m-jg93) and @babel/core 7.29.0 (GHSA-4x5r-pxfx-6jf8) transitively. Pin patched versions via pnpm overrides (workspace file — pnpm 11 no longer reads package.json pnpm.overrides). * fix(security): close path-traversal gaps flagged by CodeQL Adversarial audit of the 37 go/path-injection alerts: 28 are false positives (isWithinDir/withinBase after filepath.Clean, which CodeQL's query does not recognize), 9 are real defense-in-depth gaps, fixed here. - music: validate temp path (destPath+".tmp"/".assembling") and the MoveFile destination dir; a filename collapsing destPath to the music root let the atomic-write temp file / mkdir land in a sibling dir outside the root - video: reject ".." event names in GetEventDetails before they reach the parseEvent read chain - video handlers: verify Thumbnail/SessionThumbnail thumbPath stays within ThumbnailDir before stat/generate/serve
fix: resolve bugs, races, and perf issues across backend and web (#29) Codebase-wide audit fixing ~120 verified issues. Data-loss / corruption: - cleanup: reject malformed execute body instead of defaulting to a real (non-dry-run) deletion - quick_edit: require a clean RW unmount before restoring the gadget LUN, so the car and local kernel never write one FAT image at once; wait out the callback on timeout; refresh the lock mtime while busy - mount: kill only foreign mount holders on unmount retry, never the argus daemon itself (was `fuser -km`) - loop: drop LO_FLAGS_AUTOCLEAR so the device can't self-detach before callers use it - fsck: refuse to check an image that is live (present mode or an open quick-edit window) - chime: reject writes when the partition is unmounted instead of writing to CWD and reporting success Correctness: - api: share one chime.Service so schedule/group edits take effect and are not clobbered by the running scheduler - telegram: run watcher/queue regardless of boot-time enabled so runtime enable works; guard config fields and queue persistence - telegram/watcher: retry event video lookup instead of dropping events whose clips are not yet written - config: validate the whole PATCH before mutating live config; store generated config 0600 (holds secrets) - web: fix folder-drop >100 file loss, schedule loading, upload error handling, SSE reconnect, fetch races, and audio/interval leaks Security: - login brute-force backoff, Sec-Fetch-Site CSRF guard, X-Real-IP/XFF validation, and no default-cred disclosure to anonymous callers Perf: - stream uploads, skip gzip on binary media, bound SEI parser allocations, and stop hidden SystemPanel polling on phones Also removes dead internal/system/fsck package.
Security/bug/perf fixes + web UI login (#27) * fix: patch path traversal, AP config injection, token leak, and update-check bugs Security and correctness fixes found during a code review of the API, service, and system layers: - music: HandleChunk performed no path-traversal validation while reading filename/path from raw form values (not sanitized by the stdlib like multipart filenames are), allowing an arbitrary file write. Validate the upload ID, sanitize the filename to its base, and constrain the target and destination to the music directory. Add the same containment backstop to SaveFile. - chime: UploadChime wrote the upload before any containment check, unlike its sibling methods. Add the missing path-traversal guard. - ap: SSID/passphrase/DHCP/CIDR values were templated into hostapd.conf and dnsmasq.conf without escaping, so an embedded newline could inject directives (e.g. wpa=0 to disable AP encryption). Validate inputs in UpdateAPConfig and refuse to emit a config containing control characters as a backstop for the PATCH /api/config path. - telegram: network errors are *url.Error and embed the request URL, which contains the bot token; redact the token before returning/logging it. - config: check_on_startup was a plain bool force-set to true whenever false, so an explicit `check_on_startup: false` could never disable startup update checks. Make it a *bool (like samba_enabled) with a CheckUpdateOnStartup() accessor that defaults to true only when unset. - logs: validate the systemd unit name against the unit-name charset. - video: cap thumbnail width/height to bound ffmpeg work. Adds regression tests for the music traversal fix and the update-check default. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * fix(web,build): MP4 parser DoS, URL encoding, resource leaks, and build fragility Frontend and packaging fixes from an audit of the web UI, installer, CI, and build config (the Go API XSS surface was reviewed and found clean — the only dangerouslySetInnerHTML is a static literal). Frontend: - sei-parser: findBox could infinite-loop on a malformed MP4 whose 64-bit extended box size is 0 (or smaller than its own header) — `pos += size` never advanced, hanging the synchronous main-thread parse on attacker- supplied USB bytes and freezing the tab. Guard with `if (size < headerSize) break;`. - api: streamURL/downloadURL/seiURL/telemetryURL/playURL/deleteFile/deleteDir interpolated server-controlled path segments (video/music filenames from the Tesla USB) into request URLs without encoding, unlike every other builder. A name containing #/?/%/&/= could truncate the path, inject a query string, or (for the delete endpoints) target the wrong resource. Add an encodePath() helper that percent-encodes each segment while preserving separators. - Resource leaks: revoke loaded-MP4 blob URLs on unmount (videos/local), close the previous AudioContext before opening a new one (AudioTrimmer), and pause audio playback on unmount (music page). Build/packaging: - install-argus.sh: reject unsupported architectures instead of silently installing the armv6 binary, and verify the downloaded binary against the release's published sha256 checksum before installing it as root. - Makefile: use pnpm (the repo ships only pnpm-lock.yaml; goreleaser uses pnpm) and stage the static export into cmd/argus/web/out where the binary embeds it, so `make all` actually works. - Add cmd/argus/web/out/.gitkeep so `//go:embed all:web/out` compiles on a clean checkout before the frontend is built. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * fix(api,services): close arbitrary-delete, OOM, panic, and concurrency bugs Backend fixes from an exhaustive adversarially-verified audit of internal/ and cmd/ (19 confirmed findings; the genuinely serious ones first). Security / DoS: - cleanup: cleanup folder-policy keys were joined onto the TeslaCam path and fed to os.RemoveAll with no validation, so an unauthenticated client could POST a key like "../../etc" and have the root process recursively delete arbitrary directories. Reject non-single-segment keys in SavePolicies and skip unsafe keys in CalculateCleanupPlan (also protects the boot-time cleanup path). - router: the request-body size limit (and gzip skip) was bypassable by sending Accept: text/event-stream on any upload POST, removing the only cap before handlers io.ReadAll the body into RAM. Gate the SSE exemption on the actual /api/events/stream and /api/logs routes instead of the client header. - chime: ValidateTeslaWAV allocated make([]byte, chunkSize) from an untrusted uint32, so a tiny crafted WAV could force a ~4 GiB allocation. Reject a chunk larger than the file. - video: bound the mvhd box payload in scanBoxes (same unbounded-alloc class). - telemetry: a crafted protobuf length-delimited field could drive the parse position negative and panic on data[pos]; bound the skip in 64-bit space. - lightshow: UploadZip extracted with no per-entry/total cap (zip bomb / disk fill); add per-entry, total, and entry-count limits via io.CopyN. Concurrency / correctness: - chime: GetSchedule returned an interior pointer into the mutex-protected slice; return a copy so callers can't race the per-minute tick / reslices. - fsck: Cancel cleared the running flag while the worker was alive, letting a racing Start spawn a second worker; only the worker clears running now. - config: serialize Save() so concurrent savers can't clobber the shared .tmp file. (The broader cfg field-write-vs-marshal race is documented for a follow-up that routes all writers through one lock.) - updater: restore the backup binary if the swap rename fails, so a failed update can't leave the service with no executable. Hardening: - config PATCH: validate offline_ap SSID/passphrase/CIDR/IP/ping_target like UpdateAPConfig does, instead of writing them through unchecked. - ap: tighten interface-name validation (no '/', no leading '-'). - wifi: reject a ping target that ping would parse as a flag. - webhook/telegram: drain response bodies before Close for keep-alive reuse. - realip: handle a leading-comma X-Forwarded-For without blanking RemoteAddr. Adds regression tests for the cleanup traversal, SEI panic, and WAV allocation. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * fix(security): add explicit path-confinement barriers at delete/extract/upload sinks Resolve a CodeQL high-severity path-injection finding and harden the adjacent sinks. The existing guards used filepath.Base equality / ContainsAny checks, which confine the path in practice but are not recognized as sanitizers by static analysis, leaving the os.RemoveAll / os.Create sinks flagged. Add the canonical HasPrefix(joined, base+separator) containment check (the same withinBase pattern already used in the video handler) at each sink: - cleanup: confine the resolved folder path under the TeslaCam root before it is used to build the event paths ExecuteCleanup removes. - lightshow: confine the ZIP extraction target under the show directory. - music: confine the chunk-upload scratch directory (derived from upload_id) under the music directory. No behavior change for valid inputs; these make the traversal-safety invariant explicit and analyzable. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * harden(server): set ReadHeaderTimeout and MaxHeaderBytes The HTTP server had no read timeout, so a slow-header (slowloris) client could hold a connection open indefinitely on the single-core Pi. Bound header reads and header size. Write/Idle timeouts are intentionally left unset so they don't truncate the long-lived SSE streams (/api/logs, /api/events/stream). Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * feat(auth): add login with a default credential Gate the web UI/API behind a session login, since the device's AP exposes the API (which runs as root) to anyone in radio range, and the no-auth API was also open to DNS-rebinding/CSRF from a site the operator visits while connected. Backend: - config: add web.auth_enabled (*bool, default true), auth_username, and auth_password, shipping with default credentials admin / argus. AuthEnabled() and UsingDefaultAuth() accessors. - auth: stateless HMAC-signed session tokens (internal/auth), signed with the previously-unused web.secret_key, with a 7-day TTL and constant-time checks. - handlers: POST /api/login, POST /api/logout, GET /api/auth/status. Login compares credentials in constant time and sets an HttpOnly, SameSite=Strict session cookie (the SameSite attribute also blunts CSRF). - middleware: RequireAuth gates every /api/ route except the auth endpoints; non-API routes (static UI shell, captive-portal probes) stay open so the login page can load. Returns 401 JSON when unauthenticated. - config GET/PATCH: expose/edit auth settings (password is write-only and never returned); credentials are changeable from the settings API. Frontend: - AuthGate wraps the app: checks /api/auth/status on mount and renders a login form when auth is enabled and the user isn't authenticated. - the API client dispatches a global event on any 401 so an expired session re-prompts for login without each caller handling it. - SystemPanel gains a Sign out button. Note: the default credentials are intentionally weak and meant to be changed; the login screen and settings surface a "using default credentials" nudge. Tests cover session sign/verify/expiry/tamper and the login + gating flow. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * fix(auth): set cookie Secure attribute when the request is over HTTPS CodeQL flagged the session cookies for not setting Secure. Argus serves plain HTTP on the local network/AP by default, so forcing Secure unconditionally would stop the browser from ever sending the cookie and break login. Instead set Secure when the request actually arrives over TLS — directly (r.TLS) or via a TLS-terminating reverse proxy (X-Forwarded-Proto: https) — which protects proxied deployments without breaking the default HTTP one. Spoofing the header only makes the cookie more restrictive, so it can't weaken the session. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * docs(readme): document the web UI login and default credentials Add an "Accessing the web UI" section (default admin / argus, how to change or disable, session-cookie details), a web-interface feature bullet, and a `web` row in the config sections table. Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH * feat(security): DNS-rebinding Host check + default-credential startup warning Per review: keep the default login (admin/argus) but make the device advise changing it, and add the DNS-rebinding defense discussed on the auth work. - middleware: RequireLocalHost rejects /api/ requests whose Host header isn't a local identity (IP literal, localhost, bare single-label name, *.local/*.lan) or an operator-configured host. Blocks a malicious public site that rebinds its domain to the device's LAN IP from driving the API in a victim's browser. Root paths (captive-portal probes, hit with public Host headers on purpose) and static assets are not gated. - config: web.allowed_hosts lets reverse-proxy/custom-domain deployments add their FQDN to the allowlist. - run: log a warning at startup when the default credentials are still in use, and when auth is disabled entirely, so headless operators are nudged. - docs: document allowed_hosts and the rebinding defense in the README. Tests cover the Host allow/deny matrix (local identities pass, public domains are blocked, non-API paths are exempt). Claude-Session: https://claude.ai/code/session_016vMstjHpKgvddCfBPTSrRH --------- Co-authored-by: Claude <noreply@anthropic.com>
fix(ap): evict stale dnsmasq/hostapd before restarting AP (#20) After a crash or unclean shutdown the previous dnsmasq instance keeps holding the 192.168.4.1:53 socket, causing "Address already in use" on the next startAPLocked call. Kill orphaned processes (same pattern used by stopAPLocked) before touching the interface so the new dnsmasq can bind successfully. https://claude.ai/code/session_0168SwBt9HqT1qU9VirQD9Js Co-authored-by: Claude <noreply@anthropic.com>
fix(release): migrate pnpm build approvals to pnpm-workspace.yaml (#19) pnpm 11 dropped support for the pnpm field in package.json. Move sharp and unrs-resolver build approvals to allowBuilds in pnpm-workspace.yaml per the pnpm 11 migration guide. https://claude.ai/code/session_01EJxAoYdTJTqdidy1oZeJdz Co-authored-by: Claude <noreply@anthropic.com>
Claude/single video download (#14) * revert: restore Inter font, remove Universal Sans setup https://claude.ai/code/session_01YL6zD5ZwvrzNQLXm8EggEM * fix: composed view default, HUD in composed mode, proper pedal icons - DashcamPlayer opens in composed view by default. - ArgusHUD is now rendered over the master (front) camera cell in composed mode; HUD toggle button is always visible regardless of mode. - Pedal icons replaced with solid T-shaped silhouettes matching Tesla's official dashcam viewer: brake = wide plate + stem, throttle = narrower plate + stem. https://claude.ai/code/session_01YL6zD5ZwvrzNQLXm8EggEM * feat: GPS map overlay using SEI telemetry data Adds an interactive map overlay to the dashcam player using Leaflet with CartoDB dark tiles. Shows the driven route as a polyline and a heading-aware position marker that follows the vehicle in real-time as the video plays. - MapOverlay.tsx: client-side Leaflet map, dynamically imported (ssr:false) to avoid server-side window access errors - Map toggle button (map pin icon) in the player controls bar - mapEnabled persisted in localStorage alongside hudEnabled - SEI telemetry is shared between HUD and map (loaded once, used by both) - Overlay positioned bottom-right of video area in both single and composed modes - GPS frames with lat/lng = 0,0 filtered out (protobuf default = no fix) * feat: enable map overlay by default * feat: resizable map/HUD, map theme setting, light tiles by default * feat: realistic pedal icons (outline style, brake wide pad, gas angled arm) * fix: add missing react-icons dependency * feat: single video download button in player controls and per-camera cells --------- Co-authored-by: Claude <noreply@anthropic.com>
PreviousNext