{"record":{"id":"89e088922a16c82a","repo":"argoproj/argo-workflows","slug":"artifact-key-q-must-not-contain","errorCode":null,"errorMessage":"artifact key %q must not contain '..'","messagePattern":"artifact key %q must not contain '\\.\\.'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/artifactkey.go","lineNumber":26,"sourceCode":"\n\t\"github.com/google/uuid\"\n)\n\n// ValidateUploadedArtifactKey checks that key is exactly the format the upload\n// endpoint generates for namespace: uploads/{namespace}/{uuid}/{filename}. It\n// rejects path traversal, absolute paths, empty segments, and any key outside\n// the upload prefix, since a client-supplied key is otherwise applied to the\n// artifact location without further checks.\n//\n// This is defense-in-depth, not a proof of ownership: a valid-looking key\n// naming another user's upload under the same namespace still passes.\nfunc ValidateUploadedArtifactKey(namespace, key string) error {\n\tprefix := \"uploads/\" + namespace + \"/\"\n\tif !strings.HasPrefix(key, prefix) {\n\t\treturn fmt.Errorf(\"artifact key %q must start with %q\", key, prefix)\n\t}\n\tif strings.Contains(key, \"..\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not contain '..'\", key)\n\t}\n\tif strings.HasPrefix(key, \"/\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not be an absolute path\", key)\n\t}\n\tif path.Clean(key) != key {\n\t\treturn fmt.Errorf(\"artifact key %q is not in canonical form\", key)\n\t}\n\n\tparts := strings.Split(key, \"/\")\n\tif len(parts) != 4 {\n\t\treturn fmt.Errorf(\"artifact key %q must have exactly 4 segments: uploads/{namespace}/{uuid}/{filename}\", key)\n\t}\n\tif slices.Contains(parts, \"\") {\n\t\treturn fmt.Errorf(\"artifact key %q must not contain empty segments\", key)\n\t}\n\n\tuuidSegment := parts[2]\n\tif _, err := uuid.Parse(uuidSegment); err != nil {","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/utils/artifactkey.go#L8-L44","documentation":"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.","triggerScenarios":"Keys like 'uploads/ns/uuid/../../secret', keys built by joining user input, or copying keys containing relative-path segments.","commonSituations":"Path traversal attempts or accidental concatenation of relative paths; template interpolation injecting '..'; naive filepath joining of untrusted filenames.","solutions":["Remove '..' segments — use only a bare filename for the final segment.","Sanitize/validate any user-supplied filename before composing the key.","Use path.Join with the fixed prefix and a cleaned, base-name-only filename."],"exampleFix":"// before\nkey := \"uploads/my-ns/\" + id + \"/../../etc/passwd\"\n// after\nkey := \"uploads/my-ns/\" + id + \"/\" + path.Base(userFilename)","handlingStrategy":"validation","validationCode":"if strings.Contains(key, \"..\") {\n\treturn fmt.Errorf(\"key %q must not contain '..'\", key)\n}","typeGuard":"func traversalFree(key string) bool { return !strings.Contains(key, \"..\") }","tryCatchPattern":"if err := utils.ValidateUploadedArtifactKey(ns, key); err != nil {\n\tif strings.Contains(err.Error(), \"must not contain '..'\") {\n\t\tkey = path.Join(\"uploads\", ns, uuid.NewString(), path.Base(userFilename))\n\t}\n}","preventionTips":["Always pass filenames through path.Base before composing keys.","Treat any user-supplied filename as untrusted and strip path separators.","Never concatenate relative paths into artifact keys."],"tags":["artifacts","security","path-traversal","validation"],"backgroundTag":"path-traversal-detected","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}