Hmbown/CodeWhale · error · io::Error

Codewhale-owned credential file DACL must grant only one…

Error message

Codewhale-owned credential file DACL must grant only one user

What it means

The library enumerates the DACL with GetExplicitEntriesFromAclW and requires exactly one explicit access entry; anything else (zero entries, or more than one grantee) means some principal other than the single intended user may hold access, so it throws PermissionDenied. This completes the owner-only policy: one owner, one ACE, no inheritance residue.

Solutions

  1. Reset to a single user ACE: `icacls <file> /inheritance:r /grant:r "%USERNAME%:F"` (this drops all other ACEs).
  2. Remove individual extra grantees: `icacls <file> /remove:g Users "Authenticated Users"` until only your account remains.
  3. Inspect with `icacls <file>` or `Get-Acl <file> | Format-List` to see exactly who is granted access.
  4. Avoid GUI sharing dialogs on the credential file; they add extra ACEs.

Example fix

:: before
icacls C:\Users\me\.codewhale\token.json   :: grants me AND BUILTIN\Users
:: after
icacls C:\Users\me\.codewhale\token.json /inheritance:r /grant:r "%USERNAME%:F"
Defensive patterns

Strategy: validation

Validate before calling

// PowerShell pre-check: exactly one user should appear in the ACL
// (Get-Acl $path).Access | Select-Object IdentityReference, AccessControlType

Try / catch

match read_codewhale_owned_to_string(&path) {
    Ok(creds) => use(creds),
    Err(e) if e.to_string().contains("grant only one user") => {
        eprintln!("reset 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 file whose DACL has zero explicit entries or multiple ACEs — e.g. grants to both the user and Administrators/Users, or leftover inherited-style entries flattened onto the file.

Common situations: `icacls /grant` was run twice for different accounts; a sharing wizard granted Users or Authenticated Users read access; the file kept ACEs after a user/profile rename; group policies or folder inheritance added extra grants.

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/0b9db43d1e3840f6. Report an issue: GitHub.

Appendix: source

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

        ));
    }
    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",
        ));
    }
    // SAFETY: `count == 1` proves the first returned entry is initialized.
    let entry = unsafe { &*entries };
    let trustee_sid: PSID = entry.Trustee.ptstrName.cast();
    // SAFETY: form and null checked in this expression; sid owned by `user`.
    let current_user_only = entry.Trustee.TrusteeForm == TRUSTEE_IS_SID
        && !trustee_sid.is_null()
        && unsafe { EqualSid(trustee_sid, user.sid()) } != 0
        && matches!(entry.grfAccessMode, SET_ACCESS | GRANT_ACCESS)
        && entry.grfAccessPermissions == FILE_ALL_ACCESS;
    if !current_user_only {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "Codewhale-owned credential file DACL is not current-user-only",
        ));

View on GitHub (pinned to 73e0f67d83)