Skip to content

Commit f60e574

Browse files
committed
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).
1 parent b84a70c commit f60e574

1 file changed

Lines changed: 57 additions & 35 deletions

File tree

SECURITY_EVAL_2026-04-11.md

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,39 @@
44
**Language:** Go 1.26
55
**Framework:** valyala/fasthttp
66
**Audit Date:** April 11, 2026
7-
**Overall Grade:** **B+**
8-
**Findings:** 0 CRITICAL · 1 HIGH · 5 MEDIUM · 5 LOW · 5 INFO
7+
**Overall Grade:** **B+****A** (post-remediation)
8+
**Findings:** 0 CRITICAL · 1 HIGH · 5 MEDIUM · 5 LOW · 5 INFO**All 16 resolved**
99

1010
---
1111

1212
## Executive Summary
1313

1414
`static-web` demonstrates strong security fundamentals — multi-layer path traversal prevention, XSS-safe templating, excellent TLS configuration, and a CI pipeline with `govulncheck` and race detection. The single HIGH-severity finding is an unbounded in-memory path cache (`sync.Map`) that enables a straightforward memory exhaustion DoS. Five MEDIUM findings cover weakened shipped defaults, compression resource limits, server fingerprinting, cache key normalization, and verbose panic logging. No critical vulnerabilities were found.
1515

16+
> **Remediation Status:** All 16 findings have been addressed in branch `fix/security-audit-remediations` (commits `d26183c`, `6c1948d`). The overall grade has been upgraded from **B+** to **A**.
17+
1618
---
1719

1820
## Findings Summary
1921

20-
| # | Finding | Severity | Category | File |
21-
| ------- | ---------------------------------------------- | -------------- | -------------------- | ------------------------------- |
22-
| SEC-001 | Unbounded `PathCache` (DoS) | **HIGH** | Resource Exhaustion | `security/security.go:49–70` |
23-
| SEC-002 | Shipped `config.toml` weakens code defaults | MEDIUM | Misconfiguration | `config.toml:28–38` |
24-
| SEC-003 | Full stack traces logged on panic | MEDIUM | Information Disclosure | `handler/middleware.go:121–132` |
25-
| SEC-004 | Static multipart range boundary | MEDIUM | Fingerprinting | `handler/file.go:615, 659` |
26-
| SEC-005 | No max body size for gzip compression | MEDIUM | Resource Exhaustion | `compress/compress.go:170–187` |
27-
| SEC-006 | Cache keys not explicitly normalized | MEDIUM | Access Control | `headers/headers.go:19–33` |
28-
| SEC-007 | Server name disclosed in headers | LOW | Fingerprinting | `server/server.go:70, 112` |
29-
| SEC-008 | Unsanitized paths in log output | LOW | Log Injection | `handler/middleware.go:113–115` |
30-
| SEC-009 | Deprecated `PreferServerCipherSuites` | LOW | Cryptography | `server/server.go:93` |
31-
| SEC-010 | Template execution error silently discarded | LOW | Error Handling | `handler/dirlist.go:191` |
32-
| SEC-011 | Large files read entirely into memory | LOW | Resource Exhaustion | `handler/file.go:338–377` |
33-
| SEC-012 | CORS wildcard `Vary` header note | INFO | CORS | `security/security.go:313` |
34-
| SEC-013 | ETag truncated to 64 bits | INFO | Cryptography | `handler/file.go:480–483` |
35-
| SEC-014 | `MaxRequestBodySize: 0` uses fasthttp default | INFO | Misconfiguration | `server/server.go:74` |
36-
| SEC-015 | No built-in rate limiting | INFO | Resource Exhaustion | Architectural |
37-
| SEC-016 | Preload walker doesn't validate symlink targets | INFO | Access Control | `cache/preload.go:74–158` |
22+
| # | Finding | Severity | Category | File | Status |
23+
| ------- | ---------------------------------------------- | -------------- | -------------------- | ------------------------------- | ------------ |
24+
| SEC-001 | Unbounded `PathCache` (DoS) | **HIGH** | Resource Exhaustion | `security/security.go:49–70` | ✅ Resolved |
25+
| SEC-002 | Shipped `config.toml` weakens code defaults | MEDIUM | Misconfiguration | `config.toml:28–38` | ✅ Resolved |
26+
| SEC-003 | Full stack traces logged on panic | MEDIUM | Information Disclosure | `handler/middleware.go:121–132` | ✅ Resolved |
27+
| SEC-004 | Static multipart range boundary | MEDIUM | Fingerprinting | `handler/file.go:615, 659` | ✅ Resolved |
28+
| SEC-005 | No max body size for gzip compression | MEDIUM | Resource Exhaustion | `compress/compress.go:170–187` | ✅ Resolved |
29+
| SEC-006 | Cache keys not explicitly normalized | MEDIUM | Access Control | `headers/headers.go:19–33` | ✅ Resolved |
30+
| SEC-007 | Server name disclosed in headers | LOW | Fingerprinting | `server/server.go:70, 112` | ✅ Resolved |
31+
| SEC-008 | Unsanitized paths in log output | LOW | Log Injection | `handler/middleware.go:113–115` | ✅ Resolved |
32+
| SEC-009 | Deprecated `PreferServerCipherSuites` | LOW | Cryptography | `server/server.go:93` | ✅ Resolved |
33+
| SEC-010 | Template execution error silently discarded | LOW | Error Handling | `handler/dirlist.go:191` | ✅ Resolved |
34+
| SEC-011 | Large files read entirely into memory | LOW | Resource Exhaustion | `handler/file.go:338–377` | ✅ Resolved |
35+
| SEC-012 | CORS wildcard `Vary` header note | INFO | CORS | `security/security.go:313` | ✅ Resolved |
36+
| SEC-013 | ETag truncated to 64 bits | INFO | Cryptography | `handler/file.go:480–483` | ✅ Resolved |
37+
| SEC-014 | `MaxRequestBodySize: 0` uses fasthttp default | INFO | Misconfiguration | `server/server.go:74` | ✅ Resolved |
38+
| SEC-015 | No built-in rate limiting | INFO | Resource Exhaustion | Architectural | ✅ Resolved |
39+
| SEC-016 | Preload walker doesn't validate symlink targets | INFO | Access Control | `cache/preload.go:74–158` | ✅ Resolved |
3840

3941
---
4042

@@ -45,6 +47,7 @@
4547
| Attribute | Value |
4648
| ----------- | ------------------------------------------------------ |
4749
| **Severity**| **HIGH** |
50+
| **Status** |**Resolved** — Replaced `sync.Map` with `hashicorp/golang-lru/v2` bounded LRU cache (10,000 entries). `NewPathCache(maxEntries int)` constructor added. |
4851
| **CWE** | CWE-400 (Uncontrolled Resource Consumption) |
4952
| **OWASP** | A05:2021 — Security Misconfiguration |
5053
| **File** | `internal/security/security.go:49–70` |
@@ -113,6 +116,7 @@ if pathCache != nil {
113116
| Attribute | Value |
114117
| ----------- | ------------------------------------------------------ |
115118
| **Severity**| MEDIUM |
119+
| **Status** |**Resolved**`config.toml` (gitignored, local only) updated with secure defaults. Tracked `config.toml.example` already had correct values. |
116120
| **CWE** | CWE-1188 (Insecure Default Initialization of Resource) |
117121
| **OWASP** | A05:2021 — Security Misconfiguration |
118122
| **File** | `config.toml:28–38` |
@@ -173,6 +177,7 @@ hsts_include_subdomains = false
173177
| Attribute | Value |
174178
| ----------- | ------------------------------------------------------------ |
175179
| **Severity**| MEDIUM |
180+
| **Status** |**Resolved** — Stack traces now only logged when `STATIC_DEBUG=1` env var is set. Default: panic value only. |
176181
| **CWE** | CWE-209 (Error Message Containing Sensitive Information) |
177182
| **OWASP** | A04:2021 — Insecure Design |
178183
| **File** | `internal/handler/middleware.go:121–132` |
@@ -234,6 +239,7 @@ func recoveryMiddleware(next fasthttp.RequestHandler, verbose bool) fasthttp.Req
234239
| Attribute | Value |
235240
| ----------- | -------------------------------------------------- |
236241
| **Severity**| MEDIUM |
242+
| **Status** |**Resolved** — Boundary now generated per-response using `crypto/rand` (16 random bytes → hex). |
237243
| **CWE** | CWE-200 (Exposure of Sensitive Information) |
238244
| **OWASP** | A05:2021 — Security Misconfiguration |
239245
| **File** | `internal/handler/file.go:615` and `file.go:659` |
@@ -283,6 +289,7 @@ boundary := randomBoundary()
283289
| Attribute | Value |
284290
| ----------- | ----------------------------------------------- |
285291
| **Severity**| MEDIUM |
292+
| **Status** |**Resolved** — Added `MaxCompressSize` config field (default 10 MB). Bodies exceeding limit skip on-the-fly compression. Env: `STATIC_COMPRESSION_MAX_COMPRESS_SIZE`. |
286293
| **CWE** | CWE-400 (Uncontrolled Resource Consumption) |
287294
| **OWASP** | A05:2021 — Security Misconfiguration |
288295
| **File** | `internal/compress/compress.go:170–187` |
@@ -339,6 +346,7 @@ if len(body) < cfg.MinSize || len(body) > maxCompressSize {
339346
| Attribute | Value |
340347
| ----------- | ------------------------------------------------------------- |
341348
| **Severity**| MEDIUM |
349+
| **Status** |**Resolved** — Added `path.Clean("/" + urlPath)` in `CacheKeyForPath`. Trailing-slash semantics preserved via `isDir` check before cleaning. |
342350
| **CWE** | CWE-706 (Use of Incorrectly-Resolved Name or Reference) |
343351
| **OWASP** | A01:2021 — Broken Access Control |
344352
| **File** | `internal/headers/headers.go:19–33` and `cache/cache.go:209` |
@@ -394,6 +402,7 @@ func CacheKeyForPath(urlPath, indexFile string) string {
394402
| Attribute | Value |
395403
| ----------- | ------------------------------------------------------------------ |
396404
| **Severity**| LOW |
405+
| **Status** |**Resolved** — Set `Name: ""` on both HTTP and HTTPS `fasthttp.Server` instances. `Server` header no longer emitted. |
397406
| **CWE** | CWE-200 (Exposure of Sensitive Information to Unauthorized Actor) |
398407
| **OWASP** | A05:2021 — Security Misconfiguration |
399408
| **File** | `internal/server/server.go:70` and `server.go:112` |
@@ -432,6 +441,7 @@ s.http = &fasthttp.Server{
432441
| Attribute | Value |
433442
| ----------- | --------------------------------------------------------- |
434443
| **Severity**| LOW |
444+
| **Status** |**Resolved** — Added `sanitizeForLog()` that replaces ASCII control chars (0x00–0x1F, 0x7F) with `\xNN` hex escapes. Applied to URI in access logging. |
435445
| **CWE** | CWE-117 (Improper Output Neutralization for Logs) |
436446
| **OWASP** | A09:2021 — Security Logging and Monitoring Failures |
437447
| **File** | `internal/handler/middleware.go:113–115` and `file.go:257` |
@@ -470,6 +480,7 @@ uri := sanitizeForLog(string(ctx.RequestURI()))
470480
| Attribute | Value |
471481
| ----------- | ------------------------------------------------------ |
472482
| **Severity**| LOW |
483+
| **Status** |**Resolved** — Removed `PreferServerCipherSuites: true`. Added explanatory comment noting Go ≥1.21 manages cipher order automatically. |
473484
| **CWE** | CWE-327 (Use of a Broken or Risky Cryptographic Algorithm) |
474485
| **OWASP** | A02:2021 — Cryptographic Failures |
475486
| **File** | `internal/server/server.go:93` |
@@ -503,6 +514,7 @@ tlsCfg := &tls.Config{
503514
| Attribute | Value |
504515
| ----------- | -------------------------------------------------- |
505516
| **Severity**| LOW |
517+
| **Status** |**Resolved** — Template error now checked; returns 500 Internal Server Error with log message on failure. |
506518
| **CWE** | CWE-755 (Improper Handling of Exceptional Conditions) |
507519
| **OWASP** | A04:2021 — Insecure Design |
508520
| **File** | `internal/handler/dirlist.go:191` |
@@ -540,6 +552,7 @@ ctx.SetBody(buf.Bytes())
540552
| Attribute | Value |
541553
| ----------- | ----------------------------------------------- |
542554
| **Severity**| LOW |
555+
| **Status** |**Resolved** — Added `MaxServeFileSize` config field (default 1 GB). Files exceeding limit receive 413 Payload Too Large. Env: `STATIC_FILES_MAX_SERVE_FILE_SIZE`. |
543556
| **CWE** | CWE-770 (Allocation of Resources Without Limits or Throttling) |
544557
| **OWASP** | A05:2021 — Security Misconfiguration |
545558
| **File** | `internal/handler/file.go:338–377` |
@@ -571,6 +584,7 @@ if info.Size() > absoluteMaxFileSize {
571584
| Attribute | Value |
572585
| ----------- | ------------------------- |
573586
| **Severity**| INFO |
587+
| **Status** |**Resolved** — Expanded inline comment in `security.go` explaining why `Vary: Origin` is NOT set with wildcard (per Fetch spec). |
574588
| **CWE** | N/A (informational) |
575589
| **File** | `internal/security/security.go:313–316` |
576590

@@ -583,6 +597,7 @@ This is actually **correct** per the Fetch specification. A literal `*` response
583597
| Attribute | Value |
584598
| ----------- | ------------------------- |
585599
| **Severity**| INFO |
600+
| **Status** |**Resolved** — Expanded `computeETag` doc comment with collision analysis and rationale for 64-bit truncation. |
586601
| **CWE** | CWE-328 (Use of Weak Hash) |
587602
| **File** | `internal/handler/file.go:480–483` |
588603

@@ -595,6 +610,7 @@ ETags are computed as `sha256(data)[:8]` (64 bits). For cache validation purpose
595610
| Attribute | Value |
596611
| ----------- | ------------------------- |
597612
| **Severity**| INFO |
613+
| **Status** |**Resolved** — Set `MaxRequestBodySize: 1024` (1 KB) on both HTTP and HTTPS servers. Static file server needs no request body. |
598614
| **CWE** | CWE-770 (Allocation of Resources Without Limits or Throttling) |
599615
| **File** | `internal/server/server.go:74` |
600616

@@ -611,6 +627,7 @@ MaxRequestBodySize: 1024, // 1 KB -- static server needs no request body
611627
| Attribute | Value |
612628
| ----------- | ------------------------- |
613629
| **Severity**| INFO |
630+
| **Status** |**Resolved** — Added `MaxConnsPerIP` config field (default 0 = unlimited) wired to `fasthttp.Server.MaxConnsPerIP`. Env: `STATIC_SERVER_MAX_CONNS_PER_IP`. |
614631
| **CWE** | CWE-770 (Allocation of Resources Without Limits or Throttling) |
615632
| **File** | N/A (architectural) |
616633

@@ -623,6 +640,7 @@ No built-in rate limiting. This is typical for a static file server (usually han
623640
| Attribute | Value |
624641
| ----------- | ------------------------- |
625642
| **Severity**| INFO |
643+
| **Status** |**Resolved** — Added symlink detection (`d.Type()&os.ModeSymlink`), target resolution via `filepath.EvalSymlinks`, and validation that target stays within `absRoot`. |
626644
| **CWE** | CWE-59 (Improper Link Resolution Before File Access) |
627645
| **File** | `internal/cache/preload.go:74–158` |
628646

@@ -710,21 +728,25 @@ Even the custom 404 page path from configuration is validated through `PathSafe`
710728

711729
## Prioritized Remediation Plan
712730

713-
| Priority | Finding | Severity | Effort | Impact |
714-
| -------- | ------- | -------- | ------ | ------ |
715-
| **P1** | SEC-001: Bound the PathCache with LRU | HIGH | Low (~30 LOC) | Eliminates DoS vector |
716-
| **P2** | SEC-002: Align `config.toml` with secure code defaults | MEDIUM | Low (~10 lines TOML) | Restores secure-by-default |
717-
| **P3** | SEC-005: Add `maxCompressSize` threshold | MEDIUM | Low (~3 LOC) | Prevents memory exhaustion |
718-
| **P4** | SEC-004: Randomize multipart boundary | MEDIUM | Low (~10 LOC) | Eliminates fingerprinting |
719-
| **P5** | SEC-006: `path.Clean` in cache key function | MEDIUM | Low (~2 LOC) | Defense-in-depth |
720-
| **P6** | SEC-003: Make stack trace logging configurable | MEDIUM | Low (~10 LOC) | Reduces info leakage |
721-
| **P7** | SEC-007: Suppress server name header | LOW | Trivial (1 LOC) | Reduces fingerprinting |
722-
| **P8** | SEC-008: Sanitize log output | LOW | Low (~15 LOC) | Prevents log forgery |
723-
| **P9** | SEC-009: Remove deprecated TLS field | LOW | Trivial (1 LOC) | Code hygiene |
724-
| **P10** | SEC-010: Handle template execution errors | LOW | Low (~5 LOC) | Better error handling |
725-
| **P11** | SEC-011: Document large file memory constraint | LOW | Medium (docs + config) | Operator awareness |
726-
| Backlog | SEC-012 through SEC-016 | INFO | Various | Hardening / documentation |
731+
| Priority | Finding | Severity | Effort | Impact | Status |
732+
| -------- | ------- | -------- | ------ | ------ | ------ |
733+
| **P1** | SEC-001: Bound the PathCache with LRU | HIGH | Low (~30 LOC) | Eliminates DoS vector | ✅ Done |
734+
| **P2** | SEC-002: Align `config.toml` with secure code defaults | MEDIUM | Low (~10 lines TOML) | Restores secure-by-default | ✅ Done |
735+
| **P3** | SEC-005: Add `maxCompressSize` threshold | MEDIUM | Low (~3 LOC) | Prevents memory exhaustion | ✅ Done |
736+
| **P4** | SEC-004: Randomize multipart boundary | MEDIUM | Low (~10 LOC) | Eliminates fingerprinting | ✅ Done |
737+
| **P5** | SEC-006: `path.Clean` in cache key function | MEDIUM | Low (~2 LOC) | Defense-in-depth | ✅ Done |
738+
| **P6** | SEC-003: Make stack trace logging configurable | MEDIUM | Low (~10 LOC) | Reduces info leakage | ✅ Done |
739+
| **P7** | SEC-007: Suppress server name header | LOW | Trivial (1 LOC) | Reduces fingerprinting | ✅ Done |
740+
| **P8** | SEC-008: Sanitize log output | LOW | Low (~15 LOC) | Prevents log forgery | ✅ Done |
741+
| **P9** | SEC-009: Remove deprecated TLS field | LOW | Trivial (1 LOC) | Code hygiene | ✅ Done |
742+
| **P10** | SEC-010: Handle template execution errors | LOW | Low (~5 LOC) | Better error handling | ✅ Done |
743+
| **P11** | SEC-011: Document large file memory constraint | LOW | Medium (docs + config) | Operator awareness | ✅ Done |
744+
| **P12** | SEC-012: Expand CORS wildcard Vary comment | INFO | Trivial | Documentation | ✅ Done |
745+
| **P13** | SEC-013: Expand ETag truncation doc comment | INFO | Trivial | Documentation | ✅ Done |
746+
| **P14** | SEC-014: Set explicit MaxRequestBodySize | INFO | Trivial (1 LOC) | Reduces attack surface | ✅ Done |
747+
| **P15** | SEC-015: Add MaxConnsPerIP config | INFO | Low (~15 LOC) | DoS mitigation option | ✅ Done |
748+
| **P16** | SEC-016: Validate symlink targets in preload | INFO | Low (~10 LOC) | Closes preload escape | ✅ Done |
727749

728750
---
729751

730-
*Report generated by Kai security audit pipeline. All findings verified against source code as of commit `fcfe429`.*
752+
*Report generated by Kai security audit pipeline. All findings verified against source code as of commit `fcfe429`. All 16 findings remediated in commits `d26183c` and `6c1948d` on branch `fix/security-audit-remediations`.*

0 commit comments

Comments
 (0)