1
+ /**
2
+ * Security Manager for MCP Chess Server
3
+ *
4
+ * Provides authentication, authorization, and security validation
5
+ * for the agentic chess API.
6
+ */
7
+
8
+ import jwt from 'jsonwebtoken' ;
9
+ import bcrypt from 'bcryptjs' ;
10
+ import { z } from 'zod' ;
11
+
12
+ const RequestSchema = z . object ( {
13
+ params : z . object ( {
14
+ name : z . string ( ) ,
15
+ arguments : z . any ( ) ,
16
+ } ) ,
17
+ meta : z . object ( {
18
+ clientId : z . string ( ) . optional ( ) ,
19
+ apiKey : z . string ( ) . optional ( ) ,
20
+ userAgent : z . string ( ) . optional ( ) ,
21
+ } ) . optional ( ) ,
22
+ } ) ;
23
+
24
+ export class SecurityManager {
25
+ private readonly jwtSecret : string ;
26
+ private readonly apiKeys : Set < string > ;
27
+ private readonly rateLimits : Map < string , number > ;
28
+
29
+ constructor ( ) {
30
+ this . jwtSecret = process . env . JWT_SECRET || 'chess-engine-secret-key-change-in-production' ;
31
+ this . apiKeys = new Set ( [
32
+ 'chess-api-key-demo' , // Demo API key
33
+ 'satware-ai-internal' , // Internal satware AI key
34
+ ] ) ;
35
+ this . rateLimits = new Map ( ) ;
36
+ }
37
+
38
+ validateRequest ( request : any ) : void {
39
+ try {
40
+ // Validate request structure
41
+ RequestSchema . parse ( request ) ;
42
+
43
+ // Check for suspicious patterns
44
+ this . checkSuspiciousPatterns ( request ) ;
45
+
46
+ // Validate tool name
47
+ this . validateToolName ( request . params . name ) ;
48
+
49
+ // Validate arguments
50
+ this . validateArguments ( request . params . arguments ) ;
51
+
52
+ } catch ( error ) {
53
+ throw new Error ( `Security validation failed: ${ error . message } ` ) ;
54
+ }
55
+ }
56
+
57
+ authenticateApiKey ( apiKey : string ) : boolean {
58
+ return this . apiKeys . has ( apiKey ) ;
59
+ }
60
+
61
+ generateJWT ( payload : any ) : string {
62
+ return jwt . sign ( payload , this . jwtSecret , { expiresIn : '24h' } ) ;
63
+ }
64
+
65
+ verifyJWT ( token : string ) : any {
66
+ try {
67
+ return jwt . verify ( token , this . jwtSecret ) ;
68
+ } catch ( error ) {
69
+ throw new Error ( 'Invalid JWT token' ) ;
70
+ }
71
+ }
72
+
73
+ hashPassword ( password : string ) : string {
74
+ return bcrypt . hashSync ( password , 10 ) ;
75
+ }
76
+
77
+ verifyPassword ( password : string , hash : string ) : boolean {
78
+ return bcrypt . compareSync ( password , hash ) ;
79
+ }
80
+
81
+ private checkSuspiciousPatterns ( request : any ) : void {
82
+ const suspicious = [
83
+ 'eval(' ,
84
+ 'Function(' ,
85
+ 'require(' ,
86
+ 'import(' ,
87
+ 'process.' ,
88
+ 'global.' ,
89
+ '__dirname' ,
90
+ '__filename' ,
91
+ 'fs.' ,
92
+ 'child_process' ,
93
+ ] ;
94
+
95
+ const requestStr = JSON . stringify ( request ) ;
96
+ for ( const pattern of suspicious ) {
97
+ if ( requestStr . includes ( pattern ) ) {
98
+ throw new Error ( `Suspicious pattern detected: ${ pattern } ` ) ;
99
+ }
100
+ }
101
+ }
102
+
103
+ private validateToolName ( toolName : string ) : void {
104
+ const allowedTools = [
105
+ 'analyze_position' ,
106
+ 'generate_moves' ,
107
+ 'play_move' ,
108
+ 'evaluate_position' ,
109
+ 'simulate_game' ,
110
+ 'opening_book' ,
111
+ ] ;
112
+
113
+ if ( ! allowedTools . includes ( toolName ) ) {
114
+ throw new Error ( `Unknown tool: ${ toolName } ` ) ;
115
+ }
116
+ }
117
+
118
+ private validateArguments ( args : any ) : void {
119
+ if ( ! args || typeof args !== 'object' ) {
120
+ return ; // Allow empty or simple arguments
121
+ }
122
+
123
+ // Check for excessively large arguments
124
+ const argsStr = JSON . stringify ( args ) ;
125
+ if ( argsStr . length > 10000 ) {
126
+ throw new Error ( 'Arguments too large' ) ;
127
+ }
128
+
129
+ // Validate FEN strings if present
130
+ if ( args . fen && typeof args . fen === 'string' ) {
131
+ this . validateFEN ( args . fen ) ;
132
+ }
133
+
134
+ // Validate numeric ranges
135
+ if ( args . depth !== undefined ) {
136
+ if ( typeof args . depth !== 'number' || args . depth < 1 || args . depth > 20 ) {
137
+ throw new Error ( 'Invalid depth parameter' ) ;
138
+ }
139
+ }
140
+
141
+ if ( args . time_limit !== undefined ) {
142
+ if ( typeof args . time_limit !== 'number' || args . time_limit < 100 || args . time_limit > 30000 ) {
143
+ throw new Error ( 'Invalid time_limit parameter' ) ;
144
+ }
145
+ }
146
+ }
147
+
148
+ private validateFEN ( fen : string ) : void {
149
+ // Basic FEN validation
150
+ const parts = fen . split ( ' ' ) ;
151
+ if ( parts . length < 4 || parts . length > 6 ) {
152
+ throw new Error ( 'Invalid FEN format' ) ;
153
+ }
154
+
155
+ // Validate board position
156
+ const board = parts [ 0 ] ;
157
+ const ranks = board . split ( '/' ) ;
158
+ if ( ranks . length !== 8 ) {
159
+ throw new Error ( 'Invalid FEN board format' ) ;
160
+ }
161
+
162
+ // Validate side to move
163
+ if ( ! [ 'w' , 'b' ] . includes ( parts [ 1 ] ) ) {
164
+ throw new Error ( 'Invalid side to move in FEN' ) ;
165
+ }
166
+
167
+ // Basic castling rights validation
168
+ if ( ! / ^ [ K Q k q - ] + $ / . test ( parts [ 2 ] ) ) {
169
+ throw new Error ( 'Invalid castling rights in FEN' ) ;
170
+ }
171
+
172
+ // Basic en passant validation
173
+ if ( ! / ^ ( [ a - h ] [ 3 6 ] | - ) $ / . test ( parts [ 3 ] ) ) {
174
+ throw new Error ( 'Invalid en passant square in FEN' ) ;
175
+ }
176
+ }
177
+ }
0 commit comments