Home / Stories / Repository scanner
Story · Hygiene Live

Every manifest read, and only the lines that matter changed.

A GitOps repository (a repository that holds your infrastructure as files) collects small debts. Examples are a container with no resource requests, a pod that runs as root, a missing label, and an image pull policy that nobody set. None of them is urgent, but all of them add up. The scanner reads every YAML file and fixes what a rule can fix. It asks an AI model (LLM) only about what needs judgement. Then it gives you small draft merge requests (MRs) that touch nothing else.

Starts fromEvery Kubernetes and Flux YAML file in your configured repositories.
Looks atThe manifest itself, rule-based linters (kube-score, in-house structural rules, Trivy), recent usage metrics for the namespace, and the owner of the live object.
You getDraft MRs with minimal diffs that keep comments, one MR per concern. The assistant bundles several findings into one MR when they can safely merge together.
01 · Walk the repository

Every YAML file, but only the ones that changed.

Every six hours the scanner lists the YAML files in each configured repository. It skips a file whose content hash has not changed since the last pass, so a quiet repository costs almost nothing.

By default the scanner excludes system namespaces (kube-system, flux-system, cert-manager) and kinds such as CRDs (custom resource definitions). It skips very large files instead of half-reading them.

02 · Rules before models

Rule-based linters go first.

The scanner runs each manifest through three tools. The first is kube-score. The second is an in-house structural rule pack (Flux API versions, HelmRelease remediation settings, HPA target bands). The third is the Trivy misconfiguration checks.

When a rule can decide a finding, the assistant never leaves it to an AI model to rediscover. It also recognises findings that it already proposed and that are still open, and does not raise them again.

03 · Patch what a rule can patch

Known fixes are applied by code, not by prompt.

A fixed set of rules has a patcher. These are image pull policy, read-only root filesystem, running as a non-root user, ephemeral-storage requests, rollout strategy, replicas, and new PodDisruptionBudgets or NetworkPolicies. The patcher makes the patch in memory. The patch becomes its own proposal.

A patcher can also refuse. Suppose a container writes to an unbounded local cache. If the patcher picks an ephemeral-storage cap, Kubernetes could evict the pod. So the scanner asks you for the size instead of guessing.

04 · Judgement where it is needed

The AI model sees the patched manifest, and real usage.

Only then does an AI model read the manifest. It sees the file after the rule-based patches, so it cannot re-propose them. It also sees the linter findings that it should skip. For workloads, it sees recent CPU and memory usage from your metrics backend.

Each suggestion must carry a category, a confidence and evidence. The assistant drops low-confidence suggestions.

05 · A minimal patch

The AI model says what to change. Code makes the change.

The AI model does not rewrite the file. It returns a short list of structured edits. Each edit has an operation, a YAML path and a value. A round-trip YAML editor applies them and touches only that path.

Comments, key order, block scalars and every other document in a multi-document file stay byte-for-byte as they were. The assistant rejects an edit that names a file outside the scan. There is no cosmetic churn.

06 · Safety checks on the diff

Suspicious diffs are held, not shipped.

The assistant checks every diff before it can become an MR. It escalates a diff that removes more than half of a file to high risk. It holds that diff as NEEDS_REVIEW. It also flags a diff that looks like a truncated file.

Sometimes a Helm chart (a packaged application) renders the live object behind the file. Editing the rendered output would fight the chart forever, so the assistant escalates the proposal. It points to the HelmRelease values as the right place for the fix.

07 · Bundle what belongs together

One MR per concern, not one per line.

The assistant merges two additive findings on the same file into one diff. The same additive change across many files (standard labels, say) becomes one MR that lists every file, instead of twenty.

The assistant never bundles functional changes, such as upgrades or API-version migrations, across environments. So you can still merge dev, staging and production one at a time. Rule-based patches and AI model suggestions stay in separate MRs.

08 · The proposal

A small, additive diff with the evidence attached.

For orders-api that means one draft MR. It sets resource requests sized from observed usage. It also adds a hardened security context. Both changes go into one file's diff. The full MR is below.

repo scan · acme-deploy · 214 YAML files
  • apps/shop/checkout-web/*.yaml hash unchanged · skipped
  • apps/payments/*.yaml hash unchanged · skipped
  • infrastructure/cert-manager/*.yaml excluded namespace
  • crds/orders.acme.example.yaml excluded kind · CustomResourceDefinition
  • apps/shop/orders-api/deployment_orders-api.yaml changed · analyse
  • apps/shop/cart/deployment_cart.yaml changed · analyse
2 of 214 files changed since the last pass
deterministic pre-pass · deployment_orders-api.yaml
  • kube-score · container-image-pull-policy patcher available
  • kube-score · container-ephemeral-storage-request-and-limit patcher available
  • kube-score · container-resources needs usage data · to the model
  • trivy · runs as root, writable root filesystem context for the model
  • structural rules Flux API versions current · no findings
  • kube-score · pod-probes already open as a proposal · not raised again
patcher · container-image-pull-policyin memory
      containers:
        - name: orders-api
          image: registry.acme.example/shop/orders-api:4.12.0
+         imagePullPolicy: IfNotPresent
patcher · ephemeral-storage · cart
refused: container cart mounts an unbounded emptyDir cache
  a fixed cap could get the pod evicted; recurring refusals
  become an issue asking you for the real size
Patched manifestrule fixes applied
Usage7d CPU / memory
Modeljudgement only
suggestions · shop/orders-api
  • resources · set requests conf 0.86 · p95 CPU 180m, peak memory 410Mi, no requests set
  • security · harden securityContext conf 0.82 · no runAsNonRoot, root FS writable
  • "follow best practices for probes" conf 0.55 · below threshold · dropped
structured edits returned by the model
[ { op: set, path: spec.template.spec.containers[orders-api].resources.requests,
    value: { cpu: 200m, memory: 512Mi } } ]
applied by the round-trip editor
        - name: orders-api
          # pinned by the release job, do not edit by hand
          image: registry.acme.example/shop/orders-api:4.12.0
+         resources:
+           requests:
+             cpu: 200m
+             memory: 512Mi
          env: … untouched, comments kept …
4 lines added · 0 lines reformatted · other documents spliced back verbatim
diff checks before emission
  • orders-api · +11 −0 additive · PENDING
  • billing/configmap_rules.yaml · −212 of 260 lines over 50% removed · risk high · NEEDS_REVIEW
  • search/statefulset.yaml · deletes to end of file looks truncated · flagged
  • monitoring/deployment_exporter.yaml live object is Helm-rendered · edit the HelmRelease values instead · escalated
held proposals wait for a human; nothing risky is opened quietly
bundling · acme-deploy
  • same file · orders-api requests + securityContext · both additive → 1 MR
  • cross file · "Add standard Kubernetes labels" 14 files → 1 MR
  • cross file · Flux apiVersion migration split by env: dev · staging · production → 3 MRs
  • kube-score · image pull policy × 6 files rule lane · 1 MR, separate from model suggestions
31 findingsemitted
6 MRsto review
Draft · Set resource requests and harden securityContext for shop/orders-api
+         resources:
+           requests: { cpu: 200m, memory: 512Mi }
+         securityContext:
+           runAsNonRoot: true
+           allowPrivilegeEscalation: false
+           readOnlyRootFilesystem: true
category:resourcessource:repo_scan:acme-deployrisk:lowxrev::LGTM
↓ the full merge request is below

The merge request you get.

Illustrative. Two findings on the same file are in one draft MR, because both only add lines.

Draft

Set resource requests and harden securityContext for shop/orders-api

from gitops-ai/set-resource-requests-and-harden-securitycontext-5d2e91ab into main · 1 file · +11
category:resourcessource:repo_scan:acme-deployrisk:lowxrev::LGTM
OverviewEvidenceChangesValidation
Summary

This bundle combines two findings on apps/shop/orders-api/deployment_orders-api.yaml. Both only add lines, so they merge without conflict.

FindingCategoryChange
No resource requests on orders-apiresourcesRequests set from observed usage
Container may run as root with a writable root filesystemsecuritysecurityContext added
Why it matters

Without requests the scheduler packs this pod as if it needed nothing. So the pod is first to be starved on a busy node. Without a security context a compromised process can write to its own image and run as UID 0.

Observed usage, last 7 days
Metricp95PeakProposed request
CPU180m310m200m
Memory395Mi410Mi512Mi
From the manifest

The container has no resources block and no securityContext. The image declares a non-root user, so runAsNonRoot: true does not change who the process runs as. Writes go to the mounted /tmp volume, not the root filesystem.

The assistant proposes no limits. Limits on a latency-sensitive service are a decision for its owners.

       containers:
         - name: orders-api
           # pinned by the release job, do not edit by hand
           image: registry.acme.example/shop/orders-api:4.12.0
+          resources:
+            requests:
+              cpu: 200m
+              memory: 512Mi
+          securityContext:
+            runAsNonRoot: true
+            allowPrivilegeEscalation: false
+            readOnlyRootFilesystem: true
+            capabilities:
+              drop: ["ALL"]
+            seccompProfile: { type: RuntimeDefault }
           env:
             - name: LOG_LEVEL
Checked before the assistant opened this MR
  • The diff is purely additive. It adds 11 lines and removes 0. Comments and key order stay the same.
  • The live Deployment is not rendered by a Helm chart, so the file in git is the right place for the edit.
  • No other open proposal touches this file.
After merge, expect
  • One rolling restart of orders-api.
  • Pods start and pass readiness. If the process tries to write outside /tmp, it fails at start, so watch the first rollout.
  • The scheduler now reserves 200m CPU / 512Mi per replica.
Rollback

Revert this MR.

R
independent reviewer second opinion, no pipeline context

I checked that the image runs as a non-root user and only writes to the mounted volume. Requests sit just above p95. The diff scope matches the title. LGTM.

What keeps it honest.

The AI model proposes. Code applies the change and decides whether it may leave.

Rules before modelsCode patches what kube-score or a structural rule can decide. The AI model never sees those findings as open work.
Surgical edits onlyA round-trip YAML editor applies AI model suggestions as path-level edits. Comments, order and untouched documents stay as they were.
Large removals heldThe assistant escalates a diff that removes more than half a file, or looks truncated. It holds the diff for human review instead of opening an MR.
Chart owns chart outputThe assistant does not edit a file directly if Helm renders its live object. It points the fix at the HelmRelease values.
Refusing is allowedSometimes nobody can know a safe value, such as a disk cap over an unbounded cache. Then the scanner asks you instead of guessing.
No duplicate noiseThe assistant does not raise open or recently dismissed proposals again. You can opt a manifest out with a skip annotation.

Where this stands.

Live
  • Change-aware scanning of every YAML file across configured repositories
  • kube-score and structural-rule patchers, Trivy findings as model context
  • Structured, comment-preserving edits for model suggestions
  • Diff safety, truncation and chart-ownership escalation
  • Same-file and cross-file bundling with per-environment split
In development
  • Deterministic checks that drop model suggestions whose evidence quotes nothing from the manifest
  • Measuring the chart-ownership check across more repository layouts
Roadmap
  • Trivy rules graduating to code patchers, rule by rule, as their merge rate proves them
  • Storage-class and setting drift between sibling environments
← Previous storySecurity posture