fix: apply Windows state ACLs to existing descriptors

This commit is contained in:
Edward Park 2026-08-07 21:37:24 -07:00
commit f856ce5e60
2 changed files with 17 additions and 10 deletions

View file

@ -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

View file

@ -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"]}