Fix ECDH zeroization, add audit logging, and remediate high findings

- Fix #61: handleRotateKey and handleDeleteUser now zeroize stored
  privBytes instead of calling Bytes() (which returns a copy). New
  state populates privBytes; old references nil'd for GC.
- Add audit logging subsystem (internal/audit) with structured event
  recording for cryptographic operations.
- Add audit log engine spec (engines/auditlog.md).
- Add ValidateName checks across all engines for path traversal (#48).
- Update AUDIT.md: all High findings resolved (0 open).
- Add REMEDIATION.md with detailed remediation tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 14:04:39 -07:00
parent b33d1f99a0
commit 5c5d7e184e
24 changed files with 1699 additions and 72 deletions

View File

@@ -11,6 +11,7 @@ import (
mcias "git.wntrmute.dev/kyle/mcias/clients/go"
"git.wntrmute.dev/kyle/metacrypt/internal/audit"
"git.wntrmute.dev/kyle/metacrypt/internal/auth"
"git.wntrmute.dev/kyle/metacrypt/internal/barrier"
"git.wntrmute.dev/kyle/metacrypt/internal/crypto"
@@ -286,6 +287,14 @@ func (s *Server) handleEngineMount(w http.ResponseWriter, r *http.Request) {
return
}
// Inject external_url into CA engine config if available and not already set.
if req.Config == nil {
req.Config = make(map[string]interface{})
}
if _, ok := req.Config["external_url"]; !ok && s.cfg.Server.ExternalURL != "" {
req.Config["external_url"] = s.cfg.Server.ExternalURL
}
if err := s.engines.Mount(r.Context(), req.Name, engine.EngineType(req.Type), req.Config); err != nil {
s.logger.Error("mount engine", "name", req.Name, "type", req.Type, "error", err)
writeJSONError(w, err.Error(), http.StatusBadRequest)
@@ -435,10 +444,16 @@ func (s *Server) handleEngineRequest(w http.ResponseWriter, r *http.Request) {
case strings.Contains(err.Error(), "not found"):
status = http.StatusNotFound
}
outcome := "error"
if status == http.StatusForbidden || status == http.StatusUnauthorized {
outcome = "denied"
}
s.auditOp(r, info, req.Operation, "", req.Mount, outcome, nil, err)
writeJSONError(w, err.Error(), status)
return
}
s.auditOp(r, info, req.Operation, "", req.Mount, "success", nil, nil)
writeJSON(w, http.StatusOK, resp.Data)
}
@@ -1317,6 +1332,24 @@ func writeJSONError(w http.ResponseWriter, msg string, status int) {
writeJSON(w, status, map[string]string{"error": msg})
}
// auditOp logs an audit event for a completed engine operation.
func (s *Server) auditOp(r *http.Request, info *auth.TokenInfo,
op, engineType, mount, outcome string, detail map[string]interface{}, err error) {
e := audit.Event{
Caller: info.Username,
Roles: info.Roles,
Operation: op,
Engine: engineType,
Mount: mount,
Outcome: outcome,
Detail: detail,
}
if err != nil {
e.Error = err.Error()
}
s.audit.Log(r.Context(), e)
}
// newPolicyChecker builds a PolicyChecker closure for a caller, used by typed
// REST handlers to pass service-level policy evaluation into the engine.
func (s *Server) newPolicyChecker(r *http.Request, info *auth.TokenInfo) engine.PolicyChecker {