Skip to content

Blob Storage Intelligence

When a user uploads jean-dupont-passport-scan.pdf, your storage module has no way to know it contains a sensitive identity document — or that the filename itself exposes a person’s name. Granit.BlobStorage.AI adds an intelligence layer that classifies files and detects PII in filenames before they reach permanent storage.

Every blob uploaded through Granit.BlobStorage optionally passes through an AI classification step that runs as an IBlobValidator (Order = 100, after fast byte-level checks):

SignalWhat the LLM receivesOutput
Filenameinvoice-2026-03-paris.pdfCategory, tags
MIME typeapplication/pdfConfidence score
Filename patternsjean.dupont.ssn.pdfContainsPiiInFileName = true

The LLM never receives file content — only the filename and declared MIME type. This keeps the feature lightweight and GDPR-safe by default.

[DependsOn(
typeof(GranitBlobStorageAIModule),
typeof(GranitAIOllamaModule))] // or any provider
public class AppModule : GranitModule { }

Once registered, classification happens automatically during upload — no changes to your upload endpoints required.

The BlobClassification record exposes four fields:

public sealed record BlobClassification(
string Category, // "invoice", "identity_document", "photo", "contract", ...
double Confidence, // 0.0 – 1.0
IReadOnlyList<string> DetectedTags, // ["financial", "legal", "personal", ...]
bool ContainsPiiInFileName); // true if filename appears to contain PII
FilenameCategoryTagsPII in filename
invoice-2026-03.pdfinvoicefinancialNo
contrat-cdi-martin.docxcontractlegal, personalYes
photo_produit_chaise.jpgphotoproductNo
id_card_john_doe.pngidentity_documentpersonal, governmentYes
rapport-q4-2025.xlsxreportfinancialNo

In addition to the automatic validator pipeline, IAIBlobClassifier can be injected and called explicitly — useful for building classification-aware UI flows:

public class UploadService(IAIBlobClassifier classifier, IBlobStorage storage)
{
public async Task<UploadResult> UploadAsync(
Stream content,
string fileName,
string contentType,
CancellationToken ct)
{
BlobClassification classification = await classifier
.ClassifyAsync(fileName, contentType, ct)
.ConfigureAwait(false);
if (classification.ContainsPiiInFileName)
{
// Sanitize the filename before storage
fileName = SanitizeFileName(fileName);
}
string key = await storage.StoreAsync(content, fileName, contentType, ct)
.ConfigureAwait(false);
return new UploadResult(key, classification.Category, classification.Tags);
}
}

When EnablePiiDetection: true (the default), the classifier flags filenames that contain personal identifiers — names, email addresses, phone numbers, national IDs — before the file is stored.

When ContainsPiiInFileName is true in the validator pipeline, the upload is not automatically rejected — it returns a validation warning so the calling code can decide:

// Custom IBlobValidator decorator
public class PiiFilenameBlocker(IAIBlobClassifier classifier) : IBlobValidator
{
public int Order => 200; // runs after AI classifier (Order = 100)
public async Task<BlobValidationResult> ValidateAsync(BlobUploadContext ctx, CancellationToken ct)
{
BlobClassification result = await classifier
.ClassifyAsync(ctx.FileName, ctx.ContentType, ct)
.ConfigureAwait(false);
return result.ContainsPiiInFileName
? BlobValidationResult.Failure("Filename contains personal information. Please rename the file.")
: BlobValidationResult.Success();
}
}

For upload pipelines where classification latency matters, run it asynchronously:

// Upload endpoint — returns immediately
app.MapPost("/upload", async (IFormFile file, IBlobStorage storage, IMessageBus bus, CancellationToken ct) =>
{
string key = await storage.StoreAsync(file.OpenReadStream(), file.FileName, file.ContentType, ct);
await bus.PublishAsync(new BlobUploadedEvent(key, file.FileName, file.ContentType));
return TypedResults.Accepted($"/uploads/{key}");
});
// Wolverine handler — runs in background
public static class BlobUploadedEventHandler
{
public static async Task Handle(
BlobUploadedEvent evt,
IAIBlobClassifier classifier,
IBlobDescriptorWriter writer,
CancellationToken ct)
{
BlobClassification result = await classifier
.ClassifyAsync(evt.FileName, evt.ContentType, ct)
.ConfigureAwait(false);
await writer.UpdateTagsAsync(evt.Key, result.DetectedTags, ct).ConfigureAwait(false);
}
}

Granit.BlobStorage has zero knowledge of this package. Classification is an additive concern:

Granit.BlobStorage → defines IBlobValidator pipeline
(no reference to any AI package)
Granit.BlobStorage.AI → implements IAIBlobClassifier
references Granit.BlobStorage registers as IBlobValidator (Order = 100)
references Granit.AI

Remove the NuGet package and the upload pipeline continues working unchanged — without classification.

PropertyTypeDefaultDescription
WorkspaceNamestring?null (default workspace)AI workspace for classification
TimeoutSecondsint10LLM call timeout
EnablePiiDetectionbooltrueFlag filenames containing PII patterns
RiskMitigation
LLM timeoutConfigurable timeout (default 10s); validation step returns Success on timeout so upload is not blocked
False positives on PIIContainsPiiInFileName is informational; blocking requires an explicit validator (see example above)
MIME type spoofingAI only sees the declared MIME type, not file content — combine with byte-level validators for security-critical uploads
CostCalled once per upload; filename + MIME type = small prompt, minimal token cost