Go back

Why and how I rewrote these Obsidian plugins

If you’re plugged into “The JavaScript World” then you’ve probably been hearing a lot about supply chain attacks recently. The NPM ecosystem has seen a self replicating worm spread by malicious post-install scripts - and in response a lot of people are becoming more worried about their dependency on... dependencies.

In an attempt to capitalise on this rare surge of interest in software security the Obsidian team wrote an ill judged blog post about how they’re delivering a more secure product by having less dependencies. In the post they explain how Obsidian is built to minimise third party dependencies and how by being particular, and not very timely, in how they update those dependencies they can further minimise supply chain attack risks.

Unfortunately for the Obsidian team the main result of this blog post has been to draw people’s attention to some long standing concerns about their plugin system.

A

What is wrong with Obsidian’s plugin system?

So, Obsidian lets you write “plugins” in JavaScript. These plugins get access to a bunch of Obsidian APIs so you can build weird new features into what is by default a fairly bare bones app. This is catnip for people who like to spend more time configuring their tools than doing things.

In order for these plugins to be sufficiently powerful, they need to be able to do anything that the Obsidian app itself can do. Which means, for most users, that plugins can read any file on a computer they run on, make requests to random web servers, delete things at random. They can more or less do anything. If you install a plugin made by a malicious developer they can easily slurp up any information in your Obsidian vault, and cause a lot of other damage.

Obsidian have tried to mitigate these risks by locking plugins behind a scary opt-in, requiring plugins to be reviewed and approved before they appear in their ‘marketplace’, and by requiring source code to be open. But once a plugin is approved, its author is able to push new versions at will - and because everyone now minifies and transpiles the code they write, the build code that gets installed can easily not match the published source code without anyone realising.

There are thousands of plugins in Obsidian’s marketplace, many of which are unmaintained, but still popular. It would be relatively easy for an attacker to take of an existing plugin, and then push malicious code to all the users of that plugin. We’ve seen this happen in like every other similar system, so chances are that it will at some point happen in Obsidian.

Obsidian isn’t especially dangerous

I want to stress that I don’t think Obsidian is doing anything unusually bad with their plugin systems. Many other applications have similar systems with the same risk profile. The CEO of Obsidian correctly notes that “This is not unique to Obsidian. VS Code (and Cursor) works the same way despite Microsoft being a multi-trillion dollar company.”

Obsidian is a different threat model for most users though, since it contains a lot of personal, potentially sensitive information. Whereas their Virtual Studio Code just contains company secrets.

How I’ve tried to mitigate the risk from Obsidian’s plugins

So, circuitously we approach the point. I like Obsidian, but am genuinely concerned about the risks in the plugin ecosystem. Unfortunately the amount of noise around software supply chain attacks has materially increased the risk that someone will successfully attack the ecosystem. The advent of AI tools has also made it easier to automate the process of finding ways to attack the ecosystem, and the amount of attention means that someone is certainly trying.

Therefore I’ve decided that I will not update any community plugins that I am currently using, I will only use new plugins if they are made by myself, or by the core Obsidian team and I will begin replacing the plugins that I do use with my own re-implementations.

Now I want to tell you about how it was easy, and also fun, to rewrite a bunch of plugins! Obsidian plugins are straightforward to build - but if you look at the source code of most popular plugins you will be overwhelmed. This is because widely used plugins need to consider lots of different use cases, and provide lots of options for their many users. A plugin might, for example, let people do things like select their prefered date format, or link format, or enable and disable features in a granular manner. Plugins also tend to bundle various tangentially related functionality together, and it’s common to install a plugin in order to use a tiny subset of its feature set.

So I’ve been rewriting the plugins that I’ve used in the most barebones way possible - avoiding dependencies other than build tools, and Obsidian itself.

Here’s what I’ve done

Homepage -> Johnpage

I was using a plugin called Homepage, that lets you set a note as a “homepage”. You can then open that page with a keyboard shortcut, or when the app opens, or when you open a new tab or whatever.

The Homepage plugin also has a bunch of features that I frankly don’t understand and is hundreds of lines of code.

My version only lets you set a file as a homepage, and then bind a key to open it. Nothing else. Most of the code is to create an input box so you can select the file that should open.

Nldates -> Johnldates

Nldates is a nice plugin which lets you write @today or @next friday or whatever and have that get turned into a link into a daily note. It also has a bunch of weird other features that I have no interest in, like custom URI actions, and a date picker. It also pulls in Chrono to actually parse the dates which weighs in at a sweet 154kB when minified!

I just wanted to be able to write @Today or @Saturday or similar, and so I figured that I could replace the Chrono library with something simpler. Like a series of if statements.

In the end I started by building this somewhat nightmarish regex which handles the subset of natural language dates that I actually cared about writing:

const regex = /(Today|Yesterday|Tomorrow)|(last|next)?\s?(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)/

It’s ugly and stupid - but with some supporting code, it worked perfectly for my particular use case. And because I’m only building these plugins for myself that’s the only use case I need to think about.

However, after a few days I felt a bit unsatisfied with this approach. I realised that if I wanted to handle other sorts of input, then I’d need to update a Regex. So instead I decided to use a Trie. This was really fun, because I got to actually write and use a data structure for the first time since I last prepared for a job interview.

Obsidian Git -> a cronjob

I was using this plugin to sync my vault into a git repo on a regular basis. I’m now doing exactly the same thing with a cronjob. YOLO.

What’s left?

I have one theme, and two community plugins that I didn’t write.

The theme is “Minimal” - made by the CEO of Obsidian. I Hopefully this one should remain safe. Ditto for “Minimal Theme Settings” - a plugin he made to let you toggle elements of that theme.

The final plugin is Style Settings - which I use to further customise the minimal theme. It was last updated a year ago.

So for now I plan to leave these plugins installed and never update them. Alternatively, I could probably remove them and then simply use the default UI. Which would be fine.

Oh and I’ll probably have to think about my neovim config. Since it’s susceptible to all the same threats as Obsidian. And then also think about every other program on my computer.

:(

Anyway. A lot of the time we assume that to do something on a computer, we need to rely on someone to do it for us. But it’s fun and empowering to make your own things.

Further reading

Thread about plugin security from 2020 An open letter to the Obsidian team CEO of Obsidian responds to plugin concerns

Permalink

Want to read something else? Try one of these (randomly selected)

Web Mentions