{"record":{"id":"f75f8ac70585a9b1","repo":"fullstackhero/dotnet-starter-kit","slug":"extension-extension-not-allowed-for-category-cmd-category","errorCode":null,"errorMessage":"Extension '{extension}' not allowed for category '{cmd.Category}'.","messagePattern":"Extension '(.+?)' not allowed for category '(.+?)'\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs","lineNumber":48,"sourceCode":"\n        var tenantId = currentUser.GetTenant() ?? throw new UnauthorizedException(\"invalid tenant\");\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty)\n        {\n            throw new UnauthorizedException(\"no current user\");\n        }\n\n        // Category lookup + extension/size validation.\n        if (!options.Value.Categories.TryGetValue(cmd.Category, out var category))\n        {\n            throw new CustomException($\"Unknown category '{cmd.Category}'.\", (IEnumerable<string>?)null, HttpStatusCode.BadRequest);\n        }\n\n        var extension = Path.GetExtension(cmd.FileName);\n        if (string.IsNullOrWhiteSpace(extension) ||\n            !category.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))\n        {\n            throw new CustomException(\n                $\"Extension '{extension}' not allowed for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n\n        if (cmd.SizeBytes > category.MaxBytes)\n        {\n            throw new CustomException(\n                $\"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n\n        // Authorization: policy must exist and allow the attach.\n        var policy = policies.Resolve(cmd.OwnerType)\n            ?? throw new ForbiddenException($\"No file access policy registered for owner type '{cmd.OwnerType}'.\");\n        if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))\n        {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs#L30-L66","documentation":"CustomException (BadRequest) thrown when the uploaded file's extension (from Path.GetExtension on cmd.FileName) is empty or not in the category's AllowedExtensions list (case-insensitive). The gate exists to keep unapproved file types out of storage before a presigned URL is minted.","triggerScenarios":"RequestUploadUrlCommand with cmd.FileName having no extension ('README'), a double extension not listed ('file.tar.gz' when only '.gz' allowed), a blocked type ('.exe' for the avatar category), or an extension present only in a different category's allow-list.","commonSituations":"Frontend letting users pick files without filtering by accept attribute; renaming files to hide types; configuration allow-list tightened without updating client UI; case handled fine (comparison is OrdinalIgnoreCase) but hidden characters or trailing dots in the filename cause mismatch.","solutions":["Send a file whose extension is in the target category's AllowedExtensions config, or convert the file to an allowed type.","If the type is legitimately needed, add that extension to the category's AllowedExtensions in configuration.","Update the client file-picker accept filter to match the server's allow-list so users cannot select disallowed files.","Validate extension client-side before calling the API to give instant feedback."],"exampleFix":"// before\nawait requestUploadUrl({ category: \"avatar\", fileName: \"photo.bmp\", ... }); // .bmp not allowed\n// after\nawait requestUploadUrl({ category: \"avatar\", fileName: \"photo.png\", ... }); // .png in AllowedExtensions","handlingStrategy":"validation","validationCode":"const ext = fileName.slice(fileName.lastIndexOf(\".\")).toLowerCase();\nif (!category.allowedExtensions.map(e => e.toLowerCase()).includes(ext)) throw new Error(`.${ext} not allowed`);","typeGuard":null,"tryCatchPattern":"try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 400 && e.message?.includes(\"not allowed for category\")) { showToast(\"File type not supported for this upload\"); } else throw e; }","preventionTips":["Set the file input's accept attribute from the same allow-list the server uses.","Normalize filenames (trim, lowercase extension) before submitting.","Keep client-side allow-lists and server config in one shared source of truth."],"tags":["validation","files","security"],"backgroundTag":"invalid-argument-value","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}