Skip to content

fix: skip POSIX permission check on Windows when writing connection info - #199

Open
svnscha wants to merge 1 commit into
TKasperczyk:mainfrom
svnscha:fix/windows-connection-info-permissions
Open

fix: skip POSIX permission check on Windows when writing connection info#199
svnscha wants to merge 1 commit into
TKasperczyk:mainfrom
svnscha:fix/windows-connection-info-permissions

Conversation

@svnscha

@svnscha svnscha commented Aug 22, 2026

Copy link
Copy Markdown

The POSIX permission hardening in writeConnectionInfo throws unconditionally on Windows, so the MCP server cannot start there.

Symptom

Failed to start MCP server: Error: thunderbird-mcp tmp directory has group/world permissions — refusing to write connection info
    init moz-extension://.../background.js:10

The add-on stays enabled and shows no error in the Add-ons Manager. Nothing listens on 8765–8774, connection.json is never written, and every MCP client just reports connection discovery failure — so from the outside it looks like the bridge is misconfigured rather than like the server never started.

Cause

The guard assumes nsIFile.permissions returns 0 where POSIX modes are unavailable — that is what the existing comment says, and it is why the check was believed inert on Windows:

// permissions is 0 on platforms that don't expose POSIX modes
// (Windows ACLs), so the chmod is a no-op there.

nsLocalFileWin doesn't do that. It synthesises a POSIX-looking mode from the read-only attribute alone — 0777 for any directory, regardless of the ACL — and assigning .permissions is a no-op on NTFS. So:

  • mode is truthy (0777)
  • mode & 0o077 is non-zero
  • the write-back to 0o700 silently does nothing
  • the re-check still sees group/world bits, and it throws

The directory that trips this is the one this same function created moments earlier with 0o700. Its ACL is inherited from the user's own %TEMP%SYSTEM, Administrators, and the user, with no Everyone entry — so there is nothing actually wrong with it.

It also recurs on every launch rather than being a one-time glitch: shutdown removes connection.json but leaves the directory behind, so the !exists() branch that skips the check is only ever taken once. Deleting %LOCALAPPDATA%\Temp\thunderbird-mcp before each start works around it.

Fix

Gate the check on Services.appinfo.OS !== "WINNT", and correct the stale comment. The POSIX hardening is unchanged everywhere it can actually do something — the shared-/tmp race it defends against has no Windows analogue here, since access is governed by the inherited user-only ACL.

Verification

Thunderbird 154.0 on Windows 11. Before: the server failed to start on every restart unless the tmp directory was deleted by hand. After: startup and connection discovery work with the directory left in place across restarts.

node --test test/*.cjs → 500 passing, 0 failures (17 skipped while a real Thunderbird holds the port). eslint . → 0 errors.

No test is included: the surrounding logic reads Services.dirsvc and live nsIFile state, and it isn't inside one of the BEGIN/END marker blocks that the existing suite extracts for sandboxed testing. Happy to add one if you'd like the block factored out for that.

writeConnectionInfo hardens the tmp directory against a shared-/tmp race
by rejecting group/world bits. The guard assumes nsIFile.permissions
returns 0 where POSIX modes are unavailable, so that the check is inert
on Windows.

That assumption does not hold. nsLocalFileWin synthesises a POSIX-looking
mode from the read-only attribute alone -- 0777 for any directory,
regardless of the ACL -- and assigning .permissions is a no-op on NTFS.
So mode is truthy, (mode & 0o077) is non-zero, the write-back cannot
clear it, and the guard throws:

    Failed to start MCP server: Error: thunderbird-mcp tmp directory has
    group/world permissions -- refusing to write connection info

This fires for the directory the same function just created with 0o700,
so the server never starts and the bridge reports "connection discovery
failed". It recurs on every launch: shutdown removes connection.json but
leaves the directory, so the !exists() branch is only taken once.

Gate the check on Services.appinfo.OS !== "WINNT". Access on Windows is
governed by the ACL inherited from the user's own %TEMP%, which is
user-only, so the hardening has nothing to add there.

Verified on Thunderbird 154.0 / Windows 11: before the change the server
failed to start on every restart unless the tmp directory was deleted by
hand; after it, startup and connection discovery work with the directory
left in place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant