fix(cv): harden document parsing

Upgrade and hash-lock upload-facing parser dependencies, reject resource-heavy or mismatched inputs, remove unsafe backend binary fallbacks, and prevent internal parser failures from leaking to users.
This commit is contained in:
cesnimda
2026-08-30 11:00:52 +02:00
parent a74daa7aa4
commit a8bf505ce5
14 changed files with 1869 additions and 110 deletions
@@ -1,5 +1,6 @@
using System.Reflection;
using System.IO.Compression;
using System.Text;
using JobTrackerApi.Controllers;
using JobTrackerApi.Models;
using Microsoft.AspNetCore.Http;
@@ -241,7 +242,7 @@ public sealed class CvExtractionCoverageTests
}
[Fact]
public async Task Docx_fallback_preserves_paragraph_and_table_boundaries()
public async Task Binary_document_fallback_is_disabled()
{
await using var package = new MemoryStream();
using (var archive = new ZipArchive(package, ZipArchiveMode.Create, leaveOpen: true))
@@ -263,14 +264,23 @@ public sealed class CvExtractionCoverageTests
}
package.Position = 0;
var upload = new FormFile(package, 0, package.Length, "file", "sanitized.docx");
var method = typeof(ProfileCvController).GetMethod("ExtractTextAsync", BindingFlags.NonPublic | BindingFlags.Static)!;
var task = Assert.IsAssignableFrom<Task<string>>(method.Invoke(null, new object[] { upload, ".docx" }));
var method = typeof(ProfileCvController).GetMethod("ExtractPlainTextAsync", BindingFlags.NonPublic | BindingFlags.Static)!;
var task = Assert.IsAssignableFrom<Task<string>>(method.Invoke(null, new object[] { upload, ".docx", CancellationToken.None }));
var extracted = await task;
Assert.Contains("Technical Skills\n", extracted);
Assert.Contains("Backend | C#, .NET", extracted);
Assert.Contains("DevOps | Docker, Linux", extracted);
Assert.Contains("\n\nExample Engineer\n- Built reliable services", extracted);
Assert.Empty(extracted);
}
[Fact]
public async Task Plain_text_fallback_reads_bounded_utf8()
{
const string source = "# Ada Lovelace\n\n## Skills\nC#\nSQL";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(source));
var upload = new FormFile(stream, 0, stream.Length, "file", "resume.md");
var method = typeof(ProfileCvController).GetMethod("ExtractPlainTextAsync", BindingFlags.NonPublic | BindingFlags.Static)!;
var task = Assert.IsAssignableFrom<Task<string>>(method.Invoke(null, new object[] { upload, ".md", CancellationToken.None }));
Assert.Equal(source, await task);
}
private static StructuredCvProfile InvokeProfileBuilder(string markdown)