Omahub
← All plugins
P

Micromachee

by Pixygon

A tiny 8-bit console in your bar. 128x128, eight colours, one Lua file per game.

Security review

Review recommended · 3 findings

Deterministic scan — not a security guarantee

Medium
Risk level
Medium
Analyzed commit
e463339
Scanned
1 month ago
  • Shell sources dynamically generated content.

    .
  • medium sudo install.sh:40

    Command runs with sudo, elevating the process beyond the plugin environment.

    sudo pacman -S rust" >&2
  • low obfuscation …/src/shelf.rs:841

    Augments a command with octal/hex escape sequences.

    \0ake",

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
e463339
Reviewed
1 month ago

The deterministic scan's medium rating is overstated: the `sudo` finding is only an error-message hint in install.sh, the octal string in helper/src/shelf.rs looks like an internal sentinel rather than shell obfuscation, and the eval finding is in web/index.html, outside the installed Panel.qml widget path. The install script is transparent and user-space, building from source and writing only to home/config/data directories, and no credential theft, persistence, or destructive behavior was found. The main residual risk is the documented in-process Lua cart runtime, but launching a cart is a deliberate user action.

  • Lua carts can be fetched from an online catalog and run inside the helper process; the project's own docs say the sandbox is not a strong security boundary, so a malicious or compromised cart is the most plausible attack surface.
  • scripts/publish.sh reads API keys from environment/Pearl/Dyson configuration and uploads with curl; it is not run by install.sh or normal widget use, but a user running it should be aware of its credential scope.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Pixygon/micromachee --enable
Widgets #bar #quickshell #games

Micromachee

A tiny 8-bit console that lives in your bar. 128×128, eight colours, thirty frames a second, and one Lua file per game.

Click the ▦ in your Omarchy bar, pick a cart off the shelf, play it with the arrow keys, press escape, go back to work.

The whole machine

Screen 128 × 128
Colours 8 — black, navy, red, orange, yellow, green, blue, white
Buttons left, right, up, down, O (z), X (x)
Speed 30 fps
A game one Lua file, 24 KB maximum

That is the entire specification. There is no sprite editor, no map, no sound chip, no cartridge container — a game draws with rectangles, lines, circles, pixels and text, and that turns out to be plenty.

Writing a game

micromachee new "My Game"        # a starting cart that already plays
micromachee check mygame.lua     # does it hold up for a minute of play?
micromachee shot  mygame.lua --frames 90 --hold 2 -o look.png

A cart looks like this, and this is the complete API:

-- title: My Game
-- author: you
-- about: one line about it     -- 48 characters, and it is cut there
-- mega: no                     -- optional; keeps it out of Mega Micromachee

function _init()   end     -- once, at the start   (optional)
function _update() end     -- 30 times a second    (optional)
function _draw()   end     -- 30 times a second    (required)
cls(c)                    fill the screen
pset(x,y,c)  pget(x,y)    one pixel, written or read back
rect(x,y,w,h,c)  rectb(…) filled box, outlined box
line(x0,y0,x1,y1,c)
circ(x,y,r,c)    circb(…) filled circle, outlined circle
print(text,x,y,c)         3×5 font, 32 characters to a line
btn(i)   btnp(i)          held / pressed just now
t()                       seconds since the cart started
rnd(n)  flr(n)  mid(lo,v,hi)
score(n)                  tell the console your score
sfx(n)                    play one of eight sounds, 0-7

Lua's own math.*, table.* and string.* are all there too. Colours are 0–7, buttons 0–5, and everything clips at the screen edge so drawing off-screen is safe and free.

Sound is eight fixed effects — blip, hit, boom, pickup, jump, hurt, win, lose — and a cart picks one by index the way it picks a colour by index. It cannot choose a frequency, because the console owns how it sounds in the same way a theme owns how it looks. The bank is generated by the helper rather than shipped as files, so nothing here carries audio and micromachee sounds rewrites it. The console can be muted from a button on its own body, and the helper then does not send the sound at all.

Only three rules: fit in 24 KB, parse as Lua, define _draw. Metadata is optional comments. There is no magic header to forget on your first attempt.

Two things that are not obvious and save a lot of code:

  • pget reads the framebuffer back, so you can do collision against what you drew last frame instead of keeping a parallel model of the world. tunnel is a cave flyer in sixty lines because of it.
  • score(n) is fire-and-forget. The console keeps the records, per cart. A game cannot read, lower, or forge another game's high score.

It is built for agents to write

check and shot exist so that writing a game needs no bar, no compositor and nobody watching. check loads the cart and plays it blind for 1,800 frames — a full minute — mashing buttons, and reports the first frame that errors. shot renders a frame to a PNG you can actually look at, with --hold so you can photograph a game mid-play rather than only its title card.

That is the whole loop: write, check, look, fix. Three of the four carts here were written by an agent working exactly that way.

Mega Micromachee

The whole shelf, a few seconds at a time. Ten seconds of Pong; survive and you move on, fail and it costs a life. Every fifth round the clock loses a second and every game runs faster, down to five seconds at 2.5x — the cart you handled comfortably at round two is a scramble at round twenty.

It is the first entry on the shelf and there is no mega.lua: a cart cannot load another cart, and should not be able to. The helper already loads carts and runs frames, so the meta-game is a state machine wrapped around what it does anyway.

Speed is more updates, not a bigger step. Running _update twice as often makes a game faster while every cart's own arithmetic stays exactly as its author wrote it. Scaling a delta would need every cart to have been written in terms of one, and none of them were.

The one thing it needed from carts was a way to know you had failed — every cart tracked that already, as alive or over or dead, and none of it was visible from outside. So carts call lose() at the point they already knew.

The panel

Two states and almost no chrome: the shelf, and a console. Each cart on the shelf shows its cover; choosing one raises that cover full size with press X to start, so there is a beat to look at it and get your hands on the keys before anything moves.

The console has a d-pad and two buttons under the screen. They light when you hold the key and they can be pressed with the mouse — both routes end at the same button bitmask the cart reads, so there is only one notion of "held". Each carries the key that works it, because a cart printing PRESS O has no way to tell you that O is the z key.

Covers are drawn by the cart, in _cover(), on the same screen with the same eight colours — see CLAUDE.md. A cart without one gets a frame of itself being played.

Making a game from a sentence

On the shelf: + MAKE A GAME. Give it a name and a sentence, and a cart gets written, checked, and — if it does not run — fixed and checked again. What comes back is playable immediately; it sits as a draft until you keep it.

micromachee make "Space Rocks" "dodge falling rocks, faster over time"
micromachee revise space-rocks "make the ship smaller and add a shield"
micromachee publish space-rocks      # onto your shelf
micromachee discard space-rocks

The strict rules are what make this work. A generated cart is never trusted — it has to parse, load, and survive six hundred frames of button mashing before it is written anywhere. When it fails, the console can say exactly what went wrong (_update failed on frame 41: attempt to index a nil value) and that goes back to the model, so the next attempt is informed rather than another guess. The rules are not a constraint on generation; they are the thing that makes it reliable.

Drafts live in their own folder and the shelf looks there too, so a new game is playable with no publishing step and nothing special anywhere in the player. Keeping one is a file move. Nothing leaves your machine — sharing a cart with anyone else is scripts/publish.sh, which is a separate, deliberate act.

It needs Anthropic credentials: ANTHROPIC_API_KEY, or an ant auth login profile. Without either, the panel says so rather than failing quietly.

In a browser

web/ is the whole console again, in JavaScript: it fetches the published catalog, runs the carts with real Lua 5.4 (wasmoon, the same version the helper embeds) and draws to a canvas. No server and no build step — but wasmoon is not vendored, so fetch it first and serve the folder:

cd web && npm install

Nothing binary is committed to this repository. wasmoon ships a WebAssembly module and it is left in node_modules/ where you can see where it came from, rather than checked in as an opaque blob.

The meta-game is there too, as web/mega.js — a port of helper/src/mega.rs.

A second implementation of anything is a liability unless something checks it, and "looks right" is not a check at 128x128:

cd web && npm install && node check.mjs

That runs every cart to the same frame in the browser console and in the real helper and compares all 16384 pixels. It reports the first coordinate that differs and both colour indexes, because that is what tells you which primitive is wrong. All seven carts are identical at frames 1, 90, 300, 600 and 900, with and without buttons held.

node megacheck.mjs does the same for the meta-game: it reads the constants back out of mega.rs and walks both difficulty curves round by round, because a ramp that quietly differs between the desktop and the site is exactly the drift nobody notices until two people compare scores.

Nothing is copied by hand: web/console-data.js — the font, the palettes and the catalog url — is generated from the Rust source by a test that fails if the committed copy is stale.

Playing without installing

The bar widget is one front end. play is a protocol — one base64 PNG per line on stdout, a button bitmask on stdin — so anything can be the screen:

micromachee list         # what is on the shelf
micromachee tty rogue    # play it, right here

tty runs the cart in this process and draws its framebuffer straight to the terminal — no PNG, no base64, no second process. Pixels become half-blocks, two to a character cell, so 128x128 lands in 128x64 of terminal. Give it a window at least 128x68 for one cell per pixel; it will halve the resolution to fit a smaller one and say so. Arrows or WASD, z and x, q to quit.

Raw mode and the window size come from stty, so this pulls in no crate — the same reasoning that made the PNG encoder hand-written.

A terminal cannot report a key being released, so a press is held briefly and then let go on a timer — btnp games are exact, and btn games feel right because key-repeat keeps renewing the press.

Installing

./install.sh
ln -s "$PWD" ~/.config/omarchy/plugins/io.pixygon.micromachee

Then add the Micromachee widget to your bar. install.sh needs no root, and it copies the shipped carts across as well as building the helper.

That copy matters: the shelf looks in ./carts and then in the data directory, and the widget is started by Quickshell from wherever Quickshell happens to be, which is never this repo. Without the carts in the data directory the widget comes up with an empty shelf while micromachee tty in the repo works fine.

Carts live in ~/.local/share/omarchy-micromachee/carts/. Installing a game is copying a .lua file there — there is no install step, no index to rebuild, no registry to tell.

micromachee sync pulls the published shelf from a catalog on the internet. Every entry carries a SHA-256 and sync checks it, so a cart that arrived mangled is refused rather than saved. The catalog is treated as somebody else's file throughout: an entry's id has to be a plain cart name, because an id becomes a filename and .. or a leading / would let whoever serves the catalog choose where on your disk a file lands; and both the catalog and each cart are read with a hard byte ceiling, so an endpoint that never stops sending is refused rather than buffered.

A cart's title, author and about are the one part of it that is shown rather than run, and after a sync they may have come off the internet. They are cleaned where they are read — angle brackets, control characters and the invisible bidirectional overrides all go — because a Qt Text field defaults to sniffing its content for markup, and a title of <img src="http://…"> would otherwise have the bar fetch that URL the moment it drew. The panel pins those fields to Text.PlainText as well; the cleaning is what protects the parts of the bar this repository does not own. That is one way for a file to travel, not how carts work — copying a file into that directory still does the same job.

A cart already on the shelf is never quietly replaced. It might be one you wrote, or one of these that you changed, and a routine sync eating that would be unforgivable. But the same checksum that catches a mangled download also says whether your copy is still the published one, so sync now names the ones that differ:

0 new, 13 already here, 0 failed

1 cart(s) here differ from the shelf: picross
`micromachee sync --update` replaces them and keeps your copies as .lua.bak

sync --update takes the shelf's version and writes yours to <id>.lua.bak beside it, which the console ignores because it only reads .lua. Without that line a machine that installed early sits on the old shelf indefinitely and the only symptom is a cart with a name nobody uses any more.

Removing it

rm ~/.config/omarchy/plugins/io.pixygon.micromachee     # the symlink
rm ~/.local/bin/omarchy-micromachee                     # the helper
rm -rf ~/.local/share/omarchy-micromachee               # carts and drafts
rm -rf ~/.local/state/omarchy-micromachee               # high scores, saves, settings

Remove the widget from your bar in Omarchy's plugin settings first. Nothing else on your system is touched — the installer never uses root and writes only to those paths.

What it does on your machine

Stated plainly, because a plugin runs unsandboxed:

  • It builds a helper binary, on your machine, from this source. install.sh compiles helper/ with cargo and installs it to ~/.local/bin. Nothing is downloaded and executed, nothing is piped to a shell, and no binary is committed to this repository. Without cargo the install stops and tells you to install Rust.

    There used to be a fallback that downloaded a prebuilt helper and checked it against a SHA-256 committed here. That proved the bytes matched the ones we published; it did not prove those bytes were built from this source, because nothing tied the executable to the commit. A marketplace security review raised it and they were right — "verified" meant less than it looked like it meant. Restoring it would need a CI build with signed provenance that the installer actually checks, and until that exists the honest thing is not to ship a downloaded executable at all.

  • It runs Lua that you may not have written. That is the product: a cart is a program. Carts get math, string and table and nothing else — no io, no os, no require — plus a per-frame instruction budget and a memory ceiling so a bad one cannot hang your bar. It is a restricted interpreter in the helper's process, not a security boundary. Read a cart from a stranger before you run it. It is one file and at most 24K, which is the point.

  • It reaches the network only when you ask. sync fetches the published shelf over HTTPS. make/revise send your prompt to the Anthropic API. Nothing else talks to anything.

  • Credentials, if you use make. It reads ANTHROPIC_API_KEY, or a token from an existing ant auth login profile. The key is never written anywhere and never printed.

  • It writes only under your home. ~/.local/bin, ~/.local/share/omarchy-micromachee and ~/.local/state/omarchy-micromachee. Reinstalling never overwrites a cart you already have.

Dependencies and licences

Micromachee is MIT (see LICENSE). What it builds on:

mlua MIT Rust bindings for Lua
Lua 5.4 MIT vendored and built by mlua
serde_json MIT / Apache-2.0 the one line of JSON the bar reads
wasmoon MIT Lua 5.4 in WebAssembly, for the browser build only

Everything else — the PNG encoder, SHA-256, the palettes, the font — is written here rather than pulled in. curl is used for network fetches and stty for the terminal player; both are expected to be present already.

Publishing

micromachee catalog          # write catalog.json from carts/
scripts/publish.sh --dry-run # what would go up, and where
scripts/publish.sh           # upload the carts and the catalog, then check

Shipping bumps the version, and both the catalog url and the web player's data are keyed to it. scripts/release.sh does them in order and verifies each, because either one missed leaves a release that looks fine and is broken in a way only the next person to install it discovers:

pearl ship && scripts/release.sh

catalog.json is generated, never hand-edited: the hand-kept one went stale the moment a cart was added, listing four of seven with byte counts for versions that no longer existed. publish.sh regenerates it from the very carts it is about to upload, so the two cannot disagree, uploads the catalog last so nothing ever points at a half-published shelf, and then fetches back what is actually served rather than trusting the upload's own reply.

What is in the box

cart
snake eat, grow, do not bite yourself
breakout the wall, the ball, the bat
meteor fly, shoot, do not get hit
tunnel fly the cave; collision is done by reading the screen

How it is built

manifest.json     what Omarchy reads
Panel.qml         the bar button, the shelf, the screen — drawing only
Service.qml       runs the helper; frames in, button bits out
helper/           the console itself, in Rust
  console.rs      framebuffer, palette, 3×5 font, every drawing primitive
  png.rs          a PNG encoder in ninety lines and no dependencies
  vm.rs           Lua, and the limits a stranger's code runs under
  cart.rs         the format, which is "a Lua file"
  shelf.rs        where carts live and how they arrive

The bar runs micromachee play <id> as a long-lived process. It prints one base64 PNG per line and reads a button bitmask on stdin — so the game loop is ordinary Rust that can be driven from a shell script, and the QML layer stays thin enough to be obviously correct.

Frames are indexed PNGs at bit depth 4, which puts a full 128×128 frame in about 8 KB, and the deflate stream is uncompressed — a compressor would be more code than the encoder it lives in, to save a few percent on a local pipe.

What a cart can and cannot do

A cart gets math, string and table. io, os, package, require, dofile and loadfile are not loaded, so a cart cannot open a file or run a program — the first version did load them, and a test cart wrote to /tmp to prove the point. There is also an instruction budget per frame, so an endless loop ends the cart rather than freezing your bar, and a memory ceiling.

It is still a Lua interpreter in your process rather than a real sandbox. Treat a cart like any other script you were sent — read it before you run it. It is one file and it is meant to be read.

License

MIT.