Skip to content

lookout stab drift

Find spec fields of Deployments/StatefulSets/DaemonSets owned by a manager other than the GitOps controller (managedFields) — out-of-band kubectl edits and rogue co-managers. Reports manager strings (tool names, not people); —identity additionally resolves each drift write to the audited principal via the cloud provider’s audit trail (GKE Cloud Audit Logs), reporting an explicit unavailable on clusters without one. Default scope: all namespaces; scanned counts workload objects examined.

MCP tool: k8s_gitops_drift

Terminal window
lookout stab drift [flags]
FlagTypeDefaultMeaning
--managerstring—the declared GitOps manager (e.g. argocd-controller); empty auto-detects it as the manager owning a strict majority (>50%) of the spec leaf fields summed across the scanned objects AND recognized as a GitOps controller (Argo CD, Flux, Helm, Config Sync, Fleet, kapp, Terraform, Pulumi). No manager clears both bars — the usual shape of a cluster with no GitOps controller at all — and the scan resolves to detection=none and emits nothing rather than measuring drift against a guess; the summary then names the leading candidate (ties to the lexicographically smallest) and its share, to pass back here if it is in fact the GitOps controller. A declared manager skips both bars: the operator knows their cluster
--identitybool—resolve each finding’s last drift write to the audited principal (who ran it) via the cloud provider’s audit trail; requires a provider with the audit capability (GKE: Cloud Audit Logs admin-activity read), otherwise the summary line reports an explicit unavailable
FlagTypeDefaultMeaning
--namespacestring—limit the scan to one namespace
-Abool—scan all namespaces
--workloadstring—target one workload as <Kind>/<namespace>/<name>, e.g. Deployment/prod/api
--sinceduration—how far back to look (0 = command default)
--formatstringlogfmtoutput format: logfmt|json (one record per line either way)
--timeoutduration10sabort the invocation after this long (exit 1)
--kubeconfigstring—path to a kubeconfig file, instead of $KUBECONFIG / ~/.kube/config
--contextstring—kubeconfig context to read, instead of its current-context. Selects a cluster for THIS invocation only — nothing is written back — so concurrent invocations can target different clusters. Reported as context=<name> in the summary line
--exemptionsstring—path to a git-reviewed exemption file (YAML); covered findings are ANNOTATED with their reason and expiry and counted as exempt=<n> in the summary, never dropped

Every kind= this command can emit, and the severities it carries them at. Nothing else appears in its output; a kind absent from a run means the check looked and found nothing. See the finding-kind glossary for the whole vocabulary.

KindSeverityClaim
drift.manual_editcritical, warninga manager other than the GitOps controller owns spec fields on this object; critical when one of them is high blast radius (image, replicas, env)

Beyond the shared envelope fields (kind, severity, namespace, kind_of_object, name, reason, message, fingerprint, exempt_reason, exempt_expires):

FieldMeaning
manageron findings: the foreign manager string from managedFields (a tool name like kubectl-edit — never a user identity; see —identity); on the summary line: the resolved GitOps manager
detectionsummary note: how the GitOps manager was resolved — declared (—manager), majority (auto-detected recognized GitOps controller owning >50% of the spec leaf fields in scope), or none (no manager resolved; nothing emitted)
detection_reasonsummary note on detection=none, naming why: no-spec-fields-in-scope (nothing in scope owns a spec field), no-majority-manager (a leading candidate exists but owns 50% or less), or not-a-gitops-manager (the majority owner is not a recognized GitOps controller — e.g. kubeadm or a kubectl manager on a cluster with no GitOps at all)
candidatesummary note on detection=none: the leading manager that fell short (of the majority, or of being a recognized GitOps controller) — pass it to —manager if it is in fact the GitOps controller
sharesummary note: the resolved manager’s (or, on detection=none, the candidate’s) percentage of every spec leaf field owned across the scanned objects, rounded. A declared manager with a low share means most findings are other legitimate owners
unmanagedsummary note, omitted at zero: scanned objects the resolved GitOps manager owns no spec field on. Nothing is reported for them — an object the manager never applied cannot have drifted from it — so a high count next to zero findings means the manager’s scope is narrower than the scan’s
operationmanagedFields operation of the foreign manager’s last write: Apply or Update
toolclient tool recognized from the manager string (kubectl for kubectl-edit/kubectl-patch/kubectl-*)
fieldscompact spec paths the foreign manager owns (e.g. spec.template.spec.containers[app].image), capped at 8 with a +N more tail
field_counttotal spec leaf fields the foreign manager owns on this object (uncapped)
agehow long ago the foreign manager last wrote (managedFields time); omitted when the API server recorded no time
principal—identity: the audited principal of the write nearest the drift time (GKE: principalEmail), or the explicit sentinel none-in-audit-window / no-write-time-anchor when the trail cannot answer
principal_agent—identity: the caller-supplied client string of that write (a kubectl or controller user-agent), when the trail records one; caller-controlled text, display-only
other_principals—identity: other distinct principals that wrote the object inside the audit window, capped at 8 with a +N more tail
identitysummary note when —identity could not be served: the unavailable marker naming why (no provider / audit capability absent)

Output: one finding per line (logfmt; —format=json for one JSON object per line), keys in fixed order; healthy resources emit nothing. The final line is always the summary: scanned= findings= elapsed= — findings=0 with a summary present means “scanned and healthy”; a stream without a summary line is void. Exit 0 data, 1 runtime error (diagnostics on stderr only), 2 usage.

Terminal window
lookout stab drift
lookout stab drift --namespace=prod --manager=argocd-controller
lookout stab drift --workload=Deployment/prod/api --identity
lookout stab drift --workload=Deployment/prod/api --format=json