#589

macOS: Session.cookies.set() drops httpOnly and does not apply sameSite

OpenTeyik0opened 10d ago

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: true

Loading 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:

  1. Never reads httpOnly. No HttpOnly property is added, even though the comment above the function documents "httpOnly":true as part of the input. The getter in the same file reads cookie.HTTPOnly back, so the round trip loses the flag.
  2. Passes sameSite through as a raw string. NSHTTPCookieSameSitePolicy expects NSHTTPCookieSameSiteLax or NSHTTPCookieSameSiteStrict. A raw "strict" (lowercase, as native.ts documents 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

0

No activity yet.