{"record":{"id":"99a21a3452b79781","repo":"Hmbown/CodeWhale","slug":"codewhale-owned-xai-oauth-dacl-is-not-current-user","errorCode":null,"errorMessage":"Codewhale-owned xAI OAuth DACL is not current-user-only","messagePattern":"Codewhale-owned xAI OAuth DACL is not current-user-only","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/config/src/xai_credentials.rs","lineNumber":1528,"sourceCode":"    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\n#[cfg(windows)]\nstruct CurrentWindowsUser {\n    token: windows_sys::Win32::Foundation::HANDLE,\n    token_info: Vec<usize>,\n}\n\n#[cfg(windows)]\nimpl CurrentWindowsUser {","sourceCodeStart":1510,"sourceCodeEnd":1546,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L1510-L1546","documentation":"Windows-only check in verify_windows_owner_only_handle validating the single DACL entry itself. The one ACE must be SID-form (TRUSTEE_IS_SID) with a non-null SID equal to the current user's SID, a grant access mode, and FILE_ALL_ACCESS permissions. Any deviation — a named trustee instead of a SID, a different user/group, SET via deny mode, or reduced permissions — fails this check so xAI OAuth tokens are only ever readable/writable by the owning user.","triggerScenarios":"Opening the xAI OAuth store on Windows when the lone ACE grants access to a different principal (another user, a group), uses TRUSTEE_IS_NAME form, uses SET_ACCESS deny semantics, or grants fewer than FILE_ALL_ACCESS — typically after manual icacls edits or ACL restoration by external tools.","commonSituations":"Admin granted access to an IT group; user lowered permissions to 'read-only' thinking that was safer; a permissions-hardening script rewrote the ACE with a named trustee or subset rights; migration between machines changed the SID (new account) so EqualSid fails.","solutions":["Reset the ACE to the current user with full control: icacls \"<path>\" /inheritance:r /grant:r \"%USERNAME%\":F.","If the account SID changed (machine migration, domain re-join), delete the storage and re-run the xAI OAuth login so it is re-provisioned for the new SID.","Remove any hardening script/sync rule that rewrites the ACE, then repair the ACL once.","Report a bug with the icacls output if this occurs on freshly created storage."],"exampleFix":"// before\nicacls xai-store\n//   CORP\\IT-Support:(M)  <-- wrong trustee / reduced rights\n// after\nicacls xai-store /inheritance:r /grant:r \"%USERNAME%\":F","handlingStrategy":"validation","validationCode":"// Verify the single ACE grants the current user full control via SID\nlet output = Command::new(\"icacls\").arg(&path).output()?;\n// Expect exactly one line of the form:\n//   <MACHINE>\\<user>:(F)   and no (RX)/(M)/group entries\nprintln!(\"{}\", String::from_utf8_lossy(&output.stdout));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Grant permissions by current user only, with full control (F); read-only grants will be rejected.","After a machine migration or domain re-join (SID change), re-authenticate instead of copying the store.","Keep ACL-hardening scripts from rewriting the ACE to named trustees or reduced rights."],"tags":["windows","security","dacl","sid","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"}