Skip to content

feat(config): make Windows cache-dir DACL hardening configurable (env var + CLI config key) — opt-out for hosts where it breaks MoveFileExW #1624

Open

Description

@roosteer

Feature request

Make the Windows cache-directory DACL hardening configurable, via:

  1. CLI configuration: codebase-memory-mcp config set windows-dacl-hardening false (persisted)
  2. Environment variable: CBM_SKIP_DACL_HARDENING=1 (kill switch, overrides the config)

This is the opt-out path requested as fix option #2 in #1620 — on hosts where the protected owner-only DACL conflicts with local security policy (EDR/minifilter), indexing is currently impossible and there is no escape hatch.

Why this is needed (verified root cause, details in #1620)

  • Since v0.9.1-rc.1 every CBM process re-applies a protected owner-only DACL to the cache dir at startup (win_runtime_directory_secure, src/daemon/ipc.c:4121).
  • On some hosts, MoveFileExW(MOVEFILE_REPLACE_EXISTING) returns ERROR_ACCESS_DENIED inside protected-DACL directories (reproduced with cmd move /y, P/Invoke and synthetic dirs — independent of CBM).
  • CBM publishes the index DB via rename-replace (publish_writer_output, src/sqlite_writer.c) → the pipeline fails silently → generic "Pipeline failed".
  • Proof: with the dir ACL in the normal inherited state, the identical run succeeds (status:"indexed").

Analysis: points to modify

Site Function Change
src/main.c main_build_identity() Read effective setting (env > config) and gate the cbm_daemon_ipc_private_directory_secure() call
src/daemon/ipc.c win_runtime_directory_secure() Skip the unconditional SetSecurityInfo(... PROTECTED_DACL_SECURITY_INFORMATION ...) when disabled
src/daemon/ipc.c win_file_security_secure() validators When disabled, accept the OS-default (inherited) DACL instead of requiring the one-ACE owner-only form — otherwise the daemon fails closed at startup
src/foundation/compat_fs.c cbm_windows_stamp_dir_owner() Skip the protected-DACL stamp on freshly created dirs when disabled
src/ui/config.c CONFIG_KEYS[] Add windows-dacl-hardening (default true) — automatically supported by config list/get/set/reset

Notes:

  • Both sides must be gated. Disabling only the write side makes the strict validators reject the inherited DACL and the daemon refuses to start (fail-closed — observed while debugging Windows: index always fails with generic 'Pipeline failed' — protected-DACL cache dir breaks MoveFileExW(REPLACE_EXISTING) on this host #1620).
  • Owner checks (win_file_owner_secure) can stay active even when DACL hardening is off; the conflict is only with the protected-DACL shape, not with ownership validation.
  • The --index-worker process inherits the behavior for free because it also passes through main_build_identity.
  • Precedence: env var > config value. The env var is read via getenv (same pattern as CBM_CACHE_DIR, CBM_LOG_LEVEL); the config value via cbm_config_get(cfg, key, default) (same pattern as ui_port etc.). The read must happen before the hardening call; the config DB lives in the cache dir, so there is no circular dependency.

Security tradeoff (for the record)

The protected DACL defends the daemon's cache/IPC boundary against other local users. Disabling it is safe on single-user machines (the affected use case) and not recommended on multi-user/terminal-server hosts — the config default stays true.

Testing suggestions

  • config set windows-dacl-hardening false → index any repo → status:"indexed" (on an affected host; regression-check on a normal host that default behavior is unchanged)
  • CBM_SKIP_DACL_HARDENING=1 overrides true in config
  • Daemon startup: with the option off, no ui.config.write_fail reason=atomic_publish, and the walk accepts the inherited DACL
  • Unit tests: gate the win_file_security_secure relaxation path (tests exist in tests/ per Makefile.cbm)

Activity

  1. roosteer commented on Aug 14, 2026

    @roosteer
    Author

    Implemented in PR #1649.

    What shipped

    • Env kill switch CBM_SKIP_DACL_HARDENING=1 — effective from the very first run (read before any directory creation).
    • Persisted config key windows-dacl-hardening (default true) via config set/get/list/reset — effective from the second run (the store lives inside the cache dir).
    • Precedence: env > config.
    • When disabled: creation stamp keeps the exact-owner stamp but drops the protected DACL; the runtime-directory walk skips re-protection and restores the parent-inherited DACL on an already-hardened directory (self-healing, no manual icacls); validators accept the OS-default inherited DACL. Owner validation stays active in every path; default behavior unchanged.

    Key implementation note (tradeoff) — the unprotect supplies a real DACL (a copy of the parent directory's DACL) and clears SE_DACL_PROTECTED. Passing a NULL pDacl with DACL_SECURITY_INFORMATION would create a NULL DACL (full access to everyone) per the SetSecurityInfo contract — avoided deliberately.

    Verification

  2. added
    enhancementNew feature or request
    securitySecurity vulnerabilities, hardening
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Aug 18, 2026
  3. roosteer commented on Sep 1, 2026

    @roosteer
    Author

    Additional sanitized evidence supporting a configurable diagnostic/escape hatch:

    • On a Windows x64 host running v0.10.8, every index_repository attempt returns the generic pipeline failure after the supervised worker exits cleanly with code 0.
    • No project database is published; the project catalog remains empty.
    • The cache directory is protected and owner-only (one explicit FullControl rule, no inherited rules).
    • The result is unchanged across full/fast, MCP/CLI, a normal source tree, and a three-file source directory with a short explicit project name.
    • A best-effort manual inheritance loop during indexing did not resolve the failure, and the original ACL was restored and verified. A supported process-level opt-out would still be materially better than this timing-dependent manual experiment because it could disable hardening before directory creation/validation and provide a clean causal discriminator.
    • The installed v0.10.8 config list does not expose windows-dacl-hardening, so the proposed switch is not available on this affected host.

    No repository paths, account/domain identifiers, SIDs, process IDs, or source details are included in this report.

  4. DeusData commented on Sep 1, 2026

    @DeusData
    Owner

    Thank you for the fresh, sanitized v0.10.8 evidence. It strengthens the case for a supported diagnostic and escape hatch, while the manual inheritance experiment not changing the result means it does not yet isolate DACL hardening as the cause. Because any opt-out changes the local-user security boundary, we need more time to review the design. No further information is requested from you at this stage.

  5. roosteer commented on Sep 3, 2026

    @roosteer
    Author

    Additional sanitized v0.10.8 evidence has been consolidated in #1620: #1620 (comment)

    It covers the still-silent inner writer-publication path, the native MoveFileExW ERROR_ACCESS_DENIED discriminator, and the results of the CBM_CACHE_DIR / CBM_RUNTIME_DIR workaround attempts on a managed-profile host. This is relevant here because the cache override retained the owner-only DACL and the profile-based runtime override was rejected before daemon startup.

    All account, domain, SID, PID, cache-fingerprint, and real-path identifiers remain omitted.

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

    enhancementNew feature or requestpriority/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