Skip to content

workspace create --no-read-only silently creates a read-only workspace (and reports read_only: false) #835

Open

Description

@matyaslustig

Summary

kbagent workspace create --no-read-only creates a workspace with read-only storage access, but the command output says "read_only": false. Anyone relying on this flag to isolate a workspace from Storage ends up with one that can read every bucket in the project, and gets no warning.

Steps to reproduce

kbagent 0.84.1, Snowflake project on connection.north-europe.azure.keboola.com:

kbagent --json workspace create --project <alias> --name "ro-test" --no-read-only
# -> "read_only": false   (in the create output)

kbagent --json workspace detail --project <alias> --workspace-id <id>
# -> "read_only": true    (what Storage actually stored)

Querying the workspace confirms the access is real: SELECT ... FROM <DB>.INFORMATION_SCHEMA.TABLES lists every bucket in the project, and a SELECT from an arbitrary in.c-* table succeeds.

Expected

The workspace is created with readOnlyStorageAccess: false, so it can only see its own schema and whatever is loaded into it. workspace detail reports read_only: false.

Actual

The workspace is created with readOnlyStorageAccess: true, which is the server default. The create output repeats back the requested value instead of the stored one.

Root cause

The flag is never sent to the API:

  • services/workspace_service.py → _create_workspace_direct() takes read_only but doesn't pass it to client.create_config_workspace(...). It only echoes it in the result dict ("read_only": read_only).
  • client.py → create_config_workspace() has no read-only parameter. The payload is only {"backend", "loginType", "publicKey"}, so Storage falls back to its default.
  • _create_workspace_via_job() (the --ui path) hard-codes "read_only": True in its result. It's unclear whether the sandboxes job honours the flag at all.

Suggested fix

  1. Add read_only: bool | None to create_config_workspace() and send payload["readOnlyStorageAccess"] = read_only.
  2. Pass it through from _create_workspace_direct() (and from the --ui path, or reject --no-read-only --ui if it can't be honoured).
  3. Build read_only in the result from the API response (ws_data["readOnlyStorageAccess"]), not from the requested value, so a mismatch can't go unnoticed again.
  4. Optionally add a test that asserts the payload contains readOnlyStorageAccess: false for --no-read-only.

Workaround

Create the workspace directly through the Storage API:

curl -X POST "https://connection.<stack>/v2/storage/branch/default/workspaces?async=0" \
  -H "X-StorageApi-Token: $TOKEN" -H "Content-Type: application/json" \
  -d '{"backend":"snowflake","readOnlyStorageAccess":false,"loginType":"snowflake-person-sso"}'

kbagent workspace load then works fine against that workspace (tested with --load-type view).

Why it matters

A workspace without storage access plus tables loaded as views is the native way to give a SQL client (e.g. the Keboola MCP query_data tool) access to only a curated set of tables. With this bug, the "isolated" workspace silently exposes the whole project.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions