From 941c8a86b41e62b9e8ca91b75dd7a33663cee83a Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 5 May 2026 17:20:22 +0530 Subject: [PATCH 01/17] fix: use a dedicated PAT for ghsa linear sync gh action (#14364) The default `GITHUB_TOKEN` cannot read `security-advisories`; that endpoint requires the `repository_advisories` permission, which is not available to the GitHub Actions installation token. Switched to a fine-grained PAT stored in `GHSA_READ_TOKEN`. Tested locally: the same PAT returns the full triage list Changes ---- - Switch to custom token - Add a discord alert for new advisories - Switch to python --- .github/scripts/ghsa_linear_sync.py | 195 +++++++++++++++++++++++++ .github/workflows/ghsa-linear-sync.yml | 94 ++---------- 2 files changed, 208 insertions(+), 81 deletions(-) create mode 100644 .github/scripts/ghsa_linear_sync.py diff --git a/.github/scripts/ghsa_linear_sync.py b/.github/scripts/ghsa_linear_sync.py new file mode 100644 index 000000000..064361b26 --- /dev/null +++ b/.github/scripts/ghsa_linear_sync.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Sync triage GitHub security advisories to Linear issues.""" + +from __future__ import annotations + +import os +import sys +from typing import Any + +import requests + +GITHUB_API = "https://api.github.com" +LINEAR_API = "https://api.linear.app/graphql" + +SEVERITY_PRIORITY = {"critical": 1, "high": 2, "medium": 3, "low": 4} +SEVERITY_COLOR = { + "critical": 15548997, + "high": 15105570, + "medium": 15844367, + "low": 3066993, +} +DEFAULT_COLOR = 9807270 + + +def required_env(name: str) -> str: + value = os.environ.get(name) + if not value: + sys.exit(f"Missing required env var: {name}") + return value + + +def fetch_triage_advisories(repo: str, token: str) -> list[dict[str, Any]]: + url: str | None = f"{GITHUB_API}/repos/{repo}/security-advisories" + params: dict[str, Any] | None = {"state": "triage", "per_page": 100} + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + } + advisories: list[dict[str, Any]] = [] + while url: + r = requests.get(url, headers=headers, params=params, timeout=30) + r.raise_for_status() + advisories.extend(r.json()) + next_link = r.links.get("next") + url = next_link["url"] if next_link else None + params = None + return advisories + + +def linear_call(query: str, variables: dict[str, Any], api_key: str) -> dict[str, Any]: + r = requests.post( + LINEAR_API, + headers={"Authorization": api_key}, + json={"query": query, "variables": variables}, + timeout=30, + ) + r.raise_for_status() + return r.json() + + +def linear_issue_exists(ghsa_id: str, api_key: str) -> bool: + query = ( + "query($q: String!) { issues(filter: {title: {contains: $q}}, first: 1) " + "{ nodes { id } } }" + ) + resp = linear_call(query, {"q": ghsa_id}, api_key) + return len(resp.get("data", {}).get("issues", {}).get("nodes", [])) > 0 + + +def linear_create_issue(input_data: dict[str, Any], api_key: str) -> dict[str, str] | None: + query = ( + "mutation($input: IssueCreateInput!) { issueCreate(input: $input) " + "{ success issue { identifier url } } }" + ) + resp = linear_call(query, {"input": input_data}, api_key) + create = resp.get("data", {}).get("issueCreate") or {} + if not create.get("success"): + return None + return create.get("issue") + + +def reporter_login(advisory: dict[str, Any]) -> str: + for credit in advisory.get("credits") or []: + user = (credit or {}).get("user") or {} + if user.get("login"): + return user["login"] + return "unknown" + + +def cvss_score(advisory: dict[str, Any]) -> str: + score = (advisory.get("cvss") or {}).get("score") + return str(score) if score is not None else "n/a" + + +def build_description(adv: dict[str, Any]) -> str: + return ( + f"**GHSA:** {adv['ghsa_id']}\n" + f"**CVE:** {adv.get('cve_id') or 'n/a'}\n" + f"**Severity:** {adv.get('severity') or 'unknown'} (CVSS {cvss_score(adv)})\n" + f"**Reporter:** {reporter_login(adv)}\n" + f"**Reported:** {(adv.get('created_at') or '').split('T')[0]}\n" + f"**Advisory:** {adv['html_url']}\n\n" + f"---\n\n" + f"{adv.get('description') or 'No description provided.'}" + ) + + +def post_discord(adv: dict[str, Any], issue: dict[str, str], webhook_url: str) -> None: + severity = adv.get("severity") or "unknown" + title = f"[{adv['ghsa_id']}] {adv['summary']}"[:250] + payload = { + "username": "GHSA Sync", + "embeds": [ + { + "title": title, + "url": issue["url"], + "color": SEVERITY_COLOR.get(severity, DEFAULT_COLOR), + "fields": [ + {"name": "Linear", "value": issue["identifier"], "inline": True}, + { + "name": "Severity", + "value": f"{severity} (CVSS {cvss_score(adv)})", + "inline": True, + }, + { + "name": "Advisory", + "value": f"[GitHub]({adv['html_url']})", + "inline": True, + }, + ], + } + ], + } + try: + requests.post(webhook_url, json=payload, timeout=10) + except requests.RequestException: + pass + + +def main() -> int: + repo = required_env("GITHUB_REPOSITORY") + gh_token = required_env("GHSA_READ_TOKEN") + linear_api_key = required_env("LINEAR_API_KEY") + team_id = required_env("LINEAR_TEAM_ID") + project_id = required_env("LINEAR_PROJECT_ID") + label_id = required_env("LINEAR_LABEL_ID") + discord_webhook = os.environ.get("DISCORD_WEBHOOK_URL") or None + + advisories = fetch_triage_advisories(repo, gh_token) + print(f"Fetched {len(advisories)} triage advisories") + + created = skipped = failed = 0 + + for adv in advisories: + ghsa_id = adv.get("ghsa_id") + if not ghsa_id: + failed += 1 + continue + + try: + if linear_issue_exists(ghsa_id, linear_api_key): + skipped += 1 + continue + + severity = adv.get("severity") or "unknown" + issue = linear_create_issue( + { + "title": f"[{ghsa_id}] {adv.get('summary', '')}", + "description": build_description(adv), + "teamId": team_id, + "projectId": project_id, + "labelIds": [label_id], + "priority": SEVERITY_PRIORITY.get(severity, 3), + }, + linear_api_key, + ) + except requests.RequestException: + failed += 1 + continue + + if not issue: + failed += 1 + continue + + created += 1 + if discord_webhook: + post_discord(adv, issue, discord_webhook) + + print(f"Created {created}, skipped {skipped}, failed {failed}") + return 1 if failed > 0 else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/ghsa-linear-sync.yml b/.github/workflows/ghsa-linear-sync.yml index 961114bc9..a21fbea7f 100644 --- a/.github/workflows/ghsa-linear-sync.yml +++ b/.github/workflows/ghsa-linear-sync.yml @@ -5,93 +5,25 @@ on: - cron: '0 4 * * *' # daily at 09:30 IST workflow_dispatch: {} +permissions: + contents: read + jobs: sync: runs-on: ubuntu-latest - permissions: - security-events: read steps: - - name: Fetch triage advisories - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api --paginate \ - -H "Accept: application/vnd.github+json" \ - "/repos/${{ github.repository }}/security-advisories?state=triage&per_page=100" \ - | jq -cs 'add | [.[] | { - ghsa_id, cve_id, summary, severity, state, html_url, - description, created_at, - cvss_score: .cvss.score, - reporter: ([.credits[]?.user.login] | first // "unknown") - }]' > advisories.json - echo "Fetched $(jq 'length' advisories.json) triage advisories" - - - name: Create Linear issues for new advisories + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: pip install requests==2.32.3 + - name: Sync advisories env: + GHSA_READ_TOKEN: ${{ secrets.GHSA_READ_TOKEN }} LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }} LINEAR_TEAM_ID: ${{ secrets.LINEAR_TEAM_ID }} LINEAR_PROJECT_ID: ${{ secrets.LINEAR_PROJECT_ID }} LINEAR_LABEL_ID: ${{ secrets.LINEAR_LABEL_ID }} - run: | - created_count=0 - skipped_count=0 - failed_count=0 - while read -r advisory; do - ghsa_id=$(printf '%s' "$advisory" | jq -r '.ghsa_id') - summary=$(printf '%s' "$advisory" | jq -r '.summary') - severity=$(printf '%s' "$advisory" | jq -r '.severity // "unknown"') - cve_id=$(printf '%s' "$advisory" | jq -r '.cve_id // "n/a"') - cvss=$(printf '%s' "$advisory" | jq -r '.cvss_score // "n/a"') - reporter=$(printf '%s' "$advisory" | jq -r '.reporter') - html_url=$(printf '%s' "$advisory" | jq -r '.html_url') - created_date=$(printf '%s' "$advisory" | jq -r '.created_at' | cut -dT -f1) - description=$(printf '%s' "$advisory" | jq -r '.description // "No description provided."') - - existing=$(curl -s -X POST https://api.linear.app/graphql \ - -H "Content-Type: application/json" \ - -H "Authorization: $LINEAR_API_KEY" \ - -d "$(jq -n --arg q "$ghsa_id" '{query: "query($q: String!) { issues(filter: {title: {contains: $q}}, first: 1) { nodes { id } } }", variables: {q: $q}}')" \ - | jq '.data.issues.nodes | length') - - if [ "${existing:-0}" -gt 0 ] 2>/dev/null; then - skipped_count=$((skipped_count+1)) - continue - fi - - priority=3 - case "$severity" in - critical) priority=1 ;; - high) priority=2 ;; - medium) priority=3 ;; - low) priority=4 ;; - esac - - title="[$ghsa_id] $summary" - body=$(printf '**GHSA:** %s\n**CVE:** %s\n**Severity:** %s (CVSS %s)\n**Reporter:** %s\n**Reported:** %s\n**Advisory:** %s\n\n---\n\n%s' \ - "$ghsa_id" "$cve_id" "$severity" "$cvss" "$reporter" "$created_date" "$html_url" "$description") - - success=$(curl -s -X POST https://api.linear.app/graphql \ - -H "Content-Type: application/json" \ - -H "Authorization: $LINEAR_API_KEY" \ - -d "$(jq -n \ - --arg title "$title" \ - --arg body "$body" \ - --arg teamId "$LINEAR_TEAM_ID" \ - --arg projectId "$LINEAR_PROJECT_ID" \ - --arg labelId "$LINEAR_LABEL_ID" \ - --argjson priority "$priority" \ - '{ - query: "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success } }", - variables: {input: {title: $title, description: $body, teamId: $teamId, projectId: $projectId, labelIds: [$labelId], priority: $priority}} - }')" | jq -r '.data.issueCreate.success // false') - - if [ "$success" = "true" ]; then - created_count=$((created_count+1)) - else - failed_count=$((failed_count+1)) - fi - done < <(jq -c '.[]' advisories.json) - echo "Created $created_count, skipped $skipped_count, failed $failed_count" - if [ "$failed_count" -gt 0 ]; then - exit 1 - fi + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + run: python3 .github/scripts/ghsa_linear_sync.py From 2192af80f405c70e3021191ff8b3f7e539c35e9d Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 5 May 2026 17:46:21 +0530 Subject: [PATCH 02/17] fix: html-escape captured values in helpcenter article markdown embeds (#14140) Embed templates interpolate regex captures from user-authored article URLs into HTML attribute values. CommonMark's angle-bracket link destination syntax allows characters that the capture regexes don't filter, so the unescaped substitution could produce malformed attribute output. Escaping at substitution time keeps the render deterministic regardless of the URL. ### How was this tested? Added specs. Fixes [CW-6934](https://linear.app/chatwoot/issue/CW-6934/) Co-authored-by: Sony Mathew Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- lib/custom_markdown_renderer.rb | 5 +++-- spec/lib/custom_markdown_renderer_spec.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/custom_markdown_renderer.rb b/lib/custom_markdown_renderer.rb index e7eefe5c8..d5c9a3351 100644 --- a/lib/custom_markdown_renderer.rb +++ b/lib/custom_markdown_renderer.rb @@ -77,9 +77,10 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer return nil unless embed_config template = embed_config['template'] - # Use Ruby's built-in named captures with gsub to handle CSS % values + # Use gsub (not format) so CSS `%` values in templates don't need escaping. + # Captured values are HTML-escaped since they land inside HTML attribute contexts. match_data.named_captures.each do |var_name, value| - template = template.gsub("%{#{var_name}}", value) + template = template.gsub("%{#{var_name}}", CGI.escapeHTML(value)) end template end diff --git a/spec/lib/custom_markdown_renderer_spec.rb b/spec/lib/custom_markdown_renderer_spec.rb index 3415a811d..cb13f6cf9 100644 --- a/spec/lib/custom_markdown_renderer_spec.rb +++ b/spec/lib/custom_markdown_renderer_spec.rb @@ -238,5 +238,23 @@ describe CustomMarkdownRenderer do expect(output).to include('allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"') end end + + context 'when captured values contain HTML-special characters' do + # CommonMark angle-bracket link destinations `[text]()` permit characters + # like `"` that the embed regex captures would otherwise pass through raw into + # attribute values. Captures are HTML-escaped before interpolation so the + # substituted value cannot break out of the surrounding attribute context. + it 'escapes double quotes in captured YouTube video_id' do + markdown = "\n[demo]()\n" + output = render_markdown(markdown) + expect(output).not_to include('onload="alert(1)"') + expect(output).to include('"') + end + + it 'leaves legitimate alphanumeric IDs untouched' do + output = render_markdown_link('https://www.youtube.com/watch?v=dQw4w9WgXcQ') + expect(output).to include('src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"') + end + end end end From cc5974da9bec82fccf7fcba1ed14b7326dc486c8 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 6 May 2026 09:54:00 +0400 Subject: [PATCH 03/17] feat(inbox): Add beta badge for TikTok and Voice channels (#14378) TikTok and Voice channels in the inbox creation flow now display a small "Beta" badge next to their title, signaling that these integrations are still being polished while keeping them available for users to try. Fixes https://linear.app/chatwoot/issue/CW-7026/add-beta-label-for-tiktok-and-voice-inboxes --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) --- .../dashboard/components/ChannelSelector.vue | 25 ++++++++++++++++--- .../components/widgets/ChannelItem.vue | 5 ++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/javascript/dashboard/components/ChannelSelector.vue b/app/javascript/dashboard/components/ChannelSelector.vue index 2e3ea7d86..ef5256577 100644 --- a/app/javascript/dashboard/components/ChannelSelector.vue +++ b/app/javascript/dashboard/components/ChannelSelector.vue @@ -1,5 +1,7 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue index 0b916a4ab..b941598fe 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroEditor.vue @@ -8,6 +8,7 @@ import { MACRO_ACTION_TYPES } from './constants'; import { useAlert } from 'dashboard/composables'; import actionQueryGenerator from 'dashboard/helper/actionQueryGenerator.js'; import { useMacros } from 'dashboard/composables/useMacros'; +import { useAdmin } from 'dashboard/composables/useAdmin'; const store = useStore(); const getters = useStoreGetters(); @@ -18,6 +19,7 @@ const router = useRouter(); const { t } = useI18n(); const { getMacroDropdownValues } = useMacros(); +const { isAdmin } = useAdmin(); const macro = ref(null); const mode = ref('CREATE'); @@ -33,6 +35,9 @@ provide('macroActionTypes', macroActionTypes); const uiFlags = computed(() => getters['macros/getUIFlags'].value); const macroId = computed(() => route.params.macroId); +const isPublicMacroReadOnly = computed( + () => macro.value?.visibility === 'global' && !isAdmin.value +); const fetchDropdownData = () => { store.dispatch('agents/get'); @@ -92,7 +97,7 @@ const initNewMacro = () => { action_params: [], }, ], - visibility: 'global', + visibility: isAdmin.value ? 'global' : 'personal', }; }; @@ -110,6 +115,8 @@ watch( ); const saveMacro = async macroData => { + if (isPublicMacroReadOnly.value) return; + try { const action = mode.value === 'EDIT' ? 'macros/update' : 'macros/create'; const successMessage = @@ -136,6 +143,8 @@ const saveMacro = async macroData => { diff --git a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue index 432b0d46b..b8b00aac9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/macros/MacroForm.vue @@ -16,6 +16,14 @@ export default { type: Object, default: () => ({}), }, + canManagePublicMacros: { + type: Boolean, + default: true, + }, + readOnly: { + type: Boolean, + default: false, + }, }, emits: ['submit'], setup() { @@ -112,19 +120,23 @@ export default {
- +
+ +
@@ -55,8 +89,13 @@ export default {

diff --git a/app/javascript/dashboard/routes/dashboard/conversation/SharedFiles.vue b/app/javascript/dashboard/routes/dashboard/conversation/SharedFiles.vue new file mode 100644 index 000000000..272407a53 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/conversation/SharedFiles.vue @@ -0,0 +1,421 @@ + + + diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js index 333009707..5e85c423c 100644 --- a/app/javascript/dashboard/store/modules/conversations/getters.js +++ b/app/javascript/dashboard/store/modules/conversations/getters.js @@ -57,6 +57,8 @@ const getters = { getSelectedChatAttachments: ({ selectedChatId, attachments }) => { return attachments[selectedChatId] || []; }, + getSelectedChatAttachmentsLoaded: ({ selectedChatId, attachments }) => + selectedChatId !== null && attachments[selectedChatId] !== undefined, getChatListFilters: ({ conversationFilters }) => conversationFilters, getLastEmailInSelectedChat: (stage, _getters) => { const selectedChat = _getters.getSelectedChat; diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js index 69bf9c2ac..a7ee3833e 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js @@ -328,6 +328,31 @@ describe('#getters', () => { }); }); + describe('#getSelectedChatAttachmentsLoaded', () => { + it('returns true when attachments have been fetched for the selected chat', () => { + const state = { selectedChatId: 1, attachments: { 1: [] } }; + expect(getters.getSelectedChatAttachmentsLoaded(state)).toBe(true); + }); + + it('returns true when the fetched attachment list is non-empty', () => { + const state = { + selectedChatId: 1, + attachments: { 1: [{ id: 1, file_name: 'test' }] }, + }; + expect(getters.getSelectedChatAttachmentsLoaded(state)).toBe(true); + }); + + it('returns false when attachments have not been fetched yet', () => { + const state = { selectedChatId: 1, attachments: {} }; + expect(getters.getSelectedChatAttachmentsLoaded(state)).toBe(false); + }); + + it('returns false when no chat is selected', () => { + const state = { selectedChatId: null, attachments: {} }; + expect(getters.getSelectedChatAttachmentsLoaded(state)).toBe(false); + }); + }); + describe('#getContextMenuChatId', () => { it('returns the context menu chat id', () => { const state = { contextMenuChatId: 1 }; diff --git a/app/views/api/v1/accounts/conversations/attachments.json.jbuilder b/app/views/api/v1/accounts/conversations/attachments.json.jbuilder index 167b18390..8bd647f27 100644 --- a/app/views/api/v1/accounts/conversations/attachments.json.jbuilder +++ b/app/views/api/v1/accounts/conversations/attachments.json.jbuilder @@ -3,6 +3,7 @@ json.meta do end json.payload @attachments do |attachment| + json.id attachment.push_event_data[:id] json.message_id attachment.push_event_data[:message_id] json.thumb_url attachment.push_event_data[:thumb_url] json.data_url attachment.push_event_data[:data_url] diff --git a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb index e007f4900..19d080b47 100644 --- a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb @@ -1039,6 +1039,8 @@ RSpec.describe 'Conversations API', type: :request do expect(response).to have_http_status(:success) response_body = response.parsed_body + attachment = conversation.messages.last.attachments.first + expect(response_body['payload'].first['id']).to eq(attachment.id) expect(response_body['payload'].first['file_type']).to eq('image') expect(response_body['payload'].first['sender']['id']).to eq(conversation.messages.last.sender.id) end From 9c8cfc40b6380be66e3e1c426a84ff8b1b21342e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 15:14:54 +0530 Subject: [PATCH 07/17] chore(deps): bump dompurify from 3.3.2 to 3.4.0 (#14074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.3.2 to 3.4.0.
Release notes

Sourced from dompurify's releases.

DOMPurify 3.4.0

Most relevant changes:

  • Fixed a problem with FORBID_TAGS not winning over ADD_TAGS, thanks @​kodareef5
  • Fixed several minor problems and typos regarding MathML attributes, thanks @​DavidOliver
  • Fixed ADD_ATTR/ADD_TAGS function leaking into subsequent array-based calls, thanks @​1Jesper1
  • Fixed a missing SAFE_FOR_TEMPLATES scrub in RETURN_DOM path, thanks @​bencalif
  • Fixed a prototype pollution via CUSTOM_ELEMENT_HANDLING, thanks @​trace37labs
  • Fixed an issue with ADD_TAGS function form bypassing FORBID_TAGS, thanks @​eddieran
  • Fixed an issue with ADD_ATTR predicates skipping URI validation, thanks @​christos-eth
  • Fixed an issue with USE_PROFILES prototype pollution, thanks @​christos-eth
  • Fixed an issue leading to possible mXSS via Re-Contextualization, thanks @​researchatfluidattacks and others
  • Fixed an issue with closing tags leading to possible mXSS, thanks @​frevadiscor
  • Fixed a problem with the type dentition patcher after Node version bump
  • Fixed freezing BS runs by reducing the tested browsers array
  • Bumped several dependencies where possible
  • Added needed files for OpenSSF scorecard checks

Published Advisories are here: https://github.com/cure53/DOMPurify/security/advisories?state=published

DOMPurify 3.3.3

  • Fixed an engine requirement for Node 20 which caused hiccups, thanks @​Rotzbua
Commits
  • 5b16e0b Getting 3.x branch ready for 3.4.0 release (#1250)
  • 8bcbf73 chore: Preparing 3.3.3 release
  • 5faddd6 fix: engine requirement (#1210)
  • 0f91e3a Update README.md
  • d5ff1a8 Merge branch 'main' of github.com:cure53/DOMPurify
  • c3efd48 fix: moved back from jsdom 28 to jsdom 20
  • 988b888 fix: moved back from jsdom 28 to jsdom 20
  • 2726c74 chore: Preparing 3.3.2 release
  • 6202c7e build(deps): bump @​tootallnate/once and jsdom (#1204)
  • 302b51d fix: Expanded the regex ever so slightly to also cover script
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.3.2&new-version=3.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index bb5b59ba3..35e09cfbc 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "countries-and-timezones": "^3.6.0", "date-fns": "2.21.1", "date-fns-tz": "^1.3.3", - "dompurify": "3.3.2", + "dompurify": "3.4.0", "flag-icons": "^7.2.3", "floating-vue": "^5.2.2", "highlight.js": "^11.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a3aee897..d76d0a061 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,8 +128,8 @@ importers: specifier: ^1.3.3 version: 1.3.8(date-fns@2.21.1) dompurify: - specifier: 3.3.2 - version: 3.3.2 + specifier: 3.4.0 + version: 3.4.0 flag-icons: specifier: ^7.2.3 version: 7.2.3 @@ -2194,9 +2194,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.2: - resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} - engines: {node: '>=20'} + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} domutils@3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} @@ -6861,7 +6860,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.2: + dompurify@3.4.0: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -9656,7 +9655,7 @@ snapshots: vue-dompurify-html@5.3.0(vue@3.5.12(typescript@5.6.2)): dependencies: - dompurify: 3.3.2 + dompurify: 3.4.0 vue: 3.5.12(typescript@5.6.2) vue-eslint-parser@9.4.3(eslint@8.57.0): From deb259c8d2ee09cdbd3856d93b12bb7cd0bceec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 15:33:40 +0530 Subject: [PATCH 08/17] chore(deps): bump rack from 3.2.5 to 3.2.6 (#13987) Bumps [rack](https://github.com/rack/rack) from 3.2.5 to 3.2.6.
Release notes

Sourced from rack's releases.

v3.2.6

Full Changelog: https://github.com/rack/rack/compare/v3.2.5...v3.2.6

Changelog

Sourced from rack's changelog.

[3.2.6] - 2026-04-01

Security

  • CVE-2026-34763 Root directory disclosure via unescaped regex interpolation in Rack::Directory.
  • CVE-2026-34230 Avoid O(n^2) algorithm in Rack::Utils.select_best_encoding which could lead to denial of service.
  • CVE-2026-32762 Forwarded header semicolon injection enables Host and Scheme spoofing.
  • CVE-2026-26961 Raise error for multipart requests with multiple boundary parameters.
  • CVE-2026-34786 Rack::Static header_rules bypass via URL-encoded path mismatch.
  • CVE-2026-34831 Content-Length mismatch in Rack::Files error responses.
  • CVE-2026-34826 Multipart byte range processing allows denial of service via excessive overlapping ranges.
  • CVE-2026-34835 Rack::Request accepts invalid Host characters, enabling host allowlist bypass.
  • CVE-2026-34830 Rack::Sendfile header-based X-Accel-Mapping regex injection enables unauthorized X-Accel-Redirect.
  • CVE-2026-34785 Rack::Static prefix matching can expose unintended files under the static root.
  • CVE-2026-34829 Multipart parsing without Content-Length header allows unbounded chunked file uploads.
  • CVE-2026-34827 Multipart header parsing allows denial of service via escape-heavy quoted parameters.
  • CVE-2026-26962 Improper unfolding of folded multipart headers preserves CRLF in parsed parameter values.
Commits
  • e1f22fd Bump patch version.
  • 31989fd Fix typo in test.
  • d268165 Fix test expectation.
  • 8f425de Add Ruby v4.0 to the test matrix.
  • bf83042 Drop EOL Rubies from external tests.
  • d50c4d3 Implement OBS unfolding for multipart requests per RFC 5322 2.2.3
  • bfb6914 Limit the number of quoted escapes during multipart parsing
  • b3e5945 Add Content-Length size check in Rack::Multipart::Parser
  • 7a8f326 Fix root prefix bug in Rack::Static
  • a57bc14 Only do a simple substitution on the x-accel-mapping paths
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sony Mathew --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d85999b57..f4d0277b4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -684,7 +684,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.2.5) + rack (3.2.6) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-contrib (2.5.0) From dd52f1d32b6cd81cd0de817e1b7c3ad30a963bcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 15:37:32 +0530 Subject: [PATCH 09/17] chore(deps): bump rack-session from 2.1.1 to 2.1.2 (#14017) Bumps [rack-session](https://github.com/rack/rack-session) from 2.1.1 to 2.1.2.
Changelog

Sourced from rack-session's changelog.

v2.1.2

  • CVE-2026-39324 Don't fall back to unencrypted coder if encryptors are present.
Commits
  • 504367b Bump patch version.
  • f43638c Don't fall back to unencrypted coder if encryptors are present.
  • dadcfe6 Bump actions/checkout from 4 to 5 (#54)
  • 4eb9ea8 Add top level session spec to validate existing formats.
  • 8f94577 Add rails to external tests.
  • 38ea47d Allow the v2 encryptor to serialize messages with Marshal (#44)
  • 43f2e3a Fix compatibility with older Rubies.
  • 6a060b8 Support UTF-8 data when using the JSON serializer (#39)
  • 8ce0146 Fix auth_tag retrieval on JRuby (#32)
  • 7727185 Add AEAD encryption (#23)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rack-session&package-manager=bundler&previous-version=2.1.1&new-version=2.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sony Mathew --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index f4d0277b4..bd21b7a36 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -699,7 +699,7 @@ GEM rack (>= 3.0.0, < 4) rack-proxy (0.7.7) rack - rack-session (2.1.1) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.1.0) From 8d7e926e06c815ecc40b51f448a2567c22f1ba94 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Wed, 6 May 2026 16:33:16 +0530 Subject: [PATCH 10/17] fix: [Snyk] Security upgrade video.js from 7.18.1 to 7.21.1 (#13973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![snyk-top-banner](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg) ### Snyk has created this PR to fix 1 vulnerabilities in the yarn dependencies of this project. #### Snyk changed the following file(s): - `package.json` #### Note for [zero-installs](https://yarnpkg.com/features/zero-installs) users If you are using the Yarn feature [zero-installs](https://yarnpkg.com/features/zero-installs) that was introduced in Yarn V2, note that this PR does not update the `.yarn/cache/` directory meaning this code cannot be pulled and immediately developed on as one would expect for a zero-install project - you will need to run `yarn` to update the contents of the `./yarn/cache` directory. If you are not using zero-install you can ignore this as your flow should likely be unchanged.
⚠️ Warning ``` Failed to update the yarn.lock, please update manually before merging. ```
#### Vulnerabilities that will be fixed with an upgrade: | | Issue | :-------------------------:|:------------------------- ![high severity](https://res.cloudinary.com/snyk/image/upload/w_20,h_20/v1561977819/icon/h.png 'high severity') | XML Injection
[SNYK-JS-XMLDOMXMLDOM-15869636](https://snyk.io/vuln/SNYK-JS-XMLDOMXMLDOM-15869636) --- > [!IMPORTANT] > > - Check the changes in this PR to ensure they won't cause issues with your project. > - Max score is 1000. Note that the real score may have changed since the PR was raised. > - This PR was automatically created by Snyk using the credentials of a real user. --- **Note:** _You are seeing this because you or someone else with access to this repository has authorized Snyk to open fix PRs._ For more information: 🧐 [View latest project report](https://app.snyk.io/org/chatwoot/project/3ca3819e-26b5-4e23-ac65-184abd9a6f10?utm_source=github&utm_medium=referral&page=fix-pr) 📜 [Customise PR templates](https://docs.snyk.io/scan-using-snyk/pull-requests/snyk-fix-pull-or-merge-requests/customize-pr-templates?utm_source=github&utm_content=fix-pr-template) 🛠 [Adjust project settings](https://app.snyk.io/org/chatwoot/project/3ca3819e-26b5-4e23-ac65-184abd9a6f10?utm_source=github&utm_medium=referral&page=fix-pr/settings) 📚 [Read about Snyk's upgrade logic](https://docs.snyk.io/scan-with-snyk/snyk-open-source/manage-vulnerabilities/upgrade-package-versions-to-fix-vulnerabilities?utm_source=github&utm_content=fix-pr-template) --- **Learn how to fix vulnerabilities with free interactive lessons:** 🦉 [XML Injection](https://learn.snyk.io/lesson/xxe/?loc=fix-pr) [//]: # 'snyk:metadata:{"breakingChangeRiskLevel":null,"FF_showPullRequestBreakingChanges":false,"FF_showPullRequestBreakingChangesWebSearch":false,"customTemplate":{"variablesUsed":[],"fieldsUsed":[]},"dependencies":[{"name":"video.js","from":"7.18.1","to":"7.21.1"}],"env":"prod","issuesToFix":["SNYK-JS-XMLDOMXMLDOM-15869636","SNYK-JS-XMLDOMXMLDOM-15869636"],"prId":"a31a1fb5-a9f0-4513-9316-be8798abfd9c","prPublicId":"a31a1fb5-a9f0-4513-9316-be8798abfd9c","packageManager":"yarn","priorityScoreList":[null],"projectPublicId":"3ca3819e-26b5-4e23-ac65-184abd9a6f10","projectUrl":"https://app.snyk.io/org/chatwoot/project/3ca3819e-26b5-4e23-ac65-184abd9a6f10?utm_source=github&utm_medium=referral&page=fix-pr","prType":"fix","templateFieldSources":{"branchName":"default","commitMessage":"default","description":"default","title":"default"},"templateVariants":["updated-fix-title","pr-warning-shown"],"type":"auto","upgrade":["SNYK-JS-XMLDOMXMLDOM-15869636"],"vulns":["SNYK-JS-XMLDOMXMLDOM-15869636"],"patch":[],"isBreakingChange":false,"remediationStrategy":"vuln"}' --------- Co-authored-by: snyk-bot Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 85 ++++++++++++++++++++------------------------------ 2 files changed, 35 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 35e09cfbc..1fbef2b1f 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "tinykeys": "^3.0.0", "turbolinks": "^5.2.0", "urlpattern-polyfill": "^10.0.0", - "video.js": "7.18.1", + "video.js": "7.21.1", "videojs-record": "4.5.0", "videojs-wavesurfer": "3.8.0", "virtua": "^0.48.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d76d0a061..b018dbdfe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,8 +206,8 @@ importers: specifier: ^10.0.0 version: 10.0.0 video.js: - specifier: 7.18.1 - version: 7.18.1 + specifier: 7.21.1 + version: 7.21.1 videojs-record: specifier: 4.5.0 version: 4.5.0 @@ -441,10 +441,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.25.6': - resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.26.7': resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} engines: {node: '>=6.9.0'} @@ -1386,17 +1382,14 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher - '@videojs/http-streaming@2.13.1': - resolution: {integrity: sha512-1x3fkGSPyL0+iaS3/lTvfnPTtfqzfgG+ELQtPPtTvDwqGol9Mx3TNyZwtSTdIufBrqYRn7XybB/3QNMsyjq13A==} + '@videojs/http-streaming@2.15.1': + resolution: {integrity: sha512-/uuN3bVkEeJAdrhu5Hyb19JoUo3CMys7yf2C1vUjeL1wQaZ4Oe8JrZzRrnWZ0rjvPgKfNLPXQomsRtgrMoRMJQ==} engines: {node: '>=8', npm: '>=5'} peerDependencies: video.js: ^6 || ^7 - '@videojs/vhs-utils@3.0.4': - resolution: {integrity: sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==} - engines: {node: '>=8', npm: '>=5'} - '@videojs/vhs-utils@3.0.5': resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==} engines: {node: '>=8', npm: '>=5'} @@ -1570,8 +1563,8 @@ packages: '@vueuse/shared@12.0.0': resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==} - '@xmldom/xmldom@0.7.13': - resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} + '@xmldom/xmldom@0.8.13': + resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} deprecated: this version has critical issues, please update to the latest version @@ -1618,8 +1611,8 @@ packages: activestorage@5.2.8: resolution: {integrity: sha512-bueFOxBGIAUdrjbLyBZ8Xlkcecy8vr05sCk5VV37BbFi+RehPoEjfvKX3iYYPY7RFVhl+L43W9/ZbN3xNNLPtQ==} - aes-decrypter@3.1.2: - resolution: {integrity: sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==} + aes-decrypter@3.1.3: + resolution: {integrity: sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==} agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -3197,8 +3190,8 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - m3u8-parser@4.7.0: - resolution: {integrity: sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==} + m3u8-parser@4.8.0: + resolution: {integrity: sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==} magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} @@ -3309,8 +3302,8 @@ packages: mlly@1.8.1: resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==} - mpd-parser@0.21.0: - resolution: {integrity: sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==} + mpd-parser@0.22.1: + resolution: {integrity: sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==} hasBin: true mri@1.2.0: @@ -4497,8 +4490,8 @@ packages: utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} - video.js@7.18.1: - resolution: {integrity: sha512-mnXdmkVcD5qQdKMZafDjqdhrnKGettZaGSVkExjACiylSB4r2Yt5W1bchsKmjFpfuNfszsMjTUnnoIWSSqoe/Q==} + video.js@7.21.1: + resolution: {integrity: sha512-AvHfr14ePDHCfW5Lx35BvXk7oIonxF6VGhSxocmTyqotkQpxwYdmt4tnQSV7MYzNrYHb0GI8tJMt20NDkCQrxg==} videojs-font@3.2.0: resolution: {integrity: sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==} @@ -4981,10 +4974,6 @@ snapshots: dependencies: '@babel/types': 7.26.0 - '@babel/runtime@7.25.6': - dependencies: - regenerator-runtime: 0.14.1 - '@babel/runtime@7.26.7': dependencies: regenerator-runtime: 0.14.1 @@ -5919,22 +5908,16 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@videojs/http-streaming@2.13.1(video.js@7.18.1)': + '@videojs/http-streaming@2.15.1(video.js@7.21.1)': dependencies: '@babel/runtime': 7.26.7 - '@videojs/vhs-utils': 3.0.4 - aes-decrypter: 3.1.2 + '@videojs/vhs-utils': 3.0.5 + aes-decrypter: 3.1.3 global: 4.4.0 - m3u8-parser: 4.7.0 - mpd-parser: 0.21.0 + m3u8-parser: 4.8.0 + mpd-parser: 0.22.1 mux.js: 6.0.1 - video.js: 7.18.1 - - '@videojs/vhs-utils@3.0.4': - dependencies: - '@babel/runtime': 7.26.7 - global: 4.4.0 - url-toolkit: 2.2.5 + video.js: 7.21.1 '@videojs/vhs-utils@3.0.5': dependencies: @@ -6213,7 +6196,7 @@ snapshots: transitivePeerDependencies: - typescript - '@xmldom/xmldom@0.7.13': {} + '@xmldom/xmldom@0.8.13': {} abab@2.0.6: {} @@ -6248,7 +6231,7 @@ snapshots: dependencies: spark-md5: 3.0.2 - aes-decrypter@3.1.2: + aes-decrypter@3.1.3: dependencies: '@babel/runtime': 7.26.7 '@videojs/vhs-utils': 3.0.5 @@ -8106,7 +8089,7 @@ snapshots: dependencies: yallist: 4.0.0 - m3u8-parser@4.7.0: + m3u8-parser@4.8.0: dependencies: '@babel/runtime': 7.26.7 '@videojs/vhs-utils': 3.0.5 @@ -8220,11 +8203,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mpd-parser@0.21.0: + mpd-parser@0.22.1: dependencies: '@babel/runtime': 7.26.7 '@videojs/vhs-utils': 3.0.5 - '@xmldom/xmldom': 0.7.13 + '@xmldom/xmldom': 0.8.13 global: 4.4.0 mri@1.2.0: {} @@ -9526,17 +9509,17 @@ snapshots: dependencies: base64-arraybuffer: 1.0.2 - video.js@7.18.1: + video.js@7.21.1: dependencies: - '@babel/runtime': 7.25.6 - '@videojs/http-streaming': 2.13.1(video.js@7.18.1) + '@babel/runtime': 7.26.7 + '@videojs/http-streaming': 2.15.1(video.js@7.21.1) '@videojs/vhs-utils': 3.0.5 '@videojs/xhr': 2.6.0 - aes-decrypter: 3.1.2 + aes-decrypter: 3.1.3 global: 4.4.0 keycode: 2.2.1 - m3u8-parser: 4.7.0 - mpd-parser: 0.21.0 + m3u8-parser: 4.8.0 + mpd-parser: 0.22.1 mux.js: 6.0.1 safe-json-parse: 4.0.0 videojs-font: 3.2.0 @@ -9547,7 +9530,7 @@ snapshots: videojs-record@4.5.0: dependencies: recordrtc: 5.6.2 - video.js: 7.18.1 + video.js: 7.21.1 videojs-wavesurfer: 3.8.0 webrtc-adapter: 9.0.1 @@ -9557,7 +9540,7 @@ snapshots: videojs-wavesurfer@3.8.0: dependencies: - video.js: 7.18.1 + video.js: 7.21.1 wavesurfer.js: 7.8.6 virtua@0.48.6(vue@3.5.12(typescript@5.6.2)): From 815593eec9c25fde840333c1b3b610be566eb042 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 6 May 2026 17:33:23 +0530 Subject: [PATCH 11/17] feat: display conversation ID conversation view (#14381) --- .../conversation/ConversationHeader.vue | 22 ++++++++++++++++++- .../i18n/locale/en/conversation.json | 1 + 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue index 4edfd643c..4a46afe73 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationHeader.vue @@ -13,6 +13,8 @@ import { conversationListPageURL } from 'dashboard/helper/URLHelper'; import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers'; import { useInbox } from 'dashboard/composables/useInbox'; import { useI18n } from 'vue-i18n'; +import { copyTextToClipboard } from 'shared/helpers/clipboard'; +import { useAlert } from 'dashboard/composables'; const props = defineProps({ chat: { @@ -91,6 +93,15 @@ const hasMultipleInboxes = computed( ); const hasSlaPolicyId = computed(() => props.chat?.sla_policy_id); + +const copyConversationId = async () => { + try { + await copyTextToClipboard(String(props.chat.id)); + useAlert(t('CONVERSATION.HEADER.COPY_ID_SUCCESS')); + } catch (error) { + // error + } +};