From f856ce5e60b1cea4b2cae5313f10b2718e6bfc74 Mon Sep 17 00:00:00 2001 From: Edward Park Date: Fri, 7 Aug 2026 21:37:24 -0700 Subject: [PATCH] fix: apply Windows state ACLs to existing descriptors --- fastmcp_slim/fastmcp/cli/deploy/state.py | 13 +++++++------ tests/cli/deploy/test_credentials.py | 14 ++++++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/fastmcp_slim/fastmcp/cli/deploy/state.py b/fastmcp_slim/fastmcp/cli/deploy/state.py index 3ee77efcc..c037ce450 100644 --- a/fastmcp_slim/fastmcp/cli/deploy/state.py +++ b/fastmcp_slim/fastmcp/cli/deploy/state.py @@ -21,11 +21,15 @@ class StateFileError(RuntimeError): _WINDOWS_ACL_SCRIPT = r""" $ErrorActionPreference = "Stop" -$path = $args[0] +$path = $env:FASTMCP_STATE_PATH $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User +$acl = Get-Acl -LiteralPath $path +$acl.SetAccessRuleProtection($true, $false) +foreach ($existingRule in @($acl.Access)) { + $acl.RemoveAccessRuleSpecific($existingRule) +} if ([System.IO.Directory]::Exists($path)) { - $acl = [System.Security.AccessControl.DirectorySecurity]::new() $inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit ` -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( @@ -36,7 +40,6 @@ if ([System.IO.Directory]::Exists($path)) { [System.Security.AccessControl.AccessControlType]::Allow ) } else { - $acl = [System.Security.AccessControl.FileSecurity]::new() $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( $sid, [System.Security.AccessControl.FileSystemRights]::FullControl, @@ -44,8 +47,6 @@ if ([System.IO.Directory]::Exists($path)) { ) } -$acl.SetOwner($sid) -$acl.SetAccessRuleProtection($true, $false) $acl.AddAccessRule($rule) Set-Acl -LiteralPath $path -AclObject $acl """ @@ -61,11 +62,11 @@ def _restrict_windows_access(path: Path) -> None: "-NonInteractive", "-Command", _WINDOWS_ACL_SCRIPT, - str(path), ], check=True, capture_output=True, text=True, + env={**os.environ, "FASTMCP_STATE_PATH": str(path)}, ) except (OSError, subprocess.SubprocessError) as exc: raise StateFileError("Could not restrict access to CLI state") from exc diff --git a/tests/cli/deploy/test_credentials.py b/tests/cli/deploy/test_credentials.py index bd6f8c514..a4ea55b45 100644 --- a/tests/cli/deploy/test_credentials.py +++ b/tests/cli/deploy/test_credentials.py @@ -5,6 +5,7 @@ import os import subprocess import traceback from pathlib import Path +from typing import cast import httpx2 import pytest @@ -162,9 +163,12 @@ def test_windows_acl_replaces_the_existing_access_list( path = tmp_path / "auth.json" path.write_text("{}") calls: list[list[str]] = [] + state_paths: list[str] = [] def run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]: calls.append(command) + environment = cast(dict[str, str], kwargs["env"]) + state_paths.append(environment["FASTMCP_STATE_PATH"]) return subprocess.CompletedProcess(command, 0, "", "") monkeypatch.setattr("fastmcp.cli.deploy.state.subprocess.run", run) @@ -178,11 +182,13 @@ def test_windows_acl_replaces_the_existing_access_list( "-NonInteractive", "-Command", calls[0][5], - str(path), ] ] - assert "FileSecurity]::new()" in calls[0][5] + assert state_paths == [str(path)] + assert "$path = $env:FASTMCP_STATE_PATH" in calls[0][5] + assert "Get-Acl -LiteralPath $path" in calls[0][5] assert "SetAccessRuleProtection($true, $false)" in calls[0][5] + assert "RemoveAccessRuleSpecific($existingRule)" in calls[0][5] @pytest.mark.skipif(os.name != "nt", reason="Windows ACL inspection") @@ -191,7 +197,7 @@ def test_windows_credential_state_allows_only_the_current_user(tmp_path: Path) - store = CredentialStore(state_directory) store.save("fmcp_secret") inspect_acl = r""" -$acl = Get-Acl -LiteralPath $args[0] +$acl = Get-Acl -LiteralPath $env:FASTMCP_STATE_PATH $current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value $access = @($acl.Access | ForEach-Object { $_.IdentityReference.Translate( @@ -215,11 +221,11 @@ $access = @($acl.Access | ForEach-Object { "-NonInteractive", "-Command", inspect_acl, - str(path), ], check=True, capture_output=True, text=True, + env={**os.environ, "FASTMCP_STATE_PATH": str(path)}, ) acl = json.loads(result.stdout) assert set(acl["access"]) == {acl["current"]}