mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-16 07:48:35 -04:00
allow localhost host to connect to CDP and MCP
This commit is contained in:
1 parent
a0e3da6a6b
commit
78fda6779f
3 files changed
+33
-9
No files matched your search
+14
-2
@@ -908,8 +908,10 @@ test "Client: http handshake host" {
|
||||
|
||||
for ([_][]const u8{
|
||||
"rebind.evil.com:9583",
|
||||
// Not even the name that resolves to loopback on every machine.
|
||||
"localhost:9583",
|
||||
// Only the exact `localhost:<port>` form is allowed.
|
||||
"localhost",
|
||||
"LOCALHOST:9583",
|
||||
"localhost.evil.com:9583",
|
||||
}) |host| {
|
||||
var buf: [256]u8 = undefined;
|
||||
try assertHTTPError(
|
||||
@@ -918,6 +920,16 @@ test "Client: http handshake host" {
|
||||
try std.fmt.bufPrint(&buf, with_host, .{host}),
|
||||
);
|
||||
}
|
||||
|
||||
// `localhost:<port>` is hardwired to loopback by browsers, no DNS
|
||||
// lookup involved, so it gets through like an IP literal.
|
||||
{
|
||||
var c = try createTestClient();
|
||||
defer c.deinit();
|
||||
var buf: [256]u8 = undefined;
|
||||
const res = try c.httpRequest(try std.fmt.bufPrint(&buf, with_host, .{"localhost:9583"}));
|
||||
try testing.expect(std.mem.startsWith(u8, res, "HTTP/1.1 101 Switching Protocols\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
test "Client: http valid handshake" {
|
||||
|
||||
+12
-5
@@ -495,20 +495,27 @@ pub fn upgrade(self: *Connection, request: []u8) !void {
|
||||
} else if (std.ascii.eqlIgnoreCase(key, "host")) {
|
||||
const host = value;
|
||||
const is_allowed = blk: {
|
||||
// allow literal localhost
|
||||
if (std.mem.startsWith(u8, host, "localhost:")) {
|
||||
break :blk true;
|
||||
}
|
||||
|
||||
_ = std.Io.net.IpAddress.parseLiteral(host) catch break :blk false;
|
||||
break :blk true;
|
||||
};
|
||||
|
||||
// Defense in depth against DNS rebinding: an IP literal is the only
|
||||
// thing that can legitimately reach us, because no name has to be
|
||||
// resolved to produce one. Any name at all - "localhost" included -
|
||||
// means something answered a DNS lookup with our address, which is
|
||||
// exactly what a rebinding attack looks like. A request without a
|
||||
// Host header isn't from a browser, so it can't be the vector.
|
||||
// resolved to produce one. The one name we accept is
|
||||
// `localhost:<port>`, which browsers hardwire to loopback without
|
||||
// any DNS lookup. Any other name means something answered a DNS
|
||||
// lookup with our address, which is exactly what a rebinding
|
||||
// attack looks like. A request without a Host header isn't from a
|
||||
// browser, so it can't be the vector.
|
||||
if (!is_allowed) {
|
||||
log.warn(.cdp, "rejected websocket host", .{
|
||||
.host = host[0..@min(host.len, 64)],
|
||||
.hint = "connect to the CDP endpoint by IP address",
|
||||
.hint = "connect to the CDP endpoint by IP address or localhost",
|
||||
});
|
||||
return error.ForbiddenHost;
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ fn serve(self: *HttpServer, out: *std.Io.Writer, arena: std.mem.Allocator, reque
|
||||
// the head's string memory.
|
||||
const session_id = checkHeaders(arena, request) catch |err| switch (err) {
|
||||
error.ForbiddenOrigin => return request.respond("Origin not allowed\n", .{ .status = .forbidden, .keep_alive = false }),
|
||||
error.ForbiddenHost => return request.respond("Host not allowed, connect to the MCP endpoint by IP address\n", .{ .status = .forbidden, .keep_alive = false }),
|
||||
error.ForbiddenHost => return request.respond("Host not allowed, connect to the MCP endpoint by IP address or localhost\n", .{ .status = .forbidden, .keep_alive = false }),
|
||||
else => return err,
|
||||
};
|
||||
const keep_alive = request.head.keep_alive;
|
||||
@@ -457,6 +457,11 @@ fn checkHeaders(arena: std.mem.Allocator, request: *std.http.Server.Request) !?[
|
||||
return error.ForbiddenOrigin;
|
||||
} else if (std.ascii.eqlIgnoreCase(key, "host")) {
|
||||
const is_allowed = blk: {
|
||||
// allow literal localhost
|
||||
if (std.mem.startsWith(u8, value, "localhost:")) {
|
||||
break :blk true;
|
||||
}
|
||||
|
||||
_ = std.Io.net.IpAddress.parseLiteral(value) catch break :blk false;
|
||||
break :blk true;
|
||||
};
|
||||
@@ -464,7 +469,7 @@ fn checkHeaders(arena: std.mem.Allocator, request: *std.http.Server.Request) !?[
|
||||
if (!is_allowed) {
|
||||
log.warn(.mcp, "rejected request host", .{
|
||||
.host = value[0..@min(value.len, 64)],
|
||||
.hint = "connect to the MCP endpoint by IP address",
|
||||
.hint = "connect to the MCP endpoint by IP address or localhost",
|
||||
});
|
||||
return error.ForbiddenHost;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user