Hmbown/CodeWhale · critical

Codewhale-owned xAI OAuth storage must have an owner-only…

Error message

Codewhale-owned xAI OAuth storage must have an owner-only DACL

What it means

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).

Solutions

  1. 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.
  2. Reset the ACL explicitly: icacls "<path>" /reset, or remove inheritance with /inheritance:r and grant only the current user full control.
  3. Check that no external sync/AV tool is rewriting ACLs on the profile directory; exclude the credentials path.
  4. If this fires on freshly created storage, report a bug: object provisioning failed to attach the owner-only DACL.

Example fix

// before: directory copied from backup has a NULL DACL
// icacls "%APPDATA%\codewhale\xai" -> DACL absent
// after
icacls "%APPDATA%\codewhale\xai" /inheritance:r /grant:r "%USERNAME%":F
Defensive patterns

Strategy: validation

Validate before calling

// Windows, before opening the store
use windows::Win32::Security::GetNamedSecurityInfoW;
let psd = GetNamedSecurityInfoW(&path_as_wide, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, None, None, None, None, None)?;
let dacl = psd.dacl();
if dacl.is_none() {
    // repair: remove inheritance, grant current user full control
    run("icacls <path> /inheritance:r /grant:r %USERNAME%:F")?;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/7981e0cc0912adad. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/xai_credentials.rs:1506

            OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
            &mut owner,
            std::ptr::null_mut(),
            &mut dacl,
            std::ptr::null_mut(),
            &mut descriptor,
        )
    };
    if result != ERROR_SUCCESS {
        return Err(std::io::Error::from_raw_os_error(result as i32))
            .context("reading Codewhale-owned xAI OAuth security descriptor");
    }
    let _descriptor = WindowsLocalAllocation(descriptor.cast());
    // SAFETY: `owner` is non-null; `user.sid()` is owned by `user`.
    anyhow::ensure!(
        !owner.is_null() && unsafe { EqualSid(owner, user.sid()) } != 0,
        "Codewhale-owned xAI OAuth storage owner is not the current user"
    );
    anyhow::ensure!(
        !dacl.is_null(),
        "Codewhale-owned xAI OAuth storage must have an owner-only DACL"
    );
    let mut count = 0;
    let mut entries: *mut EXPLICIT_ACCESS_W = std::ptr::null_mut();
    // SAFETY: `dacl` belongs to the live descriptor; Windows allocates the
    // returned entry array, released by the guard below.
    let result = unsafe { GetExplicitEntriesFromAclW(dacl, &mut count, &mut entries) };
    if result != ERROR_SUCCESS {
        return Err(std::io::Error::from_raw_os_error(result as i32))
            .context("reading Codewhale-owned xAI OAuth DACL entries");
    }
    let _entries = WindowsLocalAllocation(entries.cast());
    anyhow::ensure!(
        count == 1 && !entries.is_null(),
        "Codewhale-owned xAI OAuth DACL must grant only one user"
    );
    // SAFETY: `count == 1` proves the first returned entry is initialized.

View on GitHub (pinned to 73e0f67d83)