Skip to content

No product-build way to relocate the daemon runtime directory when %LOCALAPPDATA% has a capability-SID ACE #1574

Description

@tmonestudio

Summary

On Windows the daemon/CLI coordination endpoint is created under
%LOCALAPPDATA%, resolved by SHGetFolderPathW(CSIDL_LOCAL_APPDATA) with no
override in a product build. If that ancestry carries a capability-SID ACE with
mutation rights — common on real machines once packaged apps are installed — the
binary cannot start at all, and there is no supported way to point it elsewhere.

The only relocation hook, CBM_TEST_DAEMON_RUNTIME_PARENT, is compiled out
unless CBM_ENABLE_TEST_SEAMS is defined, so the test binary starts fine while
the product binary does not.

Environment

  • Windows 11 Pro 10.0.26200, single user, non-domain.
  • Working build: main @ 7d6cdb23 + local cc56d509.
  • Failing build: current main, built 2026-08-12.

Observed

codebase-memory-mcp: secure daemon endpoint could not be created
codebase-memory-mcp: secure CLI coordination could not be created (endpoint)

Every invocation path fails, including config list, so the settings surface is
unreachable. Independent of CBM_CACHE_DIR: with the cache on a clean
volume-root path the cache check passes and the endpoint failure remains.

Walking the ancestry with the same predicate as
win_private_directory_tree_secure, the only rejected entries under
%LOCALAPPDATA% are a single capability SID:

C:\Users\<user>\AppData        S-1-15-3-...  mask=0x000D0152  inherit_only=false
C:\Users\<user>\AppData\Local  S-1-15-3-...  mask=0x000D0152  inherit_only=false

0x000D0152 includes WRITE_DAC, WRITE_OWNER and DELETE. We are not
asking you to trust that — we agree it is a real tamper vector. Note it is not a
Windows default: C:\Users\Default\AppData\Local has no such ACE. It arrived
with an installed application, and it is present on AppData, Local,
Roaming and LocalLow.

Requests, in order of preference

  1. A product-supported override for the runtime parent directory — a config
    key or environment variable, validated by the same
    win_private_directory_tree_secure. The operator picks a path with clean
    ancestry; the check keeps its full strength. This is the one that unblocks us
    without weakening anything.
  2. Name the offending path in the endpoint error. The cache failure prints
    ipc_validation_detail and is immediately actionable; the endpoint failure
    prints neither the path nor the ACE, so diagnosing it took reading
    src/daemon/ipc.c.
  3. Document the required DACL shape for the runtime and cache directories,
    so operators can provision one deliberately instead of discovering it from a
    rejection.

Cache directory: works, with a caveat worth documenting

Our cache lives on a secondary data volume (46.5 GB; the system volume has 31.8
GB free, so relocating is not an option). F:\IndexadorData\... is rejected for
an inherited Authenticated Users → Modify from the volume root — correctly, we
think. Putting the cache directly under the volume root passes, since the
tool then owns the final directory's DACL and the only ancestor is the root
itself. That is a useful and non-obvious property; documenting it would save
others the same investigation.

Offer

We have the working and failing binaries side by side and an 823-project cache.
Happy to run any diagnostic build.

Activity

  1. added
    bugSomething isn't working
    securitySecurity vulnerabilities, hardening
    editor/integrationEditor compatibility and CLI integration
    ux/behaviorDisplay bugs, docs, adoption UX
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Aug 12, 2026
  2. DeusData commented on Aug 12, 2026

    @DeusData
    Owner

    Thank you @tmonestudio. Confirmed on current main: main_daemon_endpoint_new() reads only CBM_TEST_DAEMON_RUNTIME_PARENT under CBM_ENABLE_TEST_SEAMS, while the other product endpoint paths call cbm_daemon_bootstrap_endpoint_new(NULL). A product binary therefore has no supported runtime-parent override, and relocating CBM_CACHE_DIR does not move the daemon rendezvous.

    #1576 targets this gap without relaxing the private-directory validation. This is routed as a high-priority Windows/security compatibility bug. The PR itself first needs to be unstacked from #1561 so its runtime-only diff can be reviewed cleanly. Thank you for supplying both the failing security condition and a fail-closed design.

  3. DeusData commented on Aug 14, 2026

    @DeusData
    Owner

    Thank you for this, @tmonestudio. Walking the ancestry with the same predicate as win_private_directory_tree_secure and coming back with the exact capability SID and mask=0x000D0152 removed all the guesswork, and your check that C:\Users\Default\AppData\Local carries no such ACE settled whether this was a Windows default or something an installed app left behind.

    Re-confirmed and unchanged: the daemon runtime-directory parent is not overridable in a product build. The only override, CBM_TEST_DAEMON_RUNTIME_PARENT, is read inside #ifdef CBM_ENABLE_TEST_SEAMS in main_daemon_endpoint_new(), so it is compiled out of shipped binaries — the test binary starts and yours cannot, exactly as you described.

    A binary that cannot even run config list on an ordinary machine is an operability blocker, and this is a top item for tonight's v0.10.5, which is being prepared for precisely that class of issue. Sorry that you had to read src/daemon/ipc.c to learn why it refused; naming the offending path in the endpoint error is part of the same work.

  4. DeusData commented on Aug 17, 2026

    @DeusData
    Owner

    Fixed in v0.10.5 by #1623 (merged 076c8ae): ancestor directories now tolerate AppContainer/capability SIDs — exactly the S-1-15-3-… mask=0x000D0152 ACE you isolated — while the runtime directory itself stays strict (owned by you, no allow-ACL). Your ancestry walk with the same predicate as win_private_directory_tree_secure is what made the tolerate-vs-refuse boundary decidable, thank you. Since #1645 there is also a product-build CBM_RUNTIME_DIR override as a general lever when a host's profile tree is unusual. If v0.10.5+ still refuses on your machine, please reopen with the new refusal line (it now names the exact ancestor).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.securitySecurity vulnerabilities, hardeningux/behaviorDisplay bugs, docs, adoption UXwindowsWindows-specific issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions