Skip to content

feat(recall): fuse BM25 into hybrid ranking, add command smoke suite,… - #111

Merged
Lyellr88 merged 3 commits into
MARM-mainfrom
release/v2.29.0
Jul 24, 2026
Merged

feat(recall): fuse BM25 into hybrid ranking, add command smoke suite,…#111
Lyellr88 merged 3 commits into
MARM-mainfrom
release/v2.29.0

Conversation

@Lyellr88

@Lyellr88 Lyellr88 commented Jul 24, 2026

Copy link
Copy Markdown
Owner

v2.29.0

Retrieval:

  • Fuse FTS5 BM25 into hybrid recall ranking (65% semantic / 35% lexical) instead of discarding the lexical score after candidate filtering.
  • Vectorize chunk-aware scoring into one matrix op (identical results).
  • Apply temporal decay consistently on the text-search fallback lane; the deterministic exact/lexical lane still preserves BM25 order.

Reliability:

  • Lock the managed key file to the executing Windows identity (whoami) instead of an environment-derived username, fixing a create-then-read failure. Shared helper used by key init and auto HTTP key creation.

Testing/tooling:

  • Add scripts/test-scripts/smoke-commands.py + pytest module covering the full marm-memory command surface; Docker and destructive tiers opt-in.
  • Restore module-generation isolation in load_isolated_server (conftest).

Benchmarks:

  • bench_hotpath recall-scaling section times only shipped code paths (recall_similar, _fetch_and_score_embedding_rows) via one async dispatch; README performance tables refreshed from a single run.

Summary by CodeRabbit

  • New Features
    • Enhanced hybrid recall fusion with blended lexical (BM25) + semantic ranking and improved temporal re-ranking during fallback.
    • Added a local command-surface smoke suite covering help, lifecycle, managed-key round trips, Docker workflows, and uninstall safety.
  • Bug Fixes
    • Improved Windows managed-key reliability by locking key-file permissions to the executing user.
  • Documentation
    • Updated version references to v2.29.0 and refreshed benchmark methodology/results.
  • Tests
    • Expanded hybrid/BM25, temporal weighting, chunking, and Windows ACL coverage; added new CLI smoke validations and stricter marker gating.
  • Chores
    • Updated pytest artifact cleanup, file-length checks, and benchmark hotpath reporting.

… honest benchmarks (v2.29.0)

Retrieval:
- Fuse FTS5 BM25 into hybrid recall ranking (65% semantic / 35% lexical)
  instead of discarding the lexical score after candidate filtering.
- Vectorize chunk-aware scoring into one matrix op (identical results).
- Apply temporal decay consistently on the text-search fallback lane;
  the deterministic exact/lexical lane still preserves BM25 order.

Reliability:
- Lock the managed key file to the executing Windows identity (whoami)
  instead of an environment-derived username, fixing a create-then-read
  failure. Shared helper used by key init and auto HTTP key creation.

Testing/tooling:
- Add scripts/test-scripts/smoke-commands.py + pytest module covering the
  full marm-memory command surface; Docker and destructive tiers opt-in.
- Restore module-generation isolation in load_isolated_server (conftest).

Benchmarks:
- bench_hotpath recall-scaling section times only shipped code paths
  (recall_similar, _fetch_and_score_embedding_rows) via one async dispatch;
  README performance tables refreshed from a single run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds BM25 and embedding recall fusion with temporal fallback weighting, centralizes Windows managed-key ACL enforcement, introduces CLI smoke coverage, updates benchmark methodology, and releases version 2.29.0.

Changes

Hybrid Recall Fusion and Scoring

Layer / File(s) Summary
Scoring contracts and batched embeddings
marm-mcp-server/marm_mcp_server/core/memory_scoring.py, marm-mcp-server/tests/test_chunking.py
Normalizes BM25 scores and batches chunk-aware embedding scoring with dimension-mismatch coverage.
FTS candidate scores
marm-mcp-server/marm_mcp_server/core/memory_scoring.py, marm-mcp-server/tests/test_hybrid_search.py
FTS retrieval returns normalized (memory_id, bm25) tuples and dependent tests use the updated contract.
Hybrid and temporal recall paths
marm-mcp-server/marm_mcp_server/core/memory_recall.py, marm-mcp-server/tests/test_hybrid_search.py, marm-mcp-server/tests/test_temporal_weighting.py
Recall fuses lexical and embedding relevance and temporally re-ranks semantic text-search fallbacks.

Windows Managed-Key ACL Hardening

Layer / File(s) Summary
Shared ACL helper and integrations
marm-mcp-server/marm_mcp_server/utils/security.py, marm-mcp-server/marm_mcp_server/config/settings.py, marm-mcp-server/marm_mcp_server/services/key_management.py, marm-mcp-server/tests/test_runtime_cli.py
A shared helper resolves the executing Windows identity and replaces duplicated ACL subprocess logic.

Command Smoke Test Suite

Layer / File(s) Summary
CLI smoke inventory and execution helpers
marm-mcp-server/tests/test_command_smoke.py
Defines command inventories, isolated environments, subprocess execution, and parser/output assertions.
CLI lifecycle smoke coverage
marm-mcp-server/tests/test_command_smoke.py
Covers help, dispatch, HTTP, managed keys, Docker, and destructive uninstall flows.
Smoke runner and test isolation
scripts/test-scripts/smoke-commands.py, marm-mcp-server/pyproject.toml, marm-mcp-server/tests/conftest.py, scripts/run-tests.py
Adds marker-based execution, opt-in gating, and isolated module restoration.
Smoke support and repository tooling
AGENTS.md, scripts/test-scripts/README.md, .gitignore, scripts/clean-pytest-artifacts.py, scripts/check-file-length.py
Documents smoke commands and expands pytest artifact handling and test-directory scanning.

Benchmark Integrity Rework

Layer / File(s) Summary
Production recall scaling benchmark
scripts/benchmarking/performance/bench_hotpath.py, README.md
Compares full semantic scanning with production hybrid recall using a precomputed query vector and revised reporting.

Version 2.29.0 Release Metadata

Layer / File(s) Summary
Version metadata and release documentation
CHANGELOG.md, README.md, docs/INSTALL-*.md, docs/TECHNICAL-OVERVIEW.md, marm-mcp-server/*
Updates package, container, manifest, runtime, documentation, and changelog version references to 2.29.0.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef3054836e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

assert stop.returncode == 0, stop.stderr


@pytest.mark.smoke_destructive

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep destructive smoke out of default pytest

Marking this as smoke_destructive does not by itself prevent plain python -m pytest tests/ (the documented test command and the publish workflow’s test step) from collecting and running it, so the default suite now executes the uninstall/reinstall smoke path instead of requiring scripts/test-scripts/smoke-commands.py --destructive. In environments without pypa/build it fails at python -m build, and with that dependency present it still performs the opt-in lifecycle during normal tests; gate it behind the smoke wrapper or exclude the marker from default pytest.

AGENTS.md reference: AGENTS.md:L61-L61

Useful? React with 👍 / 👎.

return result.returncode == 0


@pytest.mark.smoke_docker

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate Docker smoke behind the existing Docker opt-in

This test is only marked smoke_docker, but normal pytest runs and scripts/run-tests.py do not deselect that marker (the runner filters the existing docker marker), so any machine with a Docker daemon will pull and start a real MARM container without passing --docker. Add the existing docker marker or an explicit skip/marker filter so Docker smoke remains opt-in.

AGENTS.md reference: AGENTS.md:L61-L61

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef3054836e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +332 to +335
sys.executable,
"-m",
"build",
"--wheel",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add build dependency for destructive smoke

When the opt-in destructive smoke suite runs in the documented local test setup, this subprocess fails before it ever reaches the uninstall/reinstall lifecycle because [dev] does not install the build module, so python -m build exits with No module named build. Either add build to the dev/test dependencies or make this test install/skip that prerequisite so smoke-commands.py --destructive is actually runnable.

AGENTS.md reference: AGENTS.md:L60-L61

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
marm-mcp-server/marm_mcp_server/core/memory_recall.py (1)

334-389: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

retrieval_mode mislabels results from the semantic-intent temporal fallback as "exact_fts"/"exact_like".

_recall_text_search is now called both from _recall_exact (true exact lane, apply_temporal=False) and from the semantic-intent fallbacks at lines 191-197 and 315-321 (apply_temporal=True), but every result still hardcodes retrieval_mode to "exact_fts"/"exact_like". Per the docstring on _recall_exact (lines 51-53), this field exists so "callers and tests can confirm which path was taken" — that contract now breaks for the fallback callers, whose results are temporally-blended, not pure BM25/lexical order, yet get labeled identically to the deterministic exact lane.

🏷️ Proposed fix: derive the label from `apply_temporal`
-                        "similarity": _blend_temporal(float(score), row["timestamp"]),
-                        "project": row["project"],
-                        "platform": row["platform"],
-                        "retrieval_mode": "exact_fts",
+                        "similarity": _blend_temporal(float(score), row["timestamp"]),
+                        "project": row["project"],
+                        "platform": row["platform"],
+                        "retrieval_mode": "semantic_fallback_fts" if apply_temporal else "exact_fts",
                     }
                     for row, score in fts_rows
                     "similarity": _blend_temporal(0.8, row[3]),
                     "project": row[6],
                     "platform": row[7],
-                    "retrieval_mode": "exact_like",
+                    "retrieval_mode": "semantic_fallback_like" if apply_temporal else "exact_like",
                 }
             )

Also applies to: 427-435

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/core/memory_recall.py` around lines 334 -
389, Update the result construction in _recall_text_search for both FTS and LIKE
fallback paths so retrieval_mode is derived from apply_temporal: retain the
exact labels for the non-temporal exact lane and use the semantic-intent
temporal fallback labels when apply_temporal is true. Ensure both branches
consistently identify which retrieval path produced the results.
🧹 Nitpick comments (1)
scripts/benchmarking/performance/bench_hotpath.py (1)

243-280: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Sequential ordering may bias the speedup metric.

full_scan is always timed immediately before production_hybrid on the same query, every iteration. If the full scan warms the SQLite page cache, the following hybrid call systematically benefits — a bias that isn't filtered out by taking the median, since it applies to every sample. This directly affects the "Speedup" number this rewrite is meant to report accurately.

Consider alternating/randomizing which path runs first per iteration (or running each path in fully separate warm-up passes) so cache effects don't asymmetrically favor production_hybrid.

Example: alternate order per iteration
-            # 1. Full semantic scan (production scorer, no pre-filter).
-            t0 = time.perf_counter()
-            await asyncio.to_thread(
-                _fetch_and_score_embedding_rows, mem.db_path, "bench", n, query_vec, 5
-            )
-            scan_samples.append((time.perf_counter() - t0) * 1000)
-
-            # 2. Production hybrid recall (precomputed vector excludes encode)
-            t0 = time.perf_counter()
-            await mem.recall_similar(
-                query, session="bench", limit=5, query_vec=query_vec
-            )
-            prod_samples.append((time.perf_counter() - t0) * 1000)
+            run_scan_first = iter_num % 2 == 0
+
+            async def _time_scan():
+                t0 = time.perf_counter()
+                await asyncio.to_thread(
+                    _fetch_and_score_embedding_rows, mem.db_path, "bench", n, query_vec, 5
+                )
+                return (time.perf_counter() - t0) * 1000
+
+            async def _time_hybrid():
+                t0 = time.perf_counter()
+                await mem.recall_similar(query, session="bench", limit=5, query_vec=query_vec)
+                return (time.perf_counter() - t0) * 1000
+
+            if run_scan_first:
+                scan_samples.append(await _time_scan())
+                prod_samples.append(await _time_hybrid())
+            else:
+                prod_samples.append(await _time_hybrid())
+                scan_samples.append(await _time_scan())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/benchmarking/performance/bench_hotpath.py` around lines 243 - 280,
Update the benchmark loop around _fetch_and_score_embedding_rows and
mem.recall_similar so the timed full_scan and production_hybrid paths alternate
or randomize execution order per iteration, rather than always running full_scan
first. Preserve each path’s existing timing and sample collection, ensuring
neither path is systematically favored by SQLite cache warm-up before
calculating the speedup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@marm-mcp-server/marm_mcp_server/config/settings.py`:
- Line 401: Check the boolean result from
restrict_windows_file_to_current_user(_MARM_ENV_PATH) in the auto-generated API
key save flow, and when it returns False, raise or log a warning using the
established “401: WARNING:...” pattern. Preserve the existing successful save
behavior.

In `@marm-mcp-server/marm_mcp_server/utils/security.py`:
- Around line 30-32: Update restrict_windows_file_to_current_user to avoid
spawning an unqualified whoami executable on Windows. Obtain the current
identity through the Windows identity API, or invoke whoami only via a validated
absolute SystemRoot\System32 path, while preserving the existing identity value
used by the icacls command.

In `@marm-mcp-server/tests/test_temporal_weighting.py`:
- Around line 199-209: Update the exact-lane test around recall_similar so it no
longer relies on SQLite’s undefined insertion order for equal BM25 scores.
Preserve coverage that exact mode does not re-rank by age, using a deterministic
assertion such as retrieval metadata or timestamp-invariant ordering; if
retaining order-based validation, make the fixture contents produce distinct
BM25 scores.

---

Outside diff comments:
In `@marm-mcp-server/marm_mcp_server/core/memory_recall.py`:
- Around line 334-389: Update the result construction in _recall_text_search for
both FTS and LIKE fallback paths so retrieval_mode is derived from
apply_temporal: retain the exact labels for the non-temporal exact lane and use
the semantic-intent temporal fallback labels when apply_temporal is true. Ensure
both branches consistently identify which retrieval path produced the results.

---

Nitpick comments:
In `@scripts/benchmarking/performance/bench_hotpath.py`:
- Around line 243-280: Update the benchmark loop around
_fetch_and_score_embedding_rows and mem.recall_similar so the timed full_scan
and production_hybrid paths alternate or randomize execution order per
iteration, rather than always running full_scan first. Preserve each path’s
existing timing and sample collection, ensuring neither path is systematically
favored by SQLite cache warm-up before calculating the speedup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 59188776-90ed-41bc-aee7-fd32c1248782

📥 Commits

Reviewing files that changed from the base of the PR and between 591ebba and ef30548.

📒 Files selected for processing (31)
  • .gitignore
  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • docs/INSTALL-DOCKER.md
  • docs/INSTALL-LINUX.md
  • docs/INSTALL-PLATFORMS.md
  • docs/INSTALL-WINDOWS.md
  • docs/TECHNICAL-OVERVIEW.md
  • marm-mcp-server/Dockerfile
  • marm-mcp-server/README.md
  • marm-mcp-server/docker-compose.yml
  • marm-mcp-server/marm-docs/README.md
  • marm-mcp-server/marm_mcp_server/__init__.py
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/core/memory_recall.py
  • marm-mcp-server/marm_mcp_server/core/memory_scoring.py
  • marm-mcp-server/marm_mcp_server/server.py
  • marm-mcp-server/marm_mcp_server/services/key_management.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/pyproject.toml
  • marm-mcp-server/server.json
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/tests/test_chunking.py
  • marm-mcp-server/tests/test_command_smoke.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • scripts/benchmarking/performance/bench_hotpath.py
  • scripts/test-scripts/README.md
  • scripts/test-scripts/smoke-commands.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (11)
{marm-mcp-server/server.json,scripts/find-tools.py,README.md,docs/**/*.md,marm-mcp-server/marm-docs/**/*.md,marm-mcp-server/tests/**/*.py}

📄 CodeRabbit inference engine (AGENTS.md)

When adding or removing an MCP tool, update the tool manifest, canonical tool list, full tool-list documentation and counts, and tests covering both transports; then run python scripts/find-tools.py.

Files:

  • docs/INSTALL-DOCKER.md
  • docs/TECHNICAL-OVERVIEW.md
  • marm-mcp-server/marm-docs/README.md
  • docs/INSTALL-WINDOWS.md
  • docs/INSTALL-LINUX.md
  • marm-mcp-server/server.json
  • marm-mcp-server/tests/test_chunking.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • docs/INSTALL-PLATFORMS.md
  • marm-mcp-server/tests/test_temporal_weighting.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • README.md
  • marm-mcp-server/tests/test_command_smoke.py
{marm-mcp-server/pyproject.toml,marm-mcp-server/server.json,marm-mcp-server/marm_mcp_server/__init__.py,marm-mcp-server/marm_mcp_server/config/settings.py,marm-mcp-server/marm_mcp_server/server.py,marm-mcp-server/Dockerfile,docker-compose.yml,README.md,marm-mcp-server/README.md,marm-mcp-server/marm-docs/README.md,docs/INSTALL-*.md}

📄 CodeRabbit inference engine (AGENTS.md)

When bumping the version, update every listed version source and audit the result with python scripts/find-versions.py. Follow SemVer: major for breaking changes, minor for new tools/parameters/features, and patch for fixes or documentation updates.

Files:

  • docs/INSTALL-DOCKER.md
  • marm-mcp-server/Dockerfile
  • marm-mcp-server/marm_mcp_server/server.py
  • marm-mcp-server/README.md
  • marm-mcp-server/marm-docs/README.md
  • marm-mcp-server/marm_mcp_server/__init__.py
  • marm-mcp-server/pyproject.toml
  • docs/INSTALL-WINDOWS.md
  • docs/INSTALL-LINUX.md
  • marm-mcp-server/server.json
  • docs/INSTALL-PLATFORMS.md
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • README.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Only flag documentation issues that are materially wrong, misleading for installation/release behavior, or inconsistent with live MCP behavior. Skip style, phrasing, formatting, and wording preferences.

Files:

  • docs/INSTALL-DOCKER.md
  • docs/TECHNICAL-OVERVIEW.md
  • marm-mcp-server/README.md
  • marm-mcp-server/marm-docs/README.md
  • scripts/test-scripts/README.md
  • AGENTS.md
  • docs/INSTALL-WINDOWS.md
  • docs/INSTALL-LINUX.md
  • CHANGELOG.md
  • docs/INSTALL-PLATFORMS.md
  • README.md
marm-mcp-server/marm_mcp_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/**/*.py: Implement MARM server logic in Python under marm-mcp-server/marm_mcp_server/, keeping endpoint logic in endpoints/ and shared helpers in core/.
All memory writes must use the serialized asynchronous write queue; do not add bypass write paths.
A semantic-store failure during marm_log_entry must never fail the corresponding log write.
Keep the memory database and isolated concept-graph database on separate connection pools; never share connections between them.
Graph and concept failures must not break the seven core memory tools; graph services should start lazily and operate degraded on failure.
marm_smart_recall must keep primary memory ranking authoritative; graph enrichment is read-only, fail-open, bounded, and must be trimmed before primary results when enforcing response limits.
Writes must succeed when the embedding encoder is unavailable; embedding loading is lazy and serialized behind a lock.
Prefer the smallest change that solves the problem; avoid speculative abstractions and unrequested configuration flags.
Keep comments minimal and use them only to explain non-obvious reasons; do not narrate the next line.

Files:

  • marm-mcp-server/marm_mcp_server/server.py
  • marm-mcp-server/marm_mcp_server/__init__.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/marm_mcp_server/services/key_management.py
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/core/memory_scoring.py
  • marm-mcp-server/marm_mcp_server/core/memory_recall.py
marm-mcp-server/marm_mcp_server/server.py

📄 CodeRabbit inference engine (AGENTS.md)

HTTP tools must be included in MCP_TOOL_OPERATIONS; tools absent from this whitelist do not exist over HTTP.

Files:

  • marm-mcp-server/marm_mcp_server/server.py
marm-mcp-server/marm_mcp_server/{server.py,server_stdio.py,endpoints/**/*.py,services/stdio_graph_tools.py}

📄 CodeRabbit inference engine (AGENTS.md)

Never fork behavior between HTTP and STDIO transports; new tools must have matching implementations or service paths in both transports.

Files:

  • marm-mcp-server/marm_mcp_server/server.py
marm-mcp-server/{marm_mcp_server/endpoints/**/*.py,marm_mcp_server/server.py,marm_mcp_server/server_stdio.py}

📄 CodeRabbit inference engine (AGENTS.md)

When adding or removing an MCP tool, implement it in endpoints/, add the HTTP route and whitelist entry, and add the matching STDIO wrapper or service path.

Files:

  • marm-mcp-server/marm_mcp_server/server.py
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Prioritize runtime correctness, async/concurrency safety, SQLite transaction safety, auth/rate-limit behavior, release-breaking packaging issues, and MCP protocol compatibility.

Files:

  • marm-mcp-server/marm_mcp_server/server.py
  • scripts/test-scripts/smoke-commands.py
  • marm-mcp-server/marm_mcp_server/__init__.py
  • marm-mcp-server/tests/test_chunking.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/marm_mcp_server/services/key_management.py
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • marm-mcp-server/marm_mcp_server/core/memory_scoring.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • scripts/benchmarking/performance/bench_hotpath.py
  • marm-mcp-server/tests/test_command_smoke.py
  • marm-mcp-server/marm_mcp_server/core/memory_recall.py
marm-mcp-server/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/tests/**/*.py: Run tests with pytest from marm-mcp-server/; prefer deep tests exercising real paths, and mock only when the mock matches real behavior with at least 95% fidelity.
Every new MARM Console API route requires at least one happy-path FastAPI response-contract test with the MCP adapter stubbed.
Do not add existence-check or coded-to-pass tests; use pytest.mark.skip only for genuinely unavailable dependencies.

Files:

  • marm-mcp-server/tests/test_chunking.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • marm-mcp-server/tests/test_command_smoke.py
**/tests/**

⚙️ CodeRabbit configuration file

**/tests/**: Focus on tests that are flaky, non-isolated, incorrectly asserting behavior, or missing coverage for a changed high-risk path. Skip minor naming, comments, and layout preferences.

Files:

  • marm-mcp-server/tests/test_chunking.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • marm-mcp-server/tests/test_command_smoke.py
scripts/benchmarking/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Do not publish performance claims that cannot be supported by the repository's benchmark scripts.

Files:

  • scripts/benchmarking/performance/bench_hotpath.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Lyellr88/marm-memory

Timestamp: 2026-07-24T07:38:31.213Z
Learning: Keep changes surgical, match existing style, and preserve behavior during refactors.
Learnt from: CR
Repo: Lyellr88/marm-memory

Timestamp: 2026-07-24T07:38:31.213Z
Learning: Do not commit changes without an explicit user request.
🪛 ast-grep (0.44.1)
scripts/test-scripts/smoke-commands.py

[error] 61-61: Command coming from incoming request
Context: subprocess.run(command, cwd=PACKAGE_ROOT, check=False)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 61-61: Use of unsanitized data to create processes
Context: subprocess.run(command, cwd=PACKAGE_ROOT, check=False)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)

marm-mcp-server/marm_mcp_server/utils/security.py

[error] 29-31: Command coming from incoming request
Context: subprocess.run(
["whoami"], check=False, capture_output=True, text=True
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 34-44: Command coming from incoming request
Context: subprocess.run(
[
"icacls",
str(path),
"/inheritance:r",
"/grant:r",
f"{identity}:(F)",
],
check=False,
capture_output=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

marm-mcp-server/tests/test_command_smoke.py

[warning] 236-238: Request-controlled URL passed to urlopen; validate against an allowlist to prevent SSRF.
Context: urllib.request.urlopen(
f"{base_url}/health", timeout=2
)
Note: [CWE-918] Server-Side Request Forgery (SSRF).

(urlopen-unsanitized-data)


[error] 133-145: Command coming from incoming request
Context: subprocess.run(
[
str(python_executable or sys.executable),
"-c",
PRODUCT_ENTRYPOINT,
*arguments,
],
cwd=cwd or PACKAGE_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=timeout,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 223-229: Command coming from incoming request
Context: subprocess.Popen(
[sys.executable, "-c", PRODUCT_ENTRYPOINT, "http"],
cwd=PACKAGE_ROOT,
env=environment,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 281-283: Command coming from incoming request
Context: subprocess.run(
["docker", "ps"], capture_output=True, text=True, timeout=20
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 329-344: Command coming from incoming request
Context: subprocess.run(
[
sys.executable,
"-m",
"build",
"--wheel",
"--no-isolation",
"--outdir",
str(wheel_dir),
],
cwd=PACKAGE_ROOT,
capture_output=True,
text=True,
env=pip_environment,
timeout=240,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 353-359: Command coming from incoming request
Context: subprocess.run(
[str(python), "-m", "pip", "install", "--no-deps", str(wheel)],
capture_output=True,
text=True,
env=pip_environment,
timeout=180,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 382-388: Command coming from incoming request
Context: subprocess.run(
[str(python), "-m", "pip", "install", "--no-deps", str(wheel)],
capture_output=True,
text=True,
env=pip_environment,
timeout=180,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🔇 Additional comments (38)
CHANGELOG.md (1)

5-27: LGTM!

README.md (1)

8-8: LGTM!

Also applies to: 176-215

docs/INSTALL-DOCKER.md (1)

5-5: LGTM!

docs/INSTALL-LINUX.md (1)

5-5: LGTM!

Also applies to: 323-323

docs/INSTALL-PLATFORMS.md (1)

1-1: LGTM!

docs/INSTALL-WINDOWS.md (1)

5-5: LGTM!

Also applies to: 297-297

docs/TECHNICAL-OVERVIEW.md (1)

3-3: LGTM!

marm-mcp-server/Dockerfile (1)

76-76: LGTM!

marm-mcp-server/README.md (1)

10-10: LGTM!

marm-mcp-server/docker-compose.yml (1)

21-21: LGTM!

marm-mcp-server/marm-docs/README.md (1)

1-1: LGTM!

marm-mcp-server/marm_mcp_server/__init__.py (1)

17-20: LGTM!

marm-mcp-server/marm_mcp_server/server.py (1)

8-8: LGTM!

marm-mcp-server/server.json (1)

6-6: LGTM!

Also applies to: 20-25

marm-mcp-server/marm_mcp_server/utils/security.py (1)

5-7: LGTM!

Also applies to: 23-48

marm-mcp-server/marm_mcp_server/config/settings.py (1)

8-8: LGTM!

Also applies to: 125-125

marm-mcp-server/marm_mcp_server/services/key_management.py (1)

5-10: LGTM!

Also applies to: 36-47

marm-mcp-server/tests/test_runtime_cli.py (1)

223-245: LGTM!

scripts/benchmarking/performance/bench_hotpath.py (2)

8-19: LGTM!

Also applies to: 45-45, 206-215, 224-229


323-326: LGTM!

Also applies to: 335-360

marm-mcp-server/tests/test_command_smoke.py (5)

290-323: 🎯 Functional Correctness | ⚡ Quick win

Assert inside finally can mask the real failure.

If the try body (e.g. run/status/logs assertions) fails, the assert stop.returncode == 0 in finally (321-322) can raise and replace the original AssertionError, hiding the actual root cause of a Docker lifecycle failure. Same pattern recurs in the destructive test below (see consolidated note).

🧪 Suggested fix: don't let cleanup assertions mask the original failure
     finally:
-        stop = _run_product(("docker", "stop", "--name", container), environment)
-        assert stop.returncode == 0, stop.stderr
+        stop = _run_product(("docker", "stop", "--name", container), environment)
+        if stop.returncode != 0:
+            warnings.warn(f"docker stop cleanup failed: {stop.stderr}", stacklevel=2)
As per path instructions, tests should not be "incorrectly asserting behavior"; this pattern can obscure the true failure.

Source: Path instructions


1-217: LGTM!


219-278: LGTM!


325-397: 🎯 Functional Correctness | ⚡ Quick win

Same assert-in-finally masking pattern as the Docker test.

Lines 383-390: if result.returncode != 0 in the try block, the finally block's assert reinstall.returncode == 0 can raise and overwrite the original assertion, obscuring which step actually failed.

As per path instructions, tests directory review should flag "incorrectly asserting behavior"; consolidating with the Docker-test instance of the same pattern below.

Source: Path instructions


79-96: 🩺 Stability & Availability

uninstall is safe in SAFE_DISPATCHES.

Plain uninstall returns from the preview path when --yes is absent, so this smoke test does not remove the package from the test environment.

scripts/test-scripts/smoke-commands.py (1)

1-67: LGTM!

marm-mcp-server/pyproject.toml (2)

7-7: 📐 Maintainability & Code Quality

Confirm version-bump audit covers all listed sources.

Verify 2.29.0 was applied consistently across every guideline-listed version source and that python scripts/find-versions.py passes clean.

As per coding guidelines: "When bumping the version, update every listed version source and audit the result with python scripts/find-versions.py."

Source: Coding guidelines


112-115: LGTM!

marm-mcp-server/tests/conftest.py (1)

60-93: LGTM!

scripts/test-scripts/README.md (1)

18-34: LGTM!

AGENTS.md (1)

61-61: LGTM!

.gitignore (1)

34-34: LGTM!

Also applies to: 121-122

marm-mcp-server/marm_mcp_server/core/memory_scoring.py (1)

7-21: LGTM!

Also applies to: 85-127, 222-222, 233-266

marm-mcp-server/tests/test_chunking.py (1)

225-256: LGTM!

marm-mcp-server/tests/test_hybrid_search.py (1)

385-388: LGTM!

Also applies to: 428-428, 563-563, 601-682

marm-mcp-server/marm_mcp_server/core/memory_recall.py (2)

191-197: LGTM (temporal fallback wiring itself is correct — see the retrieval_mode contract issue flagged on _recall_text_search at lines 334-389, which affects both of these call sites).

Also applies to: 315-321


209-236: LGTM!

Also applies to: 268-285

marm-mcp-server/tests/test_temporal_weighting.py (1)

212-253: LGTM!

)
except Exception:
pass
restrict_windows_file_to_current_user(_MARM_ENV_PATH)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg 'marm-mcp-server/marm_mcp_server/(config/settings|utils/.*/key_management|core/.*/key_management)\.py$|marm-mcp-server/marm_mcp_server/utils/.*/key_management\.py$|marm-mcp-server/marm_mcp_server/core/.*/key_management\.py$|key_management\.py$' || true

echo "== settings outline/lines =="
wc -l marm-mcp-server/marm_mcp_server/config/settings.py
sed -n '330,430p' marm-mcp-server/marm_mcp_server/config/settings.py

echo "== helper definitions/usages =="
rg -n "def restrict_windows_file_to_current_user|restrict_windows_file_to_current_user|_protect_key_file|protect.*key" marm-mcp-server/marm_mcp_server -g '*.py'

echo "== settings relevant imports/funcs names around api key =="
rg -n "MARM_API_KEY|MARM_ENV_PATH|generate|create|api_key|restrict_windows|key_file" marm-mcp-server/marm_mcp_server/config/settings.py

Repository: Lyellr88/marm-memory

Length of output: 5509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== security.py =="
sed -n '1,120p' marm-mcp-server/marm_mcp_server/utils/security.py

echo "== protect key usages =="
sed -n '25,85p' marm-mcp-server/marm_mcp_server/services/key_management.py

echo "== deterministic control-flow probe for settings branch =="
python3 - <<'PY'
from pathlib import Path
from unittest import mock

for restricted_return in (True, False):
    print(f"restricted_return={restricted_return}")
    with mock.patch("pathlib.Path.home", return_value=Path("/tmp/marmtest")):
        with mock.patch("pathlib.Path.mkdir") as mkdir:
            with mock.patch("pathlib.Path.write_text") as write:
                with mock.patch("pathlib.Path.chmod") as chmod:
                    with mock.patch("marm_mcp_server.utils.security.restrict_windows_file_to_current_user") as rip:
                        rip.return_value = restricted_return
                        exec_globals = {}
                        exec(compile(open("marm-mcp-server/marm_mcp_server/config/settings.py").read().splitlines()[355:417][::-1][-1:][::-1], "<probe>", "exec"), exec_globals)
                        print("restrict_windows_file_to_current_user call kwargs:", rip.call_args_list)
                        print("outer warning:", not rip.return_value and "Could not restrict" in compile("", "<z", "exec").co_code)
PY

Repository: Lyellr88/marm-memory

Length of output: 3776


Security Misconfiguration (CWE-732): Incorrect Permission Assignment for Critical Resource

Reachability: Internal

Check the Windows permission result when auto-saving the API key.

restrict_windows_file_to_current_user() returns False on whoami/icacls failure, but the auto-generated key path ignores that result, leaving the file writable by inherited users. Raise or log a warning there with the same 401: WARNING:... pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/config/settings.py` at line 401, Check the
boolean result from restrict_windows_file_to_current_user(_MARM_ENV_PATH) in the
auto-generated API key save flow, and when it returns False, raise or log a
warning using the established “401: WARNING:...” pattern. Preserve the existing
successful save behavior.

Comment thread marm-mcp-server/marm_mcp_server/utils/security.py Outdated
Comment thread marm-mcp-server/tests/test_temporal_weighting.py Outdated
CodeRabbit + Codex review of the v2.29.0 branch:

- Gate smoke_docker/smoke_destructive behind MARM_SMOKE_DOCKER /
  MARM_SMOKE_DESTRUCTIVE at collection time so plain pytest and the
  publish test step no longer run the Docker or uninstall/reinstall
  lifecycle. run-tests.py and smoke-commands.py set the gates.
- Add build>=1.2.2 to [dev] so the destructive wheel/reinstall smoke
  can actually run.
- settings.py: warn when restrict_windows_file_to_current_user() fails
  instead of silently leaving the key file inheritable.
- security.py: resolve whoami/icacls via absolute SystemRoot\System32
  paths to avoid an untrusted-search-path hijack (CWE-426); test updated
  to match.
- test_temporal_weighting.py: drop the undefined SQLite tie-order
  assumption on the exact lane; assert timestamp-invariant similarity
  and retrieval_mode instead.

Benchmark: alternate which path runs first each iteration so neither
gets a consistent warm-cache advantage; re-ran and refreshed README
section 4 (numbers held: 39.4x at N=10,000).

Scripts: --tests flag for check-file-length; clean-pytest-artifacts
discovers .pytest-review-*/.pytest-smoke-*/marm-pytest-* dirs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
marm-mcp-server/marm_mcp_server/utils/security.py (1)

39-55: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Security Misconfiguration (CWE-732): Incorrect Permission Assignment for Critical Resource

Reachability: Internal
● Entry
  marm-mcp-server/marm_mcp_server/config/settings.py:400
  restrict_windows_file_to_current_user
│
▼
● Sink
  marm-mcp-server/marm_mcp_server/utils/security.py

Replace the full DACL, not only the current identity’s grant.

/inheritance:r only removes inherited ACEs and /grant:r identity:(F) only replaces explicit permissions for that identity. Pre-existing explicit ACEs for other users/groups can remain, so return False unless the DACL is rebuilt/verified to contain only the intended allow-list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/utils/security.py` around lines 39 - 55,
Update the ACL-setting flow around the icacls subprocess call to rebuild the
full DACL rather than only replacing the current identity’s grant. Ensure
pre-existing explicit ACEs for other users or groups are removed, and verify the
resulting DACL contains only the intended allow-list before returning success;
otherwise return False.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@marm-mcp-server/marm_mcp_server/config/settings.py`:
- Around line 400-403: The Windows ACL failure warning in the settings
initialization block currently uses stdout and can corrupt the MCP STDIO
JSON-RPC stream. Replace the print call guarding
restrict_windows_file_to_current_user(_MARM_ENV_PATH) with stderr-directed
output, preserving the warning text and existing Windows-only behavior.

In `@marm-mcp-server/marm_mcp_server/utils/security.py`:
- Around line 31-38: Update the Windows security-path logic around the
SystemRoot, whoami, and icacls symbols to resolve the canonical Windows system
directory through the appropriate Windows API before constructing or executing
either binary. Do not trust os.environ["SystemRoot"] as the directory source;
validate or replace it with the API-resolved protected directory and preserve
the existing failure behavior when resolution is unavailable.

---

Outside diff comments:
In `@marm-mcp-server/marm_mcp_server/utils/security.py`:
- Around line 39-55: Update the ACL-setting flow around the icacls subprocess
call to rebuild the full DACL rather than only replacing the current identity’s
grant. Ensure pre-existing explicit ACEs for other users or groups are removed,
and verify the resulting DACL contains only the intended allow-list before
returning success; otherwise return False.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7051d827-599c-474b-b8d8-d7db29e1db58

📥 Commits

Reviewing files that changed from the base of the PR and between ef30548 and c561724.

📒 Files selected for processing (16)
  • .gitignore
  • README.md
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/core/memory_recall.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/pyproject.toml
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/tests/test_command_smoke.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • scripts/benchmarking/performance/bench_hotpath.py
  • scripts/check-file-length.py
  • scripts/clean-pytest-artifacts.py
  • scripts/run-tests.py
  • scripts/test-scripts/smoke-commands.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • marm-mcp-server/pyproject.toml
  • marm-mcp-server/tests/test_runtime_cli.py
  • marm-mcp-server/tests/test_temporal_weighting.py
  • marm-mcp-server/tests/test_hybrid_search.py
  • README.md
  • marm-mcp-server/marm_mcp_server/core/memory_recall.py
  • scripts/benchmarking/performance/bench_hotpath.py
  • marm-mcp-server/tests/test_command_smoke.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{py,json}

📄 CodeRabbit inference engine (AGENTS.md)

Keep HTTP and STDIO transports in exact parity across all 14 public MCP tools.

Files:

  • scripts/check-file-length.py
  • scripts/run-tests.py
  • scripts/clean-pytest-artifacts.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • scripts/test-scripts/smoke-commands.py
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Prioritize runtime correctness, async/concurrency safety, SQLite transaction safety, auth/rate-limit behavior, release-breaking packaging issues, and MCP protocol compatibility.

Files:

  • scripts/check-file-length.py
  • scripts/run-tests.py
  • scripts/clean-pytest-artifacts.py
  • marm-mcp-server/tests/conftest.py
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • scripts/test-scripts/smoke-commands.py
marm-mcp-server/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/tests/**/*.py: Tests must hit real FastAPI endpoints and real SQLite; mock only when it materially speeds testing and matches real behavior with at least 95% fidelity.
Every new MARM Console API route requires at least one happy-path FastAPI response-contract test with the MCP adapter stubbed.
Do not add existence-check or coded-to-pass tests; prefer deep tests exercising real paths.
Use pytest.mark.skip only for genuinely unavailable dependencies, never for effort.

Files:

  • marm-mcp-server/tests/conftest.py
**/tests/**

⚙️ CodeRabbit configuration file

**/tests/**: Focus on tests that are flaky, non-isolated, incorrectly asserting behavior, or missing coverage for a changed high-risk path. Skip minor naming, comments, and layout preferences.

Files:

  • marm-mcp-server/tests/conftest.py
marm-mcp-server/marm_mcp_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/**/*.py: All memory writes must use the serialized async write queue; do not bypass it.
A semantic-store failure during marm_log_entry must never fail the log write.
Never share database connections between the memory SQLite database and the isolated concept-graph database.
Graph and concept failures must never break the seven core memory tools.
marm_smart_recall must keep primary memory ranking authoritative; graph enrichment is bounded, read-only, fail-open, and trimmed before primary results when enforcing limits.
Use one lazy-loaded, lock-serialized fastembed encoder with 512 dimensions, and allow writes to succeed when the encoder is unavailable.
Prefer the smallest change that solves the task; avoid speculative abstractions and unnecessary configuration flags.
Use minimal comments only for non-obvious rationale; do not narrate the next line.
Keep orchestration in its current owner file and extract modules only at real boundaries.

Files:

  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
{marm-mcp-server/pyproject.toml,marm-mcp-server/server.json,marm-mcp-server/marm_mcp_server/__init__.py,marm-mcp-server/marm_mcp_server/config/settings.py,marm-mcp-server/marm_mcp_server/server.py,marm-mcp-server/Dockerfile,docker-compose.yml,README.md,marm-mcp-server/README.md,marm-mcp-server/marm-docs/README.md,docs/INSTALL-*.md}

📄 CodeRabbit inference engine (AGENTS.md)

When bumping the version, update every listed version source, label, README heading, and installation-document version header, then audit with python scripts/find-versions.py.

Files:

  • marm-mcp-server/marm_mcp_server/config/settings.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: Lyellr88/marm-memory

Timestamp: 2026-07-24T09:16:24.977Z
Learning: Never commit changes without an explicit user request.
Learnt from: CR
Repo: Lyellr88/marm-memory

Timestamp: 2026-07-24T09:16:24.977Z
Learning: Semver uses MAJOR for breaking changes, MINOR for new tools, parameters, or features, and PATCH for fixes and documentation updates.
🪛 ast-grep (0.44.1)
marm-mcp-server/marm_mcp_server/utils/security.py

[error] 38-40: Command coming from incoming request
Context: subprocess.run(
[str(whoami)], check=False, capture_output=True, text=True
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

scripts/test-scripts/smoke-commands.py

[error] 67-69: Command coming from incoming request
Context: subprocess.run(
command, cwd=PACKAGE_ROOT, env=environment, check=False
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 67-69: Use of unsanitized data to create processes
Context: subprocess.run(
command, cwd=PACKAGE_ROOT, env=environment, check=False
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(os-system-unsanitized-data)

🔇 Additional comments (9)
marm-mcp-server/tests/conftest.py (1)

37-52: LGTM!

Also applies to: 77-87

scripts/run-tests.py (1)

97-102: LGTM!

.gitignore (1)

114-115: LGTM!

Also applies to: 125-125

marm-mcp-server/marm_mcp_server/config/settings.py (2)

8-8: LGTM!


125-125: 🎯 Functional Correctness

Version metadata is in sync.

scripts/check-file-length.py (2)

25-27: LGTM!


67-81: LGTM!

scripts/clean-pytest-artifacts.py (1)

48-53: LGTM!

scripts/test-scripts/smoke-commands.py (1)

63-69: LGTM!

Comment thread marm-mcp-server/marm_mcp_server/config/settings.py Outdated
Comment thread marm-mcp-server/marm_mcp_server/utils/security.py Outdated
Second CodeRabbit pass on PR #111:

- security.py: replace the subprocess whoami/icacls approach with a
  native Win32 ctypes DACL replacement (OpenProcessToken -> token SID ->
  SetEntriesInAclW -> SetNamedSecurityInfoW). Spawns no external
  binaries, so the SystemRoot/untrusted-search-path exposure (CWE-426)
  is gone entirely. Verified on Windows: the file ACL collapses to a
  single owner-only full-control entry with inheritance removed.
- settings.py: send the ACL-failure warning to stderr so it cannot
  corrupt the STDIO JSON-RPC stream.
- test_runtime_cli.py: keep the delegation test and add a real
  Windows-gated test that runs the ctypes path and asserts the on-disk
  DACL is owner-only (skipped off win32).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@marm-mcp-server/marm_mcp_server/utils/security.py`:
- Around line 140-146: Update _set_windows_owner_only_dacl() to apply the
process-user SID as the file owner before installing the protected DACL,
including OWNER_SECURITY_INFORMATION and using the write handle with
WRITE_OWNER/WRITE_DAC where applicable. Ensure both ownership and DACL updates
succeed, and fail the operation if either security change fails.

In `@marm-mcp-server/tests/test_runtime_cli.py`:
- Around line 248-251: Update the DACL test setup around the before snapshot and
its “(I)” check to skip when the target does not have inherited ACEs, rather
than failing the test. Keep the ACL behavior assertions active when the
inheritance precondition is present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c2e0a9d-a593-46c4-9d8e-f7fa8925287e

📥 Commits

Reviewing files that changed from the base of the PR and between c561724 and 1f7b120.

📒 Files selected for processing (3)
  • marm-mcp-server/marm_mcp_server/config/settings.py
  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/tests/test_runtime_cli.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • marm-mcp-server/marm_mcp_server/config/settings.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
marm-mcp-server/marm_mcp_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/marm_mcp_server/**/*.py: Route all memory writes through the serialized asynchronous write queue; do not add bypass write paths.
A semantic-store failure during marm_log_entry must never fail the corresponding log write.
Never share SQLite connections between the memory database and the isolated concept-graph database.
Graph and concept failures must never break the seven core memory tools; graph services must start lazily and tolerate degraded operation.
marm_smart_recall must keep primary memory ranking authoritative; graph enrichment is read-only, fail-open, bounded, and must be trimmed before primary results when enforcing response limits.
Use one lazily loaded, lock-serialized jinaai/jina-embeddings-v2-small-en encoder with 512 dimensions; writes must succeed when the encoder is unavailable.
Prefer the smallest change that solves the task; avoid speculative abstractions and unrequested configuration flags.
Keep comments minimal and explain only non-obvious rationale; do not narrate the next line.
Keep orchestration in its current owner file and extract modules only at real boundaries consistent with the existing endpoint split.

Files:

  • marm-mcp-server/marm_mcp_server/utils/security.py
**/*.{py,md,json,toml,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Follow SemVer: MAJOR for breaking changes, MINOR for new tools or parameters or features, and PATCH for fixes and documentation updates.

Files:

  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/tests/test_runtime_cli.py
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Prioritize runtime correctness, async/concurrency safety, SQLite transaction safety, auth/rate-limit behavior, release-breaking packaging issues, and MCP protocol compatibility.

Files:

  • marm-mcp-server/marm_mcp_server/utils/security.py
  • marm-mcp-server/tests/test_runtime_cli.py
marm-mcp-server/tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

marm-mcp-server/tests/**/*.py: Test both HTTP and STDIO transports when adding or changing an MCP tool.
Use real FastAPI endpoints and real SQLite in tests; mock only when it meaningfully speeds testing and matches real behavior with at least 95% fidelity.
Every new MARM Console API route must have at least one happy-path FastAPI response-contract test with the MCP adapter stubbed.
Do not write existence-check or coded-to-pass tests; prefer deep tests exercising real paths over broad shallow coverage.
Use pytest.mark.skip only for genuinely unavailable dependencies, never for effort or convenience.

Files:

  • marm-mcp-server/tests/test_runtime_cli.py
**/tests/**

⚙️ CodeRabbit configuration file

**/tests/**: Focus on tests that are flaky, non-isolated, incorrectly asserting behavior, or missing coverage for a changed high-risk path. Skip minor naming, comments, and layout preferences.

Files:

  • marm-mcp-server/tests/test_runtime_cli.py
🪛 ast-grep (0.44.1)
marm-mcp-server/tests/test_runtime_cli.py

[error] 247-249: Command coming from incoming request
Context: subprocess.run(
["icacls", str(target)], capture_output=True, text=True
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 254-256: Command coming from incoming request
Context: subprocess.run(
["icacls", str(target)], capture_output=True, text=True
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)


[error] 257-257: Command coming from incoming request
Context: subprocess.run(["whoami"], capture_output=True, text=True)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🔇 Additional comments (2)
marm-mcp-server/marm_mcp_server/utils/security.py (1)

3-7: LGTM!

Also applies to: 25-157, 159-163

marm-mcp-server/tests/test_runtime_cli.py (1)

223-237: LGTM!

Comment on lines +140 to +146
advapi32.SetNamedSecurityInfoW(
str(path),
se_file_object,
dacl_security_information | protected_dacl_security_information,
None,
None,
dacl,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate security.py =="
fd -a 'security\.py$' . | sed 's#^\./##'

echo "== inspect relevant file outline =="
ast-grep outline marm-mcp-server/marm_mcp_server/utils/security.py --view compact || true

echo "== inspect relevant lines =="
wc -l marm-mcp-server/marm_mcp_server/utils/security.py
sed -n '1,230p' marm-mcp-server/marm_mcp_server/utils/security.py

echo "== search for managed key security call sites =="
rg -n "SetNamedSecurityInfoW|managed|key|protected_dacl_security_information|OWNER_SECURITY_INFORMATION|SetFileSecurity|Write" marm-mcp-server/marm_mcp_server -S

Repository: Lyellr88/marm-memory

Length of output: 50377


🌐 Web query:

Windows SetNamedSecurityInfo OWNER_SECURITY_INFORMATION required to transfer owner owner NULL previous owner can modify DACL

💡 Result:

To transfer ownership of an object using SetNamedSecurityInfo with the OWNER_SECURITY_INFORMATION flag, a caller must have either WRITE_OWNER access to the object or the SE_TAKE_OWNERSHIP_NAME privilege enabled in their process token [1][2][3]. The process of transferring ownership and subsequently modifying the Discretionary Access Control List (DACL) is generally a two-step procedure [4]. The SE_TAKE_OWNERSHIP_NAME privilege allows you to change the owner of an object [4]. Once you successfully become the owner, you gain the right to modify the DACL (WRITE_DAC access) [4]. You can then perform a second call to SetNamedSecurityInfo using the DACL_SECURITY_INFORMATION flag to grant yourself the necessary permissions (such as GENERIC_WRITE) to access the object [5][4]. If you wish to set the owner to a SID that is not your own, you generally require the SE_RESTORE_NAME privilege [6][7][8]. The SE_RESTORE_NAME privilege is powerful: it grants the holder the ability to set any valid user or group SID as the owner of a file and provides write access control regardless of the existing ACL [6][8]. In summary: 1. To take ownership (set yourself as owner): You need WRITE_OWNER access or the SE_TAKE_OWNERSHIP_NAME privilege [1][2][4]. 2. To set an arbitrary owner (e.g., another user or group): You generally require the SE_RESTORE_NAME privilege [6][8]. 3. To modify the DACL after becoming the owner: Once ownership is transferred, the new owner (you) can use SetNamedSecurityInfo with DACL_SECURITY_INFORMATION to update the access control list [5][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files under path =="
git ls-files 'marm-mcp-server/marm_mcp_server/**/*.py' | sed -n '1,200p'

echo "== exact security.py line count and slice =="
wc -l marm-mcp-server/marm_mcp_server/utils/security.py
sed -n '1,220p' marm-mcp-server/marm_mcp_server/utils/security.py

echo "== focused SetNamedSecurityInfoW and managed/key contexts =="
rg -n "SetNamedSecurityInfoW|create_managed_|managed|private_key|protected_dacl|OWNER_SECURITY_INFORMATION|WRITE_OWNER|WRITE_DAC|CreateFileW|SetFileSecurity" marm-mcp-server/marm_mcp_server -g '*.py'

Repository: Lyellr88/marm-memory

Length of output: 19606


🌐 Web query:

Microsoft SetNamedSecurityInfo WIN32 error 1307 SET_SECURITY_OWNER_OWNER_DEFAULTED NULL owner DACL_OWNER_DEFAULTED previous owner modify DACL

💡 Result:

The Windows error 1307 (0x51B) is defined as ERROR_INVALID_OWNER, meaning "This security ID may not be assigned as the owner of this object" [1]. When encountered while using SetNamedSecurityInfo, this error indicates that the process lacks the necessary privileges or permissions to change the owner to the specified security identifier (SID) [2][3][4]. To successfully modify the owner of an object using SetNamedSecurityInfo, the process must adhere to specific security requirements enforced by the Windows operating system [5]. Key Causes and Solutions: 1. Privilege Requirements: You cannot typically assign an arbitrary SID as an object's owner unless your process has the SE_RESTORE_NAME privilege (often referred to as SeRestorePrivilege) enabled in its access token [2][3][4]. This privilege is designed to allow administrators to restore backed-up files and specifically grants the ability to set any valid user or group SID as the owner of an object [2][4]. 2. Administrative Context: Even if you possess the required privileges, ensure your process is running with elevated privileges (Run as Administrator) [2]. Without elevation, even if the privilege is present in your token, it is likely disabled by User Account Control (UAC) [2]. 3. Ownership Constraints: Under normal, non-privileged circumstances, an object's owner can only be set to an SID that is present in the caller's own token (specifically, an SID that represents a user or group associated with the process) [2][5][6]. If your goal is to modify the Discretionary Access Control List (DACL) while simultaneously modifying the owner, be aware that these are separate operations in the security descriptor [7][8]. If you call SetNamedSecurityInfo with OWNER_SECURITY_INFORMATION, the system will validate the new owner SID against your process privileges [4][5]. If you only intend to modify the DACL, ensure you pass NULL for the owner and group arguments and specify only DACL_SECURITY_INFORMATION in the SecurityInformation parameter [7][8]. Recommended Steps: - Verify that your application enables the SE_RESTORE_NAME privilege before calling SetNamedSecurityInfo [2][4][9]. - Confirm that the SID you are attempting to assign is a valid, well-formed SID [2]. - If you are not explicitly trying to change ownership, ensure you are not inadvertently including OWNER_SECURITY_INFORMATION in your flag bitmask [7][8].

Citations:


🌐 Web query:

DevBlogs oldnewthing SetNamedSecurityInfo owner DACL owner DACL_OWNER_DEFAULTED SetSecurityDescriptorOwner DACL_OWNER_DEFAULTED

💡 Result:

The flags DACL_OWNER_DEFAULTED and SE_OWNER_DEFAULTED are related to how Windows tracks whether security descriptor components (owner or DACL) were provided explicitly or by a default mechanism. SetNamedSecurityInfo and Security Descriptor Flags: SetNamedSecurityInfo is a common Win32 function used to modify security descriptors on securable objects (like files) [1][2]. While the function itself does not take parameters like DACL_OWNER_DEFAULTED directly, it operates on security information flags (such as OWNER_SECURITY_INFORMATION and DACL_SECURITY_INFORMATION) to determine which parts of the security descriptor to update [1][2]. SE_OWNER_DEFAULTED: This is a flag within the SECURITY_DESCRIPTOR_CONTROL structure [3][4]. It indicates that the owner SID was provided by a default mechanism rather than being explicitly specified [3][5]. This flag is managed by functions like SetSecurityDescriptorOwner, which accepts a boolean parameter (bOwnerDefaulted) to toggle the SE_OWNER_DEFAULTED flag [6][5]. DACL_OWNER_DEFAULTED: There is no standard Windows flag named DACL_OWNER_DEFAULTED. This appears to be a confusion of two distinct flags: 1. SE_OWNER_DEFAULTED: Indicates a default owner SID [3][7]. 2. SE_DACL_DEFAULTED: Indicates that the DACL was provided by a default mechanism [3]. Context in Technical Discussions: Technical discussions, such as those on The Old New Thing, frequently address the practical implications of security descriptors, particularly regarding ownership and access control [8][9][10]. A common theme is that simply "taking ownership" (using SE_TAKE_OWNERSHIP_NAME privilege) does not automatically grant full access [8][10]. Instead, it provides the caller with WRITE_DAC access, which then allows the user to modify the DACL to grant themselves the necessary permissions [8][10]. In summary, when working with security descriptors in C++ or other low-level Windows programming, you use SetSecurityDescriptorOwner to control the SE_OWNER_DEFAULTED flag [6], and SetNamedSecurityInfo to apply those changes to objects [1][2]. The term DACL_OWNER_DEFAULTED is not a valid constant; ensure you are referencing SE_OWNER_DEFAULTED or SE_DACL_DEFAULTED as required for your specific security descriptor manipulation [3].

Citations:


🌐 Web query:

Microsoft DACL_OWNER_DEFAULTED previous owner can set owner DACL_SECURITY_INFORMATION ACE

💡 Result:

In Windows security architecture, the terms SE_OWNER_DEFAULTED and SE_DACL_DEFAULTED are control flags within a security descriptor, not an Access Control Entry (ACE) type [1][2]. SE_OWNER_DEFAULTED (0x0001) is a flag in the SECURITY_DESCRIPTOR_CONTROL structure indicating that the object's owner security identifier (SID) was provided by a default mechanism (such as the primary group or the creator's token) rather than being explicitly set by the object's creator [1][2][3]. Similarly, SE_DACL_DEFAULTED (0x0008) indicates that the Discretionary Access Control List (DACL) was assigned by a default mechanism [1][2][3]. These flags are used primarily by resource managers to track how security components were assigned and how they should be inherited [1][2][3]. Regarding the ability of a previous owner to set an owner or modify a DACL: 1. Ownership and Permissions: The owner of an object implicitly possesses WRITE_DAC and READ_CONTROL permissions [4]. This allows the owner to modify the object's DACL (i.e., change access control) [4]. 2. Changing Ownership: A user can only change the owner of an object if they have the WRITE_OWNER permission for that object, or if they possess the SE_RESTORE_NAME privilege [4]. Simply having been the previous owner does not grant a user the right to change the current owner unless they still retain those specific rights or privileges [4]. 3. Taking Ownership: Any user with the SE_TAKE_OWNERSHIP privilege enabled can take ownership of an object [4]. Additionally, members of the Administrators group have the inherent capability to take ownership of any object, regardless of who the current owner is [5]. DACL_SECURITY_INFORMATION is a flag used with functions like SetNamedSecurityInfo or SetSecurityInfo to specify that the caller intends to modify the object's DACL [6][7][8]. It is not an ACE. When using these functions, the system checks if the caller has the necessary permissions (typically WRITE_DAC) to perform the requested operation [6][7]. In summary, a previous owner does not retain special rights to set the owner or modify the DACL solely by virtue of their former status [4]. These actions are strictly governed by the current security descriptor's permissions (WRITE_DAC, WRITE_OWNER) and specific system privileges (e.g., SE_TAKE_OWNERSHIP, SE_RESTORE_NAME) [4][8].

Citations:


🌐 Web query:

"SetNamedSecurityInfo OWNER_SECURITY_INFORMATION SET_SECURITY_OWNER_OWNER_DEFAULTED"

💡 Result:

The flags referenced in your query, OWNER_SECURITY_INFORMATION and SET_SECURITY_OWNER_OWNER_DEFAULTED, pertain to different levels of the Windows security API and serve distinct purposes [1][2][3][4]. OWNER_SECURITY_INFORMATION is a bit flag used with the SetNamedSecurityInfo (and SetSecurityInfo) functions [1][3][5]. When calling SetNamedSecurityInfo, you must include this flag in the SecurityInfo parameter to indicate that the psidOwner argument contains a valid Security Identifier (SID) that should be applied as the new owner of the object [1][6][5]. SET_SECURITY_OWNER_OWNER_DEFAULTED (often represented as the SE_OWNER_DEFAULTED control flag) is not a flag used by SetNamedSecurityInfo [1][3][5]. Instead, this concept is associated with lower-level security descriptor functions such as SetSecurityDescriptorOwner or RtlSetOwnerSecurityDescriptor [2][4]. In those functions, a boolean parameter (often named bOwnerDefaulted) is used to specify whether the owner information was derived from a default mechanism [2][4]. If set to TRUE, the system sets the SE_OWNER_DEFAULTED flag in the security descriptor's control field [2][4]. In summary, SetNamedSecurityInfo provides a high-level interface that manages these details automatically [7]. If you need to explicitly control the SE_OWNER_DEFAULTED flag, you would typically manipulate the security descriptor directly using functions like SetSecurityDescriptorOwner, rather than through the SetNamedSecurityInfo function [2][4][7].

Citations:


🌐 Web query:

"SetNamedSecurityInfo DACL_SECURITY_INFORMATION DACL_OWNER_DEFAULTED previous owner DACL modified"

💡 Result:

When using the SetNamedSecurityInfo function, the behavior of security descriptor control flags, such as those related to the owner (e.g., SE_OWNER_DEFAULTED) or DACL (e.g., SE_DACL_DEFAULTED), is handled automatically by the system [1]. Specifically, when you use SetNamedSecurityInfo to modify a component of an object's security descriptor—such as providing a new DACL via the pDacl parameter with the DACL_SECURITY_INFORMATION flag—the system automatically updates the associated control bits in the security descriptor [1]. If you explicitly provide a DACL, the system effectively clears any "defaulted" status for that component because the DACL is no longer being supplied by a default mechanism (such as the creator's token), but rather by the explicit input provided to the function [2][3]. Key points regarding this behavior: 1. Automatic Management: Developers do not manually set these control flags when using SetNamedSecurityInfo; the function manages them as a side effect of applying the new security information [1]. 2. Defaulted Status: Flags like SE_DACL_DEFAULTED or SE_OWNER_DEFAULTED are designed to indicate whether a component was assigned by a default mechanism [2][3]. When an explicit security component is applied, these flags are updated to reflect that the component is no longer "defaulted" [1]. 3. Scope: The SetNamedSecurityInfo function is intended for setting security information by name and abstracts the underlying manipulation of the security descriptor control structure [4][1]. If fine-grained control over these specific bits is required, lower-level functions like SetSecurityDescriptorDacl or SetSecurityDescriptorOwner are typically used instead, though these are not used with the SetNamedSecurityInfo call itself [2][3]. In summary, you do not need to worry about the state of the owner-defaulted or DACL-defaulted flags when calling SetNamedSecurityInfo; the system performs the necessary logic to maintain the integrity of the security descriptor's control information based on the parameters you provide [1].

Citations:


🌐 Web query:

Microsoft SetSecurityDescriptorSelf relative security descriptor DACL_OWNER_DEFAULTED DACL_OWNER_DEFAULTED true owner DACL modification ACE

💡 Result:

The terms you mentioned relate to managing security descriptors in Windows, specifically how system-provided default values are flagged versus explicit user-provided values. SetSecurityDescriptorOwner This Win32 API function sets the owner information in an absolute-format security descriptor [1][2]. The function signature includes a parameter, bOwnerDefaulted, which informs the system whether the provided owner SID was assigned by a default mechanism or explicitly specified [1][3]. SE_OWNER_DEFAULTED Flag When you call SetSecurityDescriptorOwner, passing TRUE for the bOwnerDefaulted parameter causes the system to set the SE_OWNER_DEFAULTED flag within the SECURITY_DESCRIPTOR_CONTROL structure [1][2]. This flag serves as a hint to resource managers or the system that the owner SID was derived from a default source (such as the creator's access token) rather than being explicitly set by an application [4][5]. DACL and ACE Modification It is important to distinguish between owner flags and DACL (Discretionary Access Control List) flags: 1. SE_DACL_DEFAULTED: Similar to the owner flag, this indicates a default DACL was applied (e.g., from an access token or parent object) [4][5]. It is set using the SetSecurityDescriptorDacl function [6][7]. 2. ACE Modification: These "defaulted" flags (SE_OWNER_DEFAULTED, SE_DACL_DEFAULTED) do not inherently prevent or modify individual Access Control Entries (ACEs). Instead, they are primarily used by the system to determine how to handle inheritance or subsequent modifications when an object is created [6][4]. For example, if a DACL is marked as defaulted, the system may treat it differently during inheritance operations compared to a DACL that was explicitly defined by a user [4][8]. Summary of Key Differences: - SE_OWNER_DEFAULTED: Managed via SetSecurityDescriptorOwner; relates to the object's owner [1][6]. - SE_DACL_DEFAULTED: Managed via SetSecurityDescriptorDacl; relates to the object's discretionary access list [6][5]. - Self-Relative Format: Note that SetSecurityDescriptorOwner, along with other low-level setter functions, typically requires an absolute-format security descriptor [1][9]. Self-relative descriptors are generally used for persistent storage or transmission and may require conversion before modification [10]. These flags do not modify the ACEs themselves but provide metadata that the operating system uses for authorization logic and inheritance behavior [4][5].

Citations:


🌐 Web query:

"Owning the object" "can always change its" "owner" "Ace" "default DACL"

💡 Result:

The statement that owning an object allows one to change its owner is not universally true in Windows; rather, it is a common misunderstanding of how the WRITE_OWNER permission and ownership rights interact [1][2]. In Windows security, the owner of an object possesses the inherent right to modify the object's discretionary access control list (DACL), allowing them to grant themselves or others the WRITE_DAC (Change Permissions) and WRITE_OWNER (Change Owner) permissions [3][4][5]. However, simply being the owner does not automatically grant the ability to perform every action on the security descriptor [1][2]. Key distinctions include: 1. Ownership vs. WRITE_OWNER: Being the owner of an object grants "implicit" control to modify permissions (DACL) [3][4][1]. However, the ability to change the owner of an object (setting a new owner) specifically requires the WRITE_OWNER permission [6][7][8]. While an owner can grant this right to themselves via the DACL, the privilege is not inherently absolute for all scenarios [3][2]. 2. Taking Ownership: A user can take ownership of an object if they have the TAKE_OWNERSHIP permission or the SeTakeOwnershipPrivilege (typically held by Administrators) [4][1]. Once a user successfully takes ownership, they effectively become the owner and can then modify the DACL to grant themselves further rights, such as WRITE_OWNER, if they do not already possess them [1][9]. 3. Limitations on Changing Owner: A standard user or even an object owner cannot necessarily "give" ownership to another user at will [2][8]. The WRITE_OWNER right generally allows a principal to set themselves as the new owner [2][8]. Changing the owner to a different user typically requires the SeRestorePrivilege, which is a powerful administrative privilege [1][2]. In summary, while the owner of an object has broad control, the act of changing ownership is a distinct operation regulated by the WRITE_OWNER permission and specific system privileges (e.g., SeRestorePrivilege), rather than being a direct, unconditional consequence of current ownership [1][2][6].

Citations:


🌐 Web query:

"the DACL contains DACL_OWNER_DEFAULTED"

💡 Result:

The term you are referring to is SE_DACL_DEFAULTED (0x0008), which is a security descriptor control flag in the Windows operating system [1][2]. This flag indicates that a discretionary access control list (DACL) was provided by a default mechanism, rather than being explicitly specified by the creator of an object [1][3][2]. For example, if an object creator does not specify a DACL when creating a new securable object, the system may apply a default DACL from the creator's access token or from the object class schema [4][3][5]. When this happens, the SE_DACL_DEFAULTED flag is set to inform the system that this DACL was assigned automatically [3][2]. Key characteristics of this flag include: Effect on Inheritance: The presence of this flag can influence how the system handles access control entry (ACE) inheritance [1][2]. Dependency: The system ignores this flag if the SE_DACL_PRESENT flag (0x0004) is not set [1][2]. Not Physically Stored: While used to determine how the final DACL is computed, this flag is not stored physically in the security descriptor control of the actual securable object [1][2]. String Format: This flag is not included in security descriptor string representations (e.g., SDDL strings), unlike SE_DACL_PRESENT, which is represented by the D: token [6]. In summary, SE_DACL_DEFAULTED serves as a metadata indicator used by the system to distinguish between a manually defined DACL and one generated via default system processes [1][7][3].

Citations:


🌐 Web query:

"SetEntriesInAcl SetEntriesInAclW DACL_OWNER_DEFAULTED DACL contains DACL_OWNER_DEFAULTED"

💡 Result:

The SetEntriesInAcl and SetEntriesInAclW functions are used to create or modify Access Control Lists (ACLs) by merging new Access Control Entries (ACEs) into an existing ACL [1][2][3]. The term DACL_OWNER_DEFAULTED is not a standard, standalone flag used in the context of DACLs. Instead, security descriptors (which contain DACLs) utilize specific control flags [4][5]. The flag corresponding to the concept of a default DACL is SE_DACL_DEFAULTED [4][5][6]. Similarly, there is an SE_OWNER_DEFAULTED flag, which relates to the owner SID of a security descriptor [4][5][6]. Regarding the interaction between these functions and flags: 1. Function Scope: SetEntriesInAcl and SetEntriesInAclW operate specifically on the structure of the ACL (the list of ACEs) itself [3][7]. They do not modify the security descriptor control flags directly [4][8]. 2. Flag Management: The SE_DACL_DEFAULTED flag is a control bit in the security descriptor, not a property of the DACL structure itself [4][5][6]. It is managed via functions like SetSecurityDescriptorDacl [4][8]. 3. Relationship: The SetEntriesInAcl/SetEntriesInAclW functions do not set or clear the SE_DACL_DEFAULTED flag [4][6]. This flag is typically set by the system (or by specific security descriptor management functions) when a DACL is provided by a default mechanism (e.g., from an access token) rather than being explicitly defined by the creator of the object [6]. In summary, SetEntriesInAcl and SetEntriesInAclW are limited to manipulating the list of ACEs [2][3]. They do not interact with or set the SE_DACL_DEFAULTED or SE_OWNER_DEFAULTED flags [4][6].

Citations:


Security Misconfiguration (CWE-732): Incorrect Permission Assignment for Critical Resource

Reachability: External
● Entry
  marm-mcp-server/marm_mcp_server/config/settings.py:400
  restrict_windows_file_to_current_user
│
▼
● Sink
  marm-mcp-server/marm_mcp_server/utils/security.py

Transfer ownership before installing the protected DACL

_set_windows_owner_only_dacl() installs a DACL-only update with None owner and no OWNER_SECURITY_INFORMATION, so the file’s previous owner can still use the retained implicit owner rights to restore a wider DACL after key initialization. Apply the process-user SID as the owner with OWNER_SECURITY_INFORMATION, ideally through the handle opened for the write path with WRITE_OWNER/WRITE_DAC, and fail if ownership or the DACL cannot both be secured.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/marm_mcp_server/utils/security.py` around lines 140 - 146,
Update _set_windows_owner_only_dacl() to apply the process-user SID as the file
owner before installing the protected DACL, including OWNER_SECURITY_INFORMATION
and using the write handle with WRITE_OWNER/WRITE_DAC where applicable. Ensure
both ownership and DACL updates succeed, and fail the operation if either
security change fails.

Comment on lines +248 to +251
before = subprocess.run(
["icacls", str(target)], capture_output=True, text=True
).stdout
assert "(I)" in before, "fixture file should start with inherited ACEs"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file metadata =="
wc -l marm-mcp-server/tests/test_runtime_cli.py || true

echo "== relevant code section =="
sed -n '210,280p' marm-mcp-server/tests/test_runtime_cli.py

echo "== test imports/top =="
sed -n '1,60p' marm-mcp-server/tests/test_runtime_cli.py

echo "== references to icacls and target fixture setup =="
rg -n "icacls|target|fixture|Inherited|inherited|\\(I\\)" marm-mcp-server/tests/test_runtime_cli.py || true

Repository: Lyellr88/marm-memory

Length of output: 5205


🌐 Web query:

Microsoft icacls command inherited ACEs I parenthesis documentation

💡 Result:

In the output of the Windows icacls command, the parenthesis (I) signifies that an Access Control Entry (ACE) is inherited from a parent container [1][2][3]. When you run icacls on a file or directory, the command displays the Access Control List (ACL) [3]. Each ACE in that list may include inheritance flags within parentheses to describe how the permission is applied and where it originated [1][2][4]. Key details regarding (I) and related inheritance flags include: (I) Inherited: This ACE was inherited from the parent container [1][3]. If an ACE entry does not contain (I), it is an explicit permission set directly on that specific object [4]. Other common inheritance flags (always used with parenthesis) include: (OI) Object Inherit: The ACE is inherited by files created within this container [1][3]. (CI) Container Inherit: The ACE is inherited by subfolders (subcontainers) created within this container [1][3]. (IO) Inherit Only: The ACE is inherited by child objects, but does not apply to the current object itself [1][3]. (NP) Don't Propagate Inherit: The ACE is inherited by children, but the inheritance does not continue (propagate) further down to nested containers [1][3]. These flags appear before the permission mask (e.g., F for Full Access, R for Read) in the icacls output [1][2][3]. For example, an entry like (I)(OI)(CI)(RX) indicates an inherited permission that applies to the current object, its files, and its subfolders with Read and Execute access [3][4].

Citations:


Don’t fail the DACL test on ACL inheritance policy.

"(I)" in before makes the test dependent on the temp folder ACL policy; on a hardened environment with inheritance turned off or replaced by a managed ACL, the test can skip the real behavior under test. Skip only when the inheritance precondition is not set up, rather than asserting it hard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@marm-mcp-server/tests/test_runtime_cli.py` around lines 248 - 251, Update the
DACL test setup around the before snapshot and its “(I)” check to skip when the
target does not have inherited ACEs, rather than failing the test. Keep the ACL
behavior assertions active when the inheritance precondition is present.

@Lyellr88
Lyellr88 merged commit 7dbdbde into MARM-main Jul 24, 2026
6 checks passed
@Lyellr88
Lyellr88 deleted the release/v2.29.0 branch July 24, 2026 09:56
Lyellr88 added a commit that referenced this pull request Jul 24, 2026
Final CodeRabbit findings from PR #111, applied directly to main to
close out the review loop:

- security.py: SetNamedSecurityInfoW now also sets the process-user SID
  as owner (OWNER_SECURITY_INFORMATION) alongside the protected DACL.
  Without transferring ownership, a prior owner keeps implicit WRITE_DAC
  and could reopen the DACL after lockdown (CWE-732). Verified on
  Windows: owner and DACL both resolve to the current user only.
- test_runtime_cli.py: the real DACL test now skips when the temp dir
  has no inherited ACEs instead of hard-asserting the precondition, so
  hardened environments don't turn a missing precondition into failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant