/* © AngelaMos | 2026 container.go ContainerAnalyzer inspects live running containers via the Docker API Fetches the full inspect response for each container and checks CIS Section 5 controls: privileged mode, dangerous capabilities, Docker socket and sensitive path mounts, host network/PID/IPC/UTS namespace sharing, seccomp and AppArmor profiles, no-new-privileges flag, and memory/CPU/PIDs resource limits. Key exports: ContainerAnalyzer - implements Analyzer for live containers NewContainerAnalyzer - constructor taking a docker.Client Connects to: analyzer.go - implements Analyzer interface, uses Category constants docker/client.go - lists and inspects containers rules/capabilities.go - classifies cap_add entries rules/paths.go - identifies sensitive and socket mounts benchmark/controls.go - fetches CIS control metadata for each finding finding.go - creates findings with CISControl references */ package analyzer import ( "context" "strings" "github.com/CarterPerez-dev/docksec/internal/benchmark" "github.com/CarterPerez-dev/docksec/internal/docker" "github.com/CarterPerez-dev/docksec/internal/finding" "github.com/CarterPerez-dev/docksec/internal/rules" "github.com/docker/docker/api/types/container" ) type ContainerAnalyzer struct { client *docker.Client } func NewContainerAnalyzer(client *docker.Client) *ContainerAnalyzer { return &ContainerAnalyzer{client: client} } func (a *ContainerAnalyzer) Name() string { return "container" } func (a *ContainerAnalyzer) Analyze( ctx context.Context, ) (finding.Collection, error) { containers, err := a.client.ListContainers(ctx, true) if err != nil { return nil, err } var findings finding.Collection for _, c := range containers { info, err := a.client.InspectContainer(ctx, c.ID) if err != nil { continue } findings = append(findings, a.analyzeContainer(info)...) } return findings, nil } func (a *ContainerAnalyzer) analyzeContainer( info container.InspectResponse, ) finding.Collection { var findings finding.Collection target := finding.Target{ Type: finding.TargetContainer, Name: strings.TrimPrefix(info.Name, "/"), ID: info.ID, } if info.HostConfig == nil { return findings } findings = append(findings, a.checkPrivileged(target, info)...) findings = append(findings, a.checkCapabilities(target, info)...) findings = append(findings, a.checkMounts(target, info)...) findings = append(findings, a.checkNamespaces(target, info)...) findings = append(findings, a.checkSecurityOptions(target, info)...) findings = append(findings, a.checkResourceLimits(target, info)...) findings = append(findings, a.checkReadonlyRootfs(target, info)...) return findings } func (a *ContainerAnalyzer) checkPrivileged( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection if info.HostConfig.Privileged { control, _ := benchmark.Get("5.4") f := finding.New("CIS-5.4", control.Title, finding.SeverityCritical, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } return findings } func (a *ContainerAnalyzer) checkCapabilities( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection for _, cap := range info.HostConfig.CapAdd { capName := strings.ToUpper(string(cap)) capInfo, exists := rules.GetCapabilityInfo(capName) if !exists { continue } if capInfo.Severity >= finding.SeverityHigh { control, _ := benchmark.Get("5.3") title := "Dangerous capability added: " + capName if capInfo.Severity == finding.SeverityCritical { title = "Critical capability added: " + capName } f := finding.New("CIS-5.3", title, capInfo.Severity, target). WithDescription(capInfo.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } } return findings } func (a *ContainerAnalyzer) checkMounts( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection for _, mount := range info.Mounts { source := mount.Source if rules.IsDockerSocket(source) { control, _ := benchmark.Get("5.31") pathInfo, _ := rules.GetPathInfo(source) f := finding.New("CIS-5.31", control.Title, finding.SeverityCritical, target). WithDescription(pathInfo.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) continue } if rules.IsSensitivePath(source) { control, _ := benchmark.Get("5.5") pathInfo, _ := rules.GetPathInfo(source) severity := rules.GetPathSeverity(source) description := control.Description if pathInfo.Description != "" { description = pathInfo.Description } f := finding.New("CIS-5.5", "Sensitive host path mounted: "+source, severity, target). WithDescription(description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } } return findings } func (a *ContainerAnalyzer) checkNamespaces( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection if info.HostConfig.NetworkMode == "host" { control, _ := benchmark.Get("5.9") f := finding.New("CIS-5.9", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if info.HostConfig.PidMode == "host" { control, _ := benchmark.Get("5.15") f := finding.New("CIS-5.15", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if info.HostConfig.IpcMode == "host" { control, _ := benchmark.Get("5.16") f := finding.New("CIS-5.16", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if info.HostConfig.UTSMode == "host" { control, _ := benchmark.Get("5.20") f := finding.New("CIS-5.20", control.Title, finding.SeverityMedium, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } return findings } func (a *ContainerAnalyzer) checkSecurityOptions( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection hasAppArmor := false hasSeccomp := false hasNoNewPrivileges := false seccompDisabled := false for _, opt := range info.HostConfig.SecurityOpt { if strings.HasPrefix(opt, "apparmor=") { hasAppArmor = true } if strings.HasPrefix(opt, "seccomp=") { hasSeccomp = true if opt == "seccomp=unconfined" { seccompDisabled = true } } if opt == "no-new-privileges" || opt == "no-new-privileges:true" { hasNoNewPrivileges = true } } if !hasAppArmor && !info.HostConfig.Privileged { control, _ := benchmark.Get("5.1") f := finding.New("CIS-5.1", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if seccompDisabled { control, _ := benchmark.Get("5.21") f := finding.New("CIS-5.21", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if !hasSeccomp && !info.HostConfig.Privileged { control, _ := benchmark.Get("5.21") f := finding.New("CIS-5.21", "No seccomp profile set", finding.SeverityMedium, target). WithDescription("Container is running without an explicit seccomp profile. While Docker applies a default profile, it's recommended to explicitly set one."). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if !hasNoNewPrivileges { control, _ := benchmark.Get("5.25") f := finding.New("CIS-5.25", control.Title, finding.SeverityHigh, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } return findings } func (a *ContainerAnalyzer) checkResourceLimits( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection if info.HostConfig.Memory == 0 { control, _ := benchmark.Get("5.10") f := finding.New("CIS-5.10", control.Title, finding.SeverityMedium, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if info.HostConfig.NanoCPUs == 0 && info.HostConfig.CPUShares == 0 && info.HostConfig.CPUPeriod == 0 { control, _ := benchmark.Get("5.11") f := finding.New("CIS-5.11", control.Title, finding.SeverityMedium, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } if info.HostConfig.PidsLimit == nil || *info.HostConfig.PidsLimit == 0 || *info.HostConfig.PidsLimit == -1 { control, _ := benchmark.Get("5.28") f := finding.New("CIS-5.28", control.Title, finding.SeverityMedium, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } return findings } func (a *ContainerAnalyzer) checkReadonlyRootfs( target finding.Target, info container.InspectResponse, ) finding.Collection { var findings finding.Collection if !info.HostConfig.ReadonlyRootfs { control, _ := benchmark.Get("5.12") f := finding.New("CIS-5.12", control.Title, finding.SeverityMedium, target). WithDescription(control.Description). WithCategory(string(CategoryContainerRuntime)). WithRemediation(control.Remediation). WithReferences(control.References...). WithCISControl(control.ToCISControl()) findings = append(findings, f) } return findings }