@blamejs/exceptd-skills 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -0
- package/CHANGELOG.md +120 -0
- package/README.md +30 -5
- package/bin/exceptd.js +694 -1
- package/data/_indexes/_meta.json +2 -2
- package/data/playbooks/ai-api.json +1073 -0
- package/data/playbooks/containers.json +1078 -0
- package/data/playbooks/cred-stores.json +1000 -0
- package/data/playbooks/crypto.json +1008 -0
- package/data/playbooks/framework.json +1015 -0
- package/data/playbooks/hardening.json +945 -0
- package/data/playbooks/kernel.json +796 -0
- package/data/playbooks/mcp.json +1042 -0
- package/data/playbooks/runtime.json +913 -0
- package/data/playbooks/sbom.json +1279 -0
- package/data/playbooks/secrets.json +959 -0
- package/lib/cross-ref-api.js +224 -0
- package/lib/playbook-runner.js +896 -0
- package/lib/schemas/playbook.schema.json +657 -0
- package/manifest-snapshot.json +1 -1
- package/manifest.json +39 -39
- package/orchestrator/scanner.js +23 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"id": "containers",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"last_threat_review": "2026-05-11",
|
|
6
|
+
"threat_currency_score": 95,
|
|
7
|
+
"changelog": [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"date": "2026-05-11",
|
|
11
|
+
"summary": "Initial seven-phase container-escape posture playbook. Walks current repo for Dockerfile bases (--platform, FROM scratch, version-pinned vs. latest), docker-compose files (privileged, cap-add, host network), k8s manifests (hostPID/hostNetwork/hostIPC, privileged, runAsUser:0, missing seccompProfile). Cross-references runc / containerd / kubelet CVE lineage (Leaky Vessels CVE-2024-21626 class + subsequent breakouts). Closes GRC loop with NIST 800-190, CIS Kubernetes Benchmark, and supply-chain framework gap mapping.",
|
|
12
|
+
"framework_gaps_updated": [
|
|
13
|
+
"nist-800-190",
|
|
14
|
+
"nist-800-53-CM-7",
|
|
15
|
+
"iso-27001-2022-A.8.9",
|
|
16
|
+
"pci-dss-4-req-2",
|
|
17
|
+
"cmmc-cm-l2-3.4.6"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"owner": "@blamejs/container-security",
|
|
22
|
+
"air_gap_mode": true,
|
|
23
|
+
"scope": "code",
|
|
24
|
+
"preconditions": [
|
|
25
|
+
{
|
|
26
|
+
"id": "repo-context",
|
|
27
|
+
"description": "Playbook walks the current working directory for container-related files. Must have a working directory with read access.",
|
|
28
|
+
"check": "cwd_readable == true",
|
|
29
|
+
"on_fail": "halt"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"mutex": [],
|
|
33
|
+
"feeds_into": [
|
|
34
|
+
{
|
|
35
|
+
"playbook_id": "kernel",
|
|
36
|
+
"condition": "finding.severity >= 'high'"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"playbook_id": "secrets",
|
|
40
|
+
"condition": "always"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"domain": {
|
|
45
|
+
"name": "Container runtime posture + manifest review",
|
|
46
|
+
"attack_class": "container-escape",
|
|
47
|
+
"atlas_refs": [],
|
|
48
|
+
"attack_refs": [
|
|
49
|
+
"T1611",
|
|
50
|
+
"T1610",
|
|
51
|
+
"T1613",
|
|
52
|
+
"T1525",
|
|
53
|
+
"T1068"
|
|
54
|
+
],
|
|
55
|
+
"cve_refs": [],
|
|
56
|
+
"cwe_refs": [
|
|
57
|
+
"CWE-269",
|
|
58
|
+
"CWE-732",
|
|
59
|
+
"CWE-250",
|
|
60
|
+
"CWE-284",
|
|
61
|
+
"CWE-863"
|
|
62
|
+
],
|
|
63
|
+
"d3fend_refs": [
|
|
64
|
+
"D3-PA",
|
|
65
|
+
"D3-CBAN",
|
|
66
|
+
"D3-EI"
|
|
67
|
+
],
|
|
68
|
+
"frameworks_in_scope": [
|
|
69
|
+
"nist-800-53",
|
|
70
|
+
"iso-27001-2022",
|
|
71
|
+
"soc2",
|
|
72
|
+
"pci-dss-4",
|
|
73
|
+
"nis2",
|
|
74
|
+
"dora",
|
|
75
|
+
"uk-caf",
|
|
76
|
+
"au-ism",
|
|
77
|
+
"au-essential-8",
|
|
78
|
+
"cmmc"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"phases": {
|
|
82
|
+
"govern": {
|
|
83
|
+
"jurisdiction_obligations": [
|
|
84
|
+
{
|
|
85
|
+
"jurisdiction": "EU",
|
|
86
|
+
"regulation": "NIS2 Art.21",
|
|
87
|
+
"obligation": "patch_critical",
|
|
88
|
+
"window_hours": 720,
|
|
89
|
+
"clock_starts": "analyze_complete",
|
|
90
|
+
"evidence_required": [
|
|
91
|
+
"container_manifest_inventory",
|
|
92
|
+
"runtime_cve_status",
|
|
93
|
+
"remediation_plan"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"jurisdiction": "EU",
|
|
98
|
+
"regulation": "NIS2 Art.23",
|
|
99
|
+
"obligation": "notify_regulator",
|
|
100
|
+
"window_hours": 24,
|
|
101
|
+
"clock_starts": "detect_confirmed",
|
|
102
|
+
"evidence_required": [
|
|
103
|
+
"affected_workload_inventory",
|
|
104
|
+
"exploitation_status_assessment"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"jurisdiction": "US",
|
|
109
|
+
"regulation": "CMMC 2.0 CM.L2-3.4.6",
|
|
110
|
+
"obligation": "patch_critical",
|
|
111
|
+
"window_hours": 720,
|
|
112
|
+
"clock_starts": "validate_complete",
|
|
113
|
+
"evidence_required": [
|
|
114
|
+
"baseline_configuration_document",
|
|
115
|
+
"ssp_section_update"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"jurisdiction": "AU",
|
|
120
|
+
"regulation": "APRA CPS 234",
|
|
121
|
+
"obligation": "notify_regulator",
|
|
122
|
+
"window_hours": 72,
|
|
123
|
+
"clock_starts": "validate_complete",
|
|
124
|
+
"evidence_required": [
|
|
125
|
+
"materiality_assessment",
|
|
126
|
+
"remediation_completed_evidence"
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
"theater_fingerprints": [
|
|
131
|
+
{
|
|
132
|
+
"pattern_id": "cis-k8s-benchmark-attested-but-stale",
|
|
133
|
+
"claim": "We pass the CIS Kubernetes Benchmark / NIST 800-190 — container security covered.",
|
|
134
|
+
"fast_detection_test": "Walk the repo for k8s manifests and grep for privileged: true, hostPID: true, hostNetwork: true, hostIPC: true, runAsUser: 0, securityContext absent, seccompProfile absent. Any non-zero count in deployable manifests contradicts CIS K8s Section 5 / NIST 800-190 hardening claims regardless of cluster-wide attestation.",
|
|
135
|
+
"implicated_controls": [
|
|
136
|
+
"nist-800-190",
|
|
137
|
+
"nist-800-53-CM-7",
|
|
138
|
+
"iso-27001-2022-A.8.9"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"pattern_id": "image-scanning-attested-but-latest-tag",
|
|
143
|
+
"claim": "Container images are scanned for vulnerabilities in CI.",
|
|
144
|
+
"fast_detection_test": "Grep Dockerfiles for `FROM .*:latest` OR `FROM .*` (no tag at all). 'latest' resolves differently between scan time and deploy time; the scan result is for a different image than what ships. Any non-zero count makes the image-scanning attestation meaningless for the affected images.",
|
|
145
|
+
"implicated_controls": [
|
|
146
|
+
"nist-800-190-4.1.1",
|
|
147
|
+
"soc2-cc7.1",
|
|
148
|
+
"iso-27001-2022-A.8.8"
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"pattern_id": "supply-chain-attested-but-no-pinning",
|
|
153
|
+
"claim": "We follow SLSA / NIST SSDF for container supply chain.",
|
|
154
|
+
"fast_detection_test": "Grep Dockerfiles for FROM with @sha256: digest pinning. Compare count of pinned vs. unpinned FROM directives. SLSA Level 3+ effectively requires digest pinning. Tag-only references without digest are theater for any SLSA L3+ attestation.",
|
|
155
|
+
"implicated_controls": [
|
|
156
|
+
"nist-ssdf-ps-3.2",
|
|
157
|
+
"nist-800-218",
|
|
158
|
+
"eu-cra"
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"pattern_id": "runc-patched-but-no-version-evidence",
|
|
163
|
+
"claim": "Leaky Vessels (CVE-2024-21626 lineage) is patched cluster-wide.",
|
|
164
|
+
"fast_detection_test": "Ask the org for the kubectl version + node runc version on each pool. CVE-2024-21626 requires runc >= 1.1.12 OR containerd / Docker bundling a fixed runc. Without per-node version evidence, 'patched' is attested but not verified. Node staging windows can leave clusters mixed-patch for weeks.",
|
|
165
|
+
"implicated_controls": [
|
|
166
|
+
"nist-800-190",
|
|
167
|
+
"iso-27001-2022-A.8.8"
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"framework_context": {
|
|
172
|
+
"gap_summary": "Container-escape posture sits across NIST 800-190 (Application Container Security Guide), CIS Kubernetes Benchmark, NIST 800-53 CM-7 (least functionality), ISO A.8.9 (configuration management), and supply-chain frameworks (NIST SSDF / SLSA / EU CRA). NIST 800-190 is the most container-aware US framework but is non-mandatory guidance; CIS K8s Benchmark is widely adopted but point-in-time. Frameworks evaluate cluster-wide posture; they do not require per-manifest analysis of the actual workloads as deployed. Real-world: privileged: true / hostPID / runAsUser: 0 manifests routinely ship despite cluster-wide attestation, because the framework gap is between attestation cadence and manifest velocity. Additionally: runc / containerd / kubelet CVE lineage (CVE-2024-21626 Leaky Vessels and its progeny) requires per-node runtime version evidence, not cluster-wide patch attestation. SCOPE: self-scoped to current working directory walk; complements (does not replace) live-cluster admission control + runtime security scanning.",
|
|
173
|
+
"lag_score": 21,
|
|
174
|
+
"per_framework_gaps": [
|
|
175
|
+
{
|
|
176
|
+
"framework": "nist-800-53",
|
|
177
|
+
"control_id": "CM-7",
|
|
178
|
+
"designed_for": "Least functionality across system components.",
|
|
179
|
+
"insufficient_because": "Treats containers as a system component requiring least-functionality policy. Does not name container-specific primitives (privileged, hostPID, runAsUser:0, securityContext absent) as required enumeration items."
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"framework": "nist-800-190",
|
|
183
|
+
"control_id": "4.1.1, 4.1.2, 4.2.1, 4.4.1",
|
|
184
|
+
"designed_for": "Container image, registry, orchestrator, container security.",
|
|
185
|
+
"insufficient_because": "Non-mandatory guidance. Provides good content but no compliance-enforcement teeth on its own; organizations cite it without being audited against it."
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"framework": "iso-27001-2022",
|
|
189
|
+
"control_id": "A.8.9",
|
|
190
|
+
"designed_for": "Configuration management baseline.",
|
|
191
|
+
"insufficient_because": "Same baseline + change-management posture as host hardening — accepts attestation, doesn't require per-manifest analysis."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"framework": "pci-dss-4",
|
|
195
|
+
"control_id": "Req.2.2",
|
|
196
|
+
"designed_for": "Configuration standards for system components.",
|
|
197
|
+
"insufficient_because": "Container-aware language added in 4.0 but operationalization is left to the QSA. Auditors vary widely in container expertise."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"framework": "eu-cra",
|
|
201
|
+
"control_id": "Annex I",
|
|
202
|
+
"designed_for": "Cyber Resilience Act essential requirements (effective Dec 2027).",
|
|
203
|
+
"insufficient_because": "Names SBOM + vulnerability handling; effective date is in the future. Container-specific operationalization will be set by harmonized standards still being drafted in 2026."
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
"skill_preload": [
|
|
208
|
+
"container-runtime-security",
|
|
209
|
+
"supply-chain-integrity",
|
|
210
|
+
"framework-gap-analysis",
|
|
211
|
+
"compliance-theater",
|
|
212
|
+
"policy-exception-gen"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"direct": {
|
|
216
|
+
"threat_context": "Container escape attack class in 2025-2026 is dominated by the runc / containerd / kubelet CVE chain. CVE-2024-21626 'Leaky Vessels' (runc <1.1.12 working-directory race → fd leak into host) shipped Jan 2024 with public PoC and is the canonical container-escape primitive; subsequent runc/containerd CVEs follow the same pattern (host-resource leakage via misconfigured isolation). Mandiant 2025 IR report: ~22% of cloud-tenant compromises involved a container-escape step. The escape is exquisitely sensitive to manifest posture: privileged: true grants the attacker the full host kernel (LPE without escape); hostPID/hostNetwork/hostIPC grant cross-pod visibility; runAsUser: 0 + writable host mount = direct host write. seccompProfile + AppArmor profile presence is the primary mitigation. Manifests in repos are the attestation-vs-reality battleground — admission controllers reject some but not all anti-patterns, depending on policy maturity.",
|
|
217
|
+
"rwep_threshold": {
|
|
218
|
+
"escalate": 85,
|
|
219
|
+
"monitor": 65,
|
|
220
|
+
"close": 35
|
|
221
|
+
},
|
|
222
|
+
"framework_lag_declaration": "NIST 800-190 + CIS K8s Benchmark + NIST 800-53 CM-7 + ISO A.8.9 + PCI Req.2.2 collectively cover container hardening but accept cluster-wide attestation as evidence. None require per-manifest analysis as a control. Manifest drift in GitOps repos can introduce privileged: true / hostPID / unscoped capabilities faster than any attestation cadence. EU CRA + NIST SSDF + SLSA cover supply-chain posture but most orgs are at SLSA L1-L2 without digest pinning. Gap = ~21 days between manifest drift and review cadence; per-CVE gap (e.g. Leaky Vessels) is hours from public PoC to weaponization vs. weeks of node-staging windows.",
|
|
223
|
+
"skill_chain": [
|
|
224
|
+
{
|
|
225
|
+
"skill": "container-runtime-security",
|
|
226
|
+
"purpose": "Inventory Dockerfile + compose + k8s manifest posture; map to NIST 800-190 + CIS K8s Benchmark; identify per-manifest anti-patterns.",
|
|
227
|
+
"required": true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"skill": "supply-chain-integrity",
|
|
231
|
+
"purpose": "Assess image provenance: digest pinning, base-image lineage, signature verification, SBOM availability.",
|
|
232
|
+
"required": true
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"skill": "framework-gap-analysis",
|
|
236
|
+
"purpose": "Map findings to which framework controls claim to cover them and where the gap is.",
|
|
237
|
+
"required": true
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"skill": "compliance-theater",
|
|
241
|
+
"purpose": "Run the theater test on the org's container-security attestation (CIS K8s + image scanning + SLSA claim).",
|
|
242
|
+
"required": true
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"skill": "policy-exception-gen",
|
|
246
|
+
"purpose": "Generate auditor-ready exception language for workloads requiring elevated privileges (CNI plugins, CSI drivers, eBPF observability) where the elevation is intentional.",
|
|
247
|
+
"skip_if": "close.exception_generation.trigger_condition == false",
|
|
248
|
+
"required": false
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
"token_budget": {
|
|
252
|
+
"estimated_total": 21000,
|
|
253
|
+
"breakdown": {
|
|
254
|
+
"govern": 2600,
|
|
255
|
+
"direct": 1800,
|
|
256
|
+
"look": 2400,
|
|
257
|
+
"detect": 3400,
|
|
258
|
+
"analyze": 4400,
|
|
259
|
+
"validate": 3800,
|
|
260
|
+
"close": 2600
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"look": {
|
|
265
|
+
"artifacts": [
|
|
266
|
+
{
|
|
267
|
+
"id": "dockerfile-inventory",
|
|
268
|
+
"type": "file",
|
|
269
|
+
"source": "Walk cwd for filenames Dockerfile, *.dockerfile, Containerfile, *.containerfile",
|
|
270
|
+
"description": "All Dockerfile-shaped files in the repo. Each is a build recipe whose FROM directive + USER directive + RUN curl-pipe-bash anti-patterns are findings.",
|
|
271
|
+
"required": true,
|
|
272
|
+
"air_gap_alternative": "Filesystem walk; no network."
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"id": "dockerfile-content",
|
|
276
|
+
"type": "file",
|
|
277
|
+
"source": "For each Dockerfile: full content with line numbers.",
|
|
278
|
+
"description": "Required for per-directive analysis (FROM lines, USER directives, COPY/ADD source/dest, RUN commands, HEALTHCHECK presence, --platform flags).",
|
|
279
|
+
"required": true
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"id": "compose-files",
|
|
283
|
+
"type": "file",
|
|
284
|
+
"source": "Walk cwd for filenames docker-compose.yml, docker-compose.yaml, compose.yml, compose.yaml, docker-compose.*.yml",
|
|
285
|
+
"description": "docker-compose definitions. Services with privileged: true, cap_add, network_mode: host, pid: host, ipc: host, volumes binding /, /etc, /proc, /sys, /var/run/docker.sock.",
|
|
286
|
+
"required": true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"id": "k8s-manifests",
|
|
290
|
+
"type": "file",
|
|
291
|
+
"source": "Walk cwd for *.yaml / *.yml files; identify those with apiVersion + kind (k8s resource shape). Filter to Pod, Deployment, StatefulSet, DaemonSet, Job, CronJob, ReplicaSet kinds.",
|
|
292
|
+
"description": "Kubernetes workload manifests. spec.securityContext + per-container securityContext + hostPID/hostNetwork/hostIPC + volumes (hostPath, /var/run/docker.sock) + containers[].image (tag vs. digest).",
|
|
293
|
+
"required": true
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"id": "helm-charts",
|
|
297
|
+
"type": "file",
|
|
298
|
+
"source": "Walk for Chart.yaml + values.yaml + templates/*.yaml under helm-chart directory shape.",
|
|
299
|
+
"description": "Helm chart inventory. values.yaml is configuration surface; templates contain manifest shape. Findings parallel k8s-manifests.",
|
|
300
|
+
"required": false
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"id": "kustomize-overlays",
|
|
304
|
+
"type": "file",
|
|
305
|
+
"source": "Walk for kustomization.yaml + overlay/*.yaml.",
|
|
306
|
+
"description": "Kustomize overlays. Patch findings against base manifests; security-context-changing patches are themselves findings.",
|
|
307
|
+
"required": false
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"id": "pod-security-admission-config",
|
|
311
|
+
"type": "file",
|
|
312
|
+
"source": "Walk for files containing 'PodSecurity' configuration (PodSecurityPolicy deprecated; PodSecurity admission controller config).",
|
|
313
|
+
"description": "Cluster's admission control policy. Restrictive / Baseline / Privileged policy assignments per namespace.",
|
|
314
|
+
"required": false
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"id": "network-policies",
|
|
318
|
+
"type": "file",
|
|
319
|
+
"source": "Walk for k8s manifests with kind: NetworkPolicy or kind: CiliumNetworkPolicy / CalicoNetworkPolicy.",
|
|
320
|
+
"description": "Network policy inventory. Absence is itself a finding; namespaces without NP default to allow-all.",
|
|
321
|
+
"required": false
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"id": "service-account-manifests",
|
|
325
|
+
"type": "file",
|
|
326
|
+
"source": "Walk for k8s manifests with kind: ServiceAccount or RoleBinding / ClusterRoleBinding.",
|
|
327
|
+
"description": "Service account + RBAC binding inventory. cluster-admin-bound SAs in deployable namespaces are findings.",
|
|
328
|
+
"required": false
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"collection_scope": {
|
|
332
|
+
"time_window": "current",
|
|
333
|
+
"asset_scope": "current_repository_working_tree",
|
|
334
|
+
"depth": "deep",
|
|
335
|
+
"sampling": "complete walk; no sampling — every container-related file is read"
|
|
336
|
+
},
|
|
337
|
+
"environment_assumptions": [
|
|
338
|
+
{
|
|
339
|
+
"assumption": "cwd is a repository containing some container/k8s manifest content",
|
|
340
|
+
"if_false": "Playbook completes with zero findings + visibility note 'no container manifests in scope'. Not a finding; the workload-not-containerized posture is its own valid choice."
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"assumption": "agent can parse YAML and identify k8s resource kinds",
|
|
344
|
+
"if_false": "Mark k8s-manifest analysis inconclusive; defer to a YAML-capable agent."
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"assumption": "repo represents the as-deployed state",
|
|
348
|
+
"if_false": "Repo may contain example/test manifests not deployed (e.g. examples/ directory). Mark these with 'not-deployed-shape' and emit findings only on root-of-deploy paths (typical: k8s/, manifests/, deploy/, charts/, ci/)."
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"assumption": "node runc / containerd / kubelet versions are not in repo",
|
|
352
|
+
"if_false": "If runtime version evidence is in repo (rare but possible for self-managed clusters), cross-reference against CVE catalog for runtime CVEs."
|
|
353
|
+
}
|
|
354
|
+
],
|
|
355
|
+
"fallback_if_unavailable": [
|
|
356
|
+
{
|
|
357
|
+
"artifact_id": "dockerfile-inventory",
|
|
358
|
+
"fallback_action": "use_compensating_artifact",
|
|
359
|
+
"confidence_impact": "low"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"artifact_id": "compose-files",
|
|
363
|
+
"fallback_action": "use_compensating_artifact",
|
|
364
|
+
"confidence_impact": "low"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"artifact_id": "k8s-manifests",
|
|
368
|
+
"fallback_action": "use_compensating_artifact",
|
|
369
|
+
"confidence_impact": "medium"
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"artifact_id": "helm-charts",
|
|
373
|
+
"fallback_action": "mark_inconclusive",
|
|
374
|
+
"confidence_impact": "low"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"artifact_id": "pod-security-admission-config",
|
|
378
|
+
"fallback_action": "mark_inconclusive",
|
|
379
|
+
"confidence_impact": "low"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"artifact_id": "network-policies",
|
|
383
|
+
"fallback_action": "mark_inconclusive",
|
|
384
|
+
"confidence_impact": "medium"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"artifact_id": "service-account-manifests",
|
|
388
|
+
"fallback_action": "mark_inconclusive",
|
|
389
|
+
"confidence_impact": "medium"
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
},
|
|
393
|
+
"detect": {
|
|
394
|
+
"indicators": [
|
|
395
|
+
{
|
|
396
|
+
"id": "dockerfile-from-latest",
|
|
397
|
+
"type": "file_path",
|
|
398
|
+
"value": "Dockerfile FROM directive with :latest tag OR no tag at all (defaults to :latest)",
|
|
399
|
+
"description": "Image base resolves differently between scan and deploy. Image scanning attestation does not apply to the deployed image.",
|
|
400
|
+
"confidence": "deterministic",
|
|
401
|
+
"deterministic": true,
|
|
402
|
+
"attack_ref": "T1525"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"id": "dockerfile-no-digest-pin",
|
|
406
|
+
"type": "log_pattern",
|
|
407
|
+
"value": "Dockerfile FROM directive without @sha256:* digest",
|
|
408
|
+
"description": "Tag-only base reference. Tag mutability means provenance cannot be verified.",
|
|
409
|
+
"confidence": "high",
|
|
410
|
+
"deterministic": false,
|
|
411
|
+
"attack_ref": "T1525"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"id": "dockerfile-runs-as-root",
|
|
415
|
+
"type": "log_pattern",
|
|
416
|
+
"value": "Dockerfile lacks USER directive OR USER directive sets root / 0",
|
|
417
|
+
"description": "Container will run as root inside the namespace. Combined with any namespace escape, root-on-host.",
|
|
418
|
+
"confidence": "deterministic",
|
|
419
|
+
"deterministic": true,
|
|
420
|
+
"attack_ref": "T1611"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"id": "dockerfile-curl-pipe-bash",
|
|
424
|
+
"type": "log_pattern",
|
|
425
|
+
"value": "Dockerfile RUN directive containing `curl|wget` piped to `bash|sh|python` OR pipe pattern from network to shell interpreter",
|
|
426
|
+
"description": "Build-time supply-chain compromise primitive. Compromised CDN or DNS rebind = arbitrary code in image.",
|
|
427
|
+
"confidence": "deterministic",
|
|
428
|
+
"deterministic": true,
|
|
429
|
+
"attack_ref": "T1195.002"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"id": "compose-privileged",
|
|
433
|
+
"type": "log_pattern",
|
|
434
|
+
"value": "docker-compose service block containing `privileged: true`",
|
|
435
|
+
"description": "Container has full host kernel access. Container escape is built in by configuration.",
|
|
436
|
+
"confidence": "deterministic",
|
|
437
|
+
"deterministic": true,
|
|
438
|
+
"attack_ref": "T1611"
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"id": "compose-cap-add-sys-admin",
|
|
442
|
+
"type": "log_pattern",
|
|
443
|
+
"value": "docker-compose `cap_add: [SYS_ADMIN]` OR `cap_add: [SYS_PTRACE]` OR `cap_add: [SYS_MODULE]`",
|
|
444
|
+
"description": "Dangerous capability granted. SYS_ADMIN especially is functionally equivalent to root-on-host.",
|
|
445
|
+
"confidence": "deterministic",
|
|
446
|
+
"deterministic": true,
|
|
447
|
+
"attack_ref": "T1611"
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"id": "compose-host-network",
|
|
451
|
+
"type": "log_pattern",
|
|
452
|
+
"value": "docker-compose `network_mode: host` OR `pid: host` OR `ipc: host`",
|
|
453
|
+
"description": "Host namespace sharing. Cross-process visibility + tampering primitive.",
|
|
454
|
+
"confidence": "deterministic",
|
|
455
|
+
"deterministic": true,
|
|
456
|
+
"attack_ref": "T1610"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"id": "compose-docker-sock-mount",
|
|
460
|
+
"type": "log_pattern",
|
|
461
|
+
"value": "docker-compose volume entry mounting /var/run/docker.sock into the container",
|
|
462
|
+
"description": "Container can control the Docker daemon. Functionally equivalent to root on host.",
|
|
463
|
+
"confidence": "deterministic",
|
|
464
|
+
"deterministic": true,
|
|
465
|
+
"attack_ref": "T1611"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"id": "k8s-privileged",
|
|
469
|
+
"type": "log_pattern",
|
|
470
|
+
"value": "k8s manifest containers[].securityContext.privileged: true",
|
|
471
|
+
"description": "K8s container with privileged: true. Same risk class as compose-privileged.",
|
|
472
|
+
"confidence": "deterministic",
|
|
473
|
+
"deterministic": true,
|
|
474
|
+
"attack_ref": "T1611"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
"id": "k8s-host-namespaces",
|
|
478
|
+
"type": "log_pattern",
|
|
479
|
+
"value": "k8s manifest spec.hostPID: true OR spec.hostNetwork: true OR spec.hostIPC: true",
|
|
480
|
+
"description": "Pod shares host namespaces. Cross-host-process visibility.",
|
|
481
|
+
"confidence": "deterministic",
|
|
482
|
+
"deterministic": true,
|
|
483
|
+
"attack_ref": "T1610"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"id": "k8s-run-as-root",
|
|
487
|
+
"type": "log_pattern",
|
|
488
|
+
"value": "k8s manifest containers[].securityContext.runAsUser: 0 OR runAsNonRoot: false OR runAsUser unset AND image runs as root",
|
|
489
|
+
"description": "Container runs as root. Combined with any escape primitive = host root.",
|
|
490
|
+
"confidence": "high",
|
|
491
|
+
"deterministic": false,
|
|
492
|
+
"attack_ref": "T1611"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"id": "k8s-no-seccomp-profile",
|
|
496
|
+
"type": "log_pattern",
|
|
497
|
+
"value": "k8s manifest containers[].securityContext.seccompProfile absent AND pod.spec.securityContext.seccompProfile absent",
|
|
498
|
+
"description": "Default seccomp profile = unconfined on many CRI implementations. Allows full syscall surface inside the container.",
|
|
499
|
+
"confidence": "high",
|
|
500
|
+
"deterministic": false,
|
|
501
|
+
"attack_ref": "T1611"
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
"id": "k8s-hostpath-sensitive",
|
|
505
|
+
"type": "log_pattern",
|
|
506
|
+
"value": "k8s manifest volumes[].hostPath.path matching ^/$ OR ^/etc OR ^/proc OR ^/sys OR ^/var/run/docker.sock OR ^/var/lib/kubelet OR ^/var/log",
|
|
507
|
+
"description": "Sensitive host path mounted into container. Host filesystem read/write primitive.",
|
|
508
|
+
"confidence": "deterministic",
|
|
509
|
+
"deterministic": true,
|
|
510
|
+
"attack_ref": "T1611"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"id": "k8s-image-latest",
|
|
514
|
+
"type": "log_pattern",
|
|
515
|
+
"value": "k8s manifest containers[].image ends in :latest OR has no tag separator",
|
|
516
|
+
"description": "Image tag :latest in k8s. Resolves to whatever the registry holds at pod-start time; deployment is non-deterministic.",
|
|
517
|
+
"confidence": "deterministic",
|
|
518
|
+
"deterministic": true,
|
|
519
|
+
"attack_ref": "T1525"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"id": "k8s-cluster-admin-binding",
|
|
523
|
+
"type": "log_pattern",
|
|
524
|
+
"value": "k8s manifest kind: ClusterRoleBinding referencing cluster-admin role AND subjects include a ServiceAccount in a workload namespace",
|
|
525
|
+
"description": "Workload SA bound to cluster-admin. Pod compromise = cluster compromise.",
|
|
526
|
+
"confidence": "deterministic",
|
|
527
|
+
"deterministic": true,
|
|
528
|
+
"attack_ref": "T1078.004"
|
|
529
|
+
}
|
|
530
|
+
],
|
|
531
|
+
"false_positive_profile": [
|
|
532
|
+
{
|
|
533
|
+
"indicator_id": "k8s-privileged",
|
|
534
|
+
"benign_pattern": "Legitimate infrastructure workloads requiring privileged mode (CNI plugins like Cilium/Calico, CSI drivers, kube-proxy, eBPF observability agents).",
|
|
535
|
+
"distinguishing_test": "Check whether the manifest is in a namespace explicitly enumerated as infrastructure (kube-system, kube-flannel, cilium-system, longhorn-system, etc.) AND the workload name matches a documented infrastructure component. If yes, downgrade to medium + emit 'intentional infrastructure privilege' finding requiring documented PodSecurity exemption. Workload-namespace privileged: true holds deterministic."
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"indicator_id": "k8s-host-namespaces",
|
|
539
|
+
"benign_pattern": "Node-level agents (DaemonSets) that legitimately need host network for observability or networking (kube-proxy, ingress controllers, CNI agents).",
|
|
540
|
+
"distinguishing_test": "Same as above — check infrastructure-namespace + kind: DaemonSet + documented component. Holds for compose-host-network similarly."
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
"indicator_id": "compose-docker-sock-mount",
|
|
544
|
+
"benign_pattern": "Local development compose file for tools that legitimately orchestrate other containers (Portainer dev, dind-runner CI).",
|
|
545
|
+
"distinguishing_test": "Check whether the file path contains /dev/, /examples/, /docs/, /test/ AND whether the same repo has a separate production compose without the mount. If yes, downgrade to medium and emit 'development-only socket mount' finding. Production compose with the mount holds deterministic."
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"indicator_id": "dockerfile-runs-as-root",
|
|
549
|
+
"benign_pattern": "Multi-stage build where final stage uses USER directive but earlier stages don't (intentional pattern).",
|
|
550
|
+
"distinguishing_test": "Parse all stages in the Dockerfile. Find the FROM with `AS` matching the final-stage name (or the last FROM if no aliases). Check whether the final stage has a USER directive. If final-stage USER is set to non-root, downgrade to medium and emit 'multi-stage build, root in build stages only'. If even the final stage has no USER, hold deterministic."
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"indicator_id": "k8s-image-latest",
|
|
554
|
+
"benign_pattern": "Dev/staging manifest with :latest intentionally for fast iteration.",
|
|
555
|
+
"distinguishing_test": "Check whether the manifest path contains /dev/, /staging/, /local/ AND whether sibling files exist for /prod/ without :latest. If yes, downgrade to medium with 'dev-only latest tag'. Prod path with :latest holds deterministic."
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"indicator_id": "k8s-no-seccomp-profile",
|
|
559
|
+
"benign_pattern": "Cluster-level default seccomp profile applied via Kubelet --seccomp-default flag (k8s 1.27+). Per-pod profile not needed.",
|
|
560
|
+
"distinguishing_test": "Cluster-default flag presence is not visible from manifests alone. In air-gap mode, hold high confidence and emit a finding requiring cluster-default-seccomp evidence as compensating control."
|
|
561
|
+
}
|
|
562
|
+
],
|
|
563
|
+
"minimum_signal": {
|
|
564
|
+
"detected": "At least one deterministic indicator fires on a non-infrastructure-namespace manifest, OR two or more high-confidence indicators fire on the same workload.",
|
|
565
|
+
"inconclusive": "Manifests present but agent cannot parse YAML or determine namespace context. Distinguish 'parse failure' from 'clean manifests'.",
|
|
566
|
+
"not_detected": "All container files inspected AND no deterministic indicators fired on workload-namespace manifests AND no more than one high-confidence indicator with confirmed benign distinguishing test."
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
"analyze": {
|
|
570
|
+
"rwep_inputs": [
|
|
571
|
+
{
|
|
572
|
+
"signal_id": "compose-privileged",
|
|
573
|
+
"rwep_factor": "active_exploitation",
|
|
574
|
+
"weight": 30,
|
|
575
|
+
"notes": "privileged: true is exploitation-already-configured."
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"signal_id": "k8s-privileged",
|
|
579
|
+
"rwep_factor": "active_exploitation",
|
|
580
|
+
"weight": 30,
|
|
581
|
+
"notes": "Same as compose-privileged."
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"signal_id": "compose-docker-sock-mount",
|
|
585
|
+
"rwep_factor": "active_exploitation",
|
|
586
|
+
"weight": 30,
|
|
587
|
+
"notes": "Docker socket mount = full host control."
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"signal_id": "k8s-hostpath-sensitive",
|
|
591
|
+
"rwep_factor": "active_exploitation",
|
|
592
|
+
"weight": 25,
|
|
593
|
+
"notes": "Sensitive host path mount = direct host write."
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"signal_id": "k8s-host-namespaces",
|
|
597
|
+
"rwep_factor": "blast_radius",
|
|
598
|
+
"weight": 20,
|
|
599
|
+
"notes": "Host namespace = cross-process pivot."
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"signal_id": "compose-host-network",
|
|
603
|
+
"rwep_factor": "blast_radius",
|
|
604
|
+
"weight": 20,
|
|
605
|
+
"notes": "Same."
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"signal_id": "k8s-cluster-admin-binding",
|
|
609
|
+
"rwep_factor": "active_exploitation",
|
|
610
|
+
"weight": 30,
|
|
611
|
+
"notes": "Cluster-admin SA in workload namespace = pre-positioned escalation."
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
"signal_id": "k8s-run-as-root",
|
|
615
|
+
"rwep_factor": "blast_radius",
|
|
616
|
+
"weight": 15,
|
|
617
|
+
"notes": "Root inside container amplifies any escape primitive."
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"signal_id": "k8s-no-seccomp-profile",
|
|
621
|
+
"rwep_factor": "blast_radius",
|
|
622
|
+
"weight": 10,
|
|
623
|
+
"notes": "Unconfined syscalls = full escape primitive surface."
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
"signal_id": "k8s-image-latest",
|
|
627
|
+
"rwep_factor": "ai_weaponization",
|
|
628
|
+
"weight": 10,
|
|
629
|
+
"notes": "Image mutation between scan and deploy creates an AI-discoverable attack window."
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
"signal_id": "dockerfile-from-latest",
|
|
633
|
+
"rwep_factor": "ai_weaponization",
|
|
634
|
+
"weight": 10,
|
|
635
|
+
"notes": "Same — build vs. deploy provenance gap."
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"signal_id": "dockerfile-curl-pipe-bash",
|
|
639
|
+
"rwep_factor": "active_exploitation",
|
|
640
|
+
"weight": 20,
|
|
641
|
+
"notes": "Supply-chain build primitive."
|
|
642
|
+
}
|
|
643
|
+
],
|
|
644
|
+
"blast_radius_model": {
|
|
645
|
+
"scope_question": "Given the manifest posture, what is the realistic blast radius when this workload is deployed and an attacker achieves code execution inside the container?",
|
|
646
|
+
"scoring_rubric": [
|
|
647
|
+
{
|
|
648
|
+
"condition": "No deterministic indicators on workload-namespace manifests AND digest pinning + non-root + seccomp profile present",
|
|
649
|
+
"blast_radius_score": 1,
|
|
650
|
+
"description": "Container is hardened. Escape requires a runtime CVE chain; no pre-positioned primitives."
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
"condition": "One high-confidence indicator (e.g. no seccomp, or no digest pin) on otherwise-hardened manifest",
|
|
654
|
+
"blast_radius_score": 2,
|
|
655
|
+
"description": "Single hardening gap. Escape requires both a runtime CVE and the gap."
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
"condition": "One deterministic indicator (privileged: false but hostPath sensitive, OR latest tag) on a workload manifest",
|
|
659
|
+
"blast_radius_score": 3,
|
|
660
|
+
"description": "One pre-positioned escape primitive. Container compromise → host with manageable effort."
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
"condition": "Multiple deterministic indicators on workload manifest OR cluster-admin SA in workload namespace",
|
|
664
|
+
"blast_radius_score": 4,
|
|
665
|
+
"description": "Container compromise = cluster-wide compromise without runtime CVE chain."
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"condition": "privileged: true + hostPID/Network + docker.sock mount + cluster-admin SA on workload-namespace pod",
|
|
669
|
+
"blast_radius_score": 5,
|
|
670
|
+
"description": "Pre-positioned host-and-cluster compromise. Pod compromise = full infrastructure access."
|
|
671
|
+
}
|
|
672
|
+
]
|
|
673
|
+
},
|
|
674
|
+
"compliance_theater_check": {
|
|
675
|
+
"claim": "Org passes CIS Kubernetes Benchmark + NIST 800-190 + image scanning in CI; container security covered.",
|
|
676
|
+
"audit_evidence": "Most-recent CIS K8s scan report; image-scanner CI logs; admission controller config.",
|
|
677
|
+
"reality_test": "Run the manifest walk today and count: (a) workload-namespace manifests with privileged: true, (b) workload manifests with hostPID/hostNetwork/hostIPC, (c) manifests with runAsUser: 0 (or no runAsNonRoot constraint), (d) manifests with image: *:latest, (e) Dockerfiles with FROM *:latest or no tag, (f) Dockerfiles without USER directive, (g) ClusterRoleBindings to cluster-admin from workload-namespace SAs. Any non-zero count in workload-namespace contradicts CIS K8s Section 5 / NIST 800-190 4.4. Additionally test whether the admission controller would actually block these manifests if reapplied (paste a known-bad manifest into a `kubectl apply --dry-run=server` and confirm rejection). If admission passes, the controls are advisory.",
|
|
678
|
+
"theater_verdict_if_gap": "CIS K8s attestation describes a hardened posture that manifest reality contradicts. Workload-namespace privileged/host-namespace/cluster-admin patterns ship despite the attestation. Either (a) refactor the offending manifests to hardened equivalents, (b) update admission controller to baseline/restricted PodSecurity for workload namespaces, OR (c) generate per-workload policy exception documenting the elevation + named compensating controls."
|
|
679
|
+
},
|
|
680
|
+
"framework_gap_mapping": [
|
|
681
|
+
{
|
|
682
|
+
"finding_id": "container-manifest-posture",
|
|
683
|
+
"framework": "nist-800-53",
|
|
684
|
+
"claimed_control": "CM-7 — Least Functionality",
|
|
685
|
+
"actual_gap": "Treats containers as generic system components. No required enumeration of container-specific anti-patterns.",
|
|
686
|
+
"required_control": "CM-7 sub-control: per-manifest analysis of container security primitives (privileged, host namespaces, capabilities, runAsUser, seccomp, image pinning) with documented baseline + deviation register."
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
"finding_id": "container-manifest-posture",
|
|
690
|
+
"framework": "nist-800-190",
|
|
691
|
+
"claimed_control": "4.1.1, 4.4.1, 4.4.2, 4.4.3",
|
|
692
|
+
"actual_gap": "Non-mandatory guidance; orgs cite without being audited against specifics.",
|
|
693
|
+
"required_control": "Adopt 800-190 as a CM-7 compliance target (not just reference). Require evidence per workload manifest, not cluster-wide attestation."
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"finding_id": "container-manifest-posture",
|
|
697
|
+
"framework": "iso-27001-2022",
|
|
698
|
+
"claimed_control": "A.8.9 — Configuration Management",
|
|
699
|
+
"actual_gap": "Baseline + change-management evidence accepted. No required per-workload analysis.",
|
|
700
|
+
"required_control": "SoA footnote requiring per-workload manifest review against documented hardened baseline."
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"finding_id": "container-manifest-posture",
|
|
704
|
+
"framework": "pci-dss-4",
|
|
705
|
+
"claimed_control": "Req.2.2 — Configuration Standards",
|
|
706
|
+
"actual_gap": "Container-aware in 4.0 but QSA expertise varies; auditor may accept cluster-wide attestation.",
|
|
707
|
+
"required_control": "Require QSA training on container-specific evaluation and per-workload manifest review for systems in CDE."
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"finding_id": "container-supply-chain",
|
|
711
|
+
"framework": "eu-cra",
|
|
712
|
+
"claimed_control": "Annex I (effective Dec 2027)",
|
|
713
|
+
"actual_gap": "Will require SBOM + vulnerability handling. Operationalization unclear for containerized products as of 2026.",
|
|
714
|
+
"required_control": "Pre-CRA enforcement preparation: digest-pinned base images, SBOM-per-image, signed images, attested provenance per SLSA L3+."
|
|
715
|
+
}
|
|
716
|
+
],
|
|
717
|
+
"escalation_criteria": [
|
|
718
|
+
{
|
|
719
|
+
"condition": "rwep >= 90 AND blast_radius_score >= 4",
|
|
720
|
+
"action": "page_on_call"
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
"condition": "k8s-cluster-admin-binding fired AND target_namespace not in infrastructure_namespaces",
|
|
724
|
+
"action": "raise_severity"
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
"condition": "blast_radius_score >= 4 AND workload_is_internet_facing == true",
|
|
728
|
+
"action": "trigger_playbook",
|
|
729
|
+
"target_playbook": "secrets"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
"condition": "compliance_theater_check.verdict == 'theater' AND jurisdiction_obligations contains 'EU'",
|
|
733
|
+
"action": "notify_legal"
|
|
734
|
+
}
|
|
735
|
+
]
|
|
736
|
+
},
|
|
737
|
+
"validate": {
|
|
738
|
+
"remediation_paths": [
|
|
739
|
+
{
|
|
740
|
+
"id": "remove-privileged-flag",
|
|
741
|
+
"description": "Refactor manifest to remove privileged: true. If workload needs specific capability, replace with bounded capabilities: add: [NET_ADMIN] etc. and drop: [ALL]. If host access genuinely required (eBPF observability), move workload to a dedicated infrastructure namespace + document via PodSecurity exemption.",
|
|
742
|
+
"preconditions": [
|
|
743
|
+
"workload_owner_consulted == true",
|
|
744
|
+
"alternative_capability_path_identified == true"
|
|
745
|
+
],
|
|
746
|
+
"priority": 1,
|
|
747
|
+
"compensating_controls": [
|
|
748
|
+
"pod-security-baseline-enforced",
|
|
749
|
+
"admission-controller-blocks-privileged"
|
|
750
|
+
],
|
|
751
|
+
"estimated_time_hours": 2
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
"id": "remove-host-namespaces",
|
|
755
|
+
"description": "Remove hostPID / hostNetwork / hostIPC from workload manifests. Move legitimate host-namespace consumers to infrastructure namespaces with documented exemption.",
|
|
756
|
+
"preconditions": [
|
|
757
|
+
"workload_owner_consulted == true",
|
|
758
|
+
"host_namespace_requirement_documented_or_invalid == true"
|
|
759
|
+
],
|
|
760
|
+
"priority": 1,
|
|
761
|
+
"compensating_controls": [
|
|
762
|
+
"pod-security-baseline-enforced",
|
|
763
|
+
"namespace-isolation-tightened"
|
|
764
|
+
],
|
|
765
|
+
"estimated_time_hours": 2
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
"id": "pin-images-by-digest",
|
|
769
|
+
"description": "Replace image: name:tag with image: name@sha256:digest in k8s manifests; replace FROM name:tag with FROM name@sha256:digest in Dockerfiles. Automate digest resolution in CI.",
|
|
770
|
+
"preconditions": [
|
|
771
|
+
"ci_supports_digest_resolution == true OR manual_digest_pinning_accepted == true"
|
|
772
|
+
],
|
|
773
|
+
"priority": 2,
|
|
774
|
+
"compensating_controls": [
|
|
775
|
+
"image-signature-verified",
|
|
776
|
+
"registry-immutability-enforced"
|
|
777
|
+
],
|
|
778
|
+
"estimated_time_hours": 4
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
"id": "set-runasnonroot",
|
|
782
|
+
"description": "Add securityContext.runAsNonRoot: true and runAsUser: <non-zero> to all workload pods. Update Dockerfile USER directives accordingly.",
|
|
783
|
+
"preconditions": [
|
|
784
|
+
"image_supports_non_root == true"
|
|
785
|
+
],
|
|
786
|
+
"priority": 2,
|
|
787
|
+
"compensating_controls": [
|
|
788
|
+
"pod-security-restricted-enforced"
|
|
789
|
+
],
|
|
790
|
+
"estimated_time_hours": 2
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"id": "add-seccomp-profile",
|
|
794
|
+
"description": "Add seccompProfile.type: RuntimeDefault (or Localhost with specific profile) to pod or container securityContext. Or enable cluster-wide --seccomp-default at kubelet.",
|
|
795
|
+
"preconditions": [
|
|
796
|
+
"cluster_supports_seccomp_profile == true"
|
|
797
|
+
],
|
|
798
|
+
"priority": 2,
|
|
799
|
+
"compensating_controls": [
|
|
800
|
+
"seccomp-profile-active"
|
|
801
|
+
],
|
|
802
|
+
"estimated_time_hours": 1
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"id": "remove-docker-sock-mount",
|
|
806
|
+
"description": "Remove /var/run/docker.sock from container volumes. If workload genuinely needs to orchestrate containers, switch to (a) kubernetes API client with bounded SA permissions, (b) BuildKit / Buildah daemonless build, or (c) sysbox or kata for hardened nested runtime.",
|
|
807
|
+
"preconditions": [
|
|
808
|
+
"alternative_path_identified == true"
|
|
809
|
+
],
|
|
810
|
+
"priority": 1,
|
|
811
|
+
"compensating_controls": [
|
|
812
|
+
"bounded-rbac-applied",
|
|
813
|
+
"sysbox-or-kata-deployed"
|
|
814
|
+
],
|
|
815
|
+
"estimated_time_hours": 4
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
"id": "scope-cluster-admin-binding",
|
|
819
|
+
"description": "Replace cluster-admin ClusterRoleBinding for workload SAs with namespace-scoped Role + RoleBinding granting only required verbs/resources. Audit current usage first to enumerate required permissions.",
|
|
820
|
+
"preconditions": [
|
|
821
|
+
"sa_usage_audit_complete == true"
|
|
822
|
+
],
|
|
823
|
+
"priority": 1,
|
|
824
|
+
"compensating_controls": [
|
|
825
|
+
"scoped-role-applied",
|
|
826
|
+
"audit2rbac-output-attached"
|
|
827
|
+
],
|
|
828
|
+
"estimated_time_hours": 6
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
"id": "update-runtime-patches",
|
|
832
|
+
"description": "Confirm runc >= 1.1.12, containerd patched against current CVE lineage, kubelet at minimum supported minor. Coordinate node-by-node patch rollout.",
|
|
833
|
+
"preconditions": [
|
|
834
|
+
"maintenance_window_available == true"
|
|
835
|
+
],
|
|
836
|
+
"priority": 2,
|
|
837
|
+
"compensating_controls": [
|
|
838
|
+
"node-cordon-drain-update-uncordon-pattern"
|
|
839
|
+
],
|
|
840
|
+
"estimated_time_hours": 12
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
"id": "policy-exception",
|
|
844
|
+
"description": "For workloads requiring elevated privileges that cannot be remediated (CNI, CSI, eBPF observability, specific HPC workloads), generate auditor-ready exception with named compensating controls.",
|
|
845
|
+
"preconditions": [
|
|
846
|
+
"elevation_documented_as_required == true",
|
|
847
|
+
"infrastructure_namespace_designated == true"
|
|
848
|
+
],
|
|
849
|
+
"priority": 4,
|
|
850
|
+
"compensating_controls": [
|
|
851
|
+
"namespace-isolation-enforced",
|
|
852
|
+
"audit-log-monitoring-on-workload",
|
|
853
|
+
"runtime-policy-tightened-on-workload"
|
|
854
|
+
],
|
|
855
|
+
"estimated_time_hours": 4
|
|
856
|
+
}
|
|
857
|
+
],
|
|
858
|
+
"validation_tests": [
|
|
859
|
+
{
|
|
860
|
+
"id": "manifest-rescan-clean",
|
|
861
|
+
"test": "Re-run the manifest walk. Expect zero deterministic indicators on workload-namespace manifests.",
|
|
862
|
+
"expected_result": "All workload manifests pass; infrastructure-namespace exemptions documented.",
|
|
863
|
+
"test_type": "negative"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
"id": "admission-controller-blocks-bad-manifest",
|
|
867
|
+
"test": "Take a known-bad manifest (privileged: true in a workload namespace) and `kubectl apply --dry-run=server`. Confirm rejection.",
|
|
868
|
+
"expected_result": "Admission controller rejects the manifest with reason matching PodSecurity baseline/restricted policy.",
|
|
869
|
+
"test_type": "negative"
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
"id": "image-pins-active",
|
|
873
|
+
"test": "Grep all manifests for `image:` directives. Confirm every entry contains `@sha256:`.",
|
|
874
|
+
"expected_result": "100% of image references digest-pinned.",
|
|
875
|
+
"test_type": "functional"
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"id": "runtime-version-confirmed",
|
|
879
|
+
"test": "On each node pool: `kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{\" \"}{.status.nodeInfo.containerRuntimeVersion}{\"\\n\"}{end}'`. Cross-reference against current container runtime CVE list.",
|
|
880
|
+
"expected_result": "All nodes report runtime versions outside any active CVE's affected ranges.",
|
|
881
|
+
"test_type": "functional"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
"id": "rbac-scoped",
|
|
885
|
+
"test": "For each workload SA: `kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa>`. Confirm permissions match documented scope; no wildcard verbs.",
|
|
886
|
+
"expected_result": "SA permissions scoped; no '*' verbs on '*' resources.",
|
|
887
|
+
"test_type": "functional"
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
"id": "no-workload-regression",
|
|
891
|
+
"test": "Run the application's integration test suite. Confirm no regression from manifest hardening.",
|
|
892
|
+
"expected_result": "All integration tests pass.",
|
|
893
|
+
"test_type": "regression"
|
|
894
|
+
}
|
|
895
|
+
],
|
|
896
|
+
"residual_risk_statement": {
|
|
897
|
+
"risk": "Even with hardened manifests, runtime CVEs in runc / containerd / kubelet emerge regularly. A hardened manifest reduces blast radius of an exploited runtime CVE but does not prevent the CVE itself. Additionally, supply-chain compromise of the base image cannot be prevented by manifest posture.",
|
|
898
|
+
"why_remains": "Container security is layered defense. Hardened manifests + admission control + image signing + runtime security + node hardening together provide defense in depth; no single layer is sufficient.",
|
|
899
|
+
"acceptance_level": "manager",
|
|
900
|
+
"compensating_controls_in_place": [
|
|
901
|
+
"admission-controller-baseline",
|
|
902
|
+
"image-signatures-verified",
|
|
903
|
+
"runtime-security-monitoring",
|
|
904
|
+
"node-hardening-baselined",
|
|
905
|
+
"monthly-manifest-cadence"
|
|
906
|
+
]
|
|
907
|
+
},
|
|
908
|
+
"evidence_requirements": [
|
|
909
|
+
{
|
|
910
|
+
"evidence_type": "scan_report",
|
|
911
|
+
"description": "Full manifest walk output: per-file indicator firing counts, RWEP per workload, before-after diff.",
|
|
912
|
+
"retention_period": "1_year",
|
|
913
|
+
"framework_satisfied": [
|
|
914
|
+
"nist-800-190",
|
|
915
|
+
"nist-800-53-CM-7",
|
|
916
|
+
"iso-27001-2022-A.8.9",
|
|
917
|
+
"cmmc-cm-l2-3.4.6"
|
|
918
|
+
]
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"evidence_type": "config_diff",
|
|
922
|
+
"description": "Pre/post manifest diffs showing remediation applied; admission controller config history.",
|
|
923
|
+
"retention_period": "7_years",
|
|
924
|
+
"framework_satisfied": [
|
|
925
|
+
"nist-800-53-CM-3",
|
|
926
|
+
"iso-27001-2022-A.8.32"
|
|
927
|
+
]
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
"evidence_type": "patch_record",
|
|
931
|
+
"description": "Per-node runc / containerd / kubelet version evidence with patch deployment timestamps.",
|
|
932
|
+
"retention_period": "7_years",
|
|
933
|
+
"framework_satisfied": [
|
|
934
|
+
"nist-800-53-SI-2",
|
|
935
|
+
"iso-27001-2022-A.8.8",
|
|
936
|
+
"nis2-art21-2c"
|
|
937
|
+
]
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
"evidence_type": "attestation",
|
|
941
|
+
"description": "Signed exceptd attestation: repo identity, manifest count, indicator firing counts, runtime versions, RWEP at detection, RWEP post-remediation.",
|
|
942
|
+
"retention_period": "7_years",
|
|
943
|
+
"framework_satisfied": [
|
|
944
|
+
"nist-800-53-CA-7",
|
|
945
|
+
"iso-27001-2022-A.5.36",
|
|
946
|
+
"nis2-art21-2c"
|
|
947
|
+
]
|
|
948
|
+
}
|
|
949
|
+
],
|
|
950
|
+
"regression_trigger": [
|
|
951
|
+
{
|
|
952
|
+
"condition": "post_helm_or_kustomize_change",
|
|
953
|
+
"interval": "on_event"
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"condition": "new_cve_in_container_runtime",
|
|
957
|
+
"interval": "on_event"
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
"condition": "monthly",
|
|
961
|
+
"interval": "30d"
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
"condition": "post_admission_controller_policy_change",
|
|
965
|
+
"interval": "on_event"
|
|
966
|
+
}
|
|
967
|
+
]
|
|
968
|
+
},
|
|
969
|
+
"close": {
|
|
970
|
+
"evidence_package": {
|
|
971
|
+
"bundle_format": "csaf-2.0",
|
|
972
|
+
"contents": [
|
|
973
|
+
"scan_report",
|
|
974
|
+
"config_diff",
|
|
975
|
+
"patch_record",
|
|
976
|
+
"attestation",
|
|
977
|
+
"framework_gap_mapping",
|
|
978
|
+
"compliance_theater_verdict",
|
|
979
|
+
"residual_risk_statement"
|
|
980
|
+
],
|
|
981
|
+
"destination": "local_only",
|
|
982
|
+
"signed": true
|
|
983
|
+
},
|
|
984
|
+
"learning_loop": {
|
|
985
|
+
"enabled": true,
|
|
986
|
+
"lesson_template": {
|
|
987
|
+
"attack_vector": "Container escape via $primitive_class (privileged: true / hostPath sensitive / docker.sock mount / hostPID / runAsUser: 0 / runc-class CVE chain / unpinned image mutation between scan and deploy).",
|
|
988
|
+
"control_gap": "Container hardening controls (NIST 800-190, CIS K8s Benchmark, CM-7, A.8.9, PCI Req.2.2) accept cluster-wide attestation as evidence and do not require per-manifest analysis or per-node runtime-version evidence.",
|
|
989
|
+
"framework_gap": "Cluster-wide attestation cadence + per-workload manifest velocity creates structural drift window. Runtime CVE patch tempo + node staging window creates separate drift. Frameworks lag both by ~21 days.",
|
|
990
|
+
"new_control_requirement": "Per-manifest analysis at admission time + per-node runtime version evidence + digest-pinned images + signed image verification become required baseline controls. Continuous attestation replaces point-in-time CIS scans."
|
|
991
|
+
},
|
|
992
|
+
"feeds_back_to_skills": [
|
|
993
|
+
"container-runtime-security",
|
|
994
|
+
"supply-chain-integrity",
|
|
995
|
+
"framework-gap-analysis",
|
|
996
|
+
"compliance-theater"
|
|
997
|
+
]
|
|
998
|
+
},
|
|
999
|
+
"notification_actions": [
|
|
1000
|
+
{
|
|
1001
|
+
"obligation_ref": "EU/NIS2 Art.21 720h",
|
|
1002
|
+
"deadline": "computed_at_runtime",
|
|
1003
|
+
"recipient": "internal_legal",
|
|
1004
|
+
"evidence_attached": [
|
|
1005
|
+
"container_manifest_inventory",
|
|
1006
|
+
"runtime_cve_status",
|
|
1007
|
+
"remediation_plan"
|
|
1008
|
+
],
|
|
1009
|
+
"draft_notification": "NIS2 Art.21 vulnerability-handling record: Container posture review on ${affected_workload_count} workload(s) and ${node_count} node(s). Findings: ${finding_summary}. Remediation completed by ${remediation_date}. Container runtime versions: ${runtime_version_summary}. Evidence: manifest inventory + runtime CVE status + remediation plan attached."
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
"obligation_ref": "EU/NIS2 Art.23 24h",
|
|
1013
|
+
"deadline": "computed_at_runtime",
|
|
1014
|
+
"recipient": "internal_legal",
|
|
1015
|
+
"evidence_attached": [
|
|
1016
|
+
"affected_workload_inventory",
|
|
1017
|
+
"exploitation_status_assessment"
|
|
1018
|
+
],
|
|
1019
|
+
"draft_notification": "NIS2 Art.23 early-warning notification: Container-escape-related finding on ${affected_workload_count} workload(s). Indicator(s): ${indicator_summary}. Interim mitigation: ${interim_mitigation}. Full incident assessment within 72h per Art.23(4)."
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
"obligation_ref": "US/CMMC 2.0 CM.L2-3.4.6 720h",
|
|
1023
|
+
"deadline": "computed_at_runtime",
|
|
1024
|
+
"recipient": "internal_legal",
|
|
1025
|
+
"evidence_attached": [
|
|
1026
|
+
"baseline_configuration_document",
|
|
1027
|
+
"ssp_section_update"
|
|
1028
|
+
],
|
|
1029
|
+
"draft_notification": "CMMC CM.L2-3.4.6 update: Container baseline configuration review completed on ${affected_workload_count} workload(s). Baseline updates: ${baseline_updates}. SSP section to update: ${ssp_section}. Continuous-monitoring evidence retained."
|
|
1030
|
+
}
|
|
1031
|
+
],
|
|
1032
|
+
"exception_generation": {
|
|
1033
|
+
"trigger_condition": "elevation_documented_as_required == true OR (runtime_patch_window > obligation_window AND compensating_controls_documented == true)",
|
|
1034
|
+
"exception_template": {
|
|
1035
|
+
"scope": "Container privilege elevation OR unpatched runtime on ${affected_workload_count} workload(s) / ${node_count} node(s). Elevation reason: ${elevation_reason}.",
|
|
1036
|
+
"duration": "until_next_audit",
|
|
1037
|
+
"compensating_controls": [
|
|
1038
|
+
"namespace-isolation-enforced",
|
|
1039
|
+
"network-policy-restrictive",
|
|
1040
|
+
"runtime-security-monitoring-active",
|
|
1041
|
+
"audit-log-monitoring-on-workload",
|
|
1042
|
+
"admission-controller-exemption-documented"
|
|
1043
|
+
],
|
|
1044
|
+
"risk_acceptance_owner": "manager",
|
|
1045
|
+
"auditor_ready_language": "Pursuant to ${framework_id} ${control_id}, the organization documents a time-bound risk acceptance for container privilege configuration ${elevation_class} on ${affected_workload_count} workload(s) and/or runtime version exposure on ${node_count} node(s). Elevation reason: ${elevation_reason_narrative}. Workload(s) deployed in dedicated infrastructure namespace ${namespace_name} with restrictive network policy and runtime security monitoring. Compensating controls in place: ${compensating_controls}. Residual exposure: workload retains elevated privileges; runtime CVE patch deployment subject to node staging window. Risk accepted by ${manager_name} on ${acceptance_date}. Time-bound until ${duration_expiry} OR runtime patch completion + workload refactor completion, whichever is first. Detection coverage during the exception window: ${detection_controls}. Re-evaluation triggers: (a) workload refactor to remove elevation, (b) runtime patch completion, (c) listed expiry date, (d) any runtime security event on the workload — whichever is first."
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
"regression_schedule": {
|
|
1049
|
+
"next_run": "computed_at_runtime",
|
|
1050
|
+
"trigger": "both",
|
|
1051
|
+
"notify_on_skip": true
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
"directives": [
|
|
1056
|
+
{
|
|
1057
|
+
"id": "full-container-manifest-walk",
|
|
1058
|
+
"title": "Full container manifest walk — Dockerfile + compose + k8s + helm",
|
|
1059
|
+
"applies_to": {
|
|
1060
|
+
"always": true
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
"id": "container-escape-primitives",
|
|
1065
|
+
"title": "Targeted directive — manifests pre-positioning container escape primitives",
|
|
1066
|
+
"applies_to": {
|
|
1067
|
+
"attack_technique": "T1611"
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
"id": "supply-chain-image-provenance",
|
|
1072
|
+
"title": "Targeted directive — image provenance + digest pinning + signature verification",
|
|
1073
|
+
"applies_to": {
|
|
1074
|
+
"attack_technique": "T1525"
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
]
|
|
1078
|
+
}
|