{"record":{"id":"b24a078e95765c0d","repo":"Hmbown/CodeWhale","slug":"codewhale-owned-xai-oauth-dacl-must-grant-only-one","errorCode":null,"errorMessage":"Codewhale-owned xAI OAuth DACL must grant only one user","messagePattern":"Codewhale-owned xAI OAuth DACL must grant only one user","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/config/src/xai_credentials.rs","lineNumber":1520,"sourceCode":"    anyhow::ensure!(\n        !owner.is_null() && unsafe { EqualSid(owner, user.sid()) } != 0,\n        \"Codewhale-owned xAI OAuth storage owner is not the current user\"\n    );\n    anyhow::ensure!(\n        !dacl.is_null(),\n        \"Codewhale-owned xAI OAuth storage must have an owner-only DACL\"\n    );\n    let mut count = 0;\n    let mut entries: *mut EXPLICIT_ACCESS_W = std::ptr::null_mut();\n    // SAFETY: `dacl` belongs to the live descriptor; Windows allocates the\n    // returned entry array, released by the guard below.\n    let result = unsafe { GetExplicitEntriesFromAclW(dacl, &mut count, &mut entries) };\n    if result != ERROR_SUCCESS {\n        return Err(std::io::Error::from_raw_os_error(result as i32))\n            .context(\"reading Codewhale-owned xAI OAuth DACL entries\");\n    }\n    let _entries = WindowsLocalAllocation(entries.cast());\n    anyhow::ensure!(\n        count == 1 && !entries.is_null(),\n        \"Codewhale-owned xAI OAuth DACL must grant only one user\"\n    );\n    // SAFETY: `count == 1` proves the first returned entry is initialized.\n    let entry = unsafe { &*entries };\n    let trustee_sid: PSID = entry.Trustee.ptstrName.cast();\n    // SAFETY: form and null checked in this expression; sid owned by `user`.\n    anyhow::ensure!(\n        entry.Trustee.TrusteeForm == TRUSTEE_IS_SID\n            && !trustee_sid.is_null()\n            && unsafe { EqualSid(trustee_sid, user.sid()) } != 0\n            && matches!(entry.grfAccessMode, SET_ACCESS | GRANT_ACCESS)\n            && entry.grfAccessPermissions == FILE_ALL_ACCESS,\n        \"Codewhale-owned xAI OAuth DACL is not current-user-only\"\n    );\n    Ok(())\n}\n","sourceCodeStart":1502,"sourceCodeEnd":1538,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L1502-L1538","documentation":"Windows-only check in verify_windows_owner_only_handle. After confirming a DACL exists, the store enumerates its ACEs with GetExplicitEntriesFromAclW and requires exactly one entry. A DACL with zero entries (allow-nothing), multiple entries, or an unpopulated entries pointer does not match the owner-only contract, so the xAI OAuth storage is rejected. This prevents secret access being shared with groups or other principals.","triggerScenarios":"Opening the xAI OAuth store (open_owned_credentials_directory / validate_owned_file_handle) or running windows_store_secures_every_new_owned_object_for_the_current_user, when the object's DACL contains any number of ACEs other than exactly one — e.g. ACLs set by icacls with extra group grants, inherited entries merged in, or an empty DACL.","commonSituations":"A user or admin tool widened the ACL ('Authenticated Users', 'Administrators' added); a sync client restored an ACL with multiple ACEs; an empty DACL was applied by a cleanup script (deny-all storage).","solutions":["Delete the storage directory and let Codewhale recreate it with its owner-only DACL, then re-authenticate the xAI OAuth flow.","Fix the ACL manually to a single grant: icacls \"<path>\" /inheritance:r /grant:r \"%USERNAME%\":F (removing all other ACEs).","Identify the tool that added the extra ACEs and exclude the credentials path from its management.","If it fires on newly provisioned storage, this is a provisioning bug in the app — report it with icacls output for the path."],"exampleFix":"// before\nicacls xai-store\n//   BUILTIN\\Administrators:(F)\n//  Authenticated Users:(RX)\n// after\nicacls xai-store /inheritance:r /grant:r \"%USERNAME%\":F","handlingStrategy":"validation","validationCode":"// Before opening the store, count ACEs and require exactly one grant\n// icacls <path> /verify  (or parse GetExplicitEntriesFromAclW in-process)\n// Any output listing more than one principal means the store will refuse.\nlet output = Command::new(\"icacls\").arg(&path).arg(\"/verify\").output()?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not add group or service ACEs to the credentials directory; the contract is exactly one current-user grant.","After restoring from backup, reset the ACL to a single grant before launching the app.","Check the ACL after any third-party security tool runs on the profile directory."],"tags":["windows","security","dacl","acl","oauth"],"backgroundTag":"permission-denied","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}