KubeGuard-MCP
Container triage for Docker-built workloads: correlates container exit codes, restart behaviour and image pull/config errors, and produces the exact Dockerfile / image-level diff that remedies the failure alongside the Kubernetes manifest change.
Diagnoses and triages Kubernetes workload failures β OOMKilled, CrashLoopBackOff, ImagePullBackOff, CreateContainerConfigError, Unschedulable and Service port mismatches β by correlating pod status, events, logs and manifests into a deterministic root cause with a confidence score and severity. Returns the exact unified YAML patch for the user's Deployment (preserving comments and formatting), classifies proposed kubectl commands as safe-read, approval-required or blocked-destructive, and redacts Secrets and credentials before any analysis or output.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
π‘οΈ KubeGuard-MCP
Zero-Trust Kubernetes & Container Triage β MCP Server + CLI
Diagnose OOMKilled, CrashLoopBackOff, ImagePullBackOff, CreateContainerConfigError, Unschedulable
and Service port mismatches deterministically, get the exact YAML / Dockerfile diff that fixes them,
and never leak a secret to your terminal, your incident report or an LLM.
$0 to run, offline by design. The core engine is the Python standard library β no PyYAML, no SDKs, no API keys, no cloud, no network calls.
httpxis an optional extra used only by the opt-in LLM reviewer, which only ever receives redacted data.
Contents
Related MCP server: K8gentS
β¨ Why KubeGuard
The 3 a.m. problem | What KubeGuard does |
Pod JSON, events, logs and YAML are scattered; the root cause hides in the combination | One-shot correlator cross-references every signal and scores the evidence |
"Just bump the memory" / "just restart it" guesses | Deterministic root cause + the exact unified diff for your file β comments and formatting preserved |
Logs contain DB passwords, AWS keys, JWTs, kubeconfig tokens | A redaction firewall masks them before analysis and on every output path |
AI agents happily run | A zero-trust policy engine classifies every command |
LLM triage costs money and ships your data out | Fully offline. LLMs are an optional second opinion on redacted data only |
Tooling that works in one terminal only | Pure stdlib, Python 3.9 β 3.14, Linux / macOS / Windows (incl. cp1252 consoles) |
π 60-second quick start
No pip install, no virtualenv, no API key:
git clone https://github.com/Lokesh-Guttikonda/KubeGuard-MCP.gitcd KubeGuard-MCPpython scripts/demo_offline.pyOn Windows, enable full Unicode glyphs first (without it the output cleanly degrades to ASCII):
set PYTHONUTF8=1Install the kubeguard command (still zero runtime dependencies):
pip install .kubeguard diagnose examples/crashloop_secret_leakOr run it straight from GitHub with uv:
uvx --from git+https://github.com/Lokesh-Guttikonda/KubeGuard-MCP kubeguard demoπ₯οΈ Real demo output
The blocks below are the unedited output of the commands shown, regenerated by
scripts/update_readme.py β the test-suite fails if they drift from what the code prints.
The secret-leak scenario: the firewall masks the leaked database URL and AWS keys before the correlator sees them,
the root cause is pinned to the stale plaintext credentials in the Deployment, the fix moves them to secretKeyRef,
and the agent's "quick fix" with shell chaining is blocked.
$ python scripts/demo_offline.py --scenario crashloop_secret_leak --no-report
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β KubeGuard-MCP v1.0.0 Β· Zero-Trust Kubernetes & Container Triage Β· OFFLINE DEMO β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
engine: deterministic correlator Β· network calls: 0 Β· API keys: 0 Β· LLM reviewer: mock
βββ [1/1] crashloop_secret_leak ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
orders-api crash-loops after the database password rotation
incident KG-8E8F07F5 Β· pod shop/orders-api-6c8d9b7f45-tq9zp Β· deployment/orders-api
βΆ 1. Secret firewall 13 secrets blocked before analysis (k8s_secret_data Γ4, aws_access_key_id Γ3, db_connection_string Γ3, k8s_env_secret Γ2, aws_secret_access_key Γ1)
logs.txt:3 β¦ DEBUG DATABASE_URL=postgresql://orders_svc:[REDACTED:db_connection_string]@orders-db.shβ¦
logs.txt:4 2026-11-04T14:02:10.875Z DEBUG AWS_ACCESS_KEY_ID=[REDACTED:aws_access_key_id]
logs.txt:5 2026-11-04T14:02:10.875Z DEBUG AWS_SECRET_ACCESS_KEY=[REDACTED:aws_secret_access_key]
deployment.yaml:26 value: postgresql://orders_svc:[REDACTED:db_connection_string]@orders-db.shop.svc.clusteβ¦
β¦ and 9 more redacted lines
βΆ 2. Diagnosis CrashLoopBackOff Β· confidence 99% Β· SEV-1
root cause: The application cannot authenticate to its backing service (password authentication
failed) and exits with code 1 on startup, so the kubelet restarts it with exponential back-off.
The credentials are hard-coded in plaintext in the Deployment (DATABASE_URL, AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY) instead of being read from a Secret, so they went stale when the Secret
was rotated - and they leak into the startup logs.
evidence:
β’ [pod.status] container "orders" waiting: CrashLoopBackOff, restarts=9, last exit 1
(application error)
β’ [events] Warning/BackOff x41: Back-off restarting failed container orders in pod
orders-api-6c8d9b7f45-tq9zp_shop(2c7e9f1a-4d3b-4b8e-9f6a-1e2d3c4b5a69)
β’ [logs] line 17: asyncpg.exceptions.InvalidPasswordError: password authentication failed for
user "orders_svc"
β’ [manifest] credentials hard-coded as plaintext env values in deployment.yaml: DATABASE_URL,
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
β’ [secrets] Secret orders-db-credentials holds key 'database-url' matching DATABASE_URL
(rotated 2026-11-04T13:45:00Z)
β’ [secrets] Secret orders-aws-credentials holds key 'aws-access-key-id' matching
AWS_ACCESS_KEY_ID
β’ [secrets] Secret orders-aws-credentials holds key 'aws-secret-access-key' matching
AWS_SECRET_ACCESS_KEY
βΆ 3. Exact fix 1 patch β deployment.yaml
--- a/deployment.yaml
+++ b/deployment.yaml
@@ -23,11 +23,20 @@
containerPort: 8000
env:
- name: DATABASE_URL
- value: [REDACTED:k8s_env_secret]
+ valueFrom:
+ secretKeyRef:
+ name: orders-db-credentials
+ key: database-url
- name: AWS_ACCESS_KEY_ID
- value: [REDACTED:aws_access_key_id]
+ valueFrom:
+ secretKeyRef:
+ name: orders-aws-credentials
+ key: aws-access-key-id
- name: AWS_SECRET_ACCESS_KEY
- value: [REDACTED:k8s_env_secret]
+ valueFrom:
+ secretKeyRef:
+ name: orders-aws-credentials
+ key: aws-secret-access-key
- name: S3_INVOICE_BUCKET
value: acme-shop-invoices
- name: LOG_LEVEL
1. Replace the plaintext env values with secretKeyRef references (patch below) so the Pod
always reads the current, rotated credential.
2. Rotate every credential that appeared in the logs (database password, cloud keys) - they
must be treated as compromised.
3. Stop logging configuration values at startup; log only key names.
βΆ 4. Zero-trust policy agent-proposed actions
β SAFE_READ kubectl logs orders-api-6c8d9b7f45-tq9zp -n shop --previous
β APPROVAL_REQUIRED kubectl get secret orders-db-credentials -n shop -o jsonpath='{.data.password}'
β reads Secret values (-o yaml/json/jsonpath decodes credentials)
β BLOCKED_DESTRUCTIVE kubectl delete secret orders-db-credentials -n shop; kubectl apply -f old-secret.yaml
β shell operator ';' (pipes, chaining, redirection) is not allowed; submit one command
βΆ 5. AI review (mock) AGREE - redacted payload only
3 independent signals corroborate CrashLoopBackOff (db auth failure); the patch is minimal and
reversible.
risk: 3 credentials leaked into runtime logs - rotate before rolling out the fix.
risk: Restarting pods will not help until the underlying startup error is fixed.
βββ Summary βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# scenario root cause conf sev secrets fix
1 crashloop_secret_leak β CrashLoopBackOff 99% SEV-1 13 deployment.yaml
1/1 incidents diagnosed correctly Β· 13 secrets blocked Β· 0 network calls Β· 0 API keys Β· no LLM required$ python scripts/demo_offline.py --no-report
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β KubeGuard-MCP v1.0.0 Β· Zero-Trust Kubernetes & Container Triage Β· OFFLINE DEMO β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
engine: deterministic correlator Β· network calls: 0 Β· API keys: 0 Β· LLM reviewer: mock
βββ [1/4] oom_killed βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
payments-api restarts every few minutes under settlement load
incident KG-D509530D Β· pod payments/payments-api-7d9f8b6c5d-x2k4q Β· deployment/payments-api
βΆ 1. Secret firewall 2 secrets blocked before analysis (email Γ1, jwt Γ1)
logs.txt:6 β¦ : POST /v1/charges headers={Authorization=Bearer [REDACTED:jwt], X-Request-Id=8f14e45f}
logs.txt:7 β¦.a.p.service.ChargeService : charge accepted customer=[REDACTED:email] amount=129.00 EUR
βΆ 2. Diagnosis OOMKilled Β· confidence 99% Β· SEV-2
root cause: JVM max heap -Xmx768m (768Mi) is 150% of the container memory limit (512Mi). Heap +
metaspace + thread stacks + off-heap buffers exceed the cgroup limit, so the kernel OOM killer
SIGKILLs the process (exit 137) before the runtime can even throw an OutOfMemoryError.
evidence:
β’ [pod.status] container "api" lastState.terminated.reason=OOMKilled (exitCode=137,
restarts=6)
β’ [events] Warning/OOMKilling: Memory cgroup out of memory: Killed process 4127 (java)
total-vm:3914212kB, anon-rss:521436kB, file-rss:18220kB, shmem-rss:0kB, UID:10001
pgtables:1444kB oom_sβ¦
β’ [logs] line 1: Picked up JAVA_TOOL_OPTIONS: -Xmx768m -XX:+UseG1GC
-XX:+ExitOnOutOfMemoryError
β’ [dockerfile] -Xmx768m set in Dockerfile, but resources.limits.memory=512Mi
also: CrashLoopBackOff (54%) - symptom of OOMKilled
βΆ 3. Exact fix 2 patches β deployment.yaml, Dockerfile
--- a/deployment.yaml
+++ b/deployment.yaml
@@ -33,7 +33,7 @@
memory: 256Mi
limits:
cpu: "1"
- memory: 512Mi
+ memory: 1Gi
readinessProbe:
httpGet:
path: /actuator/health/readiness
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/payments-api.jar /app/app.jar
-ENV JAVA_TOOL_OPTIONS="-Xmx768m -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
+ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
1. Raise resources.limits.memory of container "api" from 512Mi to 1Gi.
2. Replace -Xmx768m with -XX:MaxRAMPercentage=75.0 in Dockerfile so the heap tracks the
container limit instead of a hard-coded size.
3. After the rollout, confirm the working set stays below ~80% of the limit (kubectl top) and
consider a VerticalPodAutoscaler in recommendation mode.
βΆ 4. Zero-trust policy agent-proposed actions
β SAFE_READ kubectl top pod payments-api-7d9f8b6c5d-x2k4q -n payments --containers
β APPROVAL_REQUIRED kubectl set resources deployment/payments-api -n payments -c api --limits=memory=1Gi
β 'set' changes cluster state
β BLOCKED_DESTRUCTIVE kubectl delete pods --all -n payments
β delete --all removes every matching object
βΆ 5. AI review (mock) AGREE - redacted payload only
3 independent signals corroborate OOMKilled (heap exceeds limit); the patch is minimal and
reversible.
risk: 1 credential leaked into runtime logs - rotate before rolling out the fix.
risk: A higher memory limit raises node memory pressure; confirm allocatable headroom before
the rollout.
βββ [2/4] crashloop_secret_leak ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
orders-api crash-loops after the database password rotation
incident KG-8E8F07F5 Β· pod shop/orders-api-6c8d9b7f45-tq9zp Β· deployment/orders-api
βΆ 1. Secret firewall 13 secrets blocked before analysis (k8s_secret_data Γ4, aws_access_key_id Γ3, db_connection_string Γ3, k8s_env_secret Γ2, aws_secret_access_key Γ1)
logs.txt:3 β¦ DEBUG DATABASE_URL=postgresql://orders_svc:[REDACTED:db_connection_string]@orders-db.shβ¦
logs.txt:4 2026-11-04T14:02:10.875Z DEBUG AWS_ACCESS_KEY_ID=[REDACTED:aws_access_key_id]
logs.txt:5 2026-11-04T14:02:10.875Z DEBUG AWS_SECRET_ACCESS_KEY=[REDACTED:aws_secret_access_key]
deployment.yaml:26 value: postgresql://orders_svc:[REDACTED:db_connection_string]@orders-db.shop.svc.clusteβ¦
β¦ and 9 more redacted lines
βΆ 2. Diagnosis CrashLoopBackOff Β· confidence 99% Β· SEV-1
root cause: The application cannot authenticate to its backing service (password authentication
failed) and exits with code 1 on startup, so the kubelet restarts it with exponential back-off.
The credentials are hard-coded in plaintext in the Deployment (DATABASE_URL, AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY) instead of being read from a Secret, so they went stale when the Secret
was rotated - and they leak into the startup logs.
evidence:
β’ [pod.status] container "orders" waiting: CrashLoopBackOff, restarts=9, last exit 1
(application error)
β’ [events] Warning/BackOff x41: Back-off restarting failed container orders in pod
orders-api-6c8d9b7f45-tq9zp_shop(2c7e9f1a-4d3b-4b8e-9f6a-1e2d3c4b5a69)
β’ [logs] line 17: asyncpg.exceptions.InvalidPasswordError: password authentication failed for
user "orders_svc"
β’ [manifest] credentials hard-coded as plaintext env values in deployment.yaml: DATABASE_URL,
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
β’ [secrets] Secret orders-db-credentials holds key 'database-url' matching DATABASE_URL
(rotated 2026-11-04T13:45:00Z)
β’ [secrets] Secret orders-aws-credentials holds key 'aws-access-key-id' matching
AWS_ACCESS_KEY_ID
β’ [secrets] Secret orders-aws-credentials holds key 'aws-secret-access-key' matching
AWS_SECRET_ACCESS_KEY
βΆ 3. Exact fix 1 patch β deployment.yaml
--- a/deployment.yaml
+++ b/deployment.yaml
@@ -23,11 +23,20 @@
containerPort: 8000
env:
- name: DATABASE_URL
- value: [REDACTED:k8s_env_secret]
+ valueFrom:
+ secretKeyRef:
+ name: orders-db-credentials
+ key: database-url
- name: AWS_ACCESS_KEY_ID
- value: [REDACTED:aws_access_key_id]
+ valueFrom:
+ secretKeyRef:
+ name: orders-aws-credentials
+ key: aws-access-key-id
- name: AWS_SECRET_ACCESS_KEY
- value: [REDACTED:k8s_env_secret]
+ valueFrom:
+ secretKeyRef:
+ name: orders-aws-credentials
+ key: aws-secret-access-key
- name: S3_INVOICE_BUCKET
value: acme-shop-invoices
- name: LOG_LEVEL
1. Replace the plaintext env values with secretKeyRef references (patch below) so the Pod
always reads the current, rotated credential.
2. Rotate every credential that appeared in the logs (database password, cloud keys) - they
must be treated as compromised.
3. Stop logging configuration values at startup; log only key names.
βΆ 4. Zero-trust policy agent-proposed actions
β SAFE_READ kubectl logs orders-api-6c8d9b7f45-tq9zp -n shop --previous
β APPROVAL_REQUIRED kubectl get secret orders-db-credentials -n shop -o jsonpath='{.data.password}'
β reads Secret values (-o yaml/json/jsonpath decodes credentials)
β BLOCKED_DESTRUCTIVE kubectl delete secret orders-db-credentials -n shop; kubectl apply -f old-secret.yaml
β shell operator ';' (pipes, chaining, redirection) is not allowed; submit one command
βΆ 5. AI review (mock) AGREE - redacted payload only
3 independent signals corroborate CrashLoopBackOff (db auth failure); the patch is minimal and
reversible.
risk: 3 credentials leaked into runtime logs - rotate before rolling out the fix.
risk: Restarting pods will not help until the underlying startup error is fixed.
βββ [3/4] service_port_mismatch ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
checkout-frontend returns 502 for every request although pods are healthy
incident KG-1D8ACCD3 Β· pod web/checkout-frontend-58f6d7c9b4-hn7wz Β· deployment/checkout-frontend
βΆ 1. Secret firewall 2 secrets blocked before analysis (email Γ1, secret_assignment Γ1)
logs.txt:5 [2026-11-04T16:20:00.915Z] debug: SESSION_SECRET=[REDACTED:secret_assignment]
logs.txt:10 [2026-11-04T16:22:03.004Z] info: password reset e-mail queued for [REDACTED:email]
βΆ 2. Diagnosis ServicePortMismatch Β· confidence 95% Β· SEV-1
root cause: Service checkout-frontend forwards port 80 to targetPort 8080, but the container
listens on 3000. kube-proxy sends traffic to a closed port, so clients get connection refused /
502s while the pods themselves look healthy.
evidence:
β’ [service] service.yaml: port 80 -> targetPort 8080, but containers expose http=3000
β’ [logs] application reports it is listening on port 3000
β’ [pod.status] pods are Running and Ready - traffic fails only through the Service
β’ [endpoints] Endpoints checkout-frontend route to port(s) [8080]
βΆ 3. Exact fix 1 patch β service.yaml
--- a/service.yaml
+++ b/service.yaml
@@ -12,5 +12,5 @@
ports:
- name: http
port: 80
- targetPort: 8080 # legacy Express server port (pre Node 22 migration)
+ targetPort: http
protocol: TCP
1. Set targetPort of Service checkout-frontend port 80 to http (3000) (patch below).
2. Referencing the named container port keeps Service and Deployment in sync.
βΆ 4. Zero-trust policy agent-proposed actions
β SAFE_READ kubectl get endpoints checkout-frontend -n web -o wide
β APPROVAL_REQUIRED kubectl port-forward svc/checkout-frontend 8080:80 -n web
β opens a network tunnel into the cluster
β BLOCKED_DESTRUCTIVE kubectl replace --force -f service.yaml -n web
β replace --force deletes and recreates objects
βΆ 5. AI review (mock) AGREE - redacted payload only
3 independent signals corroborate ServicePortMismatch (target port); the patch is minimal and
reversible.
risk: 1 credential leaked into runtime logs - rotate before rolling out the fix.
risk: Ingress/NetworkPolicy objects that reference the old port number must be updated
together.
βββ [4/4] image_pull_backoff βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ingest-worker stuck in ImagePullBackOff after the registry migration
incident KG-A14B00E1 Β· pod data/ingest-worker-5f7c9d8b6-m4n2x Β· deployment/ingest-worker
βΆ 1. Secret firewall 4 secrets blocked before analysis (k8s_secret_data Γ2, kubeconfig_token Γ2)
ci-deploy.log:11 client-key-data: [REDACTED:kubeconfig_token]
ci-deploy.log:12 token: [REDACTED:kubeconfig_token]
secrets.json:11 ".dockerconfigjson": "[REDACTED:k8s_secret_data]"
secrets.json:19 "data": {"api-token": "[REDACTED:k8s_secret_data]"}
βΆ 2. Diagnosis ImagePullBackOff Β· confidence 99% Β· SEV-1
root cause: The kubelet is not authorised to pull
registry.acme.internal:5000/data/ingest-worker:2.8.0 from the private registry
registry.acme.internal:5000 (401/denied). The pod spec has no imagePullSecrets, so the pull is
anonymous.
evidence:
β’ [pod.status] container "worker" waiting: ImagePullBackOff - Back-off pulling image
"registry.acme.internal:5000/data/ingest-worker:2.8.0"
β’ [events] Warning/Failed: Failed to pull image
"registry.acme.internal:5000/data/ingest-worker:2.8.0": failed to pull and unpack image
"registry.acme.internal:5000/data/ingest-worker:2.8.0": failed to resolve reference
"registry.acme.internal:50β¦
β’ [events] Normal/BackOff x23: Back-off pulling image
"registry.acme.internal:5000/data/ingest-worker:2.8.0"
β’ [secrets] dockerconfigjson Secret acme-registry-creds exists for
registry.acme.internal:5000
βΆ 3. Exact fix 1 patch β deployment.yaml
--- a/deployment.yaml
+++ b/deployment.yaml
@@ -26,3 +26,5 @@
memory: 512Mi
limits:
memory: 1Gi
+ imagePullSecrets:
+ - name: acme-registry-creds
1. Attach the existing pull secret acme-registry-creds to the pod template (patch below).
βΆ 4. Zero-trust policy agent-proposed actions
β SAFE_READ kubectl describe pod ingest-worker-5f7c9d8b6-m4n2x -n data
β APPROVAL_REQUIRED kubectl patch serviceaccount default -n data -p '{"imagePullSecrets": [{"name": "acme-registry-creds"}]}'
β 'patch' changes cluster state
β BLOCKED_DESTRUCTIVE kubectl create clusterrolebinding ingest-admin --clusterrole=cluster-admin --serviceaccount=data:default
β grants 'cluster-admin' via clusterrolebinding (privilege escalation)
βΆ 5. AI review (mock) AGREE - redacted payload only
3 independent signals corroborate ImagePullBackOff (registry auth); the patch is minimal and
reversible.
risk: 2 credentials leaked into runtime logs - rotate before rolling out the fix.
risk: Pull secrets are namespace-scoped; every namespace that runs the image needs its own
copy.
βββ Summary βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# scenario root cause conf sev secrets fix
1 oom_killed β OOMKilled 99% SEV-2 2 deployment.yaml, Dockerfile
2 crashloop_secret_leak β CrashLoopBackOff 99% SEV-1 13 deployment.yaml
3 service_port_mismatch β ServicePortMismatch 95% SEV-1 2 service.yaml
4 image_pull_backoff β ImagePullBackOff 99% SEV-1 4 deployment.yaml
4/4 incidents diagnosed correctly Β· 21 secrets blocked Β· 0 network calls Β· 0 API keys Β· no LLM requiredπ¨ SRE Incident Report β KG-D509530D
payments-api restarts every few minutes under settlement load
A Spring Boot service was upgraded to v3.4.1 (new in-memory settlement batch). Pods restart with exit code 137 roughly every 3 minutes and the on-call sees CrashLoopBackOff.
Field | Value |
Incident ID |
|
Severity | SEV-2 |
Status | Root cause identified β fix proposed |
Primary failure |
|
Workload |
|
Pod |
|
First signal | 2026-11-04T09:12:03Z |
Secret firewall | 2 values redacted (email Γ1, jwt Γ1) |
Engine | kubeguard-mcp v1.0.0 Β· deterministic Β· offline |
Summary
JVM max heap -Xmx768m (768Mi) is 150% of the container memory limit (512Mi). Heap + metaspace + thread stacks + off-heap buffers exceed the cgroup limit, so the kernel OOM killer SIGKILLs the process (exit 137) before the runtime can even throw an OutOfMemoryError.
Root cause analysis
Container "api" is being OOMKilled β OOMKilled / heap_exceeds_limit β confidence 99%
Signal source | Evidence |
| container "api" lastState.terminated.reason=OOMKilled (exitCode=137, restarts=6) |
| Warning/OOMKilling: Memory cgroup out of memory: Killed process 4127 (java) total-vm:3914212kB, anon-rss:521436kB, file-rss:18220kB, shmem-rss:0kB, UID:10001 pgtables:1444kB oom_s⦠|
| line 1: Picked up JAVA_TOOL_OPTIONS: -Xmx768m -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError |
| -Xmx768m set in Dockerfile, but resources.limits.memory=512Mi |
Remediation
Raise resources.limits.memory of container "api" from 512Mi to 1Gi.
Replace -Xmx768m with -XX:MaxRAMPercentage=75.0 in Dockerfile so the heap tracks the container limit instead of a hard-coded size.
After the rollout, confirm the working set stays below ~80% of the limit (kubectl top) and consider a VerticalPodAutoscaler in recommendation mode.
Proposed patch β deployment.yaml
memory limit 512Mi -> 1Gi
--- a/deployment.yaml
+++ b/deployment.yaml
@@ -33,7 +33,7 @@
memory: 256Mi
limits:
cpu: "1"
- memory: 512Mi
+ memory: 1Gi
readinessProbe:
httpGet:
path: /actuator/health/readinessProposed patch β Dockerfile
replace -Xmx768m with a limit-relative heap
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/payments-api.jar /app/app.jar
-ENV JAVA_TOOL_OPTIONS="-Xmx768m -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
+ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
USER 10001
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]Secondary findings
CrashLoopBackOff(54%): Container "api" is crash-looping β symptom ofOOMKilled
Zero-trust command plan
Every command was classified by the policy engine; KubeGuard never executes anything itself.
# | Command | Verdict | Purpose |
1 |
| π’ SAFE_READ | confirm Last State: OOMKilled |
2 |
| π’ SAFE_READ | observe live memory usage |
3 |
| π’ SAFE_READ | logs from the killed container |
4 |
| π‘ APPROVAL_REQUIRED | roll out the patched manifest |
5 |
| π’ SAFE_READ | watch the rollout |
Agent guardrail check
Actions an autonomous agent proposed for this incident, as judged by the zero-trust policy:
Proposed action | Verdict | Risk | Reason |
| π’ SAFE_READ | 0 | read-only command |
| π‘ APPROVAL_REQUIRED | 45 | 'set' changes cluster state |
| π΄ BLOCKED_DESTRUCTIVE | 100 | delete --all removes every matching object |
Timeline (UTC)
Time | Source | Type | Reason | Message |
2026-11-04T09:12:03Z | event | Normal | Scheduled | Successfully assigned payments/payments-api-7d9f8b6c5d-x2k4q to ip-10-0-3-17.eu-west-1.compute.internal |
2026-11-04T09:31:09Z | event | Normal | Pulled | Container image "ghcr.io/acme/payments-api:3.4.1" already present on machine (x7) |
2026-11-04T09:31:10Z | event | Normal | Started | Started container api (x7) |
2026-11-04T09:33:52Z | event | Warning | OOMKilling | Memory cgroup out of memory: Killed process 4127 (java) total-vm:3914212kB, anon-rss:521436kB, file-rss:18220kB, shmem-rss:0kB, UID:10001 pgtables:1444kB oom_score_adj:983 (x6) |
2026-11-04T09:33:52Z | container | Warning | OOMKilled | container "api" terminated (exit 137, restarts=6) |
2026-11-04T09:34:20Z | event | Warning | BackOff | Back-off restarting failed container api in pod payments-api-7d9f8b6c5d-x2k4q_payments(6f1c2a9e-2b7d-4c1e-9a43-0d8e5b7f21c4) (x14) |
Secret firewall audit
File | Redactions | Max severity | Rules |
| 2 | high | email Γ1, jwt Γ1 |
β οΈ Security impact: 1 credential written to runtime logs (jwt Γ1). Anything that reached a log pipeline must be treated as compromised: rotate it, then purge the log streams.
Values were masked before analysis and before any output, report or LLM call.
Generated by kubeguard-mcp v1.0.0 Β· deterministic correlation Β· zero network calls Β· every line passed the secret firewall.
Run python scripts/demo_offline.py without --no-report to also print the consolidated Markdown SRE incident
report for all four incidents, or --report out/incident-report.md to write it to a file.
ποΈ Architecture
MCP clients You / CI / shell
Claude Desktop, Claude Code, Cursor, ... kubeguard CLI, scripts/demo_offline.py
| JSON-RPC 2.0 over stdio |
v v
+---------------------------+ +---------------------------+
| mcp_server.py | | cli.py |
| 5 read-only tools, | | demo diagnose redact |
| resources, prompts | | audit check serve-mcp |
+-------------+-------------+ +-------------+-------------+
| |
+-----------------------+-----------------------+
|
pod.json v
events.json +---------------------------+
logs.txt -------> | redactor.py (ingress) | secret & PII firewall:
*.yaml / *.json | AWS, JWT, DB URLs, .env, | masks before any detector
Dockerfile | kubeconfig, Secrets, PII | reads the data
docker inspect +-------------+-------------+
|
v
+---------------------------+ +---------------------------+
| correlator.py | ---> | policy.py |
| 6 detectors, weighted | | SAFE_READ |
| evidence, confidence | | APPROVAL_REQUIRED |
+-------------+-------------+ | BLOCKED_DESTRUCTIVE |
| +---------------------------+
v
+---------------------------+ +---------------------------+
| patcher.py + yaml_lite.py | | auditor.py |
| exact, comment-preserving | | validate_manifest |
| YAML/JSON/Dockerfile diff | | audit_cluster_risks, RBAC |
+-------------+-------------+ +---------------------------+
|
v
+---------------------------+ +---------------------------+
| report.py | <--- | llm_reviewer.py |
| Markdown SRE report, | | optional second opinion |
| console, JSON | | mock / groq / gemini / |
+-------------+-------------+ | anthropic (redacted only) |
| +---------------------------+
v
+---------------------------+
| redactor.py (egress) |
+-------------+-------------+
|
v
terminal | report.md | MCP response | LLM promptIngress firewall β
redactor.pymasks secrets in logs/events before any detector reads them.Correlate β
correlator.pynormalises Pod status, Events (v1andevents.k8s.io/v1), logs, Deployments, Services, Secrets/ConfigMaps inventories, Nodes, Endpoints,docker inspectand docker-compose, then runs six detectors that each produce weighted evidence and a confidence score.Patch β
patcher.pyturns path-addressed edits into surgical line edits using the spans recorded byyaml_lite.py, so the diff touches only what must change (comments, key order and quoting survive).Classify β every suggested command (and every action an agent proposes) goes through
policy.py.Egress firewall β reports, diffs, MCP responses and LLM prompts are redacted again on the way out.
π Connect Claude Desktop, Claude Code or Cursor
The MCP server speaks newline-delimited JSON-RPC 2.0 over stdio (protocol 2025-06-18, also negotiates
2025-03-26 and 2024-11-05). It holds no cluster credentials and never executes commands β your agent
collects data with read-only kubectl calls and KubeGuard analyses it.
Claude Desktop β claude_desktop_config.json (after pip install .):
{
"mcpServers": {
"kubeguard": {
"command": "kubeguard",
"args": ["serve-mcp"],
"env": { "PYTHONUTF8": "1" }
}
}
}Zero-install variant straight from a clone (allows diagnose_pod to read bundles under --roots):
{
"mcpServers": {
"kubeguard": {
"command": "python",
"args": ["/absolute/path/to/KubeGuard-MCP/scripts/kubeguard.py", "serve-mcp", "--roots", "/absolute/path/to/incidents"]
}
}
}Cursor β the same mcpServers block in ~/.cursor/mcp.json (global) or .cursor/mcp.json (per project).
Claude Code:
claude mcp add kubeguard -- kubeguard serve-mcpclaude mcp add kubeguard -- uvx --from git+https://github.com/Lokesh-Guttikonda/KubeGuard-MCP kubeguard serve-mcpThen ask: "Pod payments-api-7d9f8b6c5d-x2k4q in payments keeps restarting β triage it with KubeGuard."
The bundled triage_pod prompt tells the agent which SAFE_READ commands to run and to check every command it
proposes with classify_command.
π§° MCP tools
All tools are annotated readOnlyHint: true, destructiveHint: false, openWorldHint: false, and every result
passes the egress firewall.
Tool | Input | Output |
|
| Markdown SRE report (or JSON) + |
|
| redacted text + audit (counts per rule, line numbers β never values) |
|
| findings with rule IDs, severities and line numbers, 0β100 score, grade |
|
| 0β100 risk score, top risks, RBAC escalation paths, secret exposure, per-command verdicts |
|
| verdict, reasons, risk score, whether the role may run it |
Also exposed: resources kubeguard://policy/verdicts and kubeguard://examples/<scenario>, and the
triage_pod prompt.
β¨οΈ CLI
Command | What it does |
| the four offline incidents end-to-end |
| correlate a bundle; |
| firewall for files or stdin; |
| lint manifests + RBAC + command policy; |
| zero-trust verdict; exit 0 safe, 3 needs approval, 4 blocked |
| start the MCP server on stdio |
From a source checkout every command also works as python scripts/kubeguard.py β¦ or python -m src β¦.
kubectl logs deploy/api --previous | kubeguard redact --audit > safe.logkubeguard check kubectl delete pods --all -n prodkubeguard audit k8s/ --fail-on highπ¬ What gets diagnosed β and fixed
Failure | Signals correlated | Sub-causes | Deterministic fix |
OOMKilled |
|
| raise |
CrashLoopBackOff | waiting reason, |
| plaintext env β |
ImagePullBackOff / | waiting reason, pull events, |
| attach the pull secret for that registry; roll back to the last known-good tag; lowercase the repository |
CreateContainerConfigError | waiting message, events, Secret/ConfigMap inventories |
| fix key/object-name typos (env, envFrom, volumes); |
Unschedulable |
|
| right-size requests to fit the largest node; fix |
ServicePortMismatch | Service selector/ |
| point |
Root causes outrank symptoms (a crash loop caused by OOM is reported as a symptom of OOMKilled), results are
fully deterministic (same bundle β same incident ID, report and patch), and no wall-clock time appears in output.
π₯ Secret & PII firewall
Precompiled rules with substring pre-filters, a case-folded fast path and a single splice pass (~6 MB/s of
realistic logs on one core). Rules run in priority order and a generic rule can never overlap a span claimed by a
specific one, so postgres://app:hunter2@db becomes postgres://app:[REDACTED:db_connection_string]@db β the
host and user stay visible for debugging. Redaction is idempotent and every finding is audited by rule, category,
severity, line and column β never by value.
Rule | Category | Severity | Detects |
| secret | critical | PEM / OpenSSH / PGP private key blocks |
| secret | critical | AWS credentials (env, JSON, CLI output) |
| secret | critical | kubeconfig |
| secret | high | JWTs (incl. service-account tokens), |
| secret | critical / high | passwords in Postgres/MySQL/Mongo/Redis/AMQP/β¦ URLs, |
| secret | high / critical | provider API keys (incl. Anthropic, OpenAI, Groq) |
| secret | critical | registry |
| secret | critical |
|
| secret | critical | every value under |
| secret | high |
|
| pii | high / medium | personal data ( |
| pii | low | opt-in ( |
False positives are actively suppressed: secretName:, DB_PASSWORD_FILE=, max_tokens=1024,
InvalidPasswordError: password authentication failed, dsn=settings.database_url, ${VAR} placeholders,
git@github.com: and in-cluster *.svc.cluster.local addresses are left alone.
π¦ Zero-trust policy engine
Verdict | Meaning | Examples |
π’ | read-only and non-sensitive; may run unattended |
|
π‘ | mutates state, opens a shell/tunnel, or reads secret values |
|
π΄ | irreversible, bulk, privilege-escalating or injected |
|
Roles (--role / KUBEGUARD_ROLE): viewer may only run SAFE_READ; operator may run APPROVAL_REQUIRED
once a human approved it; admin may additionally break glass on BLOCKED_DESTRUCTIVE β only with approval and
a written justification, which is flagged in the audit trail. analyze_rbac() statically flags wildcard grants,
Secret readers, pods/exec, escalate/bind/impersonate, token minting, webhook control, cluster-admin and
public-group bindings.
π€ Optional LLM reviewer
The deterministic diagnosis is always the answer; an LLM can add a second opinion. The payload is built from the already-redacted result and scanned again right before egress.
| Key | Notes |
| β | offline, deterministic, free |
|
| OpenAI-compatible chat completions |
|
| key sent in a header, never in the URL |
|
| Messages API |
KUBEGUARD_LLM_MODEL, KUBEGUARD_LLM_BASE_URL and KUBEGUARD_LLM_TIMEOUT override the defaults. Uses httpx
when installed (pip install ".[llm]") and the stdlib urllib otherwise. Failures never break triage: the review is
reported as UNAVAILABLE.
PROVIDER=groq GROQ_API_KEY=... kubeguard diagnose examples/oom_killed --reviewπ Python API
from src import correlate, Redactor, PolicyEngine # `from kubeguard import ...` once installed
from src.correlator import redacted_diff
result = correlate("examples/oom_killed")
print(result.primary.failure.value, result.primary.confidence) # OOMKilled 0.99
for patch in result.primary.patches:
print(redacted_diff(patch)) # exact, secret-free unified diff
print(Redactor().redact("DB_PASSWORD=hunter2").text) # DB_PASSWORD=[REDACTED:secret_assignment]
print(PolicyEngine(role="viewer").authorize("kubectl delete ns prod").allowed) # FalseIncidentBundle.from_inputs(pod=..., events=..., logs=..., manifests=...) accepts dicts or JSON/YAML text, so it
plugs directly into your own tooling.
π§ͺ Testing & quality
pip install pytestpython -m pytest -q500+ tests cover every module: the YAML engine (round-trips, spans, error lines), the patcher, every redaction rule
plus false-positive suppression and a 1 MB throughput budget, 100+ policy classifications and the RBAC analyser, all
six detectors and their sub-causes, reports, the LLM adapters (fake transports + a real local HTTP server), the full
MCP JSON-RPC surface (lifecycle, batching, error codes, notifications, resources, prompts, a real stdio subprocess),
every CLI command and exit code, and Windows UTF-8 safety (subprocesses under cp1252 and ascii code pages,
isolated python -I runs proving zero dependencies, CRLF manifests, byte-exact MCP UTF-8 under any locale).
CI runs them on Linux, macOS and Windows for Python 3.9 β 3.14, plus ruff, a zero-dependency demo job, a wheel
build + install smoke test and the README freshness check.
π Security model
No credentials, no execution. KubeGuard analyses data you give it; it never talks to a cluster, a registry or the internet (except the opt-in LLM reviewer).
Two firewalls. Secrets are masked on ingress (before detectors run) and again on egress (every report, diff, MCP result and LLM prompt).
Read-only MCP surface. Tools cannot mutate anything;
bundle_pathis confined toKUBEGUARD_MCP_ROOTS(default: the server's working directory); stdout carries only JSON-RPC, diagnostics go to stderr.Default deny. Unknown binaries are blocked, unknown verbs need approval, shell metacharacters are rejected.
Patches stay local.
--write-fixedwrites the patched files to a directory you choose; displayed diffs are always redacted (the CLI warns if a hunk contained a masked value).
π Project layout
src/ # installed as the `kubeguard` package
redactor.py # secret & PII firewall
policy.py # zero-trust command policy + RBAC analyser
correlator.py # incident bundle loader + 6 deterministic detectors
patcher.py # comment-preserving YAML/JSON/Dockerfile edits -> unified diff
yaml_lite.py # stdlib YAML subset parser/dumper with line spans
auditor.py # validate_manifest / audit_cluster
report.py # Markdown SRE report, console, JSON renderers
llm_reviewer.py # PROVIDER=mock|groq|gemini|anthropic (redacted-only)
mcp_server.py # JSON-RPC 2.0 stdio MCP server
cli.py # `kubeguard` command line
console.py # Unicode/ASCII-safe terminal output
demo.py, scenarios.py # offline demo engine + scenario discovery
examples/ # 4 realistic incident bundles (pod.json, events.json, logs, YAML, Dockerfile, ...)
scripts/ # demo_offline.py, kubeguard.py (zero-install launcher), update_readme.py
tests/ # pytest suiteLicense
MIT Β© 2026 Lokesh Guttikonda
This server cannot be deployed
Maintenance
Related MCP Connectors
Fail-closed policy guardrails for AI agents running kubectl, terraform, helm, and argocd.
Deterministic runtime safety for AI agents: scan PII, gate tool actions, verify LLM output.
Autonomous and governed SRE & MLOps control plane for AI agent platforms.
Pre-execution governance for AI agents. Deterministic PASS/FAIL/REVIEW verdicts, replayable proof.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables LLMs to automatically diagnose coding errors through codebase search, test execution, and live debugger integration (DAP/V8 CDP). Provides a secure, policy-gated environment for investigating failures while preventing destructive operations.9ISC
- FlicenseNot gradedqualityDmaintenanceA Kubernetes diagnostic agent that provides on-demand root cause analysis and human-in-the-loop remediation via Slack, using LLM reasoning with OPA-bounded security controls.1-
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to safely observe and troubleshoot Kubernetes workloads, with limited recovery actions like pod deletion and Helm rollbacks, while preventing dangerous modifications.18Apache 2.0
- AlicenseAqualityBmaintenanceEnables AI coding agents to access live operational context like processes, containers, and git state, while providing a tamper-evident audit log and policy-as-code compliance hooks locally.22301 npmBusiness Source 1.1