Commit Graph
6201 Commits
Author SHA1 Message Date
Muhsin KelothandGitHub fd5de4398d Merge branch 'develop' into feat/app-store-reviews 2026-05-22 12:29:06 +04:00
6b1d8203c6 fix: remove unused working hours endpoint (#13839)
Fixes #13752

Removes the standalone `working_hours` API endpoint instead of fixing
only the callback typo in `WorkingHoursController`.

## Why

The route is not used by the dashboard. The supported product flow
already updates business hours through `PATCH
/api/v1/accounts/:account_id/inboxes/:id`.

The standalone endpoint was already unusable in practice:
- The controller callback pointed to the wrong method.
- Fixing that callback alone would still leave the endpoint blocked by
missing `WorkingHourPolicy` authorization.
- Keeping the route would preserve unsupported API surface without
making the product flow better.

## What this change does

- Removes `PATCH/PUT /api/v1/accounts/:account_id/working_hours/:id`.
- Deletes `Api::V1::Accounts::WorkingHoursController`.
- Leaves the inbox working-hours update path unchanged.

Compatibility note: this removes an undocumented endpoint that was
already unusable in practice. Working-hours updates should continue to
go through the supported inbox update API.

## Validation

- Ran `bin/rails routes -g working_hours` and confirmed the standalone
working-hours API route is no longer present.
- Searched for remaining `WorkingHoursController` and `resources
:working_hours` references.

---------

Co-authored-by: easonysliu <easonysliu@tencent.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-22 13:41:09 +05:30
Muhsin KelothandGitHub df538a6a3b Merge branch 'develop' into feat/app-store-reviews 2026-05-22 12:09:40 +04:00
Muhsin 361b28cb03 chore: Use per-account feature flag for App Store channel 2026-05-22 11:56:21 +04:00
Sony MathewandGitHub 4550c4130b fix: order labels, teams, channels in sidebar by unread count (CW-7151) (#14510)
# Pull Request Template

## Description

Ordered the conversation sidebar labels, teams and channels by the
unread count.

Fixes # CW-7151

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Verified manually. Adding the screenshot below.
<img width="625" height="833" alt="Screenshot 2026-05-20 at 10 24 30 PM"
src="https://github.com/user-attachments/assets/ad04464d-0fc3-4ac7-b8cc-786e9647a299"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2026-05-22 13:11:29 +05:30
e09496078b fix(widget): improve dark mode select options (#14538)
Fixes the web widget select dropdown styling in dark mode so native
select options remain readable against the widget's dark UI.

Closes
None

## Screenshots

Previous state:

<img width="426" height="764" alt="Previous dark mode dropdown issue"
src="https://github.com/user-attachments/assets/812fa88c-ae5a-4769-be14-748fbbaf7dfe"
/>

Current state:

<img width="1210" height="610" alt="Current dark mode dropdown styling"
src="https://github.com/user-attachments/assets/0ec9b6d7-025d-4b97-b43e-ef026857f9c4"
/>

## How to test

1. Enable the web widget pre-chat form with a list/select field.
2. Load the widget with dark mode enabled.
3. Open the select field and verify the select control and option text
remain readable in dark mode.

## What changed

- Adds light/dark color-scheme handling for widget native selects.
- Applies explicit option background and text colors so dark-mode select
options do not inherit unreadable colors from the browser popup.

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-05-22 12:59:48 +05:30
Muhsin KelothandGitHub 15227a424a Merge branch 'develop' into feat/app-store-reviews 2026-05-22 10:55:59 +04:00
Muhsin 1e36126a00 Merge branch 'feat/app-store-reviews' of github.com:chatwoot/chatwoot into feat/app-store-reviews 2026-05-22 10:51:25 +04:00
Khoa NguyenandGitHub 4cdfe4168c chore: resolve sass and vue compiler deprecation warnings (#13794) 2026-05-22 12:16:43 +05:30
Sivin VargheseandGitHub 0722750a55 chore: Captain reply actions not showing correctly with content (#14160) 2026-05-22 12:16:19 +05:30
Muhsin eb9647d6c6 Merge branch 'feat/app-store-reviews' of github.com:chatwoot/chatwoot into feat/app-store-reviews 2026-05-22 10:35:44 +04:00
Muhsin KelothandGitHub b3fa179b7f Merge branch 'develop' into feat/app-store-reviews 2026-05-22 10:26:46 +04:00
Muhsin da0a122919 Merge branch 'feat/app-store-reviews' of github.com:chatwoot/chatwoot into feat/app-store-reviews 2026-05-22 10:26:06 +04:00
Aakash BakhleandGitHub bef25781de feat(attachments): add XML and PFX file support (#14539)
Update frontend allowed file types and FileIcon mapping, and backend
Attachment constants to accept .xml and .pfx files

# Pull Request Template

## Description
Customer also wanted XML support along with .pfx

Following up on #14456

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
locally

<img width="864" height="512" alt="CleanShot 2026-05-22 at 11 43 20@2x"
src="https://github.com/user-attachments/assets/4cbf65d4-b919-4a4b-bf75-a4f2e8690586"
/>

<img width="870" height="1440" alt="CleanShot 2026-05-22 at 11 44 03@2x"
src="https://github.com/user-attachments/assets/e763b49d-4365-4c45-9b43-b0c39af87656"
/>


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
2026-05-22 11:55:16 +05:30
Muhsin KelothandMuhsin eb30c8dea4 Merge branch 'develop' into feat/app-store-reviews 2026-05-22 10:23:33 +04:00
Sojan JoseandGitHub 1d7a9093d2 fix: clarify agent availability swagger fields (#14533)
Clarifies the agent availability API documentation so request payloads
use the writable `availability` field, while `availability_status`
remains documented as a read-only response field.

## Closes

Closes #13873

## Why

The backend already supports updating an agent's configured availability
through `availability`, but the Swagger request payloads documented
`availability_status`. That made clients follow a read-only response
field and see successful requests without the intended availability
change.

## What changed

- Replaces `availability_status` with `availability` in agent
create/update request schemas.
- Updates the availability enum to `online`, `busy`, and `offline`.
- Marks response `availability_status` as read-only and explains that it
is derived from configured availability, auto-offline, and presence.
- Regenerates the combined and tag-group Swagger JSON files.

## Validation

- `bundle exec rails swagger:build`
- `bundle exec rspec spec/swagger/openapi_spec.rb`
- `git diff --check`
2026-05-22 11:33:19 +05:30
3c67c41544 chore: support PFX filetype in attachment uploads (#14456)
# Pull Request Template

## Description

This PR expands the default upload rules to support PFX certificate
files (`application/x-pkcs12`, `application/pkcs12`, `.pfx`) across
private notes, Website, Email, and Telegram channels.

Also adds `.xls` / `.xlsx` extension fallbacks for cases where browsers
upload Excel files with an empty or generic MIME type.

### Utils Repo PR: https://github.com/chatwoot/utils/pull/61

Fixes
https://linear.app/chatwoot/issue/CW-7085/support-more-file-types-in-private-notes-and-in-app

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

### Screenshots
<img width="330" height="218" alt="image"
src="https://github.com/user-attachments/assets/80823250-893e-4509-adb9-61f845359151"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
2026-05-22 11:09:27 +05:30
Muhsin KelothandGitHub 2a624ac800 Merge branch 'develop' into feat/app-store-reviews 2026-05-22 09:03:48 +04:00
d0ecdc14d8 feat(webhooks): Emit inbox_updated when an inbox is disconnected (#14504)
Chatwoot now lets external apps know when an inbox loses its connection
and needs re-authentication. When a channel's authorization expires (for
example, an email inbox disconnects), Chatwoot fires an `inbox_updated`
webhook reflecting the new `reauthorization_required` status, and fires
it again once the inbox is re-authenticated. Integrators can keep their
own view of which inboxes are healthy without polling the API.

This is gated behind the `ENABLE_INBOX_EVENTS` installation flag — the
**Inbox updated** webhook subscription only appears in the dashboard
when that flag is enabled, so no event is offered that the backend
wouldn't dispatch.

Fixes
https://linear.app/chatwoot/issue/CW-7148/emit-inbox-webhook-when-an-inbox-is-disconnected

## How to test

1. Set `ENABLE_INBOX_EVENTS=true` and restart the app.
2. In **Settings → Integrations → Webhooks**, add a webhook and
subscribe to **Inbox updated**.
3. Disconnect an inbox — let an email/Instagram channel hit its
auth-error threshold, or run `inbox.channel.prompt_reauthorization!` in
a console.
4. The endpoint receives an `inbox_updated` event whose
`changed_attributes` shows `reauthorization_required` flipping to
`true`.
5. Re-authenticate the inbox (or run `inbox.channel.reauthorized!`) —
the endpoint receives the `true → false` transition.
6. Confirm the **Inbox updated** option is hidden when
`ENABLE_INBOX_EVENTS` is unset.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-05-22 09:00:18 +04:00
Tanmay Deep SharmaandGitHub b1db6c3e9b fix: make zadd function optimised to stay in rubocop limits (#14520)
## Description
Fixes rubocop for alfred.rb file on develop 

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2026-05-21 17:24:51 +05:30
Shivam MishraandGitHub 3d20a7b049 feat: generate Help Center for Onboarding (#14370)
## Manually triggering help center generation

Open a Rails console (`bundle exec rails console`):

```ruby
account = Account.find(<ACCOUNT_ID>)
user    = account.users.first

# Optional: refresh brand info from the customer's website
domain = 'example.com'
result = WebsiteBrandingService.new("noreply@#{domain}").perform
account.update!(
  name: result[:title].presence || account.name,
  custom_attributes: account.custom_attributes.merge('website' => domain, 'brand_info' => result)
)

# Optional: wipe existing portals so a fresh one is created
account.portals.destroy_all

Onboarding::HelpCenterCreationService.new(account, user).perform
```

Sidekiq must be running — articles are written by
`Onboarding::HelpCenterArticleGenerationJob`. Avoid running on
production; generation calls the LLM provider.


### Generation flow (Happy Path) 

```mermaid
sequenceDiagram
    autonumber

    participant Kickoff as HelpCenterCreationService
    participant DB as DB
    participant GenJob as HelpCenterArticleGenerationJob
    participant Curator as HelpCenterCurator
    participant Firecrawl as Firecrawl
    participant CuratorLLM as Curation LLM
    participant Redis as Redis Progress
    participant WriterJob as HelpCenterArticleWriterJob
    participant Builder as HelpCenterArticleBuilder
    participant WriterLLM as Writer LLM
    participant Cable as ActionCable

    Kickoff->>DB: Create portal for account<br/>homepage_link=https://chatwoot.com
    Kickoff->>DB: Attach brand logo if available
    Kickoff->>GenJob: Enqueue generation job<br/>account_id, portal_id, user_id, generation_id

    GenJob->>Curator: Curate help center plan
    Curator->>Firecrawl: map https://chatwoot.com<br/>search: docs help support faq
    Firecrawl-->>Curator: Return discovered links
    Curator->>CuratorLLM: Select categories + article plans<br/>from discovered links only
    CuratorLLM-->>Curator: Return categories, articles, allowed_urls

    GenJob->>DB: Create portal categories
    GenJob->>GenJob: Stamp articles with category_id
    GenJob->>GenJob: Filter article URLs against allowed_urls
    GenJob->>GenJob: Drop articles with no category<br/>or no approved source URLs

    GenJob->>Redis: Start progress<br/>status=generating, total=N, finished=0

    loop For each approved article
      GenJob->>WriterJob: Enqueue writer job<br/>title, category_id, approved URLs
    end

    par Writer jobs run independently
      WriterJob->>Builder: Build article from approved URLs
      Builder->>Firecrawl: batch_scrape approved URLs
      Firecrawl-->>Builder: Return Markdown source pages
      Builder->>WriterLLM: Rewrite sources into one article
      WriterLLM-->>Builder: Return title, description, Markdown content
      Builder->>DB: Create draft portal article<br/>meta.source_urls
      WriterJob->>Redis: Increment finished count
      WriterJob->>Cable: Broadcast help_center.article_generated
    end

    WriterJob->>Redis: If finished >= total<br/>mark status=completed
    WriterJob->>Cable: Broadcast help_center.generation_completed
```

### Redis State Management

```mermaid
 stateDiagram-v2
    [*] --> active_pointer_set
    active_pointer_set --> generating: generation job creates valid plan
    active_pointer_set --> skipped: curation skipped/failed

    generating --> generating: each writer job increments finished
    generating --> completed: finished == total
    generating --> ignored_completion: generation_id superseded

    skipped --> [*]
    completed --> [*]
    ignored_completion --> [*]
```
2026-05-21 16:25:01 +05:30
Tanmay Deep SharmaandGitHub 3cd8cf43ce fix: atomically claim conversation to prevent duplicate assignment (#14495)
## Description

Fixes a bug under Assignment V2 where a single conversation could be
reassigned dozens of times in a row by the system, producing long stacks
of "Assigned to X by Automation System via <policy>" activity messages
alternating between agents. After this change each unassigned
conversation is assigned exactly once, even on busy inboxes.

## Fixes # (issue)


## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

## How to reproduce
1. Enable `assignment_v2` on an account with at least 2 online agents in
an inbox.
2. Generate sustained resolve/snooze activity in the inbox (each one
enqueues `AutoAssignment::AssignmentJob` for the whole inbox).
3. Watch any one unassigned conversation while the jobs drain — pre-fix
it picks up multiple back-to-back "Assigned to …" activity rows
alternating between agents.


## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2026-05-21 16:14:28 +05:30
Muhsin 379daeef18 Merge branch 'feat/app-store-reviews' of github.com:chatwoot/chatwoot into feat/app-store-reviews 2026-05-21 08:29:53 +04:00
Muhsin KelothandGitHub 127a544a89 Merge branch 'develop' into feat/app-store-reviews 2026-05-21 08:23:54 +04:00
Muhsin 572d6d2e40 feat(inboxes): add app store reviews channel 2026-05-21 08:21:35 +04:00
Muhsin 609e27a4c4 feat(inboxes): add app store reviews channel 2026-05-21 08:18:22 +04:00
f33e469e9a feat: Unread Count: Frontend changes for showing unread count badges (3/3)[CW-6851] (#14372)
# Pull Request Template

## Description
This is the third and final PR in a series of PRs for Introducing unread
counts in the sidebar for inboxes and labels.

In this PR:
* Added frontend changes to show the badges for unread counts for
Inboxes and Labels
* Added specs for the changes

Issue:
https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts



## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Tested this locally. Cases to test:
* Send a message from the widget and see if the count changes
* Mark a conversation as unread and see the count change for inbox
* Open an unread conversation as agent and see the count go down
* Add a label to an unread conversation from sidebar right click action
without opening the conversation and see the count of un-reads on the
label change

Added the screenshot of how it will look like

<img width="614" height="990" alt="Screenshot 2026-05-05 at 7 00 11 PM"
src="https://github.com/user-attachments/assets/99fbaa9f-bcf2-4d8d-86e2-5727f652a9dd"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-20 19:21:25 +05:30
27f2c2b392 feat: Unread Count: added api, store refresher, invalidation and events (2/3)[CW-6851] (#14369)
# Pull Request Template

## Description

This is the second PR in a series of PRs for Introducing unread counts
in the sidebar for inboxes and labels.

In this PR:

* added api for unread counts 
* Added the store refresher and invalidation with event listeners
* Added action cable event
* Added specs for the changes

Issue:
https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-20 17:36:09 +05:30
40deaef458 feat: Store WhatsApp BSUID identifiers from inbound webhooks (#14436)
Adds storage support for WhatsApp business-scoped user identifiers
received from Meta Cloud API and Twilio WhatsApp webhooks. The change
keeps existing phone-based behavior intact, stores BSUID and parent
BSUID values as additional `contact_inboxes.source_id` rows for the same
contact, and allows BSUID-only inbound messages to create contacts,
conversations, and messages without requiring a phone number.

Related: https://github.com/chatwoot/chatwoot/issues/13837

**What changed**
- Extended WhatsApp source ID validation to accept regular BSUID and
parent BSUID formats.
- For Meta Cloud API, stores phone, `user_id`, and `parent_user_id`
identifiers as contact inbox source IDs when they are present.
- For Twilio WhatsApp, stores phone, `ExternalUserId`, and
`ParentExternalUserId` identifiers as contact inbox source IDs while
preserving the existing `whatsapp:` Twilio source ID shape.
- Supports BSUID-only inbound messages by creating a contact, contact
inbox, conversation, and message even when the phone number is missing.
- Links phone-first and later BSUID-only messages to the same contact
when the first payload contains both phone and BSUID.
- Stores WhatsApp usernames in contact `additional_attributes`, matching
existing social channel patterns.
- Keeps existing phone-based outbound and new-conversation behavior
unchanged for this milestone.

**How to test**
1. Send a Meta Cloud webhook payload with both `wa_id` and `user_id`.
2. Verify Chatwoot creates or finds the phone `contact_inbox` and also
creates a BSUID `contact_inbox` for the same contact.
3. Send a later Meta Cloud payload for the same user with only `user_id`
/ `from_user_id`.
4. Verify Chatwoot finds the BSUID `contact_inbox` and creates the
inbound message without requiring a phone number.
5. Send a Twilio WhatsApp webhook with `From: whatsapp:+E164`,
`ExternalUserId`, and optionally `ParentExternalUserId`.
6. Verify Chatwoot stores the Twilio phone and BSUID identifiers as
`whatsapp:`-prefixed source IDs for the same contact.
7. Send a Twilio WhatsApp webhook where `From` is `whatsapp:<BSUID>` and
there is no phone number.
8. Verify Chatwoot creates the contact, contact inbox, conversation, and
message without a phone number.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-05-20 13:36:43 +04:00
3fae800936 feat: base layer for unread counts (store, counter and builder) (1/3)[CW-6851] (#14368)
## Description

This is the first PR in a series of PRs for Introducing unread counts in
the sidebar for inboxes and labels.

In this PR:

* Added the unread store, counter and builder modules
* Added redis keys for unread count management
* Added specs for all 3 modules, some specs are for testing enterprise
only feature like specific roles and permissions which are added in the
respective enterprise folder itself.

**Note**
None of this changes affect anything else and nothing is wired to
existing modules.

Issue:
https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-20 14:26:21 +05:30
1913ccadfa fix: [CW-7141] fix gem audit issue for sidekiq-cron and devise (#14497)
# Pull Request Template

## Description
* sidekiq-cron upgraded to 2.4.0
* Sidekiq constrained to stay on 7.3.x
* Devise advisory ignored in .bundler-audit.yml with the reason:
Chatwoot does not enable Timeoutable, so the timeout redirect path is
not reachable


### Details
The sidekiq-cron upgrade is from 1.12.0 to 2.4.0.

What changed that matters for us:

Fixes the reported Sidekiq Web UI reflected XSS in 2.4.0.
Adds namespace handling changes from the 2.x series. Chatwoot does not
use custom cron namespaces in config/schedule.yml, so the migration
guide says no action is needed for our usage.
Drops support for old Sidekiq/Redis versions. We are still on Sidekiq
7.3.1, which is supported.
Adds new dependencies: cronex and unicode.
Keeps the same APIs we use: Sidekiq::Cron::Job.load_from_hash!(schedule,
source: 'schedule'), Sidekiq::Cron::Job.destroy(name), and require
'sidekiq/cron/web' still exist.
Chance of breakage: low, based on the current Chatwoot usage.

The main thing I would watch after deploy is scheduled job registration
in Sidekiq. The one subtle area is namespace behavior: if production has
custom, manually-created cron jobs using non-default namespaces,
load_from_hash! cleanup behavior could matter. For the committed
config/schedule.yml jobs, which do not specify namespaces, they should
continue in the default namespace.

For concerns around Devise, it does not look exploitable in current
Chatwoot, because Chatwoot does not enable Devise :timeoutable.
I checked:
app/models/user.rb (line 59) lists the Devise modules, and :timeoutable
is not included.
config/initializers/devise.rb (line 164) has the timeoutable section,
but config.timeout_in is commented out.
SuperAdmin inherits from User, so it does not add a separate timeoutable
path either.
So from a practical security perspective: the vulnerable redirect path
requires warden_message == :timeout, which is only produced by Devise
Timeoutable. Since Chatwoot does not use Timeoutable, this warning is
not currently reachable.
Is the patch really needed? Strictly for current exploitability: no.

Fixes #CW-7141

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Spec and lints and change-log checks with codex.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-05-19 20:39:40 +05:30
Sivin VargheseandGitHub bca95efb82 feat: add image resize support in articles (#14293) 2026-05-19 19:34:43 +05:30
6560dbb68d feat: Add an option on the dashboard to allow switching help center layout (#14491)
<img width="633" height="431" alt="Screenshot 2026-05-18 at 12 32 55 PM"
src="https://github.com/user-attachments/assets/682d4c5f-4c76-465b-8d2f-92fbc2bb2a40"
/>

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-05-19 06:42:48 -07:00
Sivin VargheseandGitHub 497d34c097 fix: render markdown in CSAT survey messages (#14468) 2026-05-19 10:27:14 +05:30
4371793741 fix: captain-v2 cannot see image attachments shared via email (#14449)
## Description

Inbound email attachments are stored with `file_type: 'file'` regardless
of their actual MIME type. As a result, image screenshots shared by
customers via email are not exposed to Captain V2's multimodal pipeline
— `Captain::OpenAiMessageBuilderService#attachment_parts` selects images
via `attachments.where(file_type: :image)` and emits a placeholder
`"User has shared an attachment"` text part instead of an `image_url`
part. The model never gets the image, so Captain keeps asking the
customer to retype information that is already visible in the
screenshot.

This PR makes the email mailbox derive `file_type` from the blob's
`content_type` using the existing shared `FileTypeHelper`, matching how
every other inbound channel (`twilio`, `sms`, `telegram`, `line`,
`tiktok`, `twitter`, `messenger`) and `MessageBuilder` already classify
attachments.

Fixes #14448

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Reproduced and verified on a self-hosted production instance:

1. Real customer reply via email with a PNG screenshot of an in-app
error.

   Before:
   ```ruby
   a = Message.find(<id>).attachments.first
   a.file_type                # => "file"
   a.file.blob.content_type   # => "image/png"
Captain::OpenAiMessageBuilderService.new(message:
a.message).generate_content
   # => [{type: 'text', text: '...'},
# {type: 'text', text: 'User has shared an attachment'}]  no image_url
   ```
Captain reply: "Please copy and paste the full error text…" (model never
saw the image).

2. After the patch + force-recreate, same conversation:
   ```ruby
   a.file_type                # => "image"
Captain::OpenAiMessageBuilderService.new(message:
a.message).generate_content
   # => [{type: 'text',      text: '...'},
# {type: 'image_url', image_url: {url: 'https://.../<blob>.png'}}] 
   ```
Captain reply now correctly references the on-screen error text from the
screenshot via the multimodal vision path — no more deflection.

3. Regression sanity-check on non-image attachments (PDF / Office docs):
`file_type` falls through to `:file`, behavior unchanged.

## Notes for self-hosted operators

Existing email image attachments in the DB will still have `file_type:
'file'`. A one-shot backfill is straightforward and safe (no data loss,
only metadata):

```ruby
Attachment.joins(message: :conversation)
          .where(messages: { content_type: 'incoming_email' })
          .where(file_type: 'file')
          .find_each do |a|
  next unless a.file.attached?
  ct = a.file.blob.content_type.to_s
  next unless ct.start_with?('image/', 'audio/', 'video/')
  new_type = ct.start_with?('image/') ? :image : (ct.start_with?('video/') ? :video : :audio)
  a.update_columns(file_type: Attachment.file_types[new_type])
end
```

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective — happy to add a
`mailbox_helper_spec` example for `process_regular_attachments` if
maintainers prefer; existing specs in that file focus on inline-image
handling.

---------

Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
2026-05-19 10:11:32 +05:30
64585faff0 feat: Add a documentation layout design for public help center portal (#14403)
https://github.com/user-attachments/assets/fc4d15f9-2b54-4627-940f-94772ec739b1

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2026-05-18 12:30:08 -07:00
Sony Mathew e05008e0a4 Merge branch 'release/4.14.0' into develop 2026-05-18 21:55:01 +05:30
Sony Mathew 92f8c13ce5 Bump version to 4.14.0 2026-05-18 21:53:43 +05:30
c4089f2226 fix(csat): Require confirmation before submitting rating (#14450)
Customers reported that the CSAT survey was recording their rating the
instant they tapped a star — leaving no chance to correct an accidental
pick. This change lets the customer freely change their selection until
they actually submit the comment/feedback. The rating still saves on
click (so we don't lose ratings when a customer never types a comment),
but it stays editable until the comment form is submitted. Once that
happens, the rating locks.

The flow on both surfaces:

- Customer taps a star/emoji → rating is saved.
- Customer taps a different star/emoji → previous save is overwritten
with the new value.
- Customer types a comment and submits → latest rating + comment are
saved together.
- After that submit, the rating is locked and can't be changed.

Two surfaces are updated:

- **Standalone survey page** (`/survey/responses/:uuid`) — the rating
buttons remain re-tappable until the Feedback form is submitted; once
submitted, both rating and feedback lock.
- **In-conversation widget CSAT** — same behavior, the inline
arrow-submit button on the feedback form is the locking action.

In-flight guards prevent a race where the customer changes their pick
mid-network-call (raised by the codex review on the earlier revision):
while a save is in flight, the rating controls are temporarily disabled
so the request and the displayed selection can't diverge.

## Closes

-
https://linear.app/chatwoot/issue/CW-7061/csat-star-rating-submits-on-first-click-needs-confirmation-step

## How to test

**Standalone survey page**
1. Enable CSAT on any inbox (Settings → Inboxes → Configuration → CSAT
survey).
2. Resolve a conversation in that inbox so a CSAT message is generated.
3. Open the survey URL:
`{FRONTEND_URL}/survey/responses/{conversation.uuid}` (easiest: `bundle
exec rails runner 'puts Conversation.joins(:messages).where(messages: {
content_type: "input_csat" }).last.csat_survey_link'`).
4. Tap a star/emoji — confirm the rating saves (Network panel shows a
PUT to `/public/api/v1/csat_survey/{uuid}`).
5. Tap a different star/emoji — confirm a second PUT goes out with the
new value; the latest selection is reflected.
6. Type a comment and hit Submit feedback — confirm rating + feedback
persist; both controls now lock.
7. Reload the page — the locked state is rehydrated correctly.

**Widget CSAT**
1. From an inbox with CSAT enabled, resolve a conversation that has an
active widget session.
2. In the widget, the CSAT card appears with stars/emojis + the inline
comment box.
3. Tap a star/emoji — confirm a PATCH goes out and the selection visibly
updates.
4. Tap different stars/emojis — confirm each overrides the previous
save.
5. Type a comment and click the arrow — rating + comment submit
together; stars lock.

Both display types (emoji and 5-star) should behave consistently.

## What changed

- `app/javascript/survey/views/Response.vue` — `selectRating()` saves on
every tap and short-circuits while a save is in flight (or after
feedback was submitted). Rating components are disabled by
`isFeedbackSubmitted || isUpdating` so the lock follows the feedback
submission, not the first rating tap.
- `app/javascript/survey/components/Rating.vue` — new `isDisabled` prop.
The disabled / hover styling and click guard key off it instead of the
presence of `selectedRating`, so emojis stay re-clickable until the
feedback step locks them.
- `app/javascript/shared/components/CustomerSatisfaction.vue` — same
shape for the widget: rating click auto-submits and re-clicks override
the previous save; controls disabled while a submit is in flight;
emoji-button styling and the inline `StarRating` lock on
`isFeedbackSubmitted || isUpdating`.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-05-18 21:46:30 +05:30
Sivin VargheseandGitHub bcb66cdcc0 chore: Support creating articles from category view (#14406)
# Pull Request Template

## Description

This PR adds support for creating articles directly from the category
view. Previously, articles could only be created from the main articles
page. With this change, users can now create a new article while
browsing a specific category, making the workflow faster and more
convenient.

Fixes
https://linear.app/chatwoot/issue/CW-7050/create-an-article-when-inside-a-category

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

### Screencast


https://github.com/user-attachments/assets/e5a72a85-676e-4747-948a-6b1a19d2089f




## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2026-05-18 18:09:53 +05:30
7f0d5caca4 chore: Update translations (#14276)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-18 17:31:05 +05:30
1d2f3e86dd feat(companies): track company last activity (#14435)
Tracks company recency from linked contact activity so the Companies
list and detail page can show/sort by real customer engagement instead
of generic record updates.

## Closes

None.

## Why

Company recency should reflect activity from people associated with the
company. This keeps the signal tied to persisted contact activity,
without treating passive online presence or widget heartbeat pings as
company activity.

## What Changed

- Adds a company helper to record `last_activity_at` from linked contact
activity.
- Rolls up `Contact#last_activity_at` changes to the associated company.
- Initializes company activity when an already-active contact is
associated with a company, including the business-email auto-association
path.
- Throttles company activity rollups to once every 5 minutes per company
to avoid unnecessary writes during active conversations.
- Treats company activity as monotonic: unlinking, moving, or deleting
contacts does not move a company's activity timestamp backwards.
- Leaves historical backfill, online presence tracking, widget visit
tracking, and richer activity attribution out of scope.

## How to Test

1. Open an account with Companies enabled and visit the Companies list.
2. Trigger activity for a contact that belongs to a company, for example
by receiving or sending a message in that contact's conversation.
3. Confirm the linked company shows a recent activity timestamp in the
Companies list/detail page after the contact activity updates.
4. Associate an already-active contact with a company and confirm the
company receives that contact's existing activity timestamp.
5. Confirm repeated contact activity within a short window does not
continuously rewrite the company timestamp.

---------

Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-05-18 15:01:05 +05:30
3253e863ed fix: validate OpenAI hook credentials (#14068)
# Pull Request Template

## Description

- Validates openai key while configuring hooks
- added backfill logic

Fixes # (issue)

## Type of change

- [x] New feature (non-breaking change which adds functionality)


## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
locally

<img width="1710" height="1234" alt="CleanShot 2026-04-15 at 16 15
02@2x"
src="https://github.com/user-attachments/assets/3d319fe0-19f9-4fd0-9308-74987daac2e1"
/>

<img width="2884" height="1136" alt="CleanShot 2026-05-11 at 19 22
53@2x"
src="https://github.com/user-attachments/assets/5eae8650-985b-4c4a-af42-35f7175ff52d"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-05-18 14:08:57 +05:30
Aakash BakhleandGitHub 059d840272 feat: Refresh llm settings when superadmin configs change [AI-151] (#14388)
# Pull Request Template

## Description

fixes:
https://linear.app/chatwoot/issue/AI-151/captains-super-admin-config-dont-get-applied-into-rails-without

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)


## How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
specs and locally

To test locally: 
go to super admin -> settings -> captain -> Change endpoint to something
incorrect
go to local app -> captain -> playground -> try chatting (should fail
due to incorrect endpoint)

now in super admin captain settings, set the correct endpoint then chat
in playground. Now it should work.

Current develop code doesn't reflect the changes in installation config
for captain instantly, needs a server restart.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
2026-05-18 14:08:26 +05:30
b8deb89613 fix: make SAML callback session independent (#14467)
This PR makes SAML login independent of Rails session cookies

## Problem

The normal SAML login flow should be straightforward:

- User opens Chatwoot.
- Chatwoot creates `_chatwoot_session`.
- User starts SSO.
- Chatwoot redirects the browser to the SAML provider.
- The provider authenticates the user.
- The provider sends the browser back to Chatwoot's ACS URL.
- Chatwoot reads the SAML response, finds or creates the user, and logs
them in.

The fragile step is the ACS callback. Most SSO flows return to the app
through browser redirects where cookies usually pass through as
expected. **ADFS commonly returns the SAML response with a cross-site
POST**. With Chatwoot's session cookie using `SameSite=Lax`, browsers
may not send `_chatwoot_session` on that POST.

SAML validation itself does not need the old Rails session cookie. The
problem was our callback handoff after validation. DeviseTokenAuth
stores the verified OmniAuth payload in Rails session, then redirects to
a second callback route. If the browser does not preserve that session,
Chatwoot has already received a valid SAML response but can no longer
finish login.

## Solution

This PR removes the session-backed handoff for SAML only:

- The SAML callback completes login in the same request where OmniAuth
validates the SAML response.
- Chatwoot reads the verified auth payload directly from
`request.env['omniauth.auth']`.
- Account context and RelayState come from callback params or OmniAuth
env data, not Rails session.
- Other OmniAuth providers continue using the existing DeviseTokenAuth
flow.
- Mobile SAML still works when the IdP returns `RelayState=mobile`; the
callback redirects to the mobile deep link with the generated SSO token.

The previous SAML override used `303 See Other` to avoid replaying the
SAML POST into the second callback route. This change keeps that intent,
but removes the second callback route for SAML entirely.

## Screen recording

### SP Initiated

https://github.com/user-attachments/assets/b0735e93-3864-4cc3-b6fc-419fff4b549e

### IDP Initiated

https://github.com/user-attachments/assets/3ded0246-933c-4c85-9b7c-fa15fdc34883

## Testing

Manual validation:

- Complete a SAML login.
- In the browser network trace, find the IdP POST to
`/omniauth/saml/callback?account_id=<account-id>`.
- Confirm it redirects directly to `/app/login?...sso_auth_token=...`
for web login.
- For mobile, confirm `RelayState=mobile` redirects to the configured
mobile deep link.
- Confirm there is no intermediate `/auth/saml/callback` request.

Testing with mocksaml.com:

- Configure Chatwoot with a public `FRONTEND_URL`.
- Set the mocksaml ACS URL to:

```text
https://<chatwoot-host>/omniauth/saml/callback?account_id=<account-id>
```

- Set the mocksaml audience/SP entity ID to the value shown in Chatwoot
SAML settings, usually:

```text
https://<chatwoot-host>/saml/sp/<account-id>
```

- Use an email returned by mocksaml that exists in the SAML-enabled
account.
- Start login from Chatwoot's SSO login page.
- Confirm the callback redirects directly to the app login URL with an
SSO token.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-18 12:52:45 +05:30
Shivam MishraandGitHub ef27e571f7 feat: enable quoted reply for everyone (#14469)
Quoted email replies is now available to every account by default.
Previously this was gated behind the `quoted_email_reply` account-level
feature flag, so accounts needed it toggled on (via Super Admin) before
agents saw the toggle in the reply box.

## How to test

1. Open any conversation on an email inbox.
2. Confirm the **Quote previous email** toggle is visible in the reply
box (and is **not** visible on private notes or non-email channels).
3. Toggle it on, type a reply, and send — the outbound email should
include the quoted prior email below your message.
4. Toggle it off and send another reply — the quoted block should not
appear.
5. The toggle preference should persist per channel type (UI setting),
as before.
6. Verify the toggle works on a brand new account with no feature flags
flipped on (previously it would have been hidden).

## What changed

- Removed all `isFeatureEnabledonAccount(..., QUOTED_EMAIL_REPLY)` gates
from `ReplyBox.vue`, so the toggle and quoted-content behavior are
unconditional on email channels.
- Removed the `QUOTED_EMAIL_REPLY` constant from
`dashboard/featureFlags.js`.
- Marked the flag as `deprecated: true` in `config/features.yml` (kept
the entry in place to preserve FlagShihTzu bit positions on existing
accounts; `deprecated: true` hides it from the Super Admin UI).
- Dropped the now-unnecessary
`account.enable_features('quoted_email_reply')` setup from the message
builder spec.
2026-05-15 10:59:48 -07:00
1528fcde0c fix: missing widget es translations (#14375)
# Pull Request Template

## Description

There were English strings in the Spanish i18n file for the widget. This
PR translates them.

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-15 12:16:49 +05:30
5f6bd951b9 fix: portals#create returns 500 when custom_domain is omitted (#14400)
## Description

`POST /api/v1/accounts/:account_id/portals` returns a generic 500
(`{"status":500,"error":"Internal Server Error"}`) whenever the request
body omits `custom_domain`. Root cause: `parsed_custom_domain` calls
`URI.parse(@portal.custom_domain)` and `URI.parse(nil)` raises
`URI::InvalidURIError`. Existing callers either had to know to pass
`"custom_domain": ""` as a workaround or hit a 500 with no useful
diagnostic.

This PR guards `parsed_custom_domain` against blank values so the
existing fall-through (`else @portal.custom_domain`) applies —
equivalent to passing an empty string.

It also moves the `process_attached_logo` guard from the helper into the
`create` call site so `create` mirrors `update` (`process_attached_logo
if params[:blob_id].present?`) and avoids an unnecessary signed-blob
lookup on every create that doesn't include a logo.

Fixes #14397

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

Two new request specs in
`spec/controllers/api/v1/accounts/portals_controller_spec.rb` covering
the regression:

- `creates portal when custom_domain is omitted from request body` — the
previously-broken case, now returns 200.
- `creates portal when custom_domain is blank` — verifies the existing
workaround (`"custom_domain": ""`) still works after the change.

Manually verified against `chatwoot/chatwoot:latest` Docker image before
the fix (500) and against this branch (200) using the curl repro from
the issue.

```bash
curl -X POST "https://<host>/api/v1/accounts/<account_id>/portals" \
  -H "Content-Type: application/json" \
  -H "api_access_token: <token>" \
  -d '{"name":"Test Portal","slug":"test-portal","color":"#3b82f6"}'
```

Before: `{"status":500,"error":"Internal Server Error"}`
After: `200 OK` with the portal payload.

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation (no doc
change needed — controller behaviour, fully backward-compatible)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-15 11:11:18 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Pranav
cfc7699b7e chore(deps): bump net-imap from 0.4.20 to 0.4.24 (#14361)
Bumps [net-imap](https://github.com/ruby/net-imap) from 0.4.20 to
0.4.24.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ruby/net-imap/releases">net-imap's
releases</a>.</em></p>
<blockquote>
<h2>v0.4.24</h2>
<blockquote>
<p>[!IMPORTANT]
<em>The <code>0.4.x</code> release branch will only receive critical
security fixes, and will be unsupported when ruby 3.3 is EOL.
Please upgrade to a newer version.</em></p>
</blockquote>
<h2>What's Changed</h2>
<h3>🔒 Security</h3>
<p>This release contains fixes for <strong>multiple
vulnerabilities</strong> concerning <em><strong><code>STARTTLS</code>
stripping</strong></em>, argument validation, and denial of service
attacks.</p>
<blockquote>
<p>[!WARNING]
<a
href="https://redirect.github.com/ruby/net-imap/pull/666">ruby/net-imap#666</a>
fixes a <code>STARTTLS</code> stripping vulnerability
(GHSA-vcgp-9326-pqcp).
Without this fix, a man-in-the-middle attacker can cause
<code>Net::IMAP#starttls</code> to return &quot;successfully&quot;,
<strong><em>without starting TLS</em></strong>.</p>
</blockquote>
<blockquote>
<p>[!IMPORTANT]
Argument validation is significantly improved. Several injection
vulnerabilities have been fixed:
<a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a>
fixes CRLF/command/argument injection via Symbol arguments
(GHSA-75xq-5h9v-w6px).
<a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a>
fixes CRLF/command/argument injection via the <code>attr</code> argument
to <code>#store</code>/<code>#uid_store</code> (GHSA-hm49-wcqc-g2xg)
<a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a>
fixes CRLF/command/argument injection via the <code>storage_limit</code>
argument to <code>#setquota</code> (GHSA-hm49-wcqc-g2xg).
<a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a>
fixes CRLF/command injection via <code>RawData</code>
(GHSA-hm49-wcqc-g2xg):</p>
<ul>
<li><code>#search</code> and <code>#uid_search</code> send
<code>criteria</code> as raw data, when it is a String</li>
<li><code>#fetch</code> and <code>#uid_fetch</code> send
<code>attr</code> as raw data, when it is a String.
When <code>attr</code> is an Array, its String members are sent as raw
data.</li>
</ul>
</blockquote>
<blockquote>
<p>[!CAUTION]
<code>RawData</code> does not defend against <em>other</em> forms of
argument injection! It is an intentionally low-level API.</p>
</blockquote>
<blockquote>
<p>[!NOTE]
Two denial of service vulnerabilities have been addressed.
These are generally only relevant when connecting to an <em>untrusted
hostile server</em> (or without TLS).</p>
<p><a
href="https://redirect.github.com/ruby/net-imap/pull/651">ruby/net-imap#651</a>
fixes quadratic time complexity when reading large responses containing
many string literals (GHSA-q2mw-fvj9-vvcw).
<a
href="https://redirect.github.com/ruby/net-imap/pull/655">ruby/net-imap#655</a>
adds a configurable <code>max_iterations</code> count for
<code>SCRAM-*</code> authentication (GHSA-87pf-fpwv-p7m7).</p>
<p>The default <code>ScramAuthenticator#max_iterations</code> is
<code>2**31 - 1</code> (max 32-bit signed int), which was already
OpenSSL's maximum value. <em>It provides no protection</em> against
hostile servers unless it is explicitly set to a lower value by the
user.</p>
</blockquote>
<h3>Added</h3>
<ul>
<li>🔒 Add <code>ScramAuthenticator#max_iterations</code> (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/654">#654</a>) in
<a
href="https://redirect.github.com/ruby/net-imap/pull/655">ruby/net-imap#655</a>,
reported by <a
href="https://github.com/Masamuneee"><code>@​Masamuneee</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>🔒 Fix STARTTLS stripping vulnerability (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/664">#664</a>) in
<a
href="https://redirect.github.com/ruby/net-imap/pull/666">ruby/net-imap#666</a>,
reported by <a
href="https://github.com/Masamuneee"><code>@​Masamuneee</code></a></li>
<li>🔒 Fix CRLF injection vulnerabilities (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/657">#657</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/658">#658</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/659">#659</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/660">#660</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/636">#636</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/661">#661</a>) in
<a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a>,
reported by <a
href="https://github.com/manunio"><code>@​manunio</code></a></li>
<li> Much faster ResponseReader performance (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/642">#642</a>) in
<a
href="https://redirect.github.com/ruby/net-imap/pull/651">ruby/net-imap#651</a>,
reported by <a
href="https://github.com/Masamuneee"><code>@​Masamuneee</code></a></li>
<li>🐛 Wait to continue RawData literals (backports <a
href="https://redirect.github.com/ruby/net-imap/issues/660">#660</a>) by
<a href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/663">ruby/net-imap#663</a></li>
</ul>
<h3>Other Changes</h3>
<ul>
<li>♻️ Improve internal literal sending (partially backports <a
href="https://redirect.github.com/ruby/net-imap/issues/358">#358</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/616">#616</a>, <a
href="https://redirect.github.com/ruby/net-imap/issues/649">#649</a>) by
<a href="https://github.com/nevans"><code>@​nevans</code></a> in <a
href="https://redirect.github.com/ruby/net-imap/pull/653">ruby/net-imap#653</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ruby/net-imap/compare/v0.4.23...v0.4.24">https://github.com/ruby/net-imap/compare/v0.4.23...v0.4.24</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ruby/net-imap/commit/24a4e770b43230286a05aa2a9746cdbb3eb8485e"><code>24a4e77</code></a>
🔀 Merge pull request <a
href="https://redirect.github.com/ruby/net-imap/issues/666">#666</a>
from ruby/backport/v0.4/STARTTLS-stripping</li>
<li><a
href="https://github.com/ruby/net-imap/commit/63f53ffdefa3be6c779090bdbe6b7257b632b36c"><code>63f53ff</code></a>
🔖 Bump version to 0.4.24</li>
<li><a
href="https://github.com/ruby/net-imap/commit/038ae35d5ecbb2b85f77f4fa35e46604154dc8c4"><code>038ae35</code></a>
🍒 pick 24d5c773d: 🔒🥅 Handle tagged &quot;OK&quot; to incomplete command
[backport <a
href="https://redirect.github.com/ruby/net-imap/issues/664">#664</a>]</li>
<li><a
href="https://github.com/ruby/net-imap/commit/705aa59faa28083f496dce50c0ee1eccca287506"><code>705aa59</code></a>
🍒 pick 62eea6ffe: 🔒🥅 Ensure STARTTLS tagged response was handled
[backport <a
href="https://redirect.github.com/ruby/net-imap/issues/664">#664</a>]</li>
<li><a
href="https://github.com/ruby/net-imap/commit/c9a6f28f8794cb991613d378b3920e5719062e29"><code>c9a6f28</code></a>
🍒 pick 46636cae8: 🔒 Add failing test for STARTTLS stripping [backport
<a
href="https://redirect.github.com/ruby/net-imap/issues/664">#664</a>]</li>
<li><a
href="https://github.com/ruby/net-imap/commit/aec06996eb87a7e1bbcef1f9f8926e8add2b8c71"><code>aec0699</code></a>
🔀 Merge pull request <a
href="https://redirect.github.com/ruby/net-imap/issues/663">#663</a>
from ruby/backport/v0.4/raw_data-warnings</li>
<li><a
href="https://github.com/ruby/net-imap/commit/fd245ddd1e220143c9b7c7861700590fbe9d35ef"><code>fd245dd</code></a>
🍒 pick be32e712e: 📚 Improve documentation of RawData arguments
[backports <a
href="https://redirect.github.com/ruby/net-imap/issues/661">#661</a>]</li>
<li><a
href="https://github.com/ruby/net-imap/commit/6dd110bfda6b47da9c294791ef16911bd1f49033"><code>6dd110b</code></a>
🍒 pick 47c72186d: 🐛 Validate RawData and wait to continue literals
[backports...</li>
<li><a
href="https://github.com/ruby/net-imap/commit/4e93149e65f6f1a6225ef770d0cd129acb5d63a9"><code>4e93149</code></a>
🔀 Merge branch 'backport/v0.4/QUOTA-argument-validation' into
backport/v0.4/s...</li>
<li><a
href="https://github.com/ruby/net-imap/commit/d2b23602e8617311900a9b3acf0c8d708b70bad4"><code>d2b2360</code></a>
🍒 pick 0ec4fd351: 🥅 Validate <code>#setquota</code> storage limit
argument [backports <a
href="https://redirect.github.com/ruby/net-imap/issues/659">#659</a>]</li>
<li>Additional commits viewable in <a
href="https://github.com/ruby/net-imap/compare/v0.4.20...v0.4.24">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=net-imap&package-manager=bundler&previous-version=0.4.20&new-version=0.4.24)](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)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

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 <dependency name> 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).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
2026-05-14 19:51:01 -07:00
dc332dd93e feat: add attachments endpoint for contact media view (#14391)
# Pull Request Template

## Description

This PR adds an endpoint to fetch all attachments shared with or by a
contact across all of their conversations.

Results are scoped based on the access:
* Admins can access all attachments
* Agents can access attachments only from inboxes they belong to
* Custom role agents are further filtered based on their conversation
permissions

Each attachment payload includes `conversation_id`, allowing the UI to
deep-link back to the source conversation.

Added `GET
/api/v1/accounts/:account_id/contacts/:contact_id/attachments` under the
existing contacts scope.

Fixes
https://linear.app/chatwoot/issue/CW-7021/add-media-view-to-the-contact-details-page

## Type of change

- [x] New feature (non-breaking change which adds functionality)


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-14 21:34:39 +05:30