DOTNET Contents

Static Files (Security Foot-guns)

Static files are a security boundary. Learn safe hosting, path traversal risks, cache headers, content-type sniffing, and how misconfigured static files leak secrets or enable XSS.

On this page

Static Files Can Be a Data Leak

Serving static files sounds harmless until you ship: - config backups - .env files - source maps with secrets - internal admin UIs - user-uploaded files from the same directory as app assets Static file hosting is an attack surface. Treat it as one.

Real Production Incident

Symptoms: - Security team reports that production secrets were exposed publicly. - Attackers downloaded a file that should never be public. - Incident response discovers that backups were accessible under /static/. Root cause: - Static files middleware was configured to serve from a directory that also contained deployment artifacts. - A “temporary” debug dump file was left on disk. - No denylist for sensitive extensions. - No CDN boundary; app served static directly. This is not “oops”. This is preventable design failure.

Symptom → Cause → Diagnosis → Fix

Symptom: - Unexpected files accessible via HTTP (e.g., /appsettings.json, /.env, /backup.zip) - Strange 200 responses for paths that should 404 - Security findings: exposed internal assets Cause: - Static file root includes sensitive directories - Misconfigured request path mappings - Serving user uploads via static middleware - Missing security headers and content-type controls Diagnosis: - Enumerate accessible static paths with automated scanning. - Check app container filesystem layout. - Verify StaticFileOptions and file providers. - Inspect response headers (cache, content-type, nosniff). Fix: - Strictly separate app assets from everything else. - Never serve secrets or build artifacts from the same root. - Lock down static file mappings and headers. - Consider serving static via CDN/object storage.

Anti-Pattern: Serving the Wrong Directory

This is how you leak files:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider("/app"),
    RequestPath = ""
});
If /app contains: - configs - logs - dumps - backups You just exposed them. Production rule: Static file root must contain only public assets.

Correct Pattern: Dedicated Static Root

Keep public assets in a dedicated directory (wwwroot) and nothing else. Basic safe setup:
app.UseStaticFiles();
If you must serve a custom directory, do it explicitly and narrowly:
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider("/app/public"),
    RequestPath = "/static"
});
And ensure /app/public contains only safe, intended files.

User Uploads Are Not Static Assets

Never serve user uploads from the same middleware/root as your app assets. Reasons: - content-type spoofing - stored XSS via SVG/HTML - cache poisoning - access control requirements Better patterns: - store uploads in object storage (S3, Blob Storage) - use signed URLs - scan files (AV) - enforce content-type and disposition If you must serve uploads yourself: - isolate directory - enforce strict headers - deny dangerous extensions

Content-Type Sniffing and XSS Risk

Browsers may sniff content type. If you serve untrusted content without protections, you can enable XSS. Set: - X-Content-Type-Options: nosniff Example header middleware (conceptual):
app.Use(async (ctx, next) =>
{
    ctx.Response.Headers["X-Content-Type-Options"] = "nosniff";
    await next();
});
Production rule: Static responses should include nosniff. Especially if any user content is involved.

Cache-Control and Versioning

Static assets should be cached aggressively if they are versioned (hash in filename). If you do not version assets: - you will ship broken UIs due to stale caches - rollback becomes messy Recommended: - /app.js?v=hash or app.hash.js - Cache-Control: public, max-age=31536000, immutable (for versioned files) - Short cache for non-versioned assets But do not blindly cache sensitive content.

Directory Browsing and File Enumeration

Do not enable directory browsing. Even if it seems harmless, it increases information disclosure: - file names - build artifacts - internal structures Production rule: If users can list directories, you have already lost control.

Path Traversal Considerations

ASP.NET Core static file middleware does not simply “join strings”, but mistakes happen when people build custom file endpoints. Anti-pattern custom endpoint:
app.MapGet("/files/{name}", (string name) =>
{
    var path = "/uploads/" + name;
    return Results.File(path);
});
This invites traversal attempts like ../../etc/passwd (platform dependent). If you must do dynamic file serving: - normalize paths - enforce allowlist - ensure resolved path is under the intended root

Operational Notes

Monitoring: - alert on requests for sensitive filenames (.env, appsettings.json, backup.zip) - track 404 spikes for scanning patterns - monitor bandwidth anomalies (static abuse) Rollout: - verify filesystem layout in container image - run security scan for exposed files before production rollout Rollback: - if you leak a file, rollback is not enough: - rotate secrets - invalidate caches/CDN - purge leaked artifacts - audit access logs Risk management: - prefer CDN/object storage for static and uploads - keep app instances for API traffic, not bulk file serving

Checklist

- Static root contains only intended public assets (no configs, logs, backups). - User uploads are isolated and not served as generic static assets. - X-Content-Type-Options: nosniff is present for static responses. - Cache-Control strategy defined (versioned assets cached aggressively). - Directory browsing is not enabled. - Requests for sensitive filenames are monitored and alerted. - Pre-deploy scanning verifies no secrets or artifacts are publicly accessible. - Incident plan includes secret rotation and cache invalidation for leaks.