fix(sdkserver): marshal workspace tool input as JSON #17

Merged
thklein merged 2 commits from fix/sdkserver-workspace-input-json into main 2026-10-12 11:13:48 +00:00
Owner

Impact

The workspace handlers in pkg/sdkserver/workspaces.go built tool input with fmt.Sprintf (e.g. {"workspace_id": "%s", "file_path": "%s"}). A " in any caller-supplied value could append a second "workspace_id" key, and Go's JSON decoding keeps the last duplicate, so a crafted file path (or prefix / revision ID / workspace ID) could address another workspace: read, write, list or delete files and revisions there.

Fix

All ten affected handlers now build their input through workspaceToolInput, which json.Marshals a map and sets workspace_id last so no field can override it. The write-file path already used json.Marshal. No other Sprintf-built JSON in pkg/sdkserver.

Test

workspaces_test.go adds tests showing that a file path containing ", "workspace_id": "other leaves workspace_id unchanged and round-trips exactly, that the workspace ID itself is escaped, and that a workspace_id field cannot replace it. They failed against the Sprintf-equivalent helper and pass now. go test ./pkg/sdkserver/... and go vet ./... pass. golangci-lint reports nothing new; the 32 issues on main are still there.

## Impact The workspace handlers in `pkg/sdkserver/workspaces.go` built tool input with `fmt.Sprintf` (e.g. `{"workspace_id": "%s", "file_path": "%s"}`). A `"` in any caller-supplied value could append a second `"workspace_id"` key, and Go's JSON decoding keeps the last duplicate, so a crafted file path (or prefix / revision ID / workspace ID) could address another workspace: read, write, list or delete files and revisions there. ## Fix All ten affected handlers now build their input through `workspaceToolInput`, which `json.Marshal`s a map and sets `workspace_id` last so no field can override it. The write-file path already used `json.Marshal`. No other Sprintf-built JSON in `pkg/sdkserver`. ## Test `workspaces_test.go` adds tests showing that a file path containing `", "workspace_id": "other` leaves `workspace_id` unchanged and round-trips exactly, that the workspace ID itself is escaped, and that a `workspace_id` field cannot replace it. They failed against the Sprintf-equivalent helper and pass now. `go test ./pkg/sdkserver/...` and `go vet ./...` pass. `golangci-lint` reports nothing new; the 32 issues on `main` are still there.
fix(sdkserver): marshal workspace tool input as JSON
Some checks failed
Request reviewers for Dependabot PRs / request_reviewers (pull_request_target) Has been skipped
smoke / check-label (pull_request_target) Failing after 48s
smoke / gpt-4o-2024-08-06 (pull_request_target) Has been skipped
smoke / gpt-4o-mini-2024-07-18 (pull_request_target) Has been skipped
smoke / claude-3-7-sonnet-20250219 (pull_request_target) Has been skipped
integration / integration (ubuntu-22.04) (pull_request) Failing after 26s
test / test (ubuntu-22.04) (pull_request) Failing after 52s
Validate docs build / validate-docs (pull_request) Failing after 43s
integration / integration (windows-latest) (pull_request) Has been cancelled
test / test (windows-latest) (pull_request) Has been cancelled
f5ba692263
The workspace handlers built the tool input with fmt.Sprintf, e.g.
`{"workspace_id": "%s", "file_path": "%s"}`. A quote in any
caller-supplied value (file path, prefix, revision ID, or the workspace
ID itself) escaped its string and could append a second "workspace_id"
key. Go's JSON decoding keeps the last duplicate key, so a crafted file
path could make the tool read, write, or delete in another workspace.

Build the input for all ten affected handlers through a single
workspaceToolInput helper that json.Marshals a map and sets
workspace_id last, so no field can override or replace it.
test(sdkserver): pin raw paths through workspace input
Some checks failed
smoke / check-label (pull_request_target) Failing after 2s
smoke / gpt-4o-2024-08-06 (pull_request_target) Has been skipped
smoke / gpt-4o-mini-2024-07-18 (pull_request_target) Has been skipped
smoke / claude-3-7-sonnet-20250219 (pull_request_target) Has been skipped
test / test (ubuntu-22.04) (pull_request) Failing after 39s
integration / integration (ubuntu-22.04) (pull_request) Failing after 40s
Validate docs build / validate-docs (pull_request) Failing after 38s
integration / integration (windows-latest) (pull_request) Has been cancelled
test / test (windows-latest) (pull_request) Has been cancelled
0202a556c9
Author
Owner

Fresh-context review: nothing blocking. No existing caller sees a change: key names, the string-typed with_latest_revision_id, and empty fields are all identical.

Behaviour change, for the better: backslashes and control characters in a path used to be reinterpreted, or produced invalid JSON that went out with an empty workspace id. They now arrive exactly as given. Pinned by TestWorkspaceToolInputKeepsBackslashesAndControlCharacters (0202a55).

CI: the test failure is pre-existing on main. The runner cannot git ls-remote the private git.basepeak.dev modules, so make tidy fails before any test runs. Locally, go test -count=1 ./pkg/sdkserver/... and go vet pass.

Follow-ups noted, out of scope:

  • pkg/prompt/credential.go:22 builds JSON with Sprintf from provider-supplied text.
  • Possible .. traversal through file_path, depending on workspace-provider's path handling; needs a separate look.
**Fresh-context review: nothing blocking.** No existing caller sees a change: key names, the string-typed `with_latest_revision_id`, and empty fields are all identical. **Behaviour change, for the better:** backslashes and control characters in a path used to be reinterpreted, or produced invalid JSON that went out with an empty workspace id. They now arrive exactly as given. Pinned by `TestWorkspaceToolInputKeepsBackslashesAndControlCharacters` (0202a55). **CI:** the `test` failure is pre-existing on `main`. The runner cannot `git ls-remote` the private `git.basepeak.dev` modules, so `make tidy` fails before any test runs. Locally, `go test -count=1 ./pkg/sdkserver/...` and `go vet` pass. **Follow-ups noted, out of scope:** - `pkg/prompt/credential.go:22` builds JSON with Sprintf from provider-supplied text. - Possible `..` traversal through `file_path`, depending on workspace-provider's path handling; needs a separate look.
thklein deleted branch fix/sdkserver-workspace-input-json 2026-10-12 11:13:48 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
basepeak-ai/gptscript!17
No description provided.