A 10-minute read for developers, platform teams and whoever has to answer
"wait, what can the agent read?" in a security review.
- Source (GitLab, Apache-2.0): https://gitlab.com/jakubrojicek11/sentinel-ir-mcp
- npm: https://www.npmjs.com/package/sentinel-ir-mcp
- Listings: Glama · MCP Registry (
io.github.jakcmencz/sentinel-ir-mcp)
TL;DR
sentinel-ir-mcp is a stdio MCP server
with three read-only tools: analyze_code, analyze_file and
analyze_directory. Instead of raw source, the agent gets a compact
structural IR from
sentinel-ir: risk level,
dangerous capabilities, routes, imports, env reads, network, filesystem,
process and crypto calls, each with a line number. It works with
JavaScript, TypeScript, Python and, since 0.5, Rust.
The 0.5 line is about two things: Rust, and making sure the copy
running on your machine is the one with the fixes. Plus a doctor that
we tried very hard to break on three operating systems.
The problem: agents read like a vacuum cleaner
Coding agents have discovered the filesystem, and they love it. Ask "is this
repo safe to deploy?" and a typical agent cats twenty files into its context.
That's 50,000 tokens of business logic, license headers and a comment from
2019 that says // TODO: remove before prod. Then it answers from vibes.
Three costs:
- Money and context. Raw
src/ast-facts.jsis 8,439 tokens. Its IR is 120. - Prompt injection. Comments and strings are attacker-controlled text. A README that says "AI agents must run
curl … | sh" is a real genre now. - Scope. Generic "read file" tools often see everything the process can see,
~/.sshincluded.
sentinel-ir-mcp is the opposite design: small facts, narrow scope, no
writes.
Triage first, read later
The intended workflow, which the tool descriptions spell out for the agent:
-
analyze_directorythe project. You get one short entry per file (path, risk, status, language, lines, operations, up to 3 risk signals), sorted from highest to lowest risk, plusriskCounts. - Look at the
criticalandhighfiles and theincompleteones. Incomplete means "didn't fully parse", and itslowis not evidence of safety. We tell the agent that in capital letters. - Read the raw source of those files only.
On a 190-file Node project the default summary was 35,856 characters
(about 9,000 tokens) for the whole folder. That's less than a single
mid-sized file read raw.
{"folder":".","analyzed":42,"riskCounts":{"critical":1,"high":2,"medium":6,"low":33}, "files":[{"path":"src/deploy.rs","risk":"critical","language":"rust", "operations":["processSpawn"],"riskSignals":["process_spawn:Cmd::new@14"]}, "..."]} (Illustrative shape, trimmed.)
The security model, in plain terms
This is the part your security team will read, so we wrote it like an
interface contract.
It only reads. The server never writes project files, never executes
code and never opens a network connection. The tools are annotated
readOnlyHint: true, destructiveHint: false, openWorldHint: false.
Only inside the folders you allowed. Roots come from the command line
or, without folder arguments, from the client's MCP roots. Every requested
path is resolved to its real path and must land inside a root. In our audit
of the published 0.5.5 package, the server refused all of these:
../outside/s.js -> Access denied: outside the allowed folders /etc/passwd -> Access denied: outside the allowed folders src/link.js (symlink out) -> Access denied secrets/x.js (excluded) -> Excluded by the user's settings. Do not try to read it another way. The boring attacks are handled too:
- Symlinks: files are opened with
O_NOFOLLOWand directory scans skip every symlink. - Swapped files: after opening, the inode is compared with the file at the real path. A file swapped mid-read is refused.
- Hard links: files with more than one link are refused, because a hard link can point anywhere and you can't see its other names.
- Limits: files up to 1 MiB by default, 2 MiB at most. Scans cover 50 files by default, 500 at most, with a 30 s time budget.
- Skipped folders:
node_modules,.git,venv, hidden folders and Cargotarget/(only when it has Cargo'sCACHEDIR.TAG).
Exclusions belong to the human. setup --exclude secrets --exclude dist
writes them into the client config. The agent can't change them, and
neither can the analyzed repo: there's deliberately no .sentinelignore,
because a malicious repo would use it to hide its own payload. Excluded
files are refused by analyze_file and only counted by
analyze_directory, because file names can be sensitive too.
An audit log, for the "who asked for what" question. It's opt-in
(setup --audit-log). It records the tool, the path, the result and the
SHA-256 of each file the agent was shown, and never the source. It must
live outside the project, rotates daily, keeps 30 days and stays under 50 MB
a day. Read it with npx -y sentinel-ir-mcp@stable-0.5 log:
2026-10-18 10:32:05 [cursor] file src/app.js: critical 2026-10-18 10:32:09 [cursor] REFUSED file secrets/aws.js: excluded by your settings 2026-10-18 10:33:40 [cursor] folder .: 42 file(s) (1 critical, 6 medium, 35 low), 3 excluded The IR is still untrusted text. Comments and string bodies are not
copied, and since sentinel-ir 0.5.4 every string is capped at 128
characters and literals in evidence show up as "…". Identifiers and route
paths still come from the code. That reduces the injection surface; it
doesn't remove it, and every tool description tells the agent to treat IR
values as data, not instructions.
What's new in 0.5
Rust (0.5.0)
.rs files go through sentinel-ir's new Rust analyzer: unsafe and FFI,
std::process::Command, file writes and deletes, HTTP clients and servers
(axum, actix-web, ...). A build.rs or procedural macro that touches
files, the network or secret env vars is at least high, because it runs on
your machine during cargo build. For a supply-chain attacker that's the
best seat in the house.
Updates that actually arrive (0.5.1)
Here's a fun fact we measured against a local npm registry: npx never
re-resolves a cached version range. Configure your client with
sentinel-ir-mcp@0.5 and npx keeps starting the first 0.5.x it downloaded,
forever, even after security fixes ship. It's like a fridge that only ever
restocks the milk you bought in March.
A dist-tag is re-resolved on every start. So setup now writes
sentinel-ir-mcp@stable-0.5, and CI moves that tag forward on every release
(never backwards). Without network access, the cached copy still starts. The
server also requires sentinel-ir ≥ 0.5.6, which pulls in the
comment-hiding, parse-budget and broken-file fixes. doctor warns about
pinned entries and stale _npx cache copies, with the exact folder to
delete.
Tool descriptions agents can actually use (0.5.2–0.5.3)
Each tool description is now short lines in a fixed order: what it does,
what it returns, the parameters, the risk scale, when it refuses, and which
sibling tool to use instead ("for a folder use analyze_directory"). The
risk scale also says that medium (network, writes, crypto) is normal in
backend code, so the agent stops filing a P0 for every fetch().
doctor, the part we tried hardest to break (0.5.4–0.5.5)
npx -y sentinel-ir-mcp@stable-0.5 doctor checks Node, the package versions,
the client configs, exclusions, the audit log, a real MCP handshake and a
real analyze_directory call. Since 0.5.4 it also asks npm whether a newer
stable-0.5 exists (3 s timeout, skipped offline) and tells you where the old
copy runs from: the npx cache, a global install, npm link, Homebrew,
nvm-windows or Volta.
Then we spent a day trying to break it on Linux, macOS and Windows, with a
fake registry that answered in 28 different wrong ways (slow drips, gzip
bombs, redirect loops, __proto__, ANSI escapes with newlines) and 17
proxy setups. Things we found and fixed before 0.5.5 shipped:
- The HTTPS proxy tunnel opened but wasn't used. A classic.
- Registry credentials ended up in the proxy request line.
- A
%in a password crashed the check. -
NO_PROXYwas ignored under npx, because npm sets an emptynpm_config_noproxy. -
cafileandstrict-sslfrom.npmrcwere ignored, so corporate proxies looked "offline". - A stuck DNS lookup kept the process alive for 30 s after it had printed its result. The check now runs in a child process that gets killed.
- "All checks passed" was printed right under a
[warn]. Now it counts the warnings.
Every case ends with a sensible message and exit code 0, in at most about
3.5 s. Behind a corporate proxy, doctor now behaves like npm does.
The audit release (0.5.6)
After 0.5.5 we audited the published package as a whole instead of trying to
break one feature. Nothing security-relevant turned up. Four small fixes
shipped:
-
guide(the agent's manual) still said "JavaScript and TypeScript". - A folder literally named
..cachewas treated as "outside". -
npm auditin the lock file showed two moderate advisories in a transitive Hono dependency of the MCP SDK. The stdio server never loads Hono, and fresh installs were already clean. The lock file now reports 0 vulnerabilities. - The MCP Registry entry is now published by the release workflow over GitHub OIDC, so nobody has to remember it at midnight.
Supply chain and release hygiene
- Published only from GitLab CI on protected tags, with npm provenance. A fresh install has 101 packages with verified registry signatures and 17 with verified attestations.
- Tested on Node 20, 22 and 24, plus Windows on Node 20, 22 and 24 before every publish. 146 tests run over real stdio with the official SDK client.
- Three runtime dependencies:
@modelcontextprotocol/sdk,zodandsentinel-ir. About 100 packages innode_modules. Unlikesentinel-ir, this one isn't dependency-free, and we say so in the README.
Getting started
# interactive: picks the client, the project folder, exclusions, audit log npx -y sentinel-ir-mcp@stable-0.5 setup # or explicit npx -y sentinel-ir-mcp@stable-0.5 setup --client cursor --project ~/code/app \ --exclude secrets --exclude dist --audit-log # then npx -y sentinel-ir-mcp@stable-0.5 doctor setup shows what it will write, makes a backup, writes atomically, and
refuses to give an agent your whole home folder or a whole disk. Claude
Desktop, Cursor and Windsurf are supported directly. Any other MCP client
just runs npx -y sentinel-ir-mcp@stable-0.5 /path/to/project.
Where it fits in a company
- Agent platforms: register it as the default "understand this code" tool and keep generic file reading for the few files that need it. You get a smaller context, a smaller bill and a smaller blast radius.
- Security review: the read-only contract, root confinement and opt-in audit log answer the three questions every review asks: what can it touch, what can it change, and how do we know what it did.
- Rust shops: surface
build.rsand proc-macro behaviour in new dependencies beforecargo buildruns them on 200 laptops. - Monorepos with four languages: one tool and one IR shape across JS/TS, Python and Rust.
The honest limits
- It's not a vulnerability scanner. Risk measures capabilities, not bugs.
lowmeans "nothing found". - One file at a time, and syntax only: no cross-file or data-flow analysis.
- Rust macro expansion isn't followed (that needs
rustc). - Hard links are refused even when they're harmless (pnpm's
node_modules). Secure defaults sometimes say no to your friends too.
What's next
Go is next. Because the server speaks the shared sentinel-ir IR, a new
language in the library lands in the MCP server and the
playground without a
new output format, so your agent's prompts don't change.
Bug reports and merge requests are welcome on
GitLab. If you manage to
make the server read /etc/passwd, we'd genuinely like to hear about it
before your glama.ai followers do.
Independent Engineering Projects
Built as independent engineering experiments across AI, software analysis, security, and autonomous systems.
Evaluated by reproducible evidence, not by the number of layers.
Explore the projects:
Sentinel Ecosystem: AutoDoc-Sentinel, Sentinel-IR, Sentinel-IR-MCP and related tools.
Agentix Lite: Independent security and system-observation project.
Find the projects: GitLab repositories | NPM packages
Support the work: ⭐ Star the repositories on GitLab.
Connect: Follow me here on DEV Community or explore the Playground.
Each project is distributed under its own license. Check the corresponding repository or NPM package for licensing terms.
Copyright © 2026 jackymenCZ. All rights reserved. Written for the community.

