github coddingtonbear/obsidian-local-rest-api 5.4.0

7 hours ago

This release adds a new state section to GET / so clients can tell whether Obsidian has finished indexing, and fixes some security problems including a vulnerability that allowed an authenticated client to write into your vault's configuration folder (.obsidian), which could allow that client to alter the code of running Obsidian plugins.

At a glance

Features

  • Server state on GET / — clients had no way to tell whether Obsidian was still indexing links
  • Failed authentication is throttled — nothing limited how quickly a client could guess at the key

Bug fixes

  • Configuration directory is off-limits — the API could change Obsidian's plugins and settings, not only your notes (GHSA-66m9-r757-qvq7)
  • Certificate serials — about 1 in 60 generated certificate pairs was malformed
  • Search and empty fields, re-registering extensions — two gaps in the documentation

Features

Server state: there was no way to tell whether Obsidian had finished indexing links

The problem: Backlinks, outgoing links, and unresolved links come from Obsidian's metadata cache, which fills in over time after Obsidian starts or a batch of files changes. A client reading links had no way to know whether it was reading a finished index or one that was still being built. The only signal was the cache's resolved event, and that fires every time Obsidian's resolver queue drains — many times before indexing is actually done.

What's new: An authenticated GET / now includes a state section with what the plugin has observed about the cache:

"state": {
  "metadataCache": {
    "listeningSince": "2026-10-03T21:02:34.788Z",
    "lastResolvedAt": null,
    "lastActivityAt": "2026-10-03T21:03:20.804Z"
  }
}

Indexing in progress looks like recent activity; done looks like quiet. The plugin doesn't try to decide for you when "quiet" is quiet enough, because Obsidian doesn't give it a reliable way to know. A vault that was already indexed before the plugin loaded never fires anything at all, which is why listeningSince is there: "never resolved, but listening for a while" is as good as done. Unauthenticated callers don't see this section, since these timestamps reveal when the vault's owner was last active.

The same document is available to MCP clients as a new server-status resource at obsidian://local-rest-api/status.

Other plugins can add their own namespaces to state with a new addState method on the extension API (API version 4). Each extension's state is read in parallel within a shared time budget (100 ms by default, adjustable under Advanced settings); one that is slow, throws, or returns something that isn't a JSON object is reported as null without affecting anything else in the response. If you're writing an extension, remember to update the obsidian-local-rest-api types package to pick up addState. (#327, #375)

Failed authentication is throttled: nothing limited how fast a client could guess at the API key

The problem: A client could present wrong API keys as fast as the server would answer. The generated key is long enough that this wasn't a practical attack on its own, but the key can be replaced with one you choose and the server can be bound to 0.0.0.0, and either of those makes unlimited guessing a real risk. GET / was the cheapest place to guess, since it answers 200 either way and just reports whether you were authenticated.

What's new: After ten wrong credentials (a wrong API key, or an invalid, expired, or already-used signed URL) from the same source within a minute, further wrong credentials get 429 (error code 42901) with a Retry-After header until the minute is up. Requests with a correct key or a valid signed URL are never counted or delayed, and neither are requests that present no credential at all. (#374)

Bug fixes

The configuration directory is now off-limits by default: the API could change Obsidian's plugins and settings, not only your notes

The API key belongs to the vault's owner, and the API treated everything inside the vault the same way, including Obsidian's configuration directory. That meant anything holding the key could read and change Obsidian's settings and installed plugins as well as your notes, including installing a plugin and having it run on the next reload. For someone managing their own setup through the API, that can be exactly what they want. It's a lot more reach than most people intend to give an AI agent or script they've handed the key to just for working with notes. It matters most if that agent can be steered by text it reads (prompt injection), or if you'd rather the key stored in this plugin's own data.json not be readable through the API.

The API now leaves the configuration directory alone by default, across REST endpoints (403, error code 40321), MCP tools, and signed URLs. It respects a relocated configuration directory, and it recognizes alternate spellings that reach the same directory on case-insensitive or Windows filesystems. If you manage your Obsidian configuration through the API on purpose, turn on "Allow access to the configuration directory" under Advanced settings to restore the previous behavior. (GHSA-66m9-r757-qvq7, #372)

Certificate serials: about 1 in 60 generated certificate pairs was malformed

The self-signed certificate's serial number sometimes started with a zero byte, which isn't valid DER. Strict parsers, such as Go's, reject a certificate like that. Since each generation produces both a CA and a certificate, about 1.6% of generations were affected. Serials now always start with a nonzero byte. A certificate you already have keeps its serial until you regenerate it, so if a strict client has been refusing to connect, regenerating the certificate in settings (and trusting the new one) will fix it. (#378)

Also fixed

  • The documentation didn't mention that a search query selecting a value, like {"var": "frontmatter.aliases"}, drops notes where that value is empty ([], {}, "") exactly as it drops notes without the field. The search docs and the search_query MCP tool now explain this and show how to use missing to test whether a field is present. (#371, #376; thanks @ErikEvenson!)
  • The extension docs never mentioned that an extension has to register again each time this plugin reloads, so an extension that registered only once started answering 404 after the plugin was updated or re-enabled, with no error. The README now has a section showing how to re-register on the obsidian-local-rest-api:loaded event. (#377)
  • The GET / schema documented its first field as ok; it is actually status, and the schema now says so. (#375)

Don't miss a new obsidian-local-rest-api release

NewReleases is sending notifications on new releases.