Skip to content
Merged
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
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,68 @@ make lint # Run linter
make build-prod # Production build (static, CGO disabled)
```

## Session & Pod Lifecycle

Each investigation gets its own sandbox pod. The MCP server is stateless — session
state lives in Kubernetes (pod labels/annotations) and is cached in memory for performance.

### Request Flow

```
TARSy (LLM)
│ MCP tool call + X-Session-ID header
▼
MCP Server (stateless, multi-replica)
│ HTTP POST /exec + Bearer HMAC token
▼
Sandbox Pod (per-session, persistent bash)
│ stdout / stderr / exit_code
▼
MCP Server → TARSy
```

### GetOrCreatePod — Three-Tier Lookup

When a command arrives for a session, `SessionManager.GetOrCreatePod` resolves the
target pod using a three-tier strategy:

| Tier | Method | Latency | Description |
|------|--------|---------|-------------|
| 1 | **PodCache** | ~0ms | In-memory map of session ID → pod IP with 30s TTL |
| 2 | **Label discovery** | ~50-100ms | `List` pods by `tarsy.redhat.com/session-id` label |
| 3 | **Create** | ~3-8s | Create auth Secret + Pod, wait for readiness probe |

Cache misses fall through to tier 2; if no pod exists, tier 3 creates one
idempotently (handles `AlreadyExists`).

Comment on lines +49 to +77

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files | sed -n '1,120p'

printf '\n== README around cited lines ==\n'
cat -n README.md | sed -n '40,110p'

printf '\n== search for session manager / pod cache / pod identity ==\n'
rg -n "SessionManager|GetOrCreatePod|PodCache|X-Session-ID|session-id|pod IP|pod uid|pod name|Authorization: Bearer|HMAC" .

Repository: codeready-toolchain/cli-mcp-server

Length of output: 39337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== outline likely relevant files ==\n'
fd -a '.*(session|pod|sandbox|server|transport).*' . -t f | sed -n '1,200p'

printf '\n== README lines 1-140 ==\n'
cat -n README.md | sed -n '1,140p'

Repository: codeready-toolchain/cli-mcp-server

Length of output: 5495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== relevant file candidates ==\n'
fd -a '^(README|.*session.*|.*pod.*|.*sandbox.*|.*server.*)\.(md|go|ts|js|yaml|yml)$' . -t f | sed -n '1,200p'

printf '\n== read any session/pod related code references ==\n'
rg -n "type SessionManager|func .*GetOrCreatePod|PodCache|session-id|X-Session-ID|pod IP|AlreadyExists|readiness probe|Authorization" \
  --glob '!README.md' --glob '!**/vendor/**' .

Repository: codeready-toolchain/cli-mcp-server

Length of output: 25449


Cache a stable pod identity, not just the IP. Tier 1 reads as session ID → pod IP, which can go stale after a restart and send the next request to the wrong address until TTL expiry; keep the pod name in the cache entry and re-resolve the current IP on lookup.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 51-51: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 49 - 77, The Tier 1 cache in
SessionManager.GetOrCreatePod currently stores only a pod IP, which can become
stale after restarts; update the cached entry to include a stable pod identifier
such as the pod name. On lookup, use that cached name to re-resolve the current
IP before returning the pod target, and keep the PodCache behavior aligned with
the existing label-discovery and create flow.

Source: Linked repositories

### Warm Pool

When `--warm-pool-size N` is set, N pre-warmed pods are maintained in a ready state.
New sessions are assigned a warm pod instantly (~0ms) instead of waiting for a cold
start. Disabled by default (`N=0`).

Comment on lines +78 to +83

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Document the reset boundary before reusing warm pods.

"Assigned to new sessions instantly" is only safe if every pod is fully reprovisioned or scrubbed between investigations. Without that, shell state and emptyDir contents can leak across sessions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 78 - 83, Update the Warm Pool documentation to state
that warm pods must be fully reset or scrubbed before being reused for a new
session, including shell state and emptyDir contents. In the README section
describing --warm-pool-size, revise the “assigned instantly” wording to mention
the reset boundary and clarify that reuse is only safe after
reprovisioning/cleanup of each warm pod between investigations.

### Pod Spec

Each sandbox pod runs with a hardened security context:

- **Non-root**: UID/GID 1001, `allowPrivilegeEscalation: false`, all capabilities dropped
- **Resources**: 100m/500m CPU, 128Mi/512Mi memory
- **Readiness**: HTTP GET `/health` on port 8090
- **Volumes**: `kubeconfig` (read-only Secret mount), `workspace` (emptyDir)
- **Auth**: HMAC-SHA256(key, sessionID) → bearer token delivered via Secret → env var

Comment on lines +84 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Keep the bearer token out of the bash-visible environment.

Delivering the credential via env var inside the sandbox makes it readable by any command running there. That turns a shell compromise into control-plane credential exposure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 84 - 93, The Pod Spec currently exposes the bearer
token through an environment variable in the sandbox, which makes it visible to
any command running there. Update the auth flow in the Pod Spec and related
sandbox credential handling so the HMAC-SHA256-derived bearer token is not
placed in a bash-visible env var; instead deliver it through a less exposed
mechanism already used by the sandbox such as a mounted Secret file or other
non-env secret injection path, while keeping the token available to the
component that needs it.

### Cleanup

| Method | Trigger | Description |
|--------|---------|-------------|
| **Explicit delete** | `DELETE /sessions/{id}` | TARSy calls when investigation ends. Deletes pod, Secret, and cache entry |
| **Stale cleanup** | Background ticker | Deletes pods idle > 30 minutes (via `last-activity` annotation) |

If a sandbox pod crashes mid-investigation, shell state (env vars, working directory,
files in emptyDir) is lost. The MCP server detects the transport error, invalidates
the cache, and creates a new pod on the next call. TARSy retains the investigation
context.

## Design Documents

- [Sketch](docs/proposals/cli-mcp-server-sketch.md) — Problem statement and approach
Expand Down
Loading