|
1026 | 1026 | or tool.input contains "/id_ed25519" |
1027 | 1027 | or tool.input contains "oauth_creds") |
1028 | 1028 |
|
| 1029 | +# Container privilege-escalation and host-breakout patterns in container-run |
| 1030 | +# commands. The flags below are shared by docker, podman, and nerdctl, so the |
| 1031 | +# substring matches are runtime-agnostic and cover run/create/exec alike. |
| 1032 | +# Each is a documented route to host-equivalent power or container escape — |
| 1033 | +# for a coding agent, a clean way out of whatever containment Prempti is meant |
| 1034 | +# to complement, and one Prempti cannot observe after the fact (it only sees |
| 1035 | +# what the agent declares at the hook layer). Ask rather than deny: dind, |
| 1036 | +# devcontainers, and GPU/CI workloads use several of these flags legitimately. |
| 1037 | +# Helpers are split into separate macros to keep the umbrella readable and to |
| 1038 | +# avoid (A and B) terms nested inside an or-chain. |
| 1039 | + |
| 1040 | +# --privileged on run/create/exec — full host device + capability access. |
| 1041 | +- macro: is_container_privileged_flag |
| 1042 | + condition: tool.input_command contains "--privileged" |
| 1043 | + |
| 1044 | +# Added Linux capabilities (--cap-add=SYS_ADMIN, =ALL, =SYS_PTRACE, …). |
| 1045 | +- macro: is_container_cap_add |
| 1046 | + condition: tool.input_command contains "--cap-add" |
| 1047 | + |
| 1048 | +# Disabled seccomp/AppArmor/SELinux profiles or unconfined system paths. |
| 1049 | +# Deliberately matches the dangerous values, not bare --security-opt, so the |
| 1050 | +# hardening value --security-opt no-new-privileges does not trigger an ask. |
| 1051 | +- macro: is_container_security_opt_unconfined |
| 1052 | + condition: > |
| 1053 | + tool.input_command contains "seccomp=unconfined" |
| 1054 | + or tool.input_command contains "apparmor=unconfined" |
| 1055 | + or tool.input_command contains "systempaths=unconfined" |
| 1056 | + or tool.input_command contains "label=disable" |
| 1057 | + or tool.input_command contains "label:disable" |
| 1058 | +
|
| 1059 | +# Shared host namespaces, both = and space separated. --pid=host enables |
| 1060 | +# nsenter into host PID 1; --ipc/--userns/--uts/--cgroupns=host weaken |
| 1061 | +# isolation; --net/--network=host exposes host-bound services (the most |
| 1062 | +# common of these in benign dev — relax via override first if it nags). |
| 1063 | +- macro: is_container_host_namespace |
| 1064 | + condition: > |
| 1065 | + tool.input_command contains "--pid=host" |
| 1066 | + or tool.input_command contains "--pid host" |
| 1067 | + or tool.input_command contains "--ipc=host" |
| 1068 | + or tool.input_command contains "--ipc host" |
| 1069 | + or tool.input_command contains "--userns=host" |
| 1070 | + or tool.input_command contains "--userns host" |
| 1071 | + or tool.input_command contains "--uts=host" |
| 1072 | + or tool.input_command contains "--uts host" |
| 1073 | + or tool.input_command contains "--cgroupns=host" |
| 1074 | + or tool.input_command contains "--cgroupns host" |
| 1075 | + or tool.input_command contains "--network=host" |
| 1076 | + or tool.input_command contains "--network host" |
| 1077 | + or tool.input_command contains "--net=host" |
| 1078 | + or tool.input_command contains "--net host" |
| 1079 | +
|
| 1080 | +# Docker daemon socket exposed to or targeted by the command — a container |
| 1081 | +# with the socket mounted can spawn a privileged sibling and own the host; |
| 1082 | +# DOCKER_HOST= points the client at an arbitrary daemon. |
| 1083 | +- macro: is_container_docker_socket |
| 1084 | + condition: > |
| 1085 | + tool.input_command contains "docker.sock" |
| 1086 | + or tool.input_command contains "DOCKER_HOST=" |
| 1087 | +
|
| 1088 | +# Host root filesystem bind-mounted into the container (-v /:… or |
| 1089 | +# --mount source=/,…) — direct read/write of the entire host. |
| 1090 | +- macro: is_container_host_root_mount |
| 1091 | + condition: > |
| 1092 | + tool.input_command contains "-v /:" |
| 1093 | + or tool.input_command contains "--volume /:" |
| 1094 | + or tool.input_command contains "source=/," |
| 1095 | + or tool.input_command contains "src=/," |
| 1096 | +
|
| 1097 | +# Raw host device passthrough (--device=/dev/…). Covers /dev/mem, raw disks, |
| 1098 | +# etc.; also matches benign GPU/USB passthrough, hence ask not deny. |
| 1099 | +- macro: is_container_device_passthrough |
| 1100 | + condition: > |
| 1101 | + tool.input_command contains "--device=/dev/" |
| 1102 | + or tool.input_command contains "--device /dev/" |
| 1103 | +
|
| 1104 | +# nsenter into the host's namespaces via init (PID 1) — the standard breakout |
| 1105 | +# once --pid=host or the docker socket is available. Gated on targeting PID 1 |
| 1106 | +# so the word nsenter alone in prose does not match. |
| 1107 | +- macro: is_nsenter_host |
| 1108 | + condition: > |
| 1109 | + tool.input_command contains "nsenter" |
| 1110 | + and (tool.input_command contains "-t 1" |
| 1111 | + or tool.input_command contains "--target 1" |
| 1112 | + or tool.input_command contains "-t1") |
| 1113 | +
|
| 1114 | +- macro: is_container_privilege_escalation |
| 1115 | + condition: > |
| 1116 | + is_container_privileged_flag |
| 1117 | + or is_container_cap_add |
| 1118 | + or is_container_security_opt_unconfined |
| 1119 | + or is_container_host_namespace |
| 1120 | + or is_container_docker_socket |
| 1121 | + or is_container_host_root_mount |
| 1122 | + or is_container_device_passthrough |
| 1123 | + or is_nsenter_host |
| 1124 | +
|
1029 | 1125 | # Rules |
1030 | 1126 |
|
1031 | 1127 | - rule: Deny credential file access via Bash |
|
1087 | 1183 | source: coding_agent |
1088 | 1184 | tags: [coding_agent_ask] |
1089 | 1185 |
|
| 1186 | +- rule: Ask before container privilege escalation or host breakout |
| 1187 | + desc: > |
| 1188 | + Requires user confirmation before Bash commands that grant a container |
| 1189 | + host-equivalent power or break out of container isolation: --privileged, |
| 1190 | + --cap-add, disabled seccomp/AppArmor/SELinux profiles (--security-opt |
| 1191 | + *=unconfined or label=disable), shared host namespaces (--pid / --ipc / |
| 1192 | + --userns / --uts / --cgroupns / --network = host), Docker daemon socket |
| 1193 | + exposure (docker.sock, DOCKER_HOST=), host root bind mounts (-v /: or |
| 1194 | + --mount source=/), raw device passthrough (--device=/dev/), and nsenter |
| 1195 | + into host PID 1. The flags are shared by docker, podman, and nerdctl. |
| 1196 | + Prempti runs in user space and only sees what the agent declares at the |
| 1197 | + hook layer, so a privileged container is a route to host access it cannot |
| 1198 | + observe afterward. Asks rather than denies because dind, devcontainers, |
| 1199 | + and GPU/CI workloads use these flags legitimately; --network=host is the |
| 1200 | + most common of these in benign dev and is the first to relax via override. |
| 1201 | + condition: is_bash and is_container_privilege_escalation |
| 1202 | + output: > |
| 1203 | + Falco requires confirmation before %agent.name runs a container command that escalates privileges or breaks out of container isolation (%tool.input_command) |
| 1204 | + priority: WARNING |
| 1205 | + source: coding_agent |
| 1206 | + tags: [coding_agent_ask, mitre_t1611_escape_to_host, mitre_t1610_deploy_container] |
| 1207 | + |
1090 | 1208 | - rule: Deny pipe to shell interpreter |
1091 | 1209 | desc: > |
1092 | 1210 | Blocks Bash commands that pipe network-fetched or generated content directly |
|
0 commit comments