bitwarden/server · error · BadRequestException

Invalid content.

Error message

Invalid content.

What it means

Thrown by POST /reports/organizations/{organizationId}/{reportId}/file (self-hosted upload endpoint) when the request Content-Type does not contain 'multipart/'. The endpoint uses DisableFormValueModelBinding and Request.GetFileAsync to stream the multipart body, so a non-multipart request cannot be parsed. Self-hosted only.

Source

Thrown at src/Api/Dirt/Controllers/OrganizationReportsController.cs:391

    /// Uploads a report data file for a self-hosted organization report via multipart form data.
    /// Validates the uploaded file size against the expected size (with a 1 MB leeway) and marks
    /// the report file as validated upon success. Requires the Access Intelligence new-architecture feature flag.
    /// </summary>
    /// <param name="organizationId">The unique identifier of the organization.</param>
    /// <param name="reportId">The unique identifier of the report to attach the file to.</param>
    /// <param name="reportFileId">The identifier of the report file entry to upload against.</param>
    [RequireFeature(FeatureFlagKeys.AccessIntelligenceNewArchitecture)]
    [HttpPost("{organizationId}/{reportId}/file")]
    [SelfHosted(SelfHostedOnly = true)]
    [RequestSizeLimit(Constants.FileSize501mb)]
    [DisableFormValueModelBinding]
    public async Task UploadReportFileAsync(Guid organizationId, Guid reportId, [FromQuery] string reportFileId)
    {
        var report = await GetAuthorizedReportAsync(organizationId, reportId);

        if (!Request?.ContentType?.Contains("multipart/") ?? true)
        {
            throw new BadRequestException("Invalid content.");
        }

        if (string.IsNullOrEmpty(reportFileId))
        {
            throw new BadRequestException("ReportFileId query parameter is required");
        }

        var fileData = report.GetReportFile();
        if (fileData == null || fileData.Id != reportFileId || fileData.Validated)
        {
            throw new NotFoundException();
        }

        await Request.GetFileAsync(async (stream) =>
        {
            await _storageService.UploadReportDataAsync(report, fileData, stream);
        });

View on GitHub (pinned to e93b962371)

Solutions

  1. Send the file as multipart/form-data: in .NET use MultipartFormDataContent; in curl use -F 'file=@path'.
  2. Ensure the boundary parameter is present in Content-Type (most HTTP clients add it automatically when using multipart helpers).
  3. Verify no intermediary proxy rewrites or strips the Content-Type header.

Example fix

// before
var content = new StreamContent(fileStream); // Content-Type: application/octet-stream
await client.PostAsync(uploadUrl, content);
// after
using var form = new MultipartFormDataContent();
form.Add(new StreamContent(fileStream), "file", fileName);
await client.PostAsync(uploadUrl, form);
Defensive patterns

Strategy: validation

Validate before calling

var contentType = httpClient.DefaultRequestHeaders; // ensure per-request content type
if (!content.Headers.ContentType?.MediaType?.StartsWith("multipart/") ?? true)
    throw new InvalidOperationException("Upload must be multipart/form-data.");

Try / catch

try { await client.PostAsync(uploadUrl, multipartContent); }
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.BadRequest && ex.Message.Contains("Invalid content"))
{ /* rebuild as MultipartFormDataContent and retry */ }

Prevention

When it happens

Trigger: Client POSTs the file with Content-Type application/octet-stream, application/json, or omits Content-Type entirely instead of multipart/form-data with a proper boundary.

Common situations: Using HttpClient.PostAsync with raw StreamContent instead of MultipartFormDataContent; curl invocation missing -F; proxy stripping the Content-Type header or boundary; client library defaulting to JSON for binary payloads.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/587b0b521779411d. Report an issue: GitHub.