argoproj/argo-workflows · error

artifact key %q must not contain '..'

Error message

artifact key %q must not contain '..'

What it means

As part of the key format check, ValidateUploadedArtifactKey rejects any key containing '..' to block path traversal. Even before reaching the prefix check's siblings, any '..' anywhere in the key fails validation because client-supplied keys are used in storage paths unescaped.

Source

Thrown at server/utils/artifactkey.go:26

	"github.com/google/uuid"
)

// ValidateUploadedArtifactKey checks that key is exactly the format the upload
// endpoint generates for namespace: uploads/{namespace}/{uuid}/{filename}. It
// rejects path traversal, absolute paths, empty segments, and any key outside
// the upload prefix, since a client-supplied key is otherwise applied to the
// artifact location without further checks.
//
// This is defense-in-depth, not a proof of ownership: a valid-looking key
// naming another user's upload under the same namespace still passes.
func ValidateUploadedArtifactKey(namespace, key string) error {
	prefix := "uploads/" + namespace + "/"
	if !strings.HasPrefix(key, prefix) {
		return fmt.Errorf("artifact key %q must start with %q", key, prefix)
	}
	if strings.Contains(key, "..") {
		return fmt.Errorf("artifact key %q must not contain '..'", key)
	}
	if strings.HasPrefix(key, "/") {
		return fmt.Errorf("artifact key %q must not be an absolute path", key)
	}
	if path.Clean(key) != key {
		return fmt.Errorf("artifact key %q is not in canonical form", key)
	}

	parts := strings.Split(key, "/")
	if len(parts) != 4 {
		return fmt.Errorf("artifact key %q must have exactly 4 segments: uploads/{namespace}/{uuid}/{filename}", key)
	}
	if slices.Contains(parts, "") {
		return fmt.Errorf("artifact key %q must not contain empty segments", key)
	}

	uuidSegment := parts[2]
	if _, err := uuid.Parse(uuidSegment); err != nil {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Remove '..' segments — use only a bare filename for the final segment.
  2. Sanitize/validate any user-supplied filename before composing the key.
  3. Use path.Join with the fixed prefix and a cleaned, base-name-only filename.

Example fix

// before
key := "uploads/my-ns/" + id + "/../../etc/passwd"
// after
key := "uploads/my-ns/" + id + "/" + path.Base(userFilename)
Defensive patterns

Strategy: validation

Validate before calling

if strings.Contains(key, "..") {
	return fmt.Errorf("key %q must not contain '..'", key)
}

Type guard

func traversalFree(key string) bool { return !strings.Contains(key, "..") }

Try / catch

if err := utils.ValidateUploadedArtifactKey(ns, key); err != nil {
	if strings.Contains(err.Error(), "must not contain '..'") {
		key = path.Join("uploads", ns, uuid.NewString(), path.Base(userFilename))
	}
}

Prevention

When it happens

Trigger: Keys like 'uploads/ns/uuid/../../secret', keys built by joining user input, or copying keys containing relative-path segments.

Common situations: Path traversal attempts or accidental concatenation of relative paths; template interpolation injecting '..'; naive filepath joining of untrusted filenames.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/89e088922a16c82a. Report an issue: GitHub.