{"record":{"id":"b8e9f94630dd5808","repo":"argoproj/argo-workflows","slug":"artifact-key-q-must-not-be-an-absolute-path","errorCode":null,"errorMessage":"artifact key %q must not be an absolute path","messagePattern":"artifact key %q must not be an absolute path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/artifactkey.go","lineNumber":29,"sourceCode":"\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 {\n\t\treturn fmt.Errorf(\"artifact key %q must have a valid UUID segment: %w\", key, err)\n\t}\n","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/utils/artifactkey.go#L11-L47","documentation":"ValidateUploadedArtifactKey rejects keys starting with '/' because keys are object-style relative paths, not filesystem absolute paths. An absolute path would also fail the prefix check, but this rule gives a specific error for clarity.","triggerScenarios":"Passing '/uploads/ns/uuid/file' or an absolute filesystem path like '/tmp/artifact.tar.gz' as the key.","commonSituations":"Mixing up local file paths with artifact object keys; prefixing with '/' out of habit from filesystem APIs; copying a URL path including the leading slash.","solutions":["Drop the leading slash so the key is relative: uploads/{namespace}/{uuid}/{filename}.","Use strings.TrimPrefix(key, \"/\") only if the remainder matches the required prefix.","Distinguish local file paths from artifact keys in your code."],"exampleFix":"// before\nkey := \"/uploads/my-ns/\" + id + \"/file.bin\"\n// after\nkey := \"uploads/my-ns/\" + id + \"/file.bin\"","handlingStrategy":"validation","validationCode":"if strings.HasPrefix(key, \"/\") {\n\treturn fmt.Errorf(\"key %q must be relative\", key)\n}","typeGuard":"func isRelativeKey(key string) bool { return !strings.HasPrefix(key, \"/\") }","tryCatchPattern":"if err := utils.ValidateUploadedArtifactKey(ns, key); err != nil {\n\tif strings.Contains(err.Error(), \"absolute path\") {\n\t\tkey = strings.TrimPrefix(key, \"/\")\n\t}\n}","preventionTips":["Keep local file paths and artifact object keys as separate variables.","Trim leading slashes when converting URL/filesystem paths to object keys.","Validate keys before submitting them to artifact APIs."],"tags":["artifacts","validation","path"],"backgroundTag":"artifact-key-validation","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"}