Security considerations and best practices for Allure MCP Server.
Security Model: Subprocess with inherited privileges
- No auth: runs as your user, no network exposure
- Suitable only for local development and Claude Desktop
- No credentials transmitted over network
⚠️ Never use stdio mode with untrusted code
Always set MCP_AUTH_TOKENS in production (preferred for teams — each user gets their own named token):
# Generate a strong random token per user
openssl rand -hex 32
# Set in .env — format: name:token,name:token,...
MCP_AUTH_TOKENS=artem:token1,ivan:token2,qa:token3Each client authenticates with their own token:
curl -H "Authorization: Bearer $MY_TOKEN" http://localhost:3000/sseThe token is checked on every authenticated endpoint (/sse, /messages, /mcp). /health is exempt.
Legacy single-token mode (backward-compatible, treated as user "default"):
MCP_AUTH_TOKEN=your_generated_token_hereNote: Tokens are case-sensitive and checked with the Bearer scheme.
Never expose HTTP over the internet. Always use HTTPS.
Nginx example:
server {
listen 443 ssl http2;
server_name allure-mcp.example.com;
ssl_certificate /etc/letsencrypt/live/allure-mcp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/allure-mcp.example.com/privkey.pem;
# TLS hardening
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Server limit is 32 MiB per message (attachment uploads)
client_max_body_size 32m;
location / {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-Proto $scheme;
}
}allure-mcp.example.com {
reverse_proxy localhost:3000
}Caddy automatically obtains and renews HTTPS certificates.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: allure-mcp
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "32m" # server limit per message
spec:
tls:
- hosts:
- allure-mcp.example.com
secretName: allure-mcp-tls
rules:
- host: allure-mcp.example.com
http:
paths:
- path: /
backend:
service:
name: allure-mcp
port:
number: 3000By default CORS_ALLOWED_ORIGIN is unset — CORS headers are not sent at all. Set it explicitly for browser-based clients (e.g. claude.ai):
Set to specific domains:
# Production
CORS_ALLOWED_ORIGIN=https://claude.ai
# Internal team only
CORS_ALLOWED_ORIGIN=https://allure-mcp.internal.example.comIn Nginx:
location / {
add_header 'Access-Control-Allow-Origin' 'https://claude.ai' always;
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization' always;
}Nginx:
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:3000;
}Caddy:
allure-mcp.example.com {
rate_limit 10r/s burst=20
reverse_proxy localhost:3000
}Allow only trusted IPs:
# Nginx
location / {
allow 10.0.0.0/8; # Internal network
allow 203.0.113.0/24; # Team office
deny all;
}Or at firewall level:
# iptables
sudo iptables -A INPUT -p tcp --dport 3000 -s 10.0.0.0/8 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3000 -j DROP.gitignore already contains:
.env
.env.local
.env.*.secret
Verify before committing:
git status
# Should NOT show .env
git diff --cached | grep -i "token\|password\|secret"
# Should return nothingDevelopment:
- Use
.env(local only) - Never commit to git
- Limit file permissions:
chmod 600 .env
Team/Production:
- Use secret manager: Vault, AWS Secrets Manager, Azure Key Vault
- Example with Vault:
vault write secret/allure-mcp \
base_url="https://allure.example.com" \
token="$(openssl rand -base64 32)"- Read in startup script:
export ALLURE_BASE_URL=$(vault kv get -field=base_url secret/allure-mcp)
export ALLURE_TOKEN=$(vault kv get -field=token secret/allure-mcp)
./bin/server --httpKubernetes:
apiVersion: v1
kind: Secret
metadata:
name: allure-credentials
type: Opaque
stringData:
ALLURE_BASE_URL: https://allure.example.com
ALLURE_TOKEN: your_token_here
MCP_AUTH_TOKENS: alice:token1,bob:token2Mount in pod:
env:
- name: ALLURE_TOKEN
valueFrom:
secretKeyRef:
name: allure-credentials
key: ALLURE_TOKENAllure API Token:
- Generate new token in Allure UI
- Update
ALLURE_TOKENin secret manager - Restart server with new token
- Revoke old token in Allure UI
MCP Auth Tokens:
- Generate new token for the user:
openssl rand -hex 32 - Update
MCP_AUTH_TOKENSin.envor secret manager (replace only that user's entry) - Restart server
- Notify the affected user of their new token
- Other users' tokens remain valid — no team-wide disruption
delete_test_case and bulk_delete_test_cases require explicit user confirmation before executing.
When a delete tool is called over an HTTP/SSE session, the server sends an elicitation request (elicitation/create) to the client. The client displays a confirmation dialog; the user must click Accept before the deletion proceeds. If the user rejects or cancels, the tool returns {"cancelled": true} without touching any data.
Claude → server: tools/call delete_test_case {test_case_id: 42}
server → client: elicitation/create {message: "Permanently delete test case #42?..."}
user → client: [clicks Accept]
client → server: JSON-RPC response {id: ..., result: {action: "accept"}}
server → Allure: DELETE /api/testcase/42
server → Claude: {status: "deleted"}
| Transport | Confirmation | Notes |
|---|---|---|
| Stdio | ✓ Elicitation dialog shown | The request travels over stdout, which the server always drains |
HTTP + SSE (/sse, /messages) |
✓ Elicitation dialog shown | The open /sse stream carries the request |
Streamable HTTP (/mcp) |
✓ Elicitation dialog shown, with an open GET /mcp stream |
A client that never opens the stream cannot be asked |
Client support: confirmation uses MCP elicitation. If the client did not declare the elicitation
capability in initialize, or there is no server→client stream to deliver the request on, delete tools
fail immediately with an error instead of deleting — they never proceed unconfirmed.
Not being asked is not consent, and not a refusal either. When the server cannot reach the user it
returns an error that says why. It does not report it as a cancellation: until 2.6.0 a /mcp client got
{"cancelled": true, "message": "Cancelled."} for every delete, which read as "the user said no" when
in fact nobody had been asked.
A session id is a capability. Whoever presents it drives that session and uses the Allure token stored under it, so:
initializeonPOST /mcpmints the id; it is registered server-side, and any other value is rejected with404(the client is expected toinitializeagain).- The id is bound to the authenticated user who opened it. The same id presented with a different
bearer token is refused with
403, onPOST,GETandDELETE. - Sessions expire 30 minutes after their last request if no stream is open, and
DELETE /mcpends one immediately. Either way the stored Allure token is cleared — as it is when an/sseclient disconnects. - An
X-Allure-Tokensent without a session applies to that one request only. - Background tasks (
list_running_tasks,get_task_status,cancel_task) are scoped to the session that started them; another user's task reads as not found. - Each Allure API token gets its own cookie jar, so one user's
XSRF-TOKENcookie is never sent on another user's request.
Two parameters touch the server's local disk, and both are only enabled in stdio mode, where the server runs on the user's own machine:
| Tool | Parameter | HTTP-mode alternative |
|---|---|---|
upload_test_case_attachment |
file_path — reads a local file |
send the file as content_base64 + file_name |
get_test_case_attachment_content |
save_to — writes a local file |
read content / content_base64 from the response |
In HTTP mode both are refused — otherwise any connected client could upload files from the server's disk (config, tokens, keys) into Allure, or write files onto the server. save_to opens the target with O_EXCL, so even in stdio mode it never overwrites an existing file.
The permission is set from the transport when the server starts, never from the request: HTTP clients control their Mcp-Session-Id header, so a client could claim the "stdio" session id, and a check based on the session id would be bypassable.
One JSON-RPC message is capped at 32 MiB on both transports (raised from 1 MiB so a 20 MiB attachment fits as base64). HTTP requests over the limit get 413 Request Entity Too Large; on stdio an oversized line is answered with a JSON-RPC error and the server keeps serving (it used to terminate). Attachment uploads and downloads are capped at 20 MiB.
A reverse proxy in front of the server must allow the same body size, or attachment uploads fail at the proxy — e.g. nginx client_max_body_size 32m; (nginx defaults to 1m), or the nginx.ingress.kubernetes.io/proxy-body-size: "32m" annotation on a Kubernetes ingress. Don't set it much higher than 32m: the server rejects larger bodies anyway.
The server:
- ✓ Does not store user data
- ✓ Does not log API tokens
- ✓ Does not persist launch history
- ✓ Proxies requests only to Allure TestOps
All state is in Allure TestOps. This server is stateless.
Enable structured logging for audit trails:
LOG_LEVEL=INFO
# Server logs all tool calls and errorsExample log:
{"level":"INFO","msg":"Tool called","tool":"run_allure_launch","project_id":1,"timestamp":"2025-01-15T10:30:00Z","clientIP":"10.0.0.1"}Send logs to centralized system:
- ELK Stack (Elasticsearch + Kibana + Logstash)
- Datadog / New Relic / Splunk
- Loki + Grafana
SOC2:
- ✓ Encrypted secrets (use secret manager)
- ✓ Access logs with audit trail
- ✓ Rate limiting prevents abuse
- ✓ TLS/HTTPS enforced
GDPR:
- ✓ No personal data stored
- ✓ Stateless design
- ✓ Can be deleted without data migration
HIPAA (for healthcare):
- ✓ Encrypt at rest (secret manager)
- ✓ Encrypt in transit (HTTPS)
- ✓ Access logs (centralized monitoring)
- ✓ Run in VPC/private network
-
Authentication
-
MCP_AUTH_TOKENSis set with strong per-user tokens - Tokens are stored in secret manager (Vault, AWS Secrets Manager, etc.)
- Tokens rotated periodically per user
-
-
Network
- HTTPS/TLS enabled
- Reverse proxy (Nginx, Caddy) configured
- Firewall rules restrict access
- CORS_ALLOWED_ORIGIN is specific (not
*) - Rate limiting enabled
- Proxy body size allows 32 MiB (
client_max_body_size 32m) and no more
-
Secrets
-
.envnot committed to git - Allure token stored in secret manager
- MCP auth token stored in secret manager
- File permissions:
chmod 600for.env
-
-
Monitoring
- Structured JSON logging enabled
- Logs sent to centralized system
- Alerting configured for errors/failures
- Access logs reviewed regularly
-
Infrastructure
- Running as non-root user (UID 1000)
- Read-only filesystem where possible
- Resource limits set (CPU, memory)
- Health checks configured
- Backups documented (if applicable)
-
Updates
- Go version is 1.27+
- Dependencies up-to-date (
go mod tidy) - Security advisories checked (
go list -u -m all) - Update process documented
-
Documentation
- Security policy documented
- Incident response plan in place
- Credential rotation schedule documented
- Disaster recovery plan documented
- No secrets in code or commit history
- All tests pass (
make check) - Linting passes (
make lint) - Build is reproducible (
make clean && make build) - CHANGELOG updated
- Security fixes highlighted
- Immediately revoke token in Allure UI
- Generate new token
- Update secret manager with new token
- Restart all server instances
- Review logs for unauthorized access
- Document timeline and root cause
- Generate new token:
openssl rand -base64 32 - Update
.envor secret manager - Restart server
- Notify all team members
- Force clients to update config
- Review logs for unauthorized access
- Stop the server immediately
- Revoke both Allure token and MCP auth token
- Review all logs for unauthorized tool calls
- Redeploy clean version
- Update all credentials
- Notify stakeholders
- Document incident
Do NOT open public GitHub issues for security vulnerabilities.
Email security concerns to: mimojanra@gmail.com
Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Proposed fix (if any)
We will:
- Acknowledge receipt within 24 hours
- Assess severity
- Prepare patch (if applicable)
- Coordinate disclosure with you
- Release patched version
- Credit you (with permission)
Permanently destructive operations (delete_test_case, bulk_delete_test_cases) use MCP elicitation to request explicit user confirmation before proceeding:
- When the tool is called, the server sends an
elicitation/createnotification via SSE. - The client (Claude Desktop / claude.ai) displays a confirmation dialog to the user.
- If the user accepts, the deletion proceeds. If the user rejects or cancels, the tool returns
{"cancelled": true}without touching any data.
This prevents accidental mass deletion and provides an audit trail via the audit log.
Note: Elicitation requires a client that supports it. If the client does not respond within 5 minutes, the server returns a timeout error and does not delete.
All JSON-RPC request handlers are wrapped in a panic recovery middleware. If a handler panics:
- The panic is caught and logged with the full stack trace at
ERRORlevel. - The client receives a JSON-RPC internal error (
-32603) instead of a connection drop. - The server continues running and serving other requests.
This prevents a single bad request from crashing a shared team server.
- OWASP Top 10 — Common vulnerabilities
- Go Security Checklist — Go-specific security
- MCP Security Spec — MCP protocol security
- TLS Best Practices — Mozilla TLS recommendations
Keep security updated. Rotate credentials. Monitor logs. 🔐