{"record":{"id":"7981e0cc0912adad","repo":"Hmbown/CodeWhale","slug":"codewhale-owned-xai-oauth-storage-must-have-an-own","errorCode":null,"errorMessage":"Codewhale-owned xAI OAuth storage must have an owner-only DACL","messagePattern":"Codewhale-owned xAI OAuth storage must have an owner-only DACL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/config/src/xai_credentials.rs","lineNumber":1506,"sourceCode":"            OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,\n            &mut owner,\n            std::ptr::null_mut(),\n            &mut dacl,\n            std::ptr::null_mut(),\n            &mut descriptor,\n        )\n    };\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 security descriptor\");\n    }\n    let _descriptor = WindowsLocalAllocation(descriptor.cast());\n    // SAFETY: `owner` is non-null; `user.sid()` is owned by `user`.\n    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.","sourceCodeStart":1488,"sourceCodeEnd":1524,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/xai_credentials.rs#L1488-L1524","documentation":"Windows-only security check in verify_windows_owner_only_handle (crates/config/src/xai_credentials.rs). After opening the xAI OAuth storage object, Codewhale reads its security descriptor and requires a non-null owner plus a DACL; if the DACL pointer is null the handle's security is unverifiable as owner-only, so the store refuses to use it. This guards secret material from being stored on an object with no access-control list (which on Windows can default to inherited/unrestricted access).","triggerScenarios":"Calling XaiOAuthCredentialStore::open (via open_owned_credentials_directory), validate_owned_file_handle, or the test windows_store_secures_every_new_owned_object_for_the_current_user on Windows when GetSecurityInfo returns a descriptor whose DACL is NULL — e.g. the directory/file was created outside the crate's owner-only provisioning (copied from a backup, created by another tool, or DACL inheritance stripped).","commonSituations":"A user restores the Codewhale credentials directory from a backup or sync tool (Dropbox/OneDrive) that recreated it without the original ACL; a corporate profile-cleanup tool resets ACLs; a previous version of the app or a manual mkdir created the path without the owner-only DACL.","solutions":["Delete the existing xAI OAuth storage directory (e.g. the codewhale xai credentials folder under the user profile) and let the app recreate and re-provision it, then log in again.","Reset the ACL explicitly: icacls \"<path>\" /reset, or remove inheritance with /inheritance:r and grant only the current user full control.","Check that no external sync/AV tool is rewriting ACLs on the profile directory; exclude the credentials path.","If this fires on freshly created storage, report a bug: object provisioning failed to attach the owner-only DACL."],"exampleFix":"// before: directory copied from backup has a NULL DACL\n// icacls \"%APPDATA%\\codewhale\\xai\" -> DACL absent\n// after\nicacls \"%APPDATA%\\codewhale\\xai\" /inheritance:r /grant:r \"%USERNAME%\":F","handlingStrategy":"validation","validationCode":"// Windows, before opening the store\nuse windows::Win32::Security::GetNamedSecurityInfoW;\nlet psd = GetNamedSecurityInfoW(&path_as_wide, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, None, None, None, None, None)?;\nlet dacl = psd.dacl();\nif dacl.is_none() {\n    // repair: remove inheritance, grant current user full control\n    run(\"icacls <path> /inheritance:r /grant:r %USERNAME%:F\")?;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never copy or restore the credentials directory with sync/backup tools that drop ACLs; re-create it via the app instead.","Exclude the credentials path from corporate ACL-hardening and profile-cleanup scripts.","Verify with icacls after any manual ACL surgery that the DACL is a single current-user grant."],"tags":["windows","security","dacl","oauth","credentials"],"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"}