The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Calllint listing page.
Your agent can run tools faster than you can review them.
CallLint is a pre-flight risk linter for MCP and agent-tool configs. It checks the blast radius before the tool runs: what each tool can read, write, execute, connect to, send, or mutate — then returns an evidence-backed verdict (SAFE / REVIEW / BLOCK / UNKNOWN) before your agent ever loads the server.
It never executes, installs, or connects to the servers it judges.
Status: 1.9.1 stable CLI release. Actively hardened. Verdicts are heuristic decision support, not a safety guarantee. Read Limitations before relying on a verdict for a security decision.
An agent's power is the union of its tools' permissions. A single MCP server can add filesystem write, shell execution, network egress, or model-directed instructions to an autonomous agent — usually described only by untrusted, tool-provided metadata. CallLint inspects that surface statically and tells you, with evidence, what you would be granting before you grant it.
--online (advisory only).CallLint runs thirteen static detectors over each server entry:
| Detector | Risk symbol | What it flags |
|---|---|---|
secretEnvKeys | 🔐 Secrets | Env keys whose names imply credentials (tokens, keys, passwords), incl. docker inline -e KEY |
broadFilesystemPath | 📁 Files | Filesystem roots that grant broad read/write (/, ~, home, drive roots), incl. docker bind-mount host paths |
unknownRemote | 🌐 Network | Remote/HTTP transports to unrecognized or unpinned hosts |
promptPoisoning | 🧠 Prompt | Model-directed instructions hidden in tool names, descriptions, or schemas |
hiddenInstructions | 🧠 Prompt | Hidden/obfuscated content (zero-width, bidi, tag-char, HTML comments) in model-visible metadata |
dangerousCommand | ⚙️ Exec | Shell-out / interpreter / package-runner commands (bash -c, npx, …) |
unverifiedLocalSource | ⚙️ Exec | Local script/binary that is not a recognized package, pinned image, or remote |
externalMutation | ✉️ Action | Tools that send or mutate external state (email, messages, posts) |
messagingSend | ✉️ Action | Tools that send messages/email on your behalf (Slack, Twilio, SMTP, …) |
oauthScope | ✉️ Action | OAuth scopes that are undeclared, broad, or expansive (admin, *, repo, …) |
gatewayRuntime | ✉️ Action | Long-running gateway runtimes that proxy many downstream tools under one auth |
financialAction | 💸 Money | Payment / transfer / irreversible financial actions |
unpinnedPackage | 🧩 Supply | Unpinned package specs (@latest, no version) — rug-pull surface |
Findings roll up into a risk class (S0 metadata-only → S5 financial/irreversible) and an aggregate verdict per server and per config.
Drift detection (baseline / verify) records an approved risk surface and
flags rug-pulls (🔁) — a previously-approved server whose risk surface later
changed.
This list matters more than the feature list. CallLint is a pre-flight check, not a proof of safety.
.env or credential stores.x-calllint.tools.--online, and online results
are advisory — they never upgrade a verdict toward SAFE.UNKNOWN is a real verdict: when CallLint cannot verify what a server will do,
it says so and never silently upgrades UNKNOWN to SAFE.
| CallLint is not | CallLint is |
|---|---|
| a runtime sandbox | a pre-run risk linter for agent-tool configs |
| a secret scanner (it never reads secret values) | a config-shape inspector that flags credential-shaped keys |
npm audit (known package CVEs) | a blast-radius check on the authority you are granting |
| a server source-code analyzer | a static config + tool-metadata analyzer |
| a safety certificate | heuristic decision support, not a safety guarantee |
| a replacement for human review | the start of a review, with evidence attached |
Requires Node.js ≥ 20. The published package is a single self-contained bundle
with zero runtime dependencies. calllint on the latest tag is the current
stable CLI release; @next carries release candidates and @preview
older previews.
Zero-config scanning — discover and scan all your agent configs:
Manual path scanning — scan a specific config file:
Output formats: default terminal, --compact, --json (stable schema),
--sarif (GitHub Code Scanning), --markdown (PR comments / GitHub Step
Summary), --html (self-contained report). The
diagnostics command emits a separate editor/agent-host JSON
(calllint.diagnostics.v0).
See CallLint running in CI on a deliberately risky config —
calllint-demo-risky-mcp
publishes one Code Scanning alert per finding on every push.
The same engine and verdict semantics extend past MCP-config scanning to other points where an agent grants authority:
Receipts (calllint.receipt.v0) are a reporting layer derived from a scan —
they prove which CallLint version produced which verdict over which input under
which policy. They are not a second scanner and never re-judge a verdict. A
receipt can carry an optional ed25519 signature; receipt keygen / receipt sign generate and sign one locally for development, and receipt verify
checks the signature when present (offline, with --public-key). A signature
proves provenance and integrity — never safety.
Scanning tells you the blast radius; the Trust Gateway acts on it, safely. It resolves an agent-tool target to an immutable, digest-pinned identity, judges it deterministically, and emits a reversible install plan. Applying that plan is the only thing that ever writes live config: it re-validates every digest, writes atomically, verifies the result, and rolls back on failure. The gateway never executes, installs, or connects to the target it judges.
The gateway is a deterministic, fail-closed pipeline over six sealed digests
(artifact → evidence → authority → decision/policy → install-plan → receipt). An
approval binds all six at once; if any digest changes between prepare and apply,
the approval is void and nothing is written. UNKNOWN never becomes SAFE, and
external evidence can tighten a verdict but never set it alone. Five Tier-A hosts
ship the audited apply surface — Claude Code, Cursor, Windsurf, Claude Desktop, and
VS Code. See the
CHANGELOG (Trust Gateway Core) and ADRs 0035–0039.
A tool you approved once can change later. calllint guard records the approved
authority surface and re-decides it — silent when nothing changed, and loud the
moment a previously-approved server's risk surface shifts (a rug-pull, 🔁). It adds
no new verdict engine: it reuses the same deterministic drift check as
baseline / verify and the same stable exit codes.
Guard installs on seven hosts: git (pre-commit), git-pre-push, github
(Actions), claude-code, copilot, gemini, and vscode. Every hook binds
only to a commit / push / CI / session-start event — never a per-call gate — so
a guard hook can never silently block a tool call (ADR 0045, ADR 0052). Hosts with a
dedicated file (git, GitHub, Copilot) are written whole; hosts whose hook lives
inside a shared user-owned config (Claude Code, Gemini, VS Code) get a fragment
printed for you to merge — guard install never clobbers a shared file.
integrate and the Claude plugincalllint integrate installs CallLint's own MCP server (calllint-mcp) into the
agent hosts you already use, so the agent can run the preflight itself before it
approves another server. It is plan-only by default: it detects installed hosts,
builds a reversible install plan, prints it with a digest, and writes nothing.
Applying is a separate, explicit, approved step that reuses the Trust Gateway's exact
audited writer (re-validate → atomic write → verify → roll back on failure).
It is idempotent (a host that already has the calllint server yields no change) and
project-scoped (it acts on configs under the repo you run it in, not your global
machine state).
For Claude Code, CallLint also ships as a plugin with a PreToolUse hook. When
Claude is about to write or edit an agent-tool config, the hook surfaces a one-line
recommendation to scan first. It is advisory and non-blocking: it always exits 0,
never vetoes a tool call, runs no scan itself, and neither the hook nor an LLM ever
enters the verdict path (ADR 0051). Installing it does not install a runtime blocker.
calllint-mcp)CallLint also ships as its own MCP server, so an agent can run the preflight
check itself — before it installs or approves another MCP server. It is a thin
wrapper over the same engine: every tool delegates to calllint, it carries zero
runtime dependencies, and it never executes the server it judges.
Tools exposed: scan_mcp_config_path, scan_mcp_config_json, verify_baseline,
explain_finding, generate_agent_rule, generate_ci_gate_snippet. The server
speaks stdio JSON-RPC and returns the same evidence-backed
SAFE / REVIEW / BLOCK / UNKNOWN verdicts as the CLI. See
packages/calllint-mcp for details. Published on npm as
calllint-mcp.
CallLint's verdicts are tested against a machine-checkable corpus. Each case
pins an expected verdict, required evidence, and a "dangerous input never
resolves to SAFE" policy. The corpus is enforced as a release gate:
pnpm corpus:test.
The corpus is a regression and calibration gate, not a claim of full MCP
ecosystem coverage. See
project-facts.json (the single source of
truth for these numbers). Website and README copy is kept in sync by
pnpm check:public-copy.
Each rule has a detector and a human-readable doc under
packages/risk-engine/rules/:
prompt.poisoning — model-directed instructions in tool metadata (blocker)prompt.hidden-instructions — hidden/obfuscated content (zero-width, bidi,
tag-char, HTML comments) in model-visible metadata (R4 prompt surface, ADR 0014)prompt.surface-instructions — model-directed or hidden content in a project
document read via --surface-dir (README.md / SKILL.md / AGENTS.md /
package.json description); non-blocker, ADR 0015exec.dangerous-command — shell-out / interpreter / package-runner commandsexec.unverified-local-source — runs a local script/binary that is not a
recognized package, pinned image, or remote (ADR 0011)files.broad-path — over-broad filesystem grants, incl. docker bind-mount host
paths (--mount type=bind,src=…, -v host:container; ADR 0012)supply.unpinned-package — unpinned package specs (rug-pull surface)secretEnvKeys, unknownRemote, externalMutation, financialAction
detectors (see What it checks)Verdicts are governed by policy as code (calllint.policy.json); run
calllint policy init to write the defaults and calllint policy explain to see
the effective policy.
calllint scan <config> --badge emits a shields.io endpoint JSON
object so an MCP author can show a truthful CallLint verdict in a README. It is
built for transparency: the badge shows whatever the verdict is, and only
SAFE is green — REVIEW, UNKNOWN, and BLOCK each carry a distinct
non-green colour. It is a projection of the aggregate verdict (no schema change),
and SAFE means no blockers observed, not a proof of runtime safety. See
badge.md for the wiring and the verdict→colour map.
CallLint is a security tool, so its own boundaries are explicit and auditable.
--online adds advisory registry lookups only and can
never make a verdict more permissive.calllint.report.v0).Full statement: SECURITY.md · trust boundaries: LIMITATIONS.md. Report issues to security@calllint.com.
CallLint collects nothing unless you say yes. Scanning works fully offline with telemetry off, and no verdict ever depends on it.
y/yes enables it; a bare Enter, a timeout, n, or
EOF all leave it off, and the answer is remembered so you are asked at most
once.--json/--sarif,
or with the kill-switch set, there is no prompt and no collection.CALLLINT_TELEMETRY=0 (also false/off) disables every
tier regardless of stored state.calllint telemetry status · enable ·
disable · reset (rotates the installation ID).What an event may carry: event name, host family (e.g. cursor), result
category (e.g. BLOCK), duration, input kind, discovery surface, CallLint
version, and a random installation ID. The payload is built by an allowlist
— an unlisted field is dropped, not forwarded.
Never sent: config contents · file paths · commands · arguments · secret values · prompts · finding evidence · server names · anything identifying you or your machine.
Server side, the request IP and User-Agent are used only for rate limiting and are never persisted; installation IDs are HMAC'd at ingestion and the raw value is discarded. There is no raw event log.
CallLint sees configuration, not behavior. It can miss risks a server only
reveals at runtime, and can flag surface that turns out benign. It depends on
the tool metadata you provide being accurate, and a server can change after you
approve it (use baseline / verify to catch that). It is heuristic: expect
both false positives and false negatives, and treat REVIEW/BLOCK as the
start of a review, not a complete threat assessment. See
LIMITATIONS.md for the full trust-boundary document.
CallLint stays focused on pre-run risk linting for agent-tool configurations. Hosted registries, gateways, and runtime enforcement are outside the current release scope.
CallLint is the official Apache-2.0 open-source project published at
calllint.com,
github.com/calllint/calllint, and npm
packages calllint (CLI) and
calllint-mcp (MCP server). It is
maintainer-led — see GOVERNANCE.md and
CONTRIBUTING.md.
Apache-2.0 — see LICENSE and NOTICE. The CallLint name and logo are not licensed with the code; see TRADEMARKS.md.