Sievant docs
Scan, wrap and the local UI
The local developer path: audit MCP client configurations, route their servers through Sievant, and watch the calls. docs/local-quickstart.md walks through it.
Scanning MCP client configurations
sievant scan reads the MCP server configuration of Claude Desktop,
Claude Code (user ~/.claude.json, including per-project entries, and
the project .mcp.json), Cursor, VS Code (mcp.json, settings.json,
JSONC) and Windsurf, for the current OS, plus the project files in the
current directory (--no-project skips them), or only the files given
with --config. It never changes them.
| Rule | Severity | Finds |
|---|---|---|
| SEC001 | critical | a credential in a recognizable format (GitHub, GitLab, Slack, Anthropic, OpenAI, Stripe, AWS, Google, Hugging Face, npm, Notion, Linear, private keys, JWTs, database URLs with a password) in env, args or headers |
| SEC002 | high | a literal value in a secret-named variable, header or flag |
| SEC003 | critical | a password in a server URL |
| SEC004 | high | a credential embedded in a URL’s path or query (a “capability URL”: UUID or high-entropy path segments, token-bearing query keys such as key, token, api_key, user_id, or a path on a known capability-URL provider), in url or in args |
| PERM001 / PERM002 | high / medium | the filesystem root / the whole home directory given to a server |
| PERM003 | medium | all permissions (Deno), host network, privileged container |
| SUP001 | medium | an unpinned package or image, or a moving tag (@latest) |
| SUP002 | high | a server fetched from a git repository or URL, or launched by a downloaded script (curl … | sh) |
| AUTH001 | medium | a remote server with no credential configured (it may still use OAuth) |
| NET001 | high | a remote server over plain HTTP |
| INJ001 | high/medium | with --inspect-tools: a tool description that reads like instructions to the model |
| MCP001 | info | with --inspect-tools: resources and prompts Sievant would withhold |
| MCP002 | info | with --inspect-tools: tools that need MCP tasks, on a server that does not declare tasks (docs/compatibility.md) |
| SVT001 / SVT002 | info | already launched through Sievant / could not be inspected |
Values that reference the environment or a prompt (${VAR},
${env:VAR}, ${input:id}, $VAR, %VAR%) are not hard-coded. Every
output masks secrets (ghp_… (40 chars)), including evidence that quotes
a command.
sievant scan # terminal summary
sievant scan --json > scan.json # JSON report (report_version 1)
sievant scan --html scan.html # self-contained, offline HTML (no scripts)
sievant scan --fail-on high # exit 3 when a finding is high or worse
sievant scan --inspect-tools # starts the servers to read their tools; asks first (--yes in CI)
--inspect-tools starts each server the way sievant proxy would (the
same connector, environment allowlist and limits) and runs the
prompt-injection detector over every tool’s name, title and
description. Starting a server runs its code, so it is never the
default.
Wrapping MCP clients
sievant wrap rewrites client configurations so their servers launch
through sievant proxy; sievant unwrap restores them.
sievant wrap --all --dry-run # show the diff, write nothing
sievant wrap --all # asks before writing (--yes when not on a terminal)
sievant wrap github cursor:linear # chosen servers (client:name when names repeat)
sievant unwrap # newest wrap run; --list, --backup <id>, --force
- Only the wrapped entries change. Comments, formatting, key order, line endings (LF or CRLF), a byte order mark and every other server stay byte for byte. Comments inside a wrapped entry are not carried over (unwrap restores them with the rest of the file).
- A stdio entry becomes
sievant proxy --name <server> --inherit-env <each env key> -- <original command and args>; itsenvis kept, and those values are treated as secrets (redacted from what the server sends back) unless named with--public-env NAME(for values such asNODE_OPTIONSthat are not secrets). A remote entry becomes `sievant proxy –url <url> –header-env <Header>=SIEVANT_HEADER_…` with the header values moved into `env`, so no credential is in the command line; private address ranges are allowed for it (cloud metadata addresses are not). - Tool definitions are pinned. Each newly wrapped entry also runs
sievant proxy --pin-first-use: the first definitions seen of the server’s tools are recorded, and a tool whose description or schema later changes (a rug pull) is withheld untilsievant policy pins accept <server>.--no-pinleaves it out. Entries wrapped before this default keep their arguments. - Suspected prompt injection is marked. Each newly wrapped entry
also runs
sievant proxy --annotate-injection(injection actionannotate): a result the detector suspects (a forged delimiter, instruction-like text) reaches the agent inside<untrusted-tool-output id="…">markers whose random id the text cannot close, with its score in_meta; other results are unchanged.--no-annotateleaves it out, and with--policythe file’sinjectiondecides. Entries wrapped before this default keep their arguments. - A profile, with
--policy <file>. Each newly wrapped entry runssievant proxy --config <absolute path>and takes the file’s policy layer (a profile written bysievant init; see policies.md). Without it, entries run under the proxy’s defaults.sievant scanshows, policy <file>on each wrapped server that has one. - Left as they are, with the reason printed: entries already launched through Sievant (wrapping again changes nothing), HTTP+SSE servers (Sievant connects to Streamable HTTP), and remote servers with no credential configured (the client probably signs in with OAuth, which Sievant cannot do for it yet).
- Safety. Before writing, the rewritten file is parsed again: every
untouched server must be identical and every wrapped one must launch
Sievant, or nothing is written. Each original is backed up to
<state dir>/backups/<id>/(0700; files 0600) with a SHA-256 manifest; each file is written atomically (temporary file, fsync, rename) with its original mode, through symlinks (a dotfiles link stays a link). A file that changed after it was read is never overwritten, and if any file of a run cannot be written, the files already written are restored. One wrap or unwrap runs at a time. - Unwrap restores the backed-up bytes and reads each file back: it
must match the original’s SHA-256. A file edited since the wrap is left
alone unless
--force, which keeps the edited content next to the backup first. - Which binary the entries launch. Clients start it long after wrap
exits, so the path must last:
--sievant <path>if given; otherwise thesievantonPATHwhen it is the running binary, kept as found (a package manager’s symlink, such as Homebrew’s, survives upgrades; the versioned path behind it would not); otherwise the running binary, with a note. A binary in a temporary orgo-builddirectory (go run) is refused. - Wrapped servers serve their tools only; wrap says so, and
--inspect-toolslists the resources and prompts withheld per server. Restart the client after wrapping or unwrapping.
Local UI
sievant ui shows the audit trail of servers launched through Sievant
(sievant proxy, sievant wrap): calls, policy decisions, blocked
actions and redactions, with counts for the last 24 hours. It reads the
audit store sievant proxy writes (<state dir>/audit.db, or
--audit <path>) read-only.
- Listens on
127.0.0.1only, on a random port (--addraccepts loopback addresses only). - The printed link carries a one-time token: the first visit trades it for a session cookie (HttpOnly, SameSite=Strict, 12 hours) and the link stops working, so a copied or logged link is useless.
- Every request must name the listener in its Host header (DNS
rebinding is refused); the API is GET-only; the page loads only its
embedded assets under
Content-Security-Policy: default-src 'none'; script-src 'self'; …and renders record fields as text, never as markup. No CDN, no telemetry. - Inputs are shown sanitized, as audited; URLs in them are masked.
From the Sievant repository: docs/scan-and-wrap.md at commit 8529db1.