Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
288d275
feat(specs): credential sidecar isolation architecture
May 20, 2026
1cc2dc6
feat(control-plane): add per-credential sidecar injection
May 21, 2026
f1f070a
feat(runner): SSE sidecar transport for credential MCP servers
May 21, 2026
b018062
feat(runner): MCP-tool-only git push prompts in sidecar mode
May 21, 2026
0ab4da9
feat: credential sidecar Dockerfiles and build targets
May 21, 2026
e373777
test(control-plane): credential sidecar injection unit tests
May 21, 2026
a29f2b1
feat: credential sidecar token refresh via CP token exchange
May 21, 2026
eb027eb
chore: cleanup built artifacts and gitignore
May 21, 2026
0a96c00
fix: credential sidecar security hardening and end-to-end validation
May 21, 2026
469ca40
fix: migration ordering for role_bindings and pr-test standard OpenSh…
May 21, 2026
8ed5d4d
fix: add AMBIENT_API_TOKEN to api-server in install-standard.sh
May 21, 2026
d0ecdc6
feat: support ANTHROPIC_API_KEY in install-standard.sh
May 21, 2026
fa13c83
feat: add Vertex AI support to install-standard.sh
May 21, 2026
0e29da7
fix: make credential project_id optional in API schema
May 21, 2026
68bea15
feat: add --scope-id shorthand flag for role-binding creation
May 21, 2026
0c7e419
fix: add project-scoped credential routes and fix RBAC path resolution
May 21, 2026
5ef445f
fix: increase SSE scanner buffer to 1MB to prevent stream errors
May 21, 2026
37fefc9
fix: credential sidecar security hardening and end-to-end validation
May 22, 2026
6d05767
fix: address review bot feedback on credential sidecar entrypoint and…
May 22, 2026
9184faf
fix: use PtrString for *string ProjectId field in credential integrat…
May 22, 2026
ee898e6
feat: add PodStatusSyncer to reflect pod phase back to session API
May 23, 2026
9173526
fix: adjust go module replace path in credential sidecar Dockerfiles
May 23, 2026
8823a9a
fix: base64-decode kubeconfig token from Credentials API
May 23, 2026
b18a2d5
fix: pass --kubeconfig flag to kubernetes-mcp-server
May 23, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ hack/

# Personal exports
*.csv
components/credential-sidecars/entrypoint/credential-entrypoint
31 changes: 31 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ STATE_SYNC_IMAGE ?= vteam_state_sync:$(IMAGE_TAG)
PUBLIC_API_IMAGE ?= vteam_public_api:$(IMAGE_TAG)
API_SERVER_IMAGE ?= vteam_api_server:$(IMAGE_TAG)
OBSERVABILITY_DASHBOARD_IMAGE ?= vteam_observability_dashboard:$(IMAGE_TAG)
GITHUB_MCP_IMAGE ?= vteam_credential_github:$(IMAGE_TAG)
JIRA_MCP_IMAGE ?= vteam_credential_jira:$(IMAGE_TAG)
K8S_MCP_IMAGE ?= vteam_credential_k8s:$(IMAGE_TAG)
GOOGLE_MCP_IMAGE ?= vteam_credential_google:$(IMAGE_TAG)

# kind-local overlay always references localhost/vteam_* images.
# Podman produces this prefix natively; for Docker we tag before loading.
Expand Down Expand Up @@ -221,6 +225,33 @@ build-observability-dashboard: ## Build observability dashboard image
-t $(OBSERVABILITY_DASHBOARD_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Observability dashboard built: $(OBSERVABILITY_DASHBOARD_IMAGE)"

build-credential-sidecars: build-credential-github build-credential-jira build-credential-k8s build-credential-google ## Build all credential sidecar images

build-credential-github: ## Build GitHub credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building GitHub credential sidecar with $(CONTAINER_ENGINE)..."
@$(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-f components/credential-sidecars/github/Dockerfile \
-t $(GITHUB_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) GitHub credential sidecar built: $(GITHUB_MCP_IMAGE)"

build-credential-jira: ## Build Jira credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building Jira credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/jira && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(JIRA_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Jira credential sidecar built: $(JIRA_MCP_IMAGE)"

build-credential-k8s: ## Build K8s credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building K8s credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/k8s && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(K8S_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) K8s credential sidecar built: $(K8S_MCP_IMAGE)"

build-credential-google: ## Build Google credential sidecar image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building Google credential sidecar with $(CONTAINER_ENGINE)..."
@cd components/credential-sidecars/google && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-t $(GOOGLE_MCP_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Google credential sidecar built: $(GOOGLE_MCP_IMAGE)"

Comment on lines +229 to +258
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 | 🟡 Minor | ⚡ Quick win

Declare new credential-sidecar targets as .PHONY.

The targets added at Lines 228, 230, 237, 243, and 249 are missing from .PHONY (Lines 1-13). If same-named files appear, recipes may not run.

Suggested patch
-.PHONY: _create-operator-config _auto-port-forward _show-access-info _kind-load-images
+.PHONY: _create-operator-config _auto-port-forward _show-access-info _kind-load-images
+.PHONY: build-credential-sidecars build-credential-github build-credential-jira build-credential-k8s build-credential-google
🧰 Tools
🪛 checkmake (0.3.2)

[warning] 228-228: Target "build-credential-sidecars" should be declared PHONY.

(phonydeclared)

🤖 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 `@Makefile` around lines 228 - 254, Add the new Makefile targets to the .PHONY
declaration so their recipes always run: include build-credential-sidecars,
build-credential-github, build-credential-jira, build-credential-k8s, and
build-credential-google in the existing .PHONY list (update the .PHONY line near
the top where other phony targets are declared) to prevent name collisions with
files of the same names.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Credential sidecar build targets are currently orphaned from primary workflows.

At Line 228 you add sidecar build targets, but build-all (Line 170), push-all (Line 299), and _kind-load-images (Line 1263) still omit these images. This means make kind-up LOCAL_IMAGES=true and make push-all can skip sidecar images even when sidecars are required.

Suggested patch
-build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server build-observability-dashboard ## Build all container images
+build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server build-observability-dashboard build-credential-sidecars ## Build all container images
@@
-	`@for` image in $(FRONTEND_IMAGE) $(BACKEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE); do \
+	`@for` image in $(FRONTEND_IMAGE) $(BACKEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE) $(GITHUB_MCP_IMAGE) $(JIRA_MCP_IMAGE) $(K8S_MCP_IMAGE) $(GOOGLE_MCP_IMAGE); do \
@@
-	`@for` img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE); do \
+	`@for` img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE) $(GITHUB_MCP_IMAGE) $(JIRA_MCP_IMAGE) $(K8S_MCP_IMAGE) $(GOOGLE_MCP_IMAGE); do \
🧰 Tools
🪛 checkmake (0.3.2)

[warning] 228-228: Target "build-credential-sidecars" should be declared PHONY.

(phonydeclared)

🤖 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 `@Makefile` around lines 228 - 254, The new credential sidecar targets
(build-credential-github, build-credential-jira, build-credential-k8s,
build-credential-google) are not wired into the main flows; update the primary
meta targets build-all, push-all and the _kind-load-images target to include
these sidecar targets (or include a sidecar variable list) so that running make
build-all, make push-all, or make _kind-load-images (and make kind-up
LOCAL_IMAGES=true) will build/push/load the credential sidecars; reference the
targets by name (build-credential-github, build-credential-jira,
build-credential-k8s, build-credential-google) when adding them as dependencies
or appending them to the appropriate image lists used by build-all, push-all and
_kind-load-images.

build-cli: ## Build acpctl CLI binary
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building acpctl CLI..."
@cd components/ambient-cli && make build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ func runKubeMode(ctx context.Context, cfg *config.ControlPlaneConfig) error {
RunnerImageNamespace: cfg.RunnerImageNamespace,
MCPImage: cfg.MCPImage,
MCPAPIServerURL: cfg.MCPAPIServerURL,
GitHubMCPImage: cfg.GitHubMCPImage,
JiraMCPImage: cfg.JiraMCPImage,
K8sMCPImage: cfg.K8sMCPImage,
GoogleMCPImage: cfg.GoogleMCPImage,
RunnerLogLevel: cfg.RunnerLogLevel,
CPRuntimeNamespace: cfg.CPRuntimeNamespace,
CPTokenURL: cfg.CPTokenURL,
Expand Down
8 changes: 8 additions & 0 deletions components/ambient-control-plane/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type ControlPlaneConfig struct {
RunnerImageNamespace string
MCPImage string
MCPAPIServerURL string
GitHubMCPImage string
JiraMCPImage string
K8sMCPImage string
GoogleMCPImage string
RunnerLogLevel string
ProjectKubeTokenFile string
CPTokenListenAddr string
Expand Down Expand Up @@ -74,6 +78,10 @@ func Load() (*ControlPlaneConfig, error) {
RunnerImageNamespace: os.Getenv("RUNNER_IMAGE_NAMESPACE"),
MCPImage: os.Getenv("MCP_IMAGE"),
MCPAPIServerURL: envOrDefault("MCP_API_SERVER_URL", ""),
GitHubMCPImage: os.Getenv("GITHUB_MCP_IMAGE"),
JiraMCPImage: os.Getenv("JIRA_MCP_IMAGE"),
K8sMCPImage: os.Getenv("K8S_MCP_IMAGE"),
GoogleMCPImage: os.Getenv("GOOGLE_MCP_IMAGE"),
RunnerLogLevel: envOrDefault("RUNNER_LOG_LEVEL", "info"),
ProjectKubeTokenFile: os.Getenv("PROJECT_KUBE_TOKEN_FILE"),
CPTokenListenAddr: envOrDefault("CP_TOKEN_LISTEN_ADDR", ":8080"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@ const (
mcpSidecarURL = "http://localhost:8090"
)

type credentialSidecarSpec struct {
Name string
ImageField string
Port int64
ProviderEnvs map[string]string
}

var credentialSidecarRegistry = map[string]credentialSidecarSpec{
"github": {
Name: "credential-github",
ImageField: "GitHubMCPImage",
Port: 8091,
ProviderEnvs: map[string]string{
"GITHUB_PERSONAL_ACCESS_TOKEN": "__CREDENTIAL_TOKEN__",
},
},
"jira": {
Name: "credential-jira",
ImageField: "JiraMCPImage",
Port: 8092,
},
"kubeconfig": {
Name: "credential-k8s",
ImageField: "K8sMCPImage",
Port: 8093,
},
"google": {
Name: "credential-google",
ImageField: "GoogleMCPImage",
Port: 8094,
},
}

type KubeReconcilerConfig struct {
RunnerImage string
BackendURL string
Expand All @@ -36,6 +69,10 @@ type KubeReconcilerConfig struct {
RunnerImageNamespace string
MCPImage string
MCPAPIServerURL string
GitHubMCPImage string
JiraMCPImage string
K8sMCPImage string
GoogleMCPImage string
RunnerLogLevel string
CPRuntimeNamespace string
CPTokenURL string
Expand Down Expand Up @@ -451,6 +488,19 @@ func (r *SimpleKubeReconciler) ensurePod(ctx context.Context, namespace string,
r.logger.Info().Str("session_id", session.ID).Msg("MCP sidecar enabled for session")
}

credSidecars, credMCPURLs := r.buildCredentialSidecars(session.ID, credentialIDs)
containers = append(containers, credSidecars...)
if len(credMCPURLs) > 0 {
raw, err := json.Marshal(credMCPURLs)
if err == nil {
containers[0].(map[string]interface{})["env"] = append(
containers[0].(map[string]interface{})["env"].([]interface{}),
envVar("CREDENTIAL_MCP_URLS", string(raw)),
)
}
r.logger.Info().Int("count", len(credSidecars)).Str("session_id", session.ID).Msg("credential sidecars injected")
}
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 | ⚡ Quick win

Gate credential sidecar injection on token-exchange prerequisites.

This path injects sidecars and CREDENTIAL_MCP_URLS without validating CPTokenURL and CPTokenPublicKey. If those are empty, runner still gets endpoints that cannot authenticate/fetch credentials.

Suggested fix
-	credSidecars, credMCPURLs := r.buildCredentialSidecars(session.ID, credentialIDs)
-	containers = append(containers, credSidecars...)
-	if len(credMCPURLs) > 0 {
+	canUseCredentialSidecars := r.cfg.CPTokenURL != "" && r.cfg.CPTokenPublicKey != ""
+	if !canUseCredentialSidecars && len(credentialIDs) > 0 {
+		r.logger.Warn().
+			Str("session_id", session.ID).
+			Msg("credential sidecars disabled: CP token exchange config missing")
+	}
+	credSidecars, credMCPURLs := []interface{}{}, map[string]string{}
+	if canUseCredentialSidecars {
+		credSidecars, credMCPURLs = r.buildCredentialSidecars(session.ID, credentialIDs)
+		containers = append(containers, credSidecars...)
+	}
+	if len(credMCPURLs) > 0 {
 		raw, err := json.Marshal(credMCPURLs)
 		if err == nil {
 			containers[0].(map[string]interface{})["env"] = append(
 				containers[0].(map[string]interface{})["env"].([]interface{}),
 				envVar("CREDENTIAL_MCP_URLS", string(raw)),
 			)
 		}
 		r.logger.Info().Int("count", len(credSidecars)).Str("session_id", session.ID).Msg("credential sidecars injected")
 	}

As per coding guidelines "Never silently swallow partial failures; every error path must propagate or be explicitly collected, never discarded".

📝 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
credSidecars, credMCPURLs := r.buildCredentialSidecars(session.ID, credentialIDs)
containers = append(containers, credSidecars...)
if len(credMCPURLs) > 0 {
raw, err := json.Marshal(credMCPURLs)
if err == nil {
containers[0].(map[string]interface{})["env"] = append(
containers[0].(map[string]interface{})["env"].([]interface{}),
envVar("CREDENTIAL_MCP_URLS", string(raw)),
)
}
r.logger.Info().Int("count", len(credSidecars)).Str("session_id", session.ID).Msg("credential sidecars injected")
}
canUseCredentialSidecars := r.cfg.CPTokenURL != "" && r.cfg.CPTokenPublicKey != ""
if !canUseCredentialSidecars && len(credentialIDs) > 0 {
r.logger.Warn().
Str("session_id", session.ID).
Msg("credential sidecars disabled: CP token exchange config missing")
}
credSidecars, credMCPURLs := []interface{}{}, map[string]string{}
if canUseCredentialSidecars {
credSidecars, credMCPURLs = r.buildCredentialSidecars(session.ID, credentialIDs)
containers = append(containers, credSidecars...)
}
if len(credMCPURLs) > 0 {
raw, err := json.Marshal(credMCPURLs)
if err == nil {
containers[0].(map[string]interface{})["env"] = append(
containers[0].(map[string]interface{})["env"].([]interface{}),
envVar("CREDENTIAL_MCP_URLS", string(raw)),
)
}
r.logger.Info().Int("count", len(credSidecars)).Str("session_id", session.ID).Msg("credential sidecars injected")
}
🤖 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 `@components/ambient-control-plane/internal/reconciler/kube_reconciler.go`
around lines 491 - 502, The code currently injects credential sidecars and sets
CREDENTIAL_MCP_URLS unconditionally; change the logic in the block that calls
buildCredentialSidecars (around function/method kube_reconciler.go where
buildCredentialSidecars, envVar and containers are used) to first verify
token-exchange prerequisites (non-empty CPTokenURL and CPTokenPublicKey on the
session/config) and only proceed to append credSidecars and set the
CREDENTIAL_MCP_URLS env var when both are present; if either is missing, skip
injecting the sidecars/env and emit a clear log entry (r.logger.Warn/Info)
indicating token-exchange is not configured for session.ID. Also handle
json.Marshal error explicitly instead of swallowing it: if json.Marshal returns
an error, log the error with context (including session.ID and credMCPURLs) and
do not mutate containers. Ensure references to buildCredentialSidecars,
CREDENTIAL_MCP_URLS, envVar and containers remain consistent.


pod := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
Expand Down Expand Up @@ -827,6 +877,106 @@ func boolToStr(b bool) string {
return "false"
}

func (r *SimpleKubeReconciler) credentialSidecarImage(provider string) string {
switch provider {
case "github":
return r.cfg.GitHubMCPImage
case "jira":
return r.cfg.JiraMCPImage
case "kubeconfig":
return r.cfg.K8sMCPImage
case "google":
return r.cfg.GoogleMCPImage
default:
return ""
}
}

func (r *SimpleKubeReconciler) buildCredentialSidecars(sessionID string, credentialIDs map[string]string) ([]interface{}, map[string]string) {
var sidecars []interface{}
mcpURLs := map[string]string{}

for provider := range credentialIDs {
spec, ok := credentialSidecarRegistry[provider]
if !ok {
continue
}
image := r.credentialSidecarImage(provider)
if image == "" {
continue
}

imagePullPolicy := "Always"
if strings.HasPrefix(image, "localhost/") {
imagePullPolicy = "IfNotPresent"
}

env := []interface{}{
envVar("SESSION_ID", sessionID),
envVar("AMBIENT_API_URL", r.cfg.MCPAPIServerURL),
envVar("AMBIENT_CP_TOKEN_URL", r.cfg.CPTokenURL),
envVar("AMBIENT_CP_TOKEN_PUBLIC_KEY", r.cfg.CPTokenPublicKey),
envVar("SSL_CERT_FILE", "/etc/pki/ca-trust/extracted/pem/service-ca.crt"),
}
for k, v := range spec.ProviderEnvs {
env = append(env, envVar(k, v))
}
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))
}

sidecar := map[string]interface{}{
"name": spec.Name,
"image": image,
"imagePullPolicy": imagePullPolicy,
"ports": []interface{}{
map[string]interface{}{
"name": fmt.Sprintf("cred-%s", provider),
"containerPort": spec.Port,
"protocol": "TCP",
},
},
"env": env,
"volumeMounts": []interface{}{
map[string]interface{}{
"name": "service-ca",
"mountPath": "/etc/pki/ca-trust/extracted/pem/service-ca.crt",
"subPath": "service-ca.crt",
"readOnly": true,
},
},
"resources": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": "100m",
"memory": "128Mi",
},
"limits": map[string]interface{}{
"cpu": "500m",
"memory": "256Mi",
},
},
"securityContext": map[string]interface{}{
"allowPrivilegeEscalation": false,
"capabilities": map[string]interface{}{
"drop": []interface{}{"ALL"},
},
},
}

sidecars = append(sidecars, sidecar)
mcpURLs[provider] = fmt.Sprintf("http://localhost:%d", spec.Port)
r.logger.Debug().Str("provider", provider).Str("image", image).Int64("port", spec.Port).Msg("credential sidecar configured")
}

return sidecars, mcpURLs
}

func (r *SimpleKubeReconciler) buildMCPSidecar(sessionID string) interface{} {
mcpImage := r.cfg.MCPImage
imagePullPolicy := "Always"
Expand Down
Loading
Loading