Skip to content
← All articles

Oct 13, 2026
4 min read

  • Open Source
  • Security
  • TypeScript
  • MCP
  • AI Agents

AI coding assistants can now run commands on your computer. Memnox decides what they may do — with fixed rules and an undo button. What it is, the loopholes I closed, and what I learned.

The project

In one sentence: Memnox is a safety guard for AI coding assistants: before the assistant runs a command on your computer, Memnox decides whether it's allowed, needs your approval, or is blocked.

AI assistants like Claude Code, Cursor or Codex no longer just suggest code — they run commands, install packages, push to GitHub and edit files on their own. Most of the time that's great. But one wrong command can delete work, leak a password or push broken code to production. Today, the protection is usually either "trust the AI" or "click approve a hundred times a day".

Memnox sits in between. You write simple rules ("never force-push to main", "ask me before deleting files", "never send my API keys anywhere"), and it applies them to every action of every assistant, keeps a log of what happened, and saves a restore point before anything destructive so you can roll back.

What makes it innovative

  • Rules, not guesses. Some safety tools ask another AI whether an action looks dangerous. Memnox follows fixed rules, so the same action always gets the same answer.
  • One set of rules for every assistant. Whichever AI tool you use, the same protections apply.
  • Several layers of protection. If an action slips past one check, another one catches it.
  • An undo button. Before anything destructive, it saves a restore point, and every decision is written to a log you can read.

In short: my contribution and what I learned

My part: 11 of my 13 improvements were accepted. Each one closed a way around the rules: a command written on two lines could escape a "never do this" rule, several actions sent together could skip the checks entirely, and passwords could end up written in plain text in the session history. I also made sure more dangerous commands save a restore point first.

What I took away: in a security tool, the strange cases are the job — the line break nobody thinks about is exactly what an attacker tries. And a security problem deserves to be reported quietly to the maintainers first, even when the fix is tiny.


The rest of this article goes into the technical details, for developers.

Under the hood

A TypeScript monorepo (Apache-2.0) with 8,000+ tests: agent hooks, an MCP proxy, PATH interceptors, a loopback egress proxy, an audit ledger and git-based recovery milestones.

In detail: what I brought

My contribution in numbers: 13 pull requests, 11 merged.

When a project's job is security, every bug is a bypass.

A line break that slipped past deny rules

Wildcards in policies compiled to .* without the s flag — and in JavaScript, . doesn't match \n. A multi-line shell command could escape a deny pattern. The fix is a single regex flag. I treated it as a vulnerability: draft PR, minimal public description, and the full write-up sent privately as the security policy asks.

JSON-RPC batches bypassed the MCP firewall

#89: an incoming message was parsed as a single JSON-RPC object. A batch is an array, has no method, and was simply forwarded — a tools/call inside a batch reached the server with no verdict and no ledger row. Batches are now split, each call is ruled on in order (so approval prompts reach you in the order they were sent), allowed calls go on together and denied ones come back as refusals. Review found that [[…]] nested batches still slipped through; a type guard now answers -32600 Invalid Request.

Requests mistaken for results

Client and server number their ids independently, but replies were matched by id alone. A server-initiated request with id 1 could close the client's open call id 1 and even wipe the tool list. Now a response is a message with no method and a result or error:

const isResponse = (m: JsonRpcMessage) =>
  m.method === undefined && (m.result !== undefined || m.error !== undefined);

Secrets and crashes

  • --password="correct horse battery staple" was masked only up to the first space.
  • Session transcripts stored the agent's output in clear text. They now go through a redacting stream — line by line, because chunk boundaries split tokens, and with one mask per stream (sharing one leaked part of a password, as review showed).
  • Any local process could crash the egress proxy with %zz in a header or an IPv6 address.
  • shred, truncate or git stash drop ran without a recovery milestone; database clients like mariadb, pgcli and redis-cli FLUSHALL weren't recognized as destructive.

Technical lessons

  • In a security tool, edge cases are the product. A newline, a nested array, an id collision: each one was a way around the rules. I now read every input format asking "what does the parser do with the weird version?".
  • Know your language's defaults. . not matching \n is documented, old and easy to forget. The most dangerous bugs hide in behavior everyone "knows".
  • Read the protocol, not just the code. JSON-RPC allows batches; ids are scoped per side. The spec told me where to look.
  • Disclose responsibly, even for a one-line fix. A small patch can still describe a bypass. Saying less in public and more in private costs nothing.
  • Say what your PR doesn't do. Each of my PRs ended with a "limits" section. It answers the reviewer's first questions before they ask — and it keeps me honest about what a test actually proves.