Skip to content

Commit 58fe852

Browse files
authored
Fix/security audit - 2026/04/11 (#13)
* fix(security): remediate all 16 findings from security audit Address all findings from the 2026-04-11 security evaluation: HIGH: - SEC-001: Replace unbounded sync.Map PathCache with bounded LRU (hashicorp/golang-lru) to prevent memory exhaustion DoS MEDIUM: - SEC-003: Make panic stack traces configurable via STATIC_DEBUG env var - SEC-004: Generate random multipart boundary per response (crypto/rand) - SEC-005: Add MaxCompressSize (10MB) limit for on-the-fly gzip - SEC-006: Apply path.Clean in CacheKeyForPath to prevent cache poisoning LOW: - SEC-007: Suppress server name disclosure (empty Name field) - SEC-008: Sanitize control characters in access log URIs - SEC-009: Remove deprecated PreferServerCipherSuites TLS option - SEC-010: Handle template execution errors in directory listing - SEC-011: Add MaxServeFileSize (1GB) hard limit for large file serving - SEC-012: Add clarifying comment on CORS wildcard Vary behavior - SEC-013: Document ETag 64-bit truncation rationale - SEC-014: Set explicit MaxRequestBodySize (1024 bytes) - SEC-015: Add MaxConnsPerIP config support for rate limiting - SEC-016: Validate symlink targets during cache preload Also updates dependencies: - brotli v1.2.0 → v1.2.1 - klauspost/compress v1.18.4 → v1.18.5 - fasthttp v1.69.0 → v1.70.0 * docs: update all documentation for security audit remediations - Landing page (docs/index.html): add 3 new config fields to tables, update security tabs (DoS, TLS, runtime), architecture pipeline descriptions, and feature cards - README.md: update architecture diagram, config tables (+3 fields), env vars (+3 vars), DoS mitigations, and path-safety cache design - USER_GUIDE.md: update config example, env vars table, preload section (symlink validation, bounded LRU), add 413 troubleshooting entry - config.toml.example: add max_compress_size to [compression] section - CHANGELOG.md: add v1.6.2 entry covering all 16 security fixes, dependency bumps, and documentation updates * docs: revert CHANGELOG.md — will be auto-generated by Commitizen * docs: mark all 16 security findings as resolved in audit report Update SECURITY_EVAL_2026-04-11.md with resolution status for each finding, upgrade overall grade from B+ to A, expand remediation plan table with Status column covering all 16 items (previously grouped SEC-012–016 as backlog). * test: add tests and harden code review items - Add TestBuildHandler_MaxServeFileSize (under/over/disabled) - Add TestMiddleware_MaxCompressSize (under/over/at-limit/disabled) - Expand TestCacheKeyForPath with path normalization edge cases - Harden generateBoundary with math/rand/v2 fallback on crypto/rand failure - Improve 413 response message with dynamic size limit - Add log.Printf for template execution errors in directory listing * test: add PathCache LRU and server security-defaults coverage - Add TestPathCache_BoundedLRU: Len() never exceeds maxEntries after overflow - Add TestPathCache_LookupPromotesEntry: LRU promotion keeps touched keys - Add TestPathCache_FlushClearsAll: Purge empties cache completely - Add TestPathCache_DefaultSizeOnZero: fallback to DefaultPathCacheSize - Add TestNew_HTTPOnly_SecurityDefaults: Name, MaxRequestBodySize, MaxConnsPerIP - Add TestNew_TLS_SecurityDefaults: same checks on both HTTP and HTTPS servers - Add TestNew_MaxConnsPerIP_Zero: disabled state passes through correctly
1 parent fcfe429 commit 58fe852

File tree

22 files changed

+1435
-70
lines changed

22 files changed

+1435
-70
lines changed

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ HTTP request
9191
│ • Method whitelist (GET/HEAD/OPTIONS only) │
9292
│ • Security headers (set BEFORE path check) │
9393
│ • PathSafe: null bytes, path.Clean, EvalSymlinks│
94-
│ • Path-safety cache (sync.Map, pre-warmed)
94+
│ • Path-safety cache (bounded LRU, pre-warmed) │
9595
│ • Dotfile blocking │
9696
│ • CORS (preflight + per-origin or wildcard *) │
9797
│ • Injects validated path into ctx.SetUserValue │
@@ -129,7 +129,7 @@ GET /app.js
129129
NO → os.Stat → os.ReadFile → cache.Put → serveFromCache
130130
```
131131

132-
When `preload = true`, every eligible file is loaded into cache at startup. The path-safety cache (`sync.Map`) is also pre-warmed, so the very first request for any preloaded file skips both filesystem I/O and `EvalSymlinks`.
132+
When `preload = true`, every eligible file is loaded into cache at startup. The path-safety cache (bounded LRU) is also pre-warmed, so the very first request for any preloaded file skips both filesystem I/O and `EvalSymlinks`. Symlink targets are validated against the root during the preload walk — symlinks pointing outside root are skipped.
133133

134134
---
135135

@@ -164,7 +164,7 @@ Measured on Apple M2 Pro (`go test -bench=. -benchtime=5s`):
164164
- **Direct `ctx.SetBody()` fast path**: cache hits bypass range/conditional logic entirely; pre-formatted `Content-Type` and `Content-Length` headers are assigned directly.
165165
- **Custom Range implementation**: `parseRange()`/`serveRange()` handle byte-range requests without `http.ServeContent`.
166166
- **Post-processing compression**: compress middleware runs after the handler, compressing the response body in a single pass.
167-
- **Path-safety cache**: `sync.Map`-based cache eliminates per-request `filepath.EvalSymlinks` syscalls. Pre-warmed from preload.
167+
- **Path-safety cache**: Bounded LRU cache (default 10,000 entries) eliminates per-request `filepath.EvalSymlinks` syscalls. Pre-warmed from preload.
168168
- **GC tuning**: `gc_percent = 400` reduces garbage collection frequency — the hot path avoids all formatting allocations, with only minimal byte-to-string conversions from fasthttp's `[]byte` API.
169169
- **Cache-before-stat**: `os.Stat` is never called on a cache hit — the hot path is pure memory.
170170
- **Zero-alloc `AcceptsEncoding`**: walks the `Accept-Encoding` header byte-by-byte without `strings.Split`.
@@ -214,7 +214,8 @@ Only `GET`, `HEAD`, and `OPTIONS` are accepted. All other methods (including `TR
214214
| `ReadTimeout` | 10 s (covers full read phase including headers — Slowloris protection) |
215215
| `WriteTimeout` | 10 s |
216216
| `IdleTimeout` | 75 s (keep-alive) |
217-
| `MaxRequestBodySize` | 0 (no body accepted — static server) |
217+
| `MaxRequestBodySize` | 1024 bytes (static file server needs no large request bodies) |
218+
| `MaxConnsPerIP` | Configurable (default 0 = unlimited) |
218219

219220
---
220221

@@ -235,6 +236,7 @@ Copy `config.toml.example` to `config.toml` and edit as needed. The server start
235236
| `write_timeout` | duration | `10s` | Response write deadline |
236237
| `idle_timeout` | duration | `75s` | Keep-alive idle timeout |
237238
| `shutdown_timeout` | duration | `15s` | Graceful drain window |
239+
| `max_conns_per_ip` | int | `0` | Max concurrent connections per IP (0 = unlimited) |
238240

239241
### `[files]`
240242

@@ -243,6 +245,7 @@ Copy `config.toml.example` to `config.toml` and edit as needed. The server start
243245
| `root` | string | `./public` | Directory to serve |
244246
| `index` | string | `index.html` | Index file for directory requests |
245247
| `not_found` | string || Custom 404 page (relative to `root`) |
248+
| `max_serve_file_size` | int | `1073741824` | Max file size to serve in bytes (0 = unlimited; default 1 GB). Files exceeding this limit receive 413. |
246249

247250
### `[cache]`
248251

@@ -263,6 +266,7 @@ Copy `config.toml.example` to `config.toml` and edit as needed. The server start
263266
| `min_size` | int | `1024` | Minimum bytes to compress |
264267
| `level` | int | `5` | gzip level (1–9) |
265268
| `precompressed` | bool | `true` | Serve `.gz`/`.br`/`.zst` sidecar files |
269+
| `max_compress_size` | int | `10485760` | Max body size for on-the-fly gzip compression in bytes (0 = unlimited; default 10 MB) |
266270

267271
### `[headers]`
268272

@@ -303,9 +307,11 @@ All environment variables override the corresponding TOML setting. Useful for co
303307
| `STATIC_SERVER_WRITE_TIMEOUT` | `server.write_timeout` |
304308
| `STATIC_SERVER_IDLE_TIMEOUT` | `server.idle_timeout` |
305309
| `STATIC_SERVER_SHUTDOWN_TIMEOUT` | `server.shutdown_timeout` |
310+
| `STATIC_SERVER_MAX_CONNS_PER_IP` | `server.max_conns_per_ip` |
306311
| `STATIC_FILES_ROOT` | `files.root` |
307312
| `STATIC_FILES_INDEX` | `files.index` |
308313
| `STATIC_FILES_NOT_FOUND` | `files.not_found` |
314+
| `STATIC_FILES_MAX_SERVE_FILE_SIZE` | `files.max_serve_file_size` |
309315
| `STATIC_CACHE_ENABLED` | `cache.enabled` |
310316
| `STATIC_CACHE_PRELOAD` | `cache.preload` |
311317
| `STATIC_CACHE_MAX_BYTES` | `cache.max_bytes` |
@@ -315,6 +321,7 @@ All environment variables override the corresponding TOML setting. Useful for co
315321
| `STATIC_COMPRESSION_ENABLED` | `compression.enabled` |
316322
| `STATIC_COMPRESSION_MIN_SIZE` | `compression.min_size` |
317323
| `STATIC_COMPRESSION_LEVEL` | `compression.level` |
324+
| `STATIC_COMPRESSION_MAX_COMPRESS_SIZE` | `compression.max_compress_size` |
318325
| `STATIC_HEADERS_ENABLE_ETAGS` | `headers.enable_etags` |
319326
| `STATIC_SECURITY_BLOCK_DOTFILES` | `security.block_dotfiles` |
320327
| `STATIC_SECURITY_CSP` | `security.csp` |

0 commit comments

Comments
 (0)