{"record":{"id":"e83e4e89ea08be2d","repo":"ppy/osu","slug":"filename-filename-is-not-allowed","errorCode":null,"errorMessage":"Filename \"\"{filename}\"\" is not allowed.","messagePattern":"Filename \"\"(.+?)\"\" is not allowed\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Database/ModelManager.cs","lineNumber":93,"sourceCode":"        }\r\n\r\n        /// <summary>\r\n        /// Replace a file from within an ongoing realm transaction.\r\n        /// </summary>\r\n        public void ReplaceFile(RealmNamedFileUsage file, Stream contents, Realm realm)\r\n        {\r\n            file.File = realmFileStore.Add(contents, realm);\r\n        }\r\n\r\n        /// <summary>\r\n        /// Add a file from within an ongoing realm transaction. If the file already exists, it is overwritten.\r\n        /// </summary>\r\n        public void AddFile(TModel item, Stream contents, string filename, Realm realm)\r\n        {\r\n            filename = filename.ToStandardisedPath();\r\n\r\n            if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename))\r\n                throw new InvalidOperationException($@\"Filename \"\"{filename}\"\" is not allowed.\");\r\n\r\n            var existing = item.GetFile(filename);\r\n\r\n            if (existing != null)\r\n            {\r\n                ReplaceFile(existing, contents, realm);\r\n                return;\r\n            }\r\n\r\n            var file = realmFileStore.Add(contents, realm);\r\n            var namedUsage = new RealmNamedFileUsage(file, filename);\r\n\r\n            item.Files.Add(namedUsage);\r\n        }\r\n\r\n        /// <summary>\r\n        /// Delete multiple items.\r\n        /// This will post notifications tracking progress.\r","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Database/ModelManager.cs#L75-L111","documentation":"Thrown by ModelManager<TModel>.AddFile after ToStandardisedPath() if FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename) is true. It is a hard security guard rejecting filenames that could escape the model's file directory via '..' segments, absolute paths, or other traversal patterns. Runs inside an ongoing realm transaction.","triggerScenarios":"Calling modelManager.AddFile(item, stream, filename, realm) with a filename containing '../', leading drive/root separators, backslash-based traversal, or any pattern FilesystemSanityCheckHelpers flags. Happens during editor save, import merge, or programmatic file addition.","commonSituations":"Filenames sourced from untrusted input (downloaded archives, external editor imports, user-typed paths). A ruleset/beatmap editor that constructs filenames by joining user strings without sanitising. Cross-platform path normalisation surprises (backslashes on Windows interpreted as traversal).","solutions":["Sanitise the filename before calling AddFile: strip directory components with Path.GetFileName(filename) and reject/replace any path separators or '..'.","Validate with the same helper the manager uses: guard with if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename)) return early or normalise.","Ensure user-facing filename fields are passed through ToStandardisedPath() and a GetValidFilename()-style normaliser upstream so only a relative leaf name reaches AddFile."],"exampleFix":"// before\nmodelManager.AddFile(item, stream, @\"../../etc/evil.osu\", realm);\n\n// after\nstring safeName = Path.GetFileName(filename).GetValidFilename();\nif (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(safeName))\n    throw new ArgumentException($\"Refusing unsafe filename: {filename}\");\nmodelManager.AddFile(item, stream, safeName, realm);","handlingStrategy":"validation","validationCode":"string safe = Path.GetFileName(filename).ToStandardisedPath();\nif (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(safe))\n    throw new ArgumentException($\"Unsafe filename: {filename}\");\nmodelManager.AddFile(item, stream, safe, realm);","typeGuard":"static bool IsSafeFilename(string filename)\n    => !string.IsNullOrWhiteSpace(filename)\n       && !Path.IsPathRooted(filename)\n       && !filename.Contains(\"..\")\n       && !FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename);","tryCatchPattern":"try { modelManager.AddFile(item, stream, filename, realm); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"is not allowed\"))\n{ /* log offending filename, reject user input */ }","preventionTips":["Always reduce user-supplied paths to a leaf name with Path.GetFileName before storing.","Run filenames through GetValidFilename() + ToStandardisedPath() at the input boundary.","Treat any separator ('\\\\', '/') in a stored filename as a red flag and reject."],"tags":["security","path-traversal","filesystem","realm","validation"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}