Skip to content

Applying ACLPolicy for HNSEndpoint is broken in WindowsServer2025 #616

Closed

Description

Describe the bug
When I create an ACLPolicy for HNSEndpoint, it fails to apply in Windows Server 2025 Containers. This behavior works as expected in Windows Server 2019. It's unclear what has changed in the latest version of Windows Server to break this behavior. The lack of being able to apply ACLPolicy for NAT indicates that containers on Windows Server 2025 can talk to any internal IP. For context, we have been able to shield containers from reaching internal IPs in Windows Server 2019, but we've lost this ability in Windows Server 2025.

To Reproduce

  1. Create container-1 in docker, capture the IP-Address, and run a simple web server on port 8080
  2. Create container-2 in docker, capture the IP-Address
  3. If you curl.exe container-1:8080 (from inside container-2), you should see the response from the server. That's expected because by default there is not ACLPolicy.
  4. Run Get-HNSEndpoint on the host to get the Endpoint-ID for container-2
  5. Execute acl-policy.exe (a simple binary that sets ACLPolicy) e.g. c:\acl-policy.exe endoint-id-from-step-4 ip-address-for-container-2 ip-address-for-container-1. This will create an ACLPolicy to Block Outgoing traffic from container-2 to container-1.
  6. Re-run the curl.exe from step 3 and you still see container-2 is able to talk to container-1

Expected behavior
When I re-run the curl.exe in container-2 I expect to see an error after setting the ACLPolicy

Configuration:

  • Edition: Windows Server
  • Base Image being used: Windows Server Core
  • Container engine: docker
  • Container Engine version N/A

Additional context
Simple acl-policy binary:

package main

import (
	"fmt"
	"os"
	"encoding/json"
	"github.com/Microsoft/hcsshim"
)

func main() {
	endpointId := os.Args[1]
	source := os.Args[2]
	dest := os.Args[3]

	endpoint, err := hcsshim.GetHNSEndpointByID(endpointId)
	if err != nil {
		fmt.Printf("ERR GetHNSEndpointByID: %+v\n", err.Error())
		os.Exit(1)
	}

	acl := &hcsshim.ACLPolicy{
		Type: hcsshim.ACL,
		Action: hcsshim.Block,
		Direction: hcsshim.Out,
		Protocol: uint16(256),
		LocalAddresses: source,
		RemoteAddresses: dest,
	}
	policy, err := json.Marshal(acl)
	if err != nil {
		fmt.Printf("ERR json.Marshal: %+v\n", err.Error())
		os.Exit(1)
	}
	endpoint.Policies = append(endpoint.Policies, policy)
	_, err = endpoint.Update()
	if err != nil {
		fmt.Printf("ERR endpoint.Update: %+v\n", err.Error())
		os.Exit(1)
	}
	fmt.Printf("Updated endpoint to BLOCK %s from %s\n", dest, source)
}

Simple WebServer in powershell for container-1:

$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://+:8080/")
$listener.Start()
Write-Host "Listening on http://+:8080/"

while ($listener.IsListening) {
    $context = $listener.GetContext()
    $request = $context.Request
    $response = $context.Response

    $response.ContentType = "text/plain"
    $response.StatusCode = 200
    $response.StatusDescription = "OK"

    $output = $response.OutputStream
    $writer = New-Object System.IO.StreamWriter($output)
    $writer.Write("Hello from PowerShell Server!")
    $writer.Flush()
    $writer.Close()
}
$listener.Stop()

Activity

  1. github-actions commented on Oct 2, 2025

    @github-actions

    Thank you for creating an Issue. Please note that GitHub is not an official channel for Microsoft support requests. To create an official support request, please open a ticket here. Microsoft and the GitHub Community strive to provide a best effort in answering questions and supporting Issues on GitHub.

  2. winkingturtle-vmw commented on Oct 2, 2025

    @winkingturtle-vmw
    Author

    Nicole Trappe (@ntrappe-msft) Can you please triage this issue ? This is quite an important issue with Windows Server 2025.

  3. winkingturtle-vmw commented on Nov 7, 2025

    @winkingturtle-vmw
    Author

    Pinging to keep this issue open

  4. winkingturtle-vmw commented on Dec 3, 2025

    @winkingturtle-vmw
    Author

    Pining to keep this issue open

  5. winkingturtle-vmw commented on Jan 12, 2026

    @winkingturtle-vmw
    Author

    Pinging to keep this issue open

  6. winkingturtle-vmw commented on Feb 17, 2026

    @winkingturtle-vmw
    Author

    Pinging to keep this issue open

  7. winkingturtle-vmw commented on Mar 23, 2026

    @winkingturtle-vmw
    Author

    Pinging to keep this issue open

  8. sureshbabu-msft commented on Apr 16, 2026

    @sureshbabu-msft

    Hi Amin Jamali (@winkingturtle-vmw), thank you for reporting this issue and for your patience.

    We attempted to reproduce the issue by following your steps on Windows Server 2025, but the ACLPolicy for HNSEndpoint worked as expected in our environment — outbound traffic was successfully blocked after applying the policy.

    To help us investigate further, could you please share the following details about your environment?

    1. Host OS build number — output of [System.Environment]::OSVersion.Version or winver on the host machine
    2. Latest cumulative updates on the host — output of Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5
    3. Docker version — output of docker version
    4. Container base image — exact image tag being used (e.g., mcr.microsoft.com/windows/servercore:ltsc2025)
    5. Container isolation mode — please confirm if you are using process isolation or Hyper-V isolation
    6. HNS endpoint details after applying the ACL — after running your acl-policy.exe, please share the output of: Get-HnsEndpoint
      | Where-Object { $_.Id -eq "" } | ConvertTo-Json -Depth 5

    This will help us verify whether the ACL policy was actually persisted on the endpoint, or if the Update() call silently failed
    to apply it.
    7. hcsshim library version — which version of github.com/Microsoft/hcsshim is your acl-policy.exe binary built with?

    This information will help us narrow down whether the issue is tied to a specific host build, patch level, or configuration.
    Thank you!

  9. winkingturtle-vmw commented on Apr 21, 2026

    @winkingturtle-vmw
    Author

    sureshbabu-msft Thank you for following up on this issue. There seems to have been a recent windows patch fix that has fixed the original issue, but have introduced a new issue. The original issue didn't block the traffic when I tested this in Oct 2025, the new issue is that it blocks every IP instead of the just the source. In other words, the current behavior is that once we run the acl-policy.exe it blocks access to all sources, so when creating a new container with a different IP and webserver, I can't curl the new webserver.

    Here is are my build information for the version I am running:

    1. [System.Environment]::OSVersion.Version
    Major  Minor  Build  Revision
    -----  -----  -----  --------
    10     0      26100  0
    
    1. Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5
    Source        Description      HotFixID      InstalledBy          InstalledOn
    ------        -----------      --------      -----------          -----------
    WINDOWS2025   Security Update  KB5082062     NT AUTHORITY\SYSTEM  4/14/2026 12:00:00 AM
    WINDOWS2025   Security Update  KB5082063     NT AUTHORITY\SYSTEM  4/14/2026 12:00:00 AM
    WINDOWS2025   Update           KB5082417     NT AUTHORITY\SYSTEM  4/14/2026 12:00:00 AM
    WINDOWS2025   Security Update  KB5078739                          3/6/2026 12:00:00 AM
    
    1. docker version
    Client:
     Version:           29.4.1
     API version:       1.54
     Go version:        go1.26.2
     Git commit:        055a478
     Built:             Mon Apr 20 16:35:45 2026
     OS/Arch:           windows/amd64
     Context:           default
    
    Server: Docker Engine - Community
     Engine:
      Version:          29.4.1
      API version:      1.54 (minimum version 1.40)
      Go version:       go1.26.2
      Git commit:       6c91b92c
      Built:            Mon Apr 20 16:32:27 2026
      OS/Arch:          windows/amd64
      Experimental:     false
    
    1. mcr.microsoft.com/windows/servercore:ltsc2025 is the image I am using
    2. Process isolation
    3. Get-HNSEndpoint | Where-Object { $_.Id -eq "c489649f-79f7-4319-b37d-985781a512c7" } | ConvertTo-Json -Depth 5
    {
        "ID":  "c489649f-79f7-4319-b37d-985781a512c7",
        "Name":  "Ethernet",
        "Version":  68719476736,
        "AdditionalParams":  {
                                 "NetAdapterInstanceId":  "F5972F6F-01FE-48F7-802C-5C17E235B0C0",
                                 "SwitchId":  "D80BFCEF-8486-4CD9-BFCF-73C1CDB45105",
                                 "SwitchPortId":  "C489649F-79F7-4319-B37D-985781A512C7",
                                 "VmSwitchAdapterName":  "C489649F-79F7-4319-B37D-985781A512C7"
                             },
        "Resources":  {
                          "AdditionalParams":  {
    
                                               },
                          "AllocationOrder":  4,
                          "Allocators":  [
                                             {
                                                 "AdapterNetCfgInstanceId":  "{F5972F6F-01FE-48F7-802C-5C17E235B0C0}",
                                                 "AdditionalParams":  {
    
                                                                      },
                                                 "AllocationOrder":  0,
                                                 "CompartmendId":  2,
                                                 "Connected":  true,
                                                 "DNSFirewallRules":  false,
                                                 "DeviceInstanceID":  "",
                                                 "DevicelessNic":  true,
                                                 "DhcpDisabled":  true,
                                                 "EndpointId":  "C489649F-79F7-4319-B37D-985781A512C7",
                                                 "EndpointNicGuid":  "C489649F-79F7-4319-B37D-985781A512C7",
                                                 "EndpointPortGuid":  "C489649F-79F7-4319-B37D-985781A512C7",
                                                 "Flags":  0,
                                                 "Health":  {
                                                                "LastErrorCode":  0,
                                                                "LastUpdateTime":  134212622679674898
                                                            },
                                                 "ID":  "304085D7-2A19-4BE2-AC5F-ABC193737928",
                                                 "InterfaceGuid":  "F5972F6F-01FE-48F7-802C-5C17E235B0C0",
                                                 "IsPolicy":  false,
                                                "IsolationId":  1,
                                                 "MacAddress":  "00-15-5D-A0-58-79",
                                                 "ManagementPort":  false,
                                                 "NcfHidden":  false,
                                                 "NetworkId":  "D80BFCEF-8486-4CD9-BFCF-73C1CDB45105",
                                                 "NicFriendlyName":  "Ethernet",
                                                 "NlmHidden":  true,
                                                 "PortFriendlyNamePrefix":  "Endpoint Port",
                                                 "PreferredPortFriendlyName":  "C489649F-79F7-4319-B37D-985781A512C7",
                                                 "State":  3,
                                                 "SwitchId":  "D80BFCEF-8486-4CD9-BFCF-73C1CDB45105",
                                                 "Tag":  "Endpoint Port",
                                                 "VmPort":  false,
                                                 "WaitForIpv6Interface":  true,
                                                 "nonPersistentPort":  false
                                             },
                                             {
                                                 "AdditionalParams":  {
    
                                                                      },
                                                 "AllocationOrder":  1,
                                                 "Flags":  0,
                                                 "Health":  {
                                                                "LastErrorCode":  0,
                                                                "LastUpdateTime":  134212622681202458
                                                            },
                                                 "ID":  "E478C6D8-A2D9-4CB3-88FD-45C7DF3BDAF1",
                                                 "InDefaultIsolation":  false,
                                                 "IsPolicy":  true,
                                                 "IsolationID":  1,
                                                 "IsolationType":  3,
                                                 "State":  3,
                                                 "Tag":  "Isolation Policy"
                                             },
                                             {
                                                 "AdditionalParams":  {
    
                                                                      },
                                                 "AllocationOrder":  3,
                                                 "Flags":  0,
                                                 "Health":  {
                                                                "LastErrorCode":  0,
                                                                "LastUpdateTime":  134212623384977311
                                                            },
                                                 "ID":  "38ACC629-F57C-499E-A373-6F73DD596081",
                                                 "IsNetworkACL":  false,
                                                 "IsPolicy":  true,
                                                 "Rules":  [
                                                               {
                                                                   "Action":  "Block",
                                                                   "Direction":  "Out",
                                                                   "LocalAddresses":  "172.22.116.94",
                                                                   "Protocol":  256,
                                                                   "RemoteAddresses":  "172.22.126.87",
                                                                   "Scope":  0,
                                                                   "Type":  "ACL"
                                                               }
                                                           ],
                                                 "State":  3,
                                                 "Tag":  "ACL Policy"
                                             }
                                         ],
                          "CompartmentOperationTime":  0,
                          "Flags":  0,
                          "Health":  {
                                         "LastErrorCode":  0,
                                         "LastUpdateTime":  134212622678257075
                                     },
                          "ID":  "08933AED-A5D8-4B0A-BD60-039919D8B8DF",
                          "PortOperationTime":  0,
                          "State":  1,
                          "SwitchOperationTime":  0,
                          "VfpOperationTime":  0,
                          "parentId":  "E903EE53-3E77-4CA6-893A-D2F7863E7A9E"
                      },
        "State":  2,
        "VirtualNetwork":  "d80bfcef-8486-4cd9-bfcf-73c1cdb45105",
        "VirtualNetworkName":  "nat",
        "Policies":  [
                         {
                             "Action":  "Block",
                             "Direction":  "Out",
                             "LocalAddresses":  "172.22.116.94",
                             "Protocol":  256,
                             "RemoteAddresses":  "172.22.126.87",
                             "Scope":  0,
                             "Type":  "ACL"
                         }
                     ],
        "MacAddress":  "00-15-5D-A0-58-79",
        "EnableInternalDNS":  true,
        "IPAddress":  "172.22.116.94",
        "PrefixLength":  20,
        "GatewayAddress":  "172.22.112.1",
        "IPSubnetId":  "284c4066-4b7c-42cd-8496-7f1bcee71a30",
        "DNSServerList":  "172.22.112.1,10.128.0.1",
        "DNSSuffix":  "c.app-runtime-platform-wg.internal.",
        "SharedContainers":  [
                                 "19887ec3c976f3ce0b1091b90318bc28b9f66a6b6a6a1a51da90f38b5cea5906"
                             ]
    }
    
    1. hcsshim version: v0.14.1
  10. sureshbabu-msft commented on May 11, 2026

    @sureshbabu-msft

    Hi Amin Jamali (@winkingturtle-vmw), thank you for the detailed environment information and for confirming the updated behavior after the April 2026 patches (KB5082062/KB5082063).

    We've investigated this with the networking team and can confirm that this behavior is by design. When any ACL policy is applied on an HNS endpoint, HNS switches the endpoint to a default-deny posture — meaning all outbound traffic not explicitly allowed by an ACL rule gets blocked. So, a lone Block rule effectively results in "block the target + deny everything else," which is the behavior you're observing.

    Workaround

    To achieve the intended behavior (block specific traffic while allowing everything else), you need to add an explicit Allow-all outbound rule alongside your specific Block rule. The Allow-all rule should use a higher Priority number (lower precedence) so the Block rule is evaluated first.

    Here's the updated acl-policy.exe source:

    package main
    
    import (
        "encoding/json"
        "fmt"
        "os"
    
        "github.com/Microsoft/hcsshim"
    )
    
    func main() {
        endpointId := os.Args[1]
        source := os.Args[2]
        dest := os.Args[3]
    
        endpoint, err := hcsshim.GetHNSEndpointByID(endpointId)
        if err != nil {
            fmt.Printf("ERR GetHNSEndpointByID: %+v\n", err.Error())
            os.Exit(1)
        }
    
        // 1. Default Allow-all outbound (Priority 200 = lower precedence, evaluated last)
        allowAll := &hcsshim.ACLPolicy{
            Type:      hcsshim.ACL,
            Action:    hcsshim.Allow,
            Direction: hcsshim.Out,
            Protocol:  uint16(256),
            Priority:  200,
        }
        allowPolicy, _ := json.Marshal(allowAll)
        endpoint.Policies = append(endpoint.Policies, allowPolicy)
    
        // 2. Specific Block rule (Priority 100 = higher precedence, evaluated first)
        block := &hcsshim.ACLPolicy{
            Type:            hcsshim.ACL,
            Action:          hcsshim.Block,
            Direction:       hcsshim.Out,
            Protocol:        uint16(256),
            LocalAddresses:  source,
            RemoteAddresses: dest,
            Priority:        100,
        }
        blockPolicy, _ := json.Marshal(block)
        endpoint.Policies = append(endpoint.Policies, blockPolicy)
    
        _, err = endpoint.Update()
        if err != nil {
            fmt.Printf("ERR endpoint.Update: %+v\n", err.Error())
            os.Exit(1)
        }
        fmt.Printf("Updated endpoint: BLOCK %s->%s, ALLOW all other outbound\n", source, dest)
    }

    Key points:

    • Priority 100 (Block specific) → evaluated first, blocks only the targeted destination
    • Priority 200 (Allow all) → evaluated second, permits all other outbound traffic
    • Lower Priority numbers = higher precedence in VFP

    With both rules applied, traffic to the blocked IP will be denied while traffic to all other destinations will continue to work normally.

    We understand this adds extra configuration overhead, but this is the expected pattern when working with HNS ACL policies — any ACL presence triggers default-deny, requiring explicit Allow rules for permitted traffic.

  11. sureshbabu-msft commented on Jun 2, 2026

    @sureshbabu-msft

    Hi Amin Jamali (@winkingturtle-vmw),

    We're closing this issue as we haven't heard back since our last response on May 11, 2026.

    To summarize the resolution:

    • Original issue (Oct 2025): ACL policies not blocking traffic — this was fixed by subsequent Windows patches (KB5082062/KB5082063).
    • Updated behavior: Once any ACL policy is applied, HNS switches to a default-deny posture, which is by design.
    • Workaround provided: Add an explicit Allow-all outbound rule (higher Priority number) alongside your specific Block rule to permit all other traffic while blocking only the targeted destination.

    If you're still experiencing issues or have further questions, please feel free to reopen this issue and we'll be happy to assist.

    Thank you for your patience and for reporting this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageNew and needs attention

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions