Summary
On macOS, Session.cookies.set() silently drops httpOnly and does not apply sameSite. The cookie is created without the HttpOnly flag, so page JavaScript can read it through document.cookie. Windows and Linux both honor these flags, so this is a macOS-only parity bug with security impact.
Reproduced on the packaged macOS arm64 SDK 2.0.2 and still present in the source at v2.0.4 (c54c0c1).
Reproduction
import { Session } from "electrobun/main";
const session = Session.fromPartition("persist:example");
session.cookies.set({
domain: "example.test",
name: "session",
value: "secret",
path: "/",
secure: true,
httpOnly: true,
sameSite: "strict",
});
console.log(session.cookies.get({ name: "session" }));
// macOS: httpOnly: false
// Windows (WebView2) and Linux (WebKitGTK): httpOnly: trueLoading a page from that origin in a BrowserView that uses the partition and evaluating document.cookie returns session=secret on macOS.
Root cause
sessionSetCookie in package/src/native/macos/nativeWrapper.mm builds the NSHTTPCookie property dictionary from the JSON payload but:
- Never reads
httpOnly. NoHttpOnlyproperty is added, even though the comment above the function documents"httpOnly":trueas part of the input. The getter in the same file readscookie.HTTPOnlyback, so the round trip loses the flag. - Passes
sameSitethrough as a raw string.NSHTTPCookieSameSitePolicyexpectsNSHTTPCookieSameSiteLaxorNSHTTPCookieSameSiteStrict. A raw"strict"(lowercase, asnative.tsdocuments it) is not one of those constants, so the policy is not applied.
For comparison, Windows calls put_IsHttpOnly(httpOnly) and Linux calls soup_cookie_set_http_only(cookie, httpOnly).
Impact
Apps that authenticate a partition by setting a session cookie from the main process expect HttpOnly to keep the token away from page scripts. On macOS, any script running in the view can read and exfiltrate the token, including scripts injected through XSS in remote or user-supplied content. SameSite not being applied also weakens CSRF protection. The failure is silent, so apps only notice if they read the flags back.
Proposed fix
@@ sessionSetCookie
if ([cookieDict[@"secure"] boolValue]) {
properties[NSHTTPCookieSecure] = @"TRUE";
}
+ // Foundation has no exported HttpOnly property-key constant; it accepts this key.
+ if ([cookieDict[@"httpOnly"] boolValue]) {
+ properties[@"HttpOnly"] = @"TRUE";
+ }
+
// Expiration date
@@
// SameSite
if (cookieDict[@"sameSite"]) {
- properties[NSHTTPCookieSameSitePolicy] = cookieDict[@"sameSite"];
+ NSString *policy = [cookieDict[@"sameSite"] lowercaseString];
+ if ([policy isEqualToString:@"strict"]) {
+ properties[NSHTTPCookieSameSitePolicy] = NSHTTPCookieSameSiteStrict;
+ } else if ([policy isEqualToString:@"lax"]) {
+ properties[NSHTTPCookieSameSitePolicy] = NSHTTPCookieSameSiteLax;
+ }
+ // "no_restriction" / "none": leave the policy unset, which Foundation treats as None.
}A small Foundation check confirms that a cookie built this way reports HTTPOnly == YES and the expected sameSitePolicy. An alternative that avoids the private key is to build the cookie from a Set-Cookie header string with +[NSHTTPCookie cookiesWithResponseHeaderFields:forURL:], which parses HttpOnly and SameSite natively.
Happy to open a PR with this change and a test if that helps.
Activity
0No activity yet.