Security scanner for AI agent configurations (MCP servers, Claude Desktop, Cursor, Claude Code, Windsurf).
Project description
tessen
Security scanner for AI agent configurations.
Tessen scans MCP (Model Context Protocol) configurations — the files that tell Claude Desktop, Claude Code, Cursor, and Windsurf which tools to load — for vulnerabilities that current tooling misses: plaintext credentials, dangerous invocation patterns, invisible Unicode instruction carriers, and filesystem-wide access grants.
Point it at your config, get findings in seconds, wire it into CI to keep them from coming back.
tessen scan
Named after the Japanese 鉄扇 (tessen), an iron war fan carried into places swords were forbidden. It looked like an ordinary fan. It was a weapon. So are the MCP servers hiding in your
claude_desktop_config.json.
Status
Alpha (v0.1.0). The scanner works, the tests pass, but it hasn't been published to PyPI yet and the ruleset is small. Expect breaking changes to the rule format and CLI flags before v0.2. If you use it, run it locally, don't wire it into production CI yet.
Current coverage: 3 built-in rules, 3 client formats, 26 tests.
What it catches
Every finding includes a severity, location in the config, message explaining the issue, and a remediation suggestion.
tessen-secrets-in-env — plaintext credentials
Detects API keys and tokens stored directly in MCP server environment variables. Recognizes patterns from GitHub (ghp_, github_pat_, gho_), Anthropic, OpenAI, AWS access keys, Google API keys, Slack tokens, Linear, and SendGrid. Distinguishes real secrets from environment variable references (${VAR}), placeholders (<YOUR_KEY>), and common redaction markers (REDACTED, PLACEHOLDER).
Severity: critical
tessen-dangerous-command — shell wrappers and curl-pipe-shell
Flags MCP servers invoked through a shell interpreter (bash, sh, zsh, pwsh, cmd.exe), which grants the server arbitrary command execution and makes any prompt injection catastrophic. Also detects curl | sh-style patterns that fetch and execute unverified remote code — a supply-chain risk that has produced multiple real-world incidents in 2025-2026.
Severity: high (shell wrappers), critical (curl-pipe-shell)
tessen-suspicious-args — Unicode injection and overbroad scopes
Two related detections. First, invisible Unicode characters (categories Cf and Cc — zero-width spaces, bidi overrides, format characters) in server arguments, which are the primary carrier for hidden prompt injection instructions. Second, filesystem-wide access grants like /, /home, or ~ passed to filesystem MCP servers.
Severity: critical (invisible Unicode), high (filesystem grants)
Install
From source (currently the only path):
git clone https://github.com/SlimBenTanfous1/tessen-cli.git
cd tessen-cli
uv pip install -e .
Requires Python 3.11 or newer. Uses uv as the package manager — install with curl -LsSf https://astral.sh/uv/install.sh | sh.
PyPI distribution is planned for v0.2. Once published:
pipx install tessen # coming soon
Usage
Auto-detect and scan everything
tessen scan
Walks the project tree upward from the current directory looking for .mcp.json (Claude Code) and .cursor/mcp.json (Cursor), then checks the OS-specific location for Claude Desktop (~/.config/Claude/claude_desktop_config.json on Linux, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows). Scans every config it finds.
Scan a specific file
tessen scan path/to/claude_desktop_config.json
tessen scan .cursor/mcp.json --client cursor
The --client flag forces a specific parser when the filename doesn't match convention. Valid values: claude_desktop, claude_code, cursor.
Version
tessen version
CI integration
Tessen exits non-zero when it finds issues at or above a severity threshold. This is the mechanism to keep vulnerable configs out of your main branch.
tessen scan --fail-on high
Exit codes: 0 for clean, 1 when the threshold is met, 2 for invalid flag values.
Valid --fail-on values: critical, high, medium, low, never.
GitHub Actions example (roughly — this pipeline hasn't been dogfooded yet):
name: MCP Config Security
on:
pull_request:
paths:
- ".mcp.json"
- ".cursor/mcp.json"
- ".claude/settings.json"
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install tessen
run: |
pip install git+https://github.com/SlimBenTanfous1/tessen-cli.git
- name: Scan MCP configs
run: tessen scan --fail-on high
Example output
Scanning a config with three planted issues (real GitHub PAT, bash wrapper, filesystem-wide grant):
tessen scanning /path/to/claude_desktop_with_secrets.json
Findings for Claude Desktop 1 critical · 2 high
╭─ ● [CRITICAL] GitHub Personal Access Token exposed in plaintext ─────╮
│ The env var 'GITHUB_PERSONAL_ACCESS_TOKEN' in server │
│ 'github_real_leak' contains a value matching a GitHub Personal │
│ Access Token pattern. │
│ │
│ Location: mcpServers.github_real_leak.env.GITHUB_PERSONAL_... │
│ Rule: tessen-secrets-in-env │
│ Remediation: Move the secret to a secure store (1Password, Vault, │
│ or OS keychain) and reference it via ${VAR_NAME}. │
╰──────────────────────────────────────────────────────────────────────╯
Scan summary
Total findings: 3
Critical 1
High 2
Supported clients
| Client | Config path | Parser |
|---|---|---|
| Claude Desktop | ~/.config/Claude/claude_desktop_config.json (Linux)~/Library/Application Support/Claude/claude_desktop_config.json (macOS)%APPDATA%\Claude\claude_desktop_config.json (Windows) |
claude_desktop |
| Claude Code | <project>/.mcp.json |
claude_code |
| Cursor | <project>/.cursor/mcp.json |
cursor |
| Windsurf | Planned for v0.2 |
— |
Development
git clone https://github.com/SlimBenTanfous1/tessen-cli.git
cd tessen-cli
uv sync # install runtime + dev deps
source .venv/bin/activate # or use `uv run <command>`
# Run the test suite
pytest tests/ -v
# Run tessen against test fixtures
tessen scan tests/fixtures/claude_desktop_with_secrets.json
tessen scan tests/fixtures/claude_desktop_clean.json
Project layout
tessen-cli/
├── src/tessen/
│ ├── cli.py # Typer app: scan + version + --fail-on
│ ├── models.py # MCPConfig, ParseResult, ClientType
│ ├── output/human.py # Rich severity panels
│ ├── parsers/
│ │ ├── common.py # Shared JSON+Pydantic parse logic
│ │ ├── claude_desktop.py
│ │ ├── claude_code.py # + walker for .mcp.json
│ │ ├── cursor.py # + walker for .cursor/mcp.json
│ │ └── auto.py # Multi-client auto-detection
│ └── rules/
│ ├── base.py # Rule ABC
│ ├── engine.py # RuleEngine with buggy-rule isolation
│ ├── models.py # Finding, Severity, RuleMetadata
│ └── builtin/ # Built-in detection rules
└── tests/ # 26 tests, ~0.3s runtime
Adding a rule
- Create
src/tessen/rules/builtin/your_rule.py - Subclass
Rule, defineMETADATA, implementcheck(result) -> list[Finding] - Register it in
src/tessen/rules/builtin/__init__.py(BUILTIN_RULESlist) - Add tests in
tests/test_rules.py
Rules should be pure functions: no I/O, no state, no side effects. Same input → same output.
Roadmap
v0.1 (current) — foundation: parsers for 3 clients, auto-detection, 3 detection rules, Rich terminal output, --fail-on for CI.
v0.2 — coverage:
- 4 more rules: known-vulnerable MCP servers (CVE feed), HTTP transport without auth, dangerous auto-approve flags, hook injection in
.claude/settings.json - JSON output format
- SARIF output for GitHub Advanced Security compatibility
- Windsurf parser
- PyPI distribution (
pipx install tessen)
v0.3 — trust:
- GitHub Action for one-line CI integration
- CVE feed as a subscribable JSON endpoint
- First round of dogfooded findings on
awesome-mcp-servers
Later:
- Custom rules via YAML
- Baseline files (
.tessen-baseline) to suppress known findings - Runtime hooks that observe tool calls at execution time
Anything you want prioritized? Open an issue.
Why "Tessen"
A tessen (鉄扇) is an iron folding fan carried by samurai as a concealed weapon into places where swords were forbidden — tea houses, temples, palaces. Ordinary-looking. Genuinely dangerous.
Same shape as a poisoned MCP server: a small config file that looks like plumbing, and behaves like an attack.
License
MIT. See LICENSE.
About
Built by Slim Ben Tanfous — cybersecurity engineer at BrightByDesign (Zero Trust infrastructure, CI/CD security), joining the ESGI Paris Master's in Cybersecurity in September 2026. Prior: CCNA, CyberOps Associate, AWS Cloud Security Foundations, OCI Foundations.
Reach out: github.com/SlimBenTanfous1
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tessen_cli-0.1.0.tar.gz.
File metadata
- Download URL: tessen_cli-0.1.0.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
389b0448e84f66d1206eb5095644d6acba0ed69ae06ffd34944123c402f7c148
|
|
| MD5 |
f64f63c80dab328ea0100f46175bb3da
|
|
| BLAKE2b-256 |
659eab18f629289216b48d1df33a6644fd86c32b2d889c2141a86312a25eed34
|
Provenance
The following attestation bundles were made for tessen_cli-0.1.0.tar.gz:
Publisher:
publish.yml on SlimBenTanfous1/tessen-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tessen_cli-0.1.0.tar.gz -
Subject digest:
389b0448e84f66d1206eb5095644d6acba0ed69ae06ffd34944123c402f7c148 - Sigstore transparency entry: 2154074568
- Sigstore integration time:
-
Permalink:
SlimBenTanfous1/tessen-cli@271c334ef7a60bcc75a0022068a4762d52d56123 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SlimBenTanfous1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@271c334ef7a60bcc75a0022068a4762d52d56123 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tessen_cli-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tessen_cli-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2264f0713d820ad9f0e6dcac9b5e1e2029f3a4b85148fbd05078cddaf63bc6a9
|
|
| MD5 |
a2b93bc87e072a7ba533138a159844da
|
|
| BLAKE2b-256 |
c751eb0b625bbd29d123c25bb86afa7d1cbdaa84430909326ee6b61cf16cff5d
|
Provenance
The following attestation bundles were made for tessen_cli-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on SlimBenTanfous1/tessen-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tessen_cli-0.1.0-py3-none-any.whl -
Subject digest:
2264f0713d820ad9f0e6dcac9b5e1e2029f3a4b85148fbd05078cddaf63bc6a9 - Sigstore transparency entry: 2154074692
- Sigstore integration time:
-
Permalink:
SlimBenTanfous1/tessen-cli@271c334ef7a60bcc75a0022068a4762d52d56123 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/SlimBenTanfous1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@271c334ef7a60bcc75a0022068a4762d52d56123 -
Trigger Event:
push
-
Statement type: