Metadata-Version: 2.4
Name: tessen-cli
Version: 0.1.0
Summary: Security scanner for AI agent configurations (MCP servers, Claude Desktop, Cursor, Claude Code, Windsurf).
Keywords: security,scanner,mcp,ai-agents,claude,cursor,devsecops
Author: Slim Ben Tanfous
Author-email: Slim Ben Tanfous <slimbentanfous2002@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pyyaml>=6.0.3
Requires-Dist: rich>=15.0.0
Requires-Dist: typer>=0.26.7
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/SlimBenTanfous1/tessen-cli
Project-URL: Repository, https://github.com/SlimBenTanfous1/tessen-cli
Project-URL: Issues, https://github.com/SlimBenTanfous1/tessen-cli/issues
Description-Content-Type: text/markdown

# tessen

![CI](https://github.com/SlimBenTanfous1/tessen-cli/actions/workflows/ci.yml/badge.svg)

**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):**

```bash
git clone https://github.com/SlimBenTanfous1/tessen-cli.git
cd tessen-cli
uv pip install -e .
```

Requires Python 3.11 or newer. Uses [uv](https://docs.astral.sh/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:

```bash
pipx install tessen  # coming soon
```

---

## Usage

### Auto-detect and scan everything

```bash
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

```bash
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

```bash
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.

```bash
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):

```yaml
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)<br>`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)<br>`%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

```bash
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

1. Create `src/tessen/rules/builtin/your_rule.py`
2. Subclass `Rule`, define `METADATA`, implement `check(result) -> list[Finding]`
3. Register it in `src/tessen/rules/builtin/__init__.py` (`BUILTIN_RULES` list)
4. 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](./LICENSE).

---

## About

Built by [Slim Ben Tanfous](https://github.com/SlimBenTanfous1) — 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](https://github.com/SlimBenTanfous1)
