
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
Generate an MCP server from your Prisma schema — typed per-model tools instead of execute_sql, destructive writes gated behind human approval, every action on an audit log. Plus the operator surface: approvals, audit verify, status, studio.
Give an AI agent tools over your domain, and destructive operations come along for
the ride. orangerail is the command-line side of a governed MCP server: a write
action stays available to the agent, but calling it stages the operation for a
human instead of executing it. The agent gets an approvalId; you decide in your
own terminal; only then does it run, on a hash-chained audit log.
This package is the operator surface — the approval queue, the audit and posture readouts, the scanner that generates the server, and the launcher the agent host spawns. The full story, with real recorded output and a walkthrough you can run, is in the project README.
Zero runtime third-party dependencies: argument parsing is hand-rolled, and the
MCP server is reached through the workspace package orangerail-mcp, never a
direct SDK dependency.
Requires Node 20 or newer (engines.node: ">=20.0.0"). On an older runtime the
CLI cannot load: it imports node:readline/promises, which does not exist before
Node 17, and orangerail-mcp's dependency tree requires Node 20.
Install it into the project you are going to scan:
npm i -D orangerail
npx -y orangerail on a project that has not installed it works too, and leaves
you with two copies of orangerail-core — one in the npx cache, one in your
project — which every later orangerail status reports in a runtime: block.
Writes still complete under it; it is avoidable, and installing locally is how.
See Troubleshooting.
The generated code imports orangerail-core and zod at runtime, so install
those in the project you scanned:
npm install orangerail-core zod
npm i -D orangerail
npx orangerail init --yes --preset approval-for-writes --no-studio
npm install orangerail-core zod
npm install @prisma/client@6 # match your prisma CLI's major
export DATABASE_URL="file:./dev.db"
npx prisma generate
npx prisma db push --skip-generate # skip if the database already exists
npx orangerail sync --accept-governance # review + vouch for orangerail.governance.json
npx orangerail status
init scans a prisma/schema.prisma, generates
ontology/*.mjs plus an orangerail.config.mjs, and — under the default
--gate delete — gates every delete behind human approval while leaving the
other writes executable. Pass --gate all to gate every write instead, or
--gate none to gate nothing; the closing line names which one ran and how many
actions it left executable. An OpenAPI spec is accepted too and gives you far
less — no objects and no read surface at all, just one execute: notImplemented
action per non-GET operation to wire up yourself. No schema file yet, just a live database? prisma db pull
writes one — see
Adopting orangerail against an existing database,
which also covers what Prisma 7 changes (the generated client needs a driver
adapter, and init refuses rather than emitting one that cannot construct). The scanner is deterministic: it reads your files, makes no
LLM calls, and needs no API key. The generated files are yours — init
refuses to run again over them, and orangerail sync reports drift rather than
editing them.
The @prisma/client / generate / db push lines are not optional decoration:
without them the generated actions load fine and the first governed write fails
with the datasource client is not installed or has never been generated —
after the approval has been consumed, so a human decision is spent and no row
changes.
Then point your agent host at the server. For Claude Code, a .mcp.json in your
project root:
{
"mcpServers": {
"orangerail": {
"type": "stdio",
"command": "./node_modules/.bin/orangerail",
"args": ["mcp"],
"env": { "DATABASE_URL": "file:./dev.db" }
}
}
}
orangerail mcp is a stdio server: the host spawns it, speaks JSON-RPC over its
stdin/stdout, and it dies when the host does. You never start it by hand.
status, approvals and audit verify are ordinary commands you run in your own
terminal against the same store while the host's server is up.
That .mcp.json is also read back to you. Whatever else it declares is a set
of tools orangerail does not govern — not gated, not on this project's audit
chain, and reachable by the agent the moment a question the declared verbs cannot
answer comes up. status names them under hosts:, and init names them in its
closing summary the moment you generate a narrow surface next to a wide one:
hosts: UNGOVERNED TOOLS ALONGSIDE — 1 other MCP server(s) declared here:
- postgres (.mcp.json)
Read what that claim is and is not. It is project scope only —
.mcp.json, .cursor/mcp.json, .vscode/mcp.json, next to your config — so a
clean line means "nothing in this repo declares another server", never "nothing
else is mounted"; ~/.claude.json, Claude Desktop's config and ~/.cursor/mcp.json
are deliberately not read. It reports names only, never a command, argument or
environment value, because a database server's arguments routinely carry a
connection string. It does not connect to the other server, and it does not block,
proxy or govern it. And it exits 0: mounting a Slack server next to orangerail
is an ordinary choice, and the claim is the narrow one — those tools are outside
this project's governance, not that they are unsafe.
orangerail init [--yes] [--preset <p>] [--gate all|delete|none]
[--sources <csv>] [--models <csv>] [--exclude <csv>]
[--docs|--no-docs] [--studio|--no-studio] [--no-open] [--port <n>]
scan a repo and assemble the ontology
--gate picks which generated actions carry
`policy: { approval: 'required' }`
(default: delete)
--models keeps only those models
--exclude refuses the named ones and records
the refusal, so later scans stop proposing them
--yes implies --no-studio, because nothing that
answers no prompts should be left serving; pass
--studio alongside it to serve anyway
orangerail sync [--config <path>] [--accept-new] [--accept-governance] [--exclude <csv>]
re-scan and report drift
exit 0 nothing to act on / 1 unresolved drift /
2 could not check
--accept-governance re-records the baseline
--exclude records a proposed model as refused
instead of creating it
orangerail mcp [--config <path>] launch the MCP server over stdio
(withholds actions weaker than the baseline)
orangerail status [--config <path>] show the governance posture (exit 1 on drift)
plus any MCP server mounted alongside that
this project does not govern (never exit 1)
orangerail studio [--config <path>] [--port <n>] [--no-open]
serve the read-only domain map locally
orangerail docs [--config <path>] [--out <dir>]
generate the agent-facing domain doc
orangerail approvals list [--config <path>] list pending approvals
orangerail approvals show <id> [--full] [--config <path>]
show one approval (--full: uncapped input)
orangerail approvals approve <id> [--config …] approve a staged action (CAS)
orangerail approvals reject <id> [--config …] reject a staged action (CAS)
orangerail audit verify [--config <path>] verify the audit chain
orangerail store unlock [--config <path>] clear a provably-dead store lock
--help, -h print this usage (accepted anywhere, e.g. `orangerail init --help`)
--version, -v print the CLI version
Exit codes: 0 clean, 1 a finding the command exists to report (drift, a broken
chain), 2 an error. An unrecognized flag is a hard error naming the valid set —
it is never silently ignored, because orangerail status --confg /path/to/prod
reporting a confident green result for the local project is worse than no
report.
approvals list renders the approver decision surface: full id, action,
requestedBy (with a [dev] marker in dev mode), age, and a one-line input
preview. Every agent-supplied string is stripped of ANSI/control characters and
JSON-escaped before it reaches the terminal (approval-deception defense) — the
staged input is always labeled agent-supplied. Bidi and invisible-formatting
code points (U+202E and the rest of the Trojan Source class, the zero-width
characters, the TAG block) are escaped to a visible \uXXXX rather than
deleted, so a hostile string can neither reorder the line nor pass as a clean
one.
approvals show caps the staged input so the decision context (id, action,
status, requestedBy) always stays on screen — a 1 MB input would otherwise
scroll it away. The truncation states exactly how much was withheld; pass
--full to print the whole value.
orangerail sync re-scans your sources and reports drift, and it also reviews the
governance posture — something the scan structurally cannot do, because the
scanner has no opinion on policy. The posture is read from the live registry and
compared against orangerail.governance.json, the baseline init records and
orangerail sync --accept-governance re-records.
Commit that file. Its whole value is that a pull request removing an approval gate
shows "approval": "required" turning into null in its own diff, in front of a
reviewer, before CI runs.
The baseline records who wrote it. "recordedBy": "init" is the posture init
generated, before anyone reviewed it — a starting point, not an approval, and every
sync and status says so until you run --accept-governance, which stamps it
"recordedBy": "sync". A file written before this field existed reads as "sync",
since only --accept-governance could have written it.
When the posture weakens — a gate removed, a where guard removed or rewritten,
approver roles widened, an action retargeted, or a new action that is not gated —
sync exits 1, status exits 1, and orangerail mcp withholds that action:
it is absent from tools/list and the engine will not resolve it by name, so it
cannot be staged or executed. Everything else is served. A tightening is reported
quietly and changes nothing.
Deliberate behaviors worth knowing:
sync until
you record one. The server still starts; it says the posture is unverified.where predicate records as the constant functional, so a
rewrite of its body is invisible to the diff. sync states that limit in its own
output rather than implying coverage it does not have.Every command loads an ontology config — a default export
{ registry, store, resolveIdentity?, preset?, redactAudit?, redactPrior?, allowDevMode? } —
from orangerail.config.mjs (or .js, .ts, .mts, or --config <path>) via
plain dynamic import(). TypeScript configs work through your own TS-capable
runtime (tsx, or node --experimental-strip-types); orangerail does not
bundle a loader. Every command discovers the same four names, init included —
it refuses rather than regenerate over an ontology whose config is a .ts.
Loading a config is arbitrary code execution (same trust level as an npm script) — run only configs you trust.
approve / reject resolve the caller with resolveCaller({ transport: 'cli', request: { osUser } }). A config resolveIdentity adapter maps the OS user to
{ subject, roles } (role mapping lives in the config). With no adapter the
local CLI runs in dev mode: the local-dev identity approves anything and
stamps devMode: true into audit records.
init scaffolds the approval and audit store inside the project it just
scanned — the generated config builds it as
createFileStore({ dir: join(here, '.orangerail', 'store') }). That is right for
a local single-user run and wrong the moment the governed agent also has file
tools over that repo, because the agent can then write the very log that records
what it did.
There is no CLI flag for this. The location is the dir argument of
createFileStore in your own orangerail.config.mjs, which orangerail never
regenerates, so relocating the store is a one-line edit:
const store = createFileStore({ dir: '/var/lib/orangerail/store' });
Point it somewhere the agent's tools cannot reach. The audit log's guarantees, and their limits, are stated exactly under What the audit log proves.
If a process died inside the file store's critical section, the store stays
locked (the runtime never steals a lock). Run orangerail store unlock when no
orangerail process is running — it clears the lock only when the owner pid is
provably dead, and refuses a live/ambiguous owner or a missing owner.json. As
a last resort, rm -r <store-dir>/lock while no orangerail process runs.
Approvals and audit records store action inputs — and audit records also
results, error messages, and the target's prior state — in plaintext by
default. Supply redactAudit in the config to mask audit-record inputs
(approval records persist verbatim by design), and redactPrior to mask prior
rows. They are separate hooks because a row carries columns no input mentions,
so an input-shaped mask would publish them: configure redactAudit alone and
prior rows are withheld instead. Do not put secrets in action inputs.
orangerail approvals show <id> prints the target's current state above the
staged input, so an approver sees both sides of the change. It is a live read at
display time, masked by the same policy the audit chain applies.
Two things an upgrade asks of you. The full list is in the CHANGELOG.
pending. An approval created by
0.1.0 carries no inputHash, so execute cannot bind the payload to the
approval and refuses: the call returns { status: 'invalidated', reason: 'input' } and the approval is spent. Ask the agent to call the tool again.
Existing audit chains verify unchanged, and a missing hash is never reported as
tampering.orangerail sync --accept-governance once, and commit the file. Until
you do, a project with at least one action exits 1 with governance: no recorded baseline. That first red run is intentional. Your server keeps starting in the
meantime — it just reports that it cannot verify the posture it is enforcing.FAQs
Generate an MCP server from your Prisma schema — typed per-model tools instead of execute_sql, destructive writes gated behind human approval, every action on an audit log. Plus the operator surface: approvals, audit verify, status, studio.
The npm package orangerail receives a total of 24 weekly downloads. As such, orangerail popularity was classified as not popular.
We found that orangerail demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.