security: add maximum session limit to prevent memory exhaustion
Add configurable MaxSessions limit (default: 10000) to SessionStore. When the limit is reached, new session creation returns ErrTooManySessions and HTTP transport responds with 503 Service Unavailable. This prevents attackers from exhausting server memory by creating unlimited sessions through repeated initialize requests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package mcp
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -80,34 +81,57 @@ func (s *Session) Close() {
|
||||
|
||||
// SessionStore manages active sessions with TTL-based cleanup.
|
||||
type SessionStore struct {
|
||||
sessions map[string]*Session
|
||||
ttl time.Duration
|
||||
mu sync.RWMutex
|
||||
stopClean chan struct{}
|
||||
cleanDone chan struct{}
|
||||
sessions map[string]*Session
|
||||
ttl time.Duration
|
||||
maxSessions int
|
||||
mu sync.RWMutex
|
||||
stopClean chan struct{}
|
||||
cleanDone chan struct{}
|
||||
}
|
||||
|
||||
// ErrTooManySessions is returned when the session limit is reached.
|
||||
var ErrTooManySessions = fmt.Errorf("too many active sessions")
|
||||
|
||||
// DefaultMaxSessions is the default maximum number of concurrent sessions.
|
||||
const DefaultMaxSessions = 10000
|
||||
|
||||
// NewSessionStore creates a new session store with the given TTL.
|
||||
func NewSessionStore(ttl time.Duration) *SessionStore {
|
||||
return NewSessionStoreWithLimit(ttl, DefaultMaxSessions)
|
||||
}
|
||||
|
||||
// NewSessionStoreWithLimit creates a new session store with TTL and max session limit.
|
||||
func NewSessionStoreWithLimit(ttl time.Duration, maxSessions int) *SessionStore {
|
||||
if maxSessions <= 0 {
|
||||
maxSessions = DefaultMaxSessions
|
||||
}
|
||||
s := &SessionStore{
|
||||
sessions: make(map[string]*Session),
|
||||
ttl: ttl,
|
||||
stopClean: make(chan struct{}),
|
||||
cleanDone: make(chan struct{}),
|
||||
sessions: make(map[string]*Session),
|
||||
ttl: ttl,
|
||||
maxSessions: maxSessions,
|
||||
stopClean: make(chan struct{}),
|
||||
cleanDone: make(chan struct{}),
|
||||
}
|
||||
go s.cleanupLoop()
|
||||
return s
|
||||
}
|
||||
|
||||
// Create creates a new session and adds it to the store.
|
||||
// Returns ErrTooManySessions if the maximum session limit is reached.
|
||||
func (s *SessionStore) Create() (*Session, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Check session limit
|
||||
if len(s.sessions) >= s.maxSessions {
|
||||
return nil, ErrTooManySessions
|
||||
}
|
||||
|
||||
session, err := NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.sessions[session.ID] = session
|
||||
return session, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user