Skip to content

fix(security,api-server,control-plane): CodeRabbit fixes, deletecollection fallback, idempotent agent start#1290

Merged
markturansky merged 5 commits intoalphafrom
fix/security-and-idempotent-start
Apr 11, 2026
Merged

fix(security,api-server,control-plane): CodeRabbit fixes, deletecollection fallback, idempotent agent start#1290
markturansky merged 5 commits intoalphafrom
fix/security-and-idempotent-start

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

@markturansky markturansky commented Apr 11, 2026

Summary

  • Security fixes (CodeRabbit PR feat(credentials): project-scoped credentials, MCP sidecar token exchange #1289 review): IDOR protection on credential Get/Patch/Delete/GetToken, projectID format validation in List, MCP sidecar guard on token exchange config, nil-guard for mention resolver TokenFunc
  • deletecollection RBAC fallback: When the tenant SA lacks deletecollection, session cleanup now falls back to list+delete individually — prevents orphaned pods accumulating
  • Idempotent agent start: POST /projects/{id}/agents/{agent_id}/start returns 200 with existing active session instead of creating duplicates. New sessions still return 201.

Test plan

  • go build ./... passes for api-server, control-plane, ambient-mcp
  • go vet ./... passes for all three
  • Credential integration tests pass (9/9)
  • MCP mention resolver tests pass (10/10 including new nil-guard test)
  • Control-plane unit tests pass
  • Runner tests pass (647/647)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Reuse active sessions: existing active sessions are returned instead of creating duplicates.
    • Control plane now forwards HTTP/HTTPS/NO_PROXY to spawned pods and sidecars.
  • Bug Fixes

    • Improved credential project validation and enforcement.
    • Kubernetes deletion now falls back when permissions restrict bulk deletes.
    • Resolver and session initialization now handle configuration/errors robustly.
  • Documentation

    • Added troubleshooting note about proxy env propagation to runner pods.
  • Tests

    • Updated tests to cover resolver and session behavior.

Ambient Code Bot and others added 3 commits April 10, 2026 22:46
… MCP, and control-plane

- Add project ownership validation on credential Get/Patch/Delete/GetToken (IDOR)
- Validate projectID format in credential List to prevent search injection
- Guard MCP sidecar creation on CP token exchange config availability
- Add nil-guard for TokenFunc in mention.NewResolver
- Add language tag to SKILL.md fenced code block

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…forbidden

The MPP tenant SA lacks deletecollection permission on pods, secrets,
serviceaccounts, and services in runner namespaces. Session cleanup
silently fails and orphaned pods accumulate consuming ~4Gi each.

All Delete*ByLabel methods now try deletecollection first, and on 403
Forbidden fall back to list matching resources then delete individually.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…e session

If an agent already has a session in Pending, Creating, or Running phase,
POST /projects/{id}/agents/{agent_id}/start now returns 200 with the
existing session instead of creating a duplicate. New sessions are still
created with 201 when no active session exists.

Adds ActiveByAgentID to session DAO and service layers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Adds active-session lookup and reuse to the start flow; tightens credential project ID validation and ownership checks; introduces DAO/service methods for active sessions and mock support; changes resolver construction to return errors and updates callers; adds DeleteCollection fallback logic; forwards HTTP(S)/NO_PROXY through control plane into spawned pods and MCP sidecar; minor documentation code-fence update.

Changes

Cohort / File(s) Summary
Session reuse
components/ambient-api-server/plugins/agents/start_handler.go, components/ambient-api-server/plugins/sessions/dao.go, components/ambient-api-server/plugins/sessions/mock_dao.go, components/ambient-api-server/plugins/sessions/service.go
Added ActiveByAgentID to DAO/service and mock; start handler checks for an active session and returns it (200) instead of creating a new session when found; moved initial request/agent lookup earlier for immediate error handling.
Credentials validation & scoping
components/ambient-api-server/plugins/credentials/handler.go
Added safeProjectIDPattern validation for route project IDs and enforced project-ownership checks in List/Patch/Get/GetToken/Delete, returning validation or NotFound errors when appropriate.
Resolver init & callers
components/ambient-mcp/mention/resolve.go, components/ambient-mcp/mention/resolve_test.go, components/ambient-mcp/tools/sessions.go
NewResolver now returns (*Resolver, error) and validates tokenFn != nil; tests updated to handle errors; callers (e.g., PushMessage) handle resolver errors and return config error responses when resolver creation fails.
Kubernetes delete fallback
components/ambient-control-plane/internal/kubeclient/kubeclient.go
Added deleteCollectionWithFallback that falls back from DeleteCollection to list+per-item Delete on Forbidden, used by multiple Delete*ByLabel methods; added k8s errors alias handling.
Control plane proxy config & sidecar
components/ambient-control-plane/internal/reconciler/kube_reconciler.go, components/ambient-control-plane/internal/config/config.go, components/ambient-control-plane/cmd/ambient-control-plane/main.go
Added HTTPProxy, HTTPSProxy, NoProxy fields to ControlPlaneConfig/KubeReconcilerConfig; Load() reads env vars; reconciler appends proxy envs to spawned pod env and to MCP sidecar (conditionally); main populates these config fields when creating reconcilers.
MCP sidecar conditionalization
components/ambient-control-plane/internal/reconciler/kube_reconciler.go
Only inject MCP sidecar when MCPImage is set AND both CPTokenURL and CPTokenPublicKey are non-empty; otherwise log warning and omit sidecar.
Documentation / code-fence
.claude/skills/devflow/SKILL.md, .claude/skills/ambient-pr-test/SKILL.md
Updated one code fence to explicit ```text and added troubleshooting note documenting proxy propagation fix for runner pods.
Small control flow / error handling
components/ambient-mcp/tools/sessions.go
PushMessage now returns a handler that emits a CONFIG_ERROR result when resolver creation fails, preventing further processing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StartHandler
    participant SessionService
    participant SessionDAO
    participant Database

    Client->>StartHandler: POST /agents/:agent_id/start
    StartHandler->>StartHandler: extract ctx & agent_id
    StartHandler->>SessionService: ActiveByAgentID(ctx, agent_id)
    SessionService->>SessionDAO: ActiveByAgentID(ctx, agent_id)
    SessionDAO->>Database: Query latest session WHERE agent_id=? AND phase IN (Pending,Creating,Running)
    alt active session found
        Database-->>SessionDAO: session row
        SessionDAO-->>SessionService: *Session
        SessionService-->>StartHandler: *Session
        StartHandler->>StartHandler: build StartResponse(existing)
        StartHandler-->>Client: 200 OK + existing session
    else no active session
        Database-->>SessionDAO: no rows
        SessionDAO-->>SessionService: nil
        SessionService-->>StartHandler: nil
        StartHandler->>StartHandler: unread inbox, create session, push start prompt, start session
        StartHandler-->>Client: 200 OK + new session
    end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Kubernetes Resource Safety ❓ Inconclusive Repository access failures prevented code inspection to verify Kubernetes resource safety attributes remain intact, though PR summary suggests no security-relevant signature or RBAC changes. Inspect kube_reconciler.go for OwnerReferences, resource limits/requests, securityContext fields, and RBAC rules to confirm no removal of safety attributes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format with fix type and relevant scopes, accurately summarizing three main changes: security fixes, deletecollection fallback, and idempotent agent start.
Performance And Algorithmic Complexity ✅ Passed No N+1 queries, unbounded lists, or algorithmic complexity regressions detected. Single-record DAO queries, one-time config loading, fallback List operations scoped by namespace/label selector.
Security And Secret Handling ✅ Passed PR correctly addresses security concerns: IDOR protection validates project ownership on credential endpoints, token function nil-guard prevents runtime errors, proxy config reads non-sensitive infrastructure variables, SQL uses parameterized queries, ProjectID validated against whitelist. No secrets leaked, no auth bypass, no injection vulnerabilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-and-idempotent-start
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/security-and-idempotent-start

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/ambient-control-plane/internal/reconciler/kube_reconciler.go (1)

408-409: ⚠️ Potential issue | 🟠 Major

MCP enablement flag is inconsistent with sidecar creation.

On Line 423, buildEnv(..., useMCPSidecar, ...) is computed from MCPImage only, but on Lines 444-449 the sidecar can be skipped when CP token config is missing. That leaves AMBIENT_MCP_URL configured without an MCP container, which can break delegation flows at runtime.

Proposed fix
-	useMCPSidecar := r.cfg.MCPImage != ""
+	hasMCPImage := r.cfg.MCPImage != ""
+	useMCPSidecar := hasMCPImage && r.cfg.CPTokenURL != "" && r.cfg.CPTokenPublicKey != ""

 	containers := []interface{}{
 		map[string]interface{}{
@@
-			"env":          r.buildEnv(ctx, session, sdk, useMCPSidecar, credentialIDs),
+			"env":          r.buildEnv(ctx, session, sdk, useMCPSidecar, credentialIDs),
@@
-	if useMCPSidecar {
-		if r.cfg.CPTokenURL == "" || r.cfg.CPTokenPublicKey == "" {
+	if hasMCPImage {
+		if !useMCPSidecar {
 			r.logger.Warn().Str("session_id", session.ID).Msg("MCP sidecar skipped: CP_TOKEN_URL or CPTokenPublicKey not configured")
 		} else {
 			containers = append(containers, r.buildMCPSidecar(session.ID))
 			r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
 		}
 	}

As per coding guidelines, "Flag only errors, security risks, or functionality-breaking problems."

Also applies to: 423-423, 444-449

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 408 - 409, The MCP sidecar decision is inconsistent: useMCPSidecar
is set from MCPImage alone but the code later can skip creating the MCP
container when the CP token config is missing, which can leave AMBIENT_MCP_URL
set without a sidecar. Change the sidecar flag to reflect both presence of
r.cfg.MCPImage and the CP token config check (make useMCPSidecar =
r.cfg.MCPImage != "" && <same CP token condition used when skipping creation>),
update the buildEnv(...) call to use that unified boolean, and ensure
AMBIENT_MCP_URL (and any other MCP-related env) is not injected when
useMCPSidecar is false; update the code paths that currently skip container
creation to rely on the same useMCPSidecar variable (references: useMCPSidecar,
r.cfg.MCPImage, buildEnv, and the CP token config check/sidecar creation block).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ambient-api-server/plugins/agents/start_handler.go`:
- Around line 59-67: The handler currently ignores errors from
h.session.ActiveByAgentID; update the call to capture the error (e.g., existing,
svcErr := h.session.ActiveByAgentID(ctx, agentID)), check if svcErr != nil and
call handlers.HandleError(ctx, w, svcErr) then return, and only proceed to build
StartResponse and write the 200 JSON when existing != nil; keep using
StartResponse and sessions.PresentSession for the successful path.

In `@components/ambient-api-server/plugins/sessions/service.go`:
- Around line 269-275: The ActiveByAgentID method in sqlSessionService currently
swallows all DAO errors by returning (nil, nil); instead, check the error from
sessionDao.ActiveByAgentID using errors.Is(err, gorm.ErrRecordNotFound) and only
treat ErrRecordNotFound as a nil result, otherwise wrap/convert the real DB
error into a non-nil *errors.ServiceError and return it (do not return nil, nil
on real errors). Update the caller (the start handler that invokes
ActiveByAgentID) to fail fast when a non-nil ServiceError is returned rather
than proceeding to Create, so transient DB failures do not lead to duplicate
sessions.

---

Outside diff comments:
In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`:
- Around line 408-409: The MCP sidecar decision is inconsistent: useMCPSidecar
is set from MCPImage alone but the code later can skip creating the MCP
container when the CP token config is missing, which can leave AMBIENT_MCP_URL
set without a sidecar. Change the sidecar flag to reflect both presence of
r.cfg.MCPImage and the CP token config check (make useMCPSidecar =
r.cfg.MCPImage != "" && <same CP token condition used when skipping creation>),
update the buildEnv(...) call to use that unified boolean, and ensure
AMBIENT_MCP_URL (and any other MCP-related env) is not injected when
useMCPSidecar is false; update the code paths that currently skip container
creation to rely on the same useMCPSidecar variable (references: useMCPSidecar,
r.cfg.MCPImage, buildEnv, and the CP token config check/sidecar creation block).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6cdb68f-ed8b-46a9-8866-8d557bd7f3f3

📥 Commits

Reviewing files that changed from the base of the PR and between 063953f and 30ad254.

📒 Files selected for processing (11)
  • .claude/skills/devflow/SKILL.md
  • components/ambient-api-server/plugins/agents/start_handler.go
  • components/ambient-api-server/plugins/credentials/handler.go
  • components/ambient-api-server/plugins/sessions/dao.go
  • components/ambient-api-server/plugins/sessions/mock_dao.go
  • components/ambient-api-server/plugins/sessions/service.go
  • components/ambient-control-plane/internal/kubeclient/kubeclient.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go
  • components/ambient-mcp/mention/resolve.go
  • components/ambient-mcp/mention/resolve_test.go
  • components/ambient-mcp/tools/sessions.go

Comment on lines +59 to +67
if existing, _ := h.session.ActiveByAgentID(ctx, agentID); existing != nil {
resp := &StartResponse{
Session: sessions.PresentSession(existing),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handler should propagate errors from ActiveByAgentID check.

Currently discards the error with _. If the service is updated to return real errors (per comment on service.go), this should be:

existing, svcErr := h.session.ActiveByAgentID(ctx, agentID)
if svcErr != nil {
    handlers.HandleError(ctx, w, svcErr)
    return
}
if existing != nil {
    // return 200 with existing session
}

This ensures idempotency isn't silently bypassed during DB issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-api-server/plugins/agents/start_handler.go` around lines
59 - 67, The handler currently ignores errors from h.session.ActiveByAgentID;
update the call to capture the error (e.g., existing, svcErr :=
h.session.ActiveByAgentID(ctx, agentID)), check if svcErr != nil and call
handlers.HandleError(ctx, w, svcErr) then return, and only proceed to build
StartResponse and write the 200 JSON when existing != nil; keep using
StartResponse and sessions.PresentSession for the successful path.

Comment on lines +269 to +275
func (s *sqlSessionService) ActiveByAgentID(ctx context.Context, agentID string) (*Session, *errors.ServiceError) {
session, err := s.sessionDao.ActiveByAgentID(ctx, agentID)
if err != nil {
return nil, nil
}
return session, nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Swallowing all errors breaks idempotency under DB instability.

Returning (nil, nil) for any DAO error treats DB connection failures the same as "no active session found." Combined with the handler discarding the error (start_handler.go:59), a transient DB failure during the check could lead to duplicate session creation if the DB recovers before Create().

Consider distinguishing gorm.ErrRecordNotFound from other errors:

Proposed fix
 func (s *sqlSessionService) ActiveByAgentID(ctx context.Context, agentID string) (*Session, *errors.ServiceError) {
 	session, err := s.sessionDao.ActiveByAgentID(ctx, agentID)
 	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, nil
+		}
+		return nil, errors.GeneralError("failed to check active session: %v", err)
-		return nil, nil
 	}
 	return session, nil
 }

Then update the handler to fail fast on real errors instead of proceeding to create.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *sqlSessionService) ActiveByAgentID(ctx context.Context, agentID string) (*Session, *errors.ServiceError) {
session, err := s.sessionDao.ActiveByAgentID(ctx, agentID)
if err != nil {
return nil, nil
}
return session, nil
}
func (s *sqlSessionService) ActiveByAgentID(ctx context.Context, agentID string) (*Session, *errors.ServiceError) {
session, err := s.sessionDao.ActiveByAgentID(ctx, agentID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, errors.GeneralError("failed to check active session: %v", err)
}
return session, nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-api-server/plugins/sessions/service.go` around lines 269 -
275, The ActiveByAgentID method in sqlSessionService currently swallows all DAO
errors by returning (nil, nil); instead, check the error from
sessionDao.ActiveByAgentID using errors.Is(err, gorm.ErrRecordNotFound) and only
treat ErrRecordNotFound as a nil result, otherwise wrap/convert the real DB
error into a non-nil *errors.ServiceError and return it (do not return nil, nil
on real errors). Update the caller (the start handler that invokes
ActiveByAgentID) to fail fast when a non-nil ServiceError is returned rather
than proceeding to Create, so transient DB failures do not lead to duplicate
sessions.

Ambient Code Bot and others added 2 commits April 11, 2026 16:40
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…er and MCP sidecar pods

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`:
- Around line 447-452: The code currently computes useMCPSidecar from MCPImage
only but still passes it into buildEnv causing AMBIENT_MCP_URL to be set even
when the CP token config is missing; change the logic to compute an enabled flag
(e.g., enabledMCPSidecar or enableMCPSidecar) that is true only when
useMCPSidecar is true AND r.cfg.CPTokenURL and r.cfg.CPTokenPublicKey are
non-empty, pass that new flag into buildEnv instead of useMCPSidecar, and only
call buildMCPSidecar/append the container when this combined flag is true so
AMBIENT_MCP_URL is not advertised when the sidecar is skipped (references:
useMCPSidecar, buildEnv, buildMCPSidecar, AMBIENT_MCP_URL).
- Around line 647-655: The code only injects uppercase proxy envs
(HTTP_PROXY/HTTPS_PROXY/NO_PROXY) which misses lowercase variants; update the
reconciler to append both uppercase and lowercase names when setting env (add
envVar("http_proxy", r.cfg.HTTPProxy), envVar("https_proxy", r.cfg.HTTPSProxy),
envVar("no_proxy", r.cfg.NoProxy) alongside the existing uppercase ones in the
code that constructs env using envVar and r.cfg.HTTPProxy/HTTPSProxy/NoProxy)
and also adjust the config loader in config.go to read lowercase fallbacks
(check os.Getenv("http_proxy") etc. when populating the same config fields) so
config values set via lowercase envs are preserved end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8498cea5-7991-49af-88f3-e4c4136ec713

📥 Commits

Reviewing files that changed from the base of the PR and between 30ad254 and 32c1ed2.

📒 Files selected for processing (4)
  • .claude/skills/ambient-pr-test/SKILL.md
  • components/ambient-control-plane/cmd/ambient-control-plane/main.go
  • components/ambient-control-plane/internal/config/config.go
  • components/ambient-control-plane/internal/reconciler/kube_reconciler.go
✅ Files skipped from review due to trivial changes (1)
  • .claude/skills/ambient-pr-test/SKILL.md

Comment on lines +447 to +452
if r.cfg.CPTokenURL == "" || r.cfg.CPTokenPublicKey == "" {
r.logger.Warn().Str("session_id", session.ID).Msg("MCP sidecar skipped: CP_TOKEN_URL or CPTokenPublicKey not configured")
} else {
containers = append(containers, r.buildMCPSidecar(session.ID))
r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t advertise MCP when the sidecar is skipped.

Lines 447-452 can omit the MCP container, but useMCPSidecar was already computed from MCPImage alone at Line 411 and passed into buildEnv at Line 426. That still sets AMBIENT_MCP_URL (Lines 607-609), so the runner will target localhost:8090 even though nothing is listening there.

Suggested fix
-	useMCPSidecar := r.cfg.MCPImage != ""
+	useMCPSidecar := r.cfg.MCPImage != "" && r.cfg.CPTokenURL != "" && r.cfg.CPTokenPublicKey != ""

 	containers := []interface{}{
 		map[string]interface{}{
 			"name":            "ambient-code-runner",
@@
-	if useMCPSidecar {
-		if r.cfg.CPTokenURL == "" || r.cfg.CPTokenPublicKey == "" {
-			r.logger.Warn().Str("session_id", session.ID).Msg("MCP sidecar skipped: CP_TOKEN_URL or CPTokenPublicKey not configured")
-		} else {
-			containers = append(containers, r.buildMCPSidecar(session.ID))
-			r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
-		}
+	if r.cfg.MCPImage != "" && !useMCPSidecar {
+		r.logger.Warn().Str("session_id", session.ID).Msg("MCP sidecar skipped: CP_TOKEN_URL or CPTokenPublicKey not configured")
+	}
+	if useMCPSidecar {
+		containers = append(containers, r.buildMCPSidecar(session.ID))
+		r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 447 - 452, The code currently computes useMCPSidecar from MCPImage
only but still passes it into buildEnv causing AMBIENT_MCP_URL to be set even
when the CP token config is missing; change the logic to compute an enabled flag
(e.g., enabledMCPSidecar or enableMCPSidecar) that is true only when
useMCPSidecar is true AND r.cfg.CPTokenURL and r.cfg.CPTokenPublicKey are
non-empty, pass that new flag into buildEnv instead of useMCPSidecar, and only
call buildMCPSidecar/append the container when this combined flag is true so
AMBIENT_MCP_URL is not advertised when the sidecar is skipped (references:
useMCPSidecar, buildEnv, buildMCPSidecar, AMBIENT_MCP_URL).

Comment on lines +647 to +655
if r.cfg.HTTPProxy != "" {
env = append(env, envVar("HTTP_PROXY", r.cfg.HTTPProxy))
}
if r.cfg.HTTPSProxy != "" {
env = append(env, envVar("HTTPS_PROXY", r.cfg.HTTPSProxy))
}
if r.cfg.NoProxy != "" {
env = append(env, envVar("NO_PROXY", r.cfg.NoProxy))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Forward lowercase proxy vars too, not just uppercase.

This path only injects HTTP_PROXY / HTTPS_PROXY / NO_PROXY, and components/ambient-control-plane/internal/config/config.go Lines 81-83 only read those uppercase forms. That drops deployments configured with lowercase proxy envs, and tools based on curl/libcurl in the runner or MCP sidecar may not honor uppercase HTTP_PROXY, so outbound calls can still bypass the proxy or fail.

Suggested fix
--- a/components/ambient-control-plane/internal/config/config.go
+++ b/components/ambient-control-plane/internal/config/config.go
@@
-		HTTPProxy:            os.Getenv("HTTP_PROXY"),
-		HTTPSProxy:           os.Getenv("HTTPS_PROXY"),
-		NoProxy:              os.Getenv("NO_PROXY"),
+		HTTPProxy:            firstEnv("HTTP_PROXY", "http_proxy"),
+		HTTPSProxy:           firstEnv("HTTPS_PROXY", "https_proxy"),
+		NoProxy:              firstEnv("NO_PROXY", "no_proxy"),
 	}
@@
 func envOrDefault(key, fallback string) string {
 	if v := os.Getenv(key); v != "" {
 		return v
 	}
 	return fallback
 }
+
+func firstEnv(keys ...string) string {
+	for _, key := range keys {
+		if v := os.Getenv(key); v != "" {
+			return v
+		}
+	}
+	return ""
+}
--- a/components/ambient-control-plane/internal/reconciler/kube_reconciler.go
+++ b/components/ambient-control-plane/internal/reconciler/kube_reconciler.go
@@
 	if r.cfg.HTTPProxy != "" {
-		env = append(env, envVar("HTTP_PROXY", r.cfg.HTTPProxy))
+		env = append(env,
+			envVar("HTTP_PROXY", r.cfg.HTTPProxy),
+			envVar("http_proxy", r.cfg.HTTPProxy),
+		)
 	}
 	if r.cfg.HTTPSProxy != "" {
-		env = append(env, envVar("HTTPS_PROXY", r.cfg.HTTPSProxy))
+		env = append(env,
+			envVar("HTTPS_PROXY", r.cfg.HTTPSProxy),
+			envVar("https_proxy", r.cfg.HTTPSProxy),
+		)
 	}
 	if r.cfg.NoProxy != "" {
-		env = append(env, envVar("NO_PROXY", r.cfg.NoProxy))
+		env = append(env,
+			envVar("NO_PROXY", r.cfg.NoProxy),
+			envVar("no_proxy", r.cfg.NoProxy),
+		)
 	}

Verify the current code only handles uppercase proxy vars end-to-end:

#!/bin/bash
set -euo pipefail

echo "== Proxy env loading from config =="
rg -n 'os\.Getenv\("(HTTP_PROXY|HTTPS_PROXY|NO_PROXY|http_proxy|https_proxy|no_proxy)"\)' \
  components/ambient-control-plane/internal/config/config.go

echo
echo "== Proxy env injection into pods =="
rg -n 'envVar\("(HTTP_PROXY|HTTPS_PROXY|NO_PROXY|http_proxy|https_proxy|no_proxy)"' \
  components/ambient-control-plane/internal/reconciler/kube_reconciler.go

Also applies to: 845-853

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 647 - 655, The code only injects uppercase proxy envs
(HTTP_PROXY/HTTPS_PROXY/NO_PROXY) which misses lowercase variants; update the
reconciler to append both uppercase and lowercase names when setting env (add
envVar("http_proxy", r.cfg.HTTPProxy), envVar("https_proxy", r.cfg.HTTPSProxy),
envVar("no_proxy", r.cfg.NoProxy) alongside the existing uppercase ones in the
code that constructs env using envVar and r.cfg.HTTPProxy/HTTPSProxy/NoProxy)
and also adjust the config loader in config.go to read lowercase fallbacks
(check os.Getenv("http_proxy") etc. when populating the same config fields) so
config values set via lowercase envs are preserved end-to-end.

@markturansky markturansky merged commit 7389444 into alpha Apr 11, 2026
44 checks passed
@markturansky markturansky deleted the fix/security-and-idempotent-start branch April 11, 2026 20:49
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