Repository navigation
Build GitWarren as a container, so the scanners see what ships - #106
Merged
Merged
Conversation
Glama builds every MCP server it lists from a Dockerfile, runs the result in a microVM and watches it at the syscall and network layers. When a maintainer has not written one, it infers one; when the inferred build does not come out, the profile page survives and the server drops out of search, category listings and recommendations. There is no red check anywhere in that - just fewer people finding it - which is a bad way to learn that a directory guessed wrong about how we build. So the guess is removed. The image installs the published npm package at a pinned version, which is the same artifact `npx gitwarren mcp` fetches, so what gets scanned is what a user runs rather than a build nobody has. Three choices in it are the answer to the question a scan is asking, and each was measured rather than hoped for: `GITWARREN_DATA_DIR` is set to `/data` instead of left to the platform default. Outside a container that default is the user's application-data directory, which here would scatter the one piece of state this server keeps into a container layer. Named, there is exactly one path the process writes to, and it is the one to mount. `docker diff` after a real session - add a repository, open a review, leave a comment - lists `/data/gitwarren.db` and `/data/instance-id` and nothing else in the filesystem. The default command is `mcp`, without `--serve`. Serving would also bind the review page on 127.0.0.1:41427 for the length of the run, which is the right default outside a container and a listener nobody asked for inside one. The same session runs unchanged under `--network none`: this server speaks stdio, reads the working tree and writes SQLite, and the only socket it ever opens is the best-effort poke to a GUI on loopback, which finds no owner here and carries on. The compiler is in a build stage that is thrown away. `better-sqlite3` needs a toolchain only where its prebuilds do not reach, and both paths have to work or the image is reproducible on some architectures and not others - so the toolchain is present, and the image that runs the server has no compiler, no headers and no package index, running as the base image's `node` user at uid 1000. Built and handshaked on arm64 and amd64. The one concession is `safe.directory`. A bind-mounted repository carries a uid from the host, the tooling refuses to read a repository it believes is someone else's, and the image waives that rather than asking whoever runs it to. That check defends against a repository's own config running commands as you; inside a container holding one server and one mount, where no shell is ever involved, the trade is worth making, and it is made here and nowhere else. Two things keep it from rotting. `sync-plugin-versions.mjs` now covers the pin the way it covers the plugin and registry manifests - it is the first entry that is not JSON, so `text` swaps the parse for the raw string, and the getter reports every distinct value it finds so that the two build stages drifting apart fails rather than picking one. And CI builds the image and speaks a real handshake to it, because a build that produces something unable to answer is still a broken listing. That job resolves the pin against the registry first: `npm version` commits a bump before the release workflow publishes it, and a red check for a clock is not worth having. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The release steps promised that one commit says the version everywhere it appears, and listed the plugin manifests as the everywhere. The pin in the Dockerfile joined that set in the previous commit and the sentence did not notice, which is the exact failure the sentence exists to prevent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Glama builds every MCP server it lists from a Dockerfile, runs the result in a
Firecracker microVM, and watches it at the syscall and network layers. When a
maintainer has not written one, Glama infers one. When the inferred build does
not come out, the profile page survives and the server quietly drops out of
search, category listings and recommendations, with no failing check and no
notification anywhere.
This writes ours, so what gets scanned is what we actually ship.
What the image does
It installs the published npm package at a pinned version, which is the same
artifact
npx gitwarren mcpfetches. An inferred Dockerfile would almostcertainly fetch the package at container start, turning every run into
outbound traffic to the registry, which is exactly the shape the scan flags.
Ours downloads nothing at run time.
Three choices in it are the answer to the question a security scan asks:
GITWARREN_DATA_DIRis set to/dataratherthan left to the platform default, which here would scatter the one piece of
state this server keeps into a container layer.
mcp, without--serve. Serving binds the review page on 127.0.0.1:41427, which is theright default outside a container and surface nobody asked for inside one.
better-sqlite3needs a toolchain only where itsprebuilds do not reach, and both paths have to work or the image is
reproducible on some architectures and not others. So the toolchain is
present, in a build stage that is thrown away. The shipped image runs as the
base image's
nodeuser at uid 1000.The one concession is
safe.directory. A bind-mounted repository carries a uidfrom the host, the tooling refuses to read a repository it believes is someone
else's, and the image waives that rather than asking whoever runs it to. That
check defends against a repository's own config running commands as you. Inside
a container holding one server and one mount, where no shell is ever involved,
the trade is worth making, and it is made here and nowhere else.
Measured, not assumed
/data,/data/gitwarren.db,/data/instance-id--network noneThe session was a real one over stdio against a mounted repository: handshake,
tool listing,
agent_identity,add_repository,create_reviewreading thediff live from a read-only mount, and
add_review_comment. All seventeen toolscame back with a substantive description and behaviour annotations, which is
the seventy percent of Glama's quality score that is about tool definitions.
The write set was captured with
docker diffon a run with no volume mountedfor
/data, so that every file the server touched had to land in the containerlayer where it could be listed.
Keeping it from rotting
scripts/sync-plugin-versions.mjsnow covers the pin the way it covers theplugin and registry manifests. It is the first entry in that list that is not
JSON, so
textswaps the parse for the raw string, and the getter reportsevery distinct value it finds, so the two build stages drifting apart fails
rather than silently picking one.
npm versionstages the Dockerfile with therest.
CI builds the image and speaks a real handshake to it, because a build that
produces something unable to answer is still a broken listing. That job
resolves the pin against the registry first:
npm versioncommits a bumpbefore the release workflow publishes it, and a red check for a clock is not
worth having.
Not related to Glama's hosting product
Worth saying explicitly, since the Dockerfile invites the question. Glama's
hosting is a separate paid, opt-in service that runs a server on their
machines. It does not fit this product, because a server on their hardware
cannot see anyone's repositories. This change is only about being built and
scanned correctly as a listing.
🤖 Generated with Claude Code