Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Detect unauthorized modifications to running containers by monitoring for binary execution drift, file system changes, and configuration deviations from the original container image.
.claude/skills/detecting-container-drift-at-runtime/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-15 | ✗→✓ | ▲ Improved | — | — |
| case-08 | ✗→✓ | ▲ Improved | — | — |
Container drift occurs when running containers deviate from their original image state through unauthorized file modifications, unexpected binary execution, configuration changes, or package installations. Since containers should be treated as immutable infrastructure, any drift is a potential indicator of compromise. Detection techniques leverage the DIE (Detect, Isolate, Evict) model -- an immutable workload should not change during runtime, so any observed change is potentially evidence of malicious activity.
Image-Based Comparison: Compare the running container's filesystem against its source image to identify added, modified, or removed files.
Behavioral Monitoring: Use eBPF or kernel-level monitoring to detect process execution, file access, and network activity that deviates from expected behavior.
Digest Verification: Continuously verify that running container image digests match the approved deployment manifests.
yaml- rule: Drift Detected (Container Image Modified Binary) desc: Detect execution of a binary not present in the original container image condition: > spawned_process and container and not proc.pname in (container_entrypoint) and proc.is_exe_upper_layer = true output: > Drift detected: new binary executed in container (user=%user.name command=%proc.cmdline container=%container.name image=%container.image.repository:%container.image.tag exe_path=%proc.exepath) priority: WARNING tags: [container, drift] - rule: Container Shell Spawned desc: Detect interactive shell in a container that should be immutable condition: > spawned_process and container and proc.name in (bash, sh, dash, zsh, csh, ksh) and not proc.pname in (container_entrypoint) output: > Shell spawned in container (user=%user.name shell=%proc.name container=%container.name image=%container.image.repository) priority: WARNING tags: [container, drift, shell]
yaml- rule: Package Manager Execution in Container desc: Detect use of package managers indicating drift condition: > spawned_process and container and proc.name in (apt, apt-get, yum, dnf, apk, pip, pip3, npm, gem, cargo) output: > Package manager executed in container (user=%user.name command=%proc.cmdline container=%container.name image=%container.image.repository) priority: ERROR tags: [container, drift, package-manager]
yaml- rule: Container File System Write desc: Detect writes to container upper layer filesystem condition: > open_write and container and fd.typechar = 'f' and not fd.name startswith /tmp and not fd.name startswith /var/log and not fd.name startswith /proc output: > File write in container (user=%user.name file=%fd.name container=%container.name) priority: NOTICE tags: [container, drift, filesystem]
Prevent drift by making container filesystems immutable:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: immutable-app spec: template: spec: containers: - name: app image: app:v1.0@sha256:abc123... securityContext: readOnlyRootFilesystem: true allowPrivilegeEscalation: false runAsNonRoot: true volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache volumes: - name: tmp emptyDir: sizeLimit: 100Mi - name: cache emptyDir: sizeLimit: 50Mi
yamlapiVersion: v1 kind: Namespace metadata: name: production labels: pod-security.kubernetes.io/enforce: restricted pod-security.kubernetes.io/audit: restricted pod-security.kubernetes.io/warn: restricted
bash#!/bin/bash # Compare running container digests against approved manifest NAMESPACE="production" kubectl get pods -n "$NAMESPACE" -o json | jq -r ' .items[] | .spec.containers[] | "\(.image) \(.imageID)" ' | while read IMAGE IMAGE_ID; do APPROVED_DIGEST=$(kubectl get deploy -n "$NAMESPACE" -o json | \ jq -r ".items[].spec.template.spec.containers[] | select(.image==\"$IMAGE\") | .image") if [[ "$IMAGE" != *"@sha256:"* ]]; then echo "[WARN] Container using mutable tag: $IMAGE" fi done
For Azure Kubernetes environments, Microsoft Defender provides built-in binary drift detection:
json{ "alertType": "K8S.NODE_ImageBinaryDrift", "severity": "Medium", "description": "Binary executed that was not part of the original container image", "remediationSteps": [ "Investigate the binary origin and purpose", "Check if the container was compromised", "Rebuild the container from a clean image", "Enable readOnlyRootFilesystem" ] }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.