Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"plugins": [
{
"name": "kbagent",
"version": "0.27.0",
"version": "0.27.1",
"source": "./plugins/kbagent",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"category": "development"
Expand Down
12 changes: 10 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ plugins/kbagent/
> "Plugin synchronization map" for the full list.

```
# Global options: --json, --verbose, --no-color, --config-dir, --hint client|service, --deny-writes, --deny-destructive
# Global options: --json, --verbose, --no-color, --config-dir, --hint client|service, --deny-writes, --deny-destructive, --no-env-manage-token

kbagent project add --project NAME --url URL --token TOKEN
kbagent project list
Expand Down Expand Up @@ -383,9 +383,17 @@ kbagent schedule detail --project NAME --schedule-id ID [--branch ID]
kbagent schedule find [--cron-window START-END] [--not-run-since DAYS] [--project NAME ...] [--branch ID]

kbagent context
kbagent init [--from-global]
kbagent init [--from-global] [--read-only]
kbagent doctor [--fix]
kbagent version
kbagent update
kbagent changelog [--limit N]

kbagent permissions list [--category read|write|destructive|admin]
kbagent permissions show
kbagent permissions set --mode allow|deny [--allow PATTERN ...] [--deny PATTERN ...]
kbagent permissions reset
kbagent permissions deny-manage-env
kbagent permissions allow-manage-env
kbagent permissions check OPERATION
```
535 changes: 535 additions & 0 deletions docs/manage-token-design.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion plugins/kbagent/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kbagent",
"version": "0.27.0",
"version": "0.27.1",
"description": "AI-friendly interface to Keboola Connection projects — explore configs, jobs, lineage, call MCP tools, manage dev branches, and debug SQL in workspaces",
"author": {
"name": "Keboola",
Expand Down
36 changes: 31 additions & 5 deletions plugins/kbagent/agents/keboola-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ a critical failure.
needed for the current task (e.g. `flow update` needs 0.22.0+,
`schedule find` needs 0.23.0+, `config set-default-bucket` needs
0.26.0+, `data-app create / deploy / start / stop / delete / password`
need 0.27.0+, `storage retype` is a future composite), you MUST refuse
the task and return a handoff message to the parent: `"Cannot proceed
safely on kbagent <version>. Missing: <commands>. Ask user to run
kbagent update, then re-invoke me."` Do not attempt the task with
workarounds that use MCP strip-bug-prone tools.
need 0.27.0+, multi-stack manage-token resolution + `--no-env-manage-token`
+ `permissions {deny,allow}-manage-env` need 0.27.1+, `storage retype`
is a future composite), you MUST refuse the task and return a handoff
message to the parent: `"Cannot proceed safely on kbagent <version>.
Missing: <commands>. Ask user to run kbagent update, then re-invoke
me."` Do not attempt the task with workarounds that use MCP strip-
bug-prone tools.

7. **ALWAYS USE `--json`**. Every `kbagent` invocation MUST have
`--json` as the first flag after `kbagent`. This makes output
Expand Down Expand Up @@ -104,6 +106,9 @@ a critical failure.
| Pause a running data app | `kbagent data-app stop --project P --app-id N` (0.27.0+) | -- | `kbagent data-app delete` (irreversible; cascades to Storage config) |
| Read the simpleAuth password for a password-gated app | `kbagent data-app password --project P --app-id N` (0.27.0+) -- requires `KBC_MANAGE_API_TOKEN` | -- | trying to "rotate" the password (not supported by the API; delete + recreate to mint a new one) |
| Tear down a data app | `kbagent data-app delete --project P --app-id N` (0.27.0+) -- cascades to Storage config; URL retired | -- | manually `tool call delete_config keboola.data-apps` while leaving the deployment record orphaned |
| Hold manage tokens for projects on multiple stacks (US/EU/GCP/Azure) | `KBC_MANAGE_TOKEN_<HOSTNAME_SUFFIX>` env vars (`KBC_MANAGE_TOKEN_EU_CENTRAL_1`, `KBC_MANAGE_TOKEN_US_EAST4_GCP`, `KBC_MANAGE_TOKEN_NORTH_EUROPE_AZURE`, …) (0.27.1+) -- hostname-derived suffix; legacy `KBC_MANAGE_API_TOKEN` is a single-stack fallback | TTY prompt fallback when only some stacks have env vars set (resolve_manage_token names the stack URL in the prompt) | reusing one `KBC_MANAGE_API_TOKEN` across multiple stacks (token is stack-scoped; wrong-stack call returns 401) |
| Refuse env-var manage tokens for ONE invocation | `kbagent --no-env-manage-token <command>` (0.27.1+) -- session flag; mirrors `--deny-writes` shape | -- | trusting `--deny-writes` to block raw `curl -H "X-KBC-ManageApiToken: $KBC_MANAGE_API_TOKEN" ...` (env vars sit OUTSIDE kbagent's permission firewall) |
| Refuse env-var manage tokens permanently in a sandboxed install | `kbagent permissions deny-manage-env` (0.27.1+) -- persists `allow_env_manage_token=False` in config.json; reversed by `kbagent permissions allow-manage-env`; auto-set by `kbagent init --read-only` (0.27.1+) | `--no-env-manage-token` per call (less robust against AI-agent attempts to flip the policy) | leaving `KBC_MANAGE_API_TOKEN` unrestricted in env when an AI agent has shell access |

If the table does not cover the user's task, **ask clarifying
questions** instead of guessing. Returning a targeted question is a
Expand Down Expand Up @@ -236,6 +241,27 @@ success, not a failure.
the target project's Encryption API and refuses to write plaintext if
the round-trip does not return a `KBC::Project*` ciphertext.

- **Manage tokens are stack-scoped + sit OUTSIDE the firewall** (0.27.1+):
`KBC_MANAGE_API_TOKEN` works only for the stack that minted it.
Projects spanning `connection.eu-central-1.keboola.com` /
`connection.us-east4.gcp.keboola.com` / Azure stacks need per-stack
env vars: `KBC_MANAGE_TOKEN_<HOSTNAME_SUFFIX>` (`_EU_CENTRAL_1`,
`_US_EAST4_GCP`, `_NORTH_EUROPE_AZURE`, …). Hostname-derived; no
curated table. Legacy single-var keeps working as a fallback for
single-stack callers. **AI-exfil mitigation**: env vars are NOT
guarded by `kbagent`'s permission firewall — a sandboxed agent
with `KBC_MANAGE_API_TOKEN` in its env can `curl -H "X-KBC-
ManageApiToken: $KBC_MANAGE_API_TOKEN" /manage/projects` while
`--deny-writes` silently lets it through. Three layers of opt-out:
`--no-env-manage-token` (session, like `--deny-writes`),
`kbagent permissions deny-manage-env` (persisted; gated by random-
code confirmation; survives across invocations and config reloads),
and `kbagent init --read-only` (auto-sets `allow_env_manage_token=
False` for new sandboxed installs). When the deny is active,
`resolve_manage_token` falls through to a TTY prompt that names the
target stack URL — non-interactive callers get exit code 2 and a
message naming both env-var forms.

- **`storage bucket-detail` is dialect-aware** (0.25.3+): the response
shape depends on the bucket's backend. Snowflake buckets carry
`snowflake_database` / `snowflake_schema` and per-table
Expand Down
9 changes: 9 additions & 0 deletions plugins/kbagent/skills/kbagent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ description: >
data-app create, data-app deploy, data-app password, data-app start,
app proxy, simpleAuth, app auto-suspend, configVersion, redeploy contract,
Data Science API, /apps endpoint, app password, KBC::Project ciphertext,
manage token, manage api token, KBC_MANAGE_API_TOKEN,
KBC_MANAGE_TOKEN_, per-stack manage token, multi-stack manage token,
multi-stack token resolution, manage token across stacks,
no-env-manage-token, --no-env-manage-token,
permissions deny-manage-env, permissions allow-manage-env,
AI exfiltration, exfiltrate manage token, env-var manage token,
read-only workspace, kbagent init --read-only, sandboxed agent token,
local workspace, project directory, kbagent init.
---

Expand Down Expand Up @@ -81,6 +88,8 @@ When working inside a git repository or project directory, run `kbagent init` (o
| Show the current active permission policy | `kbagent permissions show` |
| Set the permission policy (firewall rules) | `kbagent permissions set --mode MODE` |
| Remove all permission restrictions | `kbagent permissions reset` |
| Refuse to read manage tokens from environment variables | `kbagent permissions deny-manage-env` |
| Re-allow reading manage tokens from environment variables (default) | `kbagent permissions allow-manage-env` |
| Check if a specific operation is allowed | `kbagent permissions check <OPERATION>` |
| Add a new Keboola project connection | `kbagent project add --project ALIAS` |
| List all connected Keboola projects | `kbagent project list` |
Expand Down
20 changes: 17 additions & 3 deletions plugins/kbagent/skills/kbagent/references/commands-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,17 @@ All commands support `--json` for structured output. Multi-project flags (`--pro
## Permission flags (top-level, session-only)
- `--deny-writes` -- block all write/destructive/admin operations for this single invocation. Merges with any persisted permission policy; never written to config.json. Exit code 6 (PERMISSION_DENIED) on blocked operations
- `--deny-destructive` -- block only destructive operations (delete-table, delete-bucket, terminate-job, etc.) for this invocation. Pure-write ops like create-table stay allowed. Use this when you want to keep build-up capabilities but lock out tear-downs
- Both flags compose: `kbagent --deny-writes --deny-destructive ...` is the safest read-only run
- `--no-env-manage-token` (since v0.27.1) -- refuse to read manage tokens from env vars (`KBC_MANAGE_TOKEN_<SUFFIX>` and `KBC_MANAGE_API_TOKEN`) for this invocation. TTY prompt only. Use inside AI-agent sandboxes -- env vars sit OUTSIDE the firewall, so a subprocess can `curl /manage/...` even when `--deny-writes` is set
- All three flags compose: `kbagent --deny-writes --deny-destructive --no-env-manage-token ...` is the strictest one-shot run

## Permission persistence (config.json)
- `permissions list [--category read|write|destructive|admin]` -- list every operation the firewall knows about + current allow/deny status under the active policy
- `permissions show` -- print the currently active permission policy (persisted + session flags)
- `permissions set --mode allow|deny [--allow PATTERN ...] [--deny PATTERN ...]` -- replace the persisted firewall policy. Patterns: exact (`branch.delete`), glob (`tool:create_*`), category (`cli:write`, `tool:read`). Gated by random-code interactive confirmation
- `permissions reset` -- remove the persisted firewall policy (does NOT touch the manage-env policy below). Gated by random-code interactive confirmation
- `permissions deny-manage-env` (since v0.27.1) -- persist `allow_env_manage_token=False` so future invocations refuse env-var manage tokens regardless of session flags. Gated by random-code interactive confirmation. Ideal inside `kbagent init --read-only` workspaces
- `permissions allow-manage-env` (since v0.27.1) -- revert to default-allow for env-var manage tokens. Gated by random-code interactive confirmation
- `permissions check OPERATION` -- exit 0 if the operation is allowed under the active policy, exit 6 otherwise

## Organization
- `org setup --org-id ID --url URL [--dry-run] [--yes]` -- bulk-onboard all projects from an org (org admin, needs `KBC_MANAGE_API_TOKEN`)
Expand Down Expand Up @@ -167,7 +177,7 @@ Lifecycle for `keboola.data-apps`. Combines Storage API (config body, git block,
- `encrypt values --project ALIAS --component-id ID --input JSON|@file|- [--output-file PATH]` -- encrypt #-prefixed secrets via Keboola Encryption API (one-way, no decrypt). Scope: ComponentSecure (project + component). Use for MCP tool call workflows.

## Utility
- `init [--from-global]` -- create local `.kbagent/` workspace (per-directory isolation)
- `init [--from-global] [--read-only]` -- create local `.kbagent/` workspace (per-directory isolation). `--read-only` (since v0.25.x) sets a deny-writes firewall AND defaults `allow_env_manage_token=False` (since v0.27.1) for AI-agent sandboxes
- `doctor [--fix]` -- health checks; `--fix` auto-installs MCP server binary
- `version` -- show version and check for MCP server updates
- `context` -- full usage instructions for AI agents
Expand All @@ -180,13 +190,17 @@ Lifecycle for `keboola.data-apps`. Combines Storage API (config body, git block,
| `--no-color` | Disable colors |
| `--config-dir` | Override config directory |
| `--hint client\|service` | Generate Python code instead of executing (see [programming-with-cli.md](programming-with-cli.md)) |
| `--deny-writes` | Session firewall: block writes/destructive/admin |
| `--deny-destructive` | Session firewall: block destructive only |
| `--no-env-manage-token` (since v0.27.1) | Refuse env-var manage tokens for this invocation; TTY prompt only |

## Environment Variables
| Variable | Purpose |
|----------|---------|
| `KBC_TOKEN` | Fallback for `--token` |
| `KBC_STORAGE_API_URL` | Default stack URL |
| `KBC_MANAGE_API_TOKEN` | Manage API token (org setup) |
| `KBC_MANAGE_API_TOKEN` | Manage API token (org setup, project refresh, data-app password) -- legacy single-stack form; works for any stack pre-0.27.1 and as a fallback after |
| `KBC_MANAGE_TOKEN_<SUFFIX>` (since v0.27.1) | Per-stack Manage API token. Suffix is the hostname segment between `connection.` and `.keboola.com`, uppercased, non-alnum -> `_`. Examples: `KBC_MANAGE_TOKEN_EU_CENTRAL_1`, `KBC_MANAGE_TOKEN_US_EAST4_GCP`, `KBC_MANAGE_TOKEN_NORTH_EUROPE_AZURE`. The legacy stack `connection.keboola.com` has no per-stack form -- use `KBC_MANAGE_API_TOKEN` |
| `KBAGENT_CONFIG_DIR` | Override config directory |

## Exit Codes
Expand Down
59 changes: 59 additions & 0 deletions plugins/kbagent/skills/kbagent/references/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -911,3 +911,62 @@ The trade-off is deliberate: one big call avoids the O(unique-parents) round-tri
- To inspect or remove schedules: `kbagent flow schedule-remove` deletes all
scheduler configs that target the flow. Pair it with `--dry-run` to see the
affected configs (cron + timezone) without calling `delete_config`.

## Manage tokens are stack-scoped + sit OUTSIDE the firewall (since v0.27.1)

`KBC_MANAGE_API_TOKEN` works only for the stack that minted it.
A `kbagent` install that holds projects on multiple stacks (US, EU,
GCP, Azure) cannot use a single env var; the wrong stack returns 401
silently. Two related fixes shipped in 0.27.1:

**Per-stack env vars.** `resolve_manage_token()` derives the
expected env-var name from the target stack URL and looks it up
first:

| Stack URL | Expected env var |
|---|---|
| `connection.keboola.com` (legacy AWS US) | `KBC_MANAGE_API_TOKEN` (no per-stack form) |
| `connection.eu-central-1.keboola.com` | `KBC_MANAGE_TOKEN_EU_CENTRAL_1` |
| `connection.us-east4.gcp.keboola.com` | `KBC_MANAGE_TOKEN_US_EAST4_GCP` |
| `connection.eu-west1.gcp.keboola.com` | `KBC_MANAGE_TOKEN_EU_WEST1_GCP` |
| `connection.north-europe.azure.keboola.com` | `KBC_MANAGE_TOKEN_NORTH_EUROPE_AZURE` |

The suffix is the hostname between `connection.` and `.keboola.com`,
uppercased with non-alphanumerics replaced by underscores — no
curated table to maintain, future stacks slot in automatically. The
legacy `KBC_MANAGE_API_TOKEN` is the fallback when no per-stack var
is set, so single-stack users keep working unchanged. `OrgService.
refresh_tokens` also got a per-stack resolver path: `kbagent project
refresh --all` groups projects by `stack_url` and prompts/resolves
once per distinct stack.

**AI-exfiltration mitigation.** Env vars are NOT guarded by
`kbagent`'s permission firewall — `--deny-writes` blocks
`OPERATION_REGISTRY` ops but a sandboxed agent can still `curl -H
"X-KBC-ManageApiToken: $KBC_MANAGE_API_TOKEN" /manage/projects` and
the firewall stays blind because raw HTTP is not a kbagent
operation. Manage tokens are org-scoped, broader blast radius than
per-project Storage tokens. Three layers of opt-out:

1. `kbagent --no-env-manage-token <command>` — session-only flag.
Mirrors `--deny-writes`. `resolve_manage_token` refuses both env-
var forms; falls through to TTY prompt or exit 2.
2. `kbagent permissions deny-manage-env` — persisted policy
(`AppConfig.allow_env_manage_token=False`). Survives across
invocations; gated by the same random-code interactive
confirmation that `permissions set` and `permissions reset` use,
so an AI agent cannot flip it back programmatically. Reverse with
`kbagent permissions allow-manage-env`.
3. `kbagent init --read-only` — defaults the persisted policy to
`False` for new sandboxed installs. Operators who need env-var
tokens in a specific read-only workspace must `permissions
allow-manage-env` explicitly.

**For LLM/agent callers**: when `permissions show` reports
`allow_env_manage_token: False`, do NOT attempt to set
`KBC_MANAGE_API_TOKEN` in the agent's own subprocess env and call
the Manage API — the resolver refuses it and the policy is the
operator's signal that env-var manage tokens should not be used.
Ask the parent agent to pipe a token through TTY for one
invocation, or to `permissions allow-manage-env` if env-var
resolution is genuinely needed.
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,61 @@ kbagent permissions reset # type confirmation code
# optionally remove .claude/settings.json deny rules
```

## Manage tokens and the firewall (since v0.27.1)

The firewall guards `OPERATION_REGISTRY` calls -- it does NOT
intercept env-var reads or raw HTTP. So a sandboxed agent with
`KBC_MANAGE_API_TOKEN` (or `KBC_MANAGE_TOKEN_<SUFFIX>`) in its env
can `curl -H "X-KBC-ManageApiToken: $KBC_MANAGE_API_TOKEN"
https://connection.keboola.com/manage/projects` and the firewall
stays blind. Manage tokens are org-scoped (broader blast radius
than per-project Storage tokens), so this leak matters.

`kbagent` ships three layers of opt-out:

```bash
# 1. Session-only flag (one invocation):
kbagent --no-env-manage-token data-app password --project foo --app-id 42
# resolve_manage_token refuses both env-var forms and falls through
# to TTY prompt (or exit 2 in non-interactive mode).

# 2. Persisted policy (survives across invocations):
kbagent permissions deny-manage-env # type random confirmation code
# AppConfig.allow_env_manage_token = False
# Reverse with:
kbagent permissions allow-manage-env

# 3. Auto-set for AI sandboxes:
kbagent init --from-global --read-only
# In addition to the firewall, this sets
# allow_env_manage_token=False for the new workspace.
```

When the persisted policy denies env, the resolver behaves the same
as if `--no-env-manage-token` were passed on every invocation: env
vars are ignored; TTY prompt is the only path; non-interactive exits
2 with a message naming both env-var forms. The same random-code
confirmation prompt that gates `permissions set` and `reset` gates
`permissions {deny,allow}-manage-env` -- an AI agent cannot flip
the policy programmatically.

**Stack-aware resolver**: `resolve_manage_token()` also gained per-
stack env-var lookup in v0.27.1. The hostname-derived form
`KBC_MANAGE_TOKEN_<HOSTNAME_SUFFIX>` (e.g.
`KBC_MANAGE_TOKEN_EU_CENTRAL_1` for `connection.eu-central-1.keboola.com`)
is preferred when the caller can supply a `stack_url`. The legacy
single-var `KBC_MANAGE_API_TOKEN` is the fallback for callers that
cannot. Multi-stack `kbagent project refresh --all` resolves once
per distinct stack with internal caching, so prompts fire at most
once per stack. See `gotchas.md` ("Manage tokens are stack-scoped +
sit OUTSIDE the firewall") for the full table of stack-to-suffix
mappings.

## Key details

- **Exit code 6** = operation blocked by permission policy
- **`permissions` commands always work** -- you can never lock yourself out of checking/listing
- **Changing or removing the policy requires interactive confirmation** (random code typed by human)
- **New commands not in the registry** are treated as write operations (fail-closed)
- Policy is stored in `config.json` alongside project configs
- Policy is stored in `config.json` alongside project configs (mode 0600)
- **`permissions reset` does NOT touch `allow_env_manage_token`** -- the manage-env policy is intentionally a separate axis. Use `permissions allow-manage-env` to revert it
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "keboola-agent-cli"
version = "0.27.0"
version = "0.27.1"
description = "AI-friendly CLI for managing Keboola projects"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
Loading
Loading