@@ -87,16 +87,39 @@ test('ASSIGN: built-ins are reserved by default', () => {
8787 assert . ok ( ! M . BUILTIN_TOOL_NAMES . includes ( tools [ 0 ] . name ) ) ;
8888} ) ;
8989
90- test ( 'ASSIGN: two servers that sanitize alike still get distinct names' , ( ) => {
91- const { tools } = M . assignToolNames ( [
92- { server : 'my-server' , tool : 'go' } ,
93- { server : 'my/server' , tool : 'go' } // sanitizes to my_server… as does the first? force the clash
90+ test ( 'ASSIGN: two servers that sanitize to the SAME name still get distinct tool names' , ( ) => {
91+ // 'my/server' and 'my:server' BOTH sanitize to 'my_server' — a genuine collision. An earlier version
92+ // of this test paired 'my-server' with 'my/server', but '-' is already legal so they never collided:
93+ // the test passed without ever entering the dedupe path. Assert the precondition so it can't rot again.
94+ assert . strictEqual (
95+ M . namespaceToolName ( 'my/server' , 'go' ) , M . namespaceToolName ( 'my:server' , 'go' ) ,
96+ 'precondition: these inputs must actually collide, or this test proves nothing'
97+ ) ;
98+ const { tools, problems } = M . assignToolNames ( [
99+ { server : 'my/server' , tool : 'go' } ,
100+ { server : 'my:server' , tool : 'go' }
94101 ] ) ;
95102 assert . strictEqual ( tools . length , 2 ) ;
96- assert . notStrictEqual ( tools [ 0 ] . name , tools [ 1 ] . name ) ;
103+ assert . notStrictEqual ( tools [ 0 ] . name , tools [ 1 ] . name , 'the collision must be broken, not silently aliased' ) ;
104+ assert . ok ( problems . some ( ( p ) => / a l r e a d y t a k e n / . test ( p . message ) ) , 'the dedupe path must report it' ) ;
97105 for ( const t of tools ) { assert . ok ( LEGAL . test ( t . name ) ) ; }
98106} ) ;
99107
108+ test ( 'TRUST: an untrusted env can never reach the prototype setter' , ( ) => {
109+ // JSON.parse creates a REAL own "__proto__" key, so a plain Object.assign would hand it to the
110+ // prototype setter instead of copying it. The string-value check already rejects the object-valued
111+ // payload, but this makes the guarantee structural rather than incidental.
112+ const raw = JSON . parse ( '{"evil":{"command":"x","env":{"__proto__":"pwned","constructor":"no","SAFE":"ok"}}}' ) ;
113+ const { servers } = M . loadServerConfig ( { settings : raw } ) ;
114+ const env = servers [ 0 ] . env ;
115+ assert . strictEqual ( env . SAFE , 'ok' , 'legitimate vars must survive' ) ;
116+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( env , '__proto__' ) , '__proto__ must not be copied through' ) ;
117+ assert . ok ( ! Object . prototype . hasOwnProperty . call ( env , 'constructor' ) , 'constructor must not be copied through' ) ;
118+ assert . strictEqual ( Object . getPrototypeOf ( env ) , Object . prototype , 'the copy\'s prototype must not be retargeted' ) ;
119+ // @ts -expect-error — probing for global pollution
120+ assert . strictEqual ( { } . pwned , undefined , 'global Object.prototype must be untouched' ) ;
121+ } ) ;
122+
100123test ( 'ASSIGN: a server over the per-server tool cap has the surplus dropped, with a problem' , ( ) => {
101124 const pairs = [ ] ;
102125 for ( let i = 0 ; i < M . MAX_TOOLS_PER_SERVER + 5 ; i ++ ) { pairs . push ( { server : 'big' , tool : 'tool' + i } ) ; }
0 commit comments