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
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sync_relay_core/core.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -6,7 +8,8 @@ void main() {
|
||||
test('defaults are sane', () {
|
||||
final c = AppConfig.defaults();
|
||||
expect(c.port, 8080);
|
||||
expect(c.secret, isNotEmpty);
|
||||
expect(c.secret, isEmpty, reason: 'empty password = open server');
|
||||
expect(c.advertiseHost, isEmpty);
|
||||
});
|
||||
|
||||
test('fromMap tolerates missing keys', () {
|
||||
@@ -31,6 +34,26 @@ void main() {
|
||||
});
|
||||
|
||||
group('protocol', () {
|
||||
test('non-finite numbers are rejected', () {
|
||||
final inf = jsonDecode('{"t":"seek","position":1e400}') as Map<String, Object?>;
|
||||
expect(inf['position'], double.infinity);
|
||||
expect(() => SeekIntent.fromJson(inf), throwsA(isA<ProtocolException>()));
|
||||
expect(
|
||||
() => ReportMessage.fromJson(
|
||||
{'t': 'report', 'position': 1.0, 'buffered': double.infinity}),
|
||||
throwsA(isA<ProtocolException>()));
|
||||
expect(
|
||||
() => ReportMessage.fromJson(
|
||||
{'t': 'report', 'position': double.nan, 'buffered': 1.0}),
|
||||
throwsA(isA<ProtocolException>()));
|
||||
expect(
|
||||
() => SwapIntent.fromJson({
|
||||
't': 'swap',
|
||||
'media': {'name': 'a', 'size': 1, 'duration': double.infinity}
|
||||
}),
|
||||
throwsA(isA<ProtocolException>()));
|
||||
});
|
||||
|
||||
test('hello round-trips', () {
|
||||
const msg = HelloMessage(
|
||||
proto: 1,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sync_relay_core/core.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@@ -16,6 +18,24 @@ void main() {
|
||||
void buffer(String token, double secs) =>
|
||||
engine.applyReport(token, engine.positionAt(), secs, engine.eventId);
|
||||
|
||||
group('non-finite guards', () {
|
||||
test('seek/report/swap reject Infinity and NaN, broadcast stays encodable', () {
|
||||
join('a', 'alice');
|
||||
engine.applySeek('a', double.infinity);
|
||||
engine.applySeek('a', double.nan);
|
||||
expect(engine.position, 0);
|
||||
engine.applyReport('a', double.infinity, 1, engine.eventId);
|
||||
engine.applyReport('a', 1, double.infinity, engine.eventId);
|
||||
expect(engine.members['a']!.buffered, 0);
|
||||
expect(
|
||||
engine.applySwap('a',
|
||||
const HelloMedia(name: 'x', size: 1, duration: double.infinity)),
|
||||
isFalse);
|
||||
expect(engine.mediaId, 1);
|
||||
jsonEncode(engine.broadcast().toJson());
|
||||
});
|
||||
});
|
||||
|
||||
group('membership + gate', () {
|
||||
test('join lands out-of-set, room stays held', () {
|
||||
join('a', 'alice');
|
||||
|
||||
Reference in new issue
Block a user