The 2026 InfoSec Playbook · Scenario playbooks

#14.10 Kubernetes and Container Compromise

Playbook ID: PB-K8S | Default severity: SEV-2 (escalate to SEV-1 if a container escape to the node is confirmed, the cluster Secret store was read, node or cloud credentials were used outside the cluster, or the compromised image is running in more than one cluster) | Owner: Operations Lead (Platform)

#When to run this

Not for: the compromise of the CI/CD system, registry or upstream package itself — that is Playbook 14.5, which owns the vendor-side and build-chain work; run it in parallel and come back here for the cluster-side eviction. Human user or SaaS account takeover is 14.3; identity provider compromise is 14.4. Encryption of cluster storage with a ransom demand escalates to 14.1. Compromise of a model-serving or agent workload's behavior rather than its container is 14.11. The preventative controls — CSPM/CIEM, admission policy, IMDS hardening, cluster hardening baselines — belong to Chapter 6; this playbook assumes they were insufficient.

#What you are dealing with

The defining mistake in this scenario takes one keystroke. A responder sees a bad process in a pod, runs kubectl delete pod, and feels like they contained something. What they actually did was delete the container's writable layer, throw away every byte of process memory, and — because it was a Deployment — hand the scheduler an instruction to start a fresh copy of the same compromised image, possibly on a different node, almost certainly with the same service-account token mounted. The attacker gets a new pod for free and you get nothing. AWS states the rule flatly in its own EKS guidance: gather forensic evidence before removing the node, because an attacker may attempt to destroy evidence through termination (EKS Best Practices — Incident Response and Forensics). Pods are cattle right up until one of them is the crime scene.

What the adversary is after is almost never the container. It is the identity mounted inside it. The May 2026 Sysdig case is the cleanest illustration on record: an exposed Docker socket let the actor start a privileged container with the host filesystem bind-mounted at /host, read host credentials including /etc/shadow and SSH keys, and then replay the pod's own projected service-account token against the API server to dump the entire cluster Secret store — database credentials, AWS keys, third-party API keys. That chain made no IMDS call at all; the mounted token was sufficient (Sysdig). The lesson for your triage order is unambiguous: what could this token reach comes before what did they run.

It moves at control-plane speed, which is to say instantly. Cloud-conscious intrusions rose 37% overall and 266% among state-nexus actors, and 35% of cloud incidents involved valid account abuse (CrowdStrike 2026 GTR). The escape half is not theoretical either — three critical runC vulnerabilities disclosed in November 2025 affect Docker, Kubernetes, containerd and CRI-O, and CVE-2025-23266 in the NVIDIA Container Toolkit carries CVSS 9.0 (Wiz). And when a poisoned build reaches you, it arrives already knowing how to move: the March 2026 LiteLLM compromise shipped a payload that harvested credentials, moved laterally across Kubernetes clusters, and dropped a persistent systemd backdoor (Resecurity).

One more thing, because teams get it wrong every time. If what you found is a cryptominer, you have not found a nuisance. Cryptomining is the most common payload in compromised container environments, but the durable pattern established by Sysdig's SCARLETEEL research and repeated since is that the mining foothold and the credential-theft path are the same access (Dark Reading). The miner is the part they did not bother to hide. Treat it as proof of control-plane access, not as commodity noise.

#Roles for this incident

RoleResponsibility in PB-K8S
Incident CommanderOwns the observe-vs-contain call, authorises node drain and any action causing customer-facing outage.
Operations Lead (Platform)Cluster-side work: workload identification, live triage, quarantine policy, cordon and drain, token and RBAC revocation.
Operations Lead (Cloud)The cloud-credential branch: node instance role, IRSA/Workload Identity trust, IMDS posture, control-plane log export, cloud-side session revocation.
Communications LeadService-owner and customer-impact comms; status page if the drain causes degradation.
ScribeUTC/ISO 8601 timeline, chain of custody, artefact register including image digests.
Legal LiaisonLegal hold on snapshots and log exports; notification assessment once secret exposure is scoped.
Executive SponsorApproves cluster-wide image bans, registry lockdown, and rebuilds that take a production service down.

Marking used below: `TIP-OFF = the adversary can observe this action. EVIDENCE` = this destroys or degrades evidence and must not run before capture.

#Phase 1 — Detection and Triage

#ActionWhoDone whenEvidence to capture
1Declare T+0. Set a hard 45-minute triage box; containment fires at expiry whether or not scoping is complete.ICTime box recordedDeclaration time (UTC/ISO 8601), triggering finding ID
2Confirm audit logging is actually on before you rely on it. EKS control-plane audit logging is off by default and must be enabled per log type (EKS control plane logs); GKE Admin Activity is on at Metadata level but Data Access logs are off by default. If it is off, say so in the timeline now — you cannot enable it retroactively.Ops Lead (Cloud)Logging state documented per clusterScreenshot/CLI output of enabled log types, per cluster
3Export control-plane audit logs and cloud audit logs for the window before any containment. GCP Admin Activity is retained 400 days and Data Access 30 days by default (Cloud Logging retention); CloudTrail console Event history is a hard 90 days, management events only.Ops Lead (Cloud)Raw exports in the evidence storeFile hashes, query window, exporter identity, log group / bucket names
4Place legal hold on evidence objects — S3 Object Lock legal hold has no expiration and requires S3 Versioning (S3 Object Lock). Hold first, analyze second.Legal LiaisonHold confirmed on every evidence object versionObject versions held, hold timestamp, case ID
5Identify the workload and its node. Do not delete anything.Ops Lead (Platform)Pod name, namespace and node recordedkubectl output, pod spec YAML, node name
6Record the exact image digest, not the tag. Tags are mutable and an attacker who can push to the registry can move one under you.Ops Lead (Platform)Digest recorded for every container in the podimage and imageID fields from the pod status
7Enumerate blast radius across the cluster: every pod using the same service account, and every pod running the same image, cluster-wide.Ops Lead (Platform)Full pod/node list producedJSON output of both queries, timestamp
8Read the pod spec for the escape primitives: hostNetwork, hostPID, privileged: true, a hostPath mount of / or of the container runtime socket, and automountServiceAccountToken.Ops Lead (Platform)Each field dispositionedFull pod spec, annotated
9Determine what the mounted token is bound to before deciding how to kill it — submit a TokenReview and read authentication.kubernetes.io/pod-name, pod-uid, node-name, node-uid from the status (service accounts admin).Ops Lead (Platform)Binding type known (pod / node / secret / legacy)TokenReview request and status output
10Enumerate what that service account can do: its RoleBindings and ClusterRoleBindings, and specifically whether it can read Secrets, create pods, or bind roles.Ops Lead (Platform)Effective permission set written downRBAC objects, subject list
11Cloud branch: establish whether the credential left the cluster. In CloudTrail, an ASIA short-term key for the node instance role calling from a non-AWS source IP is the classic signature; ec2RoleDelivery with value "1.0" explicitly confirms IMDSv1 was used to obtain it.Ops Lead (Cloud)Cloud-side use confirmed or excludedCloudTrail records, principalId, session names, source IPs
12Query the audit log for what the token actually did at the API server: Secret reads, pods/exec, RBAC writes, pod creations with privileged or hostNetwork.Ops Lead (Platform)Action inventory completeAudit query, matching events with requestURI and verbs
shell
# Which node is the pod on, and which pods share the compromised identity or image.
# Verbatim from the EKS Best Practices Guide (Incident Response and Forensics).
kubectl get pods <name> --namespace <namespace> -o=jsonpath='{.spec.nodeName}{"\n"}'

kubectl get pods -o json --namespace <namespace> \
  | jq -r '.items[] | select(.spec.serviceAccount == "<service account name>") | "\(.metadata.name) \(.spec.nodeName)"'

IMAGE=<malicious image>
kubectl get pods -o json --all-namespaces \
  | jq -r --arg image "$IMAGE" '.items[] | select(.spec.containers[] | .image == $image) | "\(.metadata.name) \(.metadata.namespace) \(.spec.nodeName)"'

#Phase 2 — Containment

Order matters more here than in any other playbook in this chapter. Capture, then cut the network, then cut the identity, then move the node. Reverse any two of those and you lose either the evidence or the adversary.

#ActionWhoDone whenEvidence to capture
1Capture live state without restarting the pod. Attach an ephemeral debug container, or clone the pod, and collect process list, network state and open ports from the running container (debug running pods).Ops Lead (Platform)Live capture stored and hashedProcess list, netstat output, container filesystem diff, capture time
2Capture container-runtime state on the node: docker top, docker logs, docker inspect, docker diff, docker checkpoint — or the crictl equivalents for containerd and CRI-O runtimes.Ops Lead (Platform)Runtime artefacts collectedCommand outputs, container ID, runtime and version
3Capture node memory before anything touches the node — RFC 3227 order of volatility puts memory above disk, and disk above remote logging (RFC 3227). Use LiME or an equivalent acquisition tool; AWS also names its Automated Forensics Orchestrator for Amazon EC2.Ops Lead (Cloud)Memory image acquired and hashedImage hash, tool and version, acquiring operator, UTC time
4Snapshot the node's volumes. Snapshots are Region-scoped; if the snapshot is encrypted you must also share the customer-managed KMS key to use it in a forensics account, and the forensic role should get read-only access (forensic environment strategies).Ops Lead (Cloud)Snapshot complete and copied to the forensics accountSnapshot IDs, KMS key ARN, destination account, custody record
5Apply a deny-all NetworkPolicy to the labeled pod. Verify the CNI enforces it — NetworkPolicy is enforced by the CNI, and a cluster without a policy-enforcing CNI (VPC CNI network policy, Calico or Cilium) will accept the object and enforce nothing. `TIP-OFF`Ops Lead (Platform)Policy applied and enforcement proven by a failed egress testPolicy YAML, the test that proved enforcement, timestamp
6Cut egress at the cloud network layer as well. Google's own caveat applies to every provider: adding firewall rules does not close existing connections (GKE security mitigations). For established C2 you need a stateless control — NACLs on AWS — not a security-group or firewall-rule change alone. `TIP-OFF`Ops Lead (Cloud)Egress blocked and existing sessions confirmed deadRule definitions, flow-log evidence of the connection dropping
7Cordon the node so nothing new schedules onto it. kubectl cordon marks the node unschedulable and does not evict anything — this is the safe first move.Ops Lead (Platform)Node shows SchedulingDisabledCommand transcript, node status before and after
8Revoke the workload identity, matched to how the token is bound. Pod-bound token → delete the pod. Node-bound token → delete the node. Legacy long-lived Secret → delete the Secret. All tokens for the account → delete the ServiceAccount. Authentication fails immediately once the bound object is gone; for objects pending deletion with finalizers, tokens fail 60 seconds after deletionTimestamp. `EVIDENCE TIP-OFF`Ops Lead (Platform)Token confirmed rejected by the API serverDeleted object names and UIDs, first rejected-auth event
9Strip RBAC in the same burst. Deleting the ServiceAccount without removing its RoleBindings and ClusterRoleBindings leaves the grant in place for any recreated account of the same name.Ops Lead (Platform)Bindings removed and re-inventory is cleanBinding YAML before deletion, deletion record
10Cloud branch: detach the IAM role from the compromised worker node and remove IAM policies from pod-assigned roles, as EKS guidance recommends. For an assumed role, revoke sessions and change permissions — AWS is explicit that revocation alone is insufficient (revoke role sessions).Ops Lead (Cloud)Role sessions revoked and permissions deniedAWSRevokeOlderSessions policy JSON with aws:TokenIssueTime, IAM change records
11Close the metadata path on the node while it is still up: require IMDSv2 and drop the hop limit to 1, which blocks container-to-IMDS in most topologies. Note AWS's own caveat that a hop limit of 1 can break legitimate container workloads.Ops Lead (Cloud)IMDSv2 required, hop limit setmodify-instance-metadata-options output, before/after settings
12Only now drain the node — and drain it using the GKE quarantine pattern, which keeps the compromised pod pinned in place while everything else moves off. Be ready for a PodDisruptionBudget to block the drain. `TIP-OFF`Ops Lead (Platform) + ICHealthy workloads rescheduled, quarantined pod still residentDrain transcript, PDBs encountered, rescheduling record
13Block the compromised image at admission, by digest, across every cluster — not just this one. `TIP-OFF`Ops Lead (Platform)Admission policy live in all clustersPolicy definition, digest list, per-cluster confirmation
YAML
# Deny-all quarantine for a labelled pod. Enforced by the CNI, not by the API server:
# on a cluster without a policy-enforcing CNI this object is accepted and does nothing.
# Test egress from the pod after applying it. Do not assume.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
  - Ingress
  - Egress
shell
# Non-destructive live triage. Neither of these restarts the target pod.
# Ephemeral debug container attached to the running pod:
kubectl debug -it POD_NAME --image=busybox --target=CONTAINER_NAME
# Copy of the pod to work on, original left running:
kubectl debug POD_NAME --copy-to=POD_NAME-debug --image=DEBUG_IMAGE
shell
# Node quarantine, GKE's documented pattern: pin the compromised pod, move the rest.
kubectl cordon NODE_NAME
kubectl label pods POD_NAME quarantine=true
kubectl drain NODE_NAME --pod-selector='!quarantine'

# VPC-level egress cut. Reminder: this does not close connections already established.
gcloud compute instances add-tags NODE_NAME --zone COMPUTE_ZONE --tags quarantine
gcloud compute firewall-rules create quarantine-egress-deny \
  --network NETWORK_NAME --action deny --direction egress \
  --rules tcp --destination-ranges 0.0.0.0/0 --priority 0 --target-tags quarantine
shell
# Require IMDSv2 and block container-to-IMDS via the hop limit.
# AWS documents that a hop limit of 1 "can cause issues" in container environments.
aws ec2 modify-instance-metadata-options \
    --instance-id i-1234567890abcdef0 \
    --http-tokens required \
    --http-put-response-hop-limit 1 \
    --http-endpoint enabled
shell
# Pod deletion is a Phase 2 step 8 action, not a Phase 1 reflex.
# Run it only after steps 1-4 have captured memory, runtime state and volumes.
kubectl delete pods POD_NAME --grace-period=10
# If a controller will simply reschedule the payload, delete the workload object instead.

#Phase 3 — Eradication

#ActionWhoDone whenEvidence to capture
1Rotate every Secret in every namespace the compromised token could read. Not the workload's own secrets — every secret in reach of that RBAC grant. Assume read means stolen.Ops Lead (Platform)Rotation register complete and verifiedSecret names (not values), rotation timestamps, consuming workloads
2Rotate the downstream credentials those Secrets contained — database users, cloud access keys, third-party API keys, registry credentials. The Sysdig case ended with the actor holding database, AWS and third-party API keys from a single Secret dump.Ops Lead (Cloud)Every downstream credential replacedOld/new credential IDs, owning system, rotation record
3Determine how the payload got into the image, and fix it at the source. If it arrived through a poisoned package, action or base image, open Playbook 14.5 in parallel and rotate CI publishing tokens and runner secrets there.Ops Lead (Platform)Root cause identified in the build chainBuild logs, dependency diff, digest lineage
4Purge the compromised digest from every registry, mirror, pull-through cache and node image cache. A node that has the layer cached will start the container without touching the registry.Ops Lead (Platform)Digest absent from registries and node cachesRegistry delete records, node cache verification per node
5Hunt cluster persistence: unexpected DaemonSets, CronJobs, mutating or validating admission webhooks, initContainers added to existing Deployments, and new ClusterRoleBindings.Ops Lead (Platform)Every object dispositioned as expected or removedObject inventory with creation timestamps and creating principal
6Hunt node persistence on any node the actor reached: added SSH keys, new systemd units, modified /etc/shadow, cron entries. The LiteLLM payload's third stage was a persistent systemd backdoor polling for further payloads.Ops Lead (Cloud)Node dispositioned as clean or condemnedFindings with file paths, hashes and mtimes
7Hunt cloud persistence with the credentials the actor held: CreateUser, CreateAccessKey, new federated identity credentials, new OIDC audiences, new role trust relationships.Ops Lead (Cloud)All dispositionedEvent records, created principal ARNs
8Fix the escalation path, not just the pod. Pin IRSA and Workload Identity trust conditions to a specific namespace and service account — a sts:AssumeRoleWithWebIdentity condition that does not pin sub lets any pod in the cluster assume that role (EKS instance-role escalation).Ops Lead (Cloud)Every workload role's trust policy pins subjectTrust policy diffs per role
9Replace the node rather than cleaning it. Once a container escape is confirmed, the node is condemned — terminate it and let the node group build a new one from a known image. `EVIDENCE`Ops Lead (Cloud)Old node terminated after snapshots verified restorableTermination record, snapshot restore test result
10Set automountServiceAccountToken: false for every workload that does not call the API server. This is the single highest-value change to come out of this incident and it costs nothing.Ops Lead (Platform)Applied across the namespace, verified in running podsManifest diffs, list of workloads that still mount a token and why

#Phase 4 — Recovery

#ActionWhoDone whenEvidence to capture
1Rebuild the image from a verified-clean source and deploy by digest. Never restore the previous tag.Ops Lead (Platform)Clean build reproduced and signedBuild provenance, new digest, signature
2Deploy to a single canary replica with the deny-all policy relaxed to an explicit allowlist of required destinations, and watch it.Ops Lead (Platform)Canary healthy through the watch windowCanary metrics, egress destinations observed
3Recreate the service account with a minimal RBAC grant derived from the audit log of legitimate activity, not from the old Role.Ops Lead (Platform)New binding applied, workload functionalOld vs. new permission diff
4Restore normal scheduling: kubectl uncordon <node-name> on nodes that were cordoned but not condemned, and only after the drain evidence is complete.Ops Lead (Platform)Cluster capacity restoredUncordon record, node health checks
5Verify containment by observation over a defined window: no new API calls from the revoked identity, no egress from the quarantined workload, no new use of the rotated credentials.Ops LeadWindow elapsed with no hitsQuery results per source, window start and end (UTC)
6Turn the audit logging on properly — Request level on Secrets, ServiceAccounts and RBAC objects — and confirm the events are actually landing in the log destination by generating a benign test event.Ops Lead (Cloud)Test event visible in the destinationAudit policy diff, test event ID and retrieval time
7Hand Legal a written statement of which Secrets were within the token's reach and which are confirmed read, distinguishing the two clearly.Ops Lead + Legal LiaisonStatement deliveredReach list, confirmed-read list, evidence reference per item

#Phase 5 — Post-Incident

#ActionWhoDone whenEvidence to capture
1Rebuild the timeline in UTC/ISO 8601 from exported logs and artefact hashes, not from console screenshots or memory.ScribeSigned off by ICTimeline with a source reference per entry
2Blameless review on two numbers: workload compromise to detection, and detection to identity revocation.ICReview held, actions owned and datedReview record
3Answer honestly whether audit logging was on and at what level. If it was off or Metadata-only, that is a configuration finding with a name against it, and a cost to fix.Ops Lead (Cloud) + Exec SponsorGap documented with cost and ownerBefore/after log configuration, quoted cost
4Audit every cluster for the conditions that made this possible: mounted runtime sockets, privileged and hostNetwork pods, hostPath mounts of /, over-broad IRSA or Workload Identity trust, and default-mounted service-account tokens.Ops Lead (Platform)Inventory complete with remediation datesFindings list per cluster, owner per finding
5Enforce the outcome at admission rather than by policy document — block privileged pods, runtime socket mounts and unsigned images at the gate.Ops Lead (Platform)Admission policy enforcing in all clustersPolicy definitions, enforcement mode, exception register
6Convert the detection that caught this — or the one that should have — into a version-controlled rule with a validation test, per the detection-as-code practice in Chapter 9.Ops LeadRule merged and validatedPR link, validation run date
7Add this scenario to the exercise calendar as a tabletop, and specifically rehearse the capture-before-delete sequence. That is the step that fails under pressure.ICExercise scheduled with a named facilitatorCalendar entry, scenario card

#Decision points

#Communications and notification triggers

Nothing in this scenario starts a regulatory clock by itself. Containers being compromised is an operational event; what starts a clock is confirmed unauthorized access to data, which in this scenario almost always arrives through the Secret store rather than through the application. The trigger to watch for is Phase 3 step 1: the moment you can say a specific secret was read, and that secret unlocked a system holding personal or regulated data, brief Legal Liaison — do not wait until you can quantify records. If that path is confirmed, hand off to Playbook 14.7.

Two other notification paths matter here and are easy to miss. First, machine credentials cross organizational boundaries: if a rotated secret belonged to a partner, a customer, or a vendor's API, someone outside your organization needs to rotate too, and that is a contractual notice, not a courtesy call. Second, if the entry vector was a poisoned image, action or package, you may be one of many consumers — coordinate the disclosure through Playbook 14.5 rather than publishing independently. Chapter 15 holds the notification decision tree and every regulatory deadline. Do not reconstruct them here and never commit to a deadline from memory.

#Automation notes

Automate freely. Everything in Phase 1 that gathers and preserves: on a runtime detection, automatically resolve the pod's node, record the image digest, run the service-account and image blast-radius queries, export the audit-log window, snapshot the node volumes, and open the legal hold. All of it is reversible, all of it produces evidence, and all of it is verifiable after the fact. Automating the snapshot is one of the highest-value SOAR plays available, because it removes the pressure that makes responders reach for kubectl delete in the first place.

Automate behind a human gate. The deny-all NetworkPolicy and kubectl cordon are strong one-click actions — reversible, non-evicting, and a false positive costs latency rather than an outage. Gate them on a named approver and rate-limit them, and have the automation prove CNI enforcement with an egress test rather than reporting success on the API response.

Never automate. Node drain, service-account or RBAC deletion, IAM role detachment, node termination, and cluster-wide image bans. Every one of them is either irreversible or scales its blast radius with your false-positive rate, and auto-draining a node is specifically the case where the automation either gets blocked by a PDB or evicts the evidence. The documented failure modes of agentic triage are overconfident closure backed by weak proof and hallucinated detail in the investigation narrative, so make the rule structural: automation may gather, enrich, correlate and recommend without approval; it may act only where the action is reversible, scoped and rate-limited; irreversible or cluster-wide actions require a named human approver, and every automated action carries the artefact that justified it. Chapter 17 has the gate design in full.

#Pitfalls

Takeaway: treat every cluster compromise as a credential compromise until you have proved otherwise. Scope rotation by what the service account's RBAC could reach, not by what namespace the pod was in, and prove the quarantine with an egress test from inside the pod rather than with a green object in kubectl get netpol. The miner is the noise. The Secret store is the incident.

This is one of the fourteen scenario playbooks in The 2026 InfoSec Playbook, a free field manual by Daniel Ramos. Written so somebody who has never read the book can pick it up mid-incident and run it. See all fourteen. Free, in full, no email wall.