{"record":{"id":"d9b7e57d452396f3","repo":"argoproj/argo-workflows","slug":"invalid-filename","errorCode":null,"errorMessage":"Invalid filename","messagePattern":"Invalid filename","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/artifacts/artifact_server.go","lineNumber":235,"sourceCode":"\t\t\t\t\"artifactName\": artifactName,\n\t\t\t}).Info(ctx, \"Resolved artifact location from default repository\")\n\t\t}\n\t}\n\n\t// Check if the artifact has a location configured (S3, GCS, etc.)\n\tif !artifactCopy.HasLocation() {\n\t\thttp.Error(w, fmt.Sprintf(\"Artifact '%s' does not have a storage location configured (s3, gcs, azure, oss). Please configure a storage location in the WorkflowTemplate or set up a default artifact repository.\", artifactName), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Generate unique key for the artifact\n\tuploadUUID := uuid.NewString()\n\toriginalKey, _ := artifactCopy.GetKey()\n\t// Sanitize filename to prevent path traversal attacks. path.Base only\n\t// recognises '/' as a separator, so normalise Windows-style '\\' first.\n\tsanitizedFilename := path.Base(strings.ReplaceAll(header.Filename, \"\\\\\", \"/\"))\n\tif sanitizedFilename == \".\" || sanitizedFilename == \"/\" || sanitizedFilename == \"\" {\n\t\thttp.Error(w, \"Invalid filename\", http.StatusBadRequest)\n\t\treturn\n\t}\n\t// Replace the key with uploaded file path under uploads/\n\tnewKey := fmt.Sprintf(\"uploads/%s/%s/%s\", namespace, uploadUUID, sanitizedFilename)\n\tif validateErr := sutils.ValidateUploadedArtifactKey(namespace, newKey); validateErr != nil {\n\t\ta.serverInternalError(ctx, fmt.Errorf(\"generated artifact key failed self-validation: %w\", validateErr), w)\n\t\treturn\n\t}\n\n\t// Create a copy of the artifact for uploading (using artifactCopy which has resolved location)\n\toutputArtifact := artifactCopy.DeepCopy()\n\tif setErr := outputArtifact.SetKey(newKey); setErr != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to set artifact key: %v\", setErr), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\ta.logger.WithFields(logging.Fields{\n\t\t\"originalKey\": originalKey,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/artifacts/artifact_server.go#L217-L253","documentation":"The uploaded filename is sanitized with path.Base (after normalizing Windows backslashes) to prevent path traversal. If the resulting base name is \".\", \"/\", or empty, the server refuses the upload with HTTP 400 \"Invalid filename\". This blocks filenames like \"..\", \"./\", or a filename consisting only of separators.","triggerScenarios":"Uploading a file part whose filename is \"..\" or \".\"; a client that sends a filename ending in a separator (e.g. \"dir/\"); a filename that reduces to empty after backslash-to-slash normalization and base extraction; maliciously crafted multipart parts attempting path traversal (../../etc/passwd).","commonSituations":"Directory uploads where directory entries are sent with trailing slashes; scripted uploads using an empty or placeholder filename; security scanners probing the endpoint with traversal payloads.","solutions":["Send a real, non-empty base filename in the multipart part (e.g. file=@data.bin, not a directory path).","Strip trailing slashes and pass only the base name explicitly if your client builds the multipart part manually.","If uploading a directory, iterate files and upload each with its own basename."],"exampleFix":"// before (client sends a directory-like name)\nwriter.CreateFormFile(\"file\", \"mydir/\")\n// after\nwriter.CreateFormFile(\"file\", \"myfile.txt\")","handlingStrategy":"validation","validationCode":"const base = filename.replaceAll('\\\\', '/').split('/').pop() || '';\nif (!base || base === '.' || base === '..') {\n  throw new Error(`invalid upload filename: ${JSON.stringify(filename)}`);\n}\nform.append('file', file, base);","typeGuard":"function isValidUploadFilename(name) {\n  const base = String(name).replaceAll('\\\\', '/').split('/').pop();\n  return typeof base === 'string' && base.length > 0 && base !== '.' && base !== '..';\n}","tryCatchPattern":"if (!isValidUploadFilename(file.name)) {\n  return reject(new Error('client-side: filename normalizes to empty path, refusing upload'));\n}","preventionTips":["Never upload directory entries or names ending in '/' or '\\\\'.","Normalize to the basename client-side before building the multipart part.","Treat traversal-shaped filenames ('../..') as input-validation failures, not something to send.","For directory uploads, iterate real files and send each with its own basename."],"tags":["http","security","path-traversal","filename"],"backgroundTag":"invalid-filename-rejected","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"}