Files
agents_and_robots/tools/matrix/matrix.go
T
egutierrez 4e7aa95adb feat: hardening de tools — deny-by-default, SSRF, path traversal, allowlists
Cambios de seguridad en las 4 herramientas de agentes:

- tools/file: deny-by-default (AllowedPaths vacío = todo denegado),
  resolución de symlinks con EvalSymlinks, protección contra path
  traversal (../) y confusión de prefijos (/opt vs /opt1234)
- tools/ssh: nuevo AllowedCommands allowlist (complementa ForbiddenCommands),
  validación de sintaxis shell (bloquea pipes, subshells, redirects, chains)
- tools/http: protección SSRF bloqueando IPs privadas, loopback, link-local,
  metadata (169.254.169.254). Validación de dominio case-insensitive.
- tools/matrix: nuevo parámetro AllowedRooms para restringir rooms destino
- internal/config/schema: AllowedCommands en SSHToolCfg, MatrixToolCfg nueva
- agents/runtime: pasa MatrixToolCfg al constructor de matrix_send

Parte de issue 0019 (prompt injection hardening). Feature flag OFF.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:17:00 +00:00

63 lines
1.9 KiB
Go

package matrix
import (
"context"
"fmt"
"github.com/enmanuel/agents/internal/config"
"github.com/enmanuel/agents/tools"
)
// MatrixSender is the interface for sending Matrix messages.
// Satisfied by shell/matrix.Client.
type MatrixSender interface {
SendText(ctx context.Context, roomID, text string) error
SendMarkdown(ctx context.Context, roomID, markdown string) error
}
// NewMatrixSend creates a matrix_send tool that sends a message to a Matrix room.
// If AllowedRooms is configured, only those room IDs can be targeted.
func NewMatrixSend(sender MatrixSender, cfg config.MatrixToolCfg) tools.Tool {
return tools.Tool{
Def: tools.Def{
Name: "matrix_send",
Description: "Send a text message to a Matrix room.",
Parameters: []tools.Param{
{Name: "room_id", Type: "string", Description: "The Matrix room ID to send to", Required: true},
{Name: "message", Type: "string", Description: "The text message to send", Required: true},
},
},
Exec: func(ctx context.Context, args map[string]any) tools.Result {
roomID := tools.GetString(args, "room_id")
message := tools.GetString(args, "message")
if roomID == "" || message == "" {
return tools.Result{Err: fmt.Errorf("matrix_send: room_id and message are required")}
}
if err := validateRoom(roomID, cfg.AllowedRooms); err != nil {
return tools.Result{Err: err}
}
if err := sender.SendMarkdown(ctx, roomID, message); err != nil {
return tools.Result{Err: fmt.Errorf("matrix_send: %w", err)}
}
return tools.Result{Output: fmt.Sprintf("message sent to %s", roomID)}
},
}
}
// validateRoom checks that roomID is in the allowed list.
// If allowedRooms is empty, all rooms are allowed.
func validateRoom(roomID string, allowedRooms []string) error {
if len(allowedRooms) == 0 {
return nil
}
for _, r := range allowedRooms {
if roomID == r {
return nil
}
}
return fmt.Errorf("matrix_send: room %q not in allowed rooms list", roomID)
}