| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io'; | |
| import 'package:dds/dds.dart'; | |
| import 'package:test/test.dart'; | |
| import 'common/test_helper.dart'; | |
| void main() { | |
| group('DDS Security', () { | |
| late Process process; | |
| setUpAll(() async { | |
| process = await spawnDartProcess('smoke.dart'); | |
| }); | |
| tearDownAll(() async { | |
| process.kill(); | |
| }); | |
| group('Host and Origin validation (Enabled by default)', () { | |
| late DartDevelopmentService dds; | |
| late HttpClient client; | |
| setUpAll(() async { | |
| dds = await DartDevelopmentService.startDartDevelopmentService( | |
| remoteVmServiceUri, | |
| ); | |
| client = HttpClient(); | |
| }); | |
| tearDownAll(() async { | |
| await dds.shutdown(); | |
| client.close(); | |
| }); | |
| test( | |
| 'allows legitimate GET request to DDS-handled path (should return 404 ' | |
| 'Not Found, but NOT 403)', | |
| () async { | |
| final request = await client.getUrl(dds.uri!.resolve('devtools')); | |
| final response = await request.close(); | |
| expect(response.statusCode, HttpStatus.notFound); | |
| await response.drain(); | |
| }, | |
| ); | |
| test('forbids GET request with bad Host Header', () async { | |
| final request = await client.getUrl(dds.uri!.resolve('devtools')); | |
| request.headers.set(HttpHeaders.hostHeader, 'evil.example.com'); | |
| final response = await request.close(); | |
| expect(response.statusCode, HttpStatus.forbidden); | |
| await response.drain(); | |
| }); | |
| test('forbids GET request with bad Origin Header', () async { | |
| final request = await client.getUrl(dds.uri!.resolve('devtools')); | |
| request.headers.set('Origin', 'http://evil.example.com'); | |
| final response = await request.close(); | |
| expect(response.statusCode, HttpStatus.forbidden); | |
| await response.drain(); | |
| }); | |
| test('forbids WebSocket connection with bad Host header', () async { | |
| expect( | |
| () async => await WebSocket.connect( | |
| dds.wsUri.toString(), | |
| headers: { | |
| HttpHeaders.hostHeader: 'evil.example.com', | |
| }, | |
| ), | |
| throwsA(isA<WebSocketException>()), | |
| ); | |
| }); | |
| test('forbids WebSocket connection with bad Origin header', () async { | |
| expect( | |
| () async => await WebSocket.connect( | |
| dds.wsUri.toString(), | |
| headers: { | |
| 'Origin': 'http://evil.example.com', | |
| }, | |
| ), | |
| throwsA(isA<WebSocketException>()), | |
| ); | |
| }); | |
| }); | |
| group('Disable Origin Check', () { | |
| late DartDevelopmentService dds; | |
| late HttpClient client; | |
| setUpAll(() async { | |
| dds = await DartDevelopmentService.startDartDevelopmentService( | |
| remoteVmServiceUri, | |
| disableServiceOriginCheck: true, | |
| ); | |
| client = HttpClient(); | |
| }); | |
| tearDownAll(() async { | |
| await dds.shutdown(); | |
| client.close(); | |
| }); | |
| test( | |
| 'allows bad Host Header (should be ALLOWED -> 404 because DevTools not ' | |
| 'served, but not 403)', | |
| () async { | |
| final request = await client.getUrl(dds.uri!.resolve('devtools')); | |
| request.headers.set(HttpHeaders.hostHeader, 'evil.example.com'); | |
| final response = await request.close(); | |
| expect(response.statusCode, HttpStatus.notFound); | |
| await response.drain(); | |
| }, | |
| ); | |
| test('allows bad Origin Header', () async { | |
| final request = await client.getUrl(dds.uri!.resolve('devtools')); | |
| request.headers.set('Origin', 'http://evil.example.com'); | |
| final response = await request.close(); | |
| expect(response.statusCode, HttpStatus.notFound); | |
| await response.drain(); | |
| }); | |
| }); | |
| }); | |
| } |