Skip to content

fix(studio): secrets stay in Go; file verbs check their roots - #516

Merged
pbertsch merged 5 commits into
mainfrom
fix/studio-security-decisions
Oct 27, 2026
Merged

pbertsch merged 5 commits into
mainfrom
fix/studio-security-decisions

Conversation

@pbertsch

@pbertsch pbertsch commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Summary

  • The Anthropic API key never reaches the window. The settings panel shows whether a key is saved, and saving an empty field no longer clears a saved key.
  • A discovered device's WiFi token is kept in the OS keychain, one item per device, so the next connection needs no paste. Connecting with a saved token reads it in Go. A token is saved only after it connects.
  • The plain-text WiFi tokens that an earlier version kept in local storage are deleted on first start.
  • Run File, Read File, Write File, the file tree and the editor's open action touch only .probe files inside the open workspace or an automation folder. Paths with .. and symlinks that leave the root are refused.
  • main is merged into the branch, so it is current with the latest changes.

Test plan

  • go test ./... in studio. New tests cover the per-device keychain round trip, refusal of an empty device name or token, and that a saved token goes through the same target checks as a typed one.
  • go vet ./... in studio
  • Frontend: npm run build, typecheck:device-view, test:device-view (116), test:probescript (11), check:probescript
  • CI green except the review job, which needs the owner's CLAUDE_CODE_OAUTH_TOKEN
  • On a physical device over WiFi: connect with a typed token, reconnect with the saved token, then forget it

🤖 Generated with Claude Code

…le checks roots

Stored keys are reported by presence only, WiFi tokens are no longer remembered, and RunFile checks the workspace and automation roots.

GetAPIKey reports whether a key is saved and never returns it. The key panel starts with an empty field, and an empty save no longer clears a saved key; Remove key still deletes.

WiFi tokens are no longer remembered. The token is typed for each connection. The saved tag and the forget control are gone, and Studio deletes the plain-text copy an earlier version kept in localStorage on first start. The host and port are still remembered.

RunFile accepts only .probe files inside the open workspace or an automation folder: the check the automation endpoint makes. RunFileAsync makes it before the background start, so a refusal reaches the caller.

Tests cover the API key presence check, the run path refusals, and the RunFileAsync refusal. The studio_run_file description and the Studio docs say what changed.
ReadFile reads .probe files only. The editor calls it only for the .probe entries of the open workspace tree, and the automation endpoint calls it only on a path it has already checked, so that call is unchanged.

It now checks the path the way RunFile does: an absolute path, resolved through every symlink, inside the workspace a person opened or an automation folder, and a regular .probe file. A ".." segment is refused before anything else, on either separator, including one that would stay inside the workspace.

Tests cover a file inside and outside the workspace, a non-.probe file, a ".." path that climbs out and one that stays inside, a relative path, and a symlink that points out. The round-trip and headless tests now open the workspace first, as the page does.
…le follow

Studio file access now has one rule. WriteFile saves .probe files, and creates the folders above them, only inside the workspace a person opened or an automation folder. ListDir lists only folders inside those roots. With no workspace open it lists nothing, and a folder that does not exist yet lists as empty.

Both use the helper that ReadFile, RunFile and the automation endpoint already use, so the rule lives in one place (resolveInRoots). It refuses a relative path, a ".." segment on either separator, and any path whose symlinks resolve outside the roots. For writes it also resolves the nearest existing folder, so a new file under a folder that is a link out is refused, and a folder named like a .probe file is refused as a target.

The automation endpoint's open_workspace and list_dir use the same helper, so they now refuse ".." as well. The file tree's up button stops at the workspace root.

Tests cover a new file inside the workspace, outside paths, a non-.probe name, ".." on both separators, a file and a new file under a link out, a relative path, a folder named like a probe file, the workspace listing with hidden entries left out, a missing folder, folders outside, and no workspace open. The existing ListDir and round-trip tests open the workspace first, as the page does.

Not covered: LoadWorkspaceSettings and SaveWorkspaceSettings still take a folder and read or write probe.yaml in it without this check. They need the UI call order checked, and the browser suite runs only in CI.
A discovered device's WiFi token is saved in the platform keychain under the device's name, so the next connection needs no paste. ConnectWiFiSaved reads the token in Go, and the window never receives it. A token is saved only after ConnectWiFi accepts it. The plain-text copy an earlier version kept in localStorage is deleted on start.

Tests cover the per-device round trip, refusal of an empty device name or token, and that ConnectWiFiSaved runs the same target checks as ConnectWiFi.
@pbertsch
pbertsch requested a review from a team as a code owner October 27, 2026 03:08
@pbertsch
pbertsch merged commit 2ff21b2 into main Oct 27, 2026
22 of 23 checks passed
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