-
Notifications
You must be signed in to change notification settings - Fork 0
Security
This page documents the security architecture of the MATLAB MCP Server, covering authentication, input validation, secure configuration, deployment best practices, and vulnerability reporting.
The server implements session isolation to separate requests from different users or agents:
graph TD
A["AI Agent / Client"] -->|Session ID| B["MCP Server"]
B -->|Session Lookup| C["Session Manager"]
C -->|Temp Directory| D["Isolated Workspace"]
D -->|clear all| E["MATLAB Engine"]
A2["Another Agent"] -->|Different Session ID| B
B -->|Session Lookup| C2["Session Manager"]
C2 -->|Different Temp Dir| D2["Isolated Workspace"]
- Each session has a unique ID and isolated temporary directory
- Sessions are authenticated implicitly by their ID (no username/password in stdio mode)
- For multi-user SSE deployments, reverse proxy authentication is required (see below)
When using SSE (Server-Sent Events) for multi-user deployments:
server:
transport: "sse"
host: "127.0.0.1" # Never expose directly to internet
port: 8765
security:
require_proxy_auth: true # Set to true ONLY after setting up proxy authRequired setup:
- Place the server behind a reverse proxy (nginx, Caddy, Traefik, Apache)
- Configure the proxy with authentication (JWT, OAuth, OIDC, mTLS, basic auth)
- The proxy forwards authenticated requests with a header (e.g.,
X-User-ID) - Set
require_proxy_auth: trueto acknowledge this is configured
Example nginx config with basic auth:
upstream matlab_mcp {
server 127.0.0.1:8765;
}
server {
listen 443 ssl http2;
server_name matlab.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
auth_basic "MATLAB MCP Server";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://matlab_mcp;
proxy_set_header X-User-ID $remote_user;
}
}The server logs a warning at startup if SSE transport is enabled without require_proxy_auth: true.
All user-provided MATLAB code is checked before execution by the SecurityValidator class:
graph LR
A["User Code"] -->|Security Check| B{Blocked Functions?}
B -->|Found| C["❌ Reject"]
B -->|Not Found| D["✓ Execute"]
C --> E["Error Response"]
D --> F["Job Executor"]
By default, these functions are blocked:
| Function | Risk | Alternative |
|---|---|---|
system() |
OS command injection | Use MATLAB's built-in functions (e.g., fileread, writematrix) |
unix() |
Unix command injection | — |
dos() |
Windows command injection | — |
! |
Shell escape operator | — |
eval() |
Arbitrary code execution | Use function handles or feval() with validated function names |
feval() |
Function name string injection | Whitelist approved function names in code review |
evalc() |
Evaluate and capture | — |
evalin() |
Context-aware eval | — |
assignin() |
Variable injection | Direct assignment in workspace |
perl() |
Perl script injection | — |
python() |
Python script injection | Use MATLAB's Python integration with validated scripts |
web() |
Network requests | Use webread() / webwrite() with validated URLs |
The validator strips string literals and comments before checking, preventing false positives:
% ✓ SAFE (will NOT trigger blocklist):
disp('system call is dangerous') % "system" inside string
% system('ls') % "system" inside comment
msg = "unix-style paths"; % "unix" inside string
help_url = "https://system.example.com"; % "system" inside URL string
% ❌ BLOCKED (actual function calls):
system('rm -rf /') % Direct call
result = eval(code_string) % Direct call
data = evalin('base', 'x*2') % Direct callScanning algorithm:
- Remove all MATLAB comments (
%to end-of-line,%{...%}block comments) - Remove all string literals (
'...'and"...", including escaped quotes) - Check remaining code for blocked function names using regex
File operations (upload_data, delete_file, read_script, etc.) sanitize filenames to prevent path traversal attacks:
% ❌ REJECTED:
../../../etc/passwd % Path traversal
..\..\windows\system32 % Windows traversal
/etc/shadow % Absolute path
C:\Windows\System32 % Absolute Windows pathAllowed characters:
- Letters:
a-zA-Z - Digits:
0-9 - Punctuation:
._-
Examples of valid filenames:
data.csvmy-script_v2.msimulation.results.2024.mat
Implementation (src/matlab_mcp/security/validator.py):
import re
VALID_FILENAME = re.compile(r'^[a-zA-Z0-9._\-]+$')
def validate_filename(filename: str) -> bool:
"""Check filename contains only safe characters."""
return bool(VALID_FILENAME.match(filename)) and len(filename) > 0security:
max_upload_size_mb: 100 # Default limitFiles exceeding this limit are rejected before being written to disk:
async def upload_data_impl(..., max_size_mb: int = 100) -> dict:
max_bytes = max_size_mb * (1024 ** 2)
if len(decoded_content) > max_bytes:
return {"status": "error", "message": f"File exceeds {max_size_mb}MB"}# config.yaml
security:
# Function blocklist
blocked_functions_enabled: true
blocked_functions:
- "system"
- "unix"
- "dos"
- "!"
- "eval"
- "feval"
- "evalc"
- "evalin"
- "assignin"
- "perl"
- "python"
- "web"
# Add additional functions as needed
# Upload protection
max_upload_size_mb: 100
# Multi-user SSE security
require_proxy_auth: false # Set to true only after configuring reverse proxy auth
execution:
# Workspace isolation between sessions
workspace_isolation: true # Clears variables between jobs
sessions:
# Session expiration
session_timeout: 3600 # Seconds (1 hour)
max_sessions: 50
job_retention_seconds: 86400 # 24 hoursAll security settings can be overridden via environment variables using the MATLAB_MCP_ prefix:
# Override max upload size
export MATLAB_MCP_SECURITY_MAX_UPLOAD_SIZE_MB=500
# Disable function blocklist (NOT recommended)
export MATLAB_MCP_SECURITY_BLOCKED_FUNCTIONS_ENABLED=false
# Require proxy auth for SSE
export MATLAB_MCP_SECURITY_REQUIRE_PROXY_AUTH=true| Setting | Personal | Team | Production |
|---|---|---|---|
blocked_functions_enabled |
true |
true |
true |
max_upload_size_mb |
100 |
500 |
100 |
workspace_isolation |
true |
true |
true |
session_timeout |
3600 |
1800 |
1800 |
max_sessions |
50 |
20 |
10 |
require_proxy_auth |
false |
true |
true |
transport |
stdio |
sse |
sse |
When workspace_isolation: true (default), the server executes these commands between jobs in the same session:
clear all; % Remove all variables
clear global; % Remove global variables
clear functions; % Forget function definitions
fclose all; % Close file handles
restoredefaultpath; % Restore default MATLAB pathBenefits:
- Variables from one execution don't leak to the next
- File handles don't persist across jobs
- Function caches are cleared
- MATLAB path is reset to defaults
Disable if needed (not recommended):
execution:
workspace_isolation: falseMinimal configuration:
server:
transport: "stdio" # No network exposure
log_level: "info"
pool:
min_engines: 1
max_engines: 2
security:
blocked_functions_enabled: true
max_upload_size_mb: 100Deployment:
- Connect via Claude Desktop, VS Code, Cursor, or other MCP client
- No authentication required (implicit via client connection)
Recommended configuration:
server:
transport: "sse"
host: "127.0.0.1" # Listen only on localhost
port: 8765
log_level: "info"
pool:
min_engines: 2
max_engines: 8
health_check_interval: 30
execution:
sync_timeout: 30
workspace_isolation: true
security:
blocked_functions_enabled: true
max_upload_size_mb: 200
require_proxy_auth: true # MUST be true
sessions:
max_sessions: 20
session_timeout: 1800 # 30 minutes
job_retention_seconds: 86400
monitoring:
enabled: true # Monitor for abuse
sample_interval: 10Deployment steps:
- Install the server on a Linux/macOS machine (not Windows for file permissions)
- Set up reverse proxy (nginx, Caddy, Traefik)
- Configure proxy authentication (LDAP, OAuth, JWT, mTLS)
- Configure TLS/SSL on the proxy
- Use
require_proxy_auth: truein config - Enable monitoring to detect suspicious activity
- Regularly review error logs and blocked function attempts
Example Caddy reverse proxy with JWT:
matlab.example.com {
encode gzip
# Validate JWT token
jwt {
algorithms HS256
trusted_keys {
key1 file /etc/caddy/jwt-key.txt
}
}
# Reverse proxy to local server
reverse_proxy 127.0.0.1:8765 {
header_up X-User-ID {http.auth.user}
header_up X-Forwarded-For {http.request.remote}
}
}Hardened configuration:
server:
name: "matlab-mcp-prod"
transport: "sse"
host: "127.0.0.1" # Never expose directly
port: 8765
log_level: "warn" # Reduce noise
pool:
min_engines: 4
max_engines: 16
queue_max_size: 100
health_check_interval: 20
execution:
sync_timeout: 20 # Stricter timeout
max_execution_time: 3600 # 1 hour max
workspace_isolation: true
security:
blocked_functions_enabled: true
blocked_functions:
- "system"
- "unix"
- "dos"
- "!"
- "eval"
- "feval"
- "evalc"
- "evalin"
- "assignin"
- "perl"
- "python"
- "web"
- "load" # Add custom restrictions
- "save"
- "diary"
max_upload_size_mb: 50 # Stricter limit
require_proxy_auth: true # MANDATORY
sessions:
max_sessions: 10 # Small pool
session_timeout: 900 # 15 minutes
job_retention_seconds: 86400
monitoring:
enabled: true
sample_interval: 5 # Frequent sampling
store_events: true # Persistent loggingAdditional hardening:
- Network isolation: Run server in a restricted network segment
- TLS/mTLS: Require client certificates in addition to auth
- Rate limiting: Add to reverse proxy (e.g., 10 requests/sec per user)
- Audit logging: Store all requests and responses (via monitoring events)
- Resource quotas: Limit jobs per user, total concurrent jobs
- IP whitelisting: Restrict proxy access to known IP ranges
- Regular updates: Keep MATLAB engine and Python dependencies current
- Intrusion detection: Monitor for repeated blocked function attempts
- Secrets management: Store auth keys in a vault (HashiCorp Vault, AWS Secrets Manager)
Example audit logging (parse monitoring events):
# Retrieve event log
from matlab_mcp.monitoring.store import MetricsStore
store = MetricsStore("/path/to/metrics.db")
events = await store.get_events(event_type="blocked_function", limit=1000)
for event in events:
if event["details"]["function"] == "system":
print(f"ALERT: Blocked system() call at {event['timestamp']}")
# Send to SIEM systemDo NOT open a public GitHub issue for security vulnerabilities.
Instead, please email the maintainers with:
- Title: Description of the vulnerability
- Description: Detailed explanation of the issue
- Reproduction steps: How to trigger the vulnerability
- Impact: What could an attacker do?
- Suggested fix: Optional, but helpful
Email: (Check the GitHub repository's SECURITY.md file for contact information, or file a private GitHub security advisory at https://github.com/HanSur94/matlab-mcp-server-python/security/advisories/new)
- Vulnerability is reported privately
- Maintainers assess severity and impact
- A fix is developed and tested
- A security release is published with detailed notes
- Users are notified via GitHub Releases
- Stdio transport: Only safe for single-user, local use. No built-in encryption
- Function blocklist: Cannot catch all injection attacks (always review user-provided code)
- Async jobs: Results are stored in temp files; ensure proper file permissions
- MATLAB version: Only tested with MATLAB R2020b+
graph TD
A["Agent 1<br/>Session: abc123"] -->|Code| B["Security Validator"]
C["Agent 2<br/>Session: xyz789"] -->|Code| B
B -->|Check Blocklist| D{Safe?}
D -->|No| E["❌ Reject<br/>Error Response"]
D -->|Yes| F["Job Executor"]
F -->|Engine 1<br/>Workspace: /tmp/abc123| G["MATLAB Engine 1"]
F -->|Engine 2<br/>Workspace: /tmp/xyz789| H["MATLAB Engine 2"]
G -->|clear all| I["Isolated State"]
H -->|clear all| J["Isolated State"]
I -->|Result| K["Agent 1<br/>Only sees own results"]
J -->|Result| L["Agent 2<br/>Only sees own results"]
Key principles:
- Code validation happens before execution — blocklist prevents dangerous operations
- Each session has isolated temp directory — files don't leak between users
- Workspace is cleared between jobs — variables don't persist
- MATLAB path is restored — custom paths don't affect other sessions
- File handles are closed — no state leaks across sessions
The test suite includes security validation tests in tests/test_security.py:
def test_blocks_system_call():
validator = SecurityValidator(config)
result = validator.validate("system('rm -rf /')")
assert not result.is_valid
assert "system" in result.blocked_functions
def test_allows_safe_code():
validator = SecurityValidator(config)
result = validator.validate("x = [1, 2, 3]; y = mean(x);")
assert result.is_valid
assert result.blocked_functions == []
def test_ignores_string_literals():
validator = SecurityValidator(config)
result = validator.validate("msg = 'system is great'; disp(msg);")
assert result.is_valid # "system" inside string is ignoredRun tests with:
pytest tests/test_security.py -v- Configuration reference: See Configuration.md for all security settings
- Architecture: See Architecture.md for system design
- Troubleshooting: See Troubleshooting.md for common security issues