Skip to content

Security: SSanderV/agent-markdown-link

Security

SECURITY.md

Security and privacy

Agent Markdown Link is a local file tool. It is designed to reduce accidental disclosure and canonical-note damage, not to protect data from another process or user that already controls the same account.

Reporting a vulnerability

Use GitHub private vulnerability reporting for security issues. Do not include real vault contents, credentials, or other personal data. General bugs belong in the public issue tracker.

Security fixes are provided for the current 0.6.x release line while it remains supported. Pre-release snapshots and modified forks are not supported by the maintainer.

Runtime guarantees

  • Normal CLI and stdio MCP operation performs no network, Git, shell, child-process, telemetry, or Obsidian activity.
  • Context reads are limited to configured vault-relative regular files. Lexical validation and real-path checks reject traversal and linked paths outside the vault.
  • Search recursively reads regular Markdown files only below the selected project's configured vault-relative searchRoots plus the shared memoryPath when configured. It skips discovered links, never writes an index or cache, and applies fixed scan, source, result, excerpt, and output bounds.
  • Candidate publication uses a same-directory private temporary file and a no-replace hard link. An existing destination is never overwritten, and there is no copy or rename fallback.
  • Search and candidate input, plus all intended context, search, and candidate output, are byte-bounded. Errors contain only stable codes and fixed messages.
  • Queries, note bodies, excerpts, candidate bodies, prompts, credentials, absolute paths, and session data are not written to logs or metrics.
  • Created directories and files request modes 0700 and 0600 on POSIX. Windows uses inherited ACLs and makes no ACL-rewrite claim.

The Codex SessionStart hook reads one bounded JSON object and operationally uses only its event name, source, and working directory. Extra host fields are ignored; the runtime does not read transcripts. Successful curated context is capped at 9,000 UTF-8 bytes, and serialized hook stdout is capped at 32,768 bytes.

The trusted PreToolUse hook runs only for the three bundled Codex memory tools. It validates the host-supplied absolute workspace, selects exactly one configured project, and creates a random one-time workspace capability bound to that tool name and the exact original input. The hook preserves the tool input and adds only the reserved capability. Its allow decision enables that rewrite; Codex still applies its MCP approval policy afterward.

Capability records live in a codex-capabilities directory under the private state root, which must resolve outside the vault. The raw token is not stored in that record. Each record requests mode 0600, the directory requests 0700, and a capability expires after 10 minutes. For a call that reaches MCP, the runtime removes the record before validation and tool execution, then checks its expiry, tool name, exact input digest, workspace mapping, and project ID. A host-rejected call leaves its unused record until expiry and a later bounded cleanup pass. The token is present in the host-managed rewritten tool input, so Codex may retain it under its own policy. Agent Markdown Link does not add it to vault files, MCP results, diagnostics, its own logs or metrics, or captures.

Both plugin artifacts bundle the same local stdio MCP server with only context, search, and capture tools. Each protocol frame is capped at 2 MiB. The server validates AGENT_MARKDOWN_LINK_HOST as exactly codex or claude; a missing, blank, or unknown identity terminates startup with a fixed diagnostic. Codex requires the hook capability for every tool call and ignores CLAUDE_PROJECT_DIR and defaultProjectId. Claude uses CLAUDE_PROJECT_DIR when present and otherwise uses the process working directory.

Claude attempts exact project selection first. Only when exact selection returns no project may Claude use an explicitly configured defaultProjectId as a Claude-only fallback. Equal-specificity mappings for different projects are ambiguous and always reject without fallback. Tool input cannot select or override a config, project, vault, workspace, or host. Capture records the validated provenance as sourceHost: codex or sourceHost: claude.

This is the fail-closed trust boundary: invalid host identity prevents MCP initialization, an unmapped Codex workspace has no MCP access, and a disabled or untrusted Codex authorization hook cannot create a valid capability. An unmapped Claude workspace has no MCP access unless the operator explicitly configured the Claude-only fallback, and ambiguity always rejects. An explicit Claude default makes that project's configured context and the shared memoryPath available to unmatched Claude MCP sessions that can reach the local server, so enable it only for sessions inside the same intended trust boundary.

The Claude SessionStart command hook emits only a fixed, content-free instruction telling Claude to call the MCP context tool before answering. The hook does not read configuration, vault data, or hook input. Codex context-hook failures are non-blocking and inject only the fixed context-unavailable notice. MCP failures use a stable code and fixed message. Context assembly is all-or-nothing, so a later file failure does not expose earlier file content.

Operator responsibilities

Vault and memory files are plaintext. Protect the device, account, vault, backups, and sync provider as appropriate for the data. A private Git repository is not encryption; do not commit sensitive vault contents to a cloud repository unless they are encrypted before leaving the device.

Treat all curated Markdown as untrusted reference data. It cannot override system, developer, repository, or current-user instructions.

When a plugin supplies curated context, the host sends that selected text to the active model provider just as it sends other prompt context. When an agent invokes search, the host may retain the query, tool call, and intended result excerpts in task history and send them to the active model provider under the host's policy. Standard input keeps the query out of ordinary process command-line listings, and Agent Markdown Link diagnostics do not echo it; neither protection hides it from the host or model provider. The local runtime itself performs no automatic network activity.

Configure search roots narrowly and do not search sensitive material speculatively. Lexical search has no semantic-recall guarantee, so an empty result is not proof that no relevant note exists.

The credential check intentionally recognizes only a few high-confidence private-key, bearer-token, and GitHub-token patterns. It is not comprehensive data-loss prevention. Review memory records before sharing or syncing them.

Limits of containment

Containment and no-overwrite checks address mistakes and linked-path escapes during ordinary local use. They do not defend against a malicious process with the same filesystem permissions racing path changes during an operation. Do not run the CLI concurrently with untrusted software that can modify the configured roots.

Outbox mode creates a local private staging directory but does not move files into the vault. Memory mode creates new records without overwrite and never edits curated summaries. Inbox review, promotion, rejection, deletion, backup, and sync remain manual operations outside the CLI.

There aren't any published security advisories