argoproj/argo-workflows · error
artifact key %q is not in canonical form
Error message
artifact key %q is not in canonical form
What it means
The key must be in canonical path form: path.Clean(key) must equal key. Non-canonical forms like 'uploads/ns/uuid/./file' or double slashes reveal manipulative or sloppy construction and are rejected to keep keys unambiguous.
Source
Thrown at server/utils/artifactkey.go:32
// 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 {
return fmt.Errorf("artifact key %q must have a valid UUID segment: %w", key, err)
}
filename := parts[3]
if path.Base(filename) != filename {
return fmt.Errorf("artifact key %q must have a bare filename segment", key)View on GitHub (pinned to 35bff19146)
Solutions
- Run path.Clean(key) before use and confirm it equals the key; if not, rebuild the key.
- Construct keys programmatically from the 4 exact segments rather than concatenating raw strings.
- Collapse duplicate separators with path.Join of the individual segments.
Example fix
// before
key := "uploads/my-ns//" + id + "/./file.bin"
// after
key := path.Join("uploads", "my-ns", id, "file.bin") Defensive patterns
Strategy: validation
Validate before calling
if path.Clean(key) != key {
return fmt.Errorf("key %q is not canonical", key)
} Type guard
func isCanonicalKey(key string) bool { return path.Clean(key) == key } Try / catch
if err := utils.ValidateUploadedArtifactKey(ns, key); err != nil {
if strings.Contains(err.Error(), "canonical form") {
key = path.Clean(key)
// re-validate before proceeding
}
} Prevention
- Build keys with path.Join over the exact 4 segments instead of string concatenation.
- Run path.Clean on keys derived from external input.
- Reject keys with './' or duplicate slashes at construction time.
When it happens
Trigger: Keys containing './' segments, redundant slashes ('uploads//ns/...'), or trailing slashes that path.Clean would normalize differently.
Common situations: String concatenation producing duplicate slashes; inserting current-directory segments; building keys from URL paths that were not normalized.
Related errors
- artifact key %q must not be an absolute path
- key unsupported: cannot get key for artifact location, becau
- artifact key %q must start with %q
- artifact key %q must not contain '..'
- artifact key %q must have exactly 4 segments: uploads/{names
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/5a14db9b9dbef7e9.
Report an issue: GitHub.