{"record":{"id":"92d98b46cd801998","repo":"fullstackhero/dotnet-starter-kit","slug":"file-type-extension-is-not-allowed-allowed-string-join-rules-92d98b","errorCode":null,"errorMessage":"File type '{extension}' is not allowed. Allowed: {string.Join(\", \", rules.AllowedExtensions)}","messagePattern":"File type '(.+?)' is not allowed\\. Allowed: (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/BuildingBlocks/Storage/S3/S3StorageService.cs","lineNumber":51,"sourceCode":"        _logger = logger;\n        _contentTypeProvider = new FileExtensionContentTypeProvider();\n\n        if (string.IsNullOrWhiteSpace(_options.Bucket))\n        {\n            throw new InvalidOperationException(\"Storage:S3:Bucket is required when using S3 storage.\");\n        }\n    }\n\n    public async Task<string> UploadAsync<T>(FileUploadRequest request, FileType fileType, CancellationToken cancellationToken = default) where T : class\n    {\n        ArgumentNullException.ThrowIfNull(request);\n\n        var rules = FileTypeMetadata.GetRules(fileType);\n        var extension = Path.GetExtension(request.FileName);\n\n        if (string.IsNullOrWhiteSpace(extension) || !rules.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))\n        {\n            throw new InvalidOperationException($\"File type '{extension}' is not allowed. Allowed: {string.Join(\", \", rules.AllowedExtensions)}\");\n        }\n\n        if (request.Data.Count > rules.MaxSizeInMB * 1024 * 1024)\n        {\n            throw new InvalidOperationException($\"File exceeds max size of {rules.MaxSizeInMB} MB.\");\n        }\n\n        var key = BuildKey<T>(SanitizeFileName(request.FileName));\n\n        using var stream = new MemoryStream([.. request.Data]);\n\n        var putRequest = new PutObjectRequest\n        {\n            BucketName = _options.Bucket,\n            Key = key,\n            InputStream = stream,\n            ContentType = request.ContentType\n        };","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/BuildingBlocks/Storage/S3/S3StorageService.cs#L33-L69","documentation":"S3StorageService.UploadAsync resolves FileTypeRules and rejects uploads whose FileName extension is missing or not in rules.AllowedExtensions, throwing InvalidOperationException before any S3 call. Identical policy to the local provider — file types are whitelisted per FileType for security.","triggerScenarios":"UploadAsync against the S3 provider with a FileName lacking an extension or carrying an extension not whitelisted for that FileType.","commonSituations":"Switching from local to S3 storage with clients that previously bypassed validation; generated/synthetic files saved without extensions; extension casing or trailing whitespace; new file kinds added without updating FileTypeMetadata.","solutions":["Ensure the uploaded FileName has a whitelisted extension for the target FileType.","Add the required extension to rules.AllowedExtensions in FileTypeMetadata if legitimate.","Normalize file names on the client (trim, append correct extension) before upload.","Pre-validate extensions in the frontend to fail fast."],"exampleFix":"// before\nawait s3.UploadAsync(new UploadRequest { FileName = \"document.v1\", FileType = FileType.Document });\n\n// after\nawait s3.UploadAsync(new UploadRequest { FileName = \"document.pdf\", FileType = FileType.Document }); // valid extension","handlingStrategy":"validation","validationCode":"var ext = Path.GetExtension(fileName);\nvar rules = FileTypeMetadata.GetRules(fileType);\nif (string.IsNullOrWhiteSpace(ext) || !rules.AllowedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase))\n    throw new ArgumentException($\"Extension '{ext}' not allowed for {fileType}.\");","typeGuard":"bool IsAllowedExtension(string? ext, FileTypeRules rules) =>\n    !string.IsNullOrWhiteSpace(ext) && rules.AllowedExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);","tryCatchPattern":"try {\n    await s3Storage.UploadAsync(request);\n} catch (InvalidOperationException ex) when (ex.Message.StartsWith(\"File type\")) {\n    logger.LogWarning(\"Rejected disallowed upload: {Message}\", ex.Message);\n    return Results.BadRequest(ex.Message);\n}","preventionTips":["Validate extensions at the API boundary before reaching the storage provider.","Keep local and S3 providers on the same FileTypeMetadata rules so behavior doesn't drift.","Reject root-less/extension-less names early in the upload endpoint."],"tags":["storage","s3","file-validation","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"}