ChatifyPHP

Security

Authorization, route scoping, blocking, rate limiting, file validation, and SSRF protection

Chatify implements multiple layers of security to protect user data and prevent abuse.

Authorization model

Chatify uses Laravel policies to control access to conversations and messages.

ConversationPolicy

Every conversation action is gated by a policy check:

ActionWho can do it
ViewParticipants only
CreateAny authenticated user
UpdateParticipants only
Update group infoParticipants with edit_info permission
Add participantsParticipants with add_members permission
Remove participantSelf (leave), or participants with remove_members permission
Manage rolesParticipants with manage_admins permission
Transfer ownershipOwner only
DeleteOwner (groups), participant (direct). Cannot delete saved messages.
HideParticipants only. Cannot hide saved messages.
ClearParticipants only. Saved messages only.

MessagePolicy

ActionWho can do it
ViewParticipants of the message's conversation
CreateParticipants of the conversation
Update (edit)Message sender only
DeleteMessage sender only
HideAny participant

Participant-scoped route bindings

Chatify registers custom route model bindings that scope conversation and message lookups to the authenticated user's conversations. This means:

  • A user cannot access a conversation they are not a participant of, even if they guess the UUID
  • A user cannot access a message from a conversation they are not a participant of
  • These checks happen at the routing layer, before the controller or policy is reached
// The conversation binding filters by participant
Route::bind('conversation', function (string $value) {
    return Conversation::query()
        ->forUser((int) auth()->user()->getKey())
        ->where('id', $value)
        ->firstOrFail();
});

Block system

Users can block each other to prevent messaging:

  • Blocking is mutual -- if user A blocks user B, neither can message the other
  • The default RecipientResolver checks the block list before allowing a direct conversation
  • When a block is created or removed, a UserBlockChanged event is broadcast to both parties
  • Block state is reflected in the UI immediately

API

POST /api/chatify/v1/blocks/{user}     # Block a user
DELETE /api/chatify/v1/blocks/{user}    # Unblock a user
GET /api/chatify/v1/blocks             # List blocked users

Rate limiting

Two rate limiter groups protect against abuse:

LimiterDefaultScopeApplied to
chatify-messages60/minutePer user (or IP if unauthenticated)Message send, typing, search, forward
chatify-uploads10/minutePer user (or IP if unauthenticated)Avatar, attachment, background, settings

When a rate limit is exceeded, the API returns 429 Too Many Requests.

File upload validation

Attachments are validated server-side:

  • Extension whitelist -- Only allowed extensions pass (configured in chatify.attachments.allowed_images and chatify.attachments.allowed_files)
  • Size limit -- Files exceeding chatify.attachments.max_upload_size (default 150 MB) are rejected
  • PHP upload errors -- The server checks for PHP upload errors (UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, etc.) and returns appropriate error messages
  • Storage disk -- Files are stored on the configured Laravel filesystem disk, not in publicly-guessable paths

The link preview endpoint (GET /api/chatify/v1/link-preview) fetches metadata from external URLs. To prevent Server-Side Request Forgery (SSRF):

  • Requests to localhost, 127.0.0.1, ::1, and private IP ranges (10.x.x.x, 172.16.x.x, 192.168.x.x) are blocked
  • Only HTTP and HTTPS schemes are allowed
  • The response is limited to Open Graph metadata (title, description, image URL)

Input sanitization

Message bodies are sanitized to prevent XSS and injection attacks. The frontend renders message content safely using Vue's text rendering (not v-html).

Broadcast channel authorization

Broadcast channels are protected by authorization callbacks:

  • Conversation channel (chatify.conversation.{id}): Only participants can subscribe
  • User channel (chatify.user.{userId}): Only the user themselves can subscribe
Broadcast::channel('chatify.conversation.{conversationId}', function ($user, $conversationId) {
    return ConversationParticipant::query()
        ->where('conversation_id', $conversationId)
        ->where('user_id', $user->getKey())
        ->exists();
});

Broadcast::channel('chatify.user.{userId}', function ($user, $userId) {
    return (int) $user->getKey() === (int) $userId;
});

Reporting vulnerabilities

If you discover a security vulnerability, please report it responsibly via the GitHub repository's security policy. Do not open a public issue for security vulnerabilities.

On this page