Skip to content

Releases: pterbsgame-netizen/effectgate

v0.2.0 - effectgate

Choose a tag to compare

@pterbsgame-netizen pterbsgame-netizen released this 31 Jul 02:05

Renamed from mcp-guard, and installing it is now one command.

A security proxy for MCP stdio servers: the client launches effectgate,
effectgate launches the real server, and everything between them is relayed,
recorded, and — depending on the level — refused.

The rename

There were already four projects called mcp-guard in this exact niche, one of
them described as "middleware that blocks dangerous AI agent actions using a
simple YAML config"
, plus mcp-guardian at 198 stars and an npm package of the
same name. Anyone searching for this found one of them instead.

effectgate states the rule the whole design follows — control effects, not
content
— rather than repeating a word four other projects already use.

Nothing about the behaviour changed. What moved: the binary name, the module
path, ~/.effectgate/sessions, effectgate.lock, the EFFECTGATE_ environment
prefix — including the emergency switch, now EFFECTGATE_OFF=1.

New: install and uninstall

Until now the only way to wire this in was to hand-edit the client's JSON
config without breaking it and without mis-escaping a Windows path. That was the
widest part of the funnel and the least defensible.

effectgate install --dry-run    # print what would change, write nothing
effectgate install              # apply, after copying each config aside
effectgate uninstall            # put the original commands back

It finds the client configs on this machine, reports every declaration and what
it decided about each — including the ones it skipped and why — and applies it.
Servers declared under projects.<dir> are covered too; Claude Code launches
those exactly like the global ones.

The file is not re-serialised. Only the declarations that actually change
are re-emitted and spliced back into the original bytes, so key order,
formatting and every field this code has no opinion about survive untouched.
Client configs hold caches, feature flags and window positions beside the
server declarations, and a tool that reformats all of it to change two fields
gets undone by hand and never run again.

Wrapping defaults to the observing level. Enforcement is a decision to make
after a week of numbers; --enforce=enforce bakes a level in if you already
know.

Three defects were found while building it, two of them only because it was run
against real configs rather than fixtures — including encoding/json escaping
angle brackets and ampersands into their unicode form, for the benefit of HTML
nobody here was writing. A real --with mcp<2 argument came back as something
its owner would not recognise in their own config.

Also new

  • macOS builds, which v0.1.0 did not have.
  • A SECURITY.md that lists what is already known and not worth reporting, so
    the documented limits below do not arrive as bug reports.

Everything from v0.1.0 still holds

  • Pins what a server advertises. effectgate approve records each tool's
    name, description and canonicalised (RFC 8785) schema into effectgate.lock.
  • Decides on effects, not wording. Paths are resolved before they are
    matched: ~, environment variables, .. segments, symlinks, letter case,
    Windows 8.3 short names, \\?\ prefixes and NTFS alternate data streams all
    reduce to one form first.
  • Taint by source, not by phrasing.
  • Content signatures that never block — by construction they only raise the
    taint level.
  • Guards the client config from outside the traffic path.
  • Filters the child's environment, so a filesystem server does not start
    life holding a cloud credential.
  • Three levels: observe (default), enforce (deny only), strict.

Measured

On the development machine at enforce: 28 tool calls of ordinary work over
75 hours, zero blocks.
Deliberate probes are excluded and listed in
corpus/excluded-sessions.txt; including them gives 11.1 blocks per week, which
is five correct refusals of attacks. strict produces 24.5 refusals a week in
the same workload, every one of them execute_blender_code hitting the exec
class — which is why strict is marked as not usable yet rather than
recommended.

Every figure comes out of effectgate eval --benign ~/.effectgate/sessions, so
it can be checked rather than believed. If it ever disagrees with these notes,
it is the command that is right.

One machine, one workload, 28 calls. Treat it as a starting point for your own
measurement.

What it still does not do

tools/list is never blocked, so a poisoned description reaches the model.
confirm cannot ask — no client declares elicitation/create. Only stdio; a
server added over HTTP is unguarded and nothing says so. Taint does not cross
servers. A stdio proxy cannot see the server's own network traffic.

Verify what you downloaded

sha256sum -c SHA256SUMS.txt

If it goes wrong

EFFECTGATE_OFF=1 in the client's environment relays with no checks at all. It
is read before any configuration file is opened, so a broken policy cannot cost
you your tools. effectgate uninstall puts the original commands back.

v0.1.0 - control effects, not content

Choose a tag to compare

@pterbsgame-netizen pterbsgame-netizen released this 30 Jul 12:15

First release. A security proxy for MCP stdio servers: the client launches
mcp-guard, mcp-guard launches the real server, and everything between them
is relayed, recorded, and — depending on the level — refused.

Download one file, point your client's config at it. No account, no network, no
runtime to install.

What it does

  • Pins what a server advertises. mcp-guard approve records each tool's
    name, description and input schema into mcp-guard.lock. A server that
    rewrites a description or widens a schema after approval is caught. Schemas
    are canonicalised (RFC 8785) first, so re-serialisation is not mistaken for
    tampering.
  • Decides on effects, not wording. Whether a message reads like an
    instruction is not answerable — in MCP the instruction and the data are the
    same bytes. Whether a call is about to write to ~/.ssh is a fact. Paths are
    resolved before they are matched: ~, environment variables, .. segments,
    symlinks, letter case, Windows 8.3 short names, \\?\ prefixes and NTFS
    alternate data streams all reduce to one form first.
  • Taint by source. A result from a tool that brings in content nobody
    vouches for tightens what the rules already say. Nothing is blocked for that
    alone.
  • Content signatures that never block. They only raise the taint level, by
    design. The rules are public, so evasion is cheap and false positives are
    daily; as a defence the value is near zero, as telemetry it is real.
  • Guards the client config, from outside the traffic path — the proxy is a
    line in the file it would be protecting.
  • Filters the child's environment. The proxy is the parent process, so a
    filesystem server does not start life holding a cloud credential.

Enforcement is graduated

level deny confirm
observe (default) recorded recorded
enforce refused recorded, announced, relayed
strict refused refused

The two lists are not the same kind of thing. deny covers credentials, agent
configuration and shell startup files, which honest work never writes. confirm
covers package.json, Makefiles and shell scripts, which it writes all day. A
guard that refuses the second kind on the day it is installed is uninstalled the
same day.

MCPGUARD_OFF=1 relays with no checks at all, read before any config file is
opened, so a broken policy cannot cost you your tools.

Measured, not asserted

On the development machine, at enforce: 28 tool calls of ordinary work over
two days, zero blocks.
Deliberate probes are excluded from that count and
listed in corpus/excluded-sessions.txt; including them gives 11.1 blocks per
week, which is four correct refusals of attacks rather than friction.

strict produces roughly 30 refusals a week in the same workload, all of it
execute_blender_code hitting the exec class. That does not improve with more
data — it improves when a confirm can be answered instead of refused.

mcp-guard eval prints the number of calls beside the rate, because a zero over
two calls is not a rate.

What it does not do

  • tools/list is never blocked, so a poisoned description still reaches the
    model. Pinning stops the effect, not the reading.
  • confirm cannot ask. elicitation/create is the protocol's answer and no
    client seen so far declares it, so at strict a confirm is refused instead.
  • Only stdio. A server added over HTTP is unguarded, silently.
  • Taint does not cross servers. One proxy per server means one session each,
    so fetching a poisoned page taints that proxy and the one running code never
    hears about it.
  • A stdio proxy cannot see a server's own outbound network traffic. A server
    with built-in telemetry exfiltrates without a byte crossing this proxy.

Verify your download

sha256sum -c SHA256SUMS.txt

Binaries are built with CGO_ENABLED=0 and -trimpath. Windows and Linux
amd64 are covered by CI on every commit, including the race detector on Linux.
linux/arm64 is cross-compiled from the same source and not exercised by CI.
macOS is not shipped: the case-folding path is written but untested.