Hmbown/CodeWhale · error

Codewhale issue-report storage must have an owner-only DACL

Error message

Codewhale issue-report storage must have an owner-only DACL

What it means

As part of the same Windows hardening pass, `verify_windows_owner_only_handle` requires the storage's security descriptor to carry a DACL, and the surrounding code requires it to be an owner-only DACL. `anyhow::ensure!(!dacl.is_null(), ...)` throws when the descriptor has no DACL at all. Without a DACL the storage's access policy is undefined/inheritable, defeating the owner-only isolation guarantee.

Solutions

  1. Delete the storage directory and let Codewhale recreate it with the correct owner-only DACL (cleanest fix).
  2. Restore an owner-only DACL manually: `icacls <storage-path> /inheritance:r /grant:r "%USERNAME%:F"` and apply to children.
  3. Re-run the failing operation after fixing the ACL; verification reads a fresh descriptor each time.
  4. Avoid copying the storage with tools that do not preserve ACLs; recreate instead of migrate.

Example fix

// before
# DACL stripped by sync tool
# after (cmd)
icacls "%LOCALAPPDATA%\codewhale\issue-reports" /inheritance:r /grant:r "%USERNAME%:F" /t
Defensive patterns

Strategy: validation

Validate before calling

# PowerShell pre-check that the storage has a DACL
$p = "$env:LOCALAPPDATA\codewhale\issue-reports"
if (Test-Path $p) {
  $acl = Get-Acl $p
  if ($acl.Access.Count -eq 0) {
    Write-Error "no DACL entries; run: icacls $p /inheritance:r /grant:r `"$env:USERNAME`:F`" /t"
  }
}

Type guard

fn storage_has_dacl(path: &std::path::Path) -> bool {
    std::process::Command::new("icacls")
        .arg(path)
        .output()
        .map(|o| o.status.success() && !String::from_utf8_lossy(&o.stdout).trim().is_empty())
        .unwrap_or(false)
}

Try / catch

match report_tool.verify_and_open() {
    Ok(handle) => handle,
    Err(e) if e.to_string().contains("must have an owner-only DACL") => {
        // DACL missing/stripped: recreate storage with correct ACLs
        std::fs::remove_dir_all(storage_path)?;
        report_tool.verify_and_open()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the issue-report tool on Windows when the storage's security descriptor lacks a DACL — typically because the file was created without explicit ACLs on a volume without inheritance, by a tool that copied the object but dropped the DACL, or after an ACL was cleared with `icacls /remove`.

Common situations: A backup/restore or file-sync tool stripped the DACL; the storage was moved between volumes with different inheritance semantics; an admin reset permissions with 'replace all child permissions' leaving a null DACL.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/tools/github/report.rs:1077

                SE_FILE_OBJECT,
                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 issue-report security descriptor");
        }
        let _descriptor = WindowsLocalAllocation(descriptor.cast());
        anyhow::ensure!(
            !owner.is_null() && unsafe { EqualSid(owner, user.sid()) } != 0,
            "Codewhale issue-report storage owner is not the current user"
        );
        anyhow::ensure!(
            !dacl.is_null(),
            "Codewhale issue-report 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 issue-report DACL entries");
        }
        let _entries = WindowsLocalAllocation(entries.cast());
        anyhow::ensure!(
            count == 1 && !entries.is_null(),
            "Codewhale issue-report DACL must grant only one user"
        );
        // SAFETY: `count == 1` proves the first returned entry is initialized.

View on GitHub (pinned to 73e0f67d83)