@@ -9,6 +9,7 @@ import type * as vscode from 'vscode';
99import * as fs from 'node:fs/promises' ;
1010import type * as os from 'node:os' ;
1111import * as path from 'node:path' ;
12+ import * as http from 'node:http' ;
1213import { IDEServer } from './ide-server.js' ;
1314import type { DiffManager } from './diff-manager.js' ;
1415
@@ -62,26 +63,26 @@ vi.mock('./open-files-manager', () => {
6263 return { OpenFilesManager } ;
6364} ) ;
6465
66+ const getPortFromMock = (
67+ replaceMock : ReturnType <
68+ ( ) => vscode . ExtensionContext [ 'environmentVariableCollection' ] [ 'replace' ]
69+ > ,
70+ ) => {
71+ const port = vi
72+ . mocked ( replaceMock )
73+ . mock . calls . find ( ( call ) => call [ 0 ] === 'GEMINI_CLI_IDE_SERVER_PORT' ) ?. [ 1 ] ;
74+
75+ if ( port === undefined ) {
76+ expect . fail ( 'Port was not set' ) ;
77+ }
78+ return port ;
79+ } ;
80+
6581describe ( 'IDEServer' , ( ) => {
6682 let ideServer : IDEServer ;
6783 let mockContext : vscode . ExtensionContext ;
6884 let mockLog : ( message : string ) => void ;
6985
70- const getPortFromMock = (
71- replaceMock : ReturnType <
72- ( ) => vscode . ExtensionContext [ 'environmentVariableCollection' ] [ 'replace' ]
73- > ,
74- ) => {
75- const port = vi
76- . mocked ( replaceMock )
77- . mock . calls . find ( ( call ) => call [ 0 ] === 'GEMINI_CLI_IDE_SERVER_PORT' ) ?. [ 1 ] ;
78-
79- if ( port === undefined ) {
80- expect . fail ( 'Port was not set' ) ;
81- }
82- return port ;
83- } ;
84-
8586 beforeEach ( ( ) => {
8687 mockLog = vi . fn ( ) ;
8788 ideServer = new IDEServer ( mockLog , mocks . diffManager ) ;
@@ -456,3 +457,105 @@ describe('IDEServer', () => {
456457 } ) ;
457458 } ) ;
458459} ) ;
460+
461+ const request = (
462+ port : string ,
463+ options : http . RequestOptions ,
464+ body ?: string ,
465+ ) : Promise < http . IncomingMessage > =>
466+ new Promise ( ( resolve , reject ) => {
467+ const req = http . request (
468+ {
469+ hostname : '127.0.0.1' ,
470+ port,
471+ ...options ,
472+ } ,
473+ ( res ) => {
474+ res . resume ( ) ; // Consume response data to free up memory
475+ resolve ( res ) ;
476+ } ,
477+ ) ;
478+ req . on ( 'error' , reject ) ;
479+ if ( body ) {
480+ req . write ( body ) ;
481+ }
482+ req . end ( ) ;
483+ } ) ;
484+
485+ describe ( 'IDEServer HTTP endpoints' , ( ) => {
486+ let ideServer : IDEServer ;
487+ let mockContext : vscode . ExtensionContext ;
488+ let mockLog : ( message : string ) => void ;
489+ let port : string ;
490+
491+ beforeEach ( async ( ) => {
492+ mockLog = vi . fn ( ) ;
493+ ideServer = new IDEServer ( mockLog , mocks . diffManager ) ;
494+ mockContext = {
495+ subscriptions : [ ] ,
496+ environmentVariableCollection : {
497+ replace : vi . fn ( ) ,
498+ clear : vi . fn ( ) ,
499+ } ,
500+ } as unknown as vscode . ExtensionContext ;
501+ await ideServer . start ( mockContext ) ;
502+ const replaceMock = mockContext . environmentVariableCollection . replace ;
503+ port = getPortFromMock ( replaceMock ) ;
504+ } ) ;
505+
506+ afterEach ( async ( ) => {
507+ await ideServer . stop ( ) ;
508+ vi . restoreAllMocks ( ) ;
509+ } ) ;
510+
511+ it ( 'should deny requests with an origin header' , async ( ) => {
512+ const response = await request (
513+ port ,
514+ {
515+ path : '/mcp' ,
516+ method : 'POST' ,
517+ headers : {
518+ Host : `localhost:${ port } ` ,
519+ Origin : 'https://evil.com' ,
520+ 'Content-Type' : 'application/json' ,
521+ } ,
522+ } ,
523+ JSON . stringify ( { jsonrpc : '2.0' , method : 'initialize' } ) ,
524+ ) ;
525+ expect ( response . statusCode ) . toBe ( 403 ) ;
526+ } ) ;
527+
528+ it ( 'should deny requests with an invalid host header' , async ( ) => {
529+ const response = await request (
530+ port ,
531+ {
532+ path : '/mcp' ,
533+ method : 'POST' ,
534+ headers : {
535+ Host : 'evil.com' ,
536+ 'Content-Type' : 'application/json' ,
537+ } ,
538+ } ,
539+ JSON . stringify ( { jsonrpc : '2.0' , method : 'initialize' } ) ,
540+ ) ;
541+ expect ( response . statusCode ) . toBe ( 403 ) ;
542+ } ) ;
543+
544+ it ( 'should allow requests with a valid host header' , async ( ) => {
545+ const response = await request (
546+ port ,
547+ {
548+ path : '/mcp' ,
549+ method : 'POST' ,
550+ headers : {
551+ Host : `localhost:${ port } ` ,
552+ 'Content-Type' : 'application/json' ,
553+ } ,
554+ } ,
555+ JSON . stringify ( { jsonrpc : '2.0' , method : 'initialize' } ) ,
556+ ) ;
557+ // We expect a 400 here because we are not sending a valid MCP request,
558+ // but it's not a host error, which is what we are testing.
559+ expect ( response . statusCode ) . toBe ( 400 ) ;
560+ } ) ;
561+ } ) ;
0 commit comments