Repository navigation
Description
Version
codebase-memory-mcp 0.9.1-rc.1
Platform
Windows (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
When I tested v0.9.1-rc.1 against an existing local CBM installation and cache on Windows, the new daemon/ACL path left existing cache files with an empty ACL. The same Windows user could no longer read _config.db, log files, or sampled project database files.
After that, MCP requests such as search_graph and check_index_coverage did not complete within 15–20 seconds. Starting the RC daemon again reproduced the empty ACL on _config.db.
I expected the installer/daemon either to preserve usable file permissions or to fail safely before changing existing permissions. It must not leave the current user's cache unreadable.
Reproduction
Prerequisite: Windows x64 with an existing CBM installation/cache. The existing installation or one of its parent directories has inherited cross-account ACL entries.
- Back up the existing CBM executable, configuration, and cache.
- Verify the official v0.9.1-rc.1 Windows amd64 release archive and run install.ps1 with --skip-config against the existing installation.
- The installer rejects the existing installation directory with: acl-grants-cross-account-mutation
- Retry with an owner-private temporary directory. Activation then aborts with: active CBM sessions and operations could not be stopped safely; no activation was committed.
- Place the checksum-verified RC binary in a user-private directory without changing agent configuration, then start the daemon: codebase-memory-mcp.exe daemon start
- Observe: daemon.runtime_config_open_failed reason=config_db_unavailable
- Inspect the existing CBM cache: _config.db, logs, and sampled project .db files have an empty ACL and are unreadable by the current user.
- Start the RC daemon again; the empty ACL on _config.db is reproduced.
- On an already-indexed 31-node project, check_index_coverage did not complete within 15 seconds through the CLI. Small MCP search_graph and check_index_coverage calls did not complete within 15–20 seconds.
No repository source code is required for this reproduction; the failure occurs before source analysis.
Logs
activation transaction I/O failed: acl-grants-cross-account-mutation
active CBM sessions and operations could not be stopped safely; no activation was committed.
daemon.runtime_config_open_failed reason=config_db_unavailable
Diagnostics trajectory (memory / performance / leak issues)
CBM_DIAGNOSTICS=1 was enabled. The daemon created a local trajectory.ndjson file. I have not attached it because it may contain local environment details; I can provide a redacted extract if needed.
Project scale (if relevant)
31 nodes / small existing project / failure occurs before source analysis
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
ertankucukoglu commentedon Jul 30, 2026
Here is the issue log in zip format.
cbm-v0.9.1-rc.1-windows-acl-report.zip
hetasshi commentedon Jul 30, 2026
I can reproduce the same issue on Windows x64 with the v0.9.1-rc.1 UI build.
Existing stable installation with 6 indexes in:
C:\Users\user.cache\codebase-memory-mcp
The archive downloaded, checksum verification passed, extraction passed, and the candidate binary was verified successfully. Installation then failed during activation with:
activation transaction I/O failed: acl-grants-cross-account-mutation to S-1-5-21-398329113-3246588833-669947113-1583456143 (os 0)
Temporary path:
C:\Users\user\AppData\Local\Temp\cbm-install-132218333
The installer reported that all 6 existing indexes were being kept. I did not attempt to bypass the ACL check or start the RC daemon afterward.
docs(hqm): vanilla is level with DeusData; record the gate's first ov…
Merge the full upstream range into Merged (d698db8) — QUARANTINED, do…
docs(hqm): close the corpus gaps from this session — B10, the hardeni…
DeusData commentedon Aug 3, 2026
Thank you for the independent Windows x64 reproduction and the ACL evidence. Current main contains explicit private-DACL creation, validation, and repair paths for daemon/cache files, so an RC run that leaves existing cache files with an empty ACL is a direct security and availability regression in that lifecycle. I have routed this at high priority in
0.9.1-rc; this should block a stable promotion until the existing-cache upgrade case is deterministic and green on Windows.DeusData commentedon Aug 4, 2026
Update from a reproduction attempt on our Windows VM: the refusal half of your report reproduces exactly — a cache tree with cross-account mutation grants (your
acl-grants-cross-account-mutation, our stockAuthenticated Users:(M)drive-root inheritance) makes the daemon fail closed and activation abort, and #1449 now surfaces that with the flagged SID/path instead of the misleading sessions error.What we could not reproduce so far is the destructive half —
_config.dbending up with an empty ACL. In every variant we ran (daemon start against a pre-existing tainted cache, failed install, failed uninstall), existing files kept their owner-usable ACLs; the fail-closed paths never touched them. To pin down your empty-ACL rewrite, could you share:icacls _config.dboutput from a pre-upgrade backup (what the 0.9.0-era baseline looked like — especially whether the entries were inherited or explicit);install.ps1ran from an elevated PowerShell (admin token changes which repair branch runs);icaclsof the cache directory itself in the broken state, and whether the empty-ACL files were only ever touched by the RC daemon or also by the aborted installer run.With that we can reproduce the exact repair branch that wrote the empty DACL and bind it with a test. The fail-before-mutate principle you stated — never change existing permissions before validation has passed — is exactly the contract we want to enforce once we can observe the violation.
ertankucukoglu commentedon Aug 4, 2026
Thanks for checking this. I retained the pre-RC backup, the session transcript, and both damaged cache trees.
1. Pre-upgrade ACL baseline
One caveat: I did not capture a literal icacls _config.db output before running the RC. The backup was created with:
robocopy /E /COPY:DAT /DCOPY:DAT
Therefore, its security descriptor was not copied; the backup file inherited permissions from the backup destination.
The backup copy currently reports:
C:\Users\Ertan.cache\codebase-memory-mcp-backups\pre-v0.9.1-rc.1-20260730-064940\cache_config.db
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
DESKTOP-PQ5VAR4\Ertan:(I)(F)
Successfully processed 1 files; Failed processing 0 files
A live Get-Acl capture taken before any RC installer or daemon run exists for the original cache logs directory:
Path: C:\Users\Ertan.cache\codebase-memory-mcp\logs
Owner: DESKTOP-PQ5VAR4\Ertan
NT AUTHORITY\SYSTEM FullControl Allow IsInherited=True
BUILTIN\Administrators FullControl Allow IsInherited=True
DESKTOP-PQ5VAR4\Ertan FullControl Allow IsInherited=True
So the pre-RC cache tree was using inherited, owner-usable ACLs. However, I cannot claim that the backup copy
preserved the original _config.db DACL byte-for-byte.
2. Was install.ps1 elevated?
No. It was run from the normal, non-elevated Codex PowerShell session without RunAs:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File \install.ps1 --skip-config
ExecutionPolicy Bypass was only used to run the downloaded script; it was not an elevation.
3. Broken cache ACL
The second damaged tree is still preserved. It was renamed in place on the same NTFS volume rather than copied.
The cache directory itself reports:
C:\Users\Ertan.cache\codebase-memory-mcp-rc-daemon-acl-damaged-20260730-070935
DESKTOP-PQ5VAR4\Ertan:(F)
Successfully processed 1 files; Failed processing 0 files
The _config.db output contains no ACEs:
C:\Users\Ertan.cache\codebase-memory-mcp-rc-daemon-acl-damaged-20260730-070935_config.db
Successfully processed 1 files; Failed processing 0 files
The logs directory also contains no ACEs:
C:\Users\Ertan.cache\codebase-memory-mcp-rc-daemon-acl-damaged-20260730-070935\logs
Successfully processed 1 files; Failed processing 0 files
Get-Acl confirms the following:
Cache root:
Owner: DESKTOP-PQ5VAR4\Ertan
AreAccessRulesProtected: True
AccessRuleCount: 1
SDDL: O:S-1-5-21-1998637452-353888612-3906549841-1001G:S-1-5-21-1998637452-353888612-3906549841-513D:PAI(A;;FA;;;S-
1-5-21-1998637452-353888612-3906549841-1001)
_config.db:
Owner: DESKTOP-PQ5VAR4\Ertan
AreAccessRulesProtected: False
AccessRuleCount: 0
SDDL: O:S-1-5-21-1998637452-353888612-3906549841-1001G:S-1-5-21-1998637452-353888612-3906549841-513D:AI
logs:
Owner: DESKTOP-PQ5VAR4\Ertan
AreAccessRulesProtected: False
4. Was the installer involved in the empty-ACL rewrite?
The first damaged tree followed both the aborted installer attempts and RC startup, so that first occurrence alone
cannot exclude the installer.
At inspection time, these two RC processes were visible:
52220 codebase-memory-mcp.exe daemon start
59184 codebase-memory-mcp.exe
The second process was likely the MCP client/frontend. Therefore, I can rule out the aborted installer as the cause of
the second occurrence, although the saved evidence does not distinguish the daemon-internal path from the concurrently
present RC client process.
DeusData commentedon Aug 4, 2026
Follow-up on the policy question this report raised: the permission gate stays strict — CBM will continue to refuse trees carrying cross-account mutation grants rather than operate under them, which matches the fail-safe expectation you stated ("fail safely before changing existing permissions"). What improves in the next build is the diagnosis: the refusal now names the check, the SID and the path (#1449), instead of the misleading "active CBM sessions" error you hit at step 4.
The empty-ACL half of your report is still open and still the part I most want to pin down — it did not reproduce on our Windows VM in any variant (daemon start against a pre-existing tainted cache, failed install, failed uninstall all left existing files owner-readable). The questions from my previous comment still stand whenever you have time: the pre-upgrade
icacls _config.dbbaseline, whether install.ps1 ran elevated, and theicaclsof the cache directory in the broken state. With those three we can reproduce the exact repair branch that wrote the empty DACL and bind it with a regression test.Thanks again for the precise step-by-step — it's what made the refusal half diagnosable.
ertankucukoglu commentedon Aug 5, 2026
Thanks for the follow-up. I repeated the Windows x64 v0.9.1-rc.1 test on 2026-08-05 with fresh, independent copies of the preserved pre-RC cache and timestamped ACL/process watchers. The empty-ACL issue reproduced again.
Direct answers to the three questions
1. Exact pre-RC
_config.dbbaselineImmediately before each RC1 process,
_config.dbwas readable and had three inherited full-control ACEs:Recorded properties:
2. Was
install.ps1elevated?No. It ran from a normal, non-elevated PowerShell token. The run recorded
WindowsPrincipal.IsInRole(Administrator) = False; there was no UAC elevation.3. Broken cache directory ACL
After reproduction, the cache directory itself was still owner-readable but had become protected and contained only one non-inheritable ACE:
Recorded properties:
The important detail is that this ACE has no
(OI)or(CI)inheritance flags. Existing child objects then had no usable ACEs. For_config.dbafter reproduction:icacls <scenario-cache>\_config.dbprinted the path with no ACE lines.Retest matrix
daemon startinstall.ps1, cross-account/tainted install targetinstall.ps1, owner-private install targetThe four failed sampled targets in each reproducing scenario were
_config.db,logs, a small existing project database, and the largest existing project database. All four became unreadable withRuleCount=0and DACL SDDL ending inD:AI.Timing and process correlation
2026-08-05T07:44:46.4920873+03:00, while the top-leveldaemon startprocess (PID 89632) was running. This was before its internal permanent-daemon child was created.2026-08-05T07:46:01.6409509+03:00, while the top-level RC1 MCP process (PID 92604) was running and just before its internal daemon child appeared.2026-08-05T07:50:22.1554540+03:00, while the inner RC1install -y --force ... --skip-configprocess (PID 75068) was running.The tainted-target installer path behaved as intended and failed before mutation with:
The owner-private installer path reached the inner installer, changed the isolated cache ACLs, and then reported:
One additional observation: in both installer scenarios,
install.ps1itself exited with code 0 even though the inner RC1 installer printederror: installation failed (exit code 1).Test controls and data safety
7D82CA2FAEDCE72113B17F749A63A11860B9D78CBFC73691FAB9B31CB7E71C22.CBM_DIAGNOSTICS=1andCBM_LOG_LEVEL=debugwere set. RC1 did not emit a new nativetrajectory.ndjsonin the isolated scenario TEMP/evidence directories during this retest, so I used an independent English NDJSON watcher instead. It recorded local/UTC timestamps, monotonic elapsed time, ACL/readability transitions every 100 ms, and CBM PID/parent/command-line state every 500 ms.I have a redacted evidence bundle containing the timeline, watcher NDJSON, exact pre/post
icaclsand SDDL captures, stdout/stderr, and binary hashes. I can attach it if it would help with the regression test; raw databases remain local.Kiborgik commentedon Aug 11, 2026
Root cause: AddAccessAllowedAce cannot express inheritance flags
I reproduced the empty-DACL half deterministically on Windows 11 (26200) with the 0.10.0 stable release, non-elevated standard token, and traced it to source.
Reproduction
I repaired the cache root to a healthy state, then ran a single MCP client spawn that failed to connect:
Four ACEs were deleted (SYSTEM, Administrators, a local group, and the inherited user ACE), leaving one non-inheritable ACE on a protected DACL. Children created after that point get an empty DACL, which is the RuleCount=0 state reported earlier in this thread for
_config.db,logs, and project databases.The client exited with
Connection closedand rewrote the tree anyway, so the mutation happens on a run that never activates. That explains why it reproduces fromdaemon start, from the MCP client, and from an aborted install.The defect
src/daemon/ipc.c:3664builds the owner-only ACL like this:AddAccessAllowedAcehas no AceFlags parameter, so it can only emit an ACE with flags set to zero. The resolver atipc.c:3615binds the non-Ex variant by name. This ACL cannot carryCONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACEat all.It is then applied to directories together with
PROTECTED_DACL_SECURITY_INFORMATION:ipc.c:4050CreateDirectoryW(runtime_dir, &security.attributes)ipc.c:4097SetSecurityInfo(directory, …, security.acl)ipc.c:4184CreateDirectoryW(path, &security.attributes)ipc.c:3819,3823mutexipc.c:4236,4260private log filewin_runtime_directory_secureis also a repair path. Line 4051 continues onERROR_ALREADY_EXISTSand line 4093 re-stamps the existing directory unconditionally, so a tree repaired by hand with icacls is destroyed again on the next start.The three other owner-only ACL builders get this right by using
SetEntriesInAclWwith an explicitgrfInheritance:src/foundation/compat.c:88,SUB_CONTAINERS_AND_OBJECTS_INHERITsrc/foundation/compat_fs.c:454,SUB_CONTAINERS_AND_OBJECTS_INHERITsrc/cli/activation_transaction.c:368,NO_INHERITANCE, which is correct because it is applied only to a CREATE_NEW file at line 929src/foundation/private_file_lock.cresolvesAddAccessAllowedAcetoo (line 762) and appliesSE_DACL_PROTECTED, but only ever to files, so it is unaffected.Why it did not reproduce on your VM
The refusal half needs a tainted tree. The destructive half does not. It fires on any run that stamps a container, including a clean profile-layout install, so a VM whose trees were already private would only show it by inspecting AceFlags on the resulting directory rather than by any failure.
Fix
Build a second inheritable ACL for containers using
AddAccessAllowedAceExwithCONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACEand use it at the three directory sites. Leaves keep the existing flagless ACL.One trap, which I hit before getting it right: the inheritable ACE must carry specific rights (
FILE_ALL_ACCESS), notGENERIC_ALL. Windows splits an inheritable generic-rights ACE into an effective mapped ACE plus an INHERIT_ONLY one holding the generic bits. That makes two ACEs, andprivate_win_owner_only_daclrequires exactly one (private_file_lock.c:910), so every lock directory then fails validation and the process dies with:With specific rights the ACL is a single inheritable ACE and both validators accept it. The check at
private_file_lock.c:917masks onlyINHERITED_ACE | INHERIT_ONLY_ACE.Verified locally
Built from main with the patch (MinGW gcc, Windows 11 26200) and compared against the stock 0.10.0 release binary on the same cache path:
PETRO\petro:(F)PETRO\petro:(OI)(CI)(F)(F)(OI)(CI)(F)cli list_projectsThe repair row is the one that matters here. That is the branch which rewrites an already-good tree on every daemon or client start.
Test
Added
daemon_ipc_windows_private_directory_ace_is_inheritable. It asserts that the secured directory has exactly one ACE, that the ACE carries both inherit flags, that a child created inside it has a non-empty DACL, and thatcbm_private_lock_directory_adopt_windowsstill accepts the directory. The child-DACL and inherit-flag assertions fail on current main. The ACE-count assertion catches the GENERIC_ALL variant described above.Caveat on my local run
MinGW ships no libsanitizer, so I could not run
scripts/test.shin its ASan and UBSan configuration. My run was unsanitized. Several pre-existing-Werrordiagnostics intests/test_daemon_ipc.candtests/test_daemon_application.calso had to be downgraded before the test runner would build under this gcc, none of them in code the patch touches. CI remains the authority on sanitizer and warning cleanliness.fix(daemon): make private directory DACL inheritable
fix(daemon): make private directory DACL inheritable
fix(daemon): make private directory DACL inheritable
DeusData commentedon Aug 12, 2026
Fixed on main by @Kiborgik's #1531 (merge 7f23a66) — shipping in v0.10.3.
Root cause: the daemon created its private directory with a flagless ACE under
PROTECTED_DACL_SECURITY_INFORMATION. The protection severed inherited ACEs and the new ACE propagated nothing, so every file created inside was left with an empty DACL — unreadable, exactly as you reported. Directories now get a properly inheritable ACE (with specific rights rather than generic, so the owner-only validation still sees exactly one ACE).