Announcing Beacon: Self-improving memory for coding agents
← All MCP servers
MCP server

io.github.yaminbkk/nexusmem

mcp:yaminbkk/nexusmem

Local-first memory for AI coding agents: shell history with exit codes, git diffs, docs, MCP.

Verdict
Suspicious

Risky behavior the analysis could not rule out.

Analyzed Oct 26, 2026, 13:10 UTC at e184296

Dispute security verdict

If you believe this verdict is incorrect, submit a dispute and we'll review it.

1
findings
high
highest severity
1
threat classes
331
files scanned
2,434/2,434
checks satisfied
0
package advisories

Why this verdict

  • suspicious: unverified finding mcp-server:.:src/cli/commands/hook.ts:36:PERSIST

Security findings 1

Persistence

highUnverifiedlow confidence

The `hook install` command writes a block into the user's shell profile so that every later shell session logs each command's timestamp, cwd and exit code to disk; this is a documented, opt-in, removable feature of the tool.

will now log their timestamp, cwd and exit code
vector:code

Open questions 5

What the analysis could not settle from the code alone.

[file request src/hooks/install.ts] Is writing the rc-file hook (installHook) only reached via an explicit opt-in command, and is there a matching remove path that uses the marker-based strip?
Persistence
[file request src/cli/agent-hook.ts] What does the capture hook entry point do with the hook payload: does it only redact and append to a local log, or does it send data over the network?
Data exfiltration
[file request src/hooks/recorder-command.ts] Which recorder script does the shell hook invoke, and does it only hash, redact and append to the local log, with no network I/O?
Data exfiltration
[file request src/shell/install.ts] How are resolveBashProfilePath/resolveZshProfilePath used? Does the code write to rc files, and is that write explicit opt-in, marker-delimited and reversible?
Persistence
[file request src/agent/hook-command.ts] What exact commands are registered as Claude Code hooks, and does the capture/recall handler send data off-host or only write local storage?
Data exfiltration
Refuted findings 23

Candidates the review stage examined and rejected, kept for transparency.

Persistence

criticalRefutedmedium confidence

The adapter upserts NexusMem hooks for SessionStart and PostToolUse events into Claude Code settings (agent control-plane config), capturing Bash/Edit/Write tool events; this matches the declared purpose, preserves other tools' hooks, and writing the file is left to the CLI.

hooks[event] = [...withoutOurs(hooks[event] ?? []), ...matcherFor(commands, event)];
Why it was refuted

The hook upsert is the declared, opt-in Claude Code hook feature. It removes only NexusMem's own entries and leaves other tools' hooks alone, so it is neither undeclared nor harmful beyond the core capability.

Persistence

criticalRefutedmedium confidence

The CLI's agent install command writes NexusMem hook commands into Claude Code settings files (settings.local.json), which then run on agent events to capture commands and inject recall output; this is a control-plane write, though it is the tool's declared, user-invoked setup.

Project scope uses settings.local.json: a committed settings.json would
Why it was refuted

The settings write only happens when the user runs `agent install`. It is declared, it prints exactly what it captures, `agent remove` undoes it, and project scope deliberately uses the local settings file so teammates are not affected.

Persistence

criticalRefutedlow confidence

The CLI exposes an opt-in `agent install` command that writes NexusMem hooks into Claude Code settings (user-level or project .claude/settings.local.json), an agent control-plane write that is declared in the command description but whose implementation is not shown here.

write to this repository's .claude/settings.local.json
Why it was refuted

The `agent install` command is declared as opt-in in its own description, and its implementation is shown in agent.ts as an explicit, removable settings write. That makes this the declared core capability.

Persistence

criticalRefutedmedium confidence

The shell hook installer writes a capture snippet into the user's shell profile (rc file) and can strip it again; this is the declared shell-history capture feature, and the D18 'pipeline does not change' claim concerns ranking, not file writes, so the detector contradiction is not a real one.

await writeFile(target.profilePath, next, 'utf8');
Why it was refuted

The profile write is the declared, opt-in shell-hook install, and `removeHook` strips it again. The D18 contradiction is spurious because D18 is a claim about ranking, not about file writes.

Persistence

criticalRefutedmedium confidence

The installer generates a PowerShell profile block that overrides the global prompt function to capture each command line, cwd and exit code and pass them to a spawned node recorder, a persistent profile modification logging all commands.

function global:prompt {
Why it was refuted

The prompt override is the shell-command-logging hook that is NexusMem's declared core capability (search_memory covers 'shell commands'). It is marker-delimited, the raw command goes only to the recorder's stdin, and the recorder hashes and redacts before writing.

Persistence

criticalRefutedmedium confidence

The installer generates a ~/.zshrc block that registers preexec/precmd hooks to record every command with cwd and exit code via a background node recorder, a persistent shell-profile modification that logs all typed commands.

precmd_functions=(__nxm_precmd "${precmd_functions[@]}")
Why it was refuted

The preexec/precmd hook is the openly documented core shell-memory feature. The raw command is only piped to the recorder, which hashes and redacts it, and the block is marker-delimited with a strip function for cleanup.

Persistence

highRefutedlow confidence

The `hook git-post install` command installs a git post-commit hook that launches a detached background `nexusmem sync` after every commit, and it can be appended to an existing foreign hook; this is documented and removable.

in the background after each commit
Why it was refuted

The post-commit hook is an explicitly declared opt-in feature. It is removable, the output tells the user it runs in the background, and it only appends to a foreign hook when the user passes --force.

Persistence

highRefutedmedium confidence

The `hook install` command modifies the user's shell profile to log every command's timestamp, cwd and exit code; it is explicit, user-invoked, and documented in output, but is a persistent host-config write outside the declared MCP tools.

const result = await installHook(target);
Why it was refuted

`hook install` is the declared, opt-in shell-hook feature. It runs only on explicit user invocation, prints the profile and log paths it uses, and `hook remove` undoes it.

Persistence

highRefutedlow confidence

The bash hook module inserts a command-logging block into the user's ~/.bashrc that takes over the global DEBUG trap and records every shell command, which is persistent host-config modification and shell activity capture, though it is the disclosed core feature.

Generates and manages the block NexusMem inserts into `~/.bashrc` to log
Why it was refuted

The bash hook block is the declared, opt-in shell-hook feature. The module documents how it works, takes the DEBUG trap only when it is unset, and is removed with `hook remove`.

Persistence

highRefutedmedium confidence

The bash hook snippet modifies ~/.bashrc with a DEBUG trap and PROMPT_COMMAND that pipes every shell command, cwd and exit code to a node recorder that logs to a file. This is persistent command logging, though the snippet itself does no network send.

--log "$__nxm_log_path" --shell bash-hook >/dev/null 2>&1 & )
Why it was refuted

The snippet is the declared opt-in shell logger. It takes over the DEBUG trap only when nothing else holds it. The recorder redacts commands before writing them, and the snippet makes no network call.

Persistence

highRefutedmedium confidence

The installer generates a ~/.bashrc block that hooks PROMPT_COMMAND and the DEBUG trap to capture every shell command with its cwd and exit code and pipe it to a background recorder, which is a persistent shell-profile modification that logs all typed commands, including any secrets in them, to disk.

PROMPT_COMMAND="__nxm_precmd${PROMPT_COMMAND:+; $PROMPT_COMMAND}"
Why it was refuted

The PROMPT_COMMAND/DEBUG-trap block is the declared shell-logging feature. It is opt-in and removable, the recorder redacts commands, and the DEBUG trap is taken only when unset.

Persistence

highRefutedmedium confidence

The generic git hook installer writes and chmods executable hook files at a dynamic hookPath, appending to foreign hooks under --force; this is host-config persistence beyond the MCP tool surface, but it is marker-delimited, removable and refuses foreign hooks by default. The detector's contradiction with the spec's ranking-pipeline claim (D18) is a false positive.

await writeFile(target.hookPath, next, 'utf8');
Why it was refuted

This is the generic installer behind the declared opt-in git hooks. It refuses foreign hooks unless --force is passed, and it is marker-delimited and removable. The D18 contradiction is a false positive because that claim is about the ranking pipeline.

Persistence

highRefutedlow confidence

The generated post-commit hook launches a detached background `nexusmem sync` after every commit with output redirected to a log file, running silently without user interaction; this is documented, installed via an explicit command, and removable.

nohup sh -c
Why it was refuted

The detached background sync is the declared purpose of the opt-in post-commit hook. It is installed by an explicit command, removable, and only runs the project's own `nexusmem sync`.

Persistence

highRefutedlow confidence

The post-commit git hook snippet launches a detached nohup background 'nexusmem sync' resolved from PATH after each commit, so a PATH-shadowed nexusmem binary would run silently with output sent to a log file.

nexusmem sync --quiet --auto >>${LOG_PATH} 2>&1
Why it was refuted

Resolving `nexusmem` from PATH is how any installed CLI is invoked, and the hook is a declared opt-in feature. A PATH-shadowing attack would need prior control of the user's environment, so the snippet adds no new harm.

Persistence

highRefutedlow confidence

The git post-commit hook installer adds a block to .git/hooks/post-commit that launches a detached background `nexusmem sync` after every commit, logging to .nexusmem/post-commit-sync.log. This is declared, local-only behavior.

detached (`nohup ... &`) so the hook process exits immediately once it has
Why it was refuted

The module documents its own detached background sync and log destination. It is declared, local-only and opt-in, so it is the core capability and nothing is concealed.

Persistence

highRefutedlow confidence

The PowerShell hook module inserts a block into the user's PowerShell profile that wraps `prompt` to log every command with its cwd and exit code; this is declared core functionality, with commands redacted by the recorder.

Generates and manages the block NexusMem inserts into a PowerShell profile
Why it was refuted

The PowerShell profile block is the declared opt-in shell hook. It wraps the existing prompt without replacing it, and the raw command only reaches the recorder's stdin, which hashes and redacts it.

Persistence

highRefutedlow confidence

The zsh hook module writes a persistent block into the user's ~/.zshrc that records every shell command, cwd and exit code to a hook log; this is the declared core feature, and the raw command is redacted before persistence.

Generates and manages the block NexusMem inserts into `~/.zshrc` to log
Why it was refuted

The finding itself says this is the declared core feature. The module header states the purpose openly, and the block is idempotent and strippable.

Destructive action

highRefutedmedium confidence

The sync_project tool, whose declared purpose is ingesting history, also exposes an agent-callable irreversible deletion of all nodes from a source (pruneSource/pruneStaleShell with yes=true); it is disclosed and dry-run by default, but it is a destructive capability beyond a sync tool.

dry-run unless yes is also true, since this is an irreversible full wipe of that source.
Why it was refuted

The prune option is fully disclosed in the tool description. It is dry-run unless yes=true, and it only deletes NexusMem's own derived memory nodes from a stale source, not user files.

Destructive action

highRefutedlow confidence

The MCP sync_project tool forwards agent-supplied pruneSource/pruneStaleShell/yes parameters to runSync, letting a connected agent irreversibly delete memory nodes from the store without separate user confirmation.

pruneSource: input.pruneSource,
Why it was refuted

The forwarding of prune parameters implements the disclosed tool schema. Nothing is removed unless the explicit yes confirmation is passed, and the deletion is limited to NexusMem's own memory nodes.

Credential access

highRefutedlow confidence

The server resolves and reads the user's ~/.bash_history and ~/.zsh_history, which are undeclared in the target description and can contain secrets; this fits the shell-history memory purpose, and the recorder redacts content, so it is a contextual risk.

join(homedir(), '.bash_history')
Why it was refuted

The snippet only resolves a history file path. Shell-command memory is a declared capability of search_memory, so the shell-history path is not undeclared, and no exfiltration or read-to-sink chain is shown.

Persistence

highRefutedlow confidence

The server resolves the user's ~/.bashrc and ~/.zshrc profile paths (undeclared), presumably to install a shell hook; the write itself is not shown in this batch.

return join(homedir(), '.bashrc');
Why it was refuted

The function only returns the conventional ~/.bashrc path, and the documented purpose is the declared shell-hook feature. No write or concealed behavior is shown at this location.

Reconnaissance

lowRefutedlow confidence

The memory server locates and reads the user's Claude Code session transcripts under ~/.claude/projects, which can hold sensitive conversation text; they are indexed locally after redaction and no network sink appears in these units.

return join(homedir(), '.claude', 'projects', claudeProjectSlug(repoRoot));
Why it was refuted

The function only builds the transcript directory path for the current repo's slug. Reading conversation transcripts is the declared `scan-conversation` feature of a memory tool, and no network sink is shown.

Reconnaissance

lowRefutedlow confidence

search_memory accepts an allProjects flag that searches every repository NexusMem has been run in on this machine, reaching beyond the supplied projectRoot; it is disclosed in the tool schema.

Search every repository NexusMem has been run in on this machine, not just projectRoot.
Why it was refuted

The allProjects option is openly described in the tool schema. It only widens a search over NexusMem's own local memory store, which is the declared purpose of search_memory, and results are tagged by repository.