From b8c9f931bb22652c486bb82ee73f92d0e6249e0e Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Mon, 19 Jan 2026 11:40:37 -0500 Subject: [PATCH 1/3] Update package-lock.json (#34958) --- webapp/package-lock.json | 233 --------------------------------------- 1 file changed, 233 deletions(-) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 46139c4ea00..645920d0f90 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -9696,15 +9696,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "license": "MIT" @@ -11334,15 +11325,6 @@ "node": ">= 0.8.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "dev": true, - "license": "(MIT OR WTFPL)", - "optional": true, - "engines": { - "node": ">=6" - } - }, "node_modules/expect": { "version": "30.1.2", "dev": true, @@ -12142,12 +12124,6 @@ "omggif": "^1.0.10" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -16591,12 +16567,6 @@ "node": ">=10" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/moment": { "version": "2.30.1", "license": "MIT", @@ -16749,12 +16719,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/napi-postinstall": { "version": "0.3.4", "dev": true, @@ -16839,18 +16803,6 @@ "node": ">= 10.13" } }, - "node_modules/node-abi": { - "version": "3.85.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "dev": true, @@ -18246,84 +18198,6 @@ "version": "4.2.0", "license": "MIT" }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/decompress-response": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/mimic-response": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prebuild-install/node_modules/simple-get": { - "version": "4.0.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "license": "MIT", @@ -18633,30 +18507,6 @@ "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "optional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.2.0", "license": "MIT", @@ -21543,89 +21393,6 @@ "node": ">=10" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/bl": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/tar-fs/node_modules/buffer": { - "version": "5.7.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/tar-fs/node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tar-fs/node_modules/tar-stream": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tar-stream": { "version": "1.6.2", "dev": true, From 0ace6a45cdebc9f3a56b515e8a23c15003688694 Mon Sep 17 00:00:00 2001 From: Just Nev Date: Mon, 19 Jan 2026 19:25:27 +0200 Subject: [PATCH 2/3] Update prepackaged Jira plugin to v4.5.1 (#34978) Co-authored-by: Nevyana Angelova --- server/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Makefile b/server/Makefile index bbf3e1d9dfa..2ed636180dd 100644 --- a/server/Makefile +++ b/server/Makefile @@ -155,7 +155,7 @@ PLUGIN_PACKAGES ?= $(PLUGIN_PACKAGES:) PLUGIN_PACKAGES += mattermost-plugin-calls-v1.11.0 PLUGIN_PACKAGES += mattermost-plugin-github-v2.5.0 PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.11.0 -PLUGIN_PACKAGES += mattermost-plugin-jira-v4.5.0 +PLUGIN_PACKAGES += mattermost-plugin-jira-v4.5.1 PLUGIN_PACKAGES += mattermost-plugin-playbooks-v2.6.1 PLUGIN_PACKAGES += mattermost-plugin-servicenow-v2.4.0 PLUGIN_PACKAGES += mattermost-plugin-zoom-v1.11.0 From c62d103d760082d9d274af9e45d832647557be16 Mon Sep 17 00:00:00 2001 From: Nick Misasi Date: Mon, 19 Jan 2026 13:46:43 -0500 Subject: [PATCH 3/3] [MM-67160] Add audit logging for recap API endpoints (#34929) * Add audit logging for recap API endpoints - Add audit event constants for all recap operations - Implement Auditable interface for Recap model - Add comprehensive audit logging to all 6 recap endpoints - Log channel_ids to track implicit channel content access - Use LevelContent for content-related operations, LevelAPI for listing * Address PR feedback: standardize audit method order and extract helper function - Standardized order of audit record method calls across all handlers: set object type first, then prior state (if applicable), then result state - Extracted duplicated channel ID extraction logic into addRecapChannelIDsToAuditRec helper function --- server/channels/api4/recap.go | 76 +++++++++++++++++++++++++++++ server/public/model/audit_events.go | 10 ++++ server/public/model/recap.go | 21 ++++++++ 3 files changed, 107 insertions(+) diff --git a/server/channels/api4/recap.go b/server/channels/api4/recap.go index 6bc87a0fc7d..7f17e04ad2c 100644 --- a/server/channels/api4/recap.go +++ b/server/channels/api4/recap.go @@ -9,6 +9,7 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/channels/app" ) func (api *API) InitRecap() { @@ -27,6 +28,19 @@ func requireRecapsEnabled(c *Context) { } } +// addRecapChannelIDsToAuditRec extracts channel IDs from a recap and adds them to the audit record. +// This logs which channels' content was accessed through the recap operation. +func addRecapChannelIDsToAuditRec(auditRec *model.AuditRecord, recap *model.Recap) { + if len(recap.Channels) == 0 { + return + } + channelIDs := make([]string, 0, len(recap.Channels)) + for _, channel := range recap.Channels { + channelIDs = append(channelIDs, channel.ChannelId) + } + model.AddEventParameterToAuditRec(auditRec, "channel_ids", channelIDs) +} + func createRecap(c *Context, w http.ResponseWriter, r *http.Request) { requireRecapsEnabled(c) if c.Err != nil { @@ -54,12 +68,22 @@ func createRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventCreateRecap, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) + auditRec.AddEventObjectType("recap") + model.AddEventParameterToAuditRec(auditRec, "channel_ids", req.ChannelIds) + model.AddEventParameterToAuditRec(auditRec, "title", req.Title) + model.AddEventParameterToAuditRec(auditRec, "agent_id", req.AgentID) + recap, err := c.App.CreateRecap(c.AppContext, req.Title, req.ChannelIds, req.AgentID) if err != nil { c.Err = err return } + auditRec.Success() + auditRec.AddEventResultState(recap) + w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(recap); err != nil { c.Logger.Warn("Error encoding response", mlog.Err(err)) @@ -77,6 +101,11 @@ func getRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventGetRecap, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) + auditRec.AddEventObjectType("recap") + model.AddEventParameterToAuditRec(auditRec, "recap_id", c.Params.RecapId) + recap, err := c.App.GetRecap(c.AppContext, c.Params.RecapId) if err != nil { c.Err = err @@ -88,6 +117,12 @@ func getRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Log channel IDs accessed through viewing this recap summary + addRecapChannelIDsToAuditRec(auditRec, recap) + + auditRec.Success() + auditRec.AddEventResultState(recap) + if err := json.NewEncoder(w).Encode(recap); err != nil { c.Logger.Warn("Error encoding response", mlog.Err(err)) } @@ -99,12 +134,22 @@ func getRecaps(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventGetRecaps, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelAPI) + model.AddEventParameterToAuditRec(auditRec, "page", c.Params.Page) + model.AddEventParameterToAuditRec(auditRec, "per_page", c.Params.PerPage) + recaps, err := c.App.GetRecapsForUser(c.AppContext, c.Params.Page, c.Params.PerPage) if err != nil { c.Err = err return } + auditRec.Success() + if len(recaps) > 0 { + auditRec.AddMeta("recap_count", len(recaps)) + } + if err := json.NewEncoder(w).Encode(recaps); err != nil { c.Logger.Warn("Error encoding response", mlog.Err(err)) } @@ -121,6 +166,11 @@ func markRecapAsRead(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventMarkRecapAsRead, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) + auditRec.AddEventObjectType("recap") + model.AddEventParameterToAuditRec(auditRec, "recap_id", c.Params.RecapId) + // Check permissions recap, err := c.App.GetRecap(c.AppContext, c.Params.RecapId) if err != nil { @@ -133,12 +183,17 @@ func markRecapAsRead(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec.AddEventPriorState(recap) + updatedRecap, err := c.App.MarkRecapAsRead(c.AppContext, recap) if err != nil { c.Err = err return } + auditRec.Success() + auditRec.AddEventResultState(updatedRecap) + if err := json.NewEncoder(w).Encode(updatedRecap); err != nil { c.Logger.Warn("Error encoding response", mlog.Err(err)) } @@ -155,6 +210,11 @@ func regenerateRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventRegenerateRecap, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) + auditRec.AddEventObjectType("recap") + model.AddEventParameterToAuditRec(auditRec, "recap_id", c.Params.RecapId) + // Check permissions recap, err := c.App.GetRecap(c.AppContext, c.Params.RecapId) if err != nil { @@ -167,12 +227,20 @@ func regenerateRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Log channel IDs that will be re-summarized + addRecapChannelIDsToAuditRec(auditRec, recap) + + auditRec.AddEventPriorState(recap) + updatedRecap, err := c.App.RegenerateRecap(c.AppContext, c.AppContext.Session().UserId, recap) if err != nil { c.Err = err return } + auditRec.Success() + auditRec.AddEventResultState(updatedRecap) + if err := json.NewEncoder(w).Encode(updatedRecap); err != nil { c.Logger.Warn("Error encoding response", mlog.Err(err)) } @@ -189,6 +257,11 @@ func deleteRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec := c.MakeAuditRecord(model.AuditEventDeleteRecap, model.AuditStatusFail) + defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) + auditRec.AddEventObjectType("recap") + model.AddEventParameterToAuditRec(auditRec, "recap_id", c.Params.RecapId) + // Check permissions recap, err := c.App.GetRecap(c.AppContext, c.Params.RecapId) if err != nil { @@ -201,10 +274,13 @@ func deleteRecap(c *Context, w http.ResponseWriter, r *http.Request) { return } + auditRec.AddEventPriorState(recap) + if err := c.App.DeleteRecap(c.AppContext, c.Params.RecapId); err != nil { c.Err = err return } + auditRec.Success() ReturnStatusOK(w) } diff --git a/server/public/model/audit_events.go b/server/public/model/audit_events.go index 08cf9a2bea9..c26ba5182c5 100644 --- a/server/public/model/audit_events.go +++ b/server/public/model/audit_events.go @@ -257,6 +257,16 @@ const ( AuditEventBurnPost = "burnPost" // burn a post that was hidden due to burn on read ) +// Recaps +const ( + AuditEventCreateRecap = "createRecap" // create recap summarizing channel content + AuditEventGetRecap = "getRecap" // view a single recap + AuditEventGetRecaps = "getRecaps" // list user's recaps + AuditEventMarkRecapAsRead = "markRecapAsRead" // mark recap as read + AuditEventRegenerateRecap = "regenerateRecap" // regenerate recap with updated channel content + AuditEventDeleteRecap = "deleteRecap" // delete recap +) + // Preferences const ( AuditEventDeletePreferences = "deletePreferences" // delete user preferences diff --git a/server/public/model/recap.go b/server/public/model/recap.go index f1be9fff214..d3f02fc4c57 100644 --- a/server/public/model/recap.go +++ b/server/public/model/recap.go @@ -52,3 +52,24 @@ const ( RecapStatusCompleted = "completed" RecapStatusFailed = "failed" ) + +// Auditable returns safe-to-log fields for audit logging +func (r *Recap) Auditable() map[string]any { + channelIDs := make([]string, 0, len(r.Channels)) + for _, channel := range r.Channels { + channelIDs = append(channelIDs, channel.ChannelId) + } + + return map[string]any{ + "id": r.Id, + "user_id": r.UserId, + "title": r.Title, + "status": r.Status, + "channel_ids": channelIDs, + "total_message_count": r.TotalMessageCount, + "bot_id": r.BotID, + "create_at": r.CreateAt, + "update_at": r.UpdateAt, + "read_at": r.ReadAt, + } +}