Add server audit logging for all state-changing plugin operations - #934
Add server audit logging for all state-changing plugin operations#934crspeller wants to merge 20 commits into
Conversation
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…rence case Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…il record Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…coverage, persisted flag, test hardening Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
… coverage gaps Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…e fail-path test gaps Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
⛔ Snyk checks have failed. 2 issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis change adds comprehensive server-side audit logging across API operations, including centralized event registration, middleware persistence with trace propagation, endpoint-specific enrichment, sensitive-value filtering, MCP session creation tracking, and audit-focused test coverage. The audit infrastructure captures operation outcomes, identifiers, state changes, and failure modes while strictly excluding prompt content, tool arguments/results, configuration values, and authentication secrets. ChangesAudit platform and core infrastructure
API endpoint audit enrichment
MCP session tracking and grant audit deduplication
Supporting infrastructure and documentation
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AuditMiddleware
participant Handler
participant Mattermost
Client->>AuditMiddleware: audited API request
AuditMiddleware->>AuditMiddleware: create fail-mode record
AuditMiddleware->>Handler: attach record + context
Handler->>Handler: enrich with identifiers + state
Handler->>Mattermost: perform operation
AuditMiddleware->>Mattermost: persist final record (status + description)
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
conversations/tool_approval.go (1)
245-247: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConfirm the intended ordering here versus
HandleToolResult.
accepted_tools/rejected_toolsare added beforeUpdateTurnContent(Line 253) andCreateTurnAutoSequence(Line 304). If either fails, the emitted fail record still lists the decision.HandleToolResultdeliberately defers the same two parameters until after its gate so a no-op click "must not claim a share resolution" (Lines 466-467). The tools here have already executed by Line 245, so recording them may well be correct — but the divergence from the sibling path is not documented. Worth a one-line comment stating that these names describe executed side effects, not persisted state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@conversations/tool_approval.go` around lines 245 - 247, Document the intentional ordering in the approval flow near the accepted_tools and rejected_tools audit parameters: clarify that these names represent tools already executed, not persisted state, so they remain recorded even if UpdateTurnContent or CreateTurnAutoSequence later fails. Keep HandleToolResult’s deferred recording behavior unchanged.api/api_custom_prompts.go (1)
68-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider clamping the unvalidated
prompt_idpath parameter.
promptIDis recorded before any validation or ownership check, so an arbitrary-length URL segment lands verbatim in the audit record.handleCreateAgentinapi/api_agents.go(Line 389) appliesaudit.TruncateIDto comparable unvalidated request text; the same treatment here would keep the record bounded. The delete handler at Line 109 has the identical pattern.♻️ Proposed change
- audit.AddParam(auditRec(c), "prompt_id", promptID) + audit.AddParam(auditRec(c), "prompt_id", audit.TruncateID(promptID))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/api_custom_prompts.go` around lines 68 - 71, Clamp the unvalidated promptID before recording it in the audit record, reusing audit.TruncateID as handleCreateAgent does. Apply this to both the current prompt handler and the delete handler’s equivalent audit.AddParam call, while preserving the existing target identification behavior.api/api_mcp.go (1)
322-326: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
serverNameis recorded unfiltered whiledisabled_serverstwo functions above is allowlisted.The comment at Line 288 treats user-supplied server names as an "arbitrary-content injection channel into the audit log" and filters them through
knownMCPServerNames. Here the same class of value — an unvalidated path parameter — is recorded verbatim, so the mitigation is inconsistent across the two routes on the same object type. Clamping withaudit.TruncateID(asapi/api_agents.goLine 389 does) or reusing the known-server filter would close the gap.♻️ Proposed change
- audit.AddParam(auditRec(c), audit.KeyMCPServer, serverName) + audit.AddParam(auditRec(c), audit.KeyMCPServer, audit.TruncateID(serverName))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/api_mcp.go` around lines 322 - 326, Sanitize the serverName value before recording it in audit data within handleDeleteUserMCPOAuth. Reuse the established knownMCPServerNames allowlist or apply audit.TruncateID, matching the protection used by the nearby disabled_servers route, while preserving the target server on every failure path.api/api_admin_test.go (1)
1280-1339: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake
TestAuditClearMCPToolsCachetable-driven.It contains two cases written as sequential
t.Runblocks, while every neighbouring audit test in this file (TestAuditReindexPosts,TestAuditCancelReindexJob,TestAuditUpdatePluginServer) uses a table. Asetup func(e *TestEnvironment)plusexpectedStatus/validateRecordfields would fold both cases in.As per coding guidelines, "Go tests must be table-driven when they contain more than one case."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/api_admin_test.go` around lines 1280 - 1339, Refactor TestAuditClearMCPToolsCache into a table-driven test with a setup func(e *TestEnvironment), expectedStatus, and validateRecord fields. Move the existing success and missing-cache arrangements into table entries, execute each through a shared t.Run loop, and preserve their request setup, mock expectations, response assertions, and audit-record validation.Source: Coding guidelines
api/api_agents.go (1)
301-334: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTwo copies of the same JSON-diff helper.
changedAgentConfigFieldsandchangedTopLevelConfigKeysare the same routine — marshal both sides tomap[string]json.RawMessage, compare raw values withbytes.Equal, add removed keys, sort — differing only in the parameter type and the nil-prevhandling. Extracting one generic helper (e.g.audit.ChangedJSONKeys(prev, next any) []string) keeps the "which fields changed, never their values" invariant defined in one place as more audited entities are added.
api/api_agents.go#L301-L334: replace the body ofchangedAgentConfigFieldswith a call to the shared helper, or drop it and call the helper directly at Line 550.api/api_config.go#L65-L102: replacechangedTopLevelConfigKeyswith the shared helper, keeping the nil-prevcase (pass a zero-valueconfig.Configor let the helper treat a nil pointer as an empty map).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/api_agents.go` around lines 301 - 334, Extract the duplicated JSON key-diff logic from changedAgentConfigFields and changedTopLevelConfigKeys into one shared audit helper, such as audit.ChangedJSONKeys(prev, next any), that marshals top-level values, compares raw JSON values, includes removed keys, and returns sorted keys without exposing values. In api/api_agents.go lines 301-334, replace changedAgentConfigFields with the shared helper or call it directly at its use site; in api/api_config.go lines 65-102, replace changedTopLevelConfigKeys and preserve nil-prev behavior by passing an empty value or having the helper treat nil as an empty map.api/audit_middleware.go (1)
30-86: 🔒 Security & Privacy | 🔵 TrivialSolid design; one residual note on error-description content.
The middleware's success/fail/panic handling is correct (verified the panic re-raise is caught by
gin.Default()'s outerRecovery(), andc.HandlerName()resolves the final route handler regardless of later aborts).One residual gap:
audit.TruncateDescription(last.Error())(line 77) bounds length but not content — a future handler that wraps an error containing partial secrets/PII would still leak a truncated fragment into the audit log. Today this is safe only because handlers are individually disciplined to keep errors value-free (as seen inmmtools/ask_user_question.go). Since this middleware is the last line of defense for every audited route, it may be worth documenting this as an explicit contract for handler authors (or adding a lightweight sentinel/wrapper type for "audit-safe" errors) rather than relying on convention alone.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/audit_middleware.go` around lines 30 - 86, Document an explicit audit-safety contract for errors exposed through c.Errors in auditMiddleware: handler errors must not contain secrets, PII, or request-derived values because TruncateDescription only limits length. Add the guidance at the relevant handler/error-reporting boundary without changing the existing success, failure, panic, or truncation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/api_oauth.go`:
- Around line 16-22: Clamp unvalidated audit inputs in all affected handlers: in
api/api_oauth.go lines 16-22, wrap serverName in audit.TruncateID before
audit.AddParam; in api/api_post.go lines 343-344, cap data.AcceptedToolIDs and
truncate each entry before recording them; apply the identical AcceptedToolIDs
change at handleToolResult around line 407.
---
Nitpick comments:
In `@api/api_admin_test.go`:
- Around line 1280-1339: Refactor TestAuditClearMCPToolsCache into a
table-driven test with a setup func(e *TestEnvironment), expectedStatus, and
validateRecord fields. Move the existing success and missing-cache arrangements
into table entries, execute each through a shared t.Run loop, and preserve their
request setup, mock expectations, response assertions, and audit-record
validation.
In `@api/api_agents.go`:
- Around line 301-334: Extract the duplicated JSON key-diff logic from
changedAgentConfigFields and changedTopLevelConfigKeys into one shared audit
helper, such as audit.ChangedJSONKeys(prev, next any), that marshals top-level
values, compares raw JSON values, includes removed keys, and returns sorted keys
without exposing values. In api/api_agents.go lines 301-334, replace
changedAgentConfigFields with the shared helper or call it directly at its use
site; in api/api_config.go lines 65-102, replace changedTopLevelConfigKeys and
preserve nil-prev behavior by passing an empty value or having the helper treat
nil as an empty map.
In `@api/api_custom_prompts.go`:
- Around line 68-71: Clamp the unvalidated promptID before recording it in the
audit record, reusing audit.TruncateID as handleCreateAgent does. Apply this to
both the current prompt handler and the delete handler’s equivalent
audit.AddParam call, while preserving the existing target identification
behavior.
In `@api/api_mcp.go`:
- Around line 322-326: Sanitize the serverName value before recording it in
audit data within handleDeleteUserMCPOAuth. Reuse the established
knownMCPServerNames allowlist or apply audit.TruncateID, matching the protection
used by the nearby disabled_servers route, while preserving the target server on
every failure path.
In `@api/audit_middleware.go`:
- Around line 30-86: Document an explicit audit-safety contract for errors
exposed through c.Errors in auditMiddleware: handler errors must not contain
secrets, PII, or request-derived values because TruncateDescription only limits
length. Add the guidance at the relevant handler/error-reporting boundary
without changing the existing success, failure, panic, or truncation behavior.
In `@conversations/tool_approval.go`:
- Around line 245-247: Document the intentional ordering in the approval flow
near the accepted_tools and rejected_tools audit parameters: clarify that these
names represent tools already executed, not persisted state, so they remain
recorded even if UpdateTurnContent or CreateTurnAutoSequence later fails. Keep
HandleToolResult’s deferred recording behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 9389a458-c2b3-4bdc-bbe3-1a8b95dcaa90
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (36)
api/api.goapi/api_admin.goapi/api_admin_test.goapi/api_agents.goapi/api_agents_test.goapi/api_bridge_mcp.goapi/api_bridge_mcp_test.goapi/api_config.goapi/api_config_test.goapi/api_custom_prompts.goapi/api_custom_prompts_test.goapi/api_mcp.goapi/api_mcp_test.goapi/api_mcp_user_preferences_test.goapi/api_oauth.goapi/api_post.goapi/api_post_test.goapi/api_test.goapi/audit_events.goapi/audit_middleware.goapi/audit_middleware_test.goapi/mcp_handlers.goapi/mcp_handlers_test.goaudit/context.goaudit/keys.goaudit/params.goaudit/params_test.goconversations/tool_approval.goconversations/tool_approval_audit_test.godocs/admin_guide.mdgo.modmcp/client_manager.gomcp/embedded_session_store.gomcp/embedded_session_store_test.gomcpserver/test_helpers_test.gommtools/ask_user_question.go
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
go.mod (1)
31-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAlign the OpenTelemetry exporter version with the other OpenTelemetry modules.
otel,sdk, andtraceare nowv1.44.0, butotlptracegrpcremains atv1.43.0. Confirm this is intentional; otherwise, update it tov1.44.0and regeneratego.sum. Thev1.44.0exporter is available. (go.opentelemetry.io)Proposed alignment
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@go.mod` around lines 31 - 34, Update the go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency to v1.44.0 to match the otel, sdk, and trace modules, then regenerate go.sum.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@go.mod`:
- Around line 31-34: Update the
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency to
v1.44.0 to match the otel, sdk, and trace modules, then regenerate go.sum.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 43518d0d-9359-455c-804a-263cabf2d832
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (1)
go.mod
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
🤖 LLM Evaluation ResultsOpenAI
❌ Failed EvaluationsShow 6 failuresOPENAI1. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
2. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
3. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
4. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
5. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json
6. TestDirectMessageConversations/[openai]_bot_dm_tool_introspection
Anthropic
❌ Failed EvaluationsShow 8 failuresANTHROPIC1. TestReactEval/[anthropic]_react_cat_message
2. TestChannelSummarization/[anthropic]_channel_summarization_developers_webapp_channel
3. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
4. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
5. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
6. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
7. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json
8. TestDirectMessageConversations/[anthropic]_bot_dm_tool_introspection
This comment was automatically generated by the eval CI pipeline. |
… inputs, align otel exporter Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9dde2fcf2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…or messages Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
…t into audit records Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
nickmisasi
left a comment
There was a problem hiding this comment.
Implementation LGTM. I think we should update AGENTS.md to include information about this being required
Co-authored-by: Christopher Speller <crspeller@users.noreply.github.com>
Both are false positives - the pinned |
Summary
Every state-changing operation in the plugin now emits a server audit record identifying who did it, what they did, and which object it touched — 22 events total (21 HTTP routes + the non-HTTP MCP session grant). Previously the plugin emitted none. This brings the plugin to parity with Playbooks (playbooks#2072) using the plugin audit API introduced in mattermost#31204.
Architecture — mirrors the plugin's existing instrumentation seams:
api/audit_middleware.go: a gin middleware (stacked alongsideotelgin/ginlogger/metricsMiddleware) creates a record for every route registered in the audit event registry, and emits it exactly once on every exit path — success, handler errors, auth denials (401/403 still produce fail records), and panics (defer + re-panic for gin Recovery). Success isstatus < 400(the OAuth start 302 counts). Records carry the actor (user/session/IP/client fromplugin.Context), the request path (query strings excluded — they carry OAuthcode/state), the outcome, and the OpenTelemetrytrace_idso an auditor can pivot straight to the request trace.api/audit_events.go: all event constants and the route registry keyed onc.HandlerName()— the same per-route identifier the metrics middleware uses. Auditing is explicit per-route opt-in, never inferred from the HTTP method (two of the most security-relevant operations are mutatingGETs).audit/package: carries the record throughcontext.Contextso handlers and services enrich it like they add attributes to the ambient otel span (auditRec(c)/audit.RecordFromContext(ctx)+ nil-safeaudit.AddParam). Object-identifier parameter keys reuse thetelemetry/attributes.gostrings (agents.post.id,agents.channel.id, …) so audit records and traces share one vocabulary.server/public→ v0.4.3 forpluginapi.Client.Audit(the pinned replace directive was dropped per its own "drop when tagged" comment). One-line test fallout:CreateUserAccessTokengained anexpiresAtparam.Audited events (enrichment in parentheses; all failures and permission denials also produce records):
saveConfig(changed top-level key names only +persistedflag — never values)reindexPosts(clear_index),cancelReindexJob/catchUpReindex(job_status),clearMCPToolsCache(cleared_servers),updateMCPPluginServer(target plugin, effectiveenabled,tool_configs_changed)createAgent/updateAgent/deleteAgent/updateAgentAvatar(agent/bot IDs, name,changed_fieldsas field-name list — never custom instructions)createCustomPrompt/updateCustomPrompt/deleteCustomPrompt(prompt_id,is_shared— never title/template)mcpOAuthStart/mcpOAuthCallback/mcpOAuthDisconnect(server name; provider error clamped to the RFC 6749 enum — nevercode/state/descriptions/auth URLs),updateMCPUserPreferences(disabled servers intersected with known server names + full count)registerMCPPluginServer/unregisterMCPPluginServer(agents.caller_plugin.idfrom the trusted inter-plugin header; length-clamped name/path)toolCallApproval/toolResultApproval(approver, post/channel/agent IDs, accepted/rejected tool names — never arguments, results, or user answers)mcpSessionGrant— an external MCP client obtained a session holding API access as the user. Emitted on mint, on re-enabling a lapsed session, and on first external adoption of an internally-minted session (per-session KV marker); reconnects are silent.Content policy (enforced by tests): no prompt/template content, conversation or channel content, tool arguments/results, tokens, OAuth
code/state, config values, session IDs, or free-form error text ever enter a record. Fail records carry the HTTP status code (plus a static class marker for panics and session-grant failures); full error detail stays in the server log, correlated by timestamp, actor, ortrace_id— so handler error messages remain rich for debugging without becoming an audit leakage channel. Remaining free-text injection channels are closed structurally: user preference entries are intersected with known server names, OAuth error codes are clamped to the spec enum, and pre-validation identifiers are length-clamped. Tests plant sentinel secrets/content through the real code paths and assert absence over the whole marshaled record.There is no plugin-side flag: persistence is governed entirely by the server's audit configuration (
ExperimentalAuditSettings). Docs added todocs/admin_guide.md.QA (live, terminal-driven) — Mattermost enterprise
master+ this plugin,MM_EXPERIMENTALAUDITSETTINGS_FILEENABLED=true:plugin_id: mattermost-aiinto each; read-onlyGETs emit nothing.mcpSessionGrantemitted exactly once across two consecutive external MCP connects.create_channeltool call approved via the API produced atoolCallApprovalsuccess record (accepted_tools: ["mattermost__create_channel"], approver/post/channel/agent IDs,trace_id) that joins with the server's owncreateChannelrecord — the previously-missing link between the human decision and its effect:{"event_name":"toolCallApproval","status":"success", "actor":{"user_id":"nrf3dug1ybbczj561uoq7cxwue","session_id":"...","ip_address":"172.18.0.1"}, "meta":{"api_path":"/post/ocj4rsw4y3dwbkmpiwp5thzk4c/tool_call","trace_id":"0d78fc81e6930a123dbaaa171ec41fe7"}, "event":{"parameters":{"plugin_id":"mattermost-ai", "accepted_tools":["mattermost__create_channel"], "accepted_tool_ids":["toolu_011CmGeoXv2ZSqj1qf5PBLVA"], "agents.agent.id":"s9a8x1qfq3rqbxpnw5dfb6sofe", "agents.channel.id":"35paqyhuajgbfdo7qqcmqc91sr", "agents.post.id":"ocj4rsw4y3dwbkmpiwp5thzk4c"}}}Not live-verified (covered by unit tests): bridge register/unregister (requires a second plugin as caller), the session-grant adoption path, and the tool-result share flow.
Known cosmetic artifact: empty
[]stringparameters (e.g.rejected_toolswhen nothing was rejected) arrive as JSONnullin the persisted log — the plugin RPC gob round-trip collapses empty slices; key presence is preserved. This affects any plugin using the audit API.About the failing
security/snykcheck (non-required): the findings are pre-existing onmasterand surface here only because this PR touchesgo.mod(Snyk reports "no manifest changes detected" on other PRs and skips scanning). This PR already patches every fixable Go-module advisorygovulncheckreports (grpc v1.82.1, x/text v0.39.0, quic-go v0.59.1, x/net v0.56.0, otel v1.44.0, edwards25519 v1.1.1, klauspost/compress v1.18.7 — all pre-existing on master). What remains is out of this PR's control: two Go stdlib advisories fixed only in go1.26.5 (toolchain decision), one x/crypto advisory with no released fix (GO-2026-5932, not reachable from this code per govulncheck), and ~20 pre-existing npm advisories inwebapp/(untouched by this PR).Ticket Link
Screenshots
N/A — server-side only; audit-log evidence included above.
Release Note
Summary by CodeRabbit
New Features
Documentation
Bug Fixes