Skip to content

Refactor: improve code quality, security, and testability#5

Open
TerrifiedBug wants to merge 10 commits intomainfrom
refactor-5f2
Open

Refactor: improve code quality, security, and testability#5
TerrifiedBug wants to merge 10 commits intomainfrom
refactor-5f2

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

  • Security fix: Replace fragile filepath.Clean + strings.Contains("..") path validation with robust safePath() helper using filepath.Rel to prevent directory traversal in 3 web handlers
  • Bug fix: WebSocket Broadcast() now removes dead connections instead of accumulating stale entries; config.Get() returns defensive copy to prevent mutation leaks
  • Code quality: Extract magic numbers to named constants across 4 packages, deduplicate gadget retry logic into tryEnableGadget(), decompose 67-line runArchiving into focused helpers, extract shared NFS/CIFS mount-test scaffolding
  • Dependency injection: Introduce 6 interfaces (DiskOps, GadgetOps, ArchiveOps, SystemOps, KeepAwaker, Notifier) with concrete adapters in internal/wire, making the state machine fully unit-testable with mock implementations

Files Changed

  • New: internal/web/pathutil.go, internal/web/pathutil_test.go, internal/web/mounttest.go, internal/web/ws_test.go, internal/state/deps.go, internal/wire/wire.go
  • Modified: internal/state/machine.go, internal/state/machine_test.go, internal/web/server.go, internal/web/ws.go, internal/config/config.go, internal/config/config_test.go, internal/web/server_test.go, internal/disk/disk.go, internal/gadget/idle.go, internal/archive/archive.go, cmd/teslausb/main.go

Test plan

  • All existing tests pass (go test ./...)
  • New TestSafePath covers 8 path traversal scenarios
  • New TestHubBroadcastRemovesDeadClients verifies dead WS cleanup with real httptest server
  • New TestGetReturnsCopy proves config mutation isolation
  • New state machine tests (TestTryEnableGadget, TestRunAwayTransitionsOnReachable, TestRunIdleNotifiesOnGadgetEnable) use mock dependencies
  • Full build succeeds (go build ./...)

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR is a well-structured refactoring of the teslausb-go codebase, introducing dependency injection across the state machine, extracting shared helpers, fixing path traversal vulnerabilities, and cleaning up WebSocket dead-connection handling. The vast majority of the changes are clean improvements with good test coverage.

Key changes:

  • safePath() replaces the fragile filepath.Clean + strings.Contains("..") approach with a filepath.Rel-based check across all three file-serving handlers — a meaningful security fix
  • Broadcast() now removes dead WebSocket connections, preventing unbounded map growth
  • config.Get() returns a defensive shallow copy — correct since all Config fields are value types
  • Six interfaces in internal/state/deps.go + concrete adapters in internal/wire/wire.go make the state machine fully unit-testable
  • Magic numbers extracted to named constants across disk, gadget, and archive packages

Issues found:

  • In runArriving, the two fallback Enable() calls (on Disk.Mount failure and Archive.MountArchive failure) do not update m.gadgetEnabled after re-enabling the gadget. Because Disable() sets the flag to false earlier in the same function, the flag stays false even after the gadget has been re-enabled. This causes tryEnableGadget() to call Enable() again on the next runAway tick — potentially triggering redundant UDC rebinds on the Pi, which the hardware docs flag as a risk for USB disconnects visible to Tesla.
  • In the Disable() failure path, m.gadgetEnabled is incorrectly set to false despite the gadget remaining active; the flag should be left as true to prevent the same repeated-Enable problem.

Confidence Score: 3/5

  • Safe to merge after fixing the two gadgetEnabled flag inconsistencies in runArriving — the rest of the PR is a clean, well-tested refactoring.
  • The refactoring is broadly correct and well-tested, but the gadgetEnabled flag mistracking in runArriving error paths is a real correctness bug introduced by this PR: it can cause repeated Enable() calls on a live USB gadget, which the hardware docs flag as a potential kernel/Tesla-visibility issue on the Pi Zero. Everything else — safePath, Broadcast cleanup, config copy, DI interfaces, constants — looks solid.
  • internal/state/machine.go — specifically the runArriving function's error branches for Disable failure (line 208) and fallback Enable calls (lines 220, 230).

Important Files Changed

Filename Overview
internal/state/machine.go Core state machine refactored with dependency injection; two related bugs around gadgetEnabled flag management in the runArriving error paths — fallback Enable() calls don't update the flag, and the Disable() failure path sets it to false while the gadget remains active.
internal/state/deps.go New file defining six clean interfaces (DiskOps, GadgetOps, ArchiveOps, SystemOps, KeepAwaker, Notifier) for dependency injection; no issues found.
internal/wire/wire.go New adapter layer wiring concrete package functions to the state.Deps interfaces; straightforward delegation, no issues found.
internal/web/pathutil.go New safePath() helper using filepath.Rel for robust path-traversal prevention; correctly handles all test vectors including nested traversals and absolute-path injection.
internal/web/ws.go Broadcast() now collects dead connections and removes them after the iteration loop, preventing unbounded accumulation of stale WebSocket entries; clean implementation.
internal/web/server.go File/download/delete handlers now use safePath() for path-traversal protection; CIFS test endpoint uses a fixed /tmp/.cifs-test-credentials path which could be raced by concurrent requests, but low-risk on a personal Pi device.
internal/config/config.go Get() now returns a shallow copy of the config struct; safe because all Config fields are value types; nil guard added; clean implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Boot([Boot]) --> Run
    Run["Run()\nDisk.Exists? → Create\nGadget.Enable → gadgetEnabled=true"] --> Away

    Away["runAway()\nevery 30s: tryEnableGadget()\nArchive.IsReachable?"] -->|reachable| Arriving
    Away -->|ctx.Done| Stop([Stop])

    tryEnable["tryEnableGadget()\ngadgetEnabled==true → skip\nGadget.Enable → gadgetEnabled=true"] -.->|called from| Away
    tryEnable -.->|called from| Idle

    Arriving["runArriving()\nGadget.WaitForIdle\nGadget.Disable → gadgetEnabled=false"]
    Arriving -->|Disable fails| DisableErr["gadgetEnabled=false ⚠️\n(gadget still active)\n→ StateAway"]
    Arriving -->|Disk.Mount fails| MountErr["Gadget.Enable (no flag update ⚠️)\n→ StateAway"]
    Arriving -->|Archive.MountArchive fails| ArchErr["Gadget.Enable (no flag update ⚠️)\n→ StateAway"]
    Arriving -->|success| Archiving

    DisableErr --> Away
    MountErr --> Away
    ArchErr --> Away

    Archiving["runArchiving()\nstartKeepAlive\nArchive.ArchiveClips\nupdateAndPersistStats\nArchive.ManageFreeSpace"] --> Idle

    Idle["runIdle()\nGadget.Enable → gadgetEnabled=true\nevery 30s: tryEnableGadget\nArchive.IsReachable?"] -->|unreachable| Away
    Idle -->|ctx.Done| Stop
Loading

Last reviewed commit: 09a825e

Comment on lines +218 to 222
if err := m.deps.Disk.Mount(); err != nil {
log.Printf("mount cam: %v", err)
gadget.Enable(disk.BackingFile)
m.deps.Gadget.Enable(m.deps.Disk.BackingFilePath())
m.setState(StateAway)
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gadgetEnabled not updated after fallback Enable() calls

When Disk.Mount() fails (and similarly when Archive.MountArchive() fails at line 229), the code calls Enable() to restore the gadget, but m.gadgetEnabled is never set back to true. It was set to false a few lines earlier (line 214) when Disable() succeeded. This leaves the flag permanently false while the hardware is actually enabled.

The consequence: on the very next tick of runAway, tryEnableGadget() sees gadgetEnabled == false and calls Enable() again on an already-configured USB gadget. The architecture notes flag this as critical: calling Enable() when the gadget is already set up writes to /sys/kernel/config/usb_gadget/teslausb/ and rebinds the UDC, which can briefly disconnect the USB drive from Tesla or trigger a kernel oops on the Pi Zero.

Both fallback paths should update the flag:

if err := m.deps.Disk.Mount(); err != nil {
    log.Printf("mount cam: %v", err)
    if enableErr := m.deps.Gadget.Enable(m.deps.Disk.BackingFilePath()); enableErr == nil {
        m.gadgetEnabled = true
    }
    m.setState(StateAway)
    return
}

Same fix applies to the Archive.MountArchive() error branch at line 229.

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/state/machine.go
Line: 218-222

Comment:
**`gadgetEnabled` not updated after fallback `Enable()` calls**

When `Disk.Mount()` fails (and similarly when `Archive.MountArchive()` fails at line 229), the code calls `Enable()` to restore the gadget, but `m.gadgetEnabled` is never set back to `true`. It was set to `false` a few lines earlier (line 214) when `Disable()` succeeded. This leaves the flag permanently `false` while the hardware is actually enabled.

The consequence: on the very next tick of `runAway`, `tryEnableGadget()` sees `gadgetEnabled == false` and calls `Enable()` again on an already-configured USB gadget. The architecture notes flag this as critical: calling `Enable()` when the gadget is already set up writes to `/sys/kernel/config/usb_gadget/teslausb/` and rebinds the UDC, which can briefly disconnect the USB drive from Tesla or trigger a kernel oops on the Pi Zero.

Both fallback paths should update the flag:

```go
if err := m.deps.Disk.Mount(); err != nil {
    log.Printf("mount cam: %v", err)
    if enableErr := m.deps.Gadget.Enable(m.deps.Disk.BackingFilePath()); enableErr == nil {
        m.gadgetEnabled = true
    }
    m.setState(StateAway)
    return
}
```

Same fix applies to the `Archive.MountArchive()` error branch at line 229.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +208 to 213
if err := m.deps.Gadget.Disable(); err != nil {
log.Printf("disable gadget: %v", err)
m.gadgetEnabled = false
m.setState(StateAway)
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gadgetEnabled set to false despite Disable() failing

When Disable() returns an error, the gadget is still enabled — the Disable call did nothing. Setting m.gadgetEnabled = false here misrepresents the hardware state. Back in runAway, tryEnableGadget() will see gadgetEnabled == false and call Enable() on a gadget that never stopped running. Depending on whether Enable() is idempotent for the configfs/UDC setup, this either produces harmless log spam or causes a brief USB reconnect visible to Tesla.

The flag should be left as true when Disable() fails — the gadget is still active:

Suggested change
if err := m.deps.Gadget.Disable(); err != nil {
log.Printf("disable gadget: %v", err)
m.gadgetEnabled = false
m.setState(StateAway)
return
}
if err := m.deps.Gadget.Disable(); err != nil {
log.Printf("disable gadget: %v", err)
// gadgetEnabled stays true — Disable did not succeed, USB is still active
m.setState(StateAway)
return
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/state/machine.go
Line: 208-213

Comment:
**`gadgetEnabled` set to `false` despite Disable() failing**

When `Disable()` returns an error, the gadget is **still enabled** — the Disable call did nothing. Setting `m.gadgetEnabled = false` here misrepresents the hardware state. Back in `runAway`, `tryEnableGadget()` will see `gadgetEnabled == false` and call `Enable()` on a gadget that never stopped running. Depending on whether `Enable()` is idempotent for the configfs/UDC setup, this either produces harmless log spam or causes a brief USB reconnect visible to Tesla.

The flag should be left as `true` when `Disable()` fails — the gadget is still active:

```suggestion
	if err := m.deps.Gadget.Disable(); err != nil {
		log.Printf("disable gadget: %v", err)
		// gadgetEnabled stays true — Disable did not succeed, USB is still active
		m.setState(StateAway)
		return
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant