neuralmind.uk / NeuralMind v4.17.1 — three security fixes: a CUI profile a developer can’t delete, roles off the MCP path, and an audit chain that survives concurrent writers Edit this page on GitHub ↗

NeuralMind v4.17.1 — three security fixes: a CUI profile a developer can’t delete, roles off the MCP path, and an audit chain that survives concurrent writers

Type: Patch release Theme: controls that hold when someone tests them. An audit of the compliance documents against the code found three controls that didn’t do what the documents said

An assessor checks a control by trying it. Three of NeuralMind’s failed that test:


What changed

A machine policy keeps the CUI profile on

An administrator can now write the profile where a project’s owner can’t reach it:

# /etc/neuralmind/policy.yaml          (POSIX: root:root, mode 0644, directory 0755)
# C:\ProgramData\NeuralMind\policy.yaml (Windows: Administrators full control, Users read)
security:
  profile: cui

neuralmind doctor names the machine policy when one applies:

  [ ok ] Security profile: profile: cui (identity: os, encrypted storage required, servers on loopback with their token only, LLM seeding refused); OS FIPS mode on; machine policy /etc/neuralmind/policy.yaml applies

Roles apply to the daemon and the CLI’s writes

Under security.identity: os (which profile: cui forces), each daemon route that acts on a project needs the role the MCP tool doing the same thing needs:

Daemon route Needs
/query, /search, /stats, /savings neuralmind_query, neuralmind_search, neuralmind_stats, neuralmind_savings
/build neuralmind_build
/validate neuralmind_health, or neuralmind_build with write: true

neuralmind build and neuralmind validate --write need neuralmind_build too. A custom security.roles applies as it does to MCP calls, and a broken one refuses. A refusal writes a security/call_denied audit record naming the surface. The daemon answers 403 with code: security_denied and reason rbac, identity or config; the CLI prints the reason and exits 1:

neuralmind: refused: OS account 'bob' has role 'reader', which may not use neuralmind_build (cli)

Run `neuralmind doctor` to see the Security policy check.

With identity: declared (the default) nothing off the MCP path names a role, so nothing is checked, as before. Other CLI commands and the hooks run with the account’s own file permissions, which stay the boundary against a local account that owns the project’s files.

The audit chain survives concurrent writers

Each append now holds an OS lock on .neuralmind/audit_events.lock (flock on POSIX, msvcrt.locking on Windows) while it reads the last record’s hash and writes the new record, and so does audit rotate. Without it, a rotation could also send a record to the archive after its last hash was read, or truncate it away when the continuation marker opened the new file.

A log written by an earlier version may still fail verify where two records share a prev_sha256 with timestamps within a second of each other. That was this race, not an edit; a checkpoint (neuralmind audit checkpoint, v4.16.0) taken since tells the two apart.

What the agent actually sees

Nothing changes for an agent on a project with identity: declared.

On a project with identity: os or profile: cui, the MCP tools behave as before. What changes is around them:

Per-agent expectations

Agent What changes
Claude Code (hooks + MCP) Under identity: os, the hook-triggered rebuild needs neuralmind_build; MCP calls unchanged
Cursor / Cline / Continue (MCP) MCP calls unchanged; daemon clients get 403 for a route their role lacks
Hermes-Agent plugin As Claude Code
Generic MCP client As Cursor

Upgrading

Reproduce

pytest tests/test_machine_policy.py -v       # deleting the project file keeps the profile; tighten-only; untrusted files
pytest tests/test_local_role_checks.py -v    # daemon routes and CLI writes need the role the MCP tool needs
pytest tests/test_audit_new.py -k "processes_appending or rotation_during" -v   # concurrent writers keep the chain
pytest tests/test_cli_exit_codes.py -k build-refused-by-role