---
title: A Shell That Is Not a Shell
date: 2026-08-18
tags: [runawaydevil, security, software]
---
# A Shell That Is Not a Shell

The prompt at the bottom of this system looks like `bash`. It completes paths
with Tab, walks history with the arrows, understands `Ctrl+W`, and prints
`no such file or directory` in the right tone of voice.

It cannot run anything. There is no interpreter behind it at all.

## What actually happens when you press Enter

The line is split into words, honouring quotes. The first word is looked up in
a table. If it is there, that entry's function runs with the remaining words.
If it is not there, nothing happens and you get a boxed notice.

That is the entire mechanism. No `eval`, no `Function` constructor, no
dynamic dispatch on a string, no fallthrough to anything. Type

```text
guest@runawaydevil:~ $ rm -rf /
```

and the parser produces four words, fails to find `rm`, and says so. `|`, `>`,
`&&` and `$(...)` are not operators here — they are characters that end up
inside a word and get looked up like any other.

## The part that is easy to get wrong

The temptation, when building something like this, is a small convenience:
one command that takes an expression, or a debug hatch, or a plugin hook that
takes a callback name. Each is harmless alone and each one turns a table into
an interpreter.

So the rule is written as a test:

```text
for (const name of ['eval','exec','sh','bash','fetch','curl','wget','sudo'])
  expect(findCommand(name)).toBeNull()
```

`node` exists, but it prints node status — the BBS sense of the word. The test
asserts that too, because a name that *looks* dangerous deserves to be pinned
down rather than argued about.

## There is no filesystem either

`~/journal/2026-08-21-eighty-by-twenty-five.md` is not a path. It is a key in
a tree of objects built when the site was compiled, from Markdown files in a
repository. `ls` walks it, `cat` prints its contents, `grep` searches them.
Nothing has a writer. `..` cannot climb past the root, because there is no
root to climb past — resolution is string arithmetic over an array.

Which means the usual questions about a web shell do not apply. There is no
container to escape, no PTY to hijack, no socket to hold open, no host process
that exists. The whole system is static files and a program that runs in your
browser, and the worst thing a visitor can do to it is press keys.

## Why keep the shape then

Because the shape is good. A directory listing is a better index than a
navigation bar: it has dates, sizes, and an obvious way to look inside. `grep`
is a better search box than a search box. And muscle memory is real — people
who have typed `ls` ten thousand times should get what they expect.

The commands, the filesystem and the pager know nothing about the DOM. They
consume key events and produce ANSI. Point that stream at a socket instead of
a renderer and the same system answers a telnet call. That is not implemented
and may never be, but the seam is real and it is marked.
