{"record":{"id":"fbe3cf7d007f2229","repo":"Hmbown/CodeWhale","slug":"codewhale-issue-report-storage-must-have-an-owner-only-dacl","errorCode":null,"errorMessage":"Codewhale issue-report storage must have an owner-only DACL","messagePattern":"Codewhale issue-report storage must have an owner-only DACL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/github/report.rs","lineNumber":1077,"sourceCode":"                SE_FILE_OBJECT,\n                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 issue-report security descriptor\");\n        }\n        let _descriptor = WindowsLocalAllocation(descriptor.cast());\n        anyhow::ensure!(\n            !owner.is_null() && unsafe { EqualSid(owner, user.sid()) } != 0,\n            \"Codewhale issue-report storage owner is not the current user\"\n        );\n        anyhow::ensure!(\n            !dacl.is_null(),\n            \"Codewhale issue-report 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 issue-report DACL entries\");\n        }\n        let _entries = WindowsLocalAllocation(entries.cast());\n        anyhow::ensure!(\n            count == 1 && !entries.is_null(),\n            \"Codewhale issue-report DACL must grant only one user\"\n        );\n        // SAFETY: `count == 1` proves the first returned entry is initialized.","sourceCodeStart":1059,"sourceCodeEnd":1095,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/github/report.rs#L1059-L1095","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["Delete the storage directory and let Codewhale recreate it with the correct owner-only DACL (cleanest fix).","Restore an owner-only DACL manually: `icacls <storage-path> /inheritance:r /grant:r \"%USERNAME%:F\"` and apply to children.","Re-run the failing operation after fixing the ACL; verification reads a fresh descriptor each time.","Avoid copying the storage with tools that do not preserve ACLs; recreate instead of migrate."],"exampleFix":"// before\n# DACL stripped by sync tool\n# after (cmd)\nicacls \"%LOCALAPPDATA%\\codewhale\\issue-reports\" /inheritance:r /grant:r \"%USERNAME%:F\" /t","handlingStrategy":"validation","validationCode":"# PowerShell pre-check that the storage has a DACL\n$p = \"$env:LOCALAPPDATA\\codewhale\\issue-reports\"\nif (Test-Path $p) {\n  $acl = Get-Acl $p\n  if ($acl.Access.Count -eq 0) {\n    Write-Error \"no DACL entries; run: icacls $p /inheritance:r /grant:r `\"$env:USERNAME`:F`\" /t\"\n  }\n}","typeGuard":"fn storage_has_dacl(path: &std::path::Path) -> bool {\n    std::process::Command::new(\"icacls\")\n        .arg(path)\n        .output()\n        .map(|o| o.status.success() && !String::from_utf8_lossy(&o.stdout).trim().is_empty())\n        .unwrap_or(false)\n}","tryCatchPattern":"match report_tool.verify_and_open() {\n    Ok(handle) => handle,\n    Err(e) if e.to_string().contains(\"must have an owner-only DACL\") => {\n        // DACL missing/stripped: recreate storage with correct ACLs\n        std::fs::remove_dir_all(storage_path)?;\n        report_tool.verify_and_open()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Do not copy or move the storage with tools that drop ACLs; delete and recreate instead.","Avoid 'reset permissions' / 'replace child permissions' operations over the storage path.","Keep the storage on an NTFS volume with normal inheritance rather than exotic filesystems.","Re-verify ACLs after restores or sync-tool runs touching the config area."],"tags":["windows","security","acl"],"backgroundTag":"insufficient-permissions","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"}