Skip to content

Commit b798402

Browse files
michaelmcneesclaude
andcommitted
fix: address CodeRabbit review round 5
- Reject symlinks and non-regular files in TmpStorage.Put - Validate cpus param is non-negative in docker/run - Add timeout context and error logging to container cleanup/stop - Create fresh artifacts scratch dir per retry attempt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0bd9a9b commit b798402

3 files changed

Lines changed: 45 additions & 13 deletions

File tree

internal/artifact/tmp.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ func (fs *FilesystemTmpStorage) Put(ctx context.Context, key string, localPath s
4848
return "", fmt.Errorf("creating artifact dir: %w", err)
4949
}
5050

51+
// Reject symlinks and non-regular files to prevent exfiltration.
52+
fi, err := os.Lstat(localPath)
53+
if err != nil {
54+
return "", fmt.Errorf("stat source file: %w", err)
55+
}
56+
if !fi.Mode().IsRegular() {
57+
return "", fmt.Errorf("source file %q is not a regular file (mode: %s)", localPath, fi.Mode())
58+
}
59+
5160
src, err := os.Open(localPath)
5261
if err != nil {
5362
return "", fmt.Errorf("opening source file: %w", err)

internal/connector/docker.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"path/filepath"
1212
"strconv"
1313
"strings"
14+
"time"
1415

1516
"github.com/docker/docker/api/types/container"
1617
"github.com/docker/docker/api/types/image"
@@ -110,10 +111,19 @@ func parseDockerParams(params map[string]any) (*dockerConfig, error) {
110111
// cpus
111112
switch v := params["cpus"].(type) {
112113
case float64:
114+
if v < 0 {
115+
return nil, fmt.Errorf("docker/run: cpus must be non-negative, got %v", v)
116+
}
113117
cfg.CPUs = v
114118
case int:
119+
if v < 0 {
120+
return nil, fmt.Errorf("docker/run: cpus must be non-negative, got %v", v)
121+
}
115122
cfg.CPUs = float64(v)
116123
case int64:
124+
if v < 0 {
125+
return nil, fmt.Errorf("docker/run: cpus must be non-negative, got %v", v)
126+
}
117127
cfg.CPUs = float64(v)
118128
}
119129

@@ -309,7 +319,11 @@ func (c *DockerRunConnector) Execute(ctx context.Context, params map[string]any)
309319
// Ensure cleanup.
310320
defer func() {
311321
if cfg.Remove {
312-
cli.ContainerRemove(context.Background(), containerID, container.RemoveOptions{Force: true})
322+
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second) // #nosec G118 -- intentional: parent ctx may be cancelled
323+
defer cleanupCancel()
324+
if err := cli.ContainerRemove(cleanupCtx, containerID, container.RemoveOptions{Force: true}); err != nil {
325+
log.Printf("docker/run: failed to remove container %s: %v", containerID, err)
326+
}
313327
}
314328
}()
315329

@@ -343,9 +357,13 @@ func (c *DockerRunConnector) Execute(ctx context.Context, params map[string]any)
343357
select {
344358
case <-ctx.Done():
345359
gracePeriod := 10
346-
cli.ContainerStop(context.Background(), containerID, container.StopOptions{
360+
stopCtx, stopCancel := context.WithTimeout(context.Background(), 30*time.Second)
361+
defer stopCancel()
362+
if err := cli.ContainerStop(stopCtx, containerID, container.StopOptions{
347363
Timeout: &gracePeriod,
348-
})
364+
}); err != nil {
365+
log.Printf("docker/run: failed to stop container %s: %v", containerID, err)
366+
}
349367
case <-doneCh:
350368
}
351369
}()

internal/engine/engine.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -408,22 +408,13 @@ func (e *Engine) executeStepLogic(ctx context.Context, execID string, step workf
408408
resolvedParams["_registry_credential"] = regCredData
409409
}
410410

411-
// Create artifacts scratch dir if step declares artifacts.
411+
// Validate artifact subsystem is configured if step declares artifacts.
412412
var artifactsDir string
413413
if len(step.Artifacts) > 0 {
414414
if e.TmpStorage == nil || e.ArtifactStore == nil {
415415
return nil, fmt.Errorf("step %q declares artifacts but artifact subsystem is not configured (set tmp storage in mantle.yaml)", step.Name)
416416
}
417417
}
418-
if len(step.Artifacts) > 0 && e.TmpStorage != nil {
419-
var tmpErr error
420-
artifactsDir, tmpErr = os.MkdirTemp("", "mantle-artifacts-*")
421-
if tmpErr != nil {
422-
return nil, fmt.Errorf("creating artifacts dir: %v", tmpErr)
423-
}
424-
defer os.RemoveAll(artifactsDir) // clean local scratch after persisting to tmp storage
425-
ctx = artifact.WithArtifactsDir(ctx, artifactsDir)
426-
}
427418

428419
// Inject workflow/step context for AI observability metrics.
429420
resolvedParams["_workflow"] = workflowName
@@ -453,6 +444,20 @@ func (e *Engine) executeStepLogic(ctx context.Context, execID string, step workf
453444
var lastErr error
454445

455446
for attempt := 1; attempt <= maxAttempts; attempt++ {
447+
// Create a fresh artifacts scratch dir per attempt.
448+
if len(step.Artifacts) > 0 && e.TmpStorage != nil {
449+
if artifactsDir != "" {
450+
os.RemoveAll(artifactsDir) // clean previous attempt's scratch
451+
}
452+
var tmpErr error
453+
artifactsDir, tmpErr = os.MkdirTemp("", "mantle-artifacts-*")
454+
if tmpErr != nil {
455+
return nil, fmt.Errorf("creating artifacts dir: %v", tmpErr)
456+
}
457+
defer os.RemoveAll(artifactsDir)
458+
ctx = artifact.WithArtifactsDir(ctx, artifactsDir)
459+
}
460+
456461
// Apply per-step timeout.
457462
execCtx := ctx
458463
var cancel context.CancelFunc

0 commit comments

Comments
 (0)