{"record":{"id":"f1cb0b8bdd4b5770","repo":"astrid-runtime/astrid","slug":"named-pipe-dacl-has-ace-count-entries-expected","errorCode":null,"errorMessage":"named-pipe DACL has {ace_count} entries; expected exactly {expected_aces}","messagePattern":"named-pipe DACL has (.+?) entries; expected exactly (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/local_transport/windows.rs","lineNumber":943,"sourceCode":"            \"named-pipe has a null or missing DACL\",\n        ));\n    }\n\n    // SAFETY: `dacl` points into the descriptor allocation returned by\n    // GetSecurityInfo, which remains live and unmodified through\n    // `descriptor_allocation`. The parser validates and bounds the ACL before\n    // exposing any borrowed ACE or SID.\n    let dacl = unsafe {\n        ValidatedAcl::from_raw(\n            dacl,\n            &descriptor_allocation,\n            \"named-pipe security descriptor\",\n        )\n    }?;\n    let expected_aces = if current.equals(&system) { 1 } else { 2 };\n    let ace_count = dacl.ace_count();\n    if ace_count != expected_aces {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            format!(\"named-pipe DACL has {ace_count} entries; expected exactly {expected_aces}\"),\n        ));\n    }\n\n    let mut saw_current = false;\n    let mut saw_system = current.equals(&system);\n    for index in 0..ace_count {\n        let ValidatedAce::Allow { flags, mask, sid } = dacl.ace(index)? else {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"named-pipe DACL contains a non-canonical access entry\",\n            ));\n        };\n        if flags != 0 || !is_canonical_pipe_full_control(mask) {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"named-pipe DACL contains a non-canonical access entry\",","sourceCodeStart":925,"sourceCodeEnd":961,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/local_transport/windows.rs#L925-L961","documentation":"The library validates that the pipe's DACL contains exactly the canonical set of ACEs: one allow entry for the current user, plus one for Local System (or just one total when running as Local System). This error means the DACL has a different number of entries (actual count is in the message alongside the expected count), so the pipe's ACL was not created by this library's canonical setup and its access policy cannot be trusted.","triggerScenarios":"connect()/accept() on a pipe whose DACL ace_count differs from expected_aces (1 for SYSTEM, 2 otherwise), e.g. extra allow/deny ACEs added after pipe creation.","commonSituations":"An administrator or group policy added extra ACEs to the pipe; a parent process passed an inheritable ACL that merged in additional entries; the pipe was created by another tool sharing the same name; running the server as SYSTEM but the ACE count logic sees both SIDs equal and expects 1 while the ACL has 2.","solutions":["Delete the pipe and let the library recreate it so the canonical 1-or-2-entry DACL is installed.","Compare the ACL with `Get-Acl \\\\.\\pipe\\<name>` and remove non-canonical ACEs (extra users/groups) from whatever process creates the pipe.","Check that the process creating the pipe does not apply a custom SECURITY_ATTRIBUTES or inherited ACL; pass SE_DACL_PROTECTED with explicit ACEs only.","If you intentionally added ACEs for other principals, that is unsupported — use a separate pipe or socket variant that permits shared access."],"exampleFix":"// before: adding a group ACE to the pipe DACL\nSetNamedSecurityInfoW(handle, ..., dacl_with_extra_group_ace, ...);\n// after: leave the DACL exactly as the library created it (current user + SYSTEM only)\n// Do not call SetNamedSecurityInfo / icacls on the pipe after creation.","handlingStrategy":"validation","validationCode":"// Preflight in PowerShell: count access rules on the pipe\n// (Get-Acl \\\\.\\pipe\\myapp).Access.Count  # must be 1 (SYSTEM) or 2 (user + SYSTEM)","typeGuard":null,"tryCatchPattern":"if let Err(e) = connect() {\n    if e.kind() == std::io::ErrorKind::PermissionDenied && e.to_string().contains(\"expected exactly\") {\n        // extra ACEs were added after creation; recreate the pipe\n        recreate_pipe()?;\n    } else { return Err(e.into()); }\n}","preventionTips":["Do not modify pipe ACLs after creation (no icacls/SetNamedSecurityInfo on the pipe)","Exclude the pipe path from GPO/hardening ACL policies","Recreate the pipe rather than editing its DACL when principals must change","Confirm creator and consumer run as the same user so only canonical ACEs are needed"],"tags":["windows","named-pipes","acl","security","ipc"],"backgroundTag":"permission-denied","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}