From Scattered Scripts to a Sandboxed Catalog: How Sindri Came to Be
Not an app built to a plan from scratch, but a tool that emerged out of a real problem and grew according to whatever using it revealed was missing.
Where the app came from
Sindri didn't start as a project. It started as a side effect of routine maintenance after an outage, while cleaning up homelab scripts scattered across multiple machines, a question came up that sounds familiar to almost anyone managing more than one server: where's that script I wrote a month ago, what exactly does it do, and which machine did I last see it on?
Instead of a manual list in a single .md file (which had already caused exactly this kind of failure once), an app was born: a FastAPI + SQLite backend, a React frontend, full-text search that reaches into the scripts' actual content, and, from day one, not bolted on later, detection of hardcoded passwords and tokens in imported code.
Rapid growth, driven by real use
Most apps grow according to a feature plan written in advance. Sindri grew differently, every round of work added whatever real use revealed was missing, and almost every round turned up at least one genuine, live-verified bug along the way:
- Safe remote script execution over SSH to registered machines, with the sudo password sent over stdin (never stored), tested for real against actual machines. Including discovering that one of them has sudo physically bound to a hardware key (an intentional property of that machine, not an app bug, but the app had to handle it gracefully).
- Sandboxed execution: an isolated Docker container with no network, a read-only filesystem, and time and memory limits. Tested live against a real Docker daemon: writing outside the allowed space failed correctly, a fork bomb was stopped within a second, and an artificially allocated 500 MB correctly triggered an OOM-kill.
- AI-assisted review and generation of scripts via local CLI tools (with a fallback to a direct API), which on its very first real test correctly flagged a test password hardcoded in the code, exactly what it was supposed to catch.
- Content history with rollback, an execution history pulled from the audit log, bulk tag editing, a command palette (Ctrl+K), a full catalog export, and login rate-limiting with a per-IP lockout.
Several of these bugs weren't cosmetic. Double-clicking "Add to catalog" created two identical entries at once (a missing guard against duplicate submission). The copy button silently didn't work, because the app ran over plain HTTP on a LAN address and the browser's Clipboard API requires a secure context: the app looked fine on the surface, nothing just got copied. A generated SSH command for copying pointed at the SSH key's path inside the container, which didn't exist at all on the host machine: the copied command would have failed in a real terminal.
The common thread across all of these findings: none of them turned up just from reading the code. Every one required actually trying the app. Through a headless browser, through curl against the running API, or directly against real data (an existing script on disk, a real SSH key, a real target machine).
The decision to go public, and what came before it
Once the app grew to a point where going public on GitHub made sense, a phase began that can be summed up in one sentence: an app that works on your own machine isn't automatically an app that's safe to hand to someone else.
Concrete steps before the first public push:
- A full audit of the git history and the code for personal data, passwords, IP addresses, and emails. Nothing turned up, but the check was done systematically, not just assumed to be fine.
- A real portability test: a fresh export of the repo deployed on a completely different machine (different architecture) showed the 29 generic templates the library had at the time (it's grown since, see below) and zero personal scripts, confirming the app genuinely didn't assume the presence of any specific environment.
- A sandbox bug found right before the audit: a missing group permission for the non-root container to access the Docker socket meant the sandbox wasn't actually working the way its own documentation claimed. Fixed and verified live before the app was even considered as something someone else might see.
- Extra security findings beyond the original review: restricting which paths the app is allowed to import scripts from (against potential path traversal), and server-side validation of SSH paths instead of relying on the client side alone.
- 28 automated tests actually run (not just written), a first GitHub Actions CI run, a rewritten README with its own security-model and known-limitations sections, and a standalone SECURITY.md.
The repo stayed deliberately private for a while even after it was ready: the decision to publish was entirely mine; the app was just sitting there, prepared.
Why the app is built for beginners, not just for me
An interesting detail behind the whole project: the reason the app ships a fairly large library of built-in generic templates (well over a hundred today) isn't just convenience. I learned Linux on my own, without a mentor, which is exactly why the templates target beginners too, not just an experienced admin who can already write anything from scratch.
What to take away from this build
- An app born out of one annoying problem tends to be more useful than one designed up front from a feature list: every addition responded to something that actually happened during real use.
- Security checks belong there from the start, not as a last step before publishing. Hardcoded-password detection was part of the app from day one; even so, more layers (path containment, server-side validation) were added before it went public.
- "Tested" should mean actually tried, not just read. Most of the real bugs in this project turned up exactly when someone actually used the app, through a headless browser, through curl, against real data, not while reading its own source code.
- Being ready to publish and actually publishing are two separate decisions. An app can be technically ready (a clean audit, a scrubbed history, passing tests) and still stay private until I decide otherwise.
Sindri today runs as a self-hosted catalog of personal scripts with AI-assisted review, a sandbox, and remote execution. It started as a footnote to a different problem and became one of the most thoroughly reviewed projects in the whole homelab.