Harden deployment and fix PEN-01

- Fix Bearer token extraction to validate prefix (PEN-01)
- Add TestExtractBearerFromRequest covering PEN-01 edge cases
- Fix flaky TestRenewToken timing (2s → 4s lifetime)
- Move default config/install paths to /srv/mcias
- Add RUNBOOK.md for operational procedures
- Update AUDIT.md with penetration test round 4

Security: extractBearerFromRequest now uses case-insensitive prefix
validation instead of fixed-offset slicing, rejecting non-Bearer
Authorization schemes that were previously accepted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:33:24 -07:00
parent 2a85d4bf2b
commit 1121b7d4fd
14 changed files with 774 additions and 117 deletions

View File

@@ -18,6 +18,7 @@ import (
"log/slog"
"net"
"net/http"
"strings"
"time"
"git.wntrmute.dev/kyle/mcias/internal/audit"
@@ -1412,16 +1413,23 @@ func decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) bool {
}
// extractBearerFromRequest extracts a Bearer token from the Authorization header.
// Security (PEN-01): validates the "Bearer" prefix using case-insensitive
// comparison before extracting the token. The previous implementation sliced
// at a fixed offset without checking the prefix, accepting any 8+ character
// Authorization value.
func extractBearerFromRequest(r *http.Request) (string, error) {
auth := r.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf("no Authorization header")
}
const prefix = "Bearer "
if len(auth) <= len(prefix) {
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return "", fmt.Errorf("malformed Authorization header")
}
return auth[len(prefix):], nil
if parts[1] == "" {
return "", fmt.Errorf("empty Bearer token")
}
return parts[1], nil
}
// docsSecurityHeaders adds the same defensive HTTP headers as the UI sub-mux