Hmbown/CodeWhale · error · io::Error

Codewhale-owned credential file must have an owner-only DACL

Error message

Codewhale-owned credential file must have an owner-only DACL

What it means

After confirming the owner, verify_windows_owner_only_handle requires the file to have a discretionary ACL (DACL) at all. A null DACL means access is governed by inherit-only rules or (in the SE_DACL_PRESENT-less case) that everyone may access the object; the library throws PermissionDenied because an owner-only guarantee cannot be verified without an explicit DACL.

Solutions

  1. Set an explicit owner-only DACL: `icacls <file> /inheritance:r /grant:r "%USERNAME%:F"`.
  2. Recreate the file in place so Windows assigns a default secure DACL.
  3. Verify with `icacls <file>` that an explicit ACL listing only your user exists.
  4. Avoid moving credential files from non-ACL filesystems without resetting permissions.

Example fix

:: before
icacls C:\Users\me\.codewhale\token.json   :: No DACL / inherited nothing
:: after
icacls C:\Users\me\.codewhale\token.json /inheritance:r /grant:r "%USERNAME%:F"
Defensive patterns

Strategy: validation

Validate before calling

// PowerShell pre-check: an explicit DACL must exist
// $acl = Get-Acl $path; $acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]).Count -ge 1

Try / catch

match read_codewhale_owned_to_string(&path) {
    Ok(creds) => use(creds),
    Err(e) if e.to_string().contains("owner-only DACL") => {
        eprintln!("set explicit ACL: icacls {p} /inheritance:r /grant:r %USERNAME%:F", p = path.display());
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_codewhale_owned_to_string opens a credential file whose security descriptor has no DACL / a null DACL — e.g. the ACL was stripped, the file was created on a filesystem without ACL enforcement and later moved, or `icacls /reset` removed explicit entries.

Common situations: File copied from a FAT/exFAT volume (no ACLs) to NTFS; a backup/restore tool dropped the security descriptor; `icacls <file> /remove` removed all ACEs; file was created in a directory whose inheritance produced no explicit 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/d060ed8f98314ec1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/external_credentials.rs:475

            std::ptr::null_mut(),
            &mut dacl,
            std::ptr::null_mut(),
            &mut descriptor,
        )
    };
    if result != ERROR_SUCCESS {
        return Err(io::Error::from_raw_os_error(result as i32));
    }
    let _descriptor = WindowsLocalAllocation(descriptor.cast());
    // SAFETY: `owner` is non-null; `user.sid()` is owned by `user`.
    if owner.is_null() || unsafe { EqualSid(owner, user.sid()) } == 0 {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Codewhale-owned credential file owner is not the current user",
        ));
    }
    if dacl.is_null() {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Codewhale-owned credential file must have an owner-only DACL",
        ));
    }
    let mut count = 0;
    let mut entries: *mut EXPLICIT_ACCESS_W = std::ptr::null_mut();
    // SAFETY: `dacl` is owned by the live security descriptor; Windows
    // allocates the returned entries, released below.
    let result = unsafe { GetExplicitEntriesFromAclW(dacl, &mut count, &mut entries) };
    if result != ERROR_SUCCESS {
        return Err(io::Error::from_raw_os_error(result as i32));
    }
    let _entries = WindowsLocalAllocation(entries.cast());
    if count != 1 || entries.is_null() {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Codewhale-owned credential file DACL must grant only one user",
        ));

View on GitHub (pinned to 73e0f67d83)