From 6bc4ebdfed541b9a3894b39af30b1dc966d2f653 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 19 May 2026 11:39:52 +0300 Subject: [PATCH 1/2] `URL.zig`: fix NUL/CR/LF/TAB character injection through authority --- src/browser/URL.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/URL.zig b/src/browser/URL.zig index c3bfbde7..07836fe0 100644 --- a/src/browser/URL.zig +++ b/src/browser/URL.zig @@ -883,8 +883,9 @@ fn parseAuthority(raw: []const u8) ?AuthorityInfo { const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null; const authority_start = scheme_end + 3; - // Find end of authority FIRST (start of path/query/fragment or end of string) - const authority_end = if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#")) |end| + // Find end of authority FIRST (start of path/query/fragment, + // a NUL/CR/LF/TAB, or end of string). + const authority_end = if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#\x00\r\n\t")) |end| authority_start + end else raw.len; From 64a3f3edd7a9df11864c9ae45a3306e2813cb777 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 19 May 2026 11:40:14 +0300 Subject: [PATCH 2/2] `URL.zig`: update tests --- src/browser/URL.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/browser/URL.zig b/src/browser/URL.zig index 07836fe0..620e63a2 100644 --- a/src/browser/URL.zig +++ b/src/browser/URL.zig @@ -1580,6 +1580,12 @@ test "URL: getHost" { try testing.expectEqualSlices(u8, "evil.example.com", getHost("http://evil.example.com/@victim.example.com/")); try testing.expectEqualSlices(u8, "evil.example.com", getHost("https://evil.example.com/path/@victim.example.com")); + try testing.expectEqual("evil.example.com:8521", getHost("http://evil.example.com:8521\x00@victim.example.com:8520/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\x00@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\r@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\n@victim.example.com/")); + try testing.expectEqual("evil.example.com", getHost("http://evil.example.com\t@victim.example.com/")); + // IPv6 addresses try testing.expectEqualSlices(u8, "[::1]:8080", getHost("http://[::1]:8080/path")); try testing.expectEqualSlices(u8, "[::1]", getHost("http://[::1]/path")); @@ -1670,6 +1676,17 @@ test "URL: getOrigin" { .{ .url = "https://evil.example.com/path/@victim.example.com/steal", .expected = "https://evil.example.com" }, .{ .url = "http://evil.example.com/@victim.example.com:443/", .expected = "http://evil.example.com" }, + // SECURITY: Null byte injection. + .{ .url = "http://attacker:8521\x00@victim:8520/", .expected = "http://attacker:8521" }, + .{ .url = "http://attacker.com\x00@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com/\x00@victim.com/", .expected = "http://attacker.com" }, + + // SECURITY: CR / LF / TAB are stripped by the WHATWG URL parser, so a + // userinfo "@" hidden behind one must not change the origin here either. + .{ .url = "http://attacker.com\r@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com\n@victim.com/", .expected = "http://attacker.com" }, + .{ .url = "http://attacker.com\t@victim.com/", .expected = "http://attacker.com" }, + // @ in query/fragment must also not affect origin .{ .url = "https://example.com/path?user=foo@bar.com", .expected = "https://example.com" }, .{ .url = "https://example.com/path#user@host", .expected = "https://example.com" },