Client security fixes (cmd/tailscale-tray/main.go): - SSRF protection in Add Server dialog (validateControlURL): reject private/loopback/link-local/cloud-metadata IPs via DNS resolution - RCE gate on AuthURL/BrowseToURL exec paths (validateAuthURL) - Sanitized URL logging (sanitizeURLForLog drops query auth tokens) - Error handling on exec.Command with user-facing showError() Admin panel security (web-admin): - Bcrypt password hashing (replaces SHA256) - Rate limiting: 5 failed logins → 15-min lockout - Session + login attempt cleanup goroutine (hourly) - url.QueryEscape / encodeURIComponent for all API params - Fail-hard startup when no TLS and non-loopback bind - ADMIN_PASSWORD required (no default), password min 12 chars - Username regex whitelist Installer hardening (Setup.wxs): - util:PermissionEx restricts SCM access: only Administrators + SYSTEM can start/stop/reconfigure service. Authenticated Users limited to QueryStatus/QueryConfig/Interrogate - Vital="yes" on ServiceInstall Docs & roadmap: - PRODUCTION_ROADMAP.md: 5-milestone plan (security + features + distribution + ops) with granular tasks, effort, done-when - CLIENT_SECURITY_AUDIT.md, SECURITY_FIXES.md, DEPLOYMENT.md - AI assistant rules (.cursorrules, .antigravityrules, etc.) Build & distribution: - build-msi.ps1, deploy-and-sign.ps1, sign-release.ps1 - redeploy.ps1, tray-deploy.ps1, test-msi.ps1 - installer/msi/ alternative WXS setup - Restored .github/workflows/ removed in mirror cleanup .gitignore hardened: *.pfx, *.p12, *.key, *.pem, .env*
4.9 KiB
Blazor WASM Deployment Compression Optimization
Bối Cảnh
Khi deploy ứng dụng Blazor WebAssembly lên Docker/VPS, payload ban đầu rất nặng (~20MB uncompressed, ~15MB compressed). Ứng dụng phải tải hơn 80 file .wasm và .dll trước khi khởi động được. Nếu không tối ưu đúng cách, thời gian load trang có thể lên tới 3+ phút.
Các Tối Ưu Đã Áp Dụng
1. Conditional Response Compression (Tránh Double-Compression)
Vấn đề: Blazor WASM publish tự động tạo ra các file pre-compressed .br (Brotli) trong thư mục _framework/. Khi thêm middleware ResponseCompression vào pipeline, nó nén thêm lần nữa lên file đã nén → trình duyệt giải mã thất bại → TypeError: Failed to fetch.
Giải pháp: Dùng app.UseWhen() để áp dụng compression cho MỌI đường dẫn NGOẠI TRỪ /_framework:
// Program.cs — Services
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"image/svg+xml", "application/javascript", "text/css"
});
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
// Program.cs — Middleware (CONDITIONAL)
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/_framework"),
appBuilder => { appBuilder.UseResponseCompression(); }
);
Quy tắc vàng: KHÔNG BAO GIỜ nén
/_framework/*bằng middleware. Blazor đã tự xử lý quaUseBlazorFrameworkFiles().
2. Aggressive Static File Caching (365 ngày)
Vấn đề: Mỗi lần truy cập, trình duyệt tải lại toàn bộ 80+ file static (CSS, JS, WASM) vì không có Cache header.
Giải pháp: Set Cache-Control: public,max-age=31536000 (1 năm) cho tất cả static files:
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=31536000");
}
});
Lần đầu load chậm, nhưng từ lần 2 trở đi load gần như tức thì vì mọi thứ đã cached.
3. Assembly Lazy Loading (Giảm 4MB payload)
Vấn đề: DevExpress PDF và Printing DLLs chiếm tổng ~4MB compressed nhưng chỉ dùng khi xuất file/in ấn.
Giải pháp: Thêm BlazorWebAssemblyLazyLoad vào .csproj:
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="DevExpress.Pdf.v24.2.Core.wasm" />
<BlazorWebAssemblyLazyLoad Include="DevExpress.Printing.v24.2.Core.wasm" />
</ItemGroup>
Kết quả: Giảm initial payload từ ~15MB → ~11MB compressed.
4. HTTP/1.1 vs HTTP/2 (Nút thắt cổ chai lớn nhất)
Vấn đề CRITICAL: Khi truy cập qua http://IP:port (HTTP thuần), trình duyệt bắt buộc dùng HTTP/1.1 với giới hạn tối đa 6 kết nối đồng thời cho mỗi domain. Với 80+ file WASM, trình duyệt phải xếp hàng tải từng batch 6 file → cực kỳ chậm (2-3 phút).
Giải pháp: Truy cập qua HTTPS domain (ví dụ: https://sso.instratech.net/hrm/). HTTPS tự động kích hoạt HTTP/2 Multiplexing — tải tất cả 80 file cùng lúc trên 1 kết nối duy nhất.
| Phương thức | Protocol | Song song tối đa | Thời gian load |
|---|---|---|---|
http://IP:8010 |
HTTP/1.1 | 6 files | ~2.5 phút |
https://domain/hrm/ |
HTTP/2 | Unlimited | ~10-15 giây |
Bài học: LUÔN deploy Blazor WASM đằng sau reverse proxy có HTTPS (Nginx, Cloudflare, etc.) để tận dụng HTTP/2.
Thứ Tự Middleware Đúng Trong Pipeline
UseResponseCompression() ← CHỈ cho non-framework paths
UseBlazorFrameworkFiles() ← Serve pre-compressed _framework/*.br files
UseStaticFiles() ← Serve CSS/JS/images với cache headers
Kiểm Tra Nhanh
# Verify Brotli compression đang hoạt động cho _framework files:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_framework/blazor.webassembly.js
# Phải thấy: Content-Encoding: br
# Verify non-framework files cũng được nén:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_content/DevExpress.Blazor/modules/dx-blazor-all.js
# Phải thấy: Content-Encoding: br (từ middleware)
# Verify cache headers:
curl -I http://localhost:8010/app.css
# Phải thấy: Cache-Control: public,max-age=31536000
Tổng Hợp Kết Quả
| Tối ưu | Tiết kiệm |
|---|---|
| Conditional Brotli Compression | ~60-70% kích thước CSS/JS |
| Lazy Loading (PDF + Printing) | ~4MB compressed |
| Cache 365 ngày | Lần 2+ load tức thì |
| HTTP/2 Multiplexing | Từ 2.5 phút → 10-15s |