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.
What it does
Section titled “What it does”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):
| Signal | What the LLM receives | Output |
|---|---|---|
| Filename | invoice-2026-03-paris.pdf | Category, tags |
| MIME type | application/pdf | Confidence score |
| Filename patterns | jean.dupont.ssn.pdf | ContainsPiiInFileName = 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 providerpublic class AppModule : GranitModule { }builder.AddGranitAI();builder.AddGranitAIOllama();builder.AddGranitBlobStorageAI();{ "AI": { "BlobStorage": { "WorkspaceName": "default", "TimeoutSeconds": 10, "EnablePiiDetection": true } }}Once registered, classification happens automatically during upload — no changes to your upload endpoints required.
Classification result
Section titled “Classification result”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 PIICategory examples
Section titled “Category examples”| Filename | Category | Tags | PII in filename |
|---|---|---|---|
invoice-2026-03.pdf | invoice | financial | No |
contrat-cdi-martin.docx | contract | legal, personal | Yes |
photo_produit_chaise.jpg | photo | product | No |
id_card_john_doe.png | identity_document | personal, government | Yes |
rapport-q4-2025.xlsx | report | financial | No |
Using the classifier directly
Section titled “Using the classifier directly”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); }}PII detection in filenames
Section titled “PII detection in filenames”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 decoratorpublic 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(); }}Async classification (Wolverine)
Section titled “Async classification (Wolverine)”For upload pipelines where classification latency matters, run it asynchronously:
// Upload endpoint — returns immediatelyapp.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 backgroundpublic 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); }}Soft dependency
Section titled “Soft dependency”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.AIRemove the NuGet package and the upload pipeline continues working unchanged — without classification.
Configuration reference
Section titled “Configuration reference”| Property | Type | Default | Description |
|---|---|---|---|
WorkspaceName | string? | null (default workspace) | AI workspace for classification |
TimeoutSeconds | int | 10 | LLM call timeout |
EnablePiiDetection | bool | true | Flag filenames containing PII patterns |
Risks and limitations
Section titled “Risks and limitations”| Risk | Mitigation |
|---|---|
| LLM timeout | Configurable timeout (default 10s); validation step returns Success on timeout so upload is not blocked |
| False positives on PII | ContainsPiiInFileName is informational; blocking requires an explicit validator (see example above) |
| MIME type spoofing | AI only sees the declared MIME type, not file content — combine with byte-level validators for security-critical uploads |
| Cost | Called once per upload; filename + MIME type = small prompt, minimal token cost |
See also
Section titled “See also”- Granit.AI setup — providers, workspaces
- Blob Storage — the storage module
- AI: PII Detection — scan text content for PII