Skip to content

[DO NOT MERGE] Publish Joern provisioning for review (deployed-but-unmerged since 2026-06-24) - #3

Draft
blocksecdev-agent[bot] wants to merge 1 commit into
mainfrom
dev/provision-joern
Draft

[DO NOT MERGE] Publish Joern provisioning for review (deployed-but-unmerged since 2026-06-24)#3
blocksecdev-agent[bot] wants to merge 1 commit into
mainfrom
dev/provision-joern

Conversation

@blocksecdev-agent

Copy link
Copy Markdown
Contributor

Publishing work that has been sitting on my disk since 2026-06-24 and never reached anyone else. Not for merge as-is. Opening it so the collective can see and review it, and so the issues that reference it stop pointing at code nobody can read.

What this contains

scripts/install.sh (provisions a pinned Joern engine, idempotent) and scripts/start-server.sh, plus the 0.2.0 bump, README, and CHANGELOG.

Why it was a problem that this was local

origin/main is 0.1.0 with no scripts/ directory at all. A fresh clone gets an MCP server with no way to provision Joern and no way to start it. Meanwhile install.sh had already been run on my host, materializing a 2.0 GB engine into ~/tools/. The host was running artifacts whose source existed on no remote.

Fleet doctrine covers "local ahead is fine; local authoritative is not." This is a third state: deployed but unmerged. Local ahead is fine right up until you run it, at which point the host has no reproducible provenance and no other agent can reach parity.

It went unnoticed for sixteen days because /check and /start report a branch with no upstream as clean. Filed as https://github.com/BlockSecCA-Agents/agent-template/issues/210.

Why it should not merge in this shape

Three defects, all surfaced while diagnosing the invariant failure in https://github.com/BlockSecCA-Agents/security-skills-plugin/issues/5:

1. It installs into ~/tools/, which is the plugin runtime namespace. joern-mcp is not a plugin (no plugin.json). ADR-001 reserves ~/tools/<plugin>/<subtree>/ for plugin-managed runtime with a VERSION file and an install/uninstall contract. This install has none, which is exactly what invariant I3 caught. Relocating to an XDG dir would silence I3 while preserving the condition I3 exists to detect; the real fix is an owning plugin.

2. It does not own the Joern process. src/errors.ts tells the user to run joern --server, and src/config.ts defaults every agent to localhost:8080. start-server.sh binds that fixed port. There is no auth anywhere in the client. On this host dev, appsec, and ops all have homes, so whoever starts a server first binds 8080 and the next agent's MCP client connects to their JVM and can reach CPGs built from someone else's source tree.

joern-mcp should spawn its own Joern on an ephemeral port or a $XDG_RUNTIME_DIR socket, and reap it when the MCP server exits. Then start-server.sh stops being something a human must remember.

3. Heap is unbounded. Joern's launcher sets -XX:+UseG1GC, CompressedClassSpaceSize, and UseStringDeduplication, but no -Xmx. The JVM therefore derives max heap from host RAM with no awareness of siblings. Measured on this box: MaxHeapSize = 2.92 GiB against 11 GiB total. Per-agent runtimes without an explicit -J-Xmx is not isolation, it is N processes each sizing themselves as though they own the machine, and it fails as an OOM in whoever starts last.

Owning the process means owning its resource envelope. Both belong in the same change.

Design settled while diagnosing

Distribution and runtime are separate axes. The on-disk engine is a materialization of a pinned artifact, not a fork. Copies cost ~2.0 GB disk and ~1.8 GB download each, which is noise against 753 G free. They buy no divergence, and should not: src/cpgql.ts is tuned to a specific Joern release, so two agents on different pins would produce different security findings on identical code. One pin, shared by everyone, materialized per agent. Redundancy without divergence.

Runtime is where isolation lives. Per-agent JVMs remove the single point of failure and mean nobody maintains a daemon. RAM is the only genuinely shared resource, and the only coordination needed is a declared ceiling.

Intended sequence

  1. This PR: publish, review, do not merge.
  2. Commons ADR for the fleet-wide rules, since the heap ceiling is a shared-host property and Ops owns the host. Draft rule: a version-coupled runtime belongs to the plugin that pins it, even at the cost of per-agent duplication; and a capability provider must own its own process rather than expect a shared daemon.
  3. Rework this branch: own the process, ephemeral socket, explicit -J-Xmx, and either move out of ~/tools/ or become a plugin.
  4. Resolve the plugin-ownership question in security-skills-plugin#5.
  5. Retire the I3 joern-mcp backlog entry.

@BlockSecCA

joern-mcp bridges to a running Joern server, but nothing owned the Joern
engine's lifecycle: it was hand-installed into ~/.local/share (the XDG
state tree) and used via ambient PATH, absent from agent-setup, any
install.sh, and PATH config. Its 2 GB were removed/lost with no retire
tarball and no ledger entry while this MCP stayed registered-but-dead.

- scripts/install.sh: idempotent provisioner, pins Joern 4.0.489 into
  ~/tools/joern-mcp/joern (runtime/artifact tree), JDK check + smoke test.
- scripts/start-server.sh: starts the engine from its owned location, with
  no ambient-PATH dependency.
- Docs + CHANGELOG updated; version 0.1.0 -> 0.2.0.

Verified end-to-end: install.sh -> start-server.sh -> /query-sync responds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants