Skip to main content
Glama
ChristopherValvo

script-decoder-mcp

README.md
# Script Decoder MCP

A defensive, static-analysis MCP server that explains what a script does — in plain
English and with technical evidence — **without ever executing it**.

Paste a Python, PowerShell, shell, JavaScript, batch, or VBScript script (or a single
command line) and get back: a behavior breakdown (network, file, process, persistence,
credential-access, discovery, defense-evasion, execution), safely decoded obfuscated
content, extracted indicators of compromise, a transparent risk score, and a contextual
MITRE ATT&CK mapping — all with source-line evidence and explicit observed/inferred/
possible classifications.

> **Defensive-use statement**: this project performs static analysis only. It never
> executes, imports, compiles-for-execution, or otherwise runs submitted content. It
> never sends submitted scripts or decoded content to any third-party service. It is
> intended for SOC analysts, incident responders, and anyone who needs to understand an
> unfamiliar or suspicious script before deciding what to do with it — not to enable
> offensive use.

## Features

- **Five MCP tools**: `analyze_script`, `decode_blob`, `extract_indicators`,
  `explain_command`, `compare_scripts`.
- **A local browser GUI** (see [Browser GUI](#browser-gui)) for the same five analyses,
  with no MCP client required.
- **Six language analyzers**: Python (AST-based), PowerShell, shell (bash/sh/zsh),
  JavaScript, batch, and VBScript (rule-based).
- **Bounded recursive decoding**: Base64, Base32, hex, URL encoding, Unicode escapes,
  HTML entities, gzip/zlib/bz2, ROT13, reversed strings, PowerShell
  `-EncodedCommand`, integer/char-code arrays, simple string concatenation, and
  explicit-key XOR — chained safely up to a configurable depth, with decompression-bomb
  protection.
- **IOC extraction**: URLs, domains, IPv4/IPv6, emails, file paths, registry keys,
  hashes, mutex-like strings, user agents, named pipes, scheduled-task/service names, and
  common cloud resource identifiers — including defanged-notation recognition
  (`hxxp`, `example[.]com`, `10[.]0[.]0[.]1`).
- **Transparent, documented risk scoring** (0-100) with contributing factors listed in
  every response — never a black box, never a malware verdict.
- **Conservative, local, offline MITRE ATT&CK mapping.**
- **Prompt-injection safe**: all submitted and decoded content is treated as inert data,
  never as instructions, and is explicitly labeled as untrusted in evidence excerpts.
- **Optional, local, disabled-by-default CyberChef-style adapter** for a small set of
  additional decode operations, with no network access and an operation allowlist.
- **No outbound network access is required or performed during analysis.**

## Architecture

See [`docs/architecture.md`](docs/architecture.md) for the full breakdown. In short:

```
MCP client (e.g. Claude Desktop)
        │  stdio (default) or localhost HTTP
        ▼
 server.py (5 MCP tools)
        │
        ├─ analyzers/        language detection + per-language static analysis (AST for
        │                    Python, regex/rule-based for the rest)
        ├─ decoding/         bounded recursive decoding engine, native decoders, optional
        │                    local CyberChef-style adapter
        ├─ extraction/       IOC extraction
        ├─ classification/   risk scoring + ATT&CK mapping
        ├─ reporting/        assembles the final structured response + summaries
        └─ security/         sanitization and trust-boundary helpers
```

Nothing in this pipeline executes, imports, or evaluates submitted content. See
[`docs/threat-model.md`](docs/threat-model.md) for the full threat model and
[`SECURITY.md`](SECURITY.md) for the security policy.

## Installation

Requires Python 3.12+.

```bash
python -m venv .venv
# Windows: .venv\Scripts\activate    macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"
```

Optional: Node.js 18+ if you want to enable the local CyberChef-style adapter (disabled
by default; not required for any core functionality).

## Local development

```bash
pip install -e ".[dev]"
python -m pytest
ruff check .
ruff format --check .
mypy src
```

## Running the server

Default transport is stdio, which is what MCP clients like Claude Desktop expect:

```bash
python -m script_decoder_mcp
# or, after `pip install -e .`:
script-decoder-mcp
```

Set `SDMCP_TRANSPORT=http` (see [`.env.example`](.env.example)) to run over localhost
HTTP instead; it binds `127.0.0.1` by default and is never exposed remotely without
explicit additional configuration, which this project does not provide.

## Browser GUI

Prefer a browser over an MCP client? There's a local web GUI that calls the exact same
analyzer/decoder code as the MCP tools -- it's a thin FastAPI wrapper around
`reporting/formatter.py`, not a separate implementation, so results never drift from the
MCP server.

```bash
pip install -e ".[web]"
python -m script_decoder_mcp.web.app
# or: make web
```

Then open **http://127.0.0.1:8787**. It has one tab per tool (Analyze Script, Decode
Blob, Extract Indicators, Explain Command, Compare Scripts), plus "Load good/bad example"
buttons that load [`examples/good_script_benign.py`](examples/good_script_benign.py) and
[`examples/bad_script_suspicious.py`](examples/bad_script_suspicious.py) for a quick
before/after. Like the MCP server, it binds to `127.0.0.1` only by default
(`SDMCP_WEB_HOST`/`SDMCP_WEB_PORT` to change), has no authentication, is not meant to be
exposed beyond localhost, and never sends anything you paste anywhere beyond your own
machine.

## Claude Desktop / MCP client configuration

Add to your MCP client config (see
[`examples/claude-desktop-config.example.json`](examples/claude-desktop-config.example.json)
and [`examples/mcp-client-config.example.json`](examples/mcp-client-config.example.json)):

```json
{
  "mcpServers": {
    "script-decoder": {
      "command": "script-decoder-mcp",
      "args": []
    }
  }
}
```

If you haven't installed the console script, point `command` at the venv's Python and
`args` at `["-m", "script_decoder_mcp"]` instead.

## Tools

### `analyze_script`

Statically analyzes a full script. Input: `content` (required), `language` (default
`auto`), `filename`, `decode_embedded_content` (default `true`),
`include_line_references` (default `true`), `analysis_depth` (`quick`/`standard`/`deep`,
default `standard`), `map_to_attack` (default `true`).

Returns a `schema_version`-stamped response with `summary`, `plain_language_summary`,
`analyst_summary`, `verdict`, `risk_score`, `risk_factors`, `behaviors` (each with
`category`, `title`, `description`, `evidence`, `source_lines`, `confidence`,
`classification` — `observed`/`inferred`/`possible` — and `severity`),
`decoded_artifacts`, `indicators`, `imports`/`functions`/`commands`,
`suspicious_constructs`, `attack_techniques`, `limitations`, `errors`, and
`analysis_metadata`. `execution_performed` is always `false`.

### `decode_blob`

Decodes a single encoded/obfuscated value. Input: `content` (required),
`encoding_hint`, `max_depth`, `use_cyberchef` (default `false`). Returns the detected
encodings, every attempted decode chain, the successful chain (if any), decoded
text/size/hashes, any indicators found in the decoded text, and warnings. Never claims to
decode arbitrary encryption and never brute-forces a password or key.

### `extract_indicators`

Extracts IOCs from arbitrary text or source. Input: `content` (required). Returns a
dictionary of indicator categories, each entry carrying `value`, `normalized_value`,
`type`, `source_lines`, `context`, `confidence`, and `defanged_value`.

### `explain_command`

Explains a single command line. Input: `command` (required), `shell`
(`auto`/`cmd`/`powershell`/`bash`/`zsh`/`python`, default `auto`), `include_tokens`.
Returns executable, arguments, pipelines, redirections, environment changes, possible
side effects, and suspicious features — never runs the command.

### `compare_scripts`

Diffs two versions of a script. Input: `original_content`, `new_content`, `language`
(default `auto`), `decode_embedded_content` (default `true`). Returns added/removed
behaviors, changed indicators, newly decoded artifacts, the risk-score delta, and
line-level change summary.

## Example requests

See [`examples/sample_requests.md`](examples/sample_requests.md).

## CyberChef integration

Disabled by default (`SDMCP_CYBERCHEF_ENABLED=false`). When enabled, `decode_blob`
(with `use_cyberchef: true`) can fall back to a small local Node.js worker
(`cyberchef-worker/index.mjs`) for a fixed, allowlisted set of operations: `From Base64`,
`From Base32`, `From Hex`, `URL Decode`, `HTML Entity Decode`, `Gunzip`, `Zlib Inflate`,
`ROT13`, `Reverse`.

**Deviation from the original design**: rather than depending on the `cyberchef` npm
package (a large dependency tree), the worker reimplements these named operations using
only Node.js built-ins, in a single auditable file. It performs no network access, no
filesystem access beyond stdin, and no dynamic code loading. See
[`cyberchef-worker/README.md`](cyberchef-worker/README.md) for details and how to extend
it. It is never called against a public CyberChef instance, and native Python decoders
remain fully functional with or without it.

## Docker

```bash
docker compose build
docker compose run --rm script-decoder-mcp
```

The image runs as a non-root user, uses a multistage build, and does not require network
access at runtime. See [`Dockerfile`](Dockerfile) and
[`docker-compose.yml`](docker-compose.yml).

## Security model

- Static analysis only: no `eval`/`exec`/`compile`-for-execution of submitted content, no
  `os.system`/`shell=True` on submitted content, no importing of submitted scripts, no
  outbound network requests, no DNS resolution, no reputation-service calls.
- Every resource limit (input size, decode output size, decompression size, recursion
  depth, candidate branches, analysis duration, indicators per category, evidence excerpt
  length) is documented in [`.env.example`](.env.example) and enforced in
  [`src/script_decoder_mcp/limits.py`](src/script_decoder_mcp/limits.py); a threshold
  being hit is always reported as a limitation, never silently ignored.
- All submitted and decoded content is treated as untrusted data, never as instructions —
  see [`src/script_decoder_mcp/security/`](src/script_decoder_mcp/security/).
- No secrets are read, stored, or logged. Logging is JSON-structured and never emits full
  submitted content (see
  [`src/script_decoder_mcp/logging_config.py`](src/script_decoder_mcp/logging_config.py)).
- Full details: [`docs/threat-model.md`](docs/threat-model.md), [`SECURITY.md`](SECURITY.md).

## Limitations

- Static analysis cannot always determine runtime behavior. What a downloaded payload or
  a dynamically constructed command will actually do when run is not knowable from source
  alone, and responses/results of any network call are never fetched.
- Dynamically constructed values (string building, indirect calls) lower analysis
  confidence and are flagged as `inferred` or `possible`, not `observed`.
- Risk scoring is a heuristic, not a malware verdict; ATT&CK mapping is contextual
  evidence, not proof of malicious intent.
- Batch and VBScript analysis is rule-based (no safe parser exists for either), so it is
  less thorough than the Python AST analyzer.
- Encoding/administrative-tool use alone is never enough to mark something malicious.

## Supported languages

Python, PowerShell, Bash/sh/zsh, JavaScript, Batch, VBScript, and single command lines
(`auto`, or specify explicitly).

## Extending

See [`docs/adding-an-analyzer.md`](docs/adding-an-analyzer.md) and
[`docs/adding-a-decoder.md`](docs/adding-a-decoder.md).

## Testing

```bash
python -m pytest -v
```

Test fixtures under `tests/fixtures/` are inert text — no functional malware,
credential-stealing code, persistence payloads, destructive scripts, or live malicious
URLs. They use reserved example domains/IPs (`example.com`, `example.invalid`,
`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`).

Two ready-to-use manual test scripts are also included:
[`examples/good_script_benign.py`](examples/good_script_benign.py) (should score low/no
risk) and [`examples/bad_script_suspicious.py`](examples/bad_script_suspicious.py) (an
inert, non-functional script that *exhibits* several suspicious static patterns —
encoded payload, `exec`, outbound request construction — and should score meaningfully
higher). Neither script does anything if actually run; `bad_script_suspicious.py` is
deliberately written so it does not function even if a reader ignored this warning and
executed it.

## Troubleshooting

- **"No dedicated analyzer is available for language..."** — the language could not be
  detected or was not one of the six supported languages; indicator extraction and
  decoding still run.
- **CyberChef warnings in `decode_blob`** — expected when
  `SDMCP_CYBERCHEF_ENABLED=false` (the default) and `use_cyberchef: true` was requested
  anyway; native decoders already cover the same operations.
- **`errors` populated in `analyze_script`** — usually a Python `SyntaxError`; the
  response still includes a best-effort text-based fallback analysis.

## Privacy

No submitted script, decoded artifact, or any derived content is ever sent to a
third-party service. The optional CyberChef worker runs locally with no network access.
No telemetry is collected by default.

## Deployment

Intended for local, single-user use via stdio (the MCP-recommended local transport) or
containerized via Docker for isolation. The optional HTTP transport binds `127.0.0.1` by
default; exposing it beyond localhost is out of scope for this project and would require
adding authentication, which is not currently implemented — see
[`docs/threat-model.md`](docs/threat-model.md) for the residual risk this leaves if you
choose to do so anyway.

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: analyzing a full script, decoding a single blob, extracting indicators, explaining a command line, and comparing script versions. Although analyze_script returns decoded artifacts and indicators, the dedicated tools serve standalone use cases without ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern: analyze_script, decode_blob, extract_indicators, explain_command, compare_scripts. The naming convention is uniform and predictable.

Tool Count5/5

Five tools is well-scoped for a static script decoding and analysis server. Each tool covers a distinct capability without redundancy or bloat.

Completeness5/5

The tool surface covers the full static analysis workflow: analyzing scripts, decoding blobs, extracting indicators, explaining commands, and comparing versions. There are no obvious missing operations for the stated purpose of safely decoding and analyzing scripts without execution.

Maintenance

ActivityMaintained
ResponsivenessNo issues