feat: add Docker host network mode support and improve agent terminal execution

- Add host network mode support in Docker client (DOCKER_NETWORK=host)
- Update documentation for network modes (bridge vs host)
- Enhance OOB port allocation guidance with mandatory directives
- Improve terminal command execution descriptions (detach, timeout)
- Fix MSF workflow issues: add process isolation rules and RPC daemon patterns
- Add terminal execution mechanics to adviser prompts for better monitoring
- Update installer locale with host network mode explanation

Fixes agent issues with msfconsole hanging, port conflicts, and process isolation.
This commit is contained in:
Dmitry Ng
2026-03-29 15:53:30 +03:00
parent b90ea4711e
commit c8cd0e68f9
18 changed files with 364 additions and 162 deletions
+20 -6
View File
@@ -1505,7 +1505,7 @@ Configuration combines based on scenario: enable both capabilities for full pent
ToolsDockerSocket = "Docker Socket"
ToolsDockerSocketDesc = "Path to Docker socket on host filesystem"
ToolsDockerNetwork = "Docker Network"
ToolsDockerNetworkDesc = "Custom network name for worker containers"
ToolsDockerNetworkDesc = "Custom network name for worker containers, or 'host' for direct host network access"
ToolsDockerPublicIP = "Public IP Address"
ToolsDockerPublicIPDesc = "Public IP for reverse connections in OOB attacks"
@@ -1549,13 +1549,27 @@ When using DinD, use the path to the Docker socket file of the DinD container wh
Example: /var/run/docker.sock`
ToolsDockerNetworkHelp = `Custom Docker Network provides isolation for worker containers. Allows fine-grained firewall rules and network policies.
ToolsDockerNetworkHelp = `Docker Network controls network isolation mode for worker containers:
Useful for:
• Isolating worker traffic
Custom network configurations
Bridge Mode (custom network name):
• Isolated communication between containers
Port forwarding from container to host
• Enhanced security boundaries
• Network-based monitoring`
• Network-based monitoring and filtering
• Recommended for most use cases
Host Mode (value: 'host'):
• Direct access to host network interfaces
• No port forwarding - ports bind directly to host
• Required for raw packet manipulation
• Advanced network testing capabilities
• Lower isolation - use with caution
Examples:
• 'pentagi-network' - creates isolated bridge network
• 'host' - enables direct host network access
Security Note: Host network mode reduces container isolation. Only use when necessary for advanced penetration testing tasks requiring direct network stack access.`
ToolsDockerPublicIPHelp = `Public IP Address enables out-of-band (OOB) attack techniques by providing workers with a reachable address for reverse connections.
+31 -13
View File
@@ -59,7 +59,7 @@ This document serves as a comprehensive guide to the configuration system in Pen
- [Perplexity Search](#perplexity-search)
- [Searxng Search](#searxng-search)
- [Usage Details](#usage-details-10)
- [Proxy Settings](#proxy-settings)
- [Network and Proxy Settings](#network-and-proxy-settings)
- [Usage Details](#usage-details-11)
- [Graphiti Knowledge Graph Settings](#graphiti-knowledge-graph-settings)
- [Usage Details](#usage-details-12)
@@ -199,16 +199,16 @@ if cfg.LicenseKey != "" {
These settings control how PentAGI interacts with Docker, which is used for terminal isolation and executing commands in a controlled environment. They're crucial for the security and functionality of tool execution.
| Option | Environment Variable | Default Value | Description |
| ---------------------------- | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------ |
| DockerInside | `DOCKER_INSIDE` | `false` | Set to `true` if PentAGI runs inside Docker and needs to access the host Docker daemon. |
| Option | Environment Variable | Default Value | Description |
| ---------------------------- | ---------------------------------- | ---------------------- | ----------- |
| DockerInside | `DOCKER_INSIDE` | `false` | Set to `true` if PentAGI runs inside Docker and needs to access the host Docker daemon. |
| DockerNetAdmin | `DOCKER_NET_ADMIN` | `false` | Set to `true` to grant the primary container NET_ADMIN capability for advanced networking. |
| DockerSocket | `DOCKER_SOCKET` | *(none)* | Path to Docker socket for container management |
| DockerNetwork | `DOCKER_NETWORK` | *(none)* | Docker network name for container communication |
| DockerPublicIP | `DOCKER_PUBLIC_IP` | `0.0.0.0` | Public IP address for Docker containers' port bindings |
| DockerWorkDir | `DOCKER_WORK_DIR` | *(none)* | Custom working directory inside Docker containers |
| DockerDefaultImage | `DOCKER_DEFAULT_IMAGE` | `debian:latest` | Default Docker image for containers when specific images fail |
| DockerDefaultImageForPentest | `DOCKER_DEFAULT_IMAGE_FOR_PENTEST` | `vxcontrol/kali-linux` | Default Docker image for penetration testing tasks |
| DockerSocket | `DOCKER_SOCKET` | *(none)* | Path to Docker socket for container management |
| DockerNetwork | `DOCKER_NETWORK` | *(none)* | Docker network name for bridge mode, or `host` for host network mode. See network modes below. |
| DockerPublicIP | `DOCKER_PUBLIC_IP` | `0.0.0.0` | Public IP address for Docker containers' port bindings (bridge mode only) |
| DockerWorkDir | `DOCKER_WORK_DIR` | *(none)* | Custom working directory inside Docker containers |
| DockerDefaultImage | `DOCKER_DEFAULT_IMAGE` | `debian:latest` | Default Docker image for containers when specific images fail |
| DockerDefaultImageForPentest | `DOCKER_DEFAULT_IMAGE_FOR_PENTEST` | `vxcontrol/kali-linux` | Default Docker image for penetration testing tasks |
### Usage Details
@@ -227,17 +227,35 @@ The Docker settings are primarily used in `pkg/docker/client.go` which implement
}
```
- **DockerNetwork**: Sets the network that containers should join, enabling container-to-container communication:
- **DockerNetwork**: Controls the network isolation mode for containers. Supports two modes:
**Bridge Mode** (custom network name, e.g., `pentagi-network`):
- Containers run in an isolated bridge network
- Port forwarding maps container ports to host ports
- Enhanced security through network isolation
- Recommended for most deployments
**Host Mode** (special value: `host`):
- Containers share the host's network stack directly
- No port forwarding - services bind directly to host interfaces
- Required for advanced network testing (raw packets, custom protocols)
- Reduced isolation - use with caution
```go
network := cfg.DockerNetwork
// Used when creating network configuration
if dc.network != "" {
// Host network mode
if dc.network == "host" {
hostConfig.NetworkMode = container.NetworkMode("host")
// No port bindings needed
} else if dc.network != "" {
// Bridge mode with custom network
networkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
dc.network: {},
},
}
// Port bindings are configured
}
```
+30 -15
View File
@@ -92,8 +92,8 @@ The Docker client is configured through several environment variables defined in
| `DOCKER_INSIDE` | `false` | Whether PentAGI communicates with host Docker daemon from containers |
| `DOCKER_NET_ADMIN` | `false` | Whether PentAGI grants the primary container NET_ADMIN capability for advanced networking. |
| `DOCKER_SOCKET` | `/var/run/docker.sock` | Path to Docker socket on host |
| `DOCKER_NETWORK` | | Docker network for container communication |
| `DOCKER_PUBLIC_IP` | `0.0.0.0` | Public IP for port binding |
| `DOCKER_NETWORK` | | Docker network for container communication (bridge mode) or `host` for host network mode |
| `DOCKER_PUBLIC_IP` | `0.0.0.0` | Public IP for port binding (bridge mode only) |
| `DOCKER_WORK_DIR` | | Custom work directory path on host |
| `DOCKER_DEFAULT_IMAGE` | `debian:latest` | Fallback image if AI-selected image fails |
| `DOCKER_DEFAULT_IMAGE_FOR_PENTEST` | `vxcontrol/kali-linux` | Default Docker image for penetration testing tasks |
@@ -173,10 +173,25 @@ PentAGI supports running inside Docker containers while still managing other con
### Network Configuration
When `DOCKER_NETWORK` is specified, all containers are automatically connected to this network, enabling:
- Isolated communication between PentAGI components
- Controlled access to external networks
- Service discovery within the PentAGI ecosystem
PentAGI supports two network modes for container isolation:
#### Bridge Network Mode (Default)
When `DOCKER_NETWORK` is set to a custom network name (e.g., `pentagi-network`), containers are connected to an isolated bridge network:
- **Isolated Communication**: Containers communicate only within the defined network
- **Port Mapping**: Container ports are mapped to host ports for external access
- **Service Discovery**: Enables internal DNS-based service discovery
- **Enhanced Security**: Network-level isolation from other containers
#### Host Network Mode
When `DOCKER_NETWORK` is set to the special value `host`, containers use the host's network stack directly:
- **Direct Network Access**: Container shares the host's network interfaces
- **No Port Mapping**: Ports are directly accessible on host interfaces (no NAT)
- **Performance**: Eliminates network virtualization overhead
- **Use Cases**: Advanced network testing, raw packet manipulation, network monitoring
**Security Consideration**: Host network mode reduces isolation. Use only when necessary for penetration testing tasks requiring direct host network access.
## Core Interfaces
@@ -187,10 +202,10 @@ The main interface defines all Docker operations available to PentAGI components
```go
type DockerClient interface {
// Container lifecycle management
SpawnContainer(ctx context.Context, containerName string, containerType database.ContainerType,
RunContainer(ctx context.Context, containerName string, containerType database.ContainerType,
flowID int64, config *container.Config, hostConfig *container.HostConfig) (database.Container, error)
StopContainer(ctx context.Context, containerID string, dbID int64) error
DeleteContainer(ctx context.Context, containerID string, dbID int64) error
RemoveContainer(ctx context.Context, containerID string, dbID int64) error
IsContainerRunning(ctx context.Context, containerID string) (bool, error)
// Command execution
@@ -229,7 +244,7 @@ type dockerClient struct {
### Container Creation Process
The `SpawnContainer` method handles the complete container creation workflow:
The `RunContainer` method handles the complete container creation workflow:
1. **Preparation**:
- Creates flow-specific work directory
@@ -253,9 +268,9 @@ The `SpawnContainer` method handles the complete container creation workflow:
- Optionally mounts Docker socket for Docker-in-Docker
5. **Network and Ports**:
- Assigns flow-specific ports using deterministic algorithm
- Connects to specified Docker network if configured
- Binds ports to public IP
- **Bridge Mode**: Assigns flow-specific ports using deterministic algorithm, binds to public IP
- **Host Mode** (`DOCKER_NETWORK=host`): Uses host network stack, skips port bindings
- Connects to specified Docker network (unless host mode)
6. **Container Startup**:
- Creates container with all configurations
@@ -451,7 +466,7 @@ if err != nil {
// Create container for a flow
containerName := docker.PrimaryTerminalName(flowID)
container, err := dockerClient.SpawnContainer(
container, err := dockerClient.RunContainer(
ctx,
containerName,
database.ContainerTypePrimary,
@@ -513,7 +528,7 @@ isRunning, err := dockerClient.IsContainerRunning(ctx, containerID)
err = dockerClient.StopContainer(ctx, containerID, dbID)
// Remove container and volumes
err = dockerClient.DeleteContainer(ctx, containerID, dbID)
err = dockerClient.RemoveContainer(ctx, containerID, dbID)
// Global cleanup (usually called on startup)
err = dockerClient.Cleanup(ctx)
@@ -523,7 +538,7 @@ err = dockerClient.Cleanup(ctx)
```go
// The client implements comprehensive error handling
container, err := dockerClient.SpawnContainer(ctx, name, containerType, flowID, config, hostConfig)
container, err := dockerClient.RunContainer(ctx, name, containerType, flowID, config, hostConfig)
if err != nil {
// Errors include:
// - Image pull failures (handled with fallback)
+58 -45
View File
@@ -53,17 +53,17 @@ type dockerClient struct {
}
type DockerClient interface {
LaunchContainer(ctx context.Context, containerName string, containerType database.ContainerType,
RunContainer(ctx context.Context, containerName string, containerType database.ContainerType,
flowID int64, config *container.Config, hostConfig *container.HostConfig) (database.Container, error)
HaltContainer(ctx context.Context, containerID string, dbID int64) error
PurgeContainer(ctx context.Context, containerID string, dbID int64) error
VerifyContainerRuntime(ctx context.Context, containerID string) (bool, error)
StopContainer(ctx context.Context, containerID string, dbID int64) error
RemoveContainer(ctx context.Context, containerID string, dbID int64) error
IsContainerRunning(ctx context.Context, containerID string) (bool, error)
ContainerExecCreate(ctx context.Context, container string, config container.ExecOptions) (container.ExecCreateResponse, error)
ContainerExecAttach(ctx context.Context, execID string, config container.ExecAttachOptions) (types.HijackedResponse, error)
ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
CopyToContainer(ctx context.Context, containerID string, dstPath string, content io.Reader, options container.CopyToContainerOptions) error
CopyFromContainer(ctx context.Context, containerID string, srcPath string) (io.ReadCloser, container.PathStat, error)
CleanupAllResources(ctx context.Context) error
Cleanup(ctx context.Context) error
GetDefaultImage() string
}
@@ -151,7 +151,7 @@ func NewDockerClient(ctx context.Context, db database.Querier, cfg *config.Confi
}, nil
}
func (dc *dockerClient) LaunchContainer(
func (dc *dockerClient) RunContainer(
ctx context.Context,
containerName string,
containerType database.ContainerType,
@@ -181,7 +181,7 @@ func (dc *dockerClient) LaunchContainer(
"work_dir": workDir,
"host_dir": hostDir,
})
logger.Info("spawning container")
logger.Info("running container")
dbContainer, err := dc.db.CreateContainer(ctx, database.CreateContainerParams{
Type: containerType,
@@ -275,29 +275,38 @@ func (dc *dockerClient) LaunchContainer(
},
}
if hostConfig.PortBindings == nil {
hostConfig.PortBindings = nat.PortMap{}
}
if config.ExposedPorts == nil {
config.ExposedPorts = nat.PortSet{}
}
for _, port := range GetPrimaryContainerPorts(flowID) {
natPort := nat.Port(fmt.Sprintf("%d/tcp", port))
hostConfig.PortBindings[natPort] = []nat.PortBinding{
{
HostIP: dc.publicIP,
HostPort: fmt.Sprintf("%d", port),
},
}
config.ExposedPorts[natPort] = struct{}{}
}
// Configure network mode and port bindings
var networkingConfig *network.NetworkingConfig
if dc.network != "" {
networkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
dc.network: {},
},
if dc.network == "host" {
// Host network mode: container uses host network stack directly
// No port bindings needed as container has direct access to host interfaces
hostConfig.NetworkMode = container.NetworkMode("host")
logger.Debug("using host network mode - container will have direct access to host network interfaces")
} else {
// Bridge network mode: configure port bindings and custom network
if hostConfig.PortBindings == nil {
hostConfig.PortBindings = nat.PortMap{}
}
if config.ExposedPorts == nil {
config.ExposedPorts = nat.PortSet{}
}
for _, port := range GetPrimaryContainerPorts(flowID) {
natPort := nat.Port(fmt.Sprintf("%d/tcp", port))
hostConfig.PortBindings[natPort] = []nat.PortBinding{
{
HostIP: dc.publicIP,
HostPort: fmt.Sprintf("%d", port),
},
}
config.ExposedPorts[natPort] = struct{}{}
}
if dc.network != "" {
networkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
dc.network: {},
},
}
}
}
@@ -356,7 +365,7 @@ func (dc *dockerClient) LaunchContainer(
return dbContainer, nil
}
func (dc *dockerClient) HaltContainer(ctx context.Context, containerID string, dbID int64) error {
func (dc *dockerClient) StopContainer(ctx context.Context, containerID string, dbID int64) error {
logger := dc.logger.WithContext(ctx).WithField("local_id", containerID)
logger.Info("initiating container shutdown sequence")
@@ -369,23 +378,25 @@ func (dc *dockerClient) HaltContainer(ctx context.Context, containerID string, d
}
}
if _, err := dc.db.UpdateContainerStatus(ctx, database.UpdateContainerStatusParams{
_, err := dc.db.UpdateContainerStatus(ctx, database.UpdateContainerStatusParams{
Status: database.ContainerStatusStopped,
ID: dbID,
}); err != nil {
})
if err != nil {
return fmt.Errorf("database status update failed during container stop: %w", err)
}
logger.Info("container shutdown completed successfully")
return nil
}
func (dc *dockerClient) PurgeContainer(ctx context.Context, containerID string, dbID int64) error {
func (dc *dockerClient) RemoveContainer(ctx context.Context, containerID string, dbID int64) error {
logger := dc.logger.WithContext(ctx).WithField("local_id", containerID)
logger.Info("purging container and associated resources")
logger.Info("removing container and associated resources")
if err := dc.HaltContainer(ctx, containerID, dbID); err != nil {
return fmt.Errorf("failed to halt container before purge: %w", err)
if err := dc.StopContainer(ctx, containerID, dbID); err != nil {
return fmt.Errorf("failed to stop container: %w", err)
}
options := container.RemoveOptions{
@@ -413,7 +424,7 @@ func (dc *dockerClient) PurgeContainer(ctx context.Context, containerID string,
return nil
}
func (dc *dockerClient) CleanupAllResources(ctx context.Context) error {
func (dc *dockerClient) Cleanup(ctx context.Context) error {
logger := dc.logger.WithContext(ctx).WithField("docker", "cleanup")
logger.Info("cleaning up containers and making all flows finished...")
@@ -437,12 +448,12 @@ func (dc *dockerClient) CleanupAllResources(ctx context.Context) error {
}
var wg sync.WaitGroup
deleteContainer := func(containerID string, dbID int64) {
removeContainer := func(containerID string, dbID int64) {
defer wg.Done()
logger := logger.WithField("local_id", containerID)
if err := dc.PurgeContainer(ctx, containerID, dbID); err != nil {
logger.WithError(err).Errorf("failed to delete container")
if err := dc.RemoveContainer(ctx, containerID, dbID); err != nil {
logger.WithError(err).Errorf("failed to remove container")
}
_, err := dc.db.UpdateContainerStatus(ctx, database.UpdateContainerStatusParams{
@@ -492,7 +503,7 @@ func (dc *dockerClient) CleanupAllResources(ctx context.Context) error {
switch container.Status {
case database.ContainerStatusStarting, database.ContainerStatusRunning:
wg.Add(1)
go deleteContainer(container.LocalID.String, container.ID)
go removeContainer(container.LocalID.String, container.ID)
}
}
}
@@ -504,7 +515,7 @@ func (dc *dockerClient) CleanupAllResources(ctx context.Context) error {
return nil
}
func (dc *dockerClient) VerifyContainerRuntime(ctx context.Context, containerID string) (bool, error) {
func (dc *dockerClient) IsContainerRunning(ctx context.Context, containerID string) (bool, error) {
inspection, err := dc.client.ContainerInspect(ctx, containerID)
if err != nil {
return false, fmt.Errorf("container inspection failed: %w", err)
@@ -582,7 +593,7 @@ func (dc *dockerClient) pullImage(ctx context.Context, imageName string) error {
pullStream, err := dc.client.ImagePull(ctx, imageName, image.PullOptions{})
if err != nil {
return fmt.Errorf("image pull request failed: %w", err)
return fmt.Errorf("failed to pull image: %w", err)
}
defer pullStream.Close()
@@ -591,7 +602,8 @@ func (dc *dockerClient) pullImage(ctx context.Context, imageName string) error {
return fmt.Errorf("image download stream processing failed: %w", err)
}
dc.logger.WithContext(ctx).WithField("image", imageName).Debug("image download completed")
dc.logger.WithContext(ctx).WithField("image", imageName).Debug("image pull completed")
return nil
}
@@ -705,8 +717,9 @@ func getHostDataDir(ctx context.Context, cli *client.Client, dataDir, workDir st
// ensureDockerNetwork verifies that a docker network with the given name exists;
// if it does not, it attempts to create it.
// Special case: "host" network mode is built-in and doesn't need creation.
func ensureDockerNetwork(ctx context.Context, cli *client.Client, name string) error {
if name == "" {
if name == "" || name == "host" {
return nil
}
+1 -1
View File
@@ -42,7 +42,7 @@ func (r *mutationResolver) CreateFlow(ctx context.Context, modelProvider string,
r.Logger.WithFields(logrus.Fields{
"uid": uid,
"provider": modelProvider,
"input": input,
"input": input[:min(len(input), 1000)],
}).Debug("create flow")
if modelProvider == "" {
+52 -14
View File
@@ -786,24 +786,62 @@ func (fp *flowProvider) getContainerPortsDescription() string {
var buffer strings.Builder
buffer.WriteString("**OOB Attack Infrastructure:**\n\n")
buffer.WriteString("This container has TCP ports bound for receiving out-of-band (OOB) callbacks:\n\n")
for _, port := range ports {
buffer.WriteString(fmt.Sprintf("- Port %d/tcp (container) → %s:%d (external)\n", port, fp.publicIP, port))
}
// Host network mode: container has direct access to host network interfaces
if fp.dockerNetwork == "host" {
buffer.WriteString("This container uses **host network mode** with direct access to host network interfaces.\n\n")
buffer.WriteString("**MANDATORY PORTS - YOU MUST USE ONLY THESE:**\n\n")
buffer.WriteString("\n**Usage for OOB Attacks:**\n")
for _, port := range ports {
buffer.WriteString(fmt.Sprintf("- Port %d/tcp (REQUIRED)\n", port))
}
if fp.publicIP == "0.0.0.0" {
buffer.WriteString("The bind IP is 0.0.0.0 (all interfaces). To receive external callbacks:\n")
buffer.WriteString("1. Discover your public IP: `curl -s https://api.ipify.org` or `curl -s ipinfo.io/ip`\n")
buffer.WriteString("2. Use discovered IP in exploit payloads for callbacks\n")
buffer.WriteString("3. Listen on container ports (shown above) to receive connections\n\n")
buffer.WriteString("**Important:** Check Task.Input - user may have specified the public IP to use.\n")
buffer.WriteString("\n**Network Access:**\n")
buffer.WriteString("- Direct access to all host network interfaces\n")
buffer.WriteString("- No port forwarding - services bind directly to host ports\n")
buffer.WriteString("- **CRITICAL**: DO NOT use any other ports - this may interfere with other running applications on the host system\n")
buffer.WriteString("- Using non-allocated ports could cause conflicts with system services and impact host stability\n")
buffer.WriteString("- All host network interfaces are accessible for binding\n\n")
buffer.WriteString("**Usage for OOB Attacks:**\n")
if fp.publicIP == "0.0.0.0" {
buffer.WriteString("To determine the public IP for callbacks:\n")
buffer.WriteString("1. Discover your public IP: `curl -s https://api.ipify.org` or `curl -s ipinfo.io/ip`\n")
buffer.WriteString("2. Use discovered IP in exploit payloads for callbacks\n")
buffer.WriteString("3. Listen on allocated ports (shown above) to receive connections\n\n")
buffer.WriteString("**Important:** Check Task.Input - user may have specified the public IP to use.\n")
} else {
buffer.WriteString(fmt.Sprintf("Your external IP is: **%s**\n\n", fp.publicIP))
buffer.WriteString("Use this IP in exploit payloads requiring callbacks (DNS exfiltration, reverse shells, XXE OOB, SSRF verification, etc.)\n")
buffer.WriteString("Listen on the allocated ports above to receive incoming connections.\n")
}
} else {
buffer.WriteString(fmt.Sprintf("Your external IP is: %s\n", fp.publicIP))
buffer.WriteString("Use this IP in exploit payloads requiring callbacks (DNS exfiltration, reverse shells, XXE OOB, SSRF verification, etc.)\n")
buffer.WriteString("Listen on the container ports above to receive incoming connections.\n")
// Bridge network mode: traditional port forwarding
buffer.WriteString("**MANDATORY FORWARDED PORTS - YOU MUST USE ONLY THESE:**\n\n")
for _, port := range ports {
buffer.WriteString(fmt.Sprintf("- Port %d/tcp (container) → %s:%d (external)\n", port, fp.publicIP, port))
}
buffer.WriteString("\n**Port Usage Rules:**\n")
buffer.WriteString("- **YOU MUST use ONLY the ports listed above** for all listeners and reverse connections\n")
buffer.WriteString("- **CRITICAL**: Any reverse connections (shells, callbacks) will FAIL on other ports - only allocated ports are forwarded\n")
buffer.WriteString("- Standard ports like 4444, 8080, 9001 are NOT forwarded and will NOT work\n")
buffer.WriteString(fmt.Sprintf("- Example: For Metasploit reverse shell, use LPORT=%d (not 4444)\n\n", ports[0]))
buffer.WriteString("**Usage for OOB Attacks:**\n")
if fp.publicIP == "0.0.0.0" {
buffer.WriteString("The bind IP is 0.0.0.0 (all interfaces). To receive external callbacks:\n")
buffer.WriteString("1. Discover your public IP: `curl -s https://api.ipify.org` or `curl -s ipinfo.io/ip`\n")
buffer.WriteString("2. Use discovered IP in exploit payloads for callbacks\n")
buffer.WriteString("3. Listen on container ports (shown above) to receive connections\n\n")
buffer.WriteString("**Important:** Check Task.Input - user may have specified the public IP to use.\n")
} else {
buffer.WriteString(fmt.Sprintf("Your external IP is: %s\n", fp.publicIP))
buffer.WriteString("Use this IP in exploit payloads requiring callbacks (DNS exfiltration, reverse shells, XXE OOB, SSRF verification, etc.)\n")
buffer.WriteString("Listen on the container ports above to receive incoming connections.\n")
}
}
return buffer.String()
+4 -3
View File
@@ -26,7 +26,7 @@ import (
"github.com/vxcontrol/langchaingo/llms/streaming"
)
const ToolPlaceholder = "Always use your function calling functionality, instead of returning a text result."
const ToolPlaceholder = "Execute operations via function invocation - textual responses are not acceptable for task completion."
const TasksNumberLimit = 15
@@ -129,8 +129,9 @@ type flowProvider struct {
embedder embeddings.Embedder
graphitiClient *graphiti.Client
flowID int64
publicIP string
flowID int64
publicIP string
dockerNetwork string
callCounter *atomic.Int64
+6
View File
@@ -135,6 +135,7 @@ type providerController struct {
cfg *config.Config
docker docker.DockerClient
publicIP string
dockerNetwork string
embedder embeddings.Embedder
graphitiClient *graphiti.Client
@@ -357,6 +358,7 @@ func NewProviderController(
cfg: cfg,
docker: docker,
publicIP: cfg.DockerPublicIP,
dockerNetwork: cfg.DockerNetwork,
embedder: embedder,
graphitiClient: graphitiClient,
@@ -446,6 +448,7 @@ func (pc *providerController) NewFlowProvider(
graphitiClient: pc.graphitiClient,
flowID: flowID,
publicIP: pc.publicIP,
dockerNetwork: pc.dockerNetwork,
callCounter: newAtomicInt64(pc.startCallNumber.Add(deltaCallCounter)),
image: image,
title: title,
@@ -495,6 +498,7 @@ func (pc *providerController) LoadFlowProvider(
graphitiClient: pc.graphitiClient,
flowID: flowID,
publicIP: pc.publicIP,
dockerNetwork: pc.dockerNetwork,
callCounter: newAtomicInt64(pc.startCallNumber.Add(deltaCallCounter)),
image: image,
title: title,
@@ -589,6 +593,7 @@ func (pc *providerController) NewAssistantProvider(
graphitiClient: pc.graphitiClient,
flowID: flowID,
publicIP: pc.publicIP,
dockerNetwork: pc.dockerNetwork,
callCounter: newAtomicInt64(pc.startCallNumber.Add(deltaCallCounter)),
image: image,
title: title,
@@ -641,6 +646,7 @@ func (pc *providerController) LoadAssistantProvider(
graphitiClient: pc.graphitiClient,
flowID: flowID,
publicIP: pc.publicIP,
dockerNetwork: pc.dockerNetwork,
callCounter: newAtomicInt64(pc.startCallNumber.Add(deltaCallCounter)),
image: image,
title: title,
@@ -69,8 +69,47 @@ Note: Only planned (not yet started) Subtasks can be modified.
- User may specify public IP in task description - extract and use it when advising on OOB techniques
- If IP unknown, recommend discovering via: `curl -s https://api.ipify.org` or `curl -s ipinfo.io/ip`
- Always consider OOB port availability when recommending callback-based attacks
- **CRITICAL:** Agents MUST use only allocated ports - other ports are not forwarded (bridge mode) or may conflict with host services (host network mode)
</container_environment>
## BACKEND TERMINAL EXECUTION MECHANICS
<terminal_execution_model>
**Command Execution:** Each terminal command executes independently in isolated Docker exec session.
**Detach Modes:**
- **detach=true:** Process survives timeout, runs independently. Returns "started in background" after 500ms. Use for long-running daemons (msfrpcd, nc -l, HTTP servers).
- **detach=false:** Waits for completion, returns output. Command fails if timeout exceeded. Agent must predict timeout accurately.
**Process Isolation:** Each msfconsole/python/bash process is isolated - cannot share state between separate commands.
**Common Agent Mistakes to Identify:**
1. **Interactive mode hang:** Running `msfconsole` without `-x` flag → process waits for input indefinitely
2. **Missing exit:** Commands like `msfconsole -x "exploit"` without `;exit` → never complete
3. **Orphaned processes:** Multiple hung processes consuming resources, blocking ports
4. **Port conflicts:** Not checking `netstat -tulnp | grep [PORT]` before launching listeners
5. **Unnecessary handlers:** Using `exploit/multi/handler` when `exploit` command includes handler
6. **Session isolation:** Trying to check sessions via new msfconsole instance (won't see them)
**Correct MSF Patterns (recommend when you see mistakes above):**
**Standalone (simple):** `msfconsole -q -x "use exploit/...; set LPORT [allocated]; exploit; sleep 20; sessions -l; exit"`
All in one command (detach=false, timeout=120+).
**RPC Daemon (complex workflows):**
1. `msfrpcd -P pass -U user -a 127.0.0.1 -p 55553` (detach=true, check port first)
2. `msfconsole -q -x "connect 127.0.0.1:55553 user pass; exploit; exit"` (detach=false)
3. `msfconsole -q -x "connect ...; sessions -l; exit"` (connects to same daemon)
**Diagnostic Commands:**
- Check orphans: `ps aux | grep msfconsole` (look for multiple ruby processes)
- Check ports: `netstat -tulnp | grep [PORT]`
- Kill orphans: `pkill -f msfconsole`
**Output Minimization:** Always recommend `-q` flags to reduce token usage.
**Host Network Mode:** Shared localhost - check port availability before any daemon.
</terminal_execution_model>
## INPUT DATA STRUCTURE
<input_templates>
+35 -2
View File
@@ -143,7 +143,19 @@ Use both:
<timeouts>Specify appropriate timeouts and redirect output for long-running processes</timeouts>
<repetition>Maximum 3 attempts of identical tool calls</repetition>
<safety>Auto-approve commands with flags like `-y` when possible</safety>
<detachment>Use `detach` for all commands except the final one in a sequence</detachment>
<detachment>
LONG-RUNNING processes (daemons, servers, monitors) → detach=true, timeout=600-1200
Purpose: Process survives timeout, runs independently
Examples: msfrpcd, nc -l, python -m http.server, tcpdump
Behavior: Returns "started in background" after 500ms, process continues until killed
BATCH commands (scanners, exploits, clients) → detach=false, predict timeout for completion
Purpose: Get command output upon completion
Examples: nmap, msfconsole -x "...; exit", gobuster, curl
Behavior: Waits for completion, returns output; command fails if timeout too low
Output minimization: Use `-q` flags where available (msfconsole -q, nmap --open, etc.)
</detachment>
<management>Create dedicated working directories for file operations</management>
</terminal_protocol>
@@ -252,7 +264,16 @@ hydra, john, hashcat, crunch, medusa, patator, hashid, hash-identifier, *2john (
</password_attacks>
<metasploit desc="Exploitation framework for developing and executing exploits, payload generation, pattern analysis">
msfconsole, msfvenom, msfdb, msfrpc, msfupdate, msf-pattern_*, msf-find_badchars, msf-egghunter, msf-makeiplist
msfconsole, msfvenom, msfdb, msfrpcd, msfupdate, msf-pattern_*, msf-find_badchars, msf-egghunter, msf-makeiplist
CRITICAL msfconsole rules:
- NEVER run `msfconsole` without `-x` flag (enters interactive mode and hangs)
- ALWAYS use: `msfconsole -q -x "commands; exit"`
- ALWAYS end command chain with `;exit` to prevent hanging processes
- `exploit` command automatically starts handler - do NOT use `exploit/multi/handler` separately
- Each msfconsole process is isolated - combine all operations in ONE command: `exploit; sleep 20; sessions -l; exit`
- Check port availability before launch: `netstat -tulnp | grep [PORT]`
- Kill orphaned processes: `pkill -f msfconsole`
</metasploit>
<windows_ad desc="Windows and Active Directory exploitation, lateral movement, credential extraction, Kerberos attacks">
@@ -283,6 +304,18 @@ Check tool availability with 'which [tool]' before use. Install missing tools if
{{end}}
</usage_notes>
<msf_workflow_protocol>
Standalone (recommended): All operations in one command
`msfconsole -q -x "use exploit/...; set LPORT [allocated]; exploit; sleep 20; sessions -l; sessions -i 1 -c 'sysinfo'; exit"`
Timeout=120+ (predict total time). All output captured.
RPC Daemon (complex workflows):
Check port → `msfrpcd -p 55553` (detach=true) → `msfconsole -q -x "connect 127.0.0.1:55553...; exit"` (detach=false) → cleanup
Recovery from mistakes:
If you see hanging or port conflicts: `pkill -f msfconsole`, verify with `ps aux | grep msfconsole`, check ports with `netstat -tulnp`
</msf_workflow_protocol>
<tool_management_protocol>
<installation_rules>
- Verify tool availability with 'which [toolname]' before attempting installation
@@ -47,4 +47,12 @@ Based on my execution history above, I need your expert analysis on the followin
5. Is this task impossible to complete as currently defined? Should I report what I've accomplished and terminate, or request assistance from the user?
6. What are the most critical and actionable next steps I should take right now to move forward effectively?
When analyzing terminal commands, check for these common mistakes:
- Running msfconsole without `-x` flag (hangs in interactive mode)
- Missing `;exit` at end of command chain (process never completes, creates orphans)
- Using `exploit/multi/handler` separately (unnecessary - `exploit` includes handler)
- Trying to check sessions in new msfconsole process (process isolation - won't see them)
- Not checking port availability before launching listeners (causes bind failures)
- Multiple orphaned processes consuming resources (visible in `ps aux` output)
Please provide specific, concrete recommendations based on what you see in my execution history. I need clear guidance on whether to continue with my current approach, pivot to a different strategy, or conclude my work.
@@ -14,6 +14,12 @@ The plan should:
- Help me avoid redundant work by leveraging available context
- Guide me toward efficient task completion without unnecessary actions
Important context for planning:
- Terminal commands execute independently (no persistent state between calls)
- msfconsole processes are isolated - plan all MSF operations in single commands or via RPC daemon
- Check port availability before launching any listeners/daemons
- Minimize output with -q flags to reduce token usage
Please format your response as a numbered checklist like this:
1. [First critical action/verification step]
2. [Second step with specific details]
+9 -9
View File
@@ -6,18 +6,18 @@ import (
"strings"
)
type CodeAction string
type FileOp string
const (
ReadFile CodeAction = "read_file"
UpdateFile CodeAction = "update_file"
ReadFile FileOp = "read_file"
UpdateFile FileOp = "update_file"
)
type FileAction struct {
Action CodeAction `json:"action" jsonschema:"required,enum=read_file,enum=update_file" jsonschema_description:"Action to perform with the code. 'read_file' - Returns the content of the file. 'update_file' - Updates the content of the file"`
Content string `json:"content" jsonschema_description:"Content to write to the file"`
Path string `json:"path" jsonschema:"required" jsonschema_description:"Path to the file to read or update"`
Message string `json:"message" jsonschema:"required,title=File action message" jsonschema_description:"Not so long message which explain what do you want to read or to write to the file and explain written content to send to the user in user's language only"`
Action FileOp `json:"action" jsonschema:"required,enum=read_file,enum=update_file" jsonschema_description:"Action to perform with the code. 'read_file' - Returns the content of the file. 'update_file' - Updates the content of the file"`
Content string `json:"content" jsonschema_description:"Content to write to the file"`
Path string `json:"path" jsonschema:"required" jsonschema_description:"Path to the file to read or update"`
Message string `json:"message" jsonschema:"required,title=File action message" jsonschema_description:"Not so long message which explain what do you want to read or to write to the file and explain written content to send to the user in user's language only"`
}
type BrowserAction string
@@ -93,8 +93,8 @@ type Done struct {
type TerminalAction struct {
Input string `json:"input" jsonschema:"required" jsonschema_description:"Command to be run in the docker container terminal according to rules to execute commands"`
Cwd string `json:"cwd" jsonschema:"required" jsonschema_description:"Custom current working directory to execute commands in or default directory otherwise if it's not specified"`
Detach Bool `json:"detach" jsonschema:"required,type=boolean" jsonschema_description:"True if the command should be executed in the background, use timeout argument to limit of the execution time and you can not get output from the command if you use detach"`
Timeout Int64 `json:"timeout" jsonschema:"required,type=integer" jsonschema_description:"Limit in seconds for command execution in terminal to prevent blocking of the agent and it depends on the specific command (minimum 10; maximum 1200; default 60)"`
Detach Bool `json:"detach" jsonschema:"required,type=boolean" jsonschema_description:"Set to true for INTERACTIVE or LONG-RUNNING commands: shells (msfconsole, bash, python), listeners (nc -lvnp, socat TCP-LISTEN), servers (python -m http.server, php -S), monitors (tcpdump, tail -f). These commands expect user input or run indefinitely. When true: command runs in background, you get immediate confirmation, no stdout/stderr captured. When false: command must complete within timeout and return output. For quick batch commands (nmap, curl, ls) use false"`
Timeout Int64 `json:"timeout" jsonschema:"required,type=integer" jsonschema_description:"Execution time limit in seconds (minimum 10; maximum 1200; default 60). For batch commands that may run long, use the 'timeout' shell utility INSIDE your command to ensure clean completion with full output: 'timeout 55 nmap -sV target' (set 5-10 seconds less than this parameter). For interactive/long-running commands, use detach=true instead of relying solely on timeout"`
Message string `json:"message" jsonschema:"required,title=Terminal command message" jsonschema_description:"Not so long message which explain what do you want to achieve and to execute in terminal to send to the user in user's language only"`
}
+8 -7
View File
@@ -313,19 +313,20 @@ func (b *browser) resolveUrl(targetURL string) (*url.URL, error) {
return url.Parse(scraperURL)
}
func (b *browser) saveScreenshotData(imageData []byte) (string, error) {
func (b *browser) saveScreenshotData(screenshot []byte) (string, error) {
flowDirName := fmt.Sprintf("flow-%d", b.flowID)
targetDir := filepath.Join(b.dataDir, "screenshots", flowDirName)
screenshotDir := filepath.Join(b.dataDir, "screenshots", flowDirName)
if err := os.MkdirAll(targetDir, 0755); err != nil {
err := os.MkdirAll(screenshotDir, os.ModePerm)
if err != nil {
return "", fmt.Errorf("failed to prepare screenshot directory: %w", err)
}
timestamp := time.Now().Unix()
screenshotName := fmt.Sprintf("screenshot-%d.png", timestamp)
fullPath := filepath.Join(targetDir, screenshotName)
screenshotName := fmt.Sprintf("screenshot-%d.png", time.Now().Unix())
path := filepath.Join(screenshotDir, screenshotName)
if err := os.WriteFile(fullPath, imageData, 0644); err != nil {
err = os.WriteFile(path, screenshot, 0644)
if err != nil {
return "", fmt.Errorf("screenshot write operation failed: %w", err)
}
+47 -37
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"path/filepath"
"strconv"
"strings"
"time"
@@ -26,10 +27,10 @@ const (
defaultQuickCheckTimeout = 500 * time.Millisecond
// ANSI terminal color codes (aligned with PentAGI UI palette)
ansiColorInputCmd = "\033[96m" // Bright Cyan - matches UI blue accents
ansiColorSystemMsg = "\033[92m" // Bright Green - universal success/info
ansiColorReset = "\033[0m" // Reset to default
ansiLineTerminator = "\r\n" // CRLF for terminal compatibility
ansiColorInputCmd = "\033[96m" // Bright Cyan - matches UI blue accents
ansiColorSystemMsg = "\033[92m" // Bright Green - universal success/info
ansiColorReset = "\033[0m" // Reset to default
ansiLineTerminator = "\r\n" // CRLF for terminal compatibility
)
type execResult struct {
@@ -152,7 +153,7 @@ func (t *terminal) ExecCommand(
}
// verify container runtime status
isRunning, err := t.dockerClient.VerifyContainerRuntime(ctx, t.containerLID)
isRunning, err := t.dockerClient.IsContainerRunning(ctx, t.containerLID)
if err != nil {
return "", fmt.Errorf("runtime verification failed: %w", err)
}
@@ -245,8 +246,15 @@ func (t *terminal) getExecResult(ctx context.Context, id string, timeout time.Du
// Wait for the copy goroutine to finish
<-errChan
result := fmt.Sprintf("temporary output: %s", dst.String())
return "", fmt.Errorf("timeout value is too low, use greater value if you need so: %w: %s", ctx.Err(), result)
suggestedTimeout := max(int(timeout.Seconds())-10, 10)
return "", fmt.Errorf(
"command execution timeout (%v). Partial output: %s. "+
"HINT: If this is an interactive command (shell/REPL/listener), use detach=true. "+
"For long batch commands, wrap with shell timeout utility: 'timeout %d <command>' to ensure clean completion",
ctx.Err(),
truncateString(dst.String(), 500),
suggestedTimeout,
)
}
// wait for the exec process to finish
@@ -264,7 +272,7 @@ func (t *terminal) getExecResult(ctx context.Context, id string, timeout time.Du
}
if results == "" {
results = "Process terminated with status 0. No console output generated"
results = "Command completed successfully with exit code 0. No output produced (silent success)"
}
return results, nil
@@ -273,7 +281,7 @@ func (t *terminal) getExecResult(ctx context.Context, id string, timeout time.Du
func (t *terminal) ReadFile(ctx context.Context, flowID int64, path string) (string, error) {
containerName := PrimaryTerminalName(flowID)
isRunning, err := t.dockerClient.VerifyContainerRuntime(ctx, t.containerLID)
isRunning, err := t.dockerClient.IsContainerRunning(ctx, t.containerLID)
if err != nil {
return "", fmt.Errorf("runtime verification failed: %w", err)
}
@@ -355,7 +363,7 @@ func (t *terminal) ReadFile(ctx context.Context, flowID int64, path string) (str
func (t *terminal) WriteFile(ctx context.Context, flowID int64, content string, path string) (string, error) {
containerName := PrimaryTerminalName(flowID)
isRunning, err := t.dockerClient.VerifyContainerRuntime(ctx, t.containerLID)
isRunning, err := t.dockerClient.IsContainerRunning(ctx, t.containerLID)
if err != nil {
return "", fmt.Errorf("container runtime check failed: %w", err)
}
@@ -364,50 +372,45 @@ func (t *terminal) WriteFile(ctx context.Context, flowID int64, content string,
}
// Docker SDK requires TAR format for file transfer
tarBuffer := new(bytes.Buffer)
tarBuffer := &bytes.Buffer{}
archiveWriter := tar.NewWriter(tarBuffer)
defer archiveWriter.Close()
baseFilename := filepath.Base(path)
filename := filepath.Base(path)
fileDescriptor := &tar.Header{
Name: baseFilename,
Mode: 0644,
Name: filename,
Mode: 0600,
Size: int64(len(content)),
}
if err := archiveWriter.WriteHeader(fileDescriptor); err != nil {
archiveWriter.Close()
err = archiveWriter.WriteHeader(fileDescriptor)
if err != nil {
return "", fmt.Errorf("tar archive header generation failed: %w", err)
}
bytesWritten, err := archiveWriter.Write([]byte(content))
_, err = archiveWriter.Write([]byte(content))
if err != nil {
archiveWriter.Close()
return "", fmt.Errorf("tar archive content serialization failed: %w", err)
}
if bytesWritten != len(content) {
archiveWriter.Close()
return "", fmt.Errorf("incomplete tar write: expected %d bytes, wrote %d", len(content), bytesWritten)
err = archiveWriter.Close()
if err != nil {
return "", fmt.Errorf("failed to close tar writer: %w", err)
}
if err := archiveWriter.Close(); err != nil {
return "", fmt.Errorf("tar archive finalization failed: %w", err)
}
targetDirectory := filepath.Dir(path)
copyOptions := container.CopyToContainerOptions{
dir := filepath.Dir(path)
err = t.dockerClient.CopyToContainer(ctx, containerName, dir, tarBuffer, container.CopyToContainerOptions{
AllowOverwriteDirWithFile: true,
CopyUIDGID: false,
}
if err := t.dockerClient.CopyToContainer(ctx, containerName, targetDirectory, tarBuffer, copyOptions); err != nil {
return "", fmt.Errorf("container file transfer failed for '%s': %w", path, err)
})
if err != nil {
return "", fmt.Errorf("container file transfer failed: %w", err)
}
// Format success message with styling
successText := fmt.Sprintf("File successfully saved to %s", path)
styledSuccess := fmt.Sprintf("%s%s%s%s", ansiColorSystemMsg, successText, ansiColorReset, ansiLineTerminator)
if _, err := t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledSuccess, t.containerID, t.taskID, t.subtaskID); err != nil {
return "", fmt.Errorf("terminal log recording failed: %w", err)
successMsg := fmt.Sprintf("File successfully saved to %s", path)
styledMsg := fmt.Sprintf("%s%s%s%s", ansiColorSystemMsg, successMsg, ansiColorReset, ansiLineTerminator)
_, err = t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledMsg, t.containerID, t.taskID, t.subtaskID)
if err != nil {
return "", fmt.Errorf("failed to put terminal log (write file cmd): %w", err)
}
return fmt.Sprintf("Successfully wrote %d bytes to %s", len(content), path), nil
@@ -420,3 +423,10 @@ func PrimaryTerminalName(flowID int64) string {
func (t *terminal) IsAvailable() bool {
return t.dockerClient != nil
}
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "... [truncated full size is " + strconv.Itoa(len(s)) + " bytes]"
}
+6 -6
View File
@@ -40,17 +40,17 @@ type contextAwareMockDockerClient struct {
ctxWasCanceled bool
}
func (m *contextAwareMockDockerClient) LaunchContainer(_ context.Context, _ string, _ database.ContainerType,
func (m *contextAwareMockDockerClient) RunContainer(_ context.Context, _ string, _ database.ContainerType,
_ int64, _ *container.Config, _ *container.HostConfig) (database.Container, error) {
return database.Container{}, nil
}
func (m *contextAwareMockDockerClient) HaltContainer(_ context.Context, _ string, _ int64) error {
func (m *contextAwareMockDockerClient) StopContainer(_ context.Context, _ string, _ int64) error {
return nil
}
func (m *contextAwareMockDockerClient) PurgeContainer(_ context.Context, _ string, _ int64) error {
func (m *contextAwareMockDockerClient) RemoveContainer(_ context.Context, _ string, _ int64) error {
return nil
}
func (m *contextAwareMockDockerClient) VerifyContainerRuntime(_ context.Context, _ string) (bool, error) {
func (m *contextAwareMockDockerClient) IsContainerRunning(_ context.Context, _ string) (bool, error) {
return m.isRunning, nil
}
func (m *contextAwareMockDockerClient) ContainerExecCreate(_ context.Context, _ string, _ container.ExecOptions) (container.ExecCreateResponse, error) {
@@ -97,8 +97,8 @@ func (m *contextAwareMockDockerClient) CopyToContainer(_ context.Context, _ stri
func (m *contextAwareMockDockerClient) CopyFromContainer(_ context.Context, _ string, _ string) (io.ReadCloser, container.PathStat, error) {
return io.NopCloser(nil), container.PathStat{}, nil
}
func (m *contextAwareMockDockerClient) CleanupAllResources(_ context.Context) error { return nil }
func (m *contextAwareMockDockerClient) GetDefaultImage() string { return "test-image" }
func (m *contextAwareMockDockerClient) Cleanup(_ context.Context) error { return nil }
func (m *contextAwareMockDockerClient) GetDefaultImage() string { return "test-image" }
var _ docker.DockerClient = (*contextAwareMockDockerClient)(nil)
+4 -4
View File
@@ -402,7 +402,7 @@ func (fte *flowToolsExecutor) Prepare(ctx context.Context) error {
fte.primaryLID = cnt.LocalID.String
return nil
default:
fte.docker.PurgeContainer(ctx, cnt.LocalID.String, cnt.ID)
fte.docker.RemoveContainer(ctx, cnt.LocalID.String, cnt.ID)
}
}
@@ -412,7 +412,7 @@ func (fte *flowToolsExecutor) Prepare(ctx context.Context) error {
}
containerName := PrimaryTerminalName(fte.flowID)
cnt, err := fte.docker.LaunchContainer(
cnt, err := fte.docker.RunContainer(
ctx,
containerName,
database.ContainerTypePrimary,
@@ -426,7 +426,7 @@ func (fte *flowToolsExecutor) Prepare(ctx context.Context) error {
},
)
if err != nil {
return fmt.Errorf("failed to spawn container '%s': %w", containerName, err)
return fmt.Errorf("failed to launch container '%s': %w", containerName, err)
}
fte.primaryID = cnt.ID
@@ -441,7 +441,7 @@ func (fte *flowToolsExecutor) Release(ctx context.Context) error {
}
// TODO: here better to get flow containers list and purge all of them
if err := fte.docker.PurgeContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {
if err := fte.docker.RemoveContainer(ctx, fte.primaryLID, fte.primaryID); err != nil {
containerName := PrimaryTerminalName(fte.flowID)
return fmt.Errorf("failed to purge container '%s': %w", containerName, err)
}
View File