GitHub App · Python · FastAPI

Merge with
confidence.

kubeguard is a Helm-aware Kubernetes PR risk analyzer. It renders your full chart at the PR's head SHA, validates it against live cluster and AWS context, scores the findings, and posts the result before you click merge.

$ kubeguard-analyze analyze -c ./chart -e prod
Python 3.10+ FastAPI GitHub App Helm 3 github.com/gagan1510/kubeguard ↗
kubeguard · webhook receiver
PR #142 · feat: bump tag-payments-api
Deployments/staging/platform/ · +1 −1 · head: a3f9c2d
✓ cloned repo at a3f9c2d
✓ resolved values: app · env · tag
✓ rendered helm chart
✓ checked service accounts
✓ validated alb certificate ARN
✓ verified security group → VPC
✓ checked secret references
✓ Score: 80 / 100 · LOW
⚪ MEDIUMContainer has no resource limits
⚪ MEDIUMNo PodDisruptionBudget found
⚪ LOWNo podAntiAffinity set

GitHub PRs show diffs.
Helm rendering needs everything.

A one-line change to a tag-*.yaml file is never really one line. It's a container image bump that depends on the base chart, three values files, cluster service accounts, AWS security groups, ALB certificates, and secrets. None of this appears in the diff.

Δ
The diff problem
Helm renders from the entire values context, not diffs. A PR that touches only tag-payments-api.yaml still depends on app.yaml, env-app.yaml, the base chart, and the full cluster state.
⊛
Multi-file values
Real deployments use layered values: base app config, per-environment overrides, tag/image pins, often split across two or three files per service. The final manifest only exists after Helm merges them all.
⊘
Isolated validators
kubeval, kube-score, conftest, and OPA validate manifests in isolation. They don't know which cluster you're deploying to, whether that security group belongs to the correct VPC, or whether referenced secrets exist in AWS.
◎
Kyverno and kubeguard are layers
Kyverno can enforce policy at admission and its CLI can run earlier in CI. kubeguard adds repository-specific Helm assembly plus live cluster and AWS checks on the PR. I use the two together, not as alternatives.

What kubeguard
looks for

Every rule produces a severity-weighted finding. Scores start at 100 and deductions lower them — 100 is a clean chart, 0 means everything is wrong. Rules are environment-aware: nonprod findings are automatically downgraded one level.

⚖
Resource Safety
  • CPU and memory requests set
  • Resource limits defined
  • No unbounded resource claims
  • Ephemeral storage limits
♻
Availability
  • Multiple replicas configured
  • PodDisruptionBudget present
  • Readiness and liveness probes
  • Rolling update strategy
⊞
Scheduling
  • podAntiAffinity rules
  • Valid node selectors
  • Toleration correctness
  • Priority class defined
⊕
Security
  • Not running as privileged
  • securityContext set
  • Service account exists in cluster
  • No hostNetwork/hostPID
⊗
Networking
  • ALB certificate ARN valid
  • Security group in correct VPC
  • Ingress has TLS
  • Target group annotations match
⎈
Helm-specific
  • All values files resolved
  • Secret references exist in AWS
  • Render succeeds at head SHA
  • cross-repo chart resolution

Context over syntax. A manifest can be perfectly valid YAML and still fail at runtime: wrong security group, missing secret, invalid certificate ARN. kubeguard asks not "is this correct?" but "is this safe for this environment?"

From PR open
to Check Run

kubeguard runs as a long-lived GitHub App server. When a PR is opened, GitHub fires a webhook; kubeguard handles it entirely outside the PR's Actions pipeline. No runner minutes, no queue wait.

01
PR opened
GitHub fires a pull_request webhook to the kubeguard server, in parallel to any CI jobs, not inside them.
02
Clone at head SHA
kubeguard checks out the full repository at the PR's exact head.sha. Diffs are ignored. Only the full state matters.
03
Resolve values
All values files are located and merged in order: app.yaml → env-app.yaml → tag-app.yaml. Split-repo charts are cloned separately via central_chart resolver.
04
Render Helm
Full chart is rendered with the merged values. Render failure is itself a CRITICAL finding: it means the PR cannot deploy.
05
Validate context
20+ rules run against the rendered manifests, live cluster state, and AWS resources. Findings are scored and severity-adjusted for the target environment.
06
Post Check Run
Results are posted directly via the GitHub Checks API. Score, severity breakdown, and per-finding details appear on the PR before any human reviews it.

Same finding,
different weight

In nonprod, every finding is downgraded one level and the pass threshold is relaxed. The same misconfiguration that blocks a prod merge is a warning on staging, letting teams iterate quickly without becoming careless.

Production
Strict
Fail if any finding isCRITICAL
Score threshold< 30 / 100 fails
Severity downgradenone
Auto-mergerequires human approval
CRITICAL HIGH MEDIUM LOW
Non-production
Relaxed
Fail if any finding isCRITICAL
Score threshold< 15 / 100 fails
Severity downgrade−1 level each
Auto-mergeon pass, within seconds
CRITICAL→HIGH HIGH→MEDIUM MEDIUM→LOW
kubeguard-analyze analyze -c ./chart -v values.yaml -e nonprod
## Kubernetes Helm Risk Report (local) Chart: /path/to/charts/my-app Environment: nonprod Score: 20/100 (LOW) Passed checks ✅ CPU request set ✅ Memory request set ✅ Multiple replicas ✅ Readiness probe set ✅ Not privileged ✅ Ingress has TLS ... (15 more) 🟡 MEDIUM: - [Deployment/my-app] Container has no resource limits - [Deployment/my-app] No PodDisruptionBudget found - [Deployment/my-app] No securityContext set 🔵 LOW: - [Deployment/my-app] No podAntiAffinity (pods may schedule on same node) Recommendation: Review findings and improve where needed.

A score is a policy,
not a probability.

A score of 20 does not mean a deployment has a 20% chance of failing. It means the rendered result hit rules whose configured weights add up to 20.

±
Weights need calibration
The defaults are a starting point. I tune them against incidents, accepted exceptions, and false positives. A missing PDB is not equally risky for every workload.
≠
Passing is not proof
kubeguard cannot prove application correctness, capacity under load, or whether an external dependency will behave.
⌁
Live checks can drift
AWS and cluster state can change after analysis. The Check Run is a useful snapshot at the PR head SHA, not a permanent guarantee.
⌕
Exceptions stay visible
A suppressed finding needs an owner, reason, and expiry. Otherwise a risk analyzer slowly becomes a green badge generator.

If kubeguard already knows
why is a human clicking merge?

Low-risk tag bumps and config patches on nonprod merge themselves within seconds of the PR opening. The infra team only gets pulled in when something actually needs review.

The workflow itself needs care. GitHub only triggers check_run workflows when the file exists on the default branch. The merge job should verify the repository, App identity, exact head SHA, base branch, and a single matching PR before it gets write permission. I would not auto-merge forked PRs through this path.

The flow

  1. PR is opened against the deployment branch.
  2. GitHub fires the pull_request webhook to kubeguard, in parallel with CI.
  3. kubeguard renders, validates, scores, and posts a Check Run.
  4. If the score passes with no CRITICAL findings, the Check Run succeeds.
  5. A GitHub Actions workflow waits for the kubeguard check_run event.
  6. On pass, it enables auto-merge. Production still requires human approval.
github actions · auto-merge workflow
on: check_run: types: [completed] jobs: auto-merge: if: > github.event.check_run.name == 'kubeguard' && github.event.check_run.conclusion == 'success' runs-on: ubuntu-latest steps: - name: Auto-merge PR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR=$(gh pr list \ --head "${{ github.event.check_run.head_sha }}" \ --json number --jq '.[0].number') gh pr merge "$PR" --squash --auto

Two ways
to run it

Run kubeguard locally as a CLI for one-off analysis, or deploy it as a long-lived GitHub App server that handles PR webhooks automatically.

CLI

Local analysis

Point it at a chart directory and a values file. Good for spot-checks during development.

# install pip install kubeguard # analyze a chart kubeguard-analyze analyze \ -c ./chart \ -v values.yaml \ -e nonprod # split-repo setup kubeguard-analyze analyze \ -c ./chart \ -v values/ \ --central-chart org/charts \ -e prod
App

GitHub App server

Deploy as a webhook receiver. Every PR against your deployment branches gets a Check Run automatically.

# required env vars GITHUB_APP_ID=12345 GITHUB_PRIVATE_KEY_PATH=./key.pem GITHUB_WEBHOOK_SECRET=... AWS_REGION=ap-south-1 # start the server uvicorn kubeguard.app:app \ --host 0.0.0.0 --port 8000
Cfg

kubeguard.yaml

Drop a config file in the repo root to tell kubeguard where to find values files and which environment it maps to.

environments: staging: values_pattern: - "values/{app}.yaml" - "values/env-{app}.yaml" - "values/tag-{app}.yaml" thresholds: score: 85 allow_critical: false production: values_pattern: - "values/{app}.yaml" - "values/prod-{app}.yaml" - "values/tag-{app}.yaml" thresholds: score: 70 allow_critical: false
AWS

Permissions needed

kubeguard needs read-only access to the AWS resources it validates against.

# minimum IAM actions ec2:DescribeSecurityGroups ec2:DescribeVpcs acm:DescribeCertificate acm:ListCertificates secretsmanager:ListSecrets secretsmanager:DescribeSecret eks:DescribeCluster