Kareadita/Kavita · warning · KavitaException
invalid-filename
invalid-filename
Error message
invalid-filename
What it means
Thrown by UploadController.CreateThumbnail (localized key 'invalid-filename') when ResolveTempCoverPath returns null — i.e. the FileName either escapes the temp directory (IsPathWithinDirectory fails, blocking path traversal) or does not point to an existing staged file. A KavitaException surfacing as HTTP 500 unless the endpoint catches it.
Source
Thrown at Kavita.Server/Controllers/UploadController.cs:365
{
if (!IsPathWithinDirectory(_directoryService.TempDirectory, fileName)) return null;
var path = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory, fileName);
return _directoryService.FileSystem.File.Exists(path) ? path : null;
}
private async Task<string> CreateThumbnail(UploadCoverFileDto uploadCoverFileDto, string filename)
{
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
var encodeFormat = settings.EncodeMediaAs;
var (width, height) = settings.CoverImageSize.GetDimensions();
// Preferred path: the image was already streamed into temp (upload-by-url / upload-by-file) and we only
// received its filename. This avoids posting a large base64 payload back through the request body.
if (!string.IsNullOrEmpty(uploadCoverFileDto.FileName))
{
var tempPath = ResolveTempCoverPath(uploadCoverFileDto.FileName)
?? throw new KavitaException(await _localizationService.TranslateAsync(UserId, "invalid-filename"));
return _imageService.CreateThumbnailFromFile(tempPath, filename, encodeFormat, width, height);
}
// Legacy fallback: base64 payload
return _imageService.CreateThumbnailFromBase64(uploadCoverFileDto.Url, filename, encodeFormat, width, height);
}
/// <summary>
/// Replaces chapter cover image and locks it with a base64 encoded image. This will update the parent volume's cover image.
/// </summary>
/// <param name="uploadCoverFileDto"></param>
/// <returns></returns>
[Authorize(Policy = PolicyGroups.AdminPolicy)]
[RequestSizeLimit(ControllerConstants.MaxUploadSizeBytes)]
[HttpPost("chapter")]
public async Task<ActionResult> UploadChapterCoverImageFromUrl(UploadCoverFileDto uploadCoverFileDto)
{View on GitHub (pinned to 9c3e540000)
Solutions
- Always stage the file into Kavita's temp directory first (upload-by-file/upload-by-url), then send only its base filename.
- Send the bare filename, not an absolute or relative path.
- Ensure the temp upload completes and persists until the thumbnail request runs.
Example fix
// before
{ "fileName": "../../../etc/passwd" }
// after
{ "fileName": "a1b2c3-cover.jpg" } // previously staged into temp Defensive patterns
Strategy: validation
Validate before calling
var path = ResolveTempCoverPath(dto.FileName);
if (path is null) return BadRequest("invalid-filename"); Type guard
static bool IsSafeTempFileName(string? name)
=> !string.IsNullOrWhiteSpace(name)
&& Path.GetFileName(name) == name
&& !name.Contains(".."); Try / catch
try { var p = await CreateThumbnail(dto, file); }
catch (KavitaException ex) { return BadRequest(ex.Message); } Prevention
- Stage the file to temp first, then send only the base filename.
- Never send absolute or relative paths as FileName.
- Confirm the temp upload persists until thumbnail creation.
When it happens
Trigger: Upload-by-filename cover replace where FileName is missing from temp, contains traversal (../), points outside TempDirectory, or the staged temp upload expired/was cleared before the thumbnail step.
Common situations: Client sends a filename without first streaming the file to temp; path traversal attempt; temp dir cleared between upload and thumbnail creation; wrong filename casing/path from the client.
Related errors
- errors.theme-already-in-use
- url-blocked-address
- {comparison} is not applicable for {fieldName}
- Name must be set
- You cannot use the name of a system provided stream
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/c314b9b7aa1b6aa7.
Report an issue: GitHub.