Server hardening: finite numbers, open servers, unkickable host, no URL leaks
- Reject non-finite numerics at protocol parse and in the engine (a 1e400 seek became Infinity and broke every broadcast). - Reject empty swaps; sanitize URL-looking media names. - Blank password = open server; defaults no longer 'change-me'. - Loopback peers (the in-process host) can't be kicked. - /health no longer exposes media/transport; file URLs use the host the client dialled instead of 127.0.0.1. - Count served bytes as streamed, not as requested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
32848aaf71
commit
e2301aa4ec
12 files changed
+370
-45
No files matched your search
@@ -8,6 +8,15 @@ import 'package:sync_relay_server/server.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
Future<String?> _lanAddress() async {
|
||||
for (final i in await NetworkInterface.list(type: InternetAddressType.IPv4)) {
|
||||
for (final a in i.addresses) {
|
||||
if (!a.isLoopback) return a.address;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('room auth live', () {
|
||||
late File file;
|
||||
@@ -73,7 +82,7 @@ void main() {
|
||||
expect(decoded['proto'], RelayConstants.protoVersion);
|
||||
expect(decoded['viewers'], 0);
|
||||
expect(decoded['bytesServed'], 0);
|
||||
expect(decoded['media'], isNotNull);
|
||||
expect(decoded.containsKey('media'), isFalse, reason: 'no media name leak');
|
||||
});
|
||||
|
||||
test('wrong secret gets err, right secret gets welcome+token', () async {
|
||||
@@ -238,10 +247,16 @@ void main() {
|
||||
});
|
||||
|
||||
test('kick via socket: peer kicked, unknown target errors', () async {
|
||||
WebSocketChannel open() => WebSocketChannel.connect(
|
||||
Uri.parse('ws://127.0.0.1:${server.boundPort}/sync'));
|
||||
Future<(WebSocketChannel, String, String, List<String>)> join(String who) async {
|
||||
final ch = open();
|
||||
final lan = await _lanAddress();
|
||||
if (lan == null) {
|
||||
markTestSkipped('no non-loopback IPv4 to act as a non-host peer');
|
||||
return;
|
||||
}
|
||||
// Loopback peers are the host and unkickable, so bob dials the LAN address.
|
||||
Future<(WebSocketChannel, String, String, List<String>)> join(String who,
|
||||
[String host = '127.0.0.1']) async {
|
||||
final ch = WebSocketChannel.connect(
|
||||
Uri.parse('ws://$host:${server.boundPort}/sync'));
|
||||
final messages = <String>[];
|
||||
ch.stream.listen((m) => messages.add('$m'));
|
||||
final msg = HelloMessage(
|
||||
@@ -289,7 +304,7 @@ void main() {
|
||||
}
|
||||
|
||||
final (a, _, _, messagesA) = await join('alice');
|
||||
final (b, tokenB, idB, messagesB) = await join('bob');
|
||||
final (b, tokenB, idB, messagesB) = await join('bob', lan);
|
||||
try {
|
||||
a.sink.add(jsonEncode(
|
||||
KickRequest(target: idB, reason: 'afk?').toJson()));
|
||||
|
||||
Reference in new issue
Block a user