Mergen: Enterprise Security Brief
For: Security engineers, InfoSec reviewers, compliance teams Purpose: Answers the standard questions before approving Mergen for internal use Version: 1.4.x · Last updated: 2026-08-09
One-paragraph summary
Mergen is an inline Execution and Security Gateway for AI agents. Before an agent's shell command or file edit executes, a local, deterministic policy engine ("Gate A") evaluates it in under 1ms and either passes it, blocks it with a structured explanation, or holds it for human approval (Slack, VS Code, or CLI). No LLM sits in that enforcement path. Team deployments add RBAC, SSO-federated agent identity, and a hash-chained, tamper-evident audit trail. Mergen also includes a local debugging/observability layer (the sensor/capture stack this brief originally described) that binds only to 127.0.0.1 and makes no outbound connections during normal use. The diagnostic hypothesis engine (analyze_runtime's LLM-backed root-cause analysis) is never part of the enforcement path and is not required for Gate A, HITL, RBAC, or SSO to work.
What leaves the machine
| Event | Destination | When | Can be disabled |
|---|---|---|---|
| License key validation | api.lemonsqueezy.com |
On activation + weekly background re-check | Don't activate a paid license |
| LemonSqueezy billing webhook | Inbound only | When you purchase | N/A |
analyze_runtime Context Pack |
LLM API (OpenAI / Anthropic) | Only when you explicitly call analyze_runtime |
Use only free tools |
| HITL hold delivery | Slack (if configured) | Only when a HOLD-tier policy match occurs | Don't configure MERGEN_SLACK_BOT_TOKEN; the hold is still placed and logged locally either way |
| Inbound webhooks (PagerDuty, GitHub) | Inbound only, signature-verified | Only if configured | Don't configure the corresponding secret |
| SIEM/audit forwarding | Your configured SIEM endpoint | Only if MERGEN_SIEM_* is set |
Off by default |
| Usage telemetry (anonymous installId, tool call counts) | MERGEN_TELEMETRY_URL |
Off by default. Requires POST /telemetry { enabled: true } AND MERGEN_TELEMETRY_URL env var AND 24h throttle has elapsed |
Never set MERGEN_TELEMETRY_URL |
| npm version check | registry.npmjs.org |
Once per 24h, version number only | MERGEN_NO_UPDATE_CHECK=true |
Everything else (console logs, network events, stack traces, DOM state, localStorage, backend stdout, the content of commands/edits Gate A evaluates) stays on 127.0.0.1. It is architecturally impossible for it to leave without modifying the source code or deliberately configuring one of the integrations above.
Network architecture
AI IDE (Claude Code / Cursor / Windsurf / VS Code)
Bash/Edit tool call
│
▼
Mergen Execution Gateway (Gate A) ← <1ms deterministic policy eval,
│ no LLM in this path
PASS / BLOCK / HOLD
│
├── PASS → handler runs
├── BLOCK → structured error, nothing executes
└── HOLD → suspended; Slack/VS Code/CLI approval resumes it
Chrome extension (legacy sensor/capture layer)
content.js POST http://127.0.0.1:3000/ingest
background.js GET http://127.0.0.1:3000/health (badge polling, 10s interval)
Mergen server
HTTP listener 127.0.0.1:3000–3010 (loopback only, not 0.0.0.0, by default)
IDE / AI agent
the Bash/Edit PreToolUse hook (Claude Code, Windsurf) or self-heal watcher
(Cursor, plain VS Code) calls the local gate directly, never over the
open internet
The server cannot be reached from another machine on the local network by default. MERGEN_BIND=0.0.0.0 is the team-mode override, off by default, requiring deliberate configuration. Cloud mode (MERGEN_CLOUD_MODE=true, tenant-isolated Postgres-backed telemetry/CI-gate/credentials) is a separate, currently-unproductized deployment target; see this brief's "Deployment options" section.
RBAC (role-based access control)
Members are registered with a role (POST /rbac/members); Gate A policy rules can condition on conditions.roles, so an operator can write, for example, "developers may git push, only release-managers may git push --force" as an ordinary policy rule rather than a hardcoded exception. GET /rbac/me resolves the effective role for the current request. RBAC roles combine with an agent's own agentId-scoped conditions: a policy rule can match on either, both, or neither.
SSO federation
Two methods, tried in order: real OIDC (MERGEN_OIDC_ISSUER_URL + MERGEN_OIDC_CLIENT_ID), where the bearer token is verified as a JWT against the issuer's own JWKS (signature, iss, aud, exp via the jose library), not a shared-secret string compare, falling back to a shared bearer token (MERGEN_SSO_TOKEN) for solo/local deployments without an OIDC provider. A verified OIDC identity's email/preferred_username claim overwrites the caller-supplied member header, so downstream RBAC resolves a role for a cryptographically-verified identity, never a self-asserted one. MERGEN_OIDC_GROUP_ROLE_MAP can auto-provision RBAC roles from the token's groups claim. Separately, Mergen can bind a human's SSO-verified identity into a signed, HMAC-issued agent token (mergen-server agent-register --user=<email>), so every audit-ledger row an agent produces is attributable to a real person, not just an agent id.
Execution enforcement details
- Deterministic only. Every BLOCK/HOLD/PASS decision traces to a rule a human wrote or approved, never to a model's judgment. Machine-learning or similarity-scoring signals (e.g. historical-incident similarity) may only ever escalate a PASS to a HOLD; they can never independently produce a BLOCK or a PASS.
- Hash-chained audit trail. Every intercepted action (
GET /agent-blunders) carries a SHA-256 hash of its content plus the previous entry's hash: any deletion or modification of a historical entry breaks the chain and is detectable by an external auditor without trusting the server. - Internal faults fail closed. A local rule-evaluation fault, a stale heartbeat, or an unparseable payload blocks or holds rather than silently passing.
- Availability is a configured choice, and the default is
fail-open. If the runtime is stopped or unreachable there is no verdict to enforce, and since 2026-09-19MERGEN_GATE_AVAILABILITY_MODEdefaults to letting the call through, meaning an agent with shell access can runmergen-server stop && <anything>unevaluated. SetMERGEN_GATE_AVAILABILITY_MODE=fail-closedfor shared machines, CI, and any deployment where the agent is less trusted than the developer running it. A genuine policy BLOCK is enforced in either mode; the setting governs only the "no decision could be reached" case. The live value on any machine is reported bymergen doctor --securityandGET /coverage/reliability, and a stock install reportsoverallPosture: advisoryrather thanhardenedpartly for this reason. - The other intentional allow path is an operator-activated, time-bounded panic bypass, itself logged loudly.
- Works fully offline. Gate A's PASS/BLOCK/HOLD decision is always a synchronous, local computation; cloud/Postgres reachability is never required to produce one.
Chrome extension permissions
| Permission | Why it's needed | What it accesses |
|---|---|---|
storage |
Persist port config and per-tab mute state | chrome.storage.local, no page content |
tabs |
Send mute-toggle and port-change messages to content script | Tab ID only, not content |
alarms |
Schedule 10s health-poll interval | No data access |
host_permissions: http://127.0.0.1:3000–3010/* |
POST captured events to local server | Only the loopback, no external URLs |
The extension has no permissions to access external websites, read cookies from other origins, or make requests to anything outside 127.0.0.1. The manifest can be reviewed at extension/manifest.json.
The content script (extension/src/content.js) runs at document_start in every tab. It:
- Wraps
console.log/warn/errorto forward args to the local server - Wraps
fetchandXMLHttpRequestto capture request/response metadata - Captures
localStorage/sessionStorageonconsole.errorevents only - Never modifies page behavior: all patches call the original function first
- Fails silently: every code path is wrapped in
try/catch; an error in the extension cannot affect the page
Data captured and stored
| Data type | What is captured | Where it's stored | Retention |
|---|---|---|---|
| Console events | level, args[], stack, url, timestamp |
In-memory ring buffer (2,000 events) | Cleared on server restart or clear_buffer |
| Network events | method, url, status, duration, request/response body (≤8KB) |
Same ring buffer | Same |
| DOM context | url, title, activeElement, component name, localStorage keys/values |
Same ring buffer | Same |
| Backend stdout | Process name, line text, timestamp |
Same ring buffer | Same |
| SQLite history | Console errors only, for anomaly baseline | ~/.mergen/history.db (WASM SQLite, in-process) |
Configurable via MERGEN_RETENTION_HOURS |
| Agent Blunder Log / action ledger | Every PASS/BLOCK/HOLD verdict, hash-chained | ~/.mergen/agent-blunders.json, ~/.mergen/action-ledger.db (workspace-scoped) |
Configurable via MERGEN_LEDGER_RETENTION_DAYS; MERGEN_ZERO_RETENTION=true disables local persistence entirely |
| License state | Key hash, plan, customer email | ~/.mergen/license.json |
Persists until manually deleted |
| Local secret | A random UUID | ~/.mergen/secret (mode 0600) |
Persists; used to authenticate local requests |
No data is written to any path outside ~/.mergen/ and the in-process SQLite store.
PII redaction
Automatic, always-on, runs before any data enters the ring buffer or an audit/log record:
| Pattern | Action |
|---|---|
Authorization: Bearer ... header |
Value replaced with [REDACTED] |
Cookie: header |
Value replaced with [REDACTED] |
password, secret, token, api_key object keys |
Value replaced with [REDACTED] |
JWTs (eyJ... base64 patterns) |
Replaced with [REDACTED:jwt] |
| AWS access keys / secret keys | Replaced with [REDACTED] |
| Email addresses | Replaced with [REDACTED:email] |
| Credit card numbers (Luhn-valid 13–19 digit sequences) | Replaced with [REDACTED:card] |
| Network request/response bodies | Hard-capped at 8KB; remainder discarded with […truncated by mergen] marker |
Authentication
Local shared secret: a random UUID written to ~/.mergen/secret on first start (file permissions 0600). All state-changing HTTP requests (clear, license activation, telemetry toggle, admin mutations) require the x-mergen-secret header to match.
Per-boot loopback token: a second, per-port token the server writes at startup; every local API request must attach it, closing the gap where a request without it would otherwise 401 every tool call.
Signed agent tokens: Mergen issues HMAC-signed, per-profile agent tokens (mergen-server agent-register, sent as x-mergen-agent-token), replacing self-asserted agent identity for anything privilege-relevant.
Admin mutation guard: routes that can materially change enforcement behavior (policy review, RBAC changes, config writes) require the same local secret or MERGEN_ADMIN_SECRET, specifically so a process with only filesystem access (an AI coding agent, for instance) can't forge a policy change or a human-review approval.
This prevents a malicious web page from calling a state-changing endpoint via a cross-origin request; the browser will send the request, but the server rejects it without the secret. The extension reads the secret from the file and includes it on every eligible request.
An optional additional secret can be set via MERGEN_SECRET env var, which is then required on the /ingest endpoint as well.
Air-gap and offline use
All enforcement functionality works with no internet connection, since every PASS/BLOCK/HOLD decision is a synchronous local computation: Gate A, HITL holds (delivery to Slack requires network; the hold itself is placed and logged locally regardless), RBAC, and the audit trail.
Everything except license validation, analyze_runtime, and HITL Slack delivery works fully air-gapped:
- All local buffer and capture functionality
- The VS Code panel, signals, hypothesis detection
- The CLI (
mergen-server status,doctor,guard,watch) - The full Gate A policy engine and HITL hold mechanism (local decisions + local logging)
To run fully air-gapped (no outbound connections at all):
MERGEN_NO_UPDATE_CHECK=true mergen-server start
# Do not activate a paid license key
# Do not enable telemetry
# Do not configure Slack/PagerDuty/GitHub
Deployment options for enterprise
| Mode | Network exposure | Use case |
|---|---|---|
| Default (loopback) | 127.0.0.1 only |
Individual developer workstation |
Team mode (MERGEN_BIND=0.0.0.0) |
LAN, protect with firewall | Shared dev server, pair-programming |
| Docker / container | MERGEN_HOST=host.docker.internal in the container |
Containerised backend services |
| Air-gapped | No outbound, see above | Secure development environments |
| Self-hosted sync plane | Your infrastructure, no cloud dependency | Shared policy/audit across a team's own installs |
Cloud mode (MERGEN_CLOUD_MODE=true) |
Tenant-isolated, Postgres-backed | Not currently productized/sold; internal infrastructure only as of this writing |
Licensing
Mergen is commercial software, not open source. The local runtime, the IDE extensions and the SDKs are distributed under the license included with each package, and use of the service is governed by the Terms of Service. You run the runtime on your own machines; you may not offer it to others as a hosted or managed service, or remove or work around its license checks.
Common security team questions
Q: Can Mergen exfiltrate source code?
A: No. The extension captures console output (what developers explicitly console.log) and network metadata. It does not read source files, access the filesystem, or capture keystrokes. The server never accesses source files except to read .map files for stack trace de-minification, and it does not transmit them.
Q: Can a compromised Mergen server read data from other tabs?
A: The server can only receive what the extension sends. The extension is scoped to 127.0.0.1 for outbound requests. The server has no browser API access.
Q: Does the AI (Cursor, Claude, Copilot) see my raw logs? A: Yes, that is the purpose. The AI sees the same console logs and network events that would be visible in Chrome DevTools. It sees what you show it, not more. PII redaction runs before the AI can access any data.
Q: What happens to data when the developer closes their laptop?
A: The ring buffer is in-memory. Server shutdown clears it. No data survives a restart unless the SQLite history store is enabled (off for most events, on for console errors only). The audit trail (Agent Blunder Log, action ledger) does persist across restarts by default; this is intentional, since a governance audit trail that vanished on restart would defeat its own purpose, and can be disabled with MERGEN_ZERO_RETENTION=true.
Q: Can other developers on the same Wi-Fi reach the server?
A: No. The server binds to 127.0.0.1 by default. Other machines cannot reach 127.0.0.1 on your machine.
Q: Can a malicious website make requests to the Mergen server?
A: A website can attempt to POST to 127.0.0.1:3000, and the browser will send the request (CORS doesn't block sends). The server requires the local shared secret and loopback token on all state-changing endpoints, which the malicious website does not have. The /ingest endpoint accepts events without the secret by default: a malicious page could inject fake events, but this would only add noise to the developer's debug session, not exfiltrate data or bypass Gate A enforcement.
Q: Can an AI agent forge its own approval, escalate its own privileges, or bypass a HOLD? A: An agent has the same filesystem access as any local process, which is exactly the threat model Mergen's own signing-key handling is designed against: the admin mutation guard, RBAC changes, and policy edits all require a secret the agent doesn't have by default, and every fail-open path (the panic bypass) is operator-activated, time-bounded, and loudly logged, not agent-triggered.
Q: Is there a way to use Mergen without the Chrome extension?
A: Yes. The Node.js SDK (sdk/node.js) instruments backend services without any browser extension. For frontend, the DevTools snippet (sdk/devtools-snippet.js) instruments the current page via manual injection, with no extension installation required. Gate A enforcement (the Bash/Edit hook and the CLI) has no dependency on the browser extension or capture layer at all.
Contact
Security vulnerabilities: omer@mergen.app GitHub Security Advisories: mailto:omer@mergen.app Response SLA: 48 hours for critical issues