1459 Commits

Author SHA1 Message Date
LU 2a4fbecbda Add CJK (Chinese, Japanese, Korean) search support
The search functionality was silently dropping CJK characters because:

1. Query sanitization used `\w` which only matches ASCII word characters
2. Stemmer split by whitespace, which doesn't work for CJK languages
3. Highlighter used `\b` word boundaries that don't apply to CJK

This commit fixes all three issues:

- Query: Use `\p{L}\p{N}` (Unicode letters/numbers) instead of `\w`
- Stemmer: Preserve CJK characters as-is without stemming, since CJK
  languages don't have stemming rules like English
- Highlighter: Skip word boundary matching for CJK terms

Also extracts `CJK_PATTERN` to `Search` module to avoid duplication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 17:22:42 +09:00
Rosa Gutierrez 5f390f72df Support local installations where the app is loaded over HTTP
In this case requests won't be performed from a secure context [1] and
the browser won't send the Sec-Fetch-Site header. This means non-GET
requests will be rejected because CSRF protection will fail.

With this change, we allow these requests with missing Sec-Fetch-Site
headers if:
- They happen over HTTP
- The app is not configured to force SSL

The Origin check happens in any case.

[1] https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Secure_Contexts#potentially_trustworthy_origins
2026-01-05 18:50:14 +01:00
Jorge Manrubia 85bfa7aa48 Rename method 2026-01-05 16:01:25 +01:00
Jorge Manrubia 5f38e8957e Reapply "Make sure new card drafts are refreshed when reused"
This reverts commit 045ddf78f3.
2026-01-05 15:58:29 +01:00
Jorge Manrubia 045ddf78f3 Revert "Make sure new card drafts are refreshed when reused"
This reverts commit 3008011175.
2026-01-05 15:58:16 +01:00
Jorge Manrubia 3008011175 Make sure new card drafts are refreshed when reused
To deal with old timestamps messing with card ordering and so.

https://app.fizzy.do/5986089/cards/3495
2026-01-05 15:57:48 +01:00
Jeremy Daer 0eefd67b68 Block IPv4-compatible IPv6 addresses in SSRF protection (#2273)
The SSRF filter checked ipv4_mapped? but not ipv4_compat?, allowing
addresses like ::169.254.169.254 to bypass the link-local check and
reach cloud metadata endpoints.

Changes:
- Add ipv4_compat? check to block deprecated IPv4-compatible format
- Rename private_address? to blocked_address? (more accurate - method
  blocks more than just RFC 1918 private ranges)
- Add IPv6 test coverage for both mapped and compat formats

Both ipv4_mapped and ipv4_compat formats are blocked entirely as
defense-in-depth: DNS never returns these formats, so they only
appear in attack scenarios.

HackerOne: #3481701
2025-12-29 21:45:06 -08:00
Mike Dalessio f952b953e6 Add test coverage for autolinking multiple URLs 2025-12-29 12:21:02 -05:00
Mike Dalessio 97d8d6a395 Add "noopener" to autolinks' rel attribute 2025-12-29 12:21:02 -05:00
Mike Dalessio 4579f7cd61 Avoid string manipulation when autolinking.
Instead, let's use a Loofah scrubber which will create DOM nodes
directly. This should be faster and is a tiny bit simpler, as well as
removing a potential HTML injection vector.

Also, add "noreferrer" to all `mailto:` links (already present on URLs).
2025-12-29 12:21:00 -05:00
Rosa Gutierrez f66a2dc1ba Consider user avatars always public
Avatars are purposely accessible without authentication
(5e3b5b6d7c) because they can be in public
collections. Trying to restrict this by checking whether they're in fact
present in some public collection is rather expensive, so let's keep
them public.
2025-12-26 19:33:26 +01:00
Rosa Gutierrez 196d685f8d Implement authorization for Active Storage endpoints
Consider blobs attached to any public records accessible to anyone with
the URL.
2025-12-25 21:22:36 +01:00
Stanko Krtalić 24c20ad55f Merge pull request #2244 from basecamp/expose-card-ids-on-comments
Expose the card ID and URL on comments
2025-12-24 08:50:47 +01:00
Stanko K.R. 822c3bb2ad Expose the card ID and URL on comments 2025-12-24 08:45:31 +01:00
Rosa Gutierrez 3fcf5a9d08 Require authorization for direct uploads
Respond with 403 to JSON requests that are unauthorized, instead of
redirecting.
2025-12-23 11:08:51 +01:00
Dylan a005391724 Fix: allow destroying "not now" state when closing cards
- Update `Card::Closeable` to destroy associated "not now" state during close.
- Add new tests to verify closure behavior for different column types, including "not now".
2025-12-23 13:43:03 +08:00
Jason Zimdars 563c530e3e Merge pull request #2213 from basecamp/welcome-letter
Stub welcome letter for newly created accounts
2025-12-22 10:17:41 -06:00
Stanko K.R. 0df667f4fb Fix HTML injection in webhooks through card titles 2025-12-22 12:20:32 +01:00
scart88 ba887225a5 Redirect back to account settings page when the user role is changed or when the user is removed from an account. 2025-12-22 10:47:23 +02:00
Jason Zimdars bc4745c6ce Add a test 2025-12-20 18:12:42 -06:00
Julián Pinzón Eslava 12928f530b Refactor search test to use instance variables
The included SearchTestHelper disables transactional tests and has a specific setup that does not use fixtures.

This test however, ignores the setup and uses fixtures directly by creating comments and publishing cards making test state unpredictable. 

By using the records set by the helper instead of fixtures, the state of the system is not polluted.
2025-12-21 00:23:21 +11:00
Jeremy Daer bd6c1cf34f Storage: harden reconcile for concurrent writes and fix board transfer (#2096)
* Storage: harden reconcile for concurrent writes and fix board transfer

Reconcile now uses two-cursor approach: captures cursor before and after
the storage scan, aborting if they differ (entries added during scan).
Job retries 3x with 1-minute waits and limits concurrency to 1 per owner.

Board transfer now correctly moves storage for card description embeds
and comment embeds, not just direct attachments. Uses batched queries
to handle cards with thousands of comments efficiently.

Also fixes N+1 queries in attachment grouping via lookup maps.

* Storage: fix per-attachment reconcile and enforce no blob reuse

The storage ledger tracks per-attachment (not per-blob) as a business
abstraction for quotas. This fixes reconcile to match that model and
adds enforcement to prevent blob reuse in tracked contexts.

* Reconcile now uses joins(:blob).sum() for per-attachment counting
* New validation prevents reusing blobs across tracked attachments
* Race-safe storage_total creation with create_or_find_by
* Job efficiency: skip find_by when object already available
* Backfill script checks per-attachment, not just per-blob
2025-12-19 13:07:32 -08:00
Stanko K.R. e0270c6c49 Clean up interfaces
I talked to the mobile team, and to keep things simple we agreed to send
the token via a cookie.
2025-12-19 19:21:57 +01:00
Stanko K.R. 23fc7b594f Split tests by controller or responsibility 2025-12-19 19:21:57 +01:00
Stanko K.R. 1a1f4a077b Simplify auth logic 2025-12-19 19:21:57 +01:00
Fernando Olivares cddddcf83a Fix due to unit test when creating with invalid emails 2025-12-19 19:21:57 +01:00
Fernando Olivares fb487f598c Restore sessions_controller test on creating invalid email address 2025-12-19 19:21:57 +01:00
Fernando Olivares be447f90ba Move magic link api tests to their own files 2025-12-19 19:21:57 +01:00
Fernando Olivares 6be20e215b Rename test to clarify what they're about 2025-12-19 19:21:57 +01:00
Fernando Olivares 6e8d6a3df0 Update to always return a pending auth token for JSON responses. 2025-12-19 19:21:57 +01:00
Fernando Olivares fbf829b346 Update API test for cross code 2025-12-19 19:21:57 +01:00
Fernando Olivares 9009e0fb49 Change test expectation on single tenant mode account creation 2025-12-19 19:21:57 +01:00
Fernando Olivares a2333623b6 Add unit tests for the new endpoints 2025-12-19 19:21:57 +01:00
Stanko K.R. c0a0786539 Return the session cookie
We had a call about this. In short, we could reuse access tokens but then the user would see access tokens for every mobile device they have without any indication as to what is going on. So, since this really is just logging in instead of an integration which seems to be the primary purpose of access tokens, we can just use our regular session cookie for authentication.
2025-12-19 19:21:57 +01:00
Stanko K.R. 58a92f0bb7 Add tests for sign in via API 2025-12-19 19:21:57 +01:00
Mike Dalessio fa21bc9b61 Merge pull request #2169 from basecamp/flavorjones/better-hex-uuid
Extract `hex_to_base36` and `base36_to_hex` methods in `Uuid` type
2025-12-19 10:06:36 -05:00
Jorge Manrubia eb1d112013 Merge pull request #2203 from basecamp/remove-unused-param
Remove unused `tag_ids` parameter from `CardsController#update`
2025-12-19 12:16:32 +01:00
Jorge Manrubia 33fc239571 Merge pull request #2140 from dilberryhoundog/fix-stale-account-cache
Fix: resolve stale account names in jump menu and page titles
2025-12-19 11:26:23 +01:00
Rosa Gutierrez 1e640643e9 Remove unused tag_ids parameter from CardsController#update
This is no longer used in the normal flow of the app. The tags are added
via `Cards::TaggingsController`.
2025-12-19 11:16:20 +01:00
Rosa Gutierrez cb01966439 Remove unused Card.published_or_drafted_by scope
As the tests for it could lead to confusion where it seems drafted cards
are not accessible to someone with access to the board and with the
direct drafted card URL.
2025-12-19 09:46:21 +01:00
Stanko Krtalić 42185a8d6d Merge pull request #2153 from basecamp/limit-the-number-of-asignees-on-a-card
Limit the number of asignees on a card
2025-12-18 12:08:45 +01:00
Mike Dalessio 18b3319a94 Extract hex_to_base36 and base36_to_hex methods in Uuid type
replacing normalize_base36. This should make it easier to convert
UUIDs between hex and base36 formats, useful when debugging production
logs that contain hex UUIDs.

For example, if in a log you see:

    User Load (0.3ms) SELECT `users`.* FROM `users` WHERE
    `users`.`account_id` = x'019afab815897a4f920f3a24fab75400' AND
    `users`.`id` = x'019afab815a2790688c58c7f8326e700' LIMIT 1

and you want to find that account or that user in the console, you can
now do:

    id = ActiveRecord::Type::Uuid.hex_to_base36("019afab815897a4f920f3a24fab75400")
    => "03f6bilrvt3oghzhurll4pp8g"
2025-12-16 14:16:55 -05:00
Mike Dalessio 2b388dd241 Merge pull request #2164 from basecamp/flavorjones/user-validate-name
Validate User name presence and handle blank names gracefully
2025-12-16 10:49:13 -05:00
Jorge Manrubia ee80b87c8c Add billing system with Stripe
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Jason Zimdars <jz@37signals.com>
2025-12-16 16:44:20 +01:00
Mike Dalessio b75d2ae6d8 Validate User name presence and handle blank names gracefully
Addresses an issue where User#familiar_name assumed `name` was always
present, potentially raising an exception during view rendering. Now
User validates name presence, and User#familiar_name handles blank
strings without error, in case any existing invalid records exist.
2025-12-16 10:44:10 -05:00
Stanko K.R. 15daa2bf05 Limit asignees to 10 per card 2025-12-15 16:40:52 +01:00
Kevin McConnell 741eff7bdc Revert "Merge pull request #1865 from basecamp/public-avatar-caching"
This reverts commit c628f14c01, reversing
changes made to 4bafc73236.
2025-12-15 13:07:13 +00:00
Stanko Krtalić 2058743be9 Merge pull request #2146 from basecamp/show-only-published-cards-on-public-boards
Show only public cards on public boards
2025-12-15 13:26:12 +01:00
Kevin McConnell c628f14c01 Merge pull request #1865 from basecamp/public-avatar-caching
Serve own avatar from its own endpoint
2025-12-15 12:20:28 +00:00
Stanko K.R. 87081aa617 Show only public cards on public boards 2025-12-15 13:15:36 +01:00