Skip to main content
Glama
guyoverclocked

forensic-artifact-investigator

README.md
# Forensic Analyzer MCP

`forensic-analyzer-mcp` is a production-oriented [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for bounded, read-only inspection of forensic evidence files. It is built with NitroStack and calls real local forensic utilities—`file`, ExifTool, GNU `strings`, and Volatility—through literal argument-array subprocesses. It does not invent or simulate forensic output.

The GitHub repository is named **`forensic-analyzer-mcp`**. The MCP server identifier remains `forensic-artifact-investigator`, which is the name a client may display during discovery.

> **Important:** this is analyst-support software, not a malware verdict engine or a legal conclusion engine. An extension/MIME mismatch, a URL, a `malfind` result, or a VirusTotal count is evidence for review—not proof of maliciousness or innocence.

## Contents

- [What it does](#what-it-does)
- [Architecture](#architecture)
- [MCP capabilities](#mcp-capabilities)
- [Output and interpretation](#output-and-interpretation)
- [Platform support](#platform-support)
- [Linux setup](#linux-setup)
- [Windows setup](#windows-setup)
- [Docker setup](#docker-setup)
- [Connect Codex, Claude Code, or another MCP client](#connect-codex-claude-code-or-another-mcp-client)
- [Use the server](#use-the-server)
- [Configuration](#configuration)
- [Testing and verification](#testing-and-verification)
- [Security and operational limits](#security-and-operational-limits)
- [Troubleshooting](#troubleshooting)

## What it does

The server exposes three tools:

1. `extract-metadata` validates an evidence path, detects MIME type with `file`, reads metadata with ExifTool, calculates SHA-256, and compares the extension against a bundled expected-MIME reference table.
2. `extract-strings` runs real `strings -n 6`, returns bounded output, and labels matching IP addresses, URLs/domains, and suspicious keywords as **indicators**.
3. `analyze-memory-dump` runs Volatility 3 (preferred) or Volatility 2 against a Windows memory dump and reports each plugin independently as `success`, `failed`, or `unavailable`.

Every tool attempts exactly one serialized append to a local JSONL chain-of-custody log. The only optional network operation is a hash-only VirusTotal lookup; evidence bytes are never uploaded.

It intentionally does **not** perform YARA scanning, registry analysis, malware execution, uploading, deletion, sandboxing, or automatic severity scoring.

## Architecture

~~~mermaid
flowchart LR
  C["MCP client: Codex, Claude Code, or another harness"] --> S["NitroStack server over stdio"]
  S --> A["analyze module"]
  S --> M["memory module"]
  A --> P["Canonical evidence-path validation"]
  A --> F["file + ExifTool + GNU strings"]
  A --> H["Streaming SHA-256"]
  A --> L["Append-only JSONL analysis log"]
  A --> T["Optional hash-only VirusTotal lookup"]
  M --> P
  M --> V["Volatility 3 or explicit Volatility 2 fallback"]
  M --> L
  W["analysis-report widget artifact"] --> C
~~~

### How a request flows

1. The MCP client sends a tool request over stdio.
2. The server resolves `EVIDENCE_ROOT` and the requested path with `realpath`, rejects traversal/symlink escapes/non-regular files, and only permits files inside that root.
3. It invokes a configured forensic binary with `spawn(binary, args, { shell: false })`.
4. Command stdout/stderr, runtime, and return payloads are bounded. Timeouts and output caps are explicitly reported.
5. The structured result and a brief factual log record are returned to the client.

NitroStack supplies MCP registration, schema validation, stdio transport, and widget compilation. The bootstrap intentionally uses `createServer()` rather than NitroStack's starter factory because that factory adds a health resource and would violate the deliberately minimal resource inventory below.

## MCP capabilities

| Kind | Identity | Purpose |
| --- | --- | --- |
| Tool | `extract-metadata` | File type, Exif metadata, SHA-256, extension/MIME comparison |
| Tool | `extract-strings` | Bounded `strings -n 6` extraction and indicator scan |
| Tool | `analyze-memory-dump` | Real Volatility analysis with independent plugin status |
| Resource | `signatures://magic-bytes` | Bundled extension-to-expected-MIME reference table |
| Resource | `case://analysis-log` | Read-only recent view of the local append-only JSONL log |
| Resource template | `signatures://threat-intel/{hash}` | Hash-only reputation lookup; listed as `signatures://threat-intel` |
| Prompt | `full-file-analysis` | A safe, ordered analysis workflow prompt |
| Widget artifact | `analysis-report` | Static NitroStack display artifact for supported clients |

There are exactly two feature modules: `analyze` and `memory`. A fully native interactive MCP App widget must register a `ui://` resource. To preserve the literal three-resource protocol surface, this repository compiles the `analysis-report` artifact without registering an extra UI resource. Some MCP clients will expose the tools/resources/prompts but not render the widget; that is expected.

## Output and interpretation

All tool outputs are structured JSON. Paths, hashes, timestamps, byte counts, and forensic results vary by evidence and host. The following are shortened, sanitized examples of the shapes clients receive.

### extract-metadata

Request:

~~~json
{ "filePath": "/evidence/suspect_photo.jpg" }
~~~

Representative success response:

~~~json
{
  "tool": "extract-metadata",
  "success": true,
  "targetFile": "/evidence/suspect_photo.jpg",
  "fileSizeBytes": 18342,
  "extension": ".jpg",
  "detectedMimeType": "image/jpeg",
  "expectedMimeTypes": ["image/jpeg"],
  "extensionKnown": true,
  "extensionMismatch": false,
  "mismatchReason": null,
  "mismatchCheckStatus": "checked",
  "sha256": "<64-character SHA-256>",
  "hashAlgorithm": "sha256",
  "metadata": {
    "camera": { "make": null, "model": null, "lens": null },
    "gps": { "latitude": null, "longitude": null, "altitude": null },
    "software": null,
    "timestamps": {
      "createDate": null,
      "dateTimeOriginal": null,
      "modifyDate": null
    },
    "dimensions": { "width": 640, "height": 480 }
  },
  "filesystem": {
    "mode": 33188,
    "modifiedAt": "2026-07-13T00:00:00.000Z",
    "changedAt": "2026-07-13T00:00:00.000Z",
    "birthtime": "2026-07-13T00:00:00.000Z"
  },
  "execution": {
    "fileCommand": { "success": true, "durationMs": 8, "exitCode": 0, "signal": null, "timedOut": false, "truncated": false, "capturedBytes": 11 },
    "exiftoolCommand": { "success": true, "durationMs": 42, "exitCode": 0, "signal": null, "timedOut": false, "truncated": false, "capturedBytes": 572 }
  }
}
~~~

`extensionMismatch: true` means the `file` utility detected a MIME type outside the expected list for the observed extension. It is a confirmed **file-type discrepancy**, not a malware verdict. The bundled table is a comparison reference; real MIME detection comes from the operating system's `file` executable.

### extract-strings

Request:

~~~json
{ "filePath": "/evidence/suspect.bin" }
~~~

Representative response for a file containing indicators:

~~~json
{
  "tool": "extract-strings",
  "success": true,
  "targetFile": "/evidence/suspect.bin",
  "totalCapturedStringLines": 132,
  "returnedStringCount": 132,
  "returnedStringLines": ["...", "https://example.invalid/loader", "powershell -EncodedCommand ..."],
  "resultsTruncated": false,
  "truncationReason": null,
  "truncationReasons": [],
  "patternMatches": {
    "ipAddresses": [{ "value": "198.51.100.42", "normalizedValue": "198.51.100.42", "sourceString": "connect 198.51.100.42" }],
    "urlsAndDomains": [{ "value": "https://example.invalid/loader", "normalizedValue": "https://example.invalid/loader", "sourceString": "https://example.invalid/loader" }],
    "suspiciousKeywords": [{ "keyword": "powershell", "sourceString": "powershell -EncodedCommand ..." }]
  },
  "patternMatchesTruncated": false,
  "execution": {
    "stringsCommand": { "success": true, "durationMs": 11, "exitCode": 0, "signal": null, "timedOut": false, "truncated": false, "capturedBytes": 4521 }
  }
}
~~~

When `resultsTruncated` is `true`, the response is deliberately partial. Read `truncationReasons` before drawing conclusions; the complete tool output was not retained in MCP memory.

### analyze-memory-dump

Request:

~~~json
{ "filePath": "/evidence/windows-memory.raw" }
~~~

Representative partial/failure response:

~~~json
{
  "tool": "analyze-memory-dump",
  "targetFile": "/evidence/windows-memory.raw",
  "success": false,
  "volatilityVersion": 3,
  "selectedProfile": null,
  "processList": [],
  "networkConnections": [],
  "flaggedInjectedRegions": [],
  "pluginResults": {
    "info": { "status": "failed", "parsed": false, "error": "Volatility plugin exited with code 1.", "rawOutputExcerpt": "..." },
    "pslist": { "status": "failed", "parsed": false, "error": "Volatility plugin exited with code 1.", "rawOutputExcerpt": "..." },
    "netscan": { "status": "unavailable", "parsed": false, "error": "A compatible dump or symbols are required.", "rawOutputExcerpt": "" },
    "malfind": { "status": "unavailable", "parsed": false, "error": "A compatible dump or symbols are required.", "rawOutputExcerpt": "" }
  }
}
~~~

`success: false` does **not** mean the dump is clean. It means no Volatility plugin completed successfully. Likewise, a successful `malfind` plugin with zero rows only reports that plugin's returned data; it is not a blanket clean verdict.

### Resources

- `signatures://magic-bytes` returns the bundled expected-MIME mapping used by the extension comparison.
- `case://analysis-log` returns bounded recent JSONL plus `lineCount`, `truncated`, and `omittedBytes`. It never returns full raw strings/Volatility output.
- `signatures://threat-intel/<md5-or-sha1-or-sha256>` returns a small reputation object. The deterministic EICAR fixture can be checked without a key or network access:

~~~json
{
  "status": "found",
  "source": "deterministic-eicar-fixture",
  "hash": "44d88612fea8a8f36de82e1278abb02f",
  "hashAlgorithm": "md5",
  "malicious": 1,
  "suspicious": 0,
  "harmless": 0,
  "undetected": 0,
  "timeout": 0,
  "failure": 0,
  "summary": "Known EICAR antivirus test-file hash; deterministic test response."
}
~~~

For any other valid hash with no API key configured, the resource returns:

~~~json
{
  "status": "not configured",
  "message": "Set VIRUSTOTAL_API_KEY to enable live threat intelligence lookups."
}
~~~

An unknown VirusTotal hash is not proof that the evidence is safe. Only a hash—not the evidence file—is sent to VirusTotal when a key is configured.

## Platform support

| Platform | Status | Notes |
| --- | --- | --- |
| Linux native | Supported | Primary development/runtime path. The setup script supports apt, dnf, yum, and apk. |
| Linux Docker | Supported configuration | Debian/Node 24 image packages the required tools; use a persistent log mount. |
| Windows + WSL2 | Recommended | Run the Linux instructions inside Ubuntu or another WSL distribution. |
| Windows + Docker Desktop | Recommended | Run the Linux container from PowerShell and mount evidence read-only. |
| Native Windows | Best effort | Provide compatible `file`, ExifTool, GNU `strings`, and Volatility executables explicitly. |
| macOS | Development best effort | Not the target deployment; set paths to GNU-compatible binaries explicitly. |

The CI workflow runs the portable test/build/stdio-verification path on Ubuntu. A real Windows memory image is not bundled, so successful Volatility analysis must be validated against an authorized, compatible dump in your own environment.

## Linux setup

### 1. Clone and install

~~~bash
git clone https://github.com/guyoverclocked/forensic-analyzer-mcp.git
cd forensic-analyzer-mcp

# Installs Linux dependencies and Volatility 3. A project-local evidence
# directory avoids requiring a writable /evidence directory on the host.
EVIDENCE_ROOT="$PWD/evidence" bash scripts/setup-environment.sh

npm ci
npm --prefix src/widgets ci
mkdir -p evidence forensic-logs
cp .env.example .env
~~~

The Bash setup helper installs or verifies Node.js 20+, `file`, ExifTool, GNU binutils (`strings`), Python, and Volatility 3. It prints verified executable paths at the end. It does not write secrets or overwrite an existing `.env`.

### 2. Configure the server

Edit `.env` and replace every path with an absolute path from your machine. For a Volatility venv created by the helper, a typical native Linux configuration is:

~~~dotenv
EVIDENCE_ROOT=/absolute/path/to/forensic-analyzer-mcp/evidence
ANALYSIS_LOG_PATH=/absolute/path/to/forensic-analyzer-mcp/forensic-logs/analysis-log.jsonl
FILE_BINARY=/usr/bin/file
EXIFTOOL_BINARY=/usr/bin/exiftool
STRINGS_BINARY=/usr/bin/strings
VOLATILITY_MAJOR_VERSION=3
VOLATILITY_BINARY=/absolute/path/to/forensic-analyzer-mcp/.venv-volatility/bin/vol
MCP_TRANSPORT_TYPE=stdio
~~~

Some Volatility installations create `vol3` rather than `vol`; use the actual path printed by `scripts/setup-environment.sh`. Put only authorized evidence inside `EVIDENCE_ROOT`. Do not commit that directory or the log to Git.

### 3. Build and verify

~~~bash
npm run lint
npm run typecheck
npm test
npm run build

EVIDENCE_ROOT="$PWD/tests/fixtures" \
EVIDENCE_TEST_PATH="$PWD/tests/fixtures/suspect_photo.jpg" \
npm run verify:mcp
~~~

`npm run verify:mcp` starts the built server as a real stdio MCP child process, confirms the exact tool/resource/prompt inventory, reads the EICAR fixture, and—when `EVIDENCE_TEST_PATH` is set—exercises metadata and strings extraction against the harmless tracked JPEG fixture.

### 4. Start through an MCP client

The production process is a stdio server, not an HTTP daemon:

~~~bash
npm run start:prod
~~~

Running it directly will appear to wait without printing a prompt. That is normal: it is waiting for JSON-RPC messages on standard input. Launch it through Codex, Claude Code, another MCP client, or the verifier rather than typing shell commands into it.

## Windows setup

### Recommended: WSL2

In an elevated PowerShell window, install WSL if needed:

~~~powershell
wsl --install -d Ubuntu
~~~

Restart when Windows asks, open Ubuntu, and follow the [Linux setup](#linux-setup). Keep the repository under your WSL home directory for best filesystem performance. Evidence stored on Windows can be mounted through paths such as `/mnt/c/Forensics/Case-001/evidence`:

~~~bash
export EVIDENCE_ROOT=/mnt/c/Forensics/Case-001/evidence
bash scripts/setup-environment.sh
~~~

Then set the same WSL path in `.env` and in the MCP client configuration that runs inside WSL.

### Native Windows (best effort)

The supplied setup script is Bash/Linux-only. Native Windows can work only if you install and point the server at compatible executables. Use Node.js 20+, ExifTool for Windows, a compatible `file.exe`, GNU `strings.exe` that supports `-n 6`, and Volatility.

In PowerShell, install dependencies and set absolute executable paths before building:

~~~powershell
git clone https://github.com/guyoverclocked/forensic-analyzer-mcp.git
Set-Location forensic-analyzer-mcp
npm ci
npm --prefix src/widgets ci

$env:EVIDENCE_ROOT = "C:\Forensics\Case-001\evidence"
$env:ANALYSIS_LOG_PATH = "$PWD\forensic-logs\analysis-log.jsonl"
$env:FILE_BINARY = "C:\Tools\file\file.exe"
$env:EXIFTOOL_BINARY = "C:\Tools\exiftool\exiftool.exe"
$env:STRINGS_BINARY = "C:\Tools\binutils\strings.exe"
$env:VOLATILITY_MAJOR_VERSION = "3"
$env:VOLATILITY_BINARY = "C:\Tools\volatility\vol.exe"
$env:MCP_TRANSPORT_TYPE = "stdio"

npm run build
node .\dist\index.js
~~~

If any command is unavailable or incompatible, use WSL2 or Docker Desktop instead. Do not treat a missing tool's failure as a clean analysis result.

## Docker setup

Docker provides a Linux runtime containing `file`, ExifTool, GNU `strings`, Python, and Volatility 3 at `/opt/volatility/bin/vol`.

### Linux/macOS shell

~~~bash
docker build -t forensic-analyzer-mcp .
mkdir -p evidence forensic-logs

docker run --rm -i \
  -v "$PWD/evidence:/evidence:ro" \
  -v "$PWD/forensic-logs:/app/data" \
  forensic-analyzer-mcp
~~~

The evidence mount is deliberately read-only. The second mount persists the append-only log because `--rm` deletes the container filesystem on exit. As with native stdio, a bare `docker run -i` appears to wait because it is waiting for MCP messages; configure the same command as an MCP server in a client.

### Windows PowerShell / Docker Desktop

~~~powershell
docker build -t forensic-analyzer-mcp .
New-Item -ItemType Directory -Force evidence, forensic-logs
docker run --rm -i -v "${PWD}\evidence:/evidence:ro" -v "${PWD}\forensic-logs:/app/data" forensic-analyzer-mcp
~~~

For Docker configuration, the image already has these values:

~~~dotenv
EVIDENCE_ROOT=/evidence
FILE_BINARY=/usr/bin/file
EXIFTOOL_BINARY=/usr/bin/exiftool
STRINGS_BINARY=/usr/bin/strings
VOLATILITY_BINARY=/opt/volatility/bin/vol
VOLATILITY_MAJOR_VERSION=3
MCP_TRANSPORT_TYPE=stdio
~~~

## Connect Codex, Claude Code, or another MCP client

Build first with `npm run build`. For safety and portability, pass absolute `EVIDENCE_ROOT` and `ANALYSIS_LOG_PATH` values through the MCP client. Do not rely on `.env` when the client may launch the process from another working directory.

### Codex

Codex supports local stdio MCP servers in `~/.codex/config.toml`; a trusted project can instead use `.codex/config.toml`. Add the following to your local configuration, replacing each placeholder with an absolute path:

~~~toml
[mcp_servers.forensic-analyzer]
command = "node"
args = ["/absolute/path/to/forensic-analyzer-mcp/dist/index.js"]
cwd = "/absolute/path/to/forensic-analyzer-mcp"
startup_timeout_sec = 30
tool_timeout_sec = 360
default_tools_approval_mode = "prompt"

# Optional: only forward this if a VirusTotal lookup is authorized.
env_vars = ["VIRUSTOTAL_API_KEY"]

[mcp_servers.forensic-analyzer.env]
EVIDENCE_ROOT = "/absolute/path/to/authorized-evidence"
ANALYSIS_LOG_PATH = "/absolute/path/to/forensic-logs/analysis-log.jsonl"
FILE_BINARY = "/usr/bin/file"
EXIFTOOL_BINARY = "/usr/bin/exiftool"
STRINGS_BINARY = "/usr/bin/strings"
VOLATILITY_MAJOR_VERSION = "3"
VOLATILITY_BINARY = "/absolute/path/to/forensic-analyzer-mcp/.venv-volatility/bin/vol"
MCP_TRANSPORT_TYPE = "stdio"
~~~

Restart Codex, then use `/mcp` to confirm that `forensic-analyzer` is connected. The [official Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp) documents the `[mcp_servers.<server-name>]` table and its `command`, `args`, `cwd`, `env`, and `env_vars` fields.

For a Docker-backed Codex server, replace `command` and `args` with a `docker run --rm -i` command and mount the host evidence directory read-only. Keep the host log mount writable and never include secrets in a repository-scoped configuration file.

### Claude Code

Use a user-scoped local stdio configuration so host paths and any credentials remain private. Replace the paths below:

~~~bash
claude mcp add \
  --scope user \
  --transport stdio \
  --env EVIDENCE_ROOT=/absolute/path/to/authorized-evidence \
  --env ANALYSIS_LOG_PATH=/absolute/path/to/forensic-logs/analysis-log.jsonl \
  --env FILE_BINARY=/usr/bin/file \
  --env EXIFTOOL_BINARY=/usr/bin/exiftool \
  --env STRINGS_BINARY=/usr/bin/strings \
  --env VOLATILITY_MAJOR_VERSION=3 \
  --env VOLATILITY_BINARY=/absolute/path/to/forensic-analyzer-mcp/.venv-volatility/bin/vol \
  --env MCP_TRANSPORT_TYPE=stdio \
  forensic-analyzer \
  -- node /absolute/path/to/forensic-analyzer-mcp/dist/index.js
~~~

Verify it with:

~~~bash
claude mcp list
claude mcp get forensic-analyzer
~~~

Inside Claude Code, use `/mcp` to inspect connection status, then invoke a tool. The [Claude Code MCP guide](https://code.claude.com/docs/en/mcp) documents `claude mcp add`, scopes, `--env`, and the required `--` separator before the server command. On native Windows, use the full `node.exe` path if `node` is not discoverable by Claude Code.

### Generic stdio MCP configuration

Many harnesses accept a JSON object in this shape (the enclosing key/name differs by client):

~~~json
{
  "mcpServers": {
    "forensic-analyzer": {
      "command": "node",
      "args": ["/absolute/path/to/forensic-analyzer-mcp/dist/index.js"],
      "cwd": "/absolute/path/to/forensic-analyzer-mcp",
      "env": {
        "EVIDENCE_ROOT": "/absolute/path/to/authorized-evidence",
        "ANALYSIS_LOG_PATH": "/absolute/path/to/forensic-logs/analysis-log.jsonl",
        "FILE_BINARY": "/usr/bin/file",
        "EXIFTOOL_BINARY": "/usr/bin/exiftool",
        "STRINGS_BINARY": "/usr/bin/strings",
        "VOLATILITY_MAJOR_VERSION": "3",
        "VOLATILITY_BINARY": "/absolute/path/to/forensic-analyzer-mcp/.venv-volatility/bin/vol",
        "MCP_TRANSPORT_TYPE": "stdio"
      }
    }
  }
}
~~~

Some clients require `"type": "stdio"`; add it if their schema asks for a transport type. Restart or reload the client after saving configuration. Never place `VIRUSTOTAL_API_KEY` or evidence paths in a shared/committed config file.

## Use the server

Once discovery succeeds, an analyst can perform an ordered review:

1. Call `extract-metadata` for an authorized file.
2. Call `extract-strings` on the same file.
3. Read `signatures://threat-intel/<sha256-from-metadata>` if a policy-approved hash lookup is needed.
4. Read `case://analysis-log` to verify the local chronology.
5. For a legitimate Windows dump, call `analyze-memory-dump` and inspect every item in `pluginResults`.

The `full-file-analysis` prompt encodes this workflow. Invoke it with `filePath` and `evidenceType` (`file`, `image`, or `memory_dump`); it requests a report with the headings `Confirmed Anomalies`, `Possible Anomalies`, and `Clean` in that order.

Example analyst prompt for any MCP-aware harness:

~~~text
Use full-file-analysis for /evidence/suspect_photo.jpg as an image. Show the
raw tool conclusions, distinguish a confirmed file-type discrepancy from
possible indicators, and state any incomplete/failed analysis explicitly.
~~~

## Configuration

`.env.example` lists safe defaults. The process loads `.env` only relative to its current working directory, so MCP client configuration should still supply absolute paths explicitly.

| Variable | Meaning | Default |
| --- | --- | --- |
| `EVIDENCE_ROOT` | Canonical directory from which evidence can be read | `/evidence` |
| `ANALYSIS_LOG_PATH` | Append-only JSONL chain-of-custody log destination | `data/analysis-log.jsonl` under process cwd |
| `FILE_BINARY` | `file` executable path/name | `file` |
| `EXIFTOOL_BINARY` | ExifTool executable path/name | `exiftool` |
| `STRINGS_BINARY` | GNU-compatible `strings` executable path/name | `strings` |
| `VOLATILITY_MAJOR_VERSION` | `3` (preferred) or explicit `2` mode | `3` |
| `VOLATILITY_BINARY` | Verified Volatility executable path | unset; memory tool returns a dependency error |
| `VIRUSTOTAL_API_KEY` | Optional key for hash-only reputation lookups | unset; no network lookup |
| `FILE_COMMAND_TIMEOUT_MS` | Timeout for `file`, ExifTool, and strings | `60000` |
| `VOLATILITY_TIMEOUT_MS` | Timeout per Volatility plugin | `300000` |
| `MAX_RETURNED_STRINGS` | Maximum strings retained in MCP response | `5000` |
| `MAX_STRING_OUTPUT_BYTES` | Captured strings stdout cap | `2000000` |
| `VOLATILITY_MAX_OUTPUT_BYTES` | Captured Volatility stdout cap | `5000000` |
| `MAX_SUBPROCESS_STDOUT_BYTES`, `MAX_SUBPROCESS_STDERR_BYTES` | General command capture caps | `2000000`, `512000` |
| `MCP_TRANSPORT_TYPE` | MCP transport selection | `stdio` |

Volatility 3 is the intended mode. It runs `windows.info`, `windows.pslist`, `windows.netscan`, and `windows.malfind` sequentially using JSON output where supported. Volatility 2 is only selected when explicitly configured: it first runs `imageinfo`, uses the first clearly suggested profile, and marks Windows plugins unavailable if no profile can be selected.

## Testing and verification

Run these before publishing changes:

~~~bash
npm run lint
npm run typecheck
npm test
npm run build
npm run verify:mcp
~~~

Expected successful outcomes:

| Command | What it verifies |
| --- | --- |
| `npm run lint` | ESLint has no warnings/errors in source and tests |
| `npm run typecheck` | TypeScript compiles without emitting files |
| `npm test` | Unit tests plus real-command integration tests when compatible binaries are available |
| `npm run build` | NitroStack server and `analysis-report` widget artifact build |
| `npm run verify:mcp` | Real stdio discovery, exact capability inventory, EICAR resource, prompt, and optional fixture tool calls |

The integration test skips cleanly when its configured external forensic binaries are not available. A skip means that real-binary path was not exercised in that environment; it is not a successful forensic test. On Linux CI, the required packages are installed before the suite runs.

## Security and operational limits

Read [SECURITY.md](SECURITY.md) before deploying against real evidence. In summary:

- Every evidence path is canonicalized and confined to `EVIDENCE_ROOT`. Traversal, symlink escapes, directories, special files, and unreadable paths are rejected before a forensic subprocess runs.
- Subprocesses use `spawn` with `shell: false`, literal argument arrays, timeouts, and bounded capture. VirusTotal credentials are removed from spawned forensic-command environments.
- Evidence is read-only. Mount it read-only in Docker/production. Run the server in a constrained VM or container because parsers can still have vulnerabilities.
- The log is append-only only from this process's perspective. Protect its directory and retention policy as part of chain of custody; an administrator can still alter files outside this application.
- Metadata can be absent, rewritten, or falsified. IP/URL/keyword strings, Volatility output, and VirusTotal counts require human review.

This repository does not include real case evidence, a real Windows memory dump, credentials, `node_modules`, build output, logs, or local Python environments. The tracked JPEG in `tests/fixtures/` is a small harmless test fixture only.

## Troubleshooting

| Symptom | Cause and action |
| --- | --- |
| Server appears to hang in a terminal | Normal for a stdio MCP server. Launch it through a client or run `npm run verify:mcp`; use `Ctrl+C` to stop a direct test launch. |
| `SUBPROCESS_BINARY_NOT_FOUND` | Install the forensic utility or set the relevant `*_BINARY` variable to an absolute executable path. |
| `PATH_OUTSIDE_EVIDENCE_ROOT` | The requested path resolves outside the configured root, including via a symlink. Put authorized evidence inside `EVIDENCE_ROOT` and retry with its canonical path. |
| `ANALYSIS_LOG_FAILED` | Ensure the parent directory of `ANALYSIS_LOG_PATH` is writable; use a persistent Docker bind mount at `/app/data`. |
| `extensionMismatch: true` | Investigate the bytes and provenance. It is not, by itself, a malware finding. |
| `resultsTruncated: true` | Treat strings output as incomplete; adjust limits only with an explicit resource/security review. |
| Memory plugins fail or are unavailable | Confirm a compatible Windows dump, Volatility version, symbol availability, executable path, and `VOLATILITY_TIMEOUT_MS`. Failed plugins are intentionally not turned into empty clean results. |
| Docker cannot read evidence | Check Docker Desktop file sharing and use an absolute host path. Keep the `/evidence` mount read-only. |
| Codex/Claude Code cannot start the server | Build first, use absolute paths to `node`/`dist/index.js` and runtime files, then restart/reload the client and inspect its MCP status panel. |
| VirusTotal returns `not configured` | This is intentional without `VIRUSTOTAL_API_KEY`; add a key only if hash sharing is authorized. |

## Repository layout

~~~text
.
├── src/                 # NitroStack server, analyze and memory modules
├── src/widgets/         # analysis-report widget source
├── resources/           # expected-MIME reference data
├── scripts/             # Linux environment setup and MCP verifier
├── tests/               # unit, integration, and harmless fixtures
├── data/.gitkeep        # runtime log directory placeholder
├── Dockerfile           # Debian/Node 24 runtime
├── SECURITY.md          # deployment/security review
└── AGENTS.md            # contributor/harness operating guidance
~~~

No open-source license is included yet. Add a license file before representing the repository as reusable open-source software.

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct forensic operation: file metadata and hashing, string extraction, and memory dump analysis. There is no overlap in their purposes, making selection unambiguous.

Naming Consistency4/5

Tool names follow a clear verb_noun pattern: extract-metadata, extract-strings, analyze-memory-dump. The verbs differ (extract vs. analyze) but this is justifiable based on the action type, and the pattern is consistent throughout.

Tool Count4/5

Three tools is on the lower end, but each tool covers a broad, substantial forensic category. The count is appropriate for a focused investigator server, though could be slightly expanded.

Completeness4/5

The set covers core artifact investigation needs: metadata extraction (with MIME mismatch and hash), string/indicator extraction, and memory analysis. Minor gaps like disk image carving or network artifact analysis exist, but the major workflows are represented.

Maintenance

ActivityStale
ResponsivenessNo issues