and add test coverage for (un)supported file types.
The avatar and logo variants move into the models and return nil for content
types that are no longer variable, so the controllers fall back to the initials
avatar and stock logo icon instead of raising `ActiveStorage::InvariableError`.
The guard uses IPAddr but relied on something else loading it first;
require it explicitly. And the rebinding tests lost their port assertion
when the matchers were loosened for newer Net::HTTP keyword args, so
check the port alongside the IP again.
Matches the fizzy guard: the local-use NAT64 prefix (64:ff9b:1::/48,
RFC 8215) embeds an IPv4 target in its low 32 bits just like the
well-known prefix, so run it through the same embedded-IPv4 recheck.
Local-use NAT64 to a public address now resolves (keeping unfurls
working for self-hosters on such networks) while local-use NAT64 to an
internal address stays blocked.
The local-use NAT64 prefix (64:ff9b:1::/48, RFC 8215) embeds an IPv4
target like the well-known prefix does, but at a deployment-chosen
position we can't extract, so block the whole range outright.
Also block the IPv6 benchmarking range (2001:2::/48, RFC 5180) to match
the IPv4 benchmarking block on 198.18.0.0/15.
Net::HTTP now passes an open_timeout: option to TCPSocket.open, so the mock
that matched exact positional arguments no longer matches. Match on the
host instead.
The guard blocked the usual private, loopback, and link-local ranges (and
the IPv4-mapped/-compatible IPv6 forms), but let through NAT64, 6to4, and
Teredo addresses, which can point at an internal IPv4, and CGNAT.
Now it pulls the IPv4 out of a NAT64 address and checks that (so NAT64 to a
public site still works), blocks 6to4 and Teredo outright, and adds the
missing IPv4 and IPv6 ranges.
Adds a new endpoint that allows bots to add emoji reactions (boosts) to messages:
POST /rooms/:room_id/:bot_key/messages/:message_id/boosts
This enables bots to acknowledge messages with reactions like 👀 (eyes) when
mentioned, providing immediate feedback to users before generating a full response.
The endpoint:
- Validates the bot is a member of the room
- Validates the message exists in the room
- Broadcasts the boost to connected clients via Turbo Streams
- Returns 201 Created on success, 404 if room/message not found
Co-authored-by: openhands <openhands@all-hands.dev>
Fixed test bug identified by Copilot code review: pagination tests were
using Message.first and Message.last which could return messages from
different rooms than the test's @room (watercooler).
Now explicitly using messages(:fourth) and messages(:thirteenth) which
are fixtures belonging to the watercooler room where bender bot is a member.
Co-authored-by: openhands <openhands@all-hands.dev>
Both create and index now return HTTP 404 Not Found when a bot tries to
access a room it's not a member of. This is consistent with REST API
security best practices (not revealing resource existence) and ensures
read and write permissions are handled identically.
Changed create action to no longer call super (which rendered HTML) and
instead directly handle the request with proper JSON API error responses.
Added test to verify create returns 404 for non-member rooms.
Co-authored-by: openhands <openhands@all-hands.dev>
Added explicit RecordNotFound handling to return 404 when a bot tries to
read messages from a room it's not a member of. This matches the security
model used by the create action.
Added tests to verify:
- Bot gets 404 when trying to read from room it's not a member of
- Bot can successfully read from room it IS a member of
Co-authored-by: openhands <openhands@all-hands.dev>
Adds a new GET endpoint at /rooms/:room_id/:bot_key/messages that allows
bots to read messages from rooms they are members of.
The endpoint returns JSON with:
- Room info (id, name)
- Messages array with body (plain/html), created_at, and creator info
- Pagination info (oldest_id, newest_id, has_more)
Supports pagination via ?before=:id and ?after=:id query parameters,
consistent with the existing pagination in the messages controller.
This enables AI bots and other automated agents to understand conversation
context when responding to messages, rather than only receiving the single
message that triggered the webhook.
Co-authored-by: openhands <openhands@all-hands.dev>
Adds ipv4_mapped? and ipv4_compat? checks to PrivateNetworkGuard.private_ip?
to block SSRF bypass attempts using IPv6 address formats like:
- ::ffff:169.254.169.254 (IPv4-mapped)
- ::169.254.169.254 (IPv4-compatible)
These formats could previously bypass the link_local? check since Ruby
treats them as IPv6 addresses, not IPv4.
Ref: HackerOne #3481701
This adds the ability to ban a user by their IP address.
When an admin is viewing a user profile, a new "Ban user" button is
present. Clicking on that will:
- Create a ban on the IP addresses that were tracked for that user's
sessions
- Remove all the messages authored by that user
- Log the user out immediately
In addition, that user will no longer be shown in most user lists in the
app. They are still shown to admins, in account settings. Viewing their
profile from there will now show a "Remove ban" button which can be used
to restore their access (it doesn't restore their messages though --
those are already gone -- it just removes the blocks so they can log in
again).
If bot_key has no right-hand side (ex: 1-), bot_token will be nil, and the query will match a User record if bot_id matches a valid ID.
Fix it relying on `active_bots` instead.
* Bump Ruby to 3.4.5
* Update dependencies
* Adjust for Rails 8 and Ruby 3.5 API changes
* Mark params strings as mutable in prepapration for frozen strings in Ruby 3.5
* Update test for HTML5 sanitizer
With Rails 7.1 the HTML5 sanitizer became the default, this breakts this test because the old sanitizer used to delete unpermitted nodes, while the new one returns their content
The final string is safe, but different then it used to be in Rails 7.0
* Remove direct Turbo tesh helpers require & parallelize tests
* Fix Zeitwerk issues with rails extensions
* Update Resque setup for Redis 5+
* Remove unused views
* Remove GID v1 handler