feat(mcp): audit-log every tools/call (OWASP MCP08)
tools/call dispatched with no record, so repo_delete, pr_merge and file_delete left no trail in gitea-mcp; only auth acceptance was logged. One slog record per call: tool, caller, token fingerprint, target (owner/repo/number/branch/path/tag), outcome, duration. Every other argument is reduced to args_bytes so the audit line never copies file content or issue bodies. Closes #64 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
chassisauth "git.d-ma.be/mathias/mcp-chassis/auth"
|
||||
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/auth"
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||
)
|
||||
|
||||
// auditTargetKeys are the only argument values copied into the audit record:
|
||||
// they identify what a call acted on without copying what it wrote. Every
|
||||
// other argument (file content, issue bodies) is reduced to args_bytes so the
|
||||
// audit trail never becomes a second copy of repo content (gitea-mcp#64).
|
||||
var auditTargetKeys = []string{"owner", "repo", "number", "branch", "path", "tag"}
|
||||
|
||||
// logToolCall emits one structured record per tools/call (OWASP MCP08).
|
||||
func logToolCall(ctx context.Context, name string, args json.RawMessage, start time.Time, err error) {
|
||||
attrs := []any{
|
||||
"tool", name,
|
||||
"caller", auth.Caller(ctx),
|
||||
"args_bytes", len(args),
|
||||
"duration_ms", time.Since(start).Milliseconds(),
|
||||
}
|
||||
if tok, ok := gitea.TokenFromContext(ctx); ok {
|
||||
attrs = append(attrs, "token_fp", chassisauth.Fingerprint(tok))
|
||||
}
|
||||
var parsed map[string]any
|
||||
if json.Unmarshal(args, &parsed) == nil {
|
||||
for _, k := range auditTargetKeys {
|
||||
if v, ok := parsed[k]; ok {
|
||||
attrs = append(attrs, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
outcome := "ok"
|
||||
if err != nil {
|
||||
outcome = err.Error()
|
||||
}
|
||||
attrs = append(attrs, "outcome", outcome)
|
||||
slog.InfoContext(ctx, "mcp tool call", attrs...)
|
||||
}
|
||||
Reference in New Issue
Block a user