Skip to content

Security model

Datanotes is an engine that reads and writes a folder of Markdown notes, the vault. It runs on your own machine, for you and the agents you choose. This page explains:

  • How to report a vulnerability
  • Which versions get security fixes
  • What the engine exposes on the network
  • What each token can do
  • How the engine guards your files and its store
  • How to limit what an agent can do
  • Which features send data off your machine

Report vulnerabilities only through GitHub Private Vulnerability Reporting. Open a new private advisory on the repository. It’s the one channel for security reports. Don’t report them in public issues or discussions. You’ll get an answer within a week.

Include these details in your report:

  • The Datanotes version
  • How to reproduce the problem
  • What an attacker gains

Security fixes go to the latest release of Datanotes 3.

Version Supported
3.x Yes
2.x No: it was a preview
1.x No: it was a preview

Update to the latest 3.x release to get a fix.

The engine’s HTTP server listens on 127.0.0.1, so only your machine can reach it, unless you start it with --host. It answers no Cross-Origin Resource Sharing (CORS) preflight, so web pages can’t call it.

Three routes answer without a token:

  • GET /: the status and the version
  • GET /datanotes/openapi.json: the API’s description
  • /datanotes/docs: an interactive console over that description, with its script and stylesheet

These routes contain no data from your vault: the website publishes the same description. The console’s page loads nothing from other hosts, because its Content Security Policy (CSP) allows the same origin only. Its calls need a token, which the browser keeps in local storage until you log out.

To reach the engine from another device, such as a phone or a hosted assistant, publish it through a reverse proxy or a tunnel, over HTTPS. Give each device or agent its own named token. Publish the Model Context Protocol (MCP) server, datanotes mcp serve, the same way: it holds no credentials and passes each agent’s token on to the engine.

The engine accepts two kinds of token, and the owner token can do everything a named token can. Prefer a named token per device, program or agent: it has only the permissions it needs, and the journal names it. MCP clients use the same tokens, because the MCP server passes them to the engine.

The owner token is httpServerToken in the settings file. It can do everything: read and write data, change the schema, manage tokens and webhooks, undo, and read the engine’s status. datanotes status reads it from the settings file. Keep it on the machine.

The owner creates a named token with POST /datanotes/tokens/create/, and the engine stores only its hash, in tokens.json in its store, outside the vault. A named token has these permissions:

  • Read: everything
  • Write rows: only the tables in its write patterns
  • Change the schema: only with ddl
  • Maintain: with maintain, it may also undo, restore, vacuum, configure the database and rebuild the semantic index; use it for an agent’s own token

A named token can’t create webhooks, manage tokens or read the engine’s status, and without maintain it can’t undo.

The engine journals every write with its actor. The client sends the agent’s name, model and session, so they’re self-reported. The server sets the credential, auth (owner, or the named token’s name), and no client can claim another one.

The engine resolves every path inside the vault. Both its operations and its file host refuse a path that contains .., a backslash, a drive letter or a NUL character.

The engine follows symbolic links inside the vault. Don’t link to folders you wouldn’t give the engine.

The engine’s store holds its secrets: the owner token in settings.json and the webhooks’ secrets in webhooks.json. It also holds the journal and the hashes of the named tokens. On Linux and macOS, the engine keeps the store private to the user that runs it:

  • Folders: the data home and each store are created with mode 0700
  • Files: settings.json, webhooks.json and the other files of the store are created with mode 0600
  • At start: the engine tightens a data home, a store, a settings file or a webhooks.json that an older version left readable by others

On Windows, the store lives in your user profile, which other users can’t read. The Docker image runs the engine with umask 077, so every file it creates is private too.

An agent reads your notes, and a note can hold text written to manipulate it: a prompt injection. A web page you clipped or an email you saved can tell the agent to delete rows or to send your data out. The engine can’t tell such text from your own notes.

Limit what each agent can do:

  • Give it a named token: grant only the tables it writes, and leave out ddl and maintain unless it needs them
  • Give it only the tools it needs: start the MCP server with datanotes mcp --profile, --tools or --except
  • Keep the webhook tools off: they’re off by default, because a webhook sends change events to any URL

Every write is journaled with the agent’s name, so you can review it and undo it.

Nothing leaves your machine unless you turn it on. These features send data out:

  • Semantic search: sends note text to the embedding provider you configure. It’s off by default (semanticEnabled), and you choose the provider with semanticProvider and semanticBaseUrl. A local provider, such as Ollama or LM Studio, keeps the text on your machine.
  • Telemetry: sends logs to the OpenTelemetry Protocol (OTLP) endpoint you configure. It’s off by default (telemetryEnabled). The logs contain operations, routes, note paths, actors, tool names and error messages, which may quote a value, but not note bodies. The settings file stores telemetryHeaders, which may hold an API key; set OTEL_EXPORTER_OTLP_HEADERS instead to keep the key out of it.
  • Webhooks: the owner creates them, and the engine posts change events to the URLs they name.
  • Git sync: with --git-sync, the engine pushes the vault to its git remote.
  • Store backup: it’s off by default (storeBackupEnabled). When on, the engine pushes its store to the git remote you configure. The backup holds the journal, the transaction logs, the blobs, the change feed and the named tokens’ hashes. The journal, the logs and the feed quote your notes’ values. The backup leaves out settings.json and webhooks.json, which hold the secrets. Use a private repository.